linux 系统调用流程分析

  • x86

1.系统调用

  系统调用是用户空间程序与内核交互的主要机制。系统调用与普通函数调用不同,因为它调用的是内核里的代码。使用系统调用时,需要特殊指令以使处理器权限转换到内核态。另外,被调用的内核代码由系统调用号来标识,而不是函数地址。系统调用流程如下图所示:
在这里插入图片描述

2.x86 系统调用实现原理

  比如创建子进程,内核提供fork系统调用作为接口。如果用户态程序想调用这个内核提供的接口,其对应的汇编语句

movq $57, %rax
syscall

  syscall指令会先查看此时RAX的值,然后找到系统调用号为那个值的系统调用,然后执行相应的系统调用。在系统调用列表中找到,fork这个系统调用的系统调用号是57。于是,把57放入rax寄存器中,然后使用了syscall指令。这就是让内核执行了fork。

2.1.调用约定

  系统调用往往会有许多参数,比如说open系统操作,在include/linux/syscalls.h中找到其对应的C语言接口为

asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode);

  它接受三个参数。那么,参数传递是按照什么规定呢?事实上,当涉及到系统调用时,调用约定与用户态程序一般的调用约定并不相同。x86-64 ABI文档 第A.2.1节,描述了调用约定:

The Linux AMD64 kernel uses internally the same calling conventions as user-level applications (see section 3.2.3 for details). User-level applications that like to call system calls should use the functions from the C library. The interface between the C library and the Linux kernel is the same as for the user-level applications with the following differences:

User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9. The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 and %r9.

A system-call is done via the syscall instruction. The kernel clobbers registers %rcx and %r11 but preserves all other registers except %rax.

The number of the syscall has to be passed in register %rax.

System-calls are limited to six arguments, no argument is passed directly on the stack.

Returning from the syscall, register %rax contains the result of the system-call. A value in the range between -4095 and -1 indicates an error, it is -errno.

Only values of class INTEGER or class MEMORY are passed to the kernel.

  可以看出,系统调用约定了以下几个方面:

  • 参数相关
  • 系统调用号
  • 系统调用指令
  • 返回值及错误码

  这个规范就称为内核接口的调用约定,可以从第一点就显著地看到,这个调用约定与用户态的程序是不同的。也就是说,如果我们用编译器直接编译

long sys_open(const char *pathname, int flags, mode_t mode);

  那么,编译出来的可执行程序会认为,这个函数是用户态函数,其传参仍然是按 %rdi, %rsi, %rdx, %rcx, %r8, %r9的顺序,与内核接口不符。因此,gcc提供了一个标签asmlinkage来标记这个函数是内核接口的调用约定:

asmlinkage long sys_open(const char *pathname, int flags, mode_t mode);

  当函数前面有这个标签时,编译器编译出的可执行程序就会认为是按内核接口的调用约定对这个函数进行调用的。

2.1 系统调用的入参

2.1.1 参数顺序
在这里插入图片描述
对应关系查看arch/x86/entry/entry_64.S:

 435 /*
 436  * System call entry. Upto 6 arguments in registers are supported.
 437  *
 438  * SYSCALL does not save anything on the stack and does not change the
 439  * stack pointer.
 440  */
 441
 442 /*
 443  * Register setup:
 444  * rax  system call number
 445  * rdi  arg0
 446  * rcx  return address for syscall/sysret, C arg3
 447  * rsi  arg1
 448  * rdx  arg2
 449  * r10  arg3  (--> moved to rcx for C)
 450  * r8   arg4
 451  * r9   arg5
 452  * r11  eflags for syscall/sysret, temporary for C
 453  * r12-r15,rbp,rbx saved by C code, not touched.
 454  *
 455  * Interrupts are off on entry.
 456  * Only called from user space.
 457  *
 458  * XXX  if we had a free scratch register we could save the RSP into the stack frame
 459  *      and report it properly in ps. Unfortunately we haven't.
 460  *
 461  * When user can change the frames always force IRET. That is because
 462  * it deals with uncanonical addresses better. SYSRET has trouble
 463  * with them due to bugs in both AMD and Intel CPUs.
 464  */

