库函数 API 和 C 代码中嵌入汇编代码两种方式使用同一个系统调用
C语言库函数号
以下是基于linux/x86/arch下的系统调用
系统调用
比如1号就是退出,2号就是read等等。一般要引入库文件比如经典的库文件time.h,math.h,string.h等。
使用库函数API实现
需要用到的函数和头文件说明
接下来我要实现第20号的调用(注意第20号是getpid)
注意:getpid ()用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件, 以避免临时文件相同带来的问题。
要用getpid()函数需要引入头函数
#include<unistd.h>
代码实现
#include<unistd.h>
#include<stdio.h>
int main(int argc,char*argv[]){
//获取进程识别码并输出
printf("The pid is %d\n",getpid());
return 0;
}
过程复现
使用C语言中嵌入汇编实现系统调用
代码实现
//注意这个头文件必须加入
#include<unistd.h>
#include<stdio.h>
int main(int argc,char*argv[]){
int res=0;
asm volatile(
"mov $0,%%ebx\n\t" //将ebx寄存器中的值清零
"mov $0x14,%%eax\n\t" //将0x14化为十进制就是20,赋值给eax
"int $0x80\n\t" //通过0x80中断向量,执行系统调用
"mov %%eax,%0\n\t" //将eax中的值赋值给%0(也就是res变量)
: "=res"(res)
);
printf("The pid is %d\n",res);
return 0;
}
过程复现
细心的会发现之前的进程识别码是2817,现在是2855,执行这两个程序的进程不同,进程识别码当然是不同的。
汇编代码调用系统调用的工作过程
可以再来看看那段内嵌汇编代码
asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x14,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
: "=res"(res)
);
第一行是将基地址寄存器清零
第二行将第20号进程号(这个0x14就是传的参数)赋值给累加器
第三行最主要,是实现系统调用使得操作系统从用户态变为内核态(中断)
第四行将累加器内容赋值给我定义的变量
让我们来看看Linux系统的结构
操作系统分了用户态和内核态,用户态拥有权限较低,内核态拥有权限较高,之所以分这两种状态是为了安全,防止用户操作不当使系统崩溃。而用户态到内核态常使用的方法是系统调用,系统调用的机制其核心还是使用了操作系统为用户 特别开放的一个中断来实现,比如这里的int 0x80中断。
总结
从实验中可以感受到系统调用主要有两种方式,一种是直接使用API(application programming interface)接口,一种是使用C语言嵌入汇编实现调用,第二种方法可以加深我们的理解,要实现进程调用在linux中主要得益于int 0x80来实现中断,也要注意需要将你需要使用的内核态的进程号(也就是参数)保存到eax中。为了系统函数的正确执行(为了安全着想),需要把此时一些寄存器等的值进行压入堆栈保存起来,否则模式切换有可能被破坏,等到系统调用返回时再进行出栈。
注:这些只是我个人的理解,应该有一定的错误,请各位大佬指正。