上周总结了《C 标准库的基础 IO》,其实这些功能函数通过「系统调用」也能实现相应功能。这次文章并不是要详细介绍各系统调用接口的使用方法,而是要深入理解「库函数」与「系统」调用之间的关系和区别。
系统调用,我们可以理解是操作系统为用户提供的一系列操作的接口(API),这些接口提供了对系统硬件设备功能的操作。这么说可能会比较抽象,举个例子,我们最熟悉的 hello world
程序会在屏幕上打印出信息。程序中调用了 printf()
函数,而库函数 printf
本质上是调用了系统调用 write()
函数,实现了终端信息的打印功能。
库函数可以理解为是对系统调用的一层封装。系统调用作为内核提供给用户程序的接口,它的执行效率是比较高效而精简的,但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数再提供给程序员,更方便于程序猿编码。
库函数有可能包含有一个系统调用,有可能有好几个系统调用,当然也有可能没有系统调用,比如有些操作不需要涉及内核的功能。可以参考下图来理解库函数与系统调用的关系。
hello world
程序是将信息打印到终端,终端对系统来说是硬件资源,如果没有系统调用,用户程序需要自己编写终端设备的驱动,以及控制终端如何显示的代码。总而言之,我们只需要把系统调用当作一个接口,而这个接口能实现我们的一个功能,既方便又安全。
参考了《C 专家编程》书籍中的附录 A.4,书中关于两者区别的回答是这样的,函数库调用是语言或应用程序的一部分,而系统调用是操作系统的一部分。
据书中记载,库函数调用大概花费时间为半微妙,而系统调用所需要的时间大约是库函数调用的 70 倍(35微秒),因为系统调用会有内核上下文切换的开销。纯粹从性能上考虑,你应该尽可能地减少系统调用的数量,但是,你必须记住许多 C 函数库中的程序通过系统调用来实现功能。
首先解释,上述说明的库函数性能远高于系统调用的前提是,库函数种没有使用系统调用。再来解释下某些包含系统调用的库函数,然而其性能确实也要高于系统调用。比如上篇文章中关于文件 IO 函数 fread、fwrite、fputc、fgetc 等,这些函数通常情况下性能确实比系统调用高,原因在于这些库函数使用了缓冲区,减少了系统调用的次数,因而显得性能比较高。
上述内容基本说清楚了库函数与系统调用的概念以及它们之间的关系,下面我们来理解系统调用到底是如何运行的。
当一个进程正在运行,遇到读写文件操作,会发生一个中断,中断后系统会把当前用户进程的一些寄存器信息保存在内核堆栈中,接着去处理中断服务程序,这里是要去执行系统调用,Linux 中通过执行 int $0x80
来执行系统调用的中断,但内核实现了很多系统调用,这时需要传递「系统调用号」来指明需要哪个系统调用。
为了更清楚的说明系统调用的过程,我们这里参考网上的一段代码来实现系统调用:
int main()
{
time_t tt;
struct tm *t;
asm volatile (
"mov $0,%%ebx\n\t"
"mov $0xd,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
: "=m" (tt)
);
t = localtime(&tt);
printf("Time: %d-%02d-%02d %02d:%02d:%02d\n",
t->tm_year + 1900,
t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
}[linuxblogs@host ~]$ gcc a.c -oa && ./a
Time: 2018-05-06 03:23:46
首先通过 mov 0xd %%eax 来将系统调用放入 %eax 寄存器中,time() 的系统调用号是 13,然后执行 int 0x80 系统就会去执行 time() 这个系统调用了。其实代码中的汇编部分就是实现 time() 系统调用的功能,汇编代码不懂没关系(我也不太懂),这里主要是为了说清楚系统调用的整个过程。