Linux内核中的系统调用

系统调用是用户空间访问内核的唯一手段,除异常和陷入外,它们是内核唯一的合法入口。其实,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。一般应用程序中的API调用C库,C库再调用内核中的系统调用。在Unix中,最流行的应用编程接口是基于POSIX标准的,C库提供了POSIX的绝大部分API。

如何定义一个系统调用呢?

asmlinkage long sys_test(void)

注意函数声明中的asmlinkage限定词,这是一个编译指令,通知编译器仅从栈中提取该函数的参数,所有的系统调用都需要这个限定词。另外,系统调用在内核空间和用户空间有着不同的返回值,在用户空间为int,在内核空间为long。最后函数名是以sys_打头,这个系统调用函数名的命名规定。

每一个系统调用都有一个系统调用号,内核中记录了系统调用表中的所有已经注册过的系统调用列表存储在sys_call_table中,这个表为每一个有效的系统调用指定了唯一的系统调用号。

由于内核驻留在受保护的地址空间中,所以用户空间程序无法直接访问内核空间。通知内核的机制是靠软中断实现的,这个软中断都是通过执行int $0x80指令触发。通过引发一个异常来促使系统切换到内核态去执行异常处理程序,这个异常处理程序就是系统调用处理程序system_call

因为所有的系统调用陷入内核的方式都是执行int $0x80指令,然后进入系统调用处理程序system_call。仅仅这么做是不能区分所有的系统调用的,我们需要在陷入内核之前,用户空间就把相应的系统调用放入EAX中,这样系统调用处理程序一旦运行就可以从EAX中得到数据。

除了系统调用号外,大部分系统调用还需要一些外部额的参数输入,在X86-32系统中,EBX,ECX,EDX,ESI,EDI按照顺序存放前五个参数,如果不够则再用一个单独的寄存器。

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

在接收一个用户空间指针之前,内核必须保证:不能哄骗内核去读取内核空间数据;不能哄骗内核去读取其他进程的数据;不能绕过内核访问权限。内核提供了两种方法来完成必须的检查和内核空间和用户空间之间的数据拷贝,copy_to_user和copy_from_user,这两个函数都会引起阻塞。

内核在执行系统调用的时候处于进程上下文,在进程上下文中,内核可以休眠(比如系统调用阻塞或显式调用schedule函数的时候),并且可以被抢占。

系统调用返回的时候,控制权仍在system_call中,它最终会负责切换到用户空间,并让用户继续执行下去。

内核设计系统调用:

主要完成系统调用函数编写,系统调用号,系统调用表的填写。

step1: 在/include/linux/syscalls.h中增加要添加的系统调用的声明 

  asmlinkage long sys_test(void);

step2: 在/arch/arm/include/asm/unistd.h中增加系统调用号的定义 

  #define  __NR_test (__NR_SYSCALL_BASE+361)

step3: 在/kernel/sys.c中实现系统调用函数 

  asmlinkage long sys_test(void)
{
  printk("pid: %d,this is test call.\n",current->pid);
  return 0;
}

step4: 在/arch/arm/kernel/calls.S中加入系统调用表的初始化部分 

  /* 361 */      CALL(sys_test)

step5: 生成内核镜像

应用层测试程序

#include <linux/unistd.h>

#include <stdio.h>

#include <errno.h>

int main()

{

int i = 10;

i = syscall(361);

printf("after  syscall: i=%d\n",i);

return 0;

}

测试结果

pid: 859, this is test call.

after syscall:i=0

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/2b86ff868e6e55b6a2691eeb374e9b48.html