用户空间进程和硬件设备之间经过系统调用来交互,其主要做用有三个。html
系统调用是用户空间访问内核的惟一手段;除异常和陷入外,是内核惟一合法的入口。linux
应用程序经过在用户空间实现的应用编程接口(API)而非直接经过系统调用来编程。程序员
POSIX是应用编程接口的一个国际标准,C库提供了POSIX的绝大部分API。编程
Unix接口设计的特色:提供机制(须要实现什么功能)而非策略(怎样实现这些功能)。Unix系统调用抽象出了用于完成某种肯定的目的的函数,而至于函数是如何实现功能的则并不关心,从程序员的角度来看,只需经过接口即可实现功能。api
要访问系统调用,一般经过C库定义的函数调用来进行。例如,getpid()系统调用,在内核中的实现为:安全
SYSCALL_DEFINED0(getpid) { return task_tgid_vnr(current);//return current->tgid }
SYSCALL _ DEFINED0只是一个宏,定义一个无参数的系统调用,展开后的代码以下函数
asmlinkage long sys_getpid(void)
asmlinkage为限定词,是一个编译指令,通知编译器仅从栈中提取该函数的参数。函数返回值在用户态时为int,在内核态为long。性能
在Linux系统中每一个系统调用被赋予一个系统调用号,当用户空间的进程执行一个系统调用时,系统调用号用来指明执行哪一个系统调用学习
系统调用号一旦分配就不会再更改,被删除的系统调用号也不准再回收。设计
sys _ ni _ syscall()专门针对无效的系统调用而设立的,只负责返回-ENOSYS。
系统调用号被定义在arch/i386/kernel/syscall_64.c文件中。
X86中,系统调用号经过eax寄存器传递给内核,system _ call()函数经过将给定的系统调用与NR _syscalls做比较来检查其有效性,若是大于或等于NR _syscalls就返回-ENOSYS,不然就执行相应的系统调用:
call *sys_call_table(,%eax,8)//基址+偏移量*8
上一篇博客中有详细的记录,这里再也不赘述。见http://www.cnblogs.com/July0207/p/5277774.html
因为系统调用在内核空间执行,因此必须验证其参数是否合法有效,并且必须是正确的。
检查用户提供的指针是否有效,在接收这个指针以前,必须保证:
内核提供了两个方法来完成必须的检查内核空间与用户空间数据的来回拷贝。
为了向用户空间写数据,内核提供了copy _ to _user(),它须要三个参数,第一个是进程空间中的目的内存地址,第二个是内核空间内的源地址,第三个是须要拷贝的数据长度(字节数)。
为了从用户空间读数据,内核提供了copy _ from _user(),该函数把第二个参数指定位置上的数据拷贝到第一个参数的指定位置,第三个是须要拷贝的数据长度(字节数)。
若是运行成功,则返回0,若是失败,则返回没能拷贝成功的字节数。
调用capable()函数检查用户是否有权对指定资源进行操做,返回非0值则有权限,返回0无权限。
<linux/capability.h>中包含一份全部权能和其对应的权限列表。
内核在执行系统调用时处于进程上下文。在进程上下文中,内核能够:
Linux自己提供了一组宏,用于直接对系统调用进行访问。他会设置好寄存器并调用陷入指令。该宏必须了解到底有多少参数按照怎样的顺序压入寄存器。
_syscalln() //n的范围从0到6,表明须要传递给系统调用的参数个数。
例如,open()系统调用的形式是:
long open(const char *filename, int flags, int mode) 等价于 #define NR_open 5 _syscall3(long,open,const char*,filename, int,flags, int,mode)
对于每一个宏来讲,都有(2+2xn)个参数:
1.系统调用的返回值类型
2.系统调用的名称
3及之后是按照系统调用参数的顺序排列每一个参数的类型和名称。
_ NR _open在<asm/unistd.h>中定义。这个宏会被扩展成为内嵌汇编的C函数。
好处:
问题:
替代方法: