分享

系统调用 与 系统API

 君王之王 2015-09-11


1.系统调用  (可以通过编译内核的方式在内核中添加自己的系统调用)
        我们知道,Linux将整个虚拟地址空间划分为两部分:用户空间和内核空间。并且规定,用户空间不能直接访问内核空间,而内核空间则可以访问用户空间。通过这样的级别划分,可以使得内核空间更加的稳定和安全。但是,当用户进程必须访问内核或使用某个内核函数时,就得使用系统调用(System Call)。在Linux中,系统调用是用户空间访问内核空间的唯一途径。
       系统调用是内核提供的一组函数接口,它使得用户空间上运行的进程可以和内核之间进行交互。比如,用户进程通过系统调用访问硬件设备或操作系统的某些资源等。系统调用如同内核空间和用户空间的一个传话者。内核如同一个高高在上的帝王,而用户空间的进程则属于级别很小的官员。由于用户进程资质太浅,当它需要得到内核的支持时,它并没有权利直接上报内核,而只能通过系统调用这个传话人来得到内核的支持。
        具体的,用户程序通过应用编程接口来使用系统调用,而系统调用则是在内核中通过内核函数来实现的。


2.应用编程接口(API)               (应用程序直接使用API,而不是直接使用系统调用,在API中可能封装了系统调用)
        应用编程接口(Application Programming Interface,API)其实就是程序员在用户空间下可以直接使用的函数接口。每个API会对应一定的功能。比如strlen(),它所实现的功能就是求所传递字符串的长度。
       有时候,某些API所提供的功能会涉及到与内核空间进行交互。那么,这类API内部会封装系统调用。而不涉及与内核进行交互的API则不会封装系统调用。也就是说,API和系统调用并没有严格对应关系,一个API可能恰好只对应一个系统调用,比如read()系统调用和read();一个API也可能由多个系统调用实现;有时候,一个API的功能可能并不需要内核提供的服务,那么此时这个API也就不需要任何的系统调用,比如abs()。另外,一个系统调用可能还被多个API内部调用。
       对于编程者来说,系统调用和API都是一组函数,并无什么两样;但是事实上,系统调用的实现是在内核完成的,API则是在函数库中实现的。
        例如:
                  在内核中实现了write系统调用。
                  在库函数中通过宏定义的方式实现write   API。可以在unistd.h中看到write到系统调用的转换。
       API是用户程序直接可以使用的函数接口,但如果每个操作系统都拥有只属于自己的API,那么应用程序的移植性将会很差。基于POSIX(Portable Operating System Interface)标准的API拥有很好的可移植性,它定义了一套POSIX兼容标准,这使得按这个标准实现的API可以在各种版本的UNIX中使用。现如今,它也可以在除UNIX之外的操作系统中使用,比如Linux,Windows NT等。


3.函数库
       还记?得一个.c文件如何逐步成为可执行文件吗?
       一个.c文件会经过预处理(宏替换,头文件包含)、编译(将源码转换成汇编代码)、汇编(将汇编代码转换成二进制代码,即.o文件)、链接(将所有的.o文件连接在一起,并加上库文件)四个步骤。
      在汇编阶段,输出的是.o文件,即我们常说的目标文件。目标文件并不能直接执行,它需要链接器的再一次加工。
      链接器将所有的目标文件集合在一起,加上库文件,最后才能得到可执行文件。
      函数库中包含对许多函数的定义,而这些函数的原型声明则散步在不同的头文件中。比如我们常用(也许你并未感知我们频繁的使用这个函数库)的标准函数库libc.so,在其中包含多个我们常用的函数定义,但是这些函数的声明却分布在stdio.h和string.h等头文件中。
      其实,我们每次在链接程序时,都必须告诉链接器需要链接到那个库中。只不过通常默认的链接让我们忽视了这一点。
      比如,一个简单的helloworld程序中,仅使用了stdio.h头文件。我们当然可以这样轻松的编译:gcc helloworld.c -o helloworld。之所以可以毫无顾忌是因为stdio.h中所声明的函数都定义在libc.so中,而对于这个函数库,连接器是默然链接的。
如果我们编译如下程序:
01 #include < stdio.h >
02 #include < math.h >
03 int main()
04 {
05    double i;
06  
07    scanf("%lf",&i);
08    printf("%lf",sqrt(i));
09    return 0;
10 }
按照我们以往的编译方法显然是不行的:
1 edsionte@edsionte-desktop:~$ gcc test.o -o test
2 test.o: In function `main':
3 test.c:(.text+0x39): undefined reference to `sqrt'
4 collect2: ld returned 1 exit status
       因为在这个程序中使用了math.h头文件,而这个头文件中声明的函数sqrt()被定义在libm.so函数库中。那么,这个时候应该这样编译:gcc test.c -o test -lm。最后的-lm选项即告诉链接器需要加入libm.so函数库。

       上述一步到位的编译方法似乎又无形中掩盖了函数库的加入时间。如果我们按照编译程序的四个步骤依次处理相应文件时,就可以发现只有到了最后的链接过程中才会出现上述错误信息。也就是说,函数库的加入是在链接部分。
        从上述内容中,我们知道应用程序直接使用的并不是系统调用而是API。内核中提供的每个系统调用都通过C库封装成相应的API。并且,C库中的函数并不是总和系统调用一一对应,有的C库函数可能内部调用多个系统调用;有的函数内部可能不涉及任何系统调用;也有多个函数都调用一个系统调用的情况。

4.系统命令
       每一个系统命令其实就是一个可执行的程序,这些可执行程序的实现调用了某些系统调用。并且,这些可执行程序又分为普通用户可使用的命令和管理员可使用的命令。根据上述分类,普通用户可用的命令和管理可用的命令分别被存放于/bin和/sbin目录下。


5.内核函数
       内核函数和用户空间中函数并无两样,只不过内核函数是在内核中实现。虽然系统调用是用户进程进入内核的唯一途径,但是系统调用函数内部并不真正实现其功能,而是通过对内核函数的封装。也就是说,用户程序通过某个系统调用进入内核后,会接着去执行这个系统调用对应的内核函数。这个内核函数也称为系统调用的服务例程。
       由于内核函数是在内核中实现的,因此它必须符合内核编程的规则,比如函数名以sys_开始,函数定义时候需加asmlinkage标识符等。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多