系统调用
2009年08月26日
系统调用在用户空间进程和硬件设备之间添加了一个中间层。在Linux中,系统调用是用户空间访问内核的唯一手段;除异常和陷入外同,它们是内核唯一的合法入口。一般情况下,应用程序通过应用编程接口(API,例如C库)而不是直接通过系统调用来编程。从程序员的角度看,系统调用无关紧要;他们只需要跟API打资产就可以了。相反,内核只跟系统调用打交道。UNIX系统调用在出现错误的时候会把错误写入errno全局变量。通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。系统调用的声明要加上asmlinkage限定词,这是一个小戏法,用于通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词。另外,系统调用命名都遵守一个规则,在函数前加上sys,例如系统调用get_pid()在内核中被定义为sys_getpid()。
在Linux中,每个系统调用被赋予一个系统调用号。在用户进程中不会提及系统调用的名称。内核记录了系统调用高个儿中的所有已注册过的系统调用的列表,存储在sys_call_table中,它与体系结构有关,一般在entry.s中定义。
用户空间的程序无法直接执行内核代码。它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。如果进程可以直接在内核的地址空间上读写的话,系统安全就会失去控制。通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序实际就是系统调用处理程序。x86系统上的软中断由int $0x80指令产生。这条指令会触发一个异常导致系统切换到内核态并执行第128号异常处理程序,而该程序正是系统调用处理程序。这个处理程序叫做system_call()。它与硬件体系结构紧密相关,通常在entry.S文件中用汇编语言编写。最近,x86处理器增加了一条叫做sysenter的指令。与int中断指令相比,这条指令提供了更快、更专业的陷入内核执行系统调用的方式。
所有系统调用陷入内核的方式都一样(都是调用system_call()),通过传递一个系统调用号给内核,让system_call()根据调用号来调用不同的内核函数。我觉得,在x86上,系统调用号通过eax寄存器传递给内核。
因为内核与用户空间具有不同的空间(内核当然可以访问所有的内存空间,试想下内核分配内存),所以参数传递也是通过寄存器传递给内核的,在x86系统上,ebx,ecx,edx,esi和edi按照顺序存放前五个参数。需要六个或以上参数的情况比较少,如果有,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。
对于传递进来的参数,内核要进行检查,特别是指针,内核要保证:
一、指针指向的内存区域属于用户空间(进程决不能哄骗内核去读内核空间的数据)。
二、指针指向的内存区域在进程的地址空间里(进程决不能哄骗内核去读其他进程的数据)。
因为内核能读所有内存空间(包括所有的用户空间与内核空间),所以上述二点是相当重要的。内核实现了二个函数来实现并保证上述二点,copy_to_user()实现从内核向用户空间写入数据,copy_from_user()实现从用户空间读取数据。这二个函数有可能引起阻塞。当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候(为什么页会被换出?看来要看下页处理程序。),这种情况就会发生。此时,进程就会休眠,直到页处理程序将该页从硬盘重新换回物理内存。
增加一个系统调用的步骤(以foo()为例):
1、在系统调用表的最后一行加入一行。一般是entry.s文件,形式如下:
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 */
.long sys_exit
....
.long sys_mq_timedreceive /* 280 */
...
.long sys_foo
2、加入系统调用号。一般是在中,它的格式如下:
#define _NR_restart_syscall 0
#define _NR_exit 1
...
#define _NR_mq_timedreceive 280
...
#define _NR_foo 283
3、实现foo()系统调用,该系统调用必须编译到核心的内核映象中(即必须将它放到内核里去,并重新编译内核)。在这里,我们把它放到kernel/sys.c文件中,你也可以将其放到与其功能联系最紧密的代码中去。
#include
asmlinkage long sys_foo(void)
{
return THREAD_SIZE;
}
编译内核后,就可以用该系统调用了。
4、调用自己写的系统调用。如果不通过C库的支持来调用自己增加的系统调用,可以通过Linux提供的宏(这些宏我会在另一篇文章中给出)。这些宏是_syscalln(),其中n的范围从0到6,表示有几个参数。对于每个宏来说,都有2+2*n个参数,第一个参数对应系统调用返回值,第二个对应系统调用的名称,后面的参数分别是参数的类型和名称。例如,open()系统调用的定义是:
long open(const char*filename, int flags, int mode)
如果不靠库支持,直接调用此系统调用的宏的形式为:
#define _NR_open 5
_syscall3(long, open, const char*, filename, int, flags, int ,mode)
对于我们写的foo()系统调用,不用C库的支持时,可以这样做:
#define _NR_foo 283
_syscall0(long, foo)
int main()
{
long stack_size;
stack_size = foo();
printf("The kernel statck size is %ld\n", stack_size);
return 0;
}
以上四步就是没有通过C库支持,从加入一个系统调用到调用它的过程。如果弄清楚系统调用的过程及_syscalln宏的实现,就很容易理解这个过程了,下面给出一个_syscall0宏的实现来解释下(好像有说这些宏在Linux2.6.20没了)。
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
__syscall_return(type,__res); \
} 从这个宏中,我们可以看到,当我们写下_syscall0(long, foo)时,展开宏就会得到了个long foo(void)的函数,所以在main中我们可以直接调用foo(),事实上它利用宏展开的。在foo()内部,会由int $0x80指令产生软中断,然后调用系统调用处理程序__syscall_return,并传递返回值类型及一个_NR_foo(在系统调用号文件中定义)给它,它也是一个宏:
#define __syscall_return(type, res) \
do { \
if ((unsigned long)(res) >= (unsigned long)(-MAX_ERRNO)) { \
errno = -(res); \
res = -1; \
} \
return (type) (res); \
} while (0) 在这个宏里面,利用_NR_foo在系统调用表中找到相应的函数进行调用,即sys_foo,而返回值类型用于强制类型转换。
调用过程的小结:foo->通过宏_syscall0(long, foo)产生软中断->调用调度处理程序(也是一个宏)->利用传递给它的系统调用号在系统调用表中找到对应的函数进行调用。到此,就很容易明白在添加一个系统调用要做什么动作了:在系统调用表中加入sys_foo,并加入系统调用号,实现函数,编译内核,由于所有的过程依赖于宏,所以系统调用表,系统调用号这些都要有相应的格式。(至于Linux2.6.20后没有这些宏了,不知是如何实现的,学习到了再写文章。另外,可以参考另一篇文章,即syscall宏)
发表评论
-
WIN下用exp备份保存最近N天方案
2012-01-20 01:34 582WIN下用exp备份保存最近N天方案 2010年06月03日 ... -
[转]命令行创建快捷方式的批处理脚本
2012-01-20 01:34 1304[转]命令行创建快捷方式的批处理脚本 2010年10月27日 ... -
自动化测试规范小结
2012-01-20 01:34 744自动化测试规范小结 20 ... -
自动获取CPU使用率的脚本
2012-01-20 01:34 947自动获取CPU使用率的脚本 2011年04月28日 一个 ... -
业务组件学习资料
2012-01-20 01:34 580业务组件学习资料 2011 ... -
2012-1-12
2012-01-19 09:24 6042012-1-12 2012年01月12日 瀹剁┓浜轰 -
怎么使用quicktime
2012-01-17 01:58 753怎么使用quicktime 2011年1 ... -
OCI
2012-01-17 01:58 848OCI 2011年12月05日 OCI编程的一般过程 ... -
ROM修改制作工具软件集合
2012-01-17 01:58 1129ROM修改制作工具软件集合 2012年01月11日 1. ... -
游戏编程101
2012-01-17 01:58 626游戏编程101 2011年11月21 ... -
MonoTouch:用.net开发iPhone应用
2012-01-17 01:58 577MonoTouch:用.net开发iPhone ... -
[转]GCC笔记
2012-01-15 21:15 652[转]GCC笔记 2010年03月23日 The His ... -
关于cgi库
2012-01-15 21:14 624关于cgi库 2009年07月02日 目前Web技术中生 ... -
嵌入式WEB服务器BOA的移植方法(三)
2012-01-15 21:14 534嵌入式WEB服务器BOA的移 ... -
SAMSUNG S3C2440的简易BootLoader ㈢
2012-01-15 21:14 570SAMSUNG S3C2440的简易BootLoa ...
相关推荐
2 自己的系统调用,只有系统调用实现的代码,系统调用需要自己编译内核 3 字符设备驱动的编写(含代码和makefile) 4 GTK编写系统监视器,可以监测系统很多方面(含代码和makefile) 5 虚拟文件系统(实现的比较简单...
有关系统调用手册的linux内部资料~一些系统调用的内建函数~
使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上的中断信号(即按DEL键);当捕捉到中断信号后,父进程用系统调用Kill()向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止: ...
2 自己的系统调用,只有系统调用实现的代码,系统调用需要自己编译内核 3 字符设备驱动的编写(含代码和makefile) 4 GTK编写系统监视器,可以监测系统很多方面(含代码和makefile) 5 虚拟文件系统(实现的比较简单...
哈工大的操作系统实验二,今年老师要求制作PPT,于是良心制作,详细记录了系统调用的相关操作及实验结论,希望对有心人有用,大家共同进步
libc 函数库定义的一些 API 内部使用了系统调用的封装例程,其主要目的是发布系统调用,使程序员在写代码时不需要用汇编指令和寄存器传递参数来触发系统调用。一般每个系统调用对应一个系统调用的封装例程,函数库...
系统调用在内核中都是必不可少的一部分,ARM 结构对系统调用的支持相比其他 架构有很多改进,其化繁为简,为开发者提供了一个便捷的方法添加一个新的系统 调用。这里涉及 ARM 架构的系统调用表 syscall.tbl, 以及 ...
以linux2.4内核为基础添加用户自己的系统调用。该文档详细介绍了添加系统调用的过程。
在Linux的世界里,我们经常会遇到系统调用这一术语,所谓系统调用,就是内核提供的、功能十分强大的一系列的函数。这些系统调用是在内核中实现的,再通过一定的方式把系统调用给用户,一般都通过门(gate)陷入(trap)...
掌握用户程序如何利用系统调用与操作系统内核实现通信的方法,加深对系统调用机制的理解;进一步掌握如何向操作系统内核增加新的系统调用的方法,以扩展操作系统的功能。 1.向Linux 内核增加新的系统调用,系统调用...
实验目标:在Linux内核中增加一个系统调用,并编写对应的linux应用程序。利用该系统调用能够遍历系统当前所有进程的任务描述符,并按进程父子关系将这些描述符所对应的进程id(PID)组织成树形结构显示。 实验环境:...
在Linux环境下添加新的系统调用
在linux 2.6.30中添加系统调用,认识linux系统调用运行原理。
Linux系统调用的编程技术,添加一个自定义系统调用
在Linux中添加新的系统调用,涉及到Linux内核,以及添加系统调用后的重新编译内核,最后对新的系统调用的测试,本人课程设计亲自实现了!
unix系统调用大全,用实例讲解unix/linux 系统调用,可作为工具看看
北大Nachos系统调用实习报告
linux系统调用 和 进程 pdf 文档
介绍了通过添加新文件、或修改源文件的方法添加一个新的系统调用。 本文有添加及编译的详细步骤截图与分析,可作为操作系统课程设计。
linux 系统调用,理解linux 系统调用