您的位置:首页 > 运维架构 > Linux

分析Linux内核创建一个新进程的过程

2017-04-02 16:53 295 查看
章强 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

进程描述

进程控制块PCB——task_struct,也叫进程描述符,为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息。

struct task_struct数据结构很庞大:可以看到state进程状态,stack内核堆栈,flag进程标识符,CONFIG_SMP条件编译(多处理器用到)



进程的内存地址空间:



下图是抽象图:task进程链表管理,tty控制台,fs文件系统,files打开的文件描述符,mm内存管理描述,signal进程间通信信号



linux的进程状态与操作系统不同,就绪状态和运行状态都是TASK_RUNNING,fork创建好新进程变成就绪态,调度器选择了进程后变成运行态。

进程在TASK_RUNNING状态下表示其是可运行的,根据cpu是否实际执行该任务来划分是就绪态还是运行态。

正在运行进程调用do exit会变成task_zombie僵尸进程由系统处理掉

正在运行进程等待资源会进入阻塞态,可用时被唤醒进入就绪态。



进程pid



双向链表



程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系(都是通过双向链表来连接起来的)




内核处理过程

(1) do_fork

调用
copy_process
,将当前进程复制一份出来给子进程,并且为子进程设置相应地上下文信息。

调用
wake_up_new_task
,将子进程放入调度器的队列中,此时的子进程就可以被调度进程选中运行。

(2) copy_process

创建进程描述符以及子进程所需要的其他所有数据结构,为子进程准备运行环境

调用
dup_task_struct
复制一份
task_struct
结构体,作为子进程的进程描述符。

复制所有的进程信息

调用
copy_thread
,设置子进程的堆栈信息,为子进程分配一个pid。

(3) dup_ task_ struct

先调用
alloc_task_struct_node
分配一个
task_struct
结构体。

调用
alloc_thread_info_node
,分配了一个union。这里分配了一个
thread_info
结构体,还分配了一个stack数组。返回值为ti,实际上就是栈底。

tsk->stack = ti
将栈底的地址赋给task的stack变量。

最后为子进程分配了内核栈空间。

执行完
dup_task_struct
之后,子进程和父进程的task结构体,除了stack指针之外,完全相同。

(4) copy_thread

获取子进程寄存器信息的存放位置

对子进程的thread.sp赋值,将来子进程运行,这就是子进程的esp寄存器的值。

如果是创建内核线程,那么它的运行位置是
ret_from_kernel_thread
, - 将这段代码的地址赋给thread.ip,之后准备其他寄存器信息,退出

将父进程的寄存器信息复制给子进程。

将子进程的eax寄存器值设置为0,所以fork调用在子进程中的返回值为0.

子进程从
ret_from_fork
开始执行,所以它的地址赋给thread.ip,也就是将来的eip寄存器。

(5) 运行新进程:从ret_from_fork处开始执行

dup_task_struct
中为其分配了新的堆栈

copy_process
中调用了
sched_fork
,将其置为TASK_RUNNING

copy_thread
中将父进程的寄存器上下文复制给子进程,这是非常关键的一步,这里保证了父子进程的堆栈信息是一致的。

ret_from_fork
的地址设置为eip寄存器的值,这是子进程的第一条指令。







进程创建

创建新进程是通过复制当前进程实现的,fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建。

do_fork主要是复制了父进程的task_struct,然后修改必要的信息,从而得到子进程的task_struct。



刚fork出来的子进程是从ret_from_fork开始执行的,然后跳转到syscall_exit,从系统调用中返回。

实验截图

cd LinuxKernel
rm menu -rf
cd ..
cp -rf Code/shiyanlou_cs195/menu LinuxKernel
cd LinuxKernel/menu
mv test_fork.c test.c
make rootfs




qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S


在新窗口打开gdb

$ gdb
$ file linux-3.18.6/vmlinux
$ target remote:1234


设置断点

b sys_clone
b do_fork
b dup_task_struct
b copy_process
b copy_thread
b ret_from_fork










最后跳转到syscall_exit



总结

新进程的执行源于以下过程:

1. dup_task_struct中为其分配了新的堆栈。

2. 调用了sched_fork,将其置为TASK_RUNNING。

3. copy_thread中将父进程的寄存器上下文复制给子进程,保证了父子进程的堆栈信息是一致的。

4. 将ret_from_fork的地址设置为eip寄存器的值。

最终子进程从ret_from_fork开始执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: