现在的位置: 首页 > 综合 > 正文

深入理解linux内核笔记六:linux系统调用

2013年08月11日 ⁄ 综合 ⁄ 共 3808字 ⁄ 字号 评论关闭

 

一般情况下,进程不能存取系统内核的,只有系统调用是一个例外,在intel结构的计算机中,是通过中断0x80实现的

进程可以可以跳转到内核中的位置是system_call。在此检查系统调用号,它告诉内核进程请求何种服务,然后查找系统调用表sys_call_table,找到希望调用的内核地址函数,调用此函数,然后返回。

      

1、  宏定义(unistd.h)

_syscallN(type,name,x…)N是系统调用所需参数数目,type是返回类型,name是面向用户的系统调用函数名,x…是参数,个数为N

如:#define _syscall1(type,name,type1,arg1) /

type name(type1 arg1) /

{ /

long __res; /

__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" /

: "=a" (__res) /

: "0" (__NR_##name),"ri" ((long)(arg1)) : "memory"); /

__syscall_return(type,__res); /

}

分析:

ouput _res 0%  __NR##name  “=a”表示存放在eax寄存器

       也就是表示把变量_res存放在eax中,“0”表示把_NR##name关联到0%_res.返回值也存放在eax中.

 

       #define _syscall2(type,name,type1,arg1,type2,arg2) /

type name(type1 arg1,type2 arg2) /

{ /

long __res; /

__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" /

       : "=a" (__res) /

       : "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)) /

              : "memory"); /

__syscall_return(type,__res); /

}

同理可以知道arg1存放在eax中,arg2存放在ebx

 

当我们在程序中用到系统调用时,对于linux预定义的系统调用,编译器会在预处理时自动加入宏_syscallN()并将其展开,对于自己添加的系统调用,需要在程序中显示地调用宏_syscallN

 

2         系统调用入口函数

_syscallN()中执行了int 0x80后,程序进入内核态,我们在陷阱门和系统门的初始化里做了如下设置

set_system_gate(SYSCALL_VECTOR,&system_call);

程序跳到system_callentry.s)处

ENTRY(system_call)

        pushl %eax                      # save orig_eax

        SAVE_ALL

#ifdef __SMP__

        ENTER_KERNEL

#endif

        movl $-ENOSYS,EAX(%esp)

        cmpl $(NR_syscalls),%eax

        jae ret_from_sys_call

        movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax

        testl %eax,%eax

        je ret_from_sys_call

#ifdef __SMP__

        GET_PROCESSOR_OFFSET(%edx)

        movl SYMBOL_NAME(current_set)(,%edx),%ebx

#else

        movl SYMBOL_NAME(current_set),%ebx

#endif

        andl $~CF_MASK,EFLAGS(%esp)

        movl %db6,%edx

        movl %edx,dbgreg6(%ebx)

        testb $0x20,flags(%ebx)

        jne 1f

        call *%eax

        movl %eax,EAX(%esp)

        jmp ret_from_sys_call

这段代码现保存所有的寄存器值,然后检查调用号(__NR_name)是否合法(在系统调用表中查找),找到正确的函数指针后,就调用该函数(即你真正希望内核帮你运行的函数)。运行返回后,将调用ret_from_sys_call,这里就是著名的进程调度时机之一。

 

调用号定义如下(unistd.h)

#define __NR_restart_syscall      0

#define __NR_exit          1

#define __NR_fork          2

#define __NR_read          3

........................

#define __NR_sync_file_range 314

#define __NR_tee 315

#define __NR_vmsplice   316

 

#define NR_syscalls 317

 

以调用号_NR_name为下标,找出系统调用表sys_call_table中对应的表项,正好就是系统调用响应函数sys_name的入口地址

 

系统调用响应函数(syscall_table.s)

ENTRY(sys_call_table)

long sys_restart_syscall      /* 0 - old "setup()" system call, used for restarting */

.long sys_exit

       .long sys_fork

.long sys_read

.long sys_write

............

.long sys_tee                  /* 315 */

.long sys_vmsplice

 

3 实例

 

现在我们知道增加一条系统调用我们首先要添加服务例程实现代码,然后在进行对应向量的申明,最后当然还要在sys_call_table表中增加一项以指明服务例程的入口地址。

OK,有了以上简单的分析,现在我们可以开始进行源码的修改,假设我们需要添加一条系统调用计算两个整数的平方和,系统调用名为add2,我们需要修改三个文件:kernel/sys.c , arch/i386/kernel/entry.S include/asm-386/unistd.h

  1、修改kernel/sys.c ,增加服务例程代码:

  asmlinkage int sys_add2(int a , int b)

    {

      int c=0;

      c=a*a+b*b;

      return c;

    }

2、修改include/asm-386/unistd.h ,对我们刚才增加的系统调用申明向量,以使用户或系统进程能够找到这条系统调用,修改后文件如下所示:

  .... .....

  #define _NR_sendfile   187

  #define _NR_getpmsg    188

  #define _NR_putmsg    189

  #define _NR_vfork     190

  #define _NR_add2     191   /* 这是我们添加的部分,191即向量 */

  3、修改include/asm-386/unistd.h , 将服务函数入口地址加入 sys_call_table,首先找到这么一段:

  .... .....

  .long SYMBOL_NAME(sys_sendfile)

  .long SYMBOL_NAME(sys_ni_syscall) /* streams 1 */

  .long SYMBOL_NAME(sys_ni_syscall) /* streams 2 */

  .long SYMBOL_NAME(sys_vfork) /*190 */

  .rept NR_syscalls-190

  修改为如下:

  .... .....

  .long SYMBOL_NAME(sys_sendfile)

  .long SYMBOL_NAME(sys_ni_syscall) /* streams 1 */

  .long SYMBOL_NAME(sys_ni_syscall) /* streams 2 */

  .long SYMBOL_NAME(sys_vfork) /*190 */

  .long SYMBOL_NAME(sys_add2) <=我们的系统调用

  .rept NR_syscalls-191 <=190改为191

  OK,大功告成,现在只需要重新编译你的LINUX内核,然后你的LINUX就有了一条新的系统调用int add2(int a, int b)

       4 使用新的系统调用
       在应用程序中使用新添加的系统调用add2。  

  #include <linux/unistd.h>
       _syscall2(int,add2,int,a, int, b)
        main()
       {

int a = 1;

int b =2;

printf("result = %d /n",add2(a,b));  
  } 

 

抱歉!评论已关闭.