[网易云课堂]Linux内核分析(六)—— 分析Linux内核创建一个新进程的过程

付何山+原创作品转载请注明出处+《Linux内核分析》MOOC课程;

导读:本文分为三个部分,第一部分描述实验过程,第二部分描述实验原理,第三部分总结。

一、课程实验

实验环境:Ubuntu 16.04
实验代码:孟宁老师github
实验准备:正好重新换回ubuntu系统,于是就在本地按照老师的实验指导配置了一下,虽然不是本节课的内容,但希望还是能对大家配置有所帮助,毕竟确实是有一些坑在里面的。特别注意,我使用的是Ubuntu 16.04版本,且已安装qemu,未安装qemu请在进行下面步骤前使用如下两条命令安装qemu。

sudo apt-get install qemu
sudo ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu

实验指导可参考孟宁老师第三课的实验指导
链接需要有网易云课堂账号才能打开,无法打开可按照下面命令操作。P.S. 可将下面命令直接加入到bash文件中,更改bash文件权限执行即可。

# 下载内核源代码编译内核
cd ~/LinuxKernel/
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz
xz -d linux-3.18.6.tar.xz
tar -xvf linux-3.18.6.tar
cd linux-3.18.6
make i386_defconfig
make # 一般要编译很长时间,少则20分钟多则数小时

# 制作根文件系统
cd ~/LinuxKernel/
mkdir rootfs
git clone https://github.com/mengning/menu.git  # 如果被墙,可以使用附件menu.zip 
cd menu
# 下面的几条命令可以直接使用make rootfs执行
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img

# 启动MenuOS系统
cd ~/LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.im

上面可能会碰到缺少gcc-compiler5.h的情况,请自行google下载该头文件,或者调整gcc版本。
注意,如上的步骤不会让你得到一个可以调试的内核,进入linux-3.18.6文件夹下,输入

make menuconfig
在弹出的界面kernel hacking

在弹出的界面中选择kernel hacking,再选中compile-time checks and compiler options,再选中compile with kernel with debug info即可。而后退出该界面,在当前文件夹下再次make即可得到一个带有调试信息的内核。
上面可能会碰到缺少curses.h的情况,请使用sudo apt-get install libncurses5-dev命令安装即可。
而后将menu下的Makefile中rootfs部分的最后一条qemu命令末尾加上-s -S,注意与之前的rootfs.img之间有空格。在menu下make rootfs,而后打开另一个shell窗口,进入LinuxKernel文件夹,打开gdb,使用如下命令:

file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
break start_kernel # 断点的设置可以在target remote之前,也可以在之后

即可开始对内核进行调试。

实验过程:先让内核跑起来,输入c,而后使用ctrl+c使gdb可以输入。建立断点。如下所示

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

设置好断点后在qemu中输入fork,而后在gdb中不断输入c(或输入一次c后回车)即可观察得到下图:

实验断点图

此时qemu处已经输出fork的返回值,如下图所示:

实验结果截图

可以观察到程序依次停在sys_clone,do_fork,copy_process,dup_task_struct, copy_thread 和 ret_from_fork。这就是整个fork创建新进程的大致调用流程。
下面我们将对大致调用流程进行分析。

二、实验原理

新创建的子进程处于可运行状态,需要调度程序把CPU控制权交给新创建的子进程才能实际运行。
do_fork()利用copy_process来创建进程描述符以及子进程执行所需要的所有其他内核数据结构。

do_fork
stack_start
用户状态下栈的起始地址。

regs
指向寄存器集合的指针,仿照系统调用时内核栈的压栈顺序。后面会再次看到。

stack_size
用户状态下栈的大小。该参数通常可以设置为0。

parent_tidptr和child_tidptr
指向用户空间的两个指针,分别指向父子进程的TID。

返回值
如果成功返回新建进程的PID,失败返回错误码,一般为负值。

do_fork函数流程如下:

do_fork函数流程

之后do_fork调用copy_process,copy_process再调用dup_task_struct,copy_thread等进行核心的复制功能,copy_process主要流程如下。dup_task_struct主要是复制进程的task_struct(可以理解为PCB):

copy_process流程

之后使用ret_from_fork从函数返回。即完成了整个进程复制过程。

三、总结

实际上,用户空间的寄存器、用户态堆栈等信息在切换到内核态的上下文时保存在内核栈中,父进程在内核态(dup_task_struct)复制出子进程,但子进程作为一个独立的进程,之后被调度运行时必须有一个指令地址,进程切换时,ip地址及当前内核栈的位置esp都存在于thread_info中,由copy_thread设置其thread.ip指向ret_from_fork作为子进程执行的第一条语句,并完成了内核态到用户态的切换。

进程创建由系统调用来建立新进程,归根结底都是调用do_fork来实现。do_fork主要就是调用copy_process,而copy_process初始化task_struct结构体分配给子进程,并为其分配pid,最后将其加入可运行队列中。在内核栈中,dup_task_struct()àcopy_thread(),copy_thread()函数将父进程内核栈复制到子进程中,同时设置子进程调度后执行的第一条语句地址为do_frok返回,并将保存返回值的寄存器eax值置为0,因此子进程返回为0,而父进程继续执行之后的初始化,最后返回子进程的pid(tgid)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值