《Linux 内核设计与实现》05. 系统调用

news2024/11/23 19:09:33

文章目录

    • 内核通信
      • API、POSIX、C库
    • 系统调用
      • 系统调用号
      • 系统调用的性能
    • 系统调用处理程序
      • 指定恰当的系统调用
      • 参数传递
    • 系统调用的实现
      • 实现系统调用
      • 参数验证
    • 系统调用上下文
      • 绑定一个系统调用的最后步骤
      • 从用户空间绯闻系统调用

内核通信

系统调用在用户空间进程和硬件设备之间添加了一个中间层。

API、POSIX、C库

通常,应用程序通过在用户空间实现的应用变成接口(API)来进行系统调用,即应用程序调用 C 库,而库函数内部进行系统调用,而不是直接调用系统调用。

POSIX、API、C 库以及系统调用之间的关系:

在 Unix 中,最流行的的应用编程接口是基于 POSIX 标准的。

系统调用

要访问系统调用(在Linux中常称作 syscall),通常通过 C 库内部进行系统调用。

系统调用号

在 Linux 中,每个系统调用都被赋予了一个系统调用号。这个系统调用号用于绑定系统调用,是系统调用的唯一标识符。调用系统调用的时候也需要通过该系统调用号来寻找系统调用。

系统调用号一旦分配就不可变更,否则编译好的应用程序就会崩溃。此外,如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用,否则以前编译过的代码会调用这个系统调用,但事实上是调用的另一个系统调用。

sys_ni_syscall() 是 Linux 未实现的系统调用,用于给不可用的系统调用占位,即填补空缺。

记录了所有已注册的系统调用的列表:sys_call_table,是个数组,数组索引便是系统调用号,其所有的元素都是函数指针。

位于:arch/i386/kernel/syscall_64.c

系统调用的性能

Linux 的系统调用比其它操作系统执行得要快,因为:

  • Linux 上下文切换的时间很短。
  • Linux 系统调用处理程序和每个系统调用本身也都非常简洁。

系统调用处理程序

用户空间无法直接进行系统调用,而是需要通过先访问调用库,再通过库内部进行系统调用。应用程序通过软中断通知内核进行系统调用,在 x86 系统上预定义的软中断是中断号 128,通过 int $0x80 指令触发该中断。通过这个指令从用户态陷入到内核态,执行第 128 号中断处理程序,而这个程序便是系统调用处理程序,名为 system_call()。它的实现与硬件体系结构密切相关,x86-64 系统在 entry_64.S 文件中使用汇编实现。

知识扩展:sysenter 指令

指定恰当的系统调用

通过软中断从用户态陷入到内核态,此外陷入前我们还需要传入一个参数,该参数便是系统调用号,用于指定需要调用的那个系统调用。该参数放入到 eax 寄存器中。

参数传递

除了系统调用外,有时还需要其它参数,所以在陷入内核的时候都应该从用户空间传给内核。

在 x86-32 系统上,ebx、ecx、edx、esi 和 edi 按照顺序存放前五个参数。需要六个及以上的情况很少,此时应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。

返回值也通过寄存器传递,在 x86 系统上,它存放在 eax 寄存器中。

系统调用的实现

实现系统调用

  • 明确该系统调用的功能。
  • 考虑参数、返回值、错误码是什么。
  • 系统调用的接口应该力求简洁,参数尽可能少。
  • 设计接口的时候要尽量为将来做考虑。
  • 系统调用设计越通用越好。
  • 减少对函数不必要的限制。
  • 不要假设该系统调用现在怎么用的将来就一定这么用。
  • 系统调用的目的可能不变,但用法可能改变。
  • 该系统调用是否可移植?
  • 别对机器的字节长度和字节序做假设。
  • 写系统调用时,要时刻注意可移植性和健壮性,不但要考虑当前,也要考虑将来。

参数验证

最重要的一种检查是用户提供的指针是否有效。内核得到一个错误的指针后果是很重要的。

在接收一个用户空间的指针之前,内核必须保证:

  • 指针指向的内存区域属于用户空间。(进程决不能欺骗内核去读取内核空间的数据)
  • 指针指向的内存区域在进程的地址空间里。(进程决不允许欺骗内核去读其它进程的数据)
  • 若是读,该内存应被标记为可读;若是写,该内存应被标记为可写;若是可写可读,则也同理。进程决不允许绕开内存访问限制。

内核提供了两个方法来完成必须的检查和内核空间与用户空间之间数据的来回拷贝。

  • copy_to_user()
  • copy_from_user()

最后一项检查是检查是否有访问权限。内核提供了函数如下:

  • suser():检查用户是否为 Root。
  • capable():检查对某个资源是否有访问权限。

capable 举例:capable(CAP_SYS_NICE) 检查调用者是否有权改变其它进程的 nice 值。

include/linux/capability.h

系统调用上下文

内核在执行系统调用的时候处于进程上下文。current 指向当前任务,即引发系统调用的那个进程。

在进行上下文中,内核可以休眠,并且可以被抢占,这里指的都是陷入内核后系统调用的那个进程,而不是用户态的用户进程。

