Linux内核学习-系统调用

第五章 系统调用

    用户空间进程和硬件设备之间添加了一个中间层,该层主要有三个作用:

     1.提供硬件抽象接口

     2.保证系统的安全与稳定

     3.虚拟系统的实现,包括多任务与虚拟内存的实现

    

    一般情况下,应用程序通过应用编程接口(API)而不是直接通过系统调用来编程。一个API定义了一组应用程序使用的编程接口,它们可以实现一个系统调用,也可以通过多个系统调用来实现,或者不使用系统调用。在Unix世界中,最流行的应用编程接口是基于posix标准的,其目标是提供一套可移植操作系统标准,Linux与POSIX兼容

       C库实现了Unix系统的主要API包括标准C库函数和系统调用,由于C语言本身的特点,其他语言也可以很方便地把它们封装起来使用,此外C库提供了POSIX绝大部分API.Unix的界面设计有一句通用的格言“提供机制(需要实现什么样的功能)而不是策略(怎么实现这些功能)

       Linux中所有的系统调用的命名规则均是以sys_开头,每个系统调用都被赋予了一个调用号,调用号一旦分配就不可变更。Linux系统调用比其他许多操作系统执行都要快很多,Linux令人难以置信的上下文切换时间是一个非常重要的原因,内核进出都被优化的非常简洁高效。

      应用程序不能直接执行内核代码,因为内核驻留在受保护的地址空间上,应用程序是通过软中断的方式通知系统,告诉内核自己需要执行一个系统调用,x86系统上的软中断由int $0x80指令产生,这会触发一个异常导致系统切换到内核态并执行第128号异常处理程序,这个程序叫做system_call()

      指定恰当的系统调用,系统调用时需要把系统调用号一并传给内核,在x86中,系统调用号是通过eax寄存器传递给内核的。在陷入内核前,用户空间就把相应系统调用所对应的号放入eax中了。system_call()通过将给定的系统调用号与NR_syscalls做比较来检查其有效性,如果它大于或等于NR_syscalls返回-ENOSYS.由于系统调用表中俄表项以32位(4字节类型)存放的,所以内核需要将给定的系统调用号乘以4得到该命令的位置。call *sys_call_table(,&eax,4),除了系统调用号的传递还有参数的传递也是通过寄存器传递的,一般通过ebx,ecx,edx,esi,edi按顺序存放前五个参数,内核返回给用户空间的值也是通过eax寄存器传递的。

     系统调用的实现:在实现系统调用时要时刻注意可移植性和健壮性,不仅考虑当前还要为将来做打算。

      1.参数验证,系统调用必须要检查参数是否合法有效,与文件I/O相关的系统调用必须检查文件描述符是否有效,与进程相关的函数必须检查提供的PID是否有效,最重要的一项检查是检查用户提供的指针是否有效,必须保证:

          (1)指针指向的内存区域属于用户空间,进程绝不能哄骗内核去读内核空间的数据

          (2)指针指向的内存区域在进程的地址空间,进程绝不能哄骗内核去读取其他进程的数据

          (3)如果是毒该内存标记为读,如果是写内存标记为写,进程绝不能绕过内存访问限制

         内核提供了两个方法完成必须的检查和内核空间与用户空间之间数据的来回拷贝。为了内核向用户空间写数据,有copy_to_user(),需要三个参数,一个是进程空间中的目的内存地址,第二个是内核空间的源地址,第三个是需要拷贝的数据长度。为了从用户空间读取数据,同样有copy_from_user(),与copy_to_user()相似。如果执行失败,两个函数都是返回没能完成拷贝的字节数,成功返回0.

       最后一项检查是否有合法权限,现在的系统一般都是通过capable()函数来检查是否有权限能对指定资源进行操作。如capable(CAP_SYS_NICE)可以检查调用者是否有权利改变其他进程的nice值。

     

      绑定一个系统调用的步骤(当编写完一个系统调用后,把它注册成一个正式的系统调用是一件琐碎的事情):

      1.首先,在系统调用表的最后加入一个表项,分配系统调用号。

      2.对于所支持的各种体系结构,系统调用号必须在<asm/unistd.h>中定义

      3.系统调用必须被编译进内核映像,这只要把它放进kernel/下的一个相关文件中就可以了

      例子:我们虚构一个系统调用foo(),首先我们要把sys_foo加入到系统调用表里面去,对于大多数体系结构来说,该表位于entry.s文件中:形式如下:

     ENTRY(sys_call_table)

            .long sys_restart_syscall

            .long sys_exit

            …

           .long sys_mq_getsetattr

           .long sys_foo(加到末尾)

    接下来把系统调用号加入到<asm/unistd.h>

        #define _NR_restart_syscall    0

       ….

        #define  _NR_foo                   283

     最后实现系统调用foo()无论何种配置都需要编译到内核映像中去所以把它放进kernel/sys.c文件(或其他功能紧密相关的文件如kernel/sched.c等)中去,实现调用逻辑。

     

      通常系统调用靠C库支持,用户程序通过包含标准头文件并和C库连接使用系统调用,而自己编写的系统调用glibc库恐怕不会支持了,这个情况下,Linux提供了一组宏用于直接进行系统调用,这些宏是_syscalln()其中n的范围从0到6,代表需要传递给系统调用的参数个数,如open(const char *filename,int flags,int mode)三个参数,不依靠库的支持则直接调用此系统调用的宏形式为:

     #define _NR_OPEN 5

      _syscall3(long,open,const char *,filename,int,flags,int ,mode)

      对于每个宏来说都有2+2xn个参数,第一个参数对应系统返回值类型,第二个参数对应系统调用名称,再以后就是该系统调用参数的顺序排列每个参数的类型和名称。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值