OS启动流程
中断、异常、系统调用
- 中断:
由外设或硬件触发,如read系统调用后,系统发出读磁盘的操作,当磁盘数据准备好后,向OS发出一个异步通知消息,即中断。一般来说,中断是异步的方式。
- 具体某个的中断会有一个中断ID,比如1号中断,10号中断,这是OS识别具体产生了哪个中断的标识。
- 而在应用程序部分(或者说软件部分),程序会保存中断发生前一时刻的执行现场,主要是保存一些寄存器中的值,然后去转到中断的服务例程执行中断,
- 然后OS再恢复之前保存的处理状态,就好像应用程序不知道中间某处发生了中断,所以说,中断是对应用程序
透明的
。
- 异常:由应用程序触发,是一种非预期的事件,如0除事件。
异常和中断有点类似又有本质区别,本质区别就是触发的对象不同,异常是由应用程序触发的,中断是由外设触发的。一般来说,异常是同步的方式。
- 同样,某个具体的异常也有异常编号ID,异常产生的前一刻也会保存异常现场,然后转到异常处理,
- 处理的时候和中断服务例程不太一样,有可能是因为因为应用程序本身的错误(如出现了0除事件),这样一来,OS怎么也不可能解决异常,于是会将该异常的程序kill掉;还有一种情况就是由于OS本身的问题,导致某些应该正常执行的程序发生了异常,OS会尝试解决异常,并重新执行产生异常的指令(也就是我们的应用程序啦)。
- 恢复现场
- 系统调用:由应用程序触发,应用程序向OS请求某个服务,如read操作。系统调用是异步或同步的方式,主要是要看我们是从哪个出发点看待的。
例如一个应用程序中的printf()
代码,会转到write
系统调用(syscall),程序访问syscall主要是通过API的方式,例如在Windows上有WIN32 API
,而在UNIX、linux和mac os上,是通过POSIX API
来访问syscall。
用户态、内核态
用户态和内核态都是针对CPU运行状态来说的,在用户态下,CPU无法执行一些特权指令,而在内核态下是可以的。
系统调用和函数调用的区别:
函数调用、系统调用
函数调用是在用户态下的调用,堆栈也只涉及到用户堆栈下的切换;而系统调用不太一样,系统和用户都有一个自己的堆栈,所以会存在堆栈切换的开销,跨越了OS的边界,但是比较安全的。
举个栗子,你写了一个函数f1()
和f2()
,两者之前存在一个调用,这样其实你只是在你自己的堆栈下切换,这样切换是比较快的。而如果你写了一个f3()
,其中写了一个printf()
函数,这样就会涉及系统和用户的堆栈切换了。
系统调用指令:INT
和IRET
,涉及堆栈切换和特权级的切换
函数调用指令:CALL
和RET
,常规调用没有堆栈切换
跨越OS边界带来的开销:
- 需要建立中断/异常/syscall调用好与对应服务例程映射关系表。
- 需要建立系统内核堆栈
- 通过API访问syscall时,需要验证参数
- 内核态执行完成返回到用户态时,需要把一些参数复制到用户空间,因为用户空间和内核空间是分开的。
- 内核态独立地址空间TLB