2.1.2 参数数量

  系统调用参数限制为6个。

2.1.3 参数类型

  参数类型限制为 INTEGER 和 MEMORY。x86-64 ABI 定义第3.2.3节 Parameter Passing描述:

INTEGER This class consists of integral types that fifit into one of the general purpose registers.
MEMORY This class consists of types that will be passed and returned in memory via the stack.

2.2 返回值及错误码
  当从系统调用返回时,%rax里保存着系统调用结果;如果是-4095 至 -1之间的值,表示调用过程中发生了错误。

2.3 系统调用号

  系统调用号通过%rax传递。

2.4 系统调用指令

  系统调用通过指令syscall来执行。

3.将中断号与系统调用函数绑定

  系统调用函数 (system_call) 是系统调用的总入口,将其与中断号 0x80绑定。

arch/x86/kernel/traps.c
#define SYSCALL_VECTOR 0x80
set_system_trap_gate(SYSCALL_VECTOR, &system_call);

3.1.系统调用函数实现

  比如创建子进程,内核提供fork系统调用作为接口。如果用户态程序想调用这个内核提供的接口,其对应的汇编语句

movq $57, %rax
syscall

  syscall指令会先查看此时RAX的值,然后找到系统调用号为那个值的系统调用,然后执行相应的系统调用。在系统调用列表中找到,fork这个系统调用的系统调用号是57。于是,把57放入rax寄存器中,然后使用了syscall指令。这就是让内核执行了fork。

  由于需要从用户态栈切换到内核态栈,需要栈切换,因此CPU会将用户态的栈相关的参数(oldss, oldesp)压栈,再调用system_call。

  在 system_call 内部:

  将所有寄存器压入内核栈,其中 ebx, ecx, edx 存放程序的参数以 eax 为偏移量,在 sys_call_table 中找到指定的系统调用的地址,其中sys_call_table 定义了所有的系统函数的地址。

从以上可以看出参数传递的方式:

  • 从用户态到内核态通过寄存器传递
  • 内核函数通过栈读取,即通过栈传递
arch/x86/kernel/entry_32.S
.macro SAVE_ALL
	cld
	PUSH_GS
	pushl %fs
	pushl %es
	pushl %ds
	pushl %eax
	pushl %ebp
	pushl %edi
	pushl %esi
	pushl %edx
	pushl %ecx
	pushl %ebx
	movl $(__USER_DS), %edx
	movl %edx, %ds
	movl %edx, %es
	movl $(__KERNEL_PERCPU), %edx
	movl %edx, %fs
	SET_KERNEL_GS %edx
.endm

ENTRY(system_call)
	pushl %eax			# save orig_eax
	SAVE_ALL
	GET_THREAD_INFO(%ebp)		# system call tracing in operation / emulation
	testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
	jnz syscall_trace_entry
	cmpl $(nr_syscalls), %eax
	jae syscall_badsys
syscall_call:
	call *sys_call_table(,%eax,4)
	movl %eax,PT_EAX(%esp)		# store the return value

arch/x86/kernel/syscall_table_32.S:

ENTRY(sys_call_table)
	.long sys_restart_syscall	/* 0 - old "setup()" system call, used for restarting */
	.long sys_exit
	.long ptregs_fork
	.long sys_read
	.long sys_write
	.long sys_open		/* 5 */
	.long sys_close
	...
	.long sys_rt_tgsigqueueinfo	/* 335 */
	.long sys_perf_event_open
	.long sys_recvmmsg
	.long sys_fanotify_init
	.long sys_fanotify_mark
	.long sys_prlimit64		/* 340 */

refer to

  • https://juejin.cn/post/7203681024236355639
  • https://wenfh2020.com/2021/09/05/kernel-syscall/

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

