Linux内核课第三周作业。本文在云课堂中实验楼完成。
唐国泽 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
----------------------------------------------分割线---------------------------------------------------------------------
主要内容:
1.Linux系统调用的原理
2.系统调用的实现与意义
3.API和系统调用
4.系统调用程序及服务例程
5.实验:使用C语言和汇编分别进行系统调用
1.Linux系统调用的原理
系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务,比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置定时器等。
从逻辑上来说,系统调用可被看成是一个内核与用户空间程序交互的接口——它好比一个中间人,把用户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。
系统服务之所以需要通过系统调用来提供给用户空间的根本原因是为了对系统进行“保护”,因为我们知道Linux的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离。所以用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间函数。比如我们熟悉的“hello world”程序(执行时)就是标准的用户空间进程,它使用的打印函数printf就属于用户空间函数,打印的字符“hello word”字符串也属于用户空间数据。
但是很多情况下,用户进程需要获得系统服务(调用系统程序),这时就必须利用系统提供给用户的“特殊接口”——系统调用了,它的特殊性主要在于规定了用户进程进入内核的具体位置;换句话说,用户访问内核的路径是事先规定好的,只能从规定位置进入内核,而不准许肆意跳入内核。有了这样的陷入内核的统一访问路径限制才能保证内核安全无虞。我们可以形象地描述这种机制:作为一个游客,你可以买票要求进入野生动物园,但你必须老老实实地坐在观光车上,按照规定的路线观光游览。当然,不准下车,因为那样太危险,不是让你丢掉小命,就是让你吓坏了野生动物。
该段引用自博客:《Linux系统调用》
2.系统调用的实现和意义
解决这个问题的方法非常简单:首先Linux为每个系统调用都进行了编号(0—NR_syscall),同时在内核中保存了一张系统调用表,该表中保存了系统调用编号和其对应的服务例程,因此在系统调入通过系统门陷入内核前,需要把系统调用号一并传入内核,在x86上,这个传递动作是通过在执行int0x80前把调用号装入eax寄存器实现的。这样系统调用处理程序一旦运行,就可以从eax中得到数据,然后再去系统调用表中寻找相应服务例程了。
那么,调用时候的参数是如何传递的呢?
传递的参数主要有:
- 实际的值;
- 用户态进程地址空间的变量的地址;
- 甚至是包含指向用户态函数的指针的数据结构的地址;
那设置系统调用的意义何在呢?
操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性
3.API,系统调用,系统命令,内核函数
4.系统调用程序和服务例程
上面提到Linux只允许系统调用接口使用128这一个软中断向量,这也就意味着所有的系统调用接口必须共享这一个中断通道,并在同一个中断服务例程中(这里的中断服务例程就是对应于中断号为128的中断服务例程,通过查中断向量表得到)调用不同的内核服务例程,所以,系统调用接口除了要引发“int $ Ox80”软中断之外,为了进人内核后能调用不同的内核服务例程,还要提供识别内核服务例程的参数,这个参数叫做“系统调用号”。也就是说,所有可为进程提供服务的内核服务例程都应具有一个唯一的系统调用号。当然,系统调用接口还应为内核服务例程准各必要的参数。
那么,这里,我就截一个系统调用表的图片给大家看看:
在图中我们可以看到,我们的系统调用表格中的第一个调用是sys_restart_syscall,也就是重启了,系统为每一个系统调用都定义了一个唯一的编号,同时在内核中保存了一张系统调用表,该表中保存了系统调用编号和其对应的服务例程地址,第呢n个表项包含了系统调用号为n的服务例程的地址;
所有系统调用陷入内核前,需要将系统调用号一起传入内核,而该标号实际上就是系统调用表的下标,在i386上,这个传递工作是通过在执行int $0x80前把调用号装入eax寄存器来实现的,这样系统调用处理程序一旦运行起来,就可以从eax中得到系统调用号,然后再到系统调用表中去寻找相应的服务例程。
整理系统调用的过程:
- 应用程序调用封装好的API
- 要保护用户态的现场,即把处理器的用户态运行环境保护到进程的内核堆栈。
- API将对应的系统调用号存入eax,如果需要传参,还要在其他寄存器中传入相关参数,然后调用int $0x80触发中断进入内核中的中断处理函数
- 内核中的中断处理程序根据系统调用号调用对应的系统调用
- 系统完成相应功能,将返回值存入eax,返回到中断处理函数;
- 中断处理函数返回到API中;//在返回的途中,有进程调度,如果有优先级更高的进程,会调度
- API将eax,即系统调用的返回值返回给应用程序。
5.实验:使用C语言和汇编分别进行系统调用
<div style="text-align: justify;"><span style="font-family: Arial, Helvetica, sans-serif;">/*************************************************************************</span></div> > File Name: Callexit.c
> Author: GuoZe Tang
> Mail: 269831714@qq.com
<div style="text-align: justify;"><span style="font-family: Arial, Helvetica, sans-serif;"> ************************************************************************/</span></div> > Created Time: Sun 29 Mar 2015 01:08:06 PM CST
#include<stdio.h>
#include<time.h>
#include<sys/types.h>
<div style="text-align: justify;"><span style="font-family: Arial, Helvetica, sans-serif;"> printf("Fork error!\n");</span></div>#include<unistd.h>
int main(int argc,char *argvs)
{
pid_t pc,pr;
int t;
pc =fork();
if(pc < 0)
<div style="text-align: justify;"><span style="font-family: Arial, Helvetica, sans-serif;"> pr=wait(NULL);</span></div> else if(pc == 0){
printf("This is child process with pid of %d\n",getpid());
sleep(5);
}
else{
<div style="text-align: justify;"><span style="font-family: Arial, Helvetica, sans-serif;">}</span></div> printf("I catched a child process with pid of %d\n",pr);
}
<div style="text-align: justify;"><span style="font-family: Arial, Helvetica, sans-serif;"> exit(0);</span></div>
进程的退出,调用系统调用exit()就可完成,在这里exit写出汇编的形式,查询系统调用表,可知exit的系统调用号为1;使用汇编代码来书写exit系统调用如下:
/*************************************************************************
> File Name: Callexit_asm.c
> Author: GuoZe Tang
> Mail: 269831714@qq.com
> Created Time: Sun 29 Mar 2015 01:08:06 PM CST
************************************************************************/
#include<stdio.h>
#include<time.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc,char *argvs)
{
pid_t pc,pr;
int t;
pc =fork();
if(pc < 0)
printf("Fork error!\n");
else if(pc == 0){
printf("This is child process with pid of %d\n",getpid());
sleep(5);
}
else{
pr=wait(NULL);
printf("I catched a child process with pid of %d\n",pr);
}
asm volatile(
"mov $0x1,%%eax\n\t"
"mov $0x0,%%ebx\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
:"=m" (t)
);
}
执行如下: