216_系统调用
# 1.1_6_系统调用
各位同学大家好,在这个小节当中,我们会学习系统调用相关的知识点
首先我们会介绍系统调用的基本概念它有什么作用,之后我们会介绍一个比较容易混淆的知识点,系统调用和我们正常所说的库函数到底有什么区别?最后我们会从一些细节的角度来看一下系统调用的背后到底做了一些什么事情。那么我们会按照从上到下的顺序依次讲解,
# 什么是系统调用
首先来看一下什么是系统调用。其实在之前的小节的讲解当中,我们已经提到过系统调用这个概念,操作系统它作为用户和硬件之间的接口,需要对上层,对上面的这些层次提供一些简单应用的服务包括,命令接口和程序接口,那么程序接口就是由一些系统调用组成,所以说操作系统对上层的服务分为这样的两种,一部分是面向用户的叫命令接口,然后另外一种叫做程序接口,这是面向应用程序或者说面向编程人员的。那么程序接口就是由一组系统调用来组成,
我们其实可以简单的把这个系统调用理解为一种可以供应用程序,可以供应用程序来调用的一种特殊的函数,具体和我们普通的函数有什么区别,我在之后会进行讲解。那么,这些应用程序可以通过发出系统调用来请求获得操作系统的服务。
那么我们来思考一个问题,为什么要设计系统调用这样的功能这样的机制呢?首先我们来考虑一个大家都遇到过的场景,如果说我们去学校打印店打印论文的话,当我们按下打印按钮之后,打印机就会开始工作。如果说我们的论文打印到一半的时候,另外一个同学也开始使用这个电脑,然后按同样也是按下了打印按钮,开始打印他自己的论文,但是最后我们的两个人的论文并没有混杂在一起,都是按照顺序来依次打印的。
假如说我们各个这种用户进程可以随意的使用打印机的话,又会发生什么情况呢?如果说各个进用户进程都可以随意的使用打印机,那么就会导致我们的论文打印到一半。之后,另外同学打开的 word 进程同样也发出了打印这样的命令,然后两个进程就会争相的开始争抢着使用打印机,这样的结果就会导致我们自己的论文和那个同学的论文最后打印出来页面是混杂在一起的,这当然是非常糟糕的结果。
所以为了避免这些各个进程,随意的使用这些系统资源,因此操作系统就规定,所有的这些用用户进程,想要使用这些系统资源的时候,都一定只能通过系统调用这样的方式来向操作系统请求服务,最后会由操作系统来统一的对这些各个系统调用的请求进行协调管理,进行处理。就像我们刚开始提到的一样,它会让这些请求有条不紊的顺序的来进行,所以这就是系统调用的一个本质的目的。它应用程序通过系统调用来请求操作系统的服务,然后由于操作系统当中的这些各种各样的资源,他们都不应该不应该被用用户进程任意的使用,所以它都需要由操作系统来统一的掌管。因此用户程序就只能通过系统调用这样的方式来向操作系统发出服务的请求,然后由操作系统代为完成,这样的话我们就可以完保证系统的稳定性和安全性,防止用户非法操作。所以这就是系统调用相关的概念,还有它的作用。
# 系统调用的分类
那么了解了系统调用的概念之后,我们再来看一下系统调用可以有哪些分类。按照王道书上给出的这种分类,按照功能分类的方式可以分为 5 个大类,大家简单的了解有个印象就可以了。那么我们需要注意的是系统调用它其实包括像什么设备的请求释放,还有文件的创建删除删除,还有进程的什么创建阻塞,这些操作其实肯定是都需要一些权限很高的特权指令才可以完成的。因为它都涉及到一些像硬件设备,还有进程的管理之类的一些操作,所以由于我们需要特权指令才能完成这些操作。因此系统调用相关的处理,就是对这些事情相关的处理,肯定是需要在核心态下才能够完成的。因为只有在核心态下,我们才可以使用这些相应的特权指令。那么这个知识点现在只需要有个印象就可以,后面还会不断的强化,让大家再加深记忆。
# 系统调用和库函数的区别
所以这就是操作系统调用的一个分类。那么在了解了什么是系统调用之后,我们再来看一个比较容易混淆的知识点。还记得我们在这个小节刚开始的时候提到过一句话,就是系统调用可以理解为一种可以让那些应用程序来使用的一些特殊的函数,那么它和我们平时所编程所使用的那些普通的库函数有什么区别?
其实操作系统它对它的上层会提供系统调用这样的一个接口,那么应用程序理论上来说是可以通过汇编语言的形式来直接使用系统调用功能的。但是由于现代的这些软件工程的开发过程当中,大家其实使的使用的都是一些高级语言,像什么 c 语言 Java 这样的语言。
所以在现代的这种编程当中,我们一般来说都是在程序当中是使用这些高级语言提供的库函数,但是最后这些库函数的底层其实会为我们封装一些相应的系统调用功能,只不过这些细节被库函数给屏蔽了,所以我们使用这些库函数是很方便的,不需要再用那种复杂的方式进行系统调用
而这个系统调用当然也会使用系统调用相关的这些处理程序,当然也会使用操作系统内核的其他一些功能来完成相应的一些操作。
在很多操作系统的体系结构当中,系统调用是被划分为操作系统内核的功能,所以系统调用相关的处理当然是需要运行在核心态,是要在核心态下才可以进行的,这也是我们在上一张 PPT 最后强调过的一个知识点。
那么通过刚才的分析,其实我们可以得到这样的结论,如果我们按照从下到上的这种顺序来看的话,最下层我们应该是有一个裸机,在裸上机面我们会加设一层操作系统,操作系统会用系统调用的方式向上层提供一些简单应用的服务,然后在这个系统调用当中,它会屏蔽一些对硬件操作的细节,使它上层的这些用户更方便的能够使用。
那么在操作系统上面,如果我们使用的一些高级的编程语言,那么这些高级编程语言会提供一些库函数,在这个库函数当中又会对操作系统提供的这些系统调用功能进行进一步的封装,然后再隐藏一些不必要的细节,这样的话上面的这些应用程序就可以更方便的来直接通过库函数,然后来间接的使用操作系统的系统调用功能。
所以其实这就是库函数和系统调用的一个区别,我们需要注意的一个点是,有的库函数它是会使用到系统调用功能的,而有的是不会使用到系统调用功能的。比如说像创建一个文件读写文件,这些操作由于涉及到对系统中的共享资源的一个操作所以,这种操作肯定是需要通过系统调用才可以实现。因此如果说某一个库函数它实现了创建一个文件这样的功能,那么这个库函数背后肯定是使用了一些系统调用。
而有的库函数比如说像取绝对值这样简单的算术运算,这种运算的话就不需要请求操作系统的服务,也可以很简单的在用户态下就完成,所以这种库函数就不需要使用系统调用的功能,这个地方大家稍微注意一下。
# 系统调用背后的功能
那么讲了这些之后,我们再来看一下系统调用背后到底发生了一些什么事情。在我们用高级语言编写的代码当中,比如说 c 语言或者任何一个语言提供的叫 write 这样的一个函数,在 write 这个函数的内部是使用了系统调用相关的一些功能,那么高级我们用高级语言写的代码经过编译之后会形成一些对等的等价的汇编语言,只不过汇编语言是更低级的语言,所以像这种 write 这样一个简单的库函数,它经过编译之后可能会对应这样一系列的汇编语言指令。那么像上面的这条指令,其实就是一个把我们的系统调用参数放入到寄存器这样的一个指令。然后后面的 int 指令其实就是我们之前提到过很多次的陷入指令,当执行了陷入指令之后, CPU 的运行 CPU 的控制权就会交给操作系统,之后就是由操作系统的系统调用相关的处理程序来运行,进行根据我们用户程序提供的这些参数,进行相应的系统调用的处理。
那么我们再来具体的细分一下,从高级语言视角来看,其实我们写的就是一行行的代码,然后其中的某一行代码可能就是调用了一个库函数叫做 write,这个库函数就是像一个文件当中写入 ABC 这样几个简单的字符,但是由于它涉及到对文件这种系统的共享资源的一个操作,所以它背后函库函数背后肯定是使用了系统调用,只不过他把系统调用相关的那些复杂的细节为我们屏蔽了,我们只需要通过简单的这样一行代码就可以实现,其实背后很多复杂的功能。
那么经过编译之后,这样一个简单的语句,它可能会对应一系列的汇编语言的代码,汇编语言的指令,比如说一些前期的处理的指令,还有后续处理的一些指令,当然我们最终最需要关注的是中间这两条指令,像 movl 指令就是我们刚开始之前提到的,把 ABC 我们需要写入到文件的这些用户程序提供的参数,把它压入到某个通用寄存器当中,之后再来执行陷入指令。Int 指令当然陷入指令还可以叫做称作防管指令,trap 指令它的名字很多,大家需要自己记忆一下,那么执行完陷入指令之后, CPU 的使用控制权就会交给操作系统,那么之后操作系统就会处理这个系统调用相关的一系列的代码,对这个系统调用的请求进行响应。
当然这一系列的代码我们之前也强调过,它是运行在核心态的,在这些处理都结束之后,又会重新返回用户的程序,然后继续往下执行后续的这些指令。
那么这个地方有一个问题,既然我们的系统调用有多种多样的,比如说写文件操作,还有创建文件的操作,这两种操作的系统调用肯定对应的不是同一个处理的函数,那么操作系统是怎么分辨具体需要运行哪一个处理函数的呢?其实就是通过 int 指令的参数,这用了一个 x 代替这个 x 其实用来指明系统调用号,比如说像 Linux 系统里边的一些系统调用,如果说 x 我们传入的是二,那么就是说让操作系统为我们创建一个进程,如果说我们传入的是 3,那么就是说让操作系统为我们执行一个读操作,如果是 4 就是一个写操作,以此类推,所以其实操作系统来判断到底需要执行哪一个相应的处理程序,它是根据 int 陷入指令对应的参数来进行选择的。而这个地方的 int 其实不是我们平时写程序时候的 integer,不是整数的意思,它其实是 interrupt 也就是中断的缩写。因为这个系统调用陷入指令,其实它就是引会引发一个内中断,然后从而使 CPU 从用户台进入到核心态,所以它是 interrupt 的一个缩写
那么我们再来简要的捋一遍。在系统调用的背后,首先需要做的是把传入系统调用的参数,用一个相应的一系列的指令,把这些参数放入到相应的通用寄存器当中。当这些参数放好之后,我们就需要执行陷入指令或者说访管指令,trap 指令,这个指令其实是运行在用户态的,但是当运行完这个指令之后,系统会立即进入到核心态,接下来由操作系统获得 CPU 的控制权,从而开始执行系统调用相应的一系列的服务程序。当这个服务程序结束之后,最后才返回用户程序,然后继续往下执行,这就是系统调用的一个大体的过程。
传递系统调用参数 → 执行陷入指令(用户态)→ 执行系统调用相应服务程序(核心态)→ 返回用户程序
注意:1.陷入指令是在用户态执行的,执行陷入指令之后立即引发一个内中断,从而 CU 进入核心态
那么我们需要注意的就是刚才提到的这个点先入指令它是在用户态执行的,但是执行完陷入指陷入指令之后,会立即引发一个内中断一个 interrupt,之后会让 CPU 从用户态立即进入到核心态,然后进行后续的处理。
那么在理解了这一点之后,第二个结论也不难理解了,发出系统调用请求它是在用户态下进行的,但是最后对系统调用相应的处理是需要切换到核心态下才可以进行。
第三个点,嵌入指令的作用是让 CPU 从用户态进入到核心态,所以陷入指令其实它是唯一一个只能在用户态下执行,而不可以在核心态下执行的指令。一般来说,我们知道核心态下是既可以执行非特权指令,也可以执行特权指令,但是用户台下只能执行非特权指令,只不过核心态下,只有唯一一个不能执行的指令,就是陷入指令,也就是 int ,trap 指令。
# 小结
好的,那么我们再来回顾一下我们小节的内容,我们刚开始讲了什么是系统调用,它有什么作用,经常作为考点的是这个点,就是系统调用它会使处理器 CPU,从用户态进入到核心态,因为其实系统经过刚才的分析,我们已经知道系统调用的背后其实是会执行一个 int 指令或者说是陷入指令,这个陷入指令会让会发出一个内中断,从而使 CPU 从用户台进入到核心态。
然后另外我们还讲了系统调用的分类,但是这些分类大家只需要有一个印象能了解就可以了。据如果说在题目当中让我们判断什么操作需要用系统调用来完成,其实我们并不需要死记硬背,我们只需要知道,凡是和这些共享资源相关的操作,还有一些会直接影响到其他进程的操作,这些操作由于会对其他进程会对系统的安全性造成影响,因此不可能让用户进程很随意的就进行这样的操作,因此这些操作只能通过系统调用的方式来请求操作系统介入,然后由操作系统代为完成,所以只需要掌握本质特点,我们其实就可以用逻辑推理的方式来进行判断,哪些操作需要用系统调用来进行完成,哪些操作是不需要系统调用就可以完成的。
那么之后我们还讲了系统调用和库函数的区别,这个点其实并不难理解,但是确实也多次在选择题当中出现这个地方,大家再经过后面的做题再来进行巩固一下。最后我们讲了系统调用背后的这一系列的过程,我们需要重点关注的是系统调用,它是发生在用户态的,但是对系统调用的处理是发生在核心态的,这一点是经常在选择题中进行考察的一个大家容易混淆的知识点。然后我们还需要知道执行了陷入指令之后会产生一个内中断,使处理器从用户态进入到核心态,那么陷入指令还有别的一些名字,trap 指令、访管指令,这些名字大家也都需要认识,因为在考试时候他可能会任何一个名字都有可能出现。
好的,那么以上就是小节的全部内容。