level=warning msg=“failed to retrieve runc version: signal: segmentation fault“

安装docker启动后,发现里面没有runc版本信息 目前看是少了runc组件 那我们安装runc https://github.com/opencontainers/runc/releases/download/v1.1.10/runc.amd64 [rootlocalhost ~]# mv runc.amd64 /usr/bin/runc mv:是否覆盖"/usr/bin/runc&q…

09【保姆级】-GO语言的数组和切片

09【保姆级】-GO语言的数组 一、数组1.1 数组定义1.2 数组的使用1.3 数组的遍历1.4 数组的应用案例 二、切片2.1 切片的介绍2.2 切片的原理2.3 切片的三种使用 之前我学过C、Java、Python语言时总结的经验: 先建立整体框架,然后再去抠细节。先Know how&a…

MATLAB | 绘图复刻(十三) | 带NaN图例的地图绘制

有粉丝问我地图绘制如何添加NaN,大概像这样: 或者这样: 直接上干货: 原始绘图 假设我们有这样的一张图地图,注意运行本文代码需要去matlab官网下载Mapping Toolbox工具箱,但是其实原理都是相似的&…

在无回显的情况下如何判断是否存在命令注入漏洞

在无回显的情况下如何判断是否存在命令注入漏洞 这种情况下可以使用OOB带外来实现&#xff0c;言而简之&#xff0c;就是利用命令执行漏洞去解析我们的dns如果dns日志有记录那就说明存在命令注入漏洞 首先先简单搭建一个无回显的命令注入 <?phpexec($_REQUEST[777]); ?&…

Vue3 配置全局 scss 变量

variables.scss $color: #0c8ce9;vite.config.ts // 全局css变量css: {preprocessorOptions: {scss: {additionalData: import "/styles/variables.scss";,},},},.vue 文件使用

html2canvas快速使用

一、概述 html2canvas是一个HTML渲染器&#xff0c;是一个脚本&#xff0c;它允许你直接在用户浏览器截取页面或部分网页的“屏幕截屏”。底层是基于DOM的&#xff0c;根据页面上可用的信息构建屏幕截图&#xff0c;它没有制作实际的屏幕截图&#xff0c;因此生成的图片并不一定…

【藏经阁一起读】(77)__《Apache Dubbo3 云原生升级与企业最佳实践》

【藏经阁一起读】&#xff08;77&#xff09; __《Apache Dubbo3 云原生升级与企业最佳实践》 目录 一、Dubbo是什么 二、Dubbo具体提供了哪些核心能力&#xff1f; 三、构建企业级Dubbo微服务 &#xff08;一&#xff09;、创建项目模板 &#xff08;二&#xff09;、将…

部署单仓库多目录项目

部署单仓库多目录项目 文章目录 部署单仓库多目录项目1.部署单仓库多目录项目2.Shell脚本进行部署单仓库多目录项目2.1 编写Shell脚本2.2 Demo推送代码及测试 3.小结 1.部署单仓库多目录项目 #部署单仓库多目录项目 在开发过程中,研发团队往往会将一个大型项目拆分成几个子目录…

4. Pandas行列操作

4.1 新增列 4.1.1 assign Pandas中的assign&#xff08;&#xff09;函数不仅可以实现不改变原数据情况下新增列&#xff0c;而且可以同时新增多列&#xff0c;还可以配合链式操作使用一行代码完成多个新增列创建&#xff0c;使得代码非常整洁。 &#xff08;1&#xff09;函…

HTTP响应详解

