区别
-
系统调用的实现在内核中,属于内核空间,系统调用的实现是在操作系统中,在编译操作系统的时候,就实现了该方法,所以用户是不可能去执行操作系统的代码,因此它的调用执行和库函数的调用执行是不一样的。
-
库函数的实现在函数库中,属于用户空间,静态库,共享库,其实和我们自己在代码中实现一个方法去调用它,本质上没有什么区别,方法的实现均由用户自己完成。
上一篇博客中,我们也提到使用帮助手册查看的时候,不同的代号代表不同的含义
比如:fopen是库函数,而open是系统调用
凡是要对硬件或者操作系统底层做操作的话,就是系统调用。
有些功能,用户自己无法完成,需要操作系统来完成(往磁盘写入数据,操作PCB等)。
以open()为例,系统调用的执行过程如下:
在内核空间,对系统实现的方法进行编号,这个号是绝对的,以上是从内核源码中摘抄出来的一部分。
在内核中,系统调用表由系统调用号和系统调用方法的入口地址组成
- 应用程序去执行open的时候,就会产生中断,当前的这个应用程序无法继续往下执行
- 内核要开始处理这个中断,open执行以后,会将这个open对应的系统调用号5写到寄存器Eax中
- 接下来应用程序产生中断,应用程序就被换下去了,并进行现场保护,把当前的应用程序在CPU中运行的各个寄存器上的信息存在内核栈上
- 内核上来执行,操作系统从Eax寄存器中把系统调用号5读出来,在系统调用表查,查出来是
sys_open
,然后找到它对应的实现方法,然后就去打开一个文件,在内核中会创建一些数据结构,来表征这个打开的文件,再把open的返回值,也就是文件描述符写入到Eax寄存器中,相当于把刚才引发的中断处理完成 - 恢复应用程序的执行,恢复以后,首先第一时间从Eax寄存器把结果3读出来,赋给fd,然后应用程序继续向下执行。
因此,系统调用虽然效率很高,执行完之后去内核空间执行,但是,如果说频繁进行切换
,切换的开销就比较大。
这也就是为什么printf会存在缓冲区的原因了.
因为当用户往屏幕上输出信息时,最终调用的是write的系统调用,如果不添加缓冲区,执行一下printf,马上切换到内核态,执行一下write,把内容写到屏幕上,然后刚恢复到用户空间,可能马上又要执行printf,将数据往屏幕上写,又要产生中断,切换到内核态,执行write,把数据写到屏幕上,然后恢复到用户空间,让应用程序执行,频繁切换,效率变得, 因此我们就给printf一个缓冲区,当执行printf方法,把打印的信息写到缓冲区中,等缓冲区满了之后,再从用户空间切换到内核空间,把数据全部写到屏幕上,只是切换一次,效率大大提高,开销也变小很多。