操作系统运行中,种有各种事情打断和切换,通过系统陷阱来实现:
简单来说就和应用层的回调函数一样, 只不过这些处理是在操作系统内核里面,系统初始化时就填充好了IDT(interrupt dispatch table), 当中断发生时, 系统会根据中断类型,去调用对应的ISR(interrupt service routine). 当中断发生时,操作系统内核会保存足够多的信息(陷阱帧), 这样系统处理完中断之后可以回到原来的地方继续执行。看起来好像和我们应用层的函数调用一样, 但是他们的实现是完全不同的, 函数调用是同一线程, 通过堆栈来实现的;中断处理涉及到线程切换,要保存线程的执行环境。
中断处理流程图:
IRQL(interrupt request level) - 中断请求级别:
每种中断都有自己的优先级, 高优先级的中断可以打断低优先级的中断的处理, 反之则不行。
对应用层开发人员来说,最重要的是最下面的3个软件中断级别:
(1) DPC/dispatch: 系统的线程调度器工作在这一级别, 它可以决定挂起和调度哪个线程。DPC(deferred procedure call)主要是给驱动程序用的,每个处理器都有一个DPC列表, 处理器在降级当前IRQL之前, 会先把DPC列表里的事情处理完。
(2) APC: APC即asynchronous procedure call, 每个线程都有一个APC队列, 我们可以往该队列中加入自己要处理的事情(QueueUserAPC), 然后系统会在当该线程进入alertable wait state时调用我们加入的操作,我们可以通过SleepEx/WaitForMultipleObjectEx让该线程进入这种等待状态。我们常见的OVERLAPPED ReadFileEx/WriteFileEx就是通过这种方式实现的。
(3) Passive/Low: 这个实际上不是一个IRQL, 我们普通的用户代码就运行在这个级别,所以它可以随时被打断。
系统服务调用:
在Pentium II 之前, 0x2e中断进入系统内核(eax传递服务号), Pentium II 之后处理器提供了Sysenter/Sysexit指令直接进出内核。
内核对GUI线程和非GUI线程有不同的SSDT(System Services Descriptor Table), GUI线程服务分发表更完整,包含了窗口和GDI相关部分, 在第一调用user32/GDI相关的API时系统会修改该线程系统服务表的指针。
内核对象结构:
应用层打交道的内核对象实际上是执行体对象, 它是有一个或多个真正的内核对象组成的。内核对象由对象头和对象体组成, 对象头对每种对象包含一致的结构, 对象体则每种对象各不相同。对象头里的对象类型(object type), 对同种类型的对象,指向相同的地址,表明了该种对象的属性和方法。对象管理器通过对象头来管理内核对象。
内核对象同步原理:
应用层我们经常调用WaitForSingle(Multiple)Object, 操作系统内部是怎么实现的?
每个可同步的对象, 内部都有一个分发器头(dispatch_header), 分发器头包含了对象类型,状态,以及等待该对象的线程列表;每个处于等待状态的线程, 都有一个等待块列表(wait block list), 每个等待块代表一个等待线程。这样就很好理解了,当我们把一个同步对象设置成有信号状态时, 系统沿着分发器头的等待线程列表遍历,找到可激活的线程,将它转入就绪状态参与线程调度。
Critical Section是如何实现用户态等待的?
我们知道CRITICAL_SECTION是同一进程内我们最常用的同步机制, 号称不用转入内核,以高效闻名, 它是怎么实现的?
单纯在用户态等待, 我们只能死循环,不停的检测, 也就是所谓的自旋锁(SpinLock), critical section如果用这种方式实现,何来高效可言。
实际上critical section的大概实现是这样的: 它内部包含一个标志位以及一个event object, 进入critical section时首先尝试设置标志位,如果设置成功,表示成功获得资源;如果标志位已经被设置, 则等待event事件。其中标志位的设置是用类似interlockedexchange这样的原子API操作的。这样只要没有资源竞争,大部分情况下都能满足我们的高效需求, 如果有资源竞争,实际上还是会转入内核态挂起线程。
posted on 2016-03-22 22:48
Richard Wei 阅读(2168)
评论(1) 编辑 收藏 引用 所属分类:
windows desktop