我用python试图检测键盘事件,用的方法是在while循环中放置了pygame中的一个获取事件的函数event.get(),结果就是cpu始终占用100%。但是操作系统和其他语言(如C#)的事件监听函数基本不占cpu,它们是如何做到的?是牺牲了事件响应的实时性吗?
回复内容:
操作系统的事件监听是靠与CPU协作完成的,这一机制叫作硬件中断(Interrupt)。
正常情况下,CPU按照它内部程序计数器(Program counter)所指的指令(Instruction)顺序执行,或者如果所指的指令是跳转类指令,比如x86类的CALL或者JMP,跳转到指令所指的地方继续顺序执行。
只有四种情况,CPU不会顺序执行,包括上面所说的硬件中断,Trap(Trap (computing)),Fault,和Abort。
硬件中断时,CPU会转而执行硬件中断处理函数。Trap则是被x86的INT指令触发,它是内核系统调用(System call)的实现机制,它会使得CPU跳转到操作系统内核的系统函数(Exception Table)。Fault则是除0报错,缺页中断(Page fault),甚至内存保护错误实现所依靠的。
不会顺序执行的意思是,CPU会马上放下手头的指令,转而去处理这四种情况,在把这四种情况处理完之后再依靠CPU跳回到刚才的指令继续处理。这就是为什么即使单核CPU在100%占用处理另一个进程任务时,只要你的进程优先级够高,也能在键盘事件本文来源gaodai#ma#com搞*!代#%^码网5发生时让CPU停下而转而执行你的进程。
为什么监听键盘事件可以不占100%CPU,甚至可以0占用CPU呢?因为即使在CPU完全停止而不执行指令的状态(Idle (CPU)),硬件中断仍然会启动CPU开始执行中断处理函数(Interrupt handler)。
特别的,当你按下Ctrl+C时,键盘硬件会给CPU一个硬件中断,其中包含一个异常号(Exception Number),CPU拿到这个异常号马上调用之前由操作系统内核(Kernel (operating system))注册的中断处理函数,中断处理函数会调用内核中的键盘驱动,键盘驱动调用显示终端(Computer terminal)驱动在终端上显示”^C”,同时通过调度器来唤醒相关的进程(Process (computing))。*nix还会生成信号(Unix signal)发送到当前的进程组(Process group),这些进程则会执行SIGINT的信号处理函数,这时我们的CPU就从内核模式(Kernel Mode)转到了用户模式(User mode)。
如果在这一事件发生之前你的进程使用了阻塞式(Blocking)的等待键盘响应,并且用户并没有什么程序执行。那么CPU大多数时候会被内核代码中的HLT指令转到空闲状态,只会被时钟的硬件中断周期性唤醒看看调度器有没有什么事情可以做。当键盘时间发生时,你的进程就会被唤醒,从等待函数中返回,继续执行之后的代码。你也将在电脑上屏幕上看到CPU仍然是0占用。
参考: Computer Systems: A Programmer’s Perspective (3rd Edition)
这个问题涉及到系统处理事件的两种方法, 容我来组织一下语言。一种是interrupt,由CPU的中断机制来提醒操作系统发生了什么;另一种需要操作系统主动polling, 不停检查某应用程序是否有事件发生。操作系统一般是通过CPU中断机制来对应用程序提供服务的,原因很明显,使用第二种方法操作系统会不停地占有CPU资源来检查是否有事件发生。而且恰恰第二种方法实时性较差。想象用户程序要被timer interrupt打段之后才能进入kernel,然后kernel处理一些任务之后才会逐个poll所有进程的状态,这中间的latency肯定要比直接触发中断要大很多。