分析Linux内核创建一个新进程的过程
作者:李嘉
原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
1、内核三大特征
进程管理 内存管理 文件系统 核心为进程管理
进程控制块pcb结构 task_struct 400多行代码
Shell执行命令并并打入镜像
cd LinuxKernel
rm -rf menu
git clone https://github.com/mengning/menu.git
cd menu
mv test_fork.c test.c
make rootfs
启动后如图

查看test.c对应的Fork命令

运行fork

关闭并开启调式模式跟踪 S s 已经通过gdb进行调试 来分析
sys_clone
do_fork
dup_task_struct
copy_process
copy_thread
ret_from_fork
6个函数

运行c继续


如图所示 do_fork调用copy_process,copy_process调用的copy_thread,
借鉴了一位朋友的堆栈分析图

1、分析 http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235
1235 struct task_struct {
1236 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
1239 unsigned int flags; /* per process flags, defined below */
task_struct 的主要内容如下
state 进程运行状态
stack 进程的内核运行堆栈
flag 标识
CONFIG_SMP 多处理器使用到(条件编译)
进程调度相关
int on_rq; 运行队列
prio 优先级
进程链表 (双向)
struct list_head tasks
进程管理 内存地址空间相关(物理地址及逻辑地址的转换 内存管理单元mmu)
struct mm_struct *mm,*active_mm;
pid 进程标识
进程父子关系 兄弟 父子 都是通过双向链表相连
struct list_head ptraced 调式用的
utime stime 时间相关
thread struct cpu 相关 用于进程上下文切换
struct fs_struct *fs 文件系统相关数据结构
struct file_struct *files 打开的文件描述符列表
signal 信号处理相关
mutex 互斥锁
pipe 管道相关
当前进程复制进程描述符(写死)
1号复制0号的PCB 修改PID 等 加载INIT 可执行程序
1号进程是所有用户态进程的父进程
fork 出现两个进程 父进程和子进程 父进程返回子进程的ID,子进程返回0
在 Linux 内核中,供用户创建进程的系统调用fork()函数的响应函数是
sys_fork
sys_vfork
sys_clone
这3个函数这三个函数都是通过调用内核函数 do_fork() 来实现的。根据
调用时所使用的 clone_flags 参数不同,do_fork() 函数完成的工作也各异。
简要分析do_fork()是怎么工作的,代码以如下:
do_fork(unsigned long clone_flags,unsigned long stack_start,unsigned long stack_size,int __user *parent_tidptr,int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
long nr;
/*
* Determine whether and which event to report to ptracer. When
* called from kernel_thread or CLONE_UNTRACED is explicitly
* requested, no event is reported; otherwise, report if the event
* for the type of forking is enabled.
*/
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;
if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
//创建进程描述符以及子进程所需要的其他所有数据结构
p = copy_process(clone_flags, stack_start, stack_size,child_tidptr, NULL, trace);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/
if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid;
trace_sched_process_fork(current, p);
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}
//新进程加入运行队列,并启动调度程序重新调度,使得新进程获得运行机会
wake_up_new_task(p);
/* forking complete and child started to run, tell ptracer */
if (unlikely(trace))
ptrace_event_pid(trace, pid);
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
}
put_pid(pid);
} else {
//出错处理
nr = PTR_ERR(p);
}
return nr;
}
使用系统调用fork系统调用创建一个新进程,而且都是通过调用do_fork来实现进程创建,Linux通过复制父进程PCB即task_struct来创建一个新进程,要给新进程分配一个新的内核堆栈;.要修改复制过来的进程数据,比如pid、进程链表等等执行copy_process和copy_thread然后通过.p->thread.sp = (unsigned long) childregs;来调度子进程时的内核堆栈,而p->thread.ip = (unsigned long) ret_from_fork; 为调度到子进程时的第一条指令地址

浙公网安备 33010602011771号