Chinaunix首页 | 论坛 | 博客
  • 博客访问: 33938
  • 博文数量: 6
  • 博客积分: 511
  • 博客等级: 下士
  • 技术积分: 72
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-19 08:49
文章分类
文章存档

2011年(3)

2010年(3)

分类: LINUX

2010-12-24 19:05:39

系统调用

                                                                              Hkc(完整部分参考lkd 3rd

在现代的操作系统中,均提供了很多用户与系统通讯的接口。这些提供的接口可以用来让应用程序访问或控制硬件,创建新的进程,进程间的进行通讯以及请求系统的资源。这些接口就是内核与应用的消息传递者。这些接口之所以存在的主要原因是使系统更加稳定。

当然还有其它原因,它为用户空间访问硬件提供了抽象的方法,更方便了系统资源的管理,对于一些资源的访问限制保证了系统的安全性及稳定性。在linux中,系统调用是唯一的用户与内核通讯的接口

       从应用程序的编写上来看,它们均与系统调用无关,它们所关心的只是API。而相应的内核则只关心系统调用,库的使用及应用程序如何使用系统调用,内核是不去关心的。然而对内核来说,它们应该跟踪潜在的使用系统调用及保证系统调用的通用性和易用性。

 

Syscalls   

       系统调用一般均是使用由c定义的库函数来实现。它们可以由0个,1个,或多个参数,也可以返回结果通知事件的成功与否。系统调用可能会引起边际效应,既所谓的引起硬件状态的改变或系统状态的改变等。c库中通常返回0意味着系统调用成功执行,而非0则可能出错,出错代码在系统调用返回时会被写入一个全局变量errno中,这个变量可以通过函数perror()转换成可理解的错误状态。

       系统调用一般都有指定的行为,例如系统调用getpid函数返回当前进程的pid,这个系统调用在内核中的实现非常简单:

SYSCALL_DEFINE0(getpid)

{

return task_tgid_vnr(current); // returns current->tgid

}

SYSCALL_DEFINE0为一个简单的宏,它定义了一个系统调用,这个系统调用是不带参数的,它的扩展如下:

asmlinkage long sys_getpid(void)

       从以上我们可以看出如何定义一个系统调用,首先使用asnlinkage修饰符来定义函数,它告诉编译器这个函数的参数只位于栈上;其次这个函数返回值为long型;最后注意getpid系统调用在内核中被定义为sys_getpid,这是linux中的命名习惯。

 

系统调用号

       linux中,每个系统调用均被指定一个系统调用号,对于指定的系统调用号它是唯一的,当在用户空间进程执行一个系统调用,系统调用号确定了哪个系统调用要被执行,而不是使用系统调用的名字来确定要执行的系统调用。

       它是非常重要的,而且不能改变,否则编译的程序将会出错。如果一个系统调用号被移除,它的系统调用号也不能被再次使用,否则先前编译的代码本意要调用一个原告的系统调用,而实际上却调用了其它的系统调用。Linux中提供了一个被称为不被使用的系统调用,它被用来弥补那些被移除或目前不存在的系统调用,其原型为sys_ni_syscall,它除了返回-ENOSYS,什么也不做。

       系统调用号被保存在系统调用表中,sys_call_table,这个表是体系相关的,比如arch/arm/kernel/calls.S文件中。

 

 

 

系统调用性能

       系统调用对linux来说是非常快的,这主要是由于linux上下文件切换的次数,进入及退出内核是流水线的,且是非常简单的事务。另一方面的因素是系统调用的处理简化及各个系统调用独自处理。

 

系统调用处理(是如何跳转至系统调用)

       用户是不可能直接去执行内核代码的,它们无法调用一个存在于内核空间中的函数,因为内核存在于一个被保护的存储空间。如果它们可以直接读写内核空间,那么内核的安全性及稳定性将不复存在。

       用户空间的应用程序必须通知内核它们想要执行一个系统调用,让系统切换至内核态模式下,系统调用执行于内核空间,它代表了用户空间的执行。这个通知机制使用软件中断来实现:产生一个异常,内核将会切换至内核态模式,执行异常处理程序。系统调用相应的命名为system_call,它是一个与体系相关的函数。

1,指出正确的系统调用

       简单的进入内核空间是不够的,因为有很多系统调用存在,这些系统调用之前进入内核态的模式均是相同的。因此系统调用号也必须传入内核。System_call()函数将会检查给定的系统调用号是否合法------NR_syscalls比较

 

2,系统调用参数传递

       除了要传递系统调用号外,多数系统调用还需要一到多个参数。那么在自陷过程中,用户空间必须要传递一些参数至内核。早期这些参数均通过寄存器来传递。当然返回值也是通过寄存器来传递的。

 

系统调用执行

       linux真正执行一个系统调用是非常简单的,因为我们不用去关心如何完成系统调用的处理,既真正去执行系统调用函数。困难的工作在于设计一个系统调用及执行系统调用,注册它至内核是相当简单的。以下为基本过程:

       首先,定义一个系统调用的目的,既做什么,它应该确且的只有一个目的。使用标志位来定义多个系统调用是不合适的,如ioctl函数。

       其次确定形参,返回值及错误码。接口函数要尽量简单,清晰,尽可能少的参数。

       最后要考虑到未来的兼容性及稳定性,提供的是访问机制,而不是测略。

 

确认参数的合法性

       系统调用必须仔细的指定它们的参数,以保证合法的参数的传递。因为系统调用在内核空间执行,如果用户传递的参数若没有限制,则内核的安全性及稳定性得不到保证。一个重要的检查是:确认用户空间传递的指针的合法性。内核需要做的是,1指针指向的为用户空间,它们没有在欺骗内核来读取内核空间数据;2指针指向本进程的地址空间,而不是指向

其它进程的地址空间;3它操作的目的区域的属性要符合区域的属性。内核提供了两个函数在内核与用户空间来传递数据,copy_to_user:拥有三个参数,第一个为目的存储地址,位于进程中,第二个参数为内核中源地址,第三个参数为准备拷贝字节数;copy_from_user,同样也有三个参数,第一个为内核空间的地址,第二个参数为用户空间地址,第三个为准备拷贝字节数。

 

系统调用的上下文切换

       在执行系统调用时,处于内核态,位于进程上下文,此时的current指向发起系统调用的那个进程。在进程上下文中,内核可以进行睡眼及完全的抢占。这两点非常重要,有能力进行睡眠意味着它可以使用内核大多数函数,简化了内核的编程。可抢占意味着当前用户空间的进程可能被另一个进程抢占,因为新进程可能执行同样的系统调用,我们必须保证系统调用的可重入性。当系统调用返回时,控制权依然在system_call中,它最后切换至用户空间执行用户空间的进程。

 

绑定一个系统调用(完成系统调用函数真正的开始)

       完成系统调用之后,要把它注册至正式的系统调用之中:

1,  在系统调用列表中加入编写的系统调用入口,这是与体系相关的文件中。比如arch\arm\kernel\calls.S文件中。

/* 320 */ .long       sys_get_mempolicy

              .long       sys_set_mempolicy

              .long       sys_cache_sync      //新加入系统调用

2,  在体系相关文件中定义系统调用号asm\unistd.h

#define __NR_cache_sync      (__NR_SYSCALL_BASE+322) //新增加系统调用号

3,  将系统调用编译至内核中。

asmlinkage long sys_cache_sync(const void __user *addr, u_int size, int direction)

{   

       Printk”a new system call was added .\n”;

       return 0;

}

 

用户空间使用系统调用

       通常c库提供了系统调用的支持,用户可以直接从标准头文件中提取出函数原型,链接c库以使用自己的系统函数。Linux提供了一系列的封装宏来实现访问系统调用,它设置寄存器内容,发起自陷指令。这些宏被命名为_syscalln()n=123456.n代表需要传递参数的映射。例如上例中,系统调用:

#define __NR_cache_sync     322

_syscall3(long,cache_sync,const void *,addr, u_int,size, int,direction)

然后即可简单的使用这个系统调用。对于每个宏有2+2*n个参数,如上面的_syscall3可以看出一共有2+2*3=8个参数。

#include

#include

#include

#include

#define __NR_cache_sync         322

_syscall3(long,cache_sync,const void *,addr, u_int,size, int,direction)

int main(int argc,char **argv)

{

        unsigned long result=0;

        result=cache_sync(&result,4,0);

        return 0;

}        

以上函数编译后执行可以看到内核中打印出:

a new system call was added.

 

 

为什么不使用系统调用:

       以上描述了系统调用的实现,但在任何情况下均不鼓励这样做。实际上你应该小心的使用并限制增加新的系统调用。

 

 

 

 

 

阅读(2287) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~