LINUX内核研究----系统调用过程分析

系统调用的定义:

      因为计算机中的各种资源都是由操作系统统一管理,凡是和系统资源相关的操作都必须通过操作系统来完成,例如IO操作、分配内存、文件操作等。而用户程序是没有权限直接进行这些操作的,所以必须通过某种方式来向操作系统提出服务请求。因此操作系统必须提供一套某种方式的接口,让用户通过这些接口使用操作系统提供的各项功能或者访问系统资源,操作系统给应用程序的接口就是系统调用。

系统调用实现的原理

系统调用是CPU运行在内核态的,而应用程序都是CPU运行在用户态。

为了使用系统调用,CPU必须从用户态切换到内核态,而操作系统是通过中断来从用户态切换到内核态。也涉及到程序内核栈和用户栈的切换,实质上就是函数调用堆栈的切换过程。主要是CPU的权限的改变。

 

中断的类型:

硬件中断:这种中断来自于硬件异常,如电源掉电、键盘被按下。

软件中断:这种中断是一条CPU指令去触发某个中断并执行相应的中断处理程序。例如在X86下,int 0x80这条指令会去调用第0X80号中断处理程序,Linux下则使用int 0x 80来触发所有的系统调用,而操作系统再使用一个系统调用号来标识具体是哪个系统调用,这些系统调用号在执行int 0x80指令前通过固定的寄存器来传递参数。

中断有两个属性:一个称作中断号;一个称为中断处理程序。

linux内核中有一个数组叫做中断向量表,这个数组的第n项指向了第n号中断的中断处理程序的函数指针。Int 0x80对应下标为0x80项的中断处理程序。

中断发生时,CPU会暂停当前执行的代码,根据中断的中断号去调用对应的中断处理程序。

Linux是通过0x80号中断来作为系统调用的入口,WINDOWS是通过0x2E号中断作为系统调用的入口。

产生0x80触发中断时,各个通用的寄存器用来传递参数:EAX寄存器用来表示系统调用对应的系统调用号。比如EAX=1时表示exit();EAX=2时表示fork();EAX=3时表示read();EAX = 4时表示write(),这个值也是系统调用表中的下标。

每个系统调用都对应于内核源代码中的一个函数,它们都是以sys_开头的函数,如exit()系统调用对应内核中的sys_eixt()函数。

系统调用函数根据函数参数不同,分别使用6个寄存器来传递,它们分别是EBX,ECX,EDX,ESI,EDI,EBP.

当系统调用返回时,EAX等寄存器又作为保存调用结果的返回值,比如说返回文件描述符或者是fork函数在父子进程返回的PID。


以Fork()函数来具体分析系统调用的过程:

1、触发中断:

CPU执行到int 0x80中断指令产生0x80号软中断,CPU的执行指令的特权级也从用户态切换到内核态。

2、函数栈帧的切换

这时候程序也必须从用户栈切换到内核栈。从用户处理函数返回时,程序的当前栈还要从内核栈切换回用户栈。

程序当前栈是指EBP寄存器中的值所在栈空间。

用户栈切换到内核栈的实际切换行为:

找到当前进程的内核栈(每个进程都有自己的内核栈)

 

3、根据中断号0x80在中断向量表中查询到对应的中断处理程序,0x80对应的是system_call中断处理程序,再根据EAX的值(也就是系统调用号,fork的系统调用号是2)在系统调用表中调用sys_fork()系统调用函数。

内核里的系统调用函数往往以sys_加上系统调用函数名来命名。

4、然后是sys_fork()函数内核源码做的事情。

 

系统调用的弊端:

       使用不方便,操作系统提供的接口过于原始,必须了解很多操作系统相关的细节。使用不方便

       各个操作系统之间的系统调用函数不兼容如linux和windows就完全不一样。即便是同系列的操作系统都不一样,如linux 和unix就不相同。

解决这些问题

方法是通过对系统调用进行包装:

于是运行库作为系统调用和程序之间的一个抽象层,对不同操作系统的系统调用进行包装为应用程序提供同意固定的接口,运行库都有一个标准,叫做标准库。这样可以掩盖系统调用的一些缺点,但是为了兼容各个系统标准库提供的函数的功能相对来说会减少但让系统调用的使用更加简单便捷。

例如C语言的fread()函数,在windows下调用Readfile这个系统调用,但是在linux下可能就调用了read()这个系统调用。但是同样的C语言代码都可以在不同的平台上直接编译,产生一致的效果。

参考《程序员的自我修养》

参考linux内核源代码情景分析

猜你喜欢

转载自blog.csdn.net/run32875094/article/details/78388709