【Linux】syscall系统调用原理及实现

article/2025/10/30 5:46:20

一、什么是系统调用

系统调用 跟用户自定义函数一样也是一个函数,不同的是 系统调用 运行在内核态,而用户自定义函数运行在用户态。由于某些指令(如设置时钟、关闭/打开中断和I/O操作等)只能运行在内核态,所以操作系统必须提供一种能够进入内核态的方式,系统调用 就是这样的一种机制。

系统调用 是 Linux 内核提供的一段代码(函数),其实现了一些特定的功能,用户可以通过 int 0x80 中断(x86 CPU)或者 syscall 指令(x64 CPU)来调用 系统调用。 内核提供用户空间程序与内核空间进行交互的一套标准接口,这些接口让用户态程序能受限访问硬件设备,比如申请系统资源,操作设备读写,创建新进程等。用户空间发生请求,内核空间负责执行,这些接口便是用户空间和内核空间共同识别的桥梁,这里提到两个字“受限”,是由于为了保证内核稳定性,而不能让用户空间程序随意更改系统,必须是内核对外开放的且满足权限的程序才能调用相应接口。

在用户空间和内核空间之间,有一个叫做Syscall(系统调用, system call)的中间层,是连接用户态和内核态的桥梁。这样即提高了内核的安全型,也便于移植,只需实现同一套接口即可。Linux系统,用户空间通过向内核空间发出Syscall,产生软中断,从而让程序陷入内核态,执行相应的操作。对于每个系统调用都会有一个对应的系统调用号,比很多操作系统要少很多。

安全性与稳定性:内核驻留在受保护的地址空间,用户空间程序无法直接执行内核代码,也无法访问内核数据,通过系统调用

二、进入系统调用

本文主要介绍的是 x86 CPU 进入系统调用的方式

Linux 提供了 int 0x80 中断来让用户程序进入 系统调用,我们来看看 Linux 对 int 0x80 中断的处理初始化过程:

