Linux 系统调用处理流程分析:陷入内核

简介: Linux 系统调用处理流程分析:陷入内核

linux 系统调用,是以应用程序编程接口(API)的形式,内核提供有一些列服务供程序访问、包括创建新进程、执行 I/O、以及进程间通信创建管道等。


一个最基本的 write 操作,是如何传递到内核呢?为什么说系统调用十分耗 CPU 资源?


image.png


1.系统调用的本质


系统调用的本质是一种异常,当调用一个系统调用时会触发 CPU 异常,CPU 进入异常处理流程。CPU 在异常处理流程中可以识别到本次异常是由于系统调用引起的,从而进入系统调用的异常处理流程中。


image.png


2.异常


异常是异常控制流的一种形式,任何打断当前正在执行程序的过程,都叫做异常。它一部分由硬件实现,一部分由操作系统实现。如图所示,为异常处理流程:


image.png


在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表(exception table)的跳转表,进行一个间接的过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序(execption handler))。


image.png


当异常处理完成后,根据引起异常的事件类型,会发生以下 3 种情况的一种:


  • 处理程序将控制返回给当前指令,即当时事件发生时正在执行的命令。


  • 处理程序将控制返回给下一条指令,如果没有发生异常将会执行下一条指令。


  • 处理程序终止被中断的程序。


异常可以分为四类:中断(interrupt)、陷阱(trap)、故障(fault)和终止(abort):


类别 原因 异步/同步 返回行为
中断 来自 I/O 设备的信号 异步 总是返回到下一条指令
陷阱 有意的异常 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 可能返回当前指令
终止 不可恢复的错误 同步 不会返回


系统调用,就发生在陷阱这个异常中,又叫陷入内核。陷阱是有意的异常,是执行一条指令的结果。陷阱最主要的作用是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用


3.系统调用过程分析


用户程序经常需要向内核请求服务,比如读一个文件(read)、创建一个新的进程(fork)、加载一个新的程序(execve)、终止当前程序(exit)。


为了允许对这些内核服务的访问,处理器提供了一条“syscall n”指令,当用户程序想要请求服务 n 时,可以执行这条命令。执行 syscall 指令会导致一个到异常处理的陷阱(trap),这个处理程序解析参数,并调用适当的内核程序。


整个异常处理流程如下:


image.png


下面以 Linux MIPS 为主,分析一下整个系统调用过程。


3.1异常开始的地方


CPU 中所有异常入口点都位于固定区域,不需要高速缓存的入口点位于 kseg1,需要高速缓存的点位于 ksg0。如图为 MIPS 架构异常入口点(参考《MIPS 体系结构透视》):


image.png


3.2异常判断


以 mips 平台为例。CPU0 的 Cause 寄存器中的 ExcCode(参考《MIPS 体系结构透视》),记录着各种异常 。


image.png


ExcCode 是一个 5 位编码,告诉你发生了哪些异常:


image.png

image.png


首先系统调用触发 CPU 异常时会陷入base+0x180(其他异常)地址进行处理。在进行具体异常处理前,要读取 Cause(ExcCode)寄存器判断何种异常。


ExcCdoe 寄存器用来找出发生异常的类型,决定调用哪个异常处理流程。


3.3异常向量表


异常初始化是在内核中进行的。在 linux 内核中维护了一个异常向量处理函数表,这个表保存了所有异常处理入口点:异常向量表:


unsigned long exception_handlers[32];


异常初始化:


/* init/main.c */
start_kernel();    
/* arch/mips/kernel/traps.c 
*/    -> trap_init();


将各种异常的处理函数地址放入异常向量处理函数表中:



将处理异常的代码放入 base+0x180 地址处




异常处理代码(except_vec3_generic):



except_vec3_generic 函数是异常处理函数,这个函数是广义上的异常,即不区分是系统调用还是中断。


  • 所有的异常处理都以这个函数为入口点。


  • 在这个函数中会去读取 CPU0 的 CAUSE 寄存器的 ExcCode 域,判断到底是什么触发了异常。


  • 然后根据 ExcCode 和之前配置的 except_handlers 异常向量处理函数表找到对应异常处函数进行处理。


3.4syscall 调用


系统调用号


每个系统调用被赋予一个系统调用号。这样,通过独一无二的系统调用号,就可用关联系统调用。


当用户空间的进程执行一个系统调用,这个系统调用号就用来指明到底执行哪个系统调用,进程不会提及系统调用的名称。


系统调用号相当重要,一旦分配,就不能再有任何变更,否则编译好的应用程序就会崩溃。此外,如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用。


linux 系统有一个未实现的系统调用 illegal_syscall(),它除了返回-ENOSYS 外,不做其他工作,这个错误号是专用针对无效的系统调用而设置的。


linux 内核系统调用号:定义在 arch/mips/include/uapi/asm/unistd.h



glibc 系统调用号:/opt/mips-gcc540-glibc222-64bit/mips-linux-gnu/libc/usr/include/asm/unistd.h



  • glibc 系统调用号和 linux 内核系统调用号是一一对应关系。


  • 系统调用号从 4000 开始。


  • 系统调用号最多可以支持到 4999,即 1000 个系统调用。



系统调用表


内核记录了系统调用表中的所有已注册的系统调用,存储在 sys_table_call 中,每一种体系结构中(不同的平台),都明确定义了这个表。这个表为每一个有效的系统调用指定了唯一的系统编号。


如图为mips32位平台下(arch/mips/kernel/scall32-o32.S)sys_table_call。系统调用表是一张指向实现各种系统调用的内核函数的函数指针表,该表可以基于系统调用号进行索引,来定位函数地址,完成系统调用。



image.png


系统调用之 glibc


glibc 在应用程序和内核之间起了一个桥梁的作用。在应用程序中,我们操作 open、write、read、ioctl 等函数,其实是对系统调用的封装。glibc 将诸多系统调用进行封装,是我们可以以函数的形式,方便的调用系统调用:


image.png


glibc 如何传递到内核


应用程序如果想要调用内核的一个系统调用,只能通过上层应用和内核都认可的系统调用号来完成。


所以在 glibc 中,如果想要调用内核的一个系统调用,唯一的方法就是:将想要的系统调用号和需要传入的参数通过特定的方法传入到内核中。


image.png


使用 syscall 指令传递


syscall 是 MIPS(其他平台也有这条指令)的一条指令,该指令的作用就是产生一个“系统异常”,该指令执行完成后,系统会进入异常处理流程,并且 ExcCode 被置为 8,指明为系统调用:


在 MIPS Linux 中系统调用约定如下:


  • v0:保存系统调用号。


  • a0~a3:保存系统调用的前四个参数,多余四个使用栈传递。


image.png


glibc 处理系统调用流程


如图是 glibc 中对 MIPS Linux 中带一个参数的系统调用处理流程(internal_syscall1)


image.png


参数展开如下:


  • input:NR_name,系统调用名


  • arg1:携带的一个参数。


image.png


linux 内核系统异常处理


CPU 进行异常处理后,根据 ExcCode 的值判断是系统调用产生的异常,此时就会进入 handle_sys 进行处理:


  • 判断系统调用号是否符合规范。


  • 从系统调用表中取出系统调用的地址和支持的参数个数。


  • 判断系统调用是否需要参数。


  • 执行系统调用。


image.png


3.5系统调用完整路径


image.png


4.总结


本文以 Linux MIPS 平台为主,详细分析了 linux 的系统调用过程。自上而下,从应用(app)到运行时(Runtime)再到内核、最后深入 CPU 寄存器分析整个系统调用处理流程。现在回过头,再看前文的两个问题,应该就明白了。


系统调用的本质是异常;系统调用俗称“嵌入内核”,那么应该了解了,为什么都不推荐轮询操作 IO 了吧(一次调用过程,流程非常复杂,频繁的调用 IO 操作,会使得 CPU 不停的进入异常处理流程,打断当前的操作,会大大的消耗 CPU 的资源)

相关文章
|
9月前
|
安全 网络协议 Linux
深入理解Linux内核模块:加载机制、参数传递与实战开发
本文深入解析了Linux内核模块的加载机制、参数传递方式及实战开发技巧。内容涵盖模块基础概念、加载与卸载流程、生命周期管理、参数配置方法,并通过“Hello World”模块和字符设备驱动实例,带领读者逐步掌握模块开发技能。同时,介绍了调试手段、常见问题排查、开发规范及高级特性,如内核线程、模块间通信与性能优化策略。适合希望深入理解Linux内核机制、提升系统编程能力的技术人员阅读与实践。
825 1
|
7月前
|
安全 Linux iOS开发
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
672 53
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
|
7月前
|
Linux API iOS开发
Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
552 14
Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
|
9月前
|
存储 Linux
Linux环境下删除大文件后磁盘空间未释放问题诊断流程。
以上诊断流程涉及Linux底层机制与高级管理技能结合之处,并需要管理员根据实际环境灵活调整诊断策略与解决方案。
636 8
|
8月前
|
数据管理 Linux iOS开发
Splunk Enterprise 9.4.5 (macOS, Linux, Windows) - 机器数据管理和分析
Splunk Enterprise 9.4.5 (macOS, Linux, Windows) - 机器数据管理和分析
262 0
|
9月前
|
监控 Ubuntu Linux
什么Linux,Linux内核及Linux操作系统
上面只是简单的介绍了一下Linux操作系统的几个核心组件,其实Linux的整体架构要复杂的多。单纯从Linux内核的角度,它要管理CPU、内存、网卡、硬盘和输入输出等设备,因此内核本身分为进程调度,内存管理,虚拟文件系统,网络接口等4个核心子系统。
972 0
|
9月前
|
Web App开发 缓存 Rust
|
8月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
900 1
二、Linux文本处理与文件操作核心命令
|
8月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
515 137