Linux--系统调用流程

什么是系统调用?

计算机的各种硬件资源是有限的,为了更好的管理这些资源,用户进程是不允许直接操作的,所有对这些资源的访问都必须由操作系统控制。为此操作系统为用户态运行的进程与硬件设备之间进行交互提供了一组接口,这组接口就是所谓的系统调用。这些接口往往通过中断来现。

系统调用实质上就是函数调用,只不过调用的是系统函数,处于内核态而已。 用户在调用系统调用时会向内核传递一个系统调用号,然后系统调用处理程序通过此号从系统调用表中找到相应的内核函数执行,最后返回。

系统调用的优点?

1、把用户从繁杂的硬件设备设置中解放出来。

2、更安全有效的访问硬件资源,内核可以在满足某个请求前就验证这个请求的正确性,具体表现在内核得到系统调用号后会验证调用号的有效性。

3、最重要的是,这些接口使得程序更具有可移植性,因为只要不同操作系统所提供的一组接口相同,那么在这些操作 系统上就可以正确的编译和执行相同的程序。

系统调用的过程一般包括以下步骤:

准备参数: 用户程序将需要的参数传递给系统调用。这些参数可能包括文件描述符、缓冲区地址、数据长度、系统调用号等信息,具体取决于调用的系统服务。对于参数传递,Linux也是通过寄存器完成的。Linux最多允许向系统调用传递6个参数,分别依次由%ebx,%ecx,%edx,%esi,%edi和%ebp这个6个寄存器完成。这6个寄存器可能已经被使用,所以在传参前必须把当前寄存器的状态保存下来,待系统调用返回后再恢复。这一步骤可以在对应系统调用接口的汇编码中看出,如getpid函数的汇编码如下:

00000000000933e0 <__getpid>:
……
933fa: 00
933fb: 85 c0           test %eax,%eax
933fd: 75 f0           jne 933ef <__getpid+0xf>
933ff: b8 27 00 00 00  mov $0x27,%eax
93404: 0f 05           syscall
93406: 85 d2           test %edx,%edx
……

在以上输出中,mov指令将系统调用号0x27放入eax寄存器中,0x27作为syscall的参数,syscall完成调用getpid的工作。

触发中断: 用户程序通过系统调用指令(例如,X86架构中的int 0x80或类似的指令、ARM架构中的swi,svc指令)或陷阱指令(例如,syscall指令)触发一个中断,使得处理器从用户态切换到内核态。在实际执行0x80号中断向量所对应的中断处理程序(system_call)之前,CPU首先要进行堆栈切换,即从用户态切换到内核态。从中断处理函数中返回时,程序当前栈还要从内核栈切换回用户栈。

切换到内核态: 当中断发生时,处理器从用户态切换到内核态,进入操作系统内核的执行环境。这通常涉及到修改处理器的特权级别,以便让内核代码执行。

确定系统调用号: 内核通过查看特定的寄存器(例如eax寄存器)或内存中的某个位置,确定用户程序请求的具体系统调用。

执行系统调用 根据系统调用号,内核利用了一个系统调用表,存放在sys_call_table数组中,它是一个函数指针数组,每一个函数指针都指向其系统调用的封装例程,有NR_syscalls个表项,第n个表项包含系统调用号为n的服务例程的地址来调用相应的系统调用处理函数。这个函数执行用户请求的服务,并使用用户提供的参数。

返回结果: 系统调用处理完毕后,将结果返回给用户程序。通常,处理器的状态被恢复,特权级别重新设置,使得程序从内核态切换回用户态。

处理结果: 用户程序检查系统调用的返回值,以了解调用是否成功。如果发生错误,通常可以通过查看错误码来获取更多信息。

注:Linux中,在用户态和内核态运行的进程使用的栈是不同的,分别叫做用户栈和内核栈, 两者各自负责相应特权级别状态下的函数调用。当进行系统调用时,进程不仅要从用户态切换到内核态,同时也要完成栈切换, 这样处于内核态的系统调用才能在内核栈上完成调用。系统调用返回时,还要切换回用户栈,继续完成用户态下的函数调用。

