《Linux内核分析》(六)——Linux系统进程创建
2015-04-12 21:51
169 查看
作者:Sandy 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
实验环境:c+Linux64位 (32位系统可能结果会不同)
依照学术诚信条款,我保证此回答为本人原创,所有回答中引用的外部材料已经做了出处标记。
上述过程可描述为:0号进程->1号内核进程->1号内核线程->1号用户进程(init进程)->getty进程->shell进程
注意,上述过程描述中提到:1号内核进程调用执行init并演变成1号用户态进程(init进程),这里前者是init是函数,后者是进程。两者容易混淆,区别如下:
1.init()函数在内核态运行,是内核代码
2.init进程是内核启动并运行的第一个用户进程,运行在用户态下。
3.init()函数调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。
摘自文章http://www.xuebuyuan.com/1163044.html
进程的组成主要有四个部分:
进程控制块(PCB):进程标志
进程程序块:可与其他进程共享
进程数据块:进程专属空间,用于存放各种私有数据以及堆栈空间
独立的空间,32位Linux中为32G(如果没有这一项则认为是线程)
1)在内存中申请一页内存存放进程控制块task_struct,并返回进程号nr,并在task数组的nr处存放task_struct的指针,还要将task的当前指针current指到nr处;
2)将父进程的task_struct的内容复制到新进程的task_struct中作为模版
3)对task_struct中的信息进行修改,主要进行一下工作:设置父进程、清除信号位图、时间片、运行时间、根据当前环境设置tss(内核态指针esp0指向task_struct所在页的顶端)、设置LDT的选择子等(根据nr指向GDT中相应的ldt描述符)。
4)设置新进程的代码段、数据段的基地址和段长:更新task_struct中的代码开始地址:进程号(nr)×64M,更新task_struct中局部描述符表中的代码段和数据段描述符。
5)复制父进程的页表目录项和页表:在页目录表中,复制父进程的页表目录项,目的地址由新进程的线性地址计算出来;对每个对应的页表目录项申请一个空闲页,并用页表地址更新页表目录项,最后将父进程页表中各项复制到新进程对
应的页表中,也就是说,这个时候,子进程与父进程共享物理内存。
6)更新task_struct中的文件信息:文件打开次数加1,父进程的当前目录引用数加1。
7)设置TSS和LDT描述符项:在全局描述符表(GDT)中设置新任务的TSS描述符项和LDT段的描述符项,使TSS描述符项和LDT描述符项分别指向task_struct的TSS结构和LDT结构。
8)将任务设置为就绪状态,向当前进程(父进程)返回新进程号。
http://www.360doc.com/content/14/0928/15/12747488_413000182.shtml
fork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容,新旧进程使用同一代码段,复制数据段和堆栈段,这里的复制采用了注明的copy_on_write技术,即一旦子进程开始运行,则新旧进程的地址空间已经分开,两者运行独立。
2、vfork
vfork函数创建的子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改都为父进程所见。这与fork是完全不同的,fork进程是独立的空间。另外一点不同的是vfork创建的子进程后,父进程会被阻塞,直到子进程执行exec()和exit()。
3、clone
函数功能强大,带了众多参数,因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系
int clone(int (fn)(void ), void *child_stack, int flags, void *arg);
这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本”, child_stack明显是为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。
总结:
相同:
系统调用服务例程sys_clone, sys_fork, sys_vfork三者最终都是调用do_fork函数完成。
do_fork的参数与clone系统调用的参数类似, 不过多了一个regs(内核栈保存的用户模式寄存器). 实际上其他的参数也都是用regs取的
区别在于:
clone:
clone的API外衣, 把fn, arg压入用户栈中, 然后引发系统调用. 返回用户模式后下一条指令就是fn.
sysclone: parent_tidptr, child_tidptr都传到了 do_fork的参数中
sysclone: 检查是否有新的栈, 如果没有就用父进程的栈 (开始地址就是regs.esp)
fork, vfork:
服务例程就是直接调用do_fork, 不过参数稍加修改
clone_flags:
sys_fork: SIGCHLD|0;
sys_vfork: SIGCHLD| (clone_vfork | clone_vm)
用户栈: 都是父进程的栈.
parent_tidptr, child_ctidptr都是NULL.
http://www.xuebuyuan.com/1163044.html
参考文献
http://www.xuebuyuan.com/1163044.html
http://www.360doc.com/content/14/0928/15/12747488_413000182.shtml
/article/4996048.html
http://www.tucaobj.com/note/os/201407191030272928.jhtml
/article/4996048.html
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
实验环境:c+Linux64位 (32位系统可能结果会不同)
依照学术诚信条款,我保证此回答为本人原创,所有回答中引用的外部材料已经做了出处标记。
一,关于Linux进程
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的祖先。由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号…的若干终端注册进程getty。每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。上述过程可描述为:0号进程->1号内核进程->1号内核线程->1号用户进程(init进程)->getty进程->shell进程
注意,上述过程描述中提到:1号内核进程调用执行init并演变成1号用户态进程(init进程),这里前者是init是函数,后者是进程。两者容易混淆,区别如下:
1.init()函数在内核态运行,是内核代码
2.init进程是内核启动并运行的第一个用户进程,运行在用户态下。
3.init()函数调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。
摘自文章http://www.xuebuyuan.com/1163044.html
进程的组成主要有四个部分:
进程控制块(PCB):进程标志
进程程序块:可与其他进程共享
进程数据块:进程专属空间,用于存放各种私有数据以及堆栈空间
独立的空间,32位Linux中为32G(如果没有这一项则认为是线程)
二,进程创建流程
Linux中一般进程都是由现有的一个进程创建的,也就是我们所说的父进程,子进程。具体的创建是通过fork()实现的。下面就让我们一起了解一下0.11核心中fork()的大体工作过程:1)在内存中申请一页内存存放进程控制块task_struct,并返回进程号nr,并在task数组的nr处存放task_struct的指针,还要将task的当前指针current指到nr处;
2)将父进程的task_struct的内容复制到新进程的task_struct中作为模版
3)对task_struct中的信息进行修改,主要进行一下工作:设置父进程、清除信号位图、时间片、运行时间、根据当前环境设置tss(内核态指针esp0指向task_struct所在页的顶端)、设置LDT的选择子等(根据nr指向GDT中相应的ldt描述符)。
4)设置新进程的代码段、数据段的基地址和段长:更新task_struct中的代码开始地址:进程号(nr)×64M,更新task_struct中局部描述符表中的代码段和数据段描述符。
5)复制父进程的页表目录项和页表:在页目录表中,复制父进程的页表目录项,目的地址由新进程的线性地址计算出来;对每个对应的页表目录项申请一个空闲页,并用页表地址更新页表目录项,最后将父进程页表中各项复制到新进程对
应的页表中,也就是说,这个时候,子进程与父进程共享物理内存。
6)更新task_struct中的文件信息:文件打开次数加1,父进程的当前目录引用数加1。
7)设置TSS和LDT描述符项:在全局描述符表(GDT)中设置新任务的TSS描述符项和LDT段的描述符项,使TSS描述符项和LDT描述符项分别指向task_struct的TSS结构和LDT结构。
8)将任务设置为就绪状态,向当前进程(父进程)返回新进程号。
http://www.360doc.com/content/14/0928/15/12747488_413000182.shtml
三,clone, fork, vfork实现方式
1、forkfork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容,新旧进程使用同一代码段,复制数据段和堆栈段,这里的复制采用了注明的copy_on_write技术,即一旦子进程开始运行,则新旧进程的地址空间已经分开,两者运行独立。
2、vfork
vfork函数创建的子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改都为父进程所见。这与fork是完全不同的,fork进程是独立的空间。另外一点不同的是vfork创建的子进程后,父进程会被阻塞,直到子进程执行exec()和exit()。
3、clone
函数功能强大,带了众多参数,因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系
int clone(int (fn)(void ), void *child_stack, int flags, void *arg);
这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本”, child_stack明显是为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。
总结:
相同:
系统调用服务例程sys_clone, sys_fork, sys_vfork三者最终都是调用do_fork函数完成。
do_fork的参数与clone系统调用的参数类似, 不过多了一个regs(内核栈保存的用户模式寄存器). 实际上其他的参数也都是用regs取的
区别在于:
clone:
clone的API外衣, 把fn, arg压入用户栈中, 然后引发系统调用. 返回用户模式后下一条指令就是fn.
sysclone: parent_tidptr, child_tidptr都传到了 do_fork的参数中
sysclone: 检查是否有新的栈, 如果没有就用父进程的栈 (开始地址就是regs.esp)
fork, vfork:
服务例程就是直接调用do_fork, 不过参数稍加修改
clone_flags:
sys_fork: SIGCHLD|0;
sys_vfork: SIGCHLD| (clone_vfork | clone_vm)
用户栈: 都是父进程的栈.
parent_tidptr, child_ctidptr都是NULL.
http://www.xuebuyuan.com/1163044.html
参考文献
http://www.xuebuyuan.com/1163044.html
http://www.360doc.com/content/14/0928/15/12747488_413000182.shtml
/article/4996048.html
http://www.tucaobj.com/note/os/201407191030272928.jhtml
/article/4996048.html
相关文章推荐
- 《LINUX内核分析》第六周作业:Linux系统如何创建一个新进程
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
- shell监控linux系统进程创建脚本分享
- Linux系统编程之--守护进程的创建和详解【转】
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
- Linux系统创建一个新的进程
- 用C语言在Linux系统下创建守护进程(Daemon)
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
- linux下实现文件存储系统,创建和删除大量文件或文件夹时,updatedb进程会更新文件索引节点inode
- [Linux]C语言Linux系统编程创建进程
- Linux操作系统实验二:进程的创建与可执行程序的加载
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
- linux中关于创建子进程系统堆栈的分析
- 《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程
- 八、Linux系统编程-进程(一)进程概念、进程数据结构、进程状态变迁、进程创建和撤销
- Linux系统学习(进程)——1.进程的创建
- linux系统之进程的创建与上下文切换
- linux系统中进程的创建
- shell监控linux系统进程创建脚本分享
- Linux 系统调用之 fork()——进程的创建