能休眠说明系统调用可以使用内核提供的绝大部分功能。休眠的能力给内核编程带来了极大遍历。

能被抢占表名,像用户空间内的进程一样,当前的进程同样可以被其它进程抢占。因为新的进程可以使用相同的系统调用,所以必须小心,要保证该系统调用是可重入的。

当系统调用返回的时候,控制器仍然在 system_call() 中,它最终会负责切换到用户空间,并让用户进程继续执行下去。

注意:中断处理程序不能休眠,这使得中断处理程序所能进行的操作较之运行在进程上下文中的系统调用所能进行的操作受到了极大的限制。

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

  1. 在系统调用表添加一个表项。表中的位置序号(索引)就是对应的系统调用号。

    位置:arch/m32r/kernel/syscall_table.S

  2. 对于所支持的各种体系结构,系统调用号都必须定义于 arch/alpha/include/asm/unistd.h 中。

  3. 系统调用必须被编译进内核映像(不能被编译成模块)。这只要把它放进 kernel 下的一个相关文件中就可以了,如 sys.c 它包含了各种各样的系统调用。

演示: 新增系统调用 foo(),此时这个函数只是系统调用的具体实现,是在内核中的。

第一步:在系统调用表中添加一个表项。

ENTRY(sys_call_table)
	.long sys_restart_syscall	/* 0  -  old "setup()" system call*/
	.long sys_exit
	.long sys_fork
	.long sys_read
	; ...
	.long sys_ni_syscall		/* Reserved for sys_vserver */
        .long sys_ni_syscall		/* Reserved for sys_mbind */
        .long sys_ni_syscall		/* Reserved for sys_get_mempolicy */
        .long sys_ni_syscall		/* Reserved for sys_set_mempolicy */
        .long sys_mq_open
        .long sys_mq_unlink
        .long sys_mq_timedsend
        .long sys_mq_timedreceive       /* 280 */
        .long sys_mq_notify
        .long sys_mq_getsetattr
        .long sys_ni_syscall            /* reserved for kexec */
	.long sys_waitid
	.long sys_ni_syscall		/* 285 */ /* available */
	; ...
	.long sys_foo /* 新增的表项 */

第二步:将系统调用号添加到 unistd.h 中。

#define __NR_osf_syscall	  0	/* not implemented */
#define __NR_exit			  1
#define __NR_fork		  	  2
#define __NR_read		  	  3
#define __NR_write		      4
#define __NR_osf_old_open	  5	/* not implemented */
// ...
#define __NR_preadv			    490
#define __NR_pwritev			491
#define __NR_rt_tgsigqueueinfo  492
#define __NR_perf_event_open    493
#define __NR_foo			    494 // 新增的

第三步:实现 foo() 函数,即实现 foo() 系统调用。该系统调用必须编译到内核中,因此需要放到 kernel 下,这里我们放到 kernel/sys.c 中。

asmlinkage long sys_foo(void) {
    return THREAD_SIZE;
}

完成了后,启动内核在用户空间就可以调用 foo() 系统调用了。

从用户空间绯闻系统调用

若靠C库来间接的进行系统调用,可以通过 _syscallX() 进行系统调用,这时 Linux 提供的一组宏。X 的绯闻从 0 到 6,代表传递给系统调用的参数个数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498981.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【疯狂造轮子-iOS】JSON转Model系列之一

1. 前言 之前一直看别人的源码,虽然对自己提升比较大,但毕竟不是自己写的,很容易遗忘。这段时间准备自己造一些轮子,主要目的还是为了提升自身实力,总不能一遇到问题就Google 。 之前写i博客园客户端的时候&#xff0…

借助PLC-Recorder,西门子PLC S7-200SMART实现2ms周期采集的方法(带时间戳采集)

目录 1、测试条件 2、测试结论 3、PLC的发送程序 4、PLC-Recorder侧的通讯设置 5、PLC-Recorder的通道配置 6、PLC-Recorder的变量配置 7、记录数据的情况 8、再说时间戳 9、小结 高速数据采集要保证速度,也要保证时刻的准确性。在windows系统里&#xff0…

ESP32学习笔记22-TWAI-CAN

22.TWAI-CAN 22.1概述 22.1.1参考博客 ESP32 基于自带控制器实现CAN总线通信(上) - 知乎 (zhihu.com) ESP32 基于自带控制器实现CAN总线通信(下) - 知乎 (zhihu.com) 22.1.2 ESP32 TWAI/CAN外设说明 可以支持标准帧格式(11位ID)和扩展帧格式(29位ID)ESP32 包含 1 个 T…

生信刷题之ROSALIND——Part 4 (MPRT, MRNA, ORF)

目录 写在前面1、Finding a Protein MotifProblemSample DatasetSample OutputCodeOutput 2、Inferring mRNA from ProteinProblemSample DatasetSample OutputexampleCodeOutput 3、Open Reading FramesProblemSample DatasetSample OutputCodeOutput 写在前面 本来打算每周更…

3.Hive基础命令练习