void __init trap_init(void)
{...set_system_gate(SYSCALL_VECTOR, &system_call);...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

系统初始化时,会在 trap_init() 函数中对 int 0x80 中断处理进行初始化,设置其中断处理过程入口为 system_call。system_call 是一段由汇编语言编写的代码,我们看看关键部分,如下:

ENTRY(system_call)...call *SYMBOL_NAME(sys_call_table)(,%eax,4)movl %eax,EAX(%esp)     # save the return value...
  • 1
  • 2
  • 3
  • 4
  • 5

我们把上面的汇编改写成 C 代码如下:

void system_call()
{...// 变量 eax 代表 eax 寄存器的值syscall = sys_call_table[eax];eax = syscall();...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

sys_call_table 变量是一个数组,数组的每一个元素代表一个 系统调用 的入口,其定义如下(在文件 arch/i386/kernel/entry.S 中):

.data
ENTRY(sys_call_table).long SYMBOL_NAME(sys_ni_syscall).long SYMBOL_NAME(sys_exit).long SYMBOL_NAME(sys_fork).long SYMBOL_NAME(sys_read).long SYMBOL_NAME(sys_write).long SYMBOL_NAME(sys_open).long SYMBOL_NAME(sys_close)...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

翻译成 C 代码如下:

long sys_call_table[] = {sys_ni_syscall,sys_exit,sys_fork,sys_read,sys_write,sys_open,sys_close,...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

用户调用 系统调用 时,通过向 eax 寄存器写入要调用的 系统调用 编号,这个编号就是 sys_call_table 数组的下标。 system_call 过程获取 eax 寄存器的值,然后通过 eax 寄存器的值找到要调用的 系统调用 入口,并且进行调用。调用完成后,系统调用 会把返回值保存到 eax 寄存器中。

原理如下图:
在这里插入图片描述

三、系统调用实现

当用户要调用 系统调用 时,需要通过向 eax 寄存器写入要调用的 系统调用 编号。因为 用户态 和 内核态 使用的栈不同,而调用 系统调用 是在用户态调用的,而进入 系统调用 后会变成内核态,所以参数就不能通过栈来传递。Linux 使用寄存器来传递参数,参数与寄存器的关系如下:

  • 第1个参数放置在 ebx 寄存器。
  • 第2个参数放置在 ecx 寄存器。
  • 第3个参数放置在 edx 寄存器。
  • 第4个参数放置在 esi 寄存器。
  • 第5个参数放置在 edi 寄存器。
  • 第6个参数放置在 ebp 寄存器。

而 Linux 进入中断处理程序时,会把这些寄存器的值保存到内核栈中,这样 系统调用 就能通过内核栈来获取到参数。

下面我们通过 sys_open() 系统调用来说明一下 系统调用 的运作方式,sys_open() 实现如下:

asmlinkage long sys_open(const char *filename, int flags, int mode)
{...
}
  • 1
  • 2
  • 3
  • 4

一般 系统调用 都需要使用 asmlinkage 编译选项,asmlinkage 编译选项是告诉编译器从栈中读取参数,其实际是封装了 GCC 的编译选项,如下:

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
  • 1

attribute((regparm(0))) 就是告诉 GCC 所有参数都从栈中读取,而 Linux 进入中断处理上下文时,会把 ebx、ecx、edx、esi、edi、ebp 寄存器的值保存到内核栈中,那么 系统调用 就可以从内核栈获取到参数的值。

但由于寄存器只能传递 32 位的整型值(x86 CPU),所以参数一般只能传递指针或者整型的数值,如果要获取指针对应结构的数据,就必须通过从用户空间复制到内核空间,如 sys_open() 系统调用获取要打开的文件路径:

asmlinkage long sys_open(const char *filename, int flags, int mode)
{char * tmp;...tmp = getname(filename);...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

getname() 函数就是用于从用户空间复制数据到内核空间。

四、总结

4.1 内核空间
系统调用的函数原型的指针:位于文件/kernel/arch/arm/kernel/calls.S,格式为CALL(sys_xxx),指定了目标函数的入口地址。
系统调用号的宏定义:位于文件/kernel/arch/arm/include/Uapi/asm/unistd.h,记录着内核空间的系统调用号,格式为#define__NR_xxx (__NR_SYSCALL_BASE+[num])
系统调用的函数声明:位于文件/kernel/include/linux/syscalls.h,格式为asmlinkage long sys_xxx(args …);
系统调用的函数实现:不同函数位于不同文件,比如kill()位于/kernel/kernel/signal.c文件,格式为SYSCALL_DEFINEx(x, sname, …)
前面这4步都是在内核空间相关的文件定义,有了这些,那么内核就可以使用相应的系统调用号。

4.2 用户空间
系统调用号的宏定义:位于文件/bionic/libc/kernel/uapi/asm-arm/asm/unistd.h,记录着用户空间的系统调用号,格式为#define__NR_xxx (__NR_SYSCALL_BASE+[num])。这个文件就是由内核空间同名的头文件自动生成的,所以该文件与内核空间的系统调用号是完全一致。

汇编定义相关函数的中断调用过程:位于文件/bionic/libc/arch-arm/syscalls/xxx.S,比如kill()位于kill.S,格式为:

  1. ENTRY(xxx)
  2. mov ip, r7
  3. ldr r7, =__NR_xxx
  4. swi #0
  5. mov r7, ip
  6. cmn r0, #(MAX_ERRNO + 1)
  7. bxls lr
  8. neg r0, r0
  9. b __set_errno_internal
  10. END(xxx)
当然kill()方法还有函数声明,有了这些,用户空间也能在程序中使用系统调用。明白了这些过程,那么自己新添加系统调用其实也并不是多困难的一件事,新增系统调用号还需要修改syscalls总个数,但强烈不建议自己新增系统调用号,尽量保持与linux kernel主线一致,兼容性更好,所以就不进一步介绍新增流程了。


不同体系CPU寄存器不一样,X86处理器使用eax、ebx、ecx、edx、esi、edi、edp寄存器,ARM处理器使用r0-r15,spc/pr/ssr/gbr/mach/mac寄存器

自己理解:库函数调用到系统调用函数_syscall_xxx,用户态程序将系统调用号保存至寄存器,然后软中断程序从用户态切换到内核态,通过从寄存器读取系统调用编号,在中断向量表中找到系统调用函数入口CALL(sys_socket),asmlinkage标识通过内核堆栈传递参数,内核态完成sys_xxx(sys_socket)调用返回值继续保持在寄存器中。整个过程中程序执行从用户态堆栈切换到内核态堆栈,即特权级指令切换,而普通函数调用没有堆栈切换,函数调用参数直接传入堆栈,调用完返回值保存函数调用堆栈中。
后续理解待更新。。。


http://chatgpt.dhexx.cn/article/YIt5wmRg.shtml

相关文章

Syscall的实现

1. How does syscall works 2. Kernel定义一个系统调用的表sys_call_table,这个表定义了每个系统调用的: 系统调用号NR_xxx 及其对应的系统调用的处理函数, 系统调用号对应sys_call_table[]数组的下标, 数组项的值保存系统调用的处理函数, 如下: 3. 如下,…

system call——系统调用

1. 系统调用 系统调用是操作系统提供的有效服务界面,一般使用高级语言编写,如c和c,对于特定的较为底层的任务,则使用汇编语言指令。 2. API和系统调用 API,应用程序接口,提供应用程序与开发人员基于某软件…

syscall 系统调用

转自:http://blog.csdn.net/b02042236/article/details/6136598 5.1.5 如何使用系统调用 如图5.2所示,用户应用可以通过两种方式使用系统调用。第一种方式是通过C库函数,包括系统调用在C库中的封装函数和其他普通函数。 图5.2 使用系统调用…

linux下syscall函数

NAME syscall - 间接系统调用 SYNOPSIS #define _GNU_SOURCE #include <unistd.h> #include <sys/syscall.h> /* For SYS_xxx definitions */ int syscall(int number, ...); DESCRIPTION …

#Java干货分享:这五个网站能打通你的任督二脉,让你技术大增

现如今的程序员可是一个需要时刻学习的职业&#xff0c;尤其是目前非常火热的Java&#xff0c;作为应用最为广泛的语言&#xff0c;在这一点上体现得尤其强烈。今天分享一些网站资源给大家学习&#xff0c;希望能够为你提供帮助&#xff01; 1、GitHub 这个网站不用多说&…

redis 技术分享

1、是什么 Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务&#xff0c;是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 2、应用场景 2.1 特点 Redis 与其他 key - …

IT技术视频分享

Hadoop 初级/中级/高级视频 需要的加入115046170QQ群

在职场,光有技术是不行的,18年老程序员职场宝贵经验分享

程序员是公认的技术型岗位&#xff0c;我们喜欢用实力说话&#xff0c;那么是否技术实力强就能在职场如鱼得水&#xff1f; 以前我觉得只要技术过硬&#xff0c;在哪都是香饽饽&#xff0c;后来发现也不尽然&#xff0c;公司不是研究所&#xff0c;在研究所里你或许可以不管不…

【多年IT经验分享】

1、分享第一条经验&#xff1a;“学历代表过去、能力代表现在、学习力代表未来。”其实这是一个来自国外教育领域的一个研究结果。相信工作过几年、十几年的朋友对这个道理有些体会吧。但我相信这一点也很重要&#xff1a;“重要的道理明白太晚将抱憾终生&#xff01;”所以放在…

【干货分享】程序员常访问的国外技术交流网站汇总

搞技术的&#xff0c;如果想更高提升自身技能水平&#xff0c;英语这关是逃不了的。 ——某位不愿透露姓名的四级loser 技术人员经常会在各种技术交流社区游逛&#xff0c;大家互相学习、交流、分享、帮助。互联网拉近了地球人的距离&#xff0c;让全世界的技术人员可以聚集在一…

如何做技术分享

转载自&#xff1a;https://www.jianshu.com/p/02e63c85248f 公司最近让我做关于如何做分享的分享&#xff0c;题目定的太大&#xff0c;查了查资料&#xff0c;从演讲技巧到内容准备&#xff0c;泛泛的说意义不大。所以干脆化大为小&#xff0c;限定到技术分享的范围内。不包含…

(2021年)IT技术分享社区个人文章汇总(编程技术篇)

2021年即将成为过去&#xff0c;崭新的2022年即将到来&#xff0c;小编坚持每天给大家分享IT技术相关的文章&#xff0c;希望小编分享的文章能够给大家在日常的工作当中&#xff0c;带来一点帮助。也感谢大家对本公众号的支持&#xff0c;未来我会坚持创作&#xff0c;给大家分…

【技术网站分享】全面整理了一波技术网站,分享给大家!

一、在线教程 首先列出一些在线教程网站&#xff0c;这些在线教程网站通常都比较适合入门&#xff0c;可以作为开发学习路上的第一个阶梯&#xff0c;也可以作为工作中的在线文档。 1、菜鸟教程 地 址&#xff1a;https://www.runoob.com/简 介&#xff1a;在线教程网站&…

十种技术思维:给业务新人的分享

这两周比较惊讶的发现&#xff0c;团队里的小伙伴们都开始主动去用配置化、标准化的思路做事了&#xff0c;很高兴。 比起HOW、我更愿意讲WHAT&#xff0c;今天我主要想讲开发在意识和思路上的一些东西&#xff0c;花10分钟列十条吧。 1、​以终为始&#xff1a;价值是一切的起…

关于技术分享的思考

关于技术分享的思考 最近公司在推行导师制&#xff0c;鼓励有经验的导师带动团队&#xff0c;提升团队的战斗力。作为技术部门&#xff0c;技术分享最适合不过了&#xff0c;可以做到全员导师。 1. 技术分享的目的 做任何事情都是要有目的的。有了明确的目的&#xff0c;在做…

IT云运维技术分享

1 运维体系 1.1 市场对运维的需求 时代发展到今天&#xff0c;社会的生活方式与生产方式的全面的数字化&#xff0c;无论是传统企业还是互联网企业&#xff0c;都在全面上云&#xff0c;这也意味着企业的关键业务乃至“身家性命”都已经全部放在 IT 系统之上&#xff0c;因此…

IT行业里的热门技术 | 热门IT技术项目分享 | 详细介绍一下机器人技术

现在如果问什么行业最火,很多人第一反应肯定就是IT。的确,这些年随着互联网的不断发展,IT热门众所周知。那么就一起来说说,IT行业里,哪些技术更热门。 方向一:你觉得哪些是IT行业里的热门技术 提示:可以简要介绍一下该热门技术、主要的应用、发展、前景等 以下是我认为…

ADB shell出现error:device offline提示

解决办法&#xff1a; 1、adb kill-server2、adb start-server3、adb remount执行这3个命令然后重新键入adb shell应该就可以了 如果还是不行的话&#xff0c;出现error:device offline报错可能是因为ADB版本较低的原因&#xff0c;解决办法就是下载使用最新的ADB工具。 出现上…

Android 手机 黑域

黒域地址下载&#xff1a; http://pan.baidu.com/s/1bDYerc 连接手机&#xff0c;选择USB使用方式为“用作MIDI设备“ 0. (手机) 打开黑域&#xff0c;阅读向导1. (手机) 打开黑域&#xff0c;按屏幕提示&#xff0c;进入“开发者选项”&#xff0c;开启“USB调试”2. (电脑) 下…