寄存器%esp(栈指针,指向栈顶)所在的内存空间叫做当前栈, 比如%esp在用户空间则当前栈就是用户栈,否则是内核栈。栈切换主要就是%esp在用户空间和内核空间间的来回赋值。 在Linux中,每个进程都有一个私有的内核栈,当从用户栈切换到内核栈时,需完成保存%esp以及相关寄存器的值(%ebx,%ecx…)并将%esp设置成内核栈的相应值。而从内核栈切换回用户栈时,需要恢复用户栈的%esp及相关寄存器的值以及保存内核栈的信息。

系统调用很费时,在学习了系统调用的原理后,我们应该也知道了其中的原因。

系统调用通过中断实现,需要从用户态切换到内核态,也就是要完成栈切换和进程切换。
会使用寄存器传参,需要额外的保存和恢复的过程。



系统调用最终还是会由内核函数完成,那么为什么不直接调用内核函数呢?
这是因为用户空间的程序无法直接执行内核代码,因为内核驻留在受保护的地址空间上,不允许用户进程在内核地址空间上读写。所以,应用程序会以某种方式通知系统,告诉内核需要执行一个函数调用,这种通知机制是靠软中断来实现的,通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序就是所谓的系统调用处理程序。

Intel x86系列微机共支持256中向量中断,Linux对256个向量的分配如下:
(1) 从0~31的向量对应异常和非屏蔽中断。
(2) 从32~47的向量分配给屏蔽中断。
(3) 从48~255的向量用来标识软中断。Linux只用了其中一个(128向量即0x80向量)用来实现系统调用。

注:Linux为什么只使用一个中断号来对应所有的系统调用,而不是一个中断号对应一个系统调用?

        由于中断号是有限的,操作系统不舍得每一个系统调用对应一个中断号,而更倾向于用一个或少数几个中断号来对应所有的系统调用。Linux则使用int 0x80来触发所有系统调用。每个系统调用对应一份系统调用号,这个系统调用号在执行int 0x80指令前会放置在某个固定的寄存器里(eax),对应的中断代码会取得这个系统调用号,并且调用正确的函数


                        

总结:

使用系统调用的方式有两种:

  1. glibc库中封装了系统调用,通过c库间接调用

glibc 是 Linux 下使用的开源的标准 C 库,它是 GNU 发布的 libc 库,即运行时库。glibc 为程序员提供丰富的 API(Application Programming Interface),除了例如字符串处理、数学运算等用户态服务之外,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。

  • 通常情况,每个特定的系统调用对应了至少一个 glibc 封装的库函数,如系统提供的打开文件系统调用 sys_open 对应的是 glibc 中的 open 函数;
  • 其次,glibc 一个单独的 API 可能调用多个系统调用,如 glibc 提供的 printf 函数就会调用如 sys_opensys_mmapsys_writesys_close 等等系统调用;
  • 另外,多个 API 也可能只对应同一个系统调用,如glibc 下实现的 malloccallocfree 等函数用来分配和释放内存,都利用了内核的 sys_brk 的系统调用。

     2.传递系统调用号,通过syscall直接调用

使用上面的方法有很多好处,首先你无须知道更多的细节,如 chmod 系统调用号,你只需了解 glibc 提供的 API 的原型;其次,该方法具有更好的移植性,你可以很轻松将该程序移植到其他平台,或者将 glibc 库换成其它库,程序只需做少量改动。
但有点不足是,如果 glibc 没有封装某个内核提供的系统调用时,就没办法通过上面的方法来调用该系统调用。如我自己通过编译内核增加了一个系统调用,这时 glibc 不可能有你新增系统调用的封装 API,此时我们可以利用 glibc 提供的syscall 函数直接调用。

系统调用流程概述:

也就是通过系统调用接口(read、write等函数)到达系统调用函数的过程(sys_read,sys_write等函数)的过程

系统调用按照固定的规则进行。寄存器EAX传递系统调用号。系统调用号用来确定系统调用。寄存器EBX,ECX,EDX,ESI,EDI,EBP依次传递系统调用参数。参数个数决定设置寄存器的个数。int0x80指令切入内核,触发软中断,执行system_call,再根据系统调用号执行对应的系统调用。系统调用执行完成后返回。寄存器EAX保存系统调用的返回值。

原文链接:https://blog.csdn.net/qq_43646576/article/details/102841078
原文链接:https://blog.csdn.net/qq_36822217/article/details/107237670                       
原文链接:https://blog.csdn.net/leimeili/article/details/134842298原文链接:https://www.cnblogs.com/hazir/p/three_methods_of_syscall.html

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值