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 的资源)

相关文章
|
1天前
|
运维 监控 网络协议
Linux 下的性能监控与分析技巧
在Linux环境中,命令行工具助力服务器管理和故障排查。通过示例展示如何监控网络、TCP连接、CPU及内存使用。例如,用`netstat`结合`awk`查TOP 20高频率IP访问80端口,识别DDoS迹象;`netstat -nat`统计TCP状态;`ps -aux`排序列出CPU和内存消耗大的进程;`find`加`tar`查找并压缩`.conf`文件。掌握这些命令提升运维效率。
8 1
|
4天前
|
Linux 数据处理 开发者
深入解析Linux中的paste命令:数据处理与分析的得力助手
`paste`命令在Linux中是数据处理的利器,它按列拼接多个文件内容,支持自定义分隔符和从标准输入读取。例如,合并`file1.txt`和`file2.txt`,使用`paste file1.txt file2.txt`,默认以制表符分隔;若要使用逗号分隔,可运行`paste -d ',' file1.txt file2.txt`。当文件行数不同时,较短文件后会填充空白行。结合管道符与其他命令使用,如`cat file1.txt | paste -s`,可按行合并内容。注意文件大小可能影响性能。
|
4天前
|
Linux 编译器 测试技术
探索Linux中的objcopy命令:数据处理与分析的得力助手
`objcopy`是GNU工具集中的实用程序,用于复制和转换二进制目标文件,如ELF到S-record。它支持格式转换、内容提取和修改,如移除调试信息。命令参数包括指定输入/输出格式和复制特定段。示例用途有:`objcopy -O binary input.elf output.bin`(ELF转二进制)和`objcopy -j .text input.elf output.o`(复制.text段)。使用时注意文件格式、备份原始文件并查阅文档。对于处理和分析二进制数据,`objcopy`是不可或缺的工具。
|
4天前
|
移动开发 数据挖掘 Linux
探索Linux命令之nl:数据处理与分析的得力助手
`nl`命令是Linux下用于为文本文件添加行号的工具,支持自定义格式和空行处理。它可以显示行首或行尾的行号,并能处理逻辑页。常用参数包括`-b`(控制空行行号)、`-n`(设定行号位置和是否补零)、`-w`(设定行号宽度)。示例用法如`nl -b a -n rz -w 3 filename.txt`。在处理大文件时需谨慎,并注意备份原始文件。nl是数据分析时的实用工具。
|
9天前
|
Linux
查看linux内核版本
在Linux中查看内核版本可使用`uname -r`、`cat /proc/version`、`lsb_release -a`(若安装LSB)、`/etc/*release`或`/etc/*version`文件、`dmesg | grep Linux`、`cat /sys/class/dmi/id/product_name`、`hostnamectl`、`kernrelease`(如果支持)、`rpm -q kernel`(RPM系统)和`dpkg -l linux-image-*`(Debian系统)。
23 4
|
10天前
|
安全 Linux 数据处理
探索Linux的kmod命令:管理内核模块的利器
`kmod`是Linux下管理内核模块的工具,用于加载、卸载和管理模块及其依赖。使用`kmod load`来加载模块,`kmod remove`卸载模块,`kmod list`查看已加载模块,`kmod alias`显示模块别名。注意需有root权限,且要考虑依赖关系和版本兼容性。最佳实践包括备份、查阅文档和使用额外的管理工具。
|
8天前
|
数据挖掘 Linux 数据处理
探索Linux下的Lua命令:轻量级脚本语言在数据处理和分析中的应用
**探索Linux上的Lua:轻量级脚本语言用于数据处理。Lua通过命令行解释器执行,适用于游戏开发、数据分析及自动化。特点包括小巧、高效、可扩展和动态类型。使用`lua`或`luajit`,配合-e、-l、-i参数执行脚本或互动模式。示例:执行`hello.lua`脚本打印"Hello, Lua!"。最佳实践涉及版本兼容、性能优化、使用C API、测试和文档编写。**
|
8天前
|
JSON 运维 安全
深入探索Linux的lsns命令:处理与分析Linux命名空间
`lsns`命令是Linux中用于查看命名空间信息的工具,帮助管理和隔离系统资源。它显示命名空间的状态、类型、进程和挂载点,适用于性能优化、故障排查。命令特点包括丰富的参数选项(如 `-t`、`-p`、`-n`),清晰的表格输出和JSON格式支持。示例:列出所有命名空间用`lsns`,列出网络命名空间用`lsns -t net`。使用时注意权限,结合其他工具,并考虑版本兼容性。
|
1天前
|
Linux API 调度
技术笔记:Linux内核跟踪和性能分析
技术笔记:Linux内核跟踪和性能分析
|
1天前
|
算法 Linux 编译器
技术笔记:LINUX2.6.32下的进程分析
技术笔记:LINUX2.6.32下的进程分析