创建表格如下: 部门表: create table if not exists dept(deptno int, -- 部门编号dname string, -- 部门名称loc int -- 部门位置 ) row format delimited fields terminated by \t; 员工表: create table if not exists emp(empno int,…

AI 工具合辑盘点(十三)持续更新 之 面向宠物爱好者的 AI 工具和面向电影爱好者的 AI 工具

亲爱的宠物爱好者,这个部分是专门为你准备的。🐾 不论你是爱狗人士还是铲屎官,AI 都能满足你。 访问地址: This Cat Does Not Exist 猫咪生成器 你知道喜欢猫的爱好有个专门的名字吗?在国外被称为ailurophilia&…

操作系统考试复习——第四章 对换 分页存储管理方式

对换技术也成为交换技术,由于当时计算机的内存都非常小,为了使该系统能分时运行多个用户程序而引入了对换技术。 1.对换的引入: 所谓“对换”,是指把内存中暂时不能运行的进程或者暂时不用的程序和数据调出到外存上,…

zk之数据的发布与订阅

数据的发布和订阅: (1)数据的发布与订阅是一个一对多的关系。多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态发生变化时,会通知所有的订阅者对象,使它们能够自动的更新自己的状态。发布和订阅可以让发…

SpringBoot项目修改application.yml,application-prod.yml配置文件中的端口,数据库链接等信息后,项目突然不能运行

SpringBoot项目修改application.yml,application-prod.yml配置文件中的端口,数据库链接等信息后,项目突然不能运行 问题记录 ,SpringBoot项目修改application.yml,application-prod.yml配置文件中的端口,数…

跟姥爷深度学习5 浅用卷积网络做mnist数字识别

一、前言 前面用TensorFlow浅做了一个温度预测,使用的是全连接网络,同时我们还对网上的示例做了调试和修改,使得预测结果还能看。本篇我们更进一步使用CNN(卷积)网络,不过再预测温度就有点大材小用&#x…

Stable Diffusion Webui 本地部署【踩坑记录】

1、安装python Python Release Python 3.10.6 | Python.org 2、安装git git是一个代码管理工具,通过它可以将开源项目仓库克隆到本地 下载地址:Git - Downloading Package 3、下载stable-diffusion-webui 可以新建一个目录,在文件夹内单…

代数余子式怎么求

代数余子式是矩阵中每个元素的代数余数,可以通过以下步骤求得: 1. 找到该元素所在的行和列,将其删除,得到一个新的矩阵。 2. 计算新矩阵的行列式,乘以(-1)^(行号列号),即为该元素的代数余子式。 例如 对…

vulnhub靶场之nasef1

1.信息收集 探测存活主机,发现192.168.239.176存活 对目标主机192.168.239.176进行端口扫描,发现存活22、80端口 浏览器访问http://192.168.239.176/,发现为apache2的页面,查看源码,未发现异常。 对http://192.16…

-笔记 tps qps

页面请求异步处理 将请求 扔进 kafka, Mq等 MQ单机抗几万并发也是ok的 底层批量处理 sql 处理 尽量批量处理,减少耗时 分库分表, 可能到了最后数据库层面还是免不了抗高并发的要求,好吧,那么就将一个数据库拆分为多个库&#xf…

Java ---System类

System 类位于 java.lang 包,代表当前 Java 程序的运行平台,系统级的很多属性和控制方法都放置在该类的内部。由于该类的构造方法是 private 的,所以无法创建该类的对象,也就是无法实例化该类。 System 类提供了一些类变量和类方…

PBDB Data Service:Thumbnail images of lifeforms(生命形式的缩略图)

Thumbnail images of lifeforms(生命形式的缩略图) 描述用法参数方法响应值格式术语表 描述 此操作返回表示指定分类的图像,或关于图像的信息。如果后缀是 .png,则返回图像内容数据。否则,将以指定的格式返回一个描述…

2023年全国硕士研究生入学统一考试英语(二)试题

2023年全国硕士研究生入学统一考试英语(二)试题 Section I Use of English Directions: Read the following text. Choose the best word ( s) for each numbered blank and mark A, B , C or D on the ANSWER SHEET. ( 10 points) Here’s a common …

OSS文件打包下载

前言 OSS 存放了很多项目(项目是 TMagic 低代码平台编辑生成,自动上传 OSS),现在需要在管理后台将项目打包ZIP下载,并不在本地生成文件。 OSS 要下载项目文件: 一、思路实现 创建 OSSClient 实例获取 Bu…

K8s基础6——应用配置管理方案、调度策略、污点和污点容忍

文章目录 一、应用配置管理方案1.1 ConfigMap1.1.1 注入变量1.1.2 挂载数据卷 1.2 Secret 二、调度策略2.1 nodeSelector定向调度2.1.1 正例2.1.2 反例 2.2 nodeAffinity亲和力调度2.2.1 In硬策略2.2.2 NotIn硬策略2.2.3 软策略 2.3 PodAffinity亲和力调度2.3.1 pod共存2.3.2 p…

【机器学习】信息量、香农熵、信息增益

这节可以搭配 【机器学习】Logistic回归(重新整理)信息量(信息)信息量公式的推理过程 香农熵信息增益 【机器学习】Logistic回归(重新整理) B站视频:“交叉熵”如何做损失函数?打包…