• 《Linux内核设计与实现》读书笔记 第五章 系统调用


    一、与内核通信

    1. 什么是系统调用

    - 让应用程序受限的访问硬件设备
    - 提供创建新进程并与已有进程通信的机制,
    - 提供申请操作系统其他资源能力
    
    • 是用户空间进程和硬件设备之间的中间层

    2. 作用

    • 硬件的抽象接口:用户程序通过系统调用来使用硬件,而不用关心具体的硬件设备,简化了用户程序的开发。

    • 保证系统的稳定与安全:基于某些规则的访问控制。

    • 增强系统的稳定性。

        - 在Linux中,系统调用是用户空间访问内核的唯一手段
        - 除异常和陷入外,他们是内核唯一的合法入口
      

    3. API、POSIX和C库

    关于Unix接口设计:提供机制而不是策略
    

    二、系统调用

    1. 如何定义一个系统调用

    asmlinkage long sys_getpid(void)
    
    • 限定词:asmlinkage
    • 函数返回值类型:long
    • 符合命名规则的命名:sys_getpid

    2. 系统调用号

    每个系统调用被赋予一个系统调用号,系统调用发生时,内核就是根据传入的系统调用号来知道是哪个系统调用的。
    
    • 系统调用号一旦分配无法变更。
    • 在x86架构中,用户空间将系统调用号是放在eax中。
    • 内核记录了系统调用表中的所有已经注册过的系统调用列表,存储在sys_call_table中。x86-64中该表定义在arch/i386/kernel/syscall_64.c中。

    3. 系统调用的性能

    • 设计原则:简洁、高效
    • 原因:很短的上下文切换时间

    三、系统调用处理程序

    1. 通知内核

    • 用户程序无法直接执行内核代码,由于内核驻留在受保护的地址空间上,不能直接调用内核空间中的函数。
    • 应用程序以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,内核就可以代表应用程序在内核空间执行系统调用。
    • 通知内核的机制是靠软中断实现的:通过引发异常来粗来系统切换内核态执行异常处理程序(系统调用处理程序)。

    重要的概念:用户空间引起异常或陷入内核
    

    2. 指定恰当的系统调用

    • eax寄存器:将系统调用号传递给内核
    • system_call():与NR_syscall比较,检查有效性
    • call *sys_call_table(,%rax,8):执行相应的系统调用

    3. 参数传递

    • x86系统,ebx,ecx,edx,esi,edi按顺序存放前五个参数。
    • 需要6个及以上参数,应用一个单独的寄存器存放指向这些参数在用户空间地址的指针。
    • 返回值存放在eax。

    四、系统调用的实现

    1. 实现系统调用

    • 第一步:决定它的用途
    • 原则:用途明确、简洁稳定、通用、可移植、健壮。

    2. 参数验证

    • 参数合法有效并正确:不应让内核访问无权访问的资源

    • 最重要的检查:用户提供的指针是否有效。内核必须保证指针:

        - 指向的内存区域属于用户空间;
        - 指向的内存区在进程的地址空间里;
        - 指向的内存区在内存的访问权限范围中。
      
    • 两个方法检查在两空间之间数据的来回拷贝:

      • copy_to_user():向用户空间写入数据
      • copy_from_user():从用户空间读取数据
    • 针对是否有合法权限的检查

      • capable():是否有权对指定的资源进行操作
      • 返回0:无权操作

    五、系统调用上下文

    1. 进程上下文

    内核在执行系统调用的时候处于进程上下文
    
    • current指针指向当前任务。
    • 在进程上下文中,内核可以休眠、被抢占。
    • 当系统调用返回时,控制权仍然在system_call()中,负责切换到用户空间,并让用户进程继续执行下去

    2. 绑定一个系统调用的最后步骤

    • 编写完系统调用之后,将其注册成一个正式的系统调用

      • 在系统调用表中加入表项;
      • 系统调用号定义于<asm/unistd.h>中;
      • 编译进内核映像,放入kernel/下的相关文件。

    3. 从用户空间访问系统调用

    • _syscalln():Linux提供的一组宏,用于直接对系统调用进行访问。会设置好寄存器并调用陷入指令。
    • n的范围:0~6,代表传递给系统调用的参数个数。
    • 对每个宏来说,都有2+2*n个参数。
      • 第一个参数:对应系统调用返回值类型
      • 第二个参数:系统调用的名称
      • 按系统调用参数顺序排列的每个参数的类型和名称

    例:

    long open(const char *filename, int flags, int mode)
    
    #define NR_open 5
    _syscall3(long, open, const char*, filename, int, flags, int, mode)
    

    4. 不提倡通过系统调用实现

    • 建立一个系统调用的好处
      • 创建容易、使用方便
      • Linux系统调用的高性能
    • 问题
      • 系统调用号需要在内核处于开发版本时官方分配
      • 系统调用加入稳定内核后被固化,接口不允许做改动
      • 需要将系统调用分别注册到每个需要支持的体系结构中去
      • 脚本中不容易调用,不能从文件系统直接访问
      • 主内核树之外难以维护和使用
    • 替代方法
      • 某些接口可以用文件描述符表示
      • 把增加的信息作为文件放在sysfs的合适位置

    六、总结:关于“提供机制而不是策略”

    1. 机制与策略

    • 我觉得,机制就像是操作系统中的原语,由若干条指令组成,通过一段不可分割的或不可中断的程序实现某个特定的简单操作。对外暴露使用的方法,通用性强。
    • 策略就像是利用这些原语解决一个实际的互斥与同步问题,对原语的不同组合能够实现符合不同具体情况的功能。

    3. 系统调用

    • 系统调用在设计时,就是朝着“机制”的方向,如果是单纯为了某个具体的问题来创建系统调用,显然会降低其通用性。
    • Linux尽量避免每出现一种新的抽象就简单的加入一个系统调用,这使得它的系统调用接口简洁的令人叹为观止,低的新系统调用增添频率体现出Linux是一个相对较为稳定并且功能已经较为完善的操作系统。

    参考资料:《Linux内核设计与实现》(原书第三版)

  • 相关阅读:
    开博的缘由
    听了一节公开课 课后感
    python中的函数、变量和递归函数
    迭代器和生成器
    字符串的格式化
    html和htm的区别
    基本数据类型总结
    数据类型-----集合
    基础测试题(字符串、列表、元组、字典)
    基本数据类型-----字典(Dictionary)
  • 原文地址:https://www.cnblogs.com/hyq20135317/p/5297270.html
Copyright © 2020-2023  润新知