前言
本文从计算机组成,操作系统角度结合个人经验分析系统调用,帮助你理解系统调用。
一、什么是系统调用
系统调用指的是对操作系统暴露的API的调用。至于为什么调用OS的API,是因为OS提供了一个应用进程运行的基础,大多数程序离开操作系统是无法运行的。 系统调用是怎么产生的呢?在编程语言层面,如果是需要操作系统支持的函数,其底层实现上会包装OS的API,最终以编程语言的普通函数一样暴露给开发者。因此,最终是否发生系统调用与进程要完成的任务有关。
在系统调用前,我们得知道OS暴露了哪些API,是什么形式的,需要什么参数,返回值是什么样的,正常是什么样,异常是什么样。这就是咱们常说的API文档。理论上说,知道这些之后,我们就可以Happy地上代码了。
但今天我不想跟大家聊怎么调用,我们要聊发起调用之后,发生了什么事情。在开始之前,我们得了解下OS,以及OS是怎么暴露API的。因此,正式开始前,还得从OS说起。
-
CentOS是微内核操作系统,当发生系统调用时,整个过程会设计内核模式和用户模式。那两个模式的区别和来源是什么呢?其实现代计算机为了安全,其主板上有一个存储器来标识当前运行的是内核代码还是用户代码,对应的就是内核模式和用户模式。同时,该存储器在CentOS启动后由OS牢牢掌控,用户代码是无权操作的,从而保证内核代码也就是OS对整个硬件有绝对控制权。 -
CentOS是中断驱动的。当然在组成原理中,中断包括软中断和硬中断。硬中断就是CPU的一个脚丫子上的电压变化,而软中断则是内存中一个数据的修改。 -
函数调用过程最底层无非是CPU上的指令寄存器的数值指向了目标函数,并用该函数相关的指令去加工内存中我们提供的参数。但,这个是编程语言下普通的函数调用。而对OS API的调用会导致进程进入内核模式。内核一般是C/C++编写的,咱们的编程语言可能是其他的,不同语言下,导致函数的表示,参数的标识都是不同的,所以直接调不行。那就需要一个共有的东西来完成参数的传递,你可以理解为数组。数组的本质就是内存中的一块区域,只要OS和本地编程语言都可以访问到就OK。剩下的就是怎么触发的问题。考虑到不能拘泥于某种编程语言,因此这种触发方式必须是通用的。 -
其实,CentOS是通过软中断来进行系统调用的,有点类似于设计模式中的发布订阅,通过topic解耦和OS和编程语言。此外,软中断也是通用的,任何编程语言都可以比较容易地实现。
二、系统调用过程
- 编程语言的基础运行时产生软中断,说明中断号和相关参数,写入软中断待处理队列中;
- CentOS下的ksoftIrqd进程做软中断处理,校验中断号是否正确,参数是否正确,以及权限校验,校验完成后执行中断号对应的系统函数;
- 函数调用完成后,将返回值复制到用户进程中;
- 重置执行位置到函数调用前的位置;
- 调用结束;
三、与非系统调用的区别
- 非系统调用没有创建中断对象,写入中断队列过程;
- 参数在本地内存环境中,一般不会发生复制;
- 在调用开始时,CPU进入从用户上下文切换到内核上下文;调用完成后, CPU从内核上下文恢复到用户上下文;
总之,比非系统调用,增加了软中断排队和软中断处理和2次上下文切换。
四、上下文切换过程
- 什么是上下文? 简单说就是当前程序的执行状态。这个状态包括寄存器,栈和内存中的数据。具体的切换过程就可以理解为记忆和恢复。记忆过程就是把当前进程相关的寄存器,栈和内存中的数据,最终都保存在内存中。当然,如果内存不够,则交换到外存中。恢复过程当该进程被调度程序赋予CPU时间片时,恢复寄存器,栈和内存中的数据。显然对同一个线程连续执行记忆和恢复过程没有意义,一般是记忆线程A,恢复线程B。然后记忆线程B恢复线程A。
- 这里延伸一个问题?Java中线程是抢占式调度的,如果某个线程死循环了,似乎永久占有了CPU。CPU的分时机制是怎么解决该问题的。这个得先说说CentOS是怎么管理进程的。每个进程都关联了一个PCB(Process Control Block),其本身是一个数据对象,其中包含1个重要信息,就是时间片大小。但时间片不是以ms为单位,而是以定时器的次数为单位。当操作系统将控制权交给用户前,会设置定时器的计数,这样时间片=计数器*定时器的周期。定时器以固定的频率产生中断,然后CPU控制权从用户进程转移到OS。OS对PCB中的计数器做-1操作。当计数器为0,意味着时间片结束,操作系统可以决定下一步要切换上下文还是继续分配时间片。从而保证OS牢牢掌握控制权。
总结
本文和你详细介绍了系统调用的过程,和非系统调用的区别,帮助你理解进程的内核模式和用户模式,以及切换过程的一些细节,帮助你更好地理解实际工作中的性能评估和优化。
|