linux内核初探(二)

我们来看一个用户进程从创建到退出的完整过程,硬盘上的源代码如下:

 #include<stdio.h>
 int foo(int n){
   char text[2048];
   if(n==0) return 0;
   else{
     int i=0;
     for(i;i<2048;i++) test[i]='\0';
     sleep(5);
     foo(n-1);
    }
 }
 int main(int argc,char **argv){
   foo(6);
   return 0;
 }

此程序文件存储在硬盘上,它的名称是str,我们可以使用./str这个命令来运行它,那么接下来会发生什么呢?

  1. shell会调用fork函数创建一个新进程,fork函数是一个系统调用,首先会为进程申请一个可用的进程号,并 在task[64]中为该进程申请一个空闲位置。

  2. fork的工作才刚刚开始,它必须为str进程新申请一个内存页面来存放task_struct和内核栈。

    • 何为内核栈?当进程转入内核后,执行用代码都是内核代码,每个进程的数据压栈顺序和内容都不一样,这样 就导致必须为每个进程维护一个栈,而这个栈出于安全性考虑又不能在进程的用户空间中,所以内核为每个进程 准备了一套专门的内核栈。

申请好的task_struct结构被挂接到task[64]中,以备内核能够访问。

  1. 由于shell进程和str进程是父子进程的关系,str进程会继承shell的全部管理信息,包括task_struct。但是 内核会对它自己的task_struct进行个性化的设置,重点包括TSS字段。

    • 何为TSS字段?进程执行时,需要用到各种寄存器,这导致进程的切换不是一个简单的跳转,而是一整套寄存器 的值随之改变,要保证进程切换时不发生混乱,进程需要在task_struct中记录每一个寄存器的值,它就是TSS 字段。所以每当进程切换的时候,TSS用来保存现场。

    当然内核会设置它的LDT,重点包括代码段基址和数据段基址,

    1. str进程将shell进程的页表项也继承了过来,这里内核会为str新申请一个页面专门存放其页表(与为task_struct 和内核栈申请空间类似),这个页表当然只能为内核所访问,因为他们是内核用爱管理进程的。

    2. 内核寻址与用户进程有何区别?内核可以通过物理地址直接访问全部的内存,而用户进程只能使用逻辑地址, 只有内核才能将用户进程的逻辑地址与物理地址一一对应,所以在用户空间并不能看到实际的物理内存页,这为 内存空间的安全做好了保障。

    3. str进程自己的LDT与TSS需要与GDT进行挂接,他们对进程的保护至关重要,它保证了段间跳转指令不能超过 段限长也不可能进行段间跳转。然后将自己的状态设置为“就绪态”,表示可以参与轮询了。

    4. 我们清楚str共享了shell进程相同的页表,而实际上这些页目录项对于str进程并没有作用,所以需要解除这些 映射关系,如果要将str程序从硬盘中加载进来,很显然这时候str进程会产生缺页中断,这时候就需要新申请页面 来存放str程序内容,如果在执行的过程中不断需要新的代码就会不断地产生缺页中断,直到加载完整个程序。

    5. 现在我们终于可以看看文章开头提到的程序了,str加载完成后就可以执行它了,我们可以将字符数组的大小 设置成2048字节,所以两次压栈就可以触发一次缺页中断了。所以str反复进行着压栈-缺页中断-分配物理内存的过程。 与我们以前理解的栈空间有很大的区别,栈只是在逻辑地址上是连续的,而实际的物理页则是内核动态分配的,所 以我们用户进程的逻辑地址是由堆栈组成的连续空间,然而对应的具体物理内存则只有内核知道。

    6. str程序执行完毕后,ESP(栈顶指针)就会向高地址收缩,实际使用的栈空间就变小了,所以之前映射的物理页 就会被释放掉,然后进程退出后,内核收回分配给它的一切资源,将其从系统中抹去。



Previous     Next
zhing /
Published under (CC) BY-NC-SA in categories linux  tagged with linux