• 【内核的设计与实现笔记】| 【03】系统调用


    1 简介

    • 在用户空间进程硬件设备间添加的中间层;
    • 主要是为了让应用程序受限(保护设备的安全性)的访问硬件设备;
    • 提供公共的接口,让每个进程都能够使用;
    • 是用户空间访问内核的唯一合法手段,其他可通过异常陷入

    2 API、POSIX、C库

    • 应用程序通过用户空间实现的API而不是直接通过系统调用,不能直接执行内核代码;
    • POSIX是最流行的基于Unix的可移植的系统标准;
    • C库实现Unix系统的主要API,包括C库函数以及系统调用接口
      在这里插入图片描述

    3 系统调用

    • 一般通过C库的函数定义来进行;
    • 当系统调用错误时,C库会将错误码写入到errno中,用户可通过perror打印错误字符串查看;

    如何定义一个系统调用

    • 系统函数中都需要声明asmlinkage,是一个编译指令,通过编译器仅从中提取该函数的参数
    • 函数中返回long是为了兼容32位和64位;
    • 系统调用在用户空间和内核空间有不同的返回值类型(int、long);

    系统调用号

    • 每个系统调用都有唯一的一个号码,指明执行的哪个系统调用,在进程中使用;
    • 一旦分配不能变更,且不能被删除,由于不能被回收,会通过sys_ni_syscall回收;
    • 内核会将号码存储在sys_call_table中;

    系统调用的性能

    • 相比与其他系统速度要快;

    • 上下文件切换速度快,函数处理本身简洁

    4 系统调用处理程序

    • 程序通过软中断通知系统,告知内核需要执行一个系统调用,希望系统切到内核态,故内核可代表程序在内核空间执行系统调用;

    软中断

    • 通过引发一个异常来让系统切换到内核态去执行程序;
    • x86系统上软中断的号码位128(int $0x80)来触发,程序名为system_call
    • 而后x86处理器增加一条sysenter指令,速度更快;

    指定恰当的系统调用

    • 在系统调用陷入内核中,必须把系统调用号传给内核,x86上在陷入内核前,用户空间将相应的调用号存储在eax中,当调用处理程序是,内核即可在eax中获取;

    • system_call

      • 通过NR_syscalls将给定的系统调用号检查有效性,错误返回-ENOSYS
      • 由于表中是以64位类型存放,故需要将给定的系统调用号乘4,在去查询位置;

    参数传递

    • 在x86-32系统上,ebx、ecx、edx、esi、edi按顺序存放函数中前5个参数;
    • 若超过6个参数,则应该用一个单独的寄存器存放指向所有该参数在用户空间地址的指针;
    • 返回值通过eax寄存器存放;

    5 系统调用的实现

    • 不提倡多用途的系统的调用,函数一般要简洁参数少,且要稳定
    • 可通过标志来增加新的功能和选项,不会破环系统的兼容性;
    • 函数设计的通用性要强;

    参数验证

    • 检查所有参数的合法性,保证内核的安全稳定
    • 针对于指针:
      • 若指向用户空间,进程不可使用它去读取内核的数据;
      • 若内存为进程的地址空间,进程不能为去获取其他进程的数据;
      • 权限需要一致,不能修改该指针的权限;

    内核提供两种方法在用户和内核间进行数据拷贝

    • 【copy_to_user】:内核往用户写数据,参1目的内存地址,参2内核源地址,参3数据长度;
    • 【copy_from_user】:从用户中拷贝数据到内核;
    • 上述函数都会引起阻塞(用户数据的页被换出到硬盘时),此时进程会休眠,直到程序从硬盘换到物理内存;

    超级权限

    • 可通过capable()来检查是否对指定的资源进行操作;

    6 系统调用上下文

    • 内核在执行系统调用时处于进程上下文
    • 在进程上下文中,内核可被休眠抢占,故需要保证系统调用为可重入
    • 当系统调用返回时,控制权依旧在system_call中,它会负责切换到用户空间,并执行剩下的;

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

    • 编写完成后,需要注册成一个正式的系统调用;、
      • 在系统调用表的最后一个表项添加,从0开始;
      • 系统调用号必须定义于asm/unistd.h中;
      • 系统调用需要被编译进内核映像,不可被编译成模块,放入kernel下的相关文件中;

    从用户空间访问系统调用

    • Linux提供一组宏syscalln(),用于直接对系统调用进行访问,会设置好寄存器并调用陷入指令;
    • 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);
    		//返回值类型, 调用名,参数类型,参数名....
    
    • 1
    • 2
    • 3
    • 4
    • 5

    7 优缺点

    优点

    • 系统调用创建容易且使用方便
    • Linux的系统调用性能高

    缺点

    • 首先需要有系统调用号,需要由内核开发官方分配
    • 系统调用会被固化,不允许改动;
    • 需要将系统调用分别注册到每个需要支持的体系结构中;
    • 在脚本中不容易调用系统调用,且不能从文件系统直接访问系统调用;
  • 相关阅读:
    归档:2022-11-17
    docker配置独立桥接IP
    【mysql 提高查询效率】Mysql 数据库查询好慢问题解决
    使用 Spring Security 实现安全认证的 Spring Boot 应用
    技术分享| 快对讲综合调度系统
    【运维】Linux基础(学习笔记)
    Android开发基础教程(2019)第17集 页面导航 Navigation(1)笔记
    关于ssh的使用
    C++ 多态(补充)
    一步一步分析ChatGPT,1 粘性,2 传染性, 3 双边网络效应
  • 原文地址:https://blog.csdn.net/weixin_45926547/article/details/127856167