天天看点

进程以下的那些事儿进程以下的那些事儿

进程以下的那些事儿

硬件驱动程序的最底层

硬件驱动程序的最底层是什么?是直接操作硬件的部分。直接与硬件交互的方法有2大类:

  • CPU主动发起,读写硬件的寄存器。这里面又分为2种,PIO和MMIO。
    • PIO的方式在x86汇编语言里,就是用io和out汇编语句,读写硬件控制器上的寄存器。
    • MMIO就是硬件会把自己的寄存器映射到主机的内存空间。也就是说,在程序员看来,访问MMIO寄存器就好像访问普通内存一样,可以直接用汇编mov语句操作。在C语言可以用声明为volatile的指针,然后直接用赋值语句就可以读写硬件寄存器。
    • 另外,还有一种是DMA。但它不是独立的,还是要先通过上述2种方法之一,CPU向硬件发出命令。接下来的过程才是,硬件自己读写指定的内存区域。读写过程不需要CPU参与。
  • 另外一种方式是IRQ。它是由硬件主动发起,打断CPU正在执行的程序。CPU会跳到IRQ号对应的中断号所对应的中断服务程序。执行完中断服务程序再返回到原来的程序。

所以,驱动程序往往包括这么几个部分:初始化(设置硬件状态,安装中断服务例程等等)、读操作、写操作、中断服务例程。

中断服务例程

中断服务例程(ISR)如果什么都不做,直接IRET,就可以返回到被打断的程序。(x86有一些中断会有error code,要先弹出error code,这样ESP才会指向返回地址。)

问题是ISR不能什么都不做啊,还是要做事的。这就需要保存原进程的上下文,做完事后,恢复上下文,然后iret,原来的进程才能继续执行。保存和恢复上下文是操作系统要做的事(硬件也会做一部分,写OS的人要搞清楚硬件做了什么,还缺什么)

进程的上下文其实就是程序(CS:EIP)、堆栈(ESP)、数据(DS),还有当前的CPU状态(从软件开发者角度看,主要是通用寄存器的值。其他的硬件负责保存和恢复)。如果有分页,就还涉及到页表的切换。

IRET本身只能负责恢复CS和EIP,而且前提是执行IRET前,ESP必须指向返回地址。

系统调用

操作系统给应用程序提供功能,也是通过中断服务例程。比如linux的系统调用。就是应用程序执行INT 80h。CPU就会跳到80号中断服务例程。这个ISR根据eax的值,决定具体调用哪个c函数(sys_open、sys_read、sys_write等等)。

stackoverflow: int 80h已经过时了

并发

假如没有中断,就没有并发了。

因为没有其他办法可以让正在执行的进程停下来,除非他自己愿意。

而且进程他自己愿意停下来,也是用INT指令,触发软中断(系统调用system call)。机制和硬件中断类似。

所以,没有中断,就没有并发。

时钟

怎么让进程在非自愿的情况下停下来?要用到时钟中断。可编程时钟(Intel 8253 - Programmable Interval Timer)是每个PC上都有硬件,操作系统可以设定让时钟定时发出中断。操作系统在时钟中断的服务例程里,就可以切换进程。

多核

多核处理器每个核(或者说每个hyperthread)都有自己的APIC timer。

硬件中断可以路由到指定的一个或多个核,其他核不受影响。

critical section、关中断、锁

如果程序要进入critical section,它不希望被非自愿地打断,他可以关中断。关中断这只能OS使用,不可以给普通进程用,否则OS怎么保持控制权?关中断只能关1个核的中断,其他核仍然在运作。

linux早期曾经用软件实现过关所有cpu的中断。后来发现这个很慢,就废弃了。多核同步改用锁来实现。

所有锁的实现基础都是spinlock。需要硬件提供支持,比如使用x86的xchg指令。

(待续)

补充:

  • Minix2和Linux其实只有一个内核栈,xv6是每个进程各自有一个内核栈。但是并没有本质不同,对于各个进程的内核态来说,大家还是井水不犯河水,仿佛内核栈是自己的。

    内核执行的时候会被打断,或者会阻塞。它的context也是保存在这个栈上。

  • IRET有时候也会切换堆栈指针(ESP)——就是如果中断发生前,运行的是用户进程,那么就涉及到特权级别转换。从用户态,切换到内核态。为了更安全,防止用户堆栈溢出导致系统崩溃。x86的CPU的硬件设计者,就这么设计:另外设置一个栈来存放用户进程的context和返回地址,由硬件来保证这个堆栈空间是够的。这个栈由TSS指定。
  • 所以,Minix2在切换上下文的时候,有时就涉及了3个栈,内核栈,TSS指定的栈(保存用户进程的context),用户栈。中间那个栈只是用户态和内核态的过渡。

继续阅读