老规矩,如果能回答上这些问题,就没必要看这篇文章了,做些更有意义的事情吧。
- 系统调用和普通函数有何区别?
- 什么是内核态 和 用户态?
- 操作系统如何让CPU切换状态?
- 内中断、外中断、软中断、硬中断是什么意思?
- 库函数和系统调用有何区别?
#include <fcntl.h>
int open(const char *path, int oflag, .../* mode_t mode */)
这是一个系统调用,看起来跟我们写的C函数签名一模一样,由此可以得出结论,系统调用就是一个函数。
这个结论是不是有点肤浅,哈哈。我们来看看这个结论是否靠谱。
这个“函数”与我们写的函数有什么差异呢?
主要差异就体现在系统调用过程中CPU发生了由用户态->内核态->用户态的状态转换,而我们应用程序写的函数自始至终都是用户态运行。
下面我们就来解密这个过程。
“内核态” 和 “用户态”
在CPU设计生产时就划分了特权指令和非特权指令,因此CPU执行一条指令前就能判断出其类型。
一些危险指令只能由操作系统来执行,例如清空内存指令,如果让某个用户程序执行那么会影响整个系统。所以CPU需要一种机制来避免这种事情发生,因此划分了这两种状态:内核态、用户态。
CPU 中有个寄存器叫 程序状态字寄存器(PSW),其中有二进制位1表示“内核态”、0表示“用户态”(有些CPU可能相反)。
- 处于内核态时,说明此时正在运行的是内核程序,此时可以执行特权指令
- 处于用户态时,说明此时正在运行的是用户程序,此时只能执行非特权指令
别名:
内核态=核心态=管态;
用户态=目态
操作系统如何让CPU切换状态?
我们通过一个案例来说明CPU切换状态的几种情况。
电脑开机后,CPU处于内核态。需要启动应用程序时,操作系统会主动出让CPU,让应用程序在CPU上执行。在出让CPU前先执行一条特权指令把PSW置为用户态,之后应用程序就能正常执行了。假如有黑客让应用程序中包含一条只有在CPU内核态才能运行的特权指令,就会触发中断(由CPU自己发出中断信号),此时操作系统收到中断信号接管CPU,开始执行操作系统内核的中断处理程序(这种情况中断程序通常会直接kill用户进程)。
总结一下这个案例中的状态切换:
- 内核态 --> 用户态:执行一条特权指令把PSW置为用户态。
- 用户态 --> 内核态:用户态执行特权指令引发中断,导致PSW置为内核态。
引发状态切换的有特权指令和中断,特权指令我们知道是CPU的一些危险指令集,那中断是什么呢?
中断和异常
1. 内中断(也称为”异常“或软中断)
与CPU当前执行的指令有关,中断信号来源于CPU内部。
例如,
- 应用程序内非法执行一条特权指令,CPU正处于用户态(不允许执行特权指令),便会触发内中断。
- 正常指令也会导致内中断,如除法指令遇到除数为零。
- 还有一种情况是应用程序需要请求操作系统内核的服务,此时会执行一条特殊的指令陷入指令(也称为“trap指令”或“访管指令”),陷入指令是一个普通指令,并不是特权指令。系统调用就是陷入指令实现的。
2. 外中断(也称为硬中断)
与CPU当前执行的指令无关,中断信号来源于CPU外部。
CPU在执行完每条指令后,都会例行检查有没有外中断信号。
例如,
- 时钟中断,由时钟部件发出来的中断信号,当CPU检测到有中断信号时,会暂停正在运行的用户程序,转而执行时钟中断的内核程序,这段程序一般就是进程调度程序。并发就是基于此实现的。
- IO中断,例如打印机完成打印任务后,触发IO中断信号,CPU就会执行操作系统内核的IO中断处理程序。
无论是内中断还是外中断,让操作系统夺回CPU使用权的唯一途径是”中断“,使CPU由用户态转变为内核态。如果没有中断机制,就无法切回内核程序,进程调度无法执行也就没有并发技术了。
下面的图总结了中断的几种情况:
3. 中断机制的基本原理
如图不同的中断信号,需要用不同的中断处理程序来处理。 当CPU检测到中断信号后,会根据中断信号的类型去查询“中断向量表”,以此来找到相应的中断处理程序在内存中的存放位置。
铺垫完这部分知识,说回系统调用。
现在我们就明白了之前讨论的,当发生系统调用时CPU由用户态->内核态->用户态的状态转换的原理。
我们拆成两个步骤来介绍进程与系统调用的细节:
- 用户态->内核态
当函数执行系统调用时,首先执行传参指令将程序参数放到CPU寄存器上,例如系统调用的类型(如fork)以及其他参数。CPU陷入指令会导致中断,因此内核接管CPU。那么此时执行这个函数的进程就被阻塞了,紧接着CPU根据中断向量表执行中断处理程序,发现是系统调用就执行这个系统调用相关的代码指令。 - 内核态->用户态
执行完系统调用相关的代码指令,将结果写入内存地址,以备进程恢复后读取结果。再之后执行特权指令把CPU设置为用户态,进程恢复读取系统调用的结果继续执行函数内其它指令。
总结一下:
应用程序可以通过系统调用来请求获得操作系统内核的服务,Java程序员可以理解为操作系统提供的API。
库函数的执行过程与我们自己写的函数并无不同,它们是由标准组织定义实现,方便开发者使用。但是因为库函数需要考虑各种边界情况,实际性能未必有我们自己实现的性能好,所以不要盲目认为库函数性能一定很强。对于Java程序员可以对标 Java标准库 理解。
下面这个图能更直观的表现出库函数、系统调用、用户程序之间的关系:
如下图就是操作系统提供的各种共享资源 ,用户进程想要使用共享资源,只能通过系统调用向操作系统内核发出请求。内核会对各个请求进行协调处理,这样可以保证系统的稳定性和安全性,防止用户进行非法操作。
这篇文章是学习《王道考研》的操作系统课整理而来。