实验六:分析Linux内核创建一个新进程的过程
作者:王朝宪 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
进程
1.进程即处于执行期的程序,并不局限于一个可执行的代码,是处于执行期程序以及其相关资源的总称。
2.Linux系统中,对于进程和线程并没有明显的区分,线程是一种特殊的进程。
3.Linux系统中,常用fork()进程创建子进程。调用fork()进程的成之为其子进程的父进程。
4.fork()继承实际上由clone()系统调用实现。最后通过exit()退出执行。
操作系统三大功能:
- 进程管理
- 内存管理
- 文件系统
任务描述符及任务结构
1.进程描述符(PID),是每一个进程的唯一标识值
2.PID中state描述了进程当前的状态,每个进程都必然处于下列五中状态中的一种。
TASK_RUNNING(可运行):标识该进程正在运行或等待运行,这是进程在用户空间执行的唯一可能状态。
TASK_INTERRUPTLBLE:(可中断):进程在睡眠,等待某种条件的达成,等待被唤醒。
TASK_UNINTERRUPTLBLR:(不可中断)
_TASK_TRACED:被其他进程跟踪的进程
_TASK_STOPPED:停止执行进程
进程创建
1.Linux将进程创建拆分为两个单独函数:fork()与exec(),前者拷贝当前进程创建子进程,后者负责读取可执行文件并将其载入地址空间开始运行。
2.Linux的fork()函数具有写时拷贝功能,只有在需要时,数据才会被复制。
3.fork()
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
线程在Linux中实现
1.线程的创建和进程创建类似,但是在调用clone()时候需要传递一些参数标志来指明需要共享的资源。
2.传递的参数决定了新创建进程的行为方式和父子进程之间的共享种类。
3.内核线程与普通线程的区别在于:内核线程没有独立的地址空间,仅在内核空间运行。内核线程只能由其他内核线程创建,其祖先为kthreadd
Task running表示可以运行。是否在运行取决于他是否取得了内核的控制权
gdb跟踪分析一个fork系统调用内核处理函数sys_clone
启动menuos
cd LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
mv test_fork.c test.c
make rootfs
gdb调试fork命令
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
gdb
file linux-3.18.6/vmlinux
target remote:1234
总结:
Linux通过复制父进程来创建一个新进程,通过调用do_ fork来实现并为每个新创建的进程动态地分配一个task_ struct结构。
1. 新进程的开始
从ret_from_fork处开始执行
-
dup_task_struct
中为其分配了新的堆栈 -
copy_process
中调用了sched_fork
,将其置为TASK_RUNNING
-
copy_thread
中将父进程的寄存器上下文复制给子进程,这是非常关键的一步,这里保证了父子进程的堆栈信息是一致的。 - 将
ret_from_fork
的地址设置为eip寄存器的值,这是子进程的第一条指令。
2. 执行起点与内核堆栈保证一致
-
在设置子进程的ip之前:
*childregs = *current_ pt_ regs();
将父进程的regs参数赋值到子进程的内核堆栈,*childregs的类型为pt_regs,其中存放了SAVE ALL中压入栈的参数。
3.大致框架
-
复制一个PCB——task_struct
p = dup_task_struct(current);//复制进程的PCB int __weak arch_dup_task_struct(struct task_struct *dst,struct task_struct *src)
{
*dst = *src;//通过赋值实现复制
return 0;
} -
给新进程分配一个新的内核堆栈
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈 -
修改复制过来的进程数据,比如pid、进程链表等(见copy_process内部)。
/*copy_thread in copy_process*/
/*拷贝内核堆栈数据和指定新进程的第一条指令地址*/
*childregs = *current_pt_regs(); //复制内核堆栈,只复制了SAVE_ALL相关的部分
childregs->ax = 0; //子进程的fork返回0的原因 p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址