Linux 0.11实验笔记之系统调用

系统调用过程

  这一部分将对Linux系统中的系统调用做简要概述,仅仅是提纲掣领的作用,读者可以略看甚至在明白大致过程的情况下不看。具体细节和有关知识将在后面部分单独给出。

  我们在学习C语言的时候写下的第一个代码一定是printf(“Hello World!”)。这一句代码的作用很简单,仅仅是在屏幕上输出一串字符,但是为什么这样一句简单的printf就能够做到这件事情呢?这其中的原理是如何?本篇文章将从系统调用过程的角度分析printf的背后究竟发生了一些什么。

  用户首先调用printf,printf函数中做了很多事情,但是最最重要的莫过于int $0x80这一中断指令。CPU一旦执行了这条指令,它马上就会执行中断操作,然后陷入内核中去执行system_call函数。system_call函数可以说是所有系统调用的中转站,因为不管哪个系统调用都必须经过这个函数,在这个函数中会进行查表以调用上层用户希望调用的那个系统调用。在这里,为了在屏幕上打出字符串,我们希望使用的是sys_write函数,所以系统将从system_call函数跳转到sys_write函数,这就完成了一次系统调用。

从函数角度来说,我们大致经历了下面三个函数:
printf -> system_call ->sys_write

下面我将具体展开其过程中的细节

系统调用细节

进入内核

  如果拥有最基本OS常识,我们会非常明白在调用printf的时候系统是处在用户态的,最后OS替我们打印出字符串的时候又是处于内核态。那么用户态和内核态的转变是如何做到的呢?究竟怎么样算用户态,怎么样算内核态?我们究竟在哪一刻的时候,可以说是我们进入了内核态呢?下面将对这些问题进行解释。

  如果不想深究具体细节,你可以认为我们在计算机执行了int $0x80指令之后就已经进入了内核,但是这篇文章显然不会止步于此。

  CPU每执行一条指令(注意,这里的指令是汇编级指令)它就会去查看一遍自己的INTR寄存器,其中的某位一旦被置位CPU将立刻产生中断。产生中断之后CPU将根据IDTR寄存器中存放的地址找到IDT表,然后拿着手里的中断号在IDT表中找到相应的门描述符,如下图所示。门描述符有三种,有关门描述符可以去查找相关资料,我们在这里碰到的是陷阱门描述符,如下图所示。
Linux 0.11实验笔记之系统调用_第1张图片
Linux 0.11实验笔记之系统调用_第2张图片
  下面我们回顾一下OS中的特权级概念。OS中的特权级一共分为4级,0优先级最大用于内核态,3优先级最小用于用户态。再回顾一下段保护概念,每个段中都会有一个两位的DPL字段用于标识该段的特权级大小。每当我们要访问一个段的时候,CPU都会对被访问段的DPL与CPL进行比较(为了区分,我们一般将现处段的DPL成为CPL)只有当CPL的优先级大于等于DPL时才能够被允许访问。

  IDT表是处于内核态中的,而我们用户本身处于用户态,这又怎么访问呢?实际上,系统在初始化的时候就给用户留了一扇后门,它故意将IDT表所处的位置的DPL设置为3。(我猜测CPU是在要载入新的段选择符的时候会查看相应的DPL与自身的CPL,如果允许,那么就装入新的段选择符)所以我们在这里就能装入新的段选择符然后跳到事先设置好的地方,也就是system_call函数进行执行。这一段回答了一开始的问题,我们什么时候真正完成了从用户态到内核态的切换,就是我们把当前DPL为3的段选择符替换为DPL为0的段选择符的那一刻我们就算是进入了内核态!所以说,CPU当前装入的段的DPL是0还是3,这是判断我们当前处于内核态还是用户态的关键。

内核之后

  进入内核之后的事情就非常简单了,IDT表中取得system_call的地址,系统向其跳转,然后在其中又会根据eax寄存器存放的数值选择将要继续跳转的函数,跳到相应的地方进行执行。

欢迎愿意与我讨论内核的同学对我私信!

你可能感兴趣的:(Linux 0.11实验笔记之系统调用)