# 系统函数
就是系统调用,系统调用是通向操作系统本身的接口,是面向底层硬件的。通过系统调用,可以使得用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互,是操作系统留给应用程序的一个接口。用户进程需要发生系统调用时,内核将调用内核相关函数来实现(`sys_read()`,`sys_write()`,`sys_fork()`)。用户程序不能直接调用这些函数,这些函数运行在内核态,CPU 通过软中断切换到内核态开始执行内核系统调用函数。
> 用户态–>系统调用–>内核态–>返回用户态
# 库函数
库函数可以理解为是对系统调用的一层封装。系统调用作为内核提供给用户程序的接口,它的执行效率是比较高效而精简的,但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数再提供给程序员,更方便于程序猿编码。
库函数有可能包含有一个系统调用,有可能有好几个系统调用,当然也有可能没有系统调用,比如有些操作不需要涉及内核的功能。可以参考下图来理解库函数与系统调用的关系。

例如该图,`fputc` 是 `C`语言的库函数,在执行该函数时,会去执行系统调用函数`write`,进而切换到内核区,实现功能。
# 库函数 vs 系统调用
比较一下两者在实现相同任务下的时间。当使用C语言中的`fgetc` 与 `fputc` 与 系统调用函数中的`open`与
`write`函数,分别对同一个文件实行`cp`命令且每次只读取一个字符,每次只写一个字符。
常理分析:在同样的任务下,C语言中的库函数`fputtc`会先去找到`write`然后在进行写操作,而`write`省去了这一步,所以理论上`write`的速度更快。来实测一下。
首先分别创建好两个需要测试的demo
```c
//read,write
#include<stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
int fd1, fd2;
fd1 = open(argv[1], O_RDONLY);
fd2 = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0664);
if(fd1 == -1){
perror("file open error\n");
exit(1);
}
char buf[1];
int len;
while( (len = read(fd1, buf, 1)) != 0){ //设置每次只读一个字符
if(len == -1){
perror("read error\n");
exit(1);
}
write(fd2, buf, len); //每次也就只写一个字符
}
close(fd1);
close(fd2);
return 0;
}
```
```c
//fgetc fputc
#include<stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
FILE *fp_in,*fp_out;
fp_in = fopen("dict.txt","r");
if(fp_in == NULL){
perror("fpoen error\n");
exit(1);
}
fp_out = fopen("dict.cp","w");
if(fp_out == NULL){
perror("fpoen error\n");
exit(1);
}
int n;
while( (n = fgetc(fp_in)) != EOF){
fputc(1, fp_out);//一次只读只写一个字符
}
fclose(fp_in);
fclose(fp_out);
return 0;
}
```
依照测试来看,一个`66M`的文件在C语言库函数的代码下瞬间完成,而在系统调用函数中却有明显是停顿。

## 原理
通过`strace`命令,它可以从头到尾跟踪一个可执行文件的执行,然后以一行文本输出系统调用的名字,参数和返回值。
分别执行两个程序可以得到如下结果
- C语言库函数

- 系统调用函数

通过上图可以看出,在执行C语言系统函数时,并没有按照我们要求的每次执行只读一个与只写一个字符,而是每次读4096,每次写4096,但是`read`与`write`是真实的按照我们设想的去一次一个字符进行操作,此时也就解释了为什么执行上述操作时,`fgetc`可以在一瞬间完成,而`read`却需要一些时间。
## 原理

图列介绍:
- 最粗的黑线代表由内核空间到用户空间
- kernel:内核区
- 里面的方框代表内核的缓冲区
- buf代表用户自动自定义的保存数据地方(相当于一个缓冲区?)
- 蓝色方框代表:用户缓冲区
在执行写操作时,系统会将用户区要写入的东西优先存放的内核缓冲区,缓冲区满后再有系统内部的调度算法,一次性冲刷进磁盘。
在执行`read`,`write`函数时,由于没有用户缓冲区,且自定义的`buf`被设置为$1$字节,那么每次进行一个字符的操作都会由用户区切换到内核区进行操作,而该操作是十分耗时的。
反观在执行`fputc`时,标准库函数会默认自带一个用户缓冲区,当使用库函数进行写操作时,会优先写入用户缓冲区(蓝色方框),然后等到用户缓冲区满,写入内核缓冲区。
`fputc`比`write`在该情况下少了`4096`倍由用户区到内核区的切换,故速度更快。
## 预读入缓输出
我们认为用户程序用户定义buf直接使用系统调用`write`函数 跳过了标准库函数进入`kernal`
让`kernal`写到磁盘文件上了实际上不是这样的。在内核中默认也维护了一个缓冲区 默认是`4096b`
内核为了提高效率会一次性等缓冲区满以后再刷到磁盘上,当自己写的`write`函数写的时候 实际上没有写到磁盘上,而是在内核的缓冲区,这种机制称为**缓输出**。
预读入同理,磁盘会先将你要读入的东西放入内核缓冲区,而非调用一次`read`,在磁盘中读取一次内容。
# 正确理解库函数高效于系统调用
首先解释,上述说明的库函数性能远高于系统调用的前提是,库函数种没有使用系统调用。再来解释下某些包含系统调用的库函数,然而其性能确实也要高于系统调用。比如上篇文章中关于文件 IO 函数 `fread`、`fwrite`、`fputc`、`fget` 等,这些函数通常情况下性能确实比系统调用高,原因在于这些库函数使用了缓冲区,减少了系统调用的次数,因而显得性能比较高。
参考了《C 专家编程》书籍中的附录 A.4,书中关于两者区别的回答是这样的,函数库调用是语言或应用程序的一部分,而系统调用是操作系统的一部分。
- 所有 C 函数库是相同的,而各个操作系统的系统调用是不同的。
- 函数库调用是调用函数库中的一个程序,而系统调用是调用系统内核的服务。
- 函数库调用是与用户程序相联系,而系统调用是操作系统的一个进入点
- 函数库调用是在用户地址空间执行,而系统调用是在内核地址空间执行
- 函数库调用的运行时间属于「用户」时间,而系统调用的运行时间属于「系统」时间
- 函数库调用属于过程调用,开销较小,而系统调用需要切换到内核上下文环境然后切换回来,开销较大
- 在C函数库libc中大约 300 个程序,在 `UNIX` 中大约有 90 个系统调用函数库典型的 C 函数:`system`, `fprintf`, `malloc`,而典型的系统调用:`chdir`, `fork`, `write`, `brk`
据书中记载,库函数调用大概花费时间为半微妙,而系统调用所需要的时间大约是库函数调用的 70 倍(35微秒),因为系统调用会有内核上下文切换的开销。纯粹从性能上考虑,你应该尽可能地减少系统调用的数量,但是,你必须记住许多 C 函数库中的程序通过系统调用来实现功能。
系统函数与库函数的区别