系统调用之二:参数传递

1.参数传递 

    在linux中,system_call和sysenter_entry是所有系统调用的公共入口,每个系统调用至少有一个参数,即系统调用号,如前所述,该参数通过eax寄存器传递。例如一个应用程序调用fork,那么在执行int 0x80或者sysenter之前,eax寄存器被设置为2。

   

     普通C函数通过将参数的值压入到进程的栈中进行参数的传递。由于系统调用是从用户态到内核态的一种特殊的函数调用,没有用户态或者内核态的堆栈可以被用来在调用函数和被调函数之间进行参数传递。系统调用通过CPU的寄存器来进行参数传递。在进行系统调用之前,系统调用的参数被写入CPU的寄存器,而在实际调用系统服务例程之前,内核将CPU寄存器的内容拷贝到内核堆栈中,实现参数的传递。

      那么为什么不直接将参数从用户态的栈中直接传递到内核态的堆栈呢?首先,同时处理两个栈是非常复杂的;对CPU寄存器的使用使系统调用例程的结构和其他的异常处理函数类似。

     然而,为了使用寄存器进行参数传递,以下两个条件必须得到满足:

  • 每个参数的长度不能超过一个寄存器的长度(32位)
  • 除了系统调用号外,参数的数目不能超过6个。

   对于第一个条件,根据POSIX标准,一个大于32位的参数必须作为引用被传递。一个例子是settimeofday()系统调用,它必须读取一个64位的数据结构。

     对于第二个条件,多于六个参数的系统调用是存在的,如何处理这种情况呢?一个寄存器指向进程地址空间的一块内存区域,该内存中保存参数的值,此种方式类似于进程间通过共享内存进行参数的传递。在每个系统调用发生时,参数被自动保存在进程的用户态栈中,封装的例程将根据各种不同的情况采取何时的方式将参数传递到内核中。

    eax,ebx,ecx,edx,esi,edi,ebp 被用来传递参数。这些寄存器的值通过SAVE_ALL宏保存到内核栈中,当系统调用服务例程访问这个栈时,其首先看到返回到system_call的地址,接着是 ebx,等等。栈的配置和普通的C语言函数是一样的,因而服务例程很容易的获取相应的参数。

    例子:

    有些系统调用不需要参数,然而其相应的服务例程需要知道系统调用之前CPU寄存器的内容。例如,fork系统调用的实现do_fork,该函数需要知道寄存器的内容,以便将这些值拷贝到子进程的thread字段。在这种情况下,一个类型为pt_regs的参数可以使服务例程访问由SAVE_ALL保存在内核栈中的值。例如

   

     服务调用的返回值必须被存进eax中。服务调用没有出参。 

2.参数校验

     在满足一个用户请求的系统调用之前,内核需要对所有参数进行检验。校验的类型依赖于特定的系统调用和参数。例如,对于write系统调用,sys_write服务例程必须检查fd参数是先前打开的一个文件描述符,并且该进程有写该文件的权限。

      然而,有一种类型的检验对于所有的系统调用是共有的:无论何时传递了一个地址参数,内核都必须检查该地址属于该进程的地址空间。进行此项检验,有如下两种可能的方法:

  • 校验线性地址是否属于该进程地址空间。
  • 仅查看该线性地址是否比PAGE_OFFSET小

  由于第一个方式需要花费太多的时间,因而目前的linux版本采用第二种较为简单的检查方式。假如真的有一个非法(即不属于本进程地址空间的地址)地址被作为参数传递到服务例程,那么通过内核的Page Fault异常处理流程进行处理。

   既然可以通过page fault异常处理来保证对异常地址的访问,那么为什么还要检查地址是否属于内核?由于内核例程可以访问内存中的所有页,因而如果用户传递属于内核地址空间的一个地址做为参数,那么就能够读写内存中的任意页,而不会引起page fault异常。

      对传递给内核地址的检查是通过access_ok宏进行的,该宏接收两个参数:size 和addr

 

     该函数检查addr+size之后,是否会造成值的溢出;然后检查两者之和是否小于current->thread_info()->addr_limit.seg,对于普通进程,该变量值为PAGE_OFFSET,对于内核线程,此值为全地址空间的大小,比如对于32位,值为0xffffffff。同时,该值可以通过宏get_fs 和set_fs进行修改,这允许内核不经过access_ok的检查,直接调用服务例程,这就是内核的特权。 

3.访问进程空间 

     现在,我们已经了解将用户态的参数如何传递给内核及内核对这些参数的处理。那么除了通过eax将返回值告知用户态外,内核服务例程怎样将一些运行结果传回给用户态呢?可以通过get_user,put_user这两个接口本质上也是通过CPU寄存器进行数据在用户态和内核态的交互,同时也需要进行access_ok检查。类似的接口还有:

---------------

get_user   __get_user

 

copy_to_user  __copy_to_user

______________________ 

 

等等。需要注意的是,带有两道下划线的接口会跳过地址的有效性检查,因而更加有效率。

 

 

 

4.页错误处理

     保留

 

 

5.内核封装接口

 

     在内核态可以直接通过_syscall0到_syscall6这七个宏进行系统调用,调用相关的服务例程。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

proware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值