本篇文章分析的是使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用,来说明在Linux系统中,系统调用的实现机制。
相关知识
首先关于这篇文章会介绍一些用到的知识。
一、什么是内核态,什么又是用户态。内核态:在高执行级别下,代码可以执行特权指令,访问任意的物理地址。
用户态:在相应的低执行状态下,代码的掌控范围受到限制,只能在对应级别允许的范围内活动。
当一个程序在用户态下执行的时,它不能直接访问内核数据结构或者内核的程序。然而,当应用程序运行在内核态下的时候,这些限制不再有效。每中CPU模型都为从用户态到内核态的转换提供了特殊的指令,反之亦然。一个程序的执行大部分时间都处于用户态下,只有需要内核所提供的服务的时候才切换到内核态。当内核满足了用户程序的请求后,它让程序又回到用户态下。
二、C代码中嵌入汇编代码
嵌入式汇编的格式如下:
下面是一些常用的嵌入汇编的限定符:
汇编到底有什么好处呢?
1.提高速度和效率。(不过这种情况很少了,现在C/C++编译器的优化很厉害了)
2.实现某些C语言中不具备、但为不同的机器所特有的功能。(这是主要原因)
3.利用通用的汇编语言例程。(也常会遇到)
三、中断机制
中断机制的一些概念可以参考我上篇博客《Linux操作系统分析》之分析精简的Linux的内核中断和时间片轮询。
但是在这里我们需要在介绍一下,中断和系统调用之间的关系。
系统调用是只是一种特殊的中断,通过trap陷阱,进入到内核态。其中包括以下的步骤。
四、应用接口编程和系统调用
应用接口编程是一个函数的定义,说明了如何获得一个给定的服务。
系统调用是通过软中断向内核态发出了一个明确的请求。
系统调用的意义:操作系统为用户态进程与硬件设备进行交互提供的一组接口。可以把用户从底层的硬件变成中解放出来,极大的提高了系统的安全性,使用户程序具有可移植性。
上图是系统调用的三个层次依次是:xyz函数(API)、system_ call(中断向量)和 sys_ xyz(中断服务程序)。
分析过程
在这里我调用getpid函数。当然也可以去看看还有什么其他的系统调用点击。
使用库函数的实现代码如下:
#include<stdio.h>
#include<unistd.h>
int main(){
pid_t num;
num = getpid();
printf("this is process id :%u\n",num);
return 0;
}
然后进行编译,并运行,结果如下图:
我们可以得到如上的结果。
我们修改代码,使用嵌入的汇编来调用20号系统调用。
#include <stdio.h>
#include<unistd.h>
int main()
{
pid_t pid;
asm volatile(
"movl $0,%%ebx\n\t"//将ebx寄存器清零,系统调用传递第一个参数使用ebx,这里是null
"movl $0x14,%%eax\n\t"//将0xd放入eax中,0x14为20,传递系统调用号20
"int $0x80\n\t"
"movl %%eax,$0\n\t"//通过eax这个寄存器返回系统调用值,和普通函数一样
:"=m"(pid)
);
printf("this process's pid is : %u\n",pid);
return 0;
}
编译后运行结果和第一种情况一样。
总结:
Linux中是通过寄存器%eax传递系统调用号,所以具体调用fork的过程是:将20存入%eax中,然后进行系统调用.对于参数传递,Linux是通过寄存器完成的。Linux最多允许向系统调用传递6个参数,分别依次由%ebx,%ecx,%edx,%esi,%edi和%ebp这个6个寄存器完成。进程运行在用户态和内核态时使用不同的栈,分别叫做用户栈和内核栈。两者各自负责相应特权级别状态下的函数调用。系统调用时,进程不仅是用户态到内核态的切换,同时也要切换栈,这样内核态的系统调用才能在内核栈上完成调用。系统调用返回时,还要切换回用户栈,继续完成用户态下的函数调用。
备注:
杨峻鹏 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000