您的位置:首页 > 运维架构 > Linux

谈谈对linux系统调用的理解

2015-03-24 15:53 169 查看
罗晓波 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

本文主要讨论一下系统调用是如何工作以及其中发生了神马。依旧以一个小实验开始。

1.getpid()系统调用的实验

1.1 实验环境:依旧是采用实验楼的环境: http://www.shiyanlou.com/courses/195
1.2 实验过程:

系统调用列表中,选择getpid这个系统调用,也就是获得当前的调用的进程识别号。代码也是比较简单,如下:
#include <stdio.h>
#include <unistd.h>
int main( void )
{
int pid = getpid();
printf( "Process id: %d\n", pid );
return 0;
}
getpid()是一个<unistd.h>库提供的API,通过这个API可以进行系统调用,细节在下面讨论。

另外,再贴一段简单的代码,通过这段代码,我们就可以大致知道系统调用干了什么东西了。

#include <stdio.h>
#include <unistd.h>
int main( void )
{
int pid_asm ;
int pid ;
asm volatile
(
"mov $0,%%ebx\n\t"
"mov $0x14,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
:"=m"(pid_asm)
);
pid = getpid();
printf( "Get Process id By getpid: %d\n", pid );
printf( "Get Process id By getpid_asm: %d\n", pid_asm );
return 0;
}
对比上面两段代码,会发现,用嵌入式汇编实现了getpid()的系统调用的功能。实验截图我们贴上:



2. 分析系统调用

程序员通过API来进行系统调用,例如一些libc库提供的posix API,getpid就是这样一个posix API,它提供了用户态到内核态的访问,在内核态中访问进程的pid,大致的来说,系统调用会执行下面的操作:

在内核态保存大多数寄存器的内容;调用名为系统调用服务例程的响应的C函数来处理系统调用;退出到系统调用处理程序,用保存在内核栈中的值加载寄存器,cpu从内核态切换回用户态。

下面来分析一下,上面的一段嵌入式汇编代码,这里的ebx作为系统调用的第一个参数,传入null,也就是0,还有5各寄存器可以用来传递参数,ecx、edx、esi、edi、ebp。下面一句就是用eax来传递传递系统调用号,这里的系统调用号是0x14,也就是对应的getpid的系统调用的号,再下一句就是int
0x80 指令了,这个指令主要是用来切换用户态到内核态的,依稀记得上一篇分析内核启动过程的文章中,提到了trap_init();这个在kernel_start()这个函数里面,在trap_init()函数里,set_system_gate(0x80,&system_call),从这里我们可以看到用户态进程发出0x80指令的时候,cpu切换到内核态并开始从地址system_call处开始执行指令。在这里调用特定的服务例程,也就是call *sys_call_table(0,%eax,4),分派表中,每个表项占据4个字节,先把系统调用号乘以4,再加上sys_call_table的其实地址,然后从这个地址单元获取指向这个服务例程的指针,内核就找到了要调用的服务例程,到这里也就找到了getpid的调用例程了,开始进行调用。

系统调用服务例程结束的时候,system_call()函数从eax获得它的返回值,并将这个返回值存放在曾保存用户态eax寄存器值得那个栈单元的位置上。至此,用户态进程也可以获得系统调用的返回值了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: