目录
内存管理
实现shell
生成子进程
子进程号
进程代码、数据、栈从何而来
execv如何组织加工进程需要的数据
子进程的的LDT段及LDT在GDT的段选择子
共享打开的文件
从终端接收命令
内存管理
说是内存管理,其实仅仅是为了实现shell、生成子进程而对内存空间简单的划分了一下。用作者的话说就是划格子,每个进程划出来1M的内存装载镜像。还有页表也都是线性映射,线性地址和物理地址都是一致的。但是不管多么简单,能达到目的就是好的设计。
实现shell
想一想shell是什么东西?好像就是能接受用户输入的命令然后执行这个命令。那么”执行这个命名”是什么意思?就是说这个命令其实是一个程序的名字,执行它就是生成一个子进程来运行这个程序,而且这个程序要放在系统能搜索的几个路径下。
生成子进程
为了shell接收和响应用户的一次输入后依然能够继续处理用户的下次输入,那就必须要要通过某一种方式让shell走两条分支,一条是去处理上次的用户输入,还有一条分支是继续等待用户的下次输入,如果把这两个分支全部都杂糅到给shell,恐怕逻辑会很复杂。
我不知道当初的子进程概念是由于什么需求而引出的。但是它用来配合shell的实现是再合适不过了。我们仅仅需要在代码上安排两条分支,在进入分支之前,克隆一个子进程出来,子进程进入处理用户的的分支,而shell本身则可以继续接收用户的输入。
子进程号
那么用什么来分流父子进程呢?也就是克隆出了一个进程后,怎么确定哪个是父进程哪个是子进程呢?这就是子进程号的功能,内核将子进程号返回给父进程,返回给子进程的却是0,所以通过这种方式,分流了父子进程逻辑。
进程代码、数据、栈从何而来
那进程需要的数据从哪里来呢?那就是execv函数的功能了,其实shell的代码还是不复杂的,仅仅是将用户的输入用空格分隔开来,将每个字符串你的其实地址赋值给一个指针数组的相应元素。然后传给execv函数,剩下的就是execv函数的职责。
execv如何组织加工进程需要的数据
相比较于linux 0.12来说,不用考虑太大的文件,不用考虑页表映射,更不用考虑缺页异常,仅仅是把逻辑地址加上每个进程的线性地址就是指令、数据或者栈的物理地址了。
代码、数据:读取命令程序在硬盘上的镜像文件,然后按照elf文件的格式放到子进程空间的相应位置。
栈:shell传递过来的是参数的指针数组,字符串本身还在另一段地址上,execv要做的就是把指针数组和字符串本身组合到一块,这样内核随后复制参数给子进程方便一些,不用一个字符串一个字符串的复制了。全部一股脑的都拿过来用就好了。
子进程的的LDT段及LDT在GDT的段选择子
不知道大家有没有想过,用LDT来寻址,那LDT本身是在进程表里面存放的,我们克隆子进程的时候可没有克隆进程表啊,只是在进程表数组中找了一个空闲的表项给子进程用。而且克隆的时候也没有动GDT,那子进程是如何能正确运转呢?
这又是一个鸡和蛋的问题?既然大家都相互牵扯纠缠不清,倒不人为的把最开始的状态确定下来,以便后来的流程能运转起来。
那就提前在初始化过程中,把进程表中的LDT和GDT中LDT描述符都初始化好,等分配给子进程的时候就可以直接用了。
共享打开的文件
还有一个问题,就是父进程打开的文件,子进程同样继承了下来,这个时候就要告诉TASK_FS了,把父进程的文件描述符的引用数都加1。
从终端接收命令
那么shell在从终端接受用户的输入时候,是如何构造指针数组的呢?
- 将用户的输入都读到一个缓冲区上
- 定义一个指针数组
- 按照空格分隔字符串,将每一个字符串的起始地址赋值给2中指针数组相应的位置,这样最初的栈数据就有了。到execv中只需要把两者放到一起就好了。