HTTP响应格式 HTTP响应报文通常由四个部分组成: 响应行(Response Line):包含协议版本、状态码和状态消息,例如:HTTP/1.1 200 OK。 响应头(Response Headers):包含了一系列的键值对,用来描述关于响应的信息,比如内容类型、日期、服务器信息等等。 空行:即CRLF(回车…

【剑指offer|图解|链表】链表的中间结点 + 链表中倒数第k个结点

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、算法模板 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️链表的中间结点二. ⛳️链表中倒数第k个结点&#x1f4dd;结语 &#x1f4c…

中国毫米波雷达产业分析1——毫米波雷达行业概述

一、毫米波雷达简介 &#xff08;一&#xff09;产品定义 雷达是英文Radar的音译&#xff0c;源于Radio Detection and Ranging的缩写&#xff0c;原意是“无线电探测和测距”&#xff0c;即用无线电方法发现目标并测定它们在空间的位置。毫米波雷达是指一种工作在毫米波频段的…

基于像素特征的kmeas聚类的图像分割方案

kmeans聚类代码 将像素进行聚类&#xff0c;得到每个像素的聚类标签&#xff0c;默认聚类簇数为3 def seg_kmeans(img,clusters3):img_flatimg.reshape((-1,3))# print(img_flat.shape)img_flatnp.float32(img_flat)criteria(cv.TERM_CRITERIA_MAX_ITERcv.TERM_CRITERIA_EPS,2…

环境配置|GitHub——如何在github上搭建自己写的网站

下面简单地总结了从本地的网页文件到在github服务器上展示出来即可以通过网络端打开的过程&#xff1a; &#xff08;以下可能会出现一些难点&#xff0c;照着做就可以了&#xff0c;由于笔者是小白&#xff0c;也不清楚具体原理是什么&#xff0c;希望有一天成为大神的时候能轻…

第一次性能测试懵逼了

最近&#xff0c;公司领导让我做下性能方面的竞品对比&#xff0c;作为一个性能测试小白的我&#xff0c;突然接到这样的任务&#xff0c;下意识发出大大的疑问。 整理好心情&#xff0c;内心想着“领导一定是为了考验我&#xff0c;才给我这个任务的”&#xff0c;开始了这一次…

人工智能时代:深入了解与学以致用的智能科技

目录 前言人工智能的领域1. 医疗健康2. 交通与智能驾驶3. 教育领域4. 金融与人工智能5. 制造业与自动化 人工智能的应用1. 智能手机与语音助手2. 智能家居系统3. 自动驾驶汽车4. 医疗诊断与治疗5. 金融风控与预测分析 对人工智能的看法1. 科技的利弊2. 伦理和隐私问题3. 人工智…

redis的高可用之持久化

1、redis的高可用考虑指标 &#xff08;1&#xff09;正常服务 &#xff08;2&#xff09;数据容量的扩展 &#xff08;3&#xff09;数据的安全性 2、redis实现高可用的四种方式 &#xff08;1&#xff09;持久化 &#xff08;2&#xff09;主从复制 &#xff08;3&…

构建智能医患沟通:陪诊小程序开发实战

在医疗科技的浪潮中&#xff0c;陪诊小程序的开发成为改善医患沟通的创新途径之一。本文将介绍如何使用Node.js和Express框架构建一个简单而强大的陪诊小程序&#xff0c;实现患者导诊和医生咨询功能。 1. 安装Node.js和Express 首先确保已安装Node.js&#xff0c;然后使用以…

【软考】文件的组织结构

目录 一、说明二、逻辑结构2.1 说明2.2 记录式文件2.2.1 说明2.2.2 顺序文件2.2.3 索引文件2.2.4 索引文件 2.3 流式文件 三、物理结构3.1 说明3.2 链接方式之隐式链接3.3 链接方式之显式链接 一、说明 1.组织结构是文件的组织形式。 2.逻辑结构为用户可见的的文件结构。 3.物理…

听GPT 讲Rust源代码--src/librustdoc

题图来自 Why is building a UI in Rust so hard? File: rust/src/librustdoc/core.rs 在Rust中&#xff0c;rust/src/librustdoc/core.rs文件的作用是实现了Rustdoc库的核心功能和数据结构。Rustdoc是一个用于生成Rust文档的工具&#xff0c;它分析Rust源代码&#xff0c;并生…