接下来我们讨论系统调用
系统调用是操作系统对上提供服务的接口
它是在怎么实现的
然后我们在这里会说
你提供的功能可以通过函数调用
也可以通过系统调用
这两者之间到底有什么区别
然后他们在实现的时候开销是什么样子
我们在这一部分来进行讨论
首先我们看一个标准的函数调用
比如说我想在这里的
我的应用程序这个大家前面都写过
我有个printf我想在屏幕上输出一份信息
我在这里有一个printf就可以了
背后实际上他怎么做
你写程序设计的时候
看到情况是说他有一个标准C库
C库里提供了printf底下就是printf
来实现功能给你用就行了
但是他背后实际他是转成了操作系统的write
系统调用 然后write系统调用
它的实现是在内核里的
根据你write里头参数不同
会把你print输出的文件
可能输出的屏幕
那把这个图再转换一种形式来看
我们更关心系统调用这个接口的话
你会看到跟这个很相似
但是这是说这是我关心的 上面是应用程序
把我们传统说的应用程序
和你那个系统库都算到这个上面去了
这是我看到系统调用接口
在这个系统调用接口里头
进到内核里头去 由于write
你这个系统调用的编号的不同
我在这选择不同的系统调用编号
这个编号导致我write里头采用它的实现
最后给出结果来 返回回去
那我在屏幕上或者文件里
就写出了我的printf内容了
这是我们说函数调用
那么对于系统调用来说
实际上是说他在底下提供操作系统服务的接口
通常情况下 我们是用C
或者C++高级语言来使用这些的
而你在写程序的时候
通常并不直接去使用系统调用
而把系统调用封装到一个库里头
比如像我们标准C库
应用程序是访问这些库里的库函数来实现的
实际上接见访问系统调用
下面这几个是我们常见的应用编程接口
也就相当于我们系统调用最后封装完了之后
用户用到接口是什么样的
在不同的系统里面是不一样
比如说在Windows它有一个Win32 API
是用Windows操作系统内核的服务来实现Win32 库
那在这个库里用户来使用
而在Unix Linux这一类系列的系列有一个POSIX接口
这个POSIX接口提供了用户需要用到各种各样的库
而这个POSIX接口底下会访问系统调用接口
来实现它的服务
而对于Java虚拟机来说
实际他上面有一个Java API
让虚拟机里的应用程序间接的
会转到我的系统调用接口上来
这是外界使用的情况
他内部实现是什么样
每一个系统调用都有一个系统调用编号
然后依据这个编号不同
我们来使用不同的功能
在这张图我们可以看到这是系统调用进来的入口
从这通过软中断进到系统内核里来
他首先体现为一个中断
中断看是系统调用软中断
这个时候就转到这个地方来
这个地方你那里系统调用的编号
在这编号
在这是体现为一个功能编号
功能编号不同我会选取不同系统调用实现
OK 得到它的结果之后返回回来
这是对外提供的系统调用编号
从用户的角度讲
我们关心只是从这进 这个地方出来
其他部分我不关心了
你不关心的底下内容
但是在这里我们需要关心一个事情
就是我在使用系统调用接口的时候
我需要把我需要的服务告诉内核
这是我要准备参数的地方
你把这信息准备好之后
我就可以系统调用在内部实现
就可以知道我如何做处理
处理完之后把结果返给你
而我们通常在这用的时候
这部分又封装到函数库
而函数库我们在上面用就不用关心了
接下来我们说系统调用和函数调用的不同
那对于所使用的指令来讲
系统调用使用的是int和iret
而函数调用使用的是call ret
这四条指令 他是在指令级是完全不同的
那么他们的区别体现在什么地方
实际功能区别在于函数调用
我们知道你在程序的设计里已经学到过了
我为了调用一个函数
我需要把参数压到堆栈里头去
然后转到相应函数去执行
执行时候从堆栈获取我的参数信息执行
返回的结果放在那里在返回回来
这样的话你在上面的函数调用
就知道我相关的返回的结果
然后用这个结果继续往下执行
而对于系统调用来讲
他由于内核是受保护的
而应用程序是他自己的区域
在这为了保护内核的实现
这个地方内核和用户态的
应用程序之间使用不同的堆栈
所以在这会有一个堆栈的切换
切换之后由于处于内核态
我就可以使用特权指令
这些特权指令所导致的结果
就是我这个时候可以直接对设备进行控制
而你在用户态是不可能进行的
这就好比说我们在银行里头
你可以告诉银行的营业员
我需要从我的某一个帐号里取多少钱
这个取钱操作到银行的内部
他的营业员是可以去直接打开保险柜
打开保险柜取出你所需要的钱
并且在你帐号上做相应的记录
这些记录就好比说你记到你堆栈一样的
他记到他自己内部的堆栈上
以至于说如果说
我们两用同一个堆栈会有什么问题
我们两用同一个堆栈
用户其他代码可以改你堆栈的信息
这对于系统来说是不安全
好比说你从里面取了钱
结果你帐号上的金额并未减少
这银行不干或者反过来说
你把钱存到银行里去了
但是银行帐号上钱并没有增加
这个时候你是不干
基于这种理由我这会有相应切换
银行可以在里进行特权的操作
当然我们在这里只是对int iret call ret
这几条指令的最主要的区别做一个介绍
如果说大家在实现新的系统调用的时候
你可能需要去查详细的区别
这是对X86来讲 它的CPU指令手册
那在手册里头有相应链接
因特尔在不停更新它的手册
你会看到在这里不同的CPU上
这些指令它的实现
有什么样的一致性和相应的区别
你要想在特定平台上正确的运行
最后需要准确了解它的行为
最后我们还有一个问题需要来讨论
就是我在什么时候用中断
用系统调用 什么时候用函数调用
这个时候我们说系统调用
会比你函数调用更安全
但是它也有它的问题 它的开销会比你大
原因是我有一个用户态到内核态的切换
具体有哪些开销 我们在这列出来
首先你有一个切换的引导
这是硬件上需要做的事情
再有一个是你在内核里有另外一个堆栈
如果说第一次调用的时候
这个时候会有内核堆栈的建立
然后我在这里传参数的时候
这个参数的有效性合法性是需要做验证的
切换到内核执行的时候
由于我访问的代码有切换
那么在这种情况下
内核需要访问到用户态的一些信息
这个时候会把做一个地址空间上的映射
这些映射会导致你的缓存会有变化
这个时候你的TLB的内容也会有失效
所有这些都会导致用户态和内核态切换的时候
你的系统调用开销是大于函数调用的