Chinaunix首页 | 论坛 | 博客
  • 博客访问: 123176
  • 博文数量: 31
  • 博客积分: 2010
  • 博客等级: 大尉
  • 技术积分: 361
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-11 15:38
文章分类

全部博文(31)

文章存档

2008年(31)

我的朋友

分类: LINUX

2008-04-12 19:46:30

    前几天,学习了fork(),自然就要学习exec()了,今天看了挺长时间,对exexc总算有了了解。

当一个程序使用fork()创建了一个子进程时候,这个子进程共享父进程的代码和数据,所以通常再用exec()加载执行另一个新程序,此时子进程的代码、数据(包括堆、栈)将完全被新程序替换掉,并在子进程中开始执行新程序,这个过程就是新进程的加载,下面就以0.11核的exec()函数为基础说一下Linux新进程的加载过程是怎样的。

想到新进程的加载,很自然的就会想到这样一个思路:加载什么,加载到哪,怎么加载。下面就顺着这个思路一起了解一下Linux新进程的加载过程。

一、加载什么

大家知道,Linux中可以直接运行的文件是二进制可执行文件和Shell脚本文件,那么新程序的加载和运行就是对这两种文件进行加载和运行。

二、加载到哪

程序是在物理内存执行的,之前我已经提到,一个进程在内存中主要占用的几个部分,分别是代码段、数据段、BSS,栈,堆,参数。其中,代码、数据、BSS的内容是可执行文件中对应的内容,加载程序并不是把它们的内容从可执行程序中填充到内存中,而是将它们的信息(基地址、长度等)更新到进程控制块(task_struct)中,当CPU第一次实际寻址执行的时候,就会引起缺页中断,操作系统再将实际的内容从可执行文件中复制内容到物理内存中。堆的内容是程序执行中动态分配的,所以加载程序只是将它的起始地址更新到进程控制块中,执行过程中遇到动态分配内存的操作的时候再在物理内存分配实际的页。参数区在新进程加载的时候要存入环境变量和命令行参数列表。栈在程序加载时候存入的内容就是环境参数列表和命令行参数列表的指针和命令行参数的个数。

                           线性地址空间

环境参数列表

命令行参数列表

环境参数列表指针

命令行参数列表指针

命令行参数个数

……………

堆栈增 长区

BSS

数据

代码

  参数                  

               SP                                         数据段/代码段/堆栈段基地址

三、如何加载

下面就说一下新程序加载并运行的具体过程,具体是以0.11核的exec.c文件的内容为基础的。

1)  申请物理页,填充参数

参数的填充要分两种情况,一种是文件是Shell脚本文件,这个时候不但要把shell文件的命令行参数填充进去,还要解析shell文件头信息,将要传入shell程序的参数也要填充到某部分物理内存内,这样参数区中命令行参数列表的长度就加长了:shell程序参数列表。之后,把shell程序作为新的可执行程序,重新启动加载过程。另一种情况就是二进制的可执行文件,这个时候就把环境参数和命令行参数填充到某部分物理内存就行了。这步之后,就会得到参数页的页指针(这些页在物理内存中)一个二进制文件(包括shell程序本身)的内存I节点。

2)  清理工作:

关闭指定的打开的文件;复位协处理器标志;释放页目录项和页表项。

3)  更新LDT

更新ldt中代码段的限长(可执行文件头中记录了代码段的长度),更新数据段的限长(64M),更新代码段和数据段的基地址(其实不用更新,在fork中已经将它俩的基地址设置为该进程的起始地址:64×nr(进程号),这里顺便说一句,堆栈段的段基地址也是这儿,只不过ssds指向ldt中的同一个段描述符,所以就一起更新了)。

4)  建立参数列表用的物理页与逻辑地址的对应关系

就是将储存参数列表的那些物理页利用页表映射到进程逻辑地址空间的最上面的部分,具体做法就是根据逻辑地址计算出页目录项表中页表项的位置,从而找到(新建)页表,再在页表中建立页与物理地址的对应关系。

建立了对应关系,就相当于把参数列表填充到了进程地址空间中了。

5)  在栈(用户栈)中建立参数表指针表:

进程空间的最上面是参数列表,在它的下面就是栈,栈中要存三个数据:环境参数列表指针、用户参数列表指针、命令行参数个数。

6)  更新task_struct中代码段、数据段、堆段的尾值;更新堆栈开始指针值;重新设置用户id和组id

7)  更改上层进程(调用exec的进程)的堆栈内容,使ip指向新进程的代码开始处:

ex.a_entry;使栈指针指向新进程的栈顶,也就是存放命令行参数的地方。由于新进程的代码段、数据段、堆栈段的段选择符和原来进程都是一样的,都是指向ldt[2]处,所以不必更改栈中csfsss的值。

 

   以上就是exec的具体工作过程,当它返回的时候,从栈中弹处的新进程的代码地址和堆栈地址,也就转向了新程序进行执行,当然,前面说过,此时代码段核数据段中是没有内容的,cpu是采用“按需加载”的机制。

  附:可以看出,这里的“加载”是广义的,不但有“复制”的意思,也有“设置”的意思。参数是从内存(命令行参数、环境参数)或者可执行文件中(shell脚本中的shell参数)复制到内存中的;程序的代码段、数据段则是在段描述符中设置它们的段信息,并没有实际把能内容复制进来。

阅读(2901) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~