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

章强 + 原创作品转载请注明出处 + 《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开始执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值