Linux 库函数与系统调用的关系与区别

上周总结了《C 标准库的基础 IO》,其实这些功能函数通过「系统调用」也能实现相应功能。这次文章并不是要详细介绍各系统调用接口的使用方法,而是要深入理解「库函数」与「系统」调用之间的关系和区别。

一、系统调用

系统调用,我们可以理解是操作系统为用户提供的一系列操作的接口(API),这些接口提供了对系统硬件设备功能的操作。这么说可能会比较抽象,举个例子,我们最熟悉的 hello world 程序会在屏幕上打印出信息。程序中调用了 printf() 函数,而库函数 printf 本质上是调用了系统调用 write() 函数,实现了终端信息的打印功能。

二、库函数

库函数可以理解为是对系统调用的一层封装。系统调用作为内核提供给用户程序的接口,它的执行效率是比较高效而精简的,但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数再提供给程序员,更方便于程序猿编码。

库函数有可能包含有一个系统调用,有可能有好几个系统调用,当然也有可能没有系统调用,比如有些操作不需要涉及内核的功能。可以参考下图来理解库函数与系统调用的关系。

三、系统调用意义

  • 避免了用户直接对底层硬件进行编程。比如最简单的hello world程序是将信息打印到终端,终端对系统来说是硬件资源,如果没有系统调用,用户程序需要自己编写终端设备的驱动,以及控制终端如何显示的代码。
  • 隐藏背后的技术细节。比如读写文件,如果使用了系统调用,用户程序无须关心数据在磁盘的哪个磁道和扇区,以及数据要加载到内存什么位置。
  • 保证系统的安全性和稳定性。要知道用户程序是不能直接操作内核地址空间的,比如一个刚出道的程序猿,让他直接去访问内核底层的数据,那么内核系统的安全性就无法保证。而系统调用的功能是由内核来实现,用户只需要调用接口,无需关心细节,也避免了系统的安全隐患。
  • 方便程序的移植性。如果针对一个系统资源的功能操作比如 write(),大家都按照自己思路去实现这个功能,那么我们写出来的程序的移植性就会非常差。

总而言之,我们只需要把系统调用当作一个接口,而这个接口能实现我们的一个功能,既方便又安全。

四、库函数 vs 系统调用

参考了《C 专家编程》书籍中的附录 A.4,书中关于两者区别的回答是这样的,函数库调用是语言或应用程序的一部分,而系统调用是操作系统的一部分。

  • 所有 C 函数库是相同的,而各个操作系统的系统调用是不同的。
  • 函数库调用是调用函数库中的一个程序,而系统调用是调用系统内核的服务。
  • 函数库调用是与用户程序相联系,而系统调用是操作系统的一个进入点
  • 函数库调用是在用户地址空间执行,而系统调用是在内核地址空间执行
  • 函数库调用的运行时间属于「用户」时间,而系统调用的运行时间属于「系统」时间
  • 函数库调用属于过程调用,开销较小,而系统调用需要切换到内核上下文环境然后切换回来,开销较大
  • 在C函数库libc中大约 300 个程序,在 UNIX 中大约有 90 个系统调用
  • 函数库典型的 C 函数:system, fprintf, malloc,而典型的系统调用:chdir, fork, write, brk

据书中记载,库函数调用大概花费时间为半微妙,而系统调用所需要的时间大约是库函数调用的 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() 系统调用的功能,汇编代码不懂没关系(我也不太懂),这里主要是为了说清楚系统调用的整个过程。

欢迎关注公众号: 「linuxblogs」

原文地址:https://www.cnblogs.com/liwei0526vip/p/8998751.html

时间: 2024-10-08 13:44:23

Linux 库函数与系统调用的关系与区别的相关文章

标准C库函数和系统调用的关系

标准 C 库函数中可以封装系统调用,也可以不封装系统调用,不同的标准 C 库函数可以封装相同的系统调用.标准 C 库属于用户态,而系统调用属于内核态. 标准 C 库中的函数在不同的操作系统环境下都可以使用,比如 fopen 函数在linux下可以使用,在windos下也可以使用.所以说程序的可移植性比较好. 系统调用是依赖于具体操作系统的,比如 read 函数只能在linux下或者满足POSIX标准的操作系统下使用,由于window不满足POSIX标准,所以window下不能使用.可移植性比较差

Linux与JVM的内存关系分析(转)

引言 在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约600m,Linux自身使用大约800m.从表面上,物理内存应该是足够使用的:但实际运行的情况是,会发生大量使用SWAP(说明物理内存不够使用了),如下图所示.同时,由于SWAP和GC同时发生会致使JVM严重卡顿,所以我们要追问:内存究竟去哪儿了? 要分析这个问题,理解JVM和操作系统之间的内存关系非常重要.接下来主要就Linux与JVM之间的内存关系进行一

转: 关于Linux与JVM的内存关系分析

转自: http://tech.meituan.com/linux-jvm-memory.html Linux与JVM的内存关系分析 葛吒2014-08-29 10:00 引言 在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约600m,Linux自身使用大约800m.从表面上,物理内存应该是足够使用的:但实际运行的情况是,会发生大量使用SWAP(说明物理内存不够使用了),如下图所示.同时,由于SWAP和GC同时

转: Linux与JVM的内存关系分析

Linux与JVM的内存关系分析 引言 在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约600m,Linux自身使用大约800m.从表面上,物理内存应该是足够使用的:但实际运行的情况是,会发生大量使用SWAP(说明物理内存不够使用了),如下图所示.同时,由于SWAP和GC同时发生会致使JVM严重卡顿,所以我们要追问:内存究竟去哪儿了? 要分析这个问题,理解JVM和操作系统之间的内存关系非常重要.接下来主要就Li

Linux与JVM的内存关系分析

引言 在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约600m,Linux自身使用大约800m.从表面上,物理内存应该是足够使用的:但实际运行的情况是,会发生大量使用SWAP(说明物理内存不够使用了),如下图所示.同时,由于SWAP和GC同时发生会致使JVM严重卡顿,所以我们要追问:内存究竟去哪儿了? 要分析这个问题,理解JVM和操作系统之间的内存关系非常重要.接下来主要就Linux与JVM之间的内存关系进行一

Linux库函数制作(静态库、动态库)

Linux库函数制作(静态库.动态库) 静态库与动态库 链接方式 链接分为两种:静态链接.动态链接 静态链接: 由链接器在链接时将库的内容加入到可执行程序中 静态链接的特点是: 优点: 对运行环境的依赖性较小,具有较好的兼容性 缺点: 生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间 库函数有了更新,必须重新编译应用程序 动态链接: 连接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序 动态链接的特点: 优点: 在需要的时候才会调入对应的资

Linux中brk()系统调用,sbrk(),mmap(),malloc(),calloc()的异同【转】

转自:http://blog.csdn.net/kobbee9/article/details/7397010 brk和sbrk主要的工作是实现虚拟内存到内存的映射.在GNUC中,内存分配是这样的:       每个进程可访问的虚拟内存空间为3G,但在程序编译时,不可能也没必要为程序分配这么大的空间,只分配并不大的数据段空间,程序中动态分配的空间就是从这一块分配的.如果这块空间不够,malloc函数族(realloc,calloc等)就调用sbrk函数将数据段的下界移动,sbrk函数在内核的管理

linux中fork, source和exec的区别

转:linux中fork, source和exec的区别 shell的命令可以分为内部命令和外部命令. 内部命令是由特殊的文件格式.def实现的,如cd,ls等.而外部命令是通过系统调用或独立程序实现的,如awk,sed. source和exec都是内部命令. fork   使用 fork 方式运行 script 时, 就是让 shell(parent process) 产生一个 child process 去执行该 script, 当 child process 结束后, 会返回 parent

进程与线程的定义、关系及区别

进程与线程的定义.关系及区别     --参考博客文章:http://blog.csdn.net/yanxiaolx/article/details/51763372 一.进程的定义 进程:指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令.数据和堆栈等组成的,是一个能独立运行的活动实体. 进程一般有三个状态:就绪状态.执行状态和等待状态[或称阻塞状态]:进程只能由父进程建立,系统中所有的进程形成一种进程树的层次体系:挂起命令可由进程自己和其他进程发出,但是解除挂起命令只能由其他进