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

linux系统调用工作过程

2015-03-21 16:01 246 查看
一、系统调用意义

操作系统为用户进程与硬件设备进行交互提供了一组接口–系统调用。

1、把用户从底层的硬件编程中解放出来。

它为用户空间提供了一种统一的硬件的抽象接口。比如当需要读些文件的时候,应用程序就可以不去管磁盘类型和介质,甚至不用去管文件所在的文件系统到底是哪种类型。

2、极大的提高了系统的安全性。

作为硬件设备和应用程序之间的中间人,内核可以基于权限和其他一些规则对需要进行的访问进行裁决。举例来说,这样可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源,或做出其他什么危害系统的事情。在Linux中,系统调用是用户空间访问内核的惟一手段;除异常和中断外,它们是内核惟一的合法入口。

3、使用户程序具有可移植性。

一般情况下,应用程序通过应用编程接口(API)而不是直接通过系统调用来编程。这点很重要,因为应用程序使用的这种编程接口实际上并不需要和内核提供的系统调用一一对应。一个API定义了一组应用程序使用的编程接口。它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,而完全不使用任何系统调用也不存在问题。实际上,API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而它们本身在这些系统上的实现却可能迥异。从而使得用户程序具有可移植性。

从程序员的角度看,系统调用无关紧要,他们只需要跟API打交道就可以了。相反,内核只跟系统调用打交道;库函数及应用程序是怎么使用系统调用不是内核所关心的。

二、系统调用实现

1、系统调用处理程序

用户空间的程序无法直接执行内核代码。它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用

通知内核的机制是靠软件中断实现的。首先,用户程序为系统调用设置参数。其中一个参数是系统调用编号。参数设置完成后,程序执行“系统调用”指令。x86系统上的软中断由int产生。这个指令会导致一个异常:产生一个事件,这个事件会致使处理器切换到内核态并跳转到一个新的地址,并开始执行那里的异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。它与硬件体系结构紧密相关。



2、系统调用号

在Linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用。进程不会提及系统调用的名称。

在x86上,系统调用号是通过eax寄存器传递给内核的。在陷人内核之前,用户空间就把相应系统调用所对应的号放入eax中了。这样系统调用处理程序一旦运行,就可以从eax中得到数据。其他体系结构上的实现也都类似。

3、参数传递

除了系统调用号以外,大部分系统调用都还需要一些外部的参数输人。所以,在发生异常的时候,应该把这些参数从用户空间传给内核。最简单的办法就是像传递系统调用号一样把这些参数也存放在寄存器里。在x86系统上,ebx, ecx, edx, esi和edi按照顺序存放前五个参数。需要六个或六个以上参数的情况不多见,此时,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针,利用内存空间进行参数传递。

给用户空间的返回值也通过寄存器传递。在x86系统上,它存放在eax寄存器中。

4、参数检查

系统调用必须仔细检查它们所有的参数是否合法有效。举例来说,与文件I/O相关的系统调用必须检查文件描述符是否有效。与进程相关的函数必须检查提供的PID是否有效。必须检查每个参数,保证它们不但合法有效,而且正确。

最重要的一种检查就是检查用户提供的指针是否有效。在接收一个用户空间的指针之前,内核必须保证:

² 指针指向的内存区域属于用户空间。进程决不能哄骗内核去读内核空间的数据。

² 指针指向的内存区域在进程的地址空间里。进程决不能哄骗内核去读其他进程的数据。

² 如果是读,该内存应被标记为可读。如果是写,该内存应被标记为可写。进程决不能绕过内存访问限制。

5、系统调用返回值

通常用一个负的返回值来表明错误。返回一个0值通常表明成功。如果一个系统调用失败,你可以读出errno的值来确定问题所在。通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。

errno不同数值所代表的错误消息定义在errno.h中,你也可以通过命令”man 3 errno”来察看它们。需要注意的是,errno的值只在函数发生错误时设置,如果函数不发生错误,errno的值就无定义,并不会被置为0。另外,在处理errno前最好先把它的值存入另一个变量,因为在错误处理过程中,即使像printf()这样的函数出错时也会改变errno的值。

三、举例说明

1、应用程序调用fork封装例程

#include<stdio.h>
#include<unistd.h>

int main()
{
int pid = fork();
if(pid == -1)
printf("error!\n");
else if(pid == 0)
{
printf("This is the child process!\n");
}
else
{
printf("This is the parent process! child process id is: %d\n",pid);
}
return 0;
}


2、汇编代码调用系统调用

#include<stdio.h>
#include<unistd.h>

int main()
{
int pid;
asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x2,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
:"=m"(pid)
);
if(pid == -1)
printf("error!\n");
else if(pid == 0)
{
printf("This is the child process!\n");
}
else
{
printf("This is the parent process! child process id is: %d\n",pid);
}

return 0;
}


如图所示:

通过系统API fork()函数及利用汇编代码调用2号系统调用system_fork,得到的程序运行结果是一样的。



接下来解释汇编代码部分。

asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x2,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
:"=m"(pid)
);


"mov $0,%%ebx\n\t"


系统调用传递第一个参数,这里是NULL,所以为0;

"mov $0x2,%%eax\n\t"


eax传递系统调用号,fork系统调用号为2;

"int $0x80\n\t"


通过执行int $0x80来执行系统调用,软中断;

"mov %%eax,%0\n\t"


系统调用返回参数由eax寄存器返回,并传给内存中的pid;

:"=m"(pid)


输出为pid;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: