当前位置: 首页 > Linux

Linux系统调用原理

时间:2023-04-06 19:17:55 Linux

操作系统通过系统调用为运行在其上的进程提供服务。当用户态进程发起系统调用时,CPU会切换到内核态并开始执行内核函数。内核函数负责响应应用程序的要求,如操作文件、进行网络通信或申请内存资源等。原文地址:https://learn-linux.readthedocs.io玩转Linux老群已满,请加新群:278378501。欢迎关注我们的公众号:小菜学习编程(coding-fan)To举个简单的例子,应用进程需要输出一行文字,需要调用write系统调用:#include#includeintmain(intargc,char*argv[]){char*msg="Hello,world!\n";写(1,味精,strlen(味精));return0;}注意读者可能会有一些疑惑——输出文本不就是使用printf等函数吗?的确。printf是建立在系统调用之上的高级库函数,用于实现数据格式化等功能。因此,从本质上讲,系统调用起着决定性的作用。调用流程那么,在应用程序中调用系统调用的流程是怎样的呢?下面以一个假设的系统调用xyz为例,介绍一个系统调用的所有环节。如上图,系统调用执行流程如下:应用代码调用系统调用(xyz),这是一个包装系统调用的库函数;库函数(xyz)负责准备传递给内核的参数,并触发软中断切换到内核;CPU被软中断中断后,执行中断处理函数,即系统调用处理函数(system_call);系统调用处理函数调用系统调用服务例程(sys_xyz),真正开始处理系统调用;执行状态切换应用程序(applicationprogram)和库函数(libc)之间,系统调用处理程序(systemcallhandler)和系统调用服务例程(systemcallserviceroutine)之间都是普通的函数调用,其中应该不难理解。由于用户态和内核态的切换,库函数和系统调用处理函数的关系更加复杂。Linux使用软中断从用户模式切换到内核模式。用户态和内核态是独立的执行流程,所以切换时需要准备执行栈和保存寄存器。内核实现了许多不同的系统调用(提供不同的功能),而系统调用处理程序只有一个。因此,用户进程必须传递一个参数来区分,这就是系统调用号(systemcallnumber)。在Linux中,系统调用号一般是通过eax寄存器传递的。综上所述,执行状态切换过程为:应用程序在用户态准备调用参数,执行int指令触发软中断,中断号为0x80;CPU被软中断中断后,执行相应的中断处理函数。进入内核态;系统调用处理函数准备内核执行栈并保存所有寄存器(一般用汇编语言实现);系统调用处理函数根据系统调用号调用相应的C函数——系统调用服务程序;系统调用处理函数准备返回值,从内核栈中恢复寄存器;系统调用处理函数执行ret指令切换回用户态;编程实践,通过一个简单的程序,看看应用程序如何在用户态准备参数并通过int指令触发软中断进入内核态执行系统调用:.section.rodatamsg:.ascii"Hello,world!\n".section.text.global_start_start:#callSYS_WRITEmovl$4,%eax#pushargumentsmovl$1,%ebxmovl$msg,%ecxmovl$14,%edxint$0x80#CallSYS_EXITmovl$1,%eax#pushargumentsmovl$0,%ebx#initiateint$0x80这是一个汇编语言程序,程序入口在_start标签之后。第12行,准备系统调用号:将常量4放入寄存器eax。系统调用编号4表示系统调用SYS_write,我们将通过它向标准输出写入一个字符串。第14-16行,准备系统调用参数:第一个参数放入寄存器ebx,第二个参数放入ecx,以此类推。write系统调用需要3个参数:文件描述符,标准输出文件描述符为1;写入内容(缓冲区)地址;写入内容长度(字节数);第17行,执行int指令触发软中断0x80,程序进入内核态,内核执行系统调用。系统调用执行完后,内核会负责切换回用户态,应用程序会继续执行后面的指令(从第20行开始)。第20-24行调用exit系统调用以退出程序。请注意,必须显式调用exit系统调用才能退出程序。否则,程序会继续执行,最终会遇到段错误(segmentationfault)!读者可能会好奇——在写C语言或者其他程序的时候,这个调用是没有必要的!这是因为C库(libc)已经为您做了所有脏活。接下来,我们编译并执行这个汇编语言程序:-int.o$lshello_world-inthello_world-int.ohello_world-int.S$./hello_world-int你好,世界!事实上,将系统调用号和调用参数放入正确的寄存器并触发正确的softirq是一个重复的麻烦。C库已经完成了肮脏的工作——试试系统调用函数!#include#include#includeintmain(intargc,char*argv[]){char*msg="Hello,world!\n";系统调用(SYS_write,1,msg,strlen(msg));return0;}接下来是订阅更新获取更多学习资料,请关注我们的微信公众号:参考SergIakovlevwrite(2)-Linuxmanualpagesyscall(2)-Linuxmanualpage_exit(2)-Linuxmanualpage