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

罗晓波 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

本文通过一个小实验,fork的这个系统调用,来说明linux内核创建一个新进程的过程。

一、实验:

同样,依旧是在实验楼上面做的实验。

先是启动一下我们之前提到的menuos这个精简的linux内核,这个内核里加入了fork系统调用所对应的命令。

接下来,用gdb来进行调试fork系统调用的相关的内核代码,我们来看一下发生了神马。在以下的代码处放了breakpoint。


接下来,我们continue之后,就可以一步步next的来进行了。

在do_fork这里停下,do_fork函数负责处理clone、fork、vfork的系统调用。

do_fork是利用copy_progcess函数来创建进程描述符以及子进程执行所需要的内核数据结构。在dofork之后,继续n;

进入copy_process函数。下面是跟踪copy_process函数的一个截图:


这里我们可以看到,正在调用alloc_thread_info,这个函数其实是在dup_task_struct()这个函数里执行的。这个宏执行之后,获取一块空闲的内存区域,用来存放新进程的threadinfo以及内核栈,并且将这块内存区字段的地址存在局部变量ti中。finish这个函数之后,继续c,来到copy_thread函数:


这里的pt_regs结构体是x86体系结构下内核定义的一个结构体,这个结构体中按照顺序依次存放了系统调用的时候,硬件保存的现场的相关值,以及SAVE_ALL所对应的值,当然还有中断号也会被保存在这个结构体中。具体的,下文再继续分析。

二:分析

不管是clone、fork、还是vfork这三个系统调用都是对应的一个系统服务例程,那就是do_fork()函数。

我们贴上do_fork的内核代码:

long do_fork(unsigned long clone_flags,
1624	      unsigned long stack_start,
1625	      unsigned long stack_size,
1626	      int __user *parent_tidptr,
1627	      int __user *child_tidptr)
1628{
1629	struct task_struct *p;
1630	int trace = 0;
1631	long nr;
1632
1633	/*
1634	 * Determine whether and which event to report to ptracer.  When
1635	 * called from kernel_thread or CLONE_UNTRACED is explicitly
1636	 * requested, no event is reported; otherwise, report if the event
1637	 * for the type of forking is enabled.
1638	 */
1639	if (!(clone_flags & CLONE_UNTRACED)) {
1640		if (clone_flags & CLONE_VFORK)
1641			trace = PTRACE_EVENT_VFORK;
1642		else if ((clone_flags & CSIGNAL) != SIGCHLD)
1643			trace = PTRACE_EVENT_CLONE;
1644		else
1645			trace = PTRACE_EVENT_FORK;
1646
1647		if (likely(!ptrace_event_enabled(current, trace)))
1648			trace = 0;
1649	}
1650
1651	p = copy_process(clone_flags, stack_start, stack_size,
1652			 child_tidptr, NULL, trace);
1653	/*
1654	 * Do this prior waking up the new thread - the thread pointer
1655	 * might get invalid after that point, if the thread exits quickly.
1656	 */
1657	if (!IS_ERR(p)) {
1658		struct completion vfork;
1659		struct pid *pid;
1660
1661		trace_sched_process_fork(current, p);
1662
1663		pid = get_task_pid(p, PIDTYPE_PID);
1664		nr = pid_vnr(pid);
1665
1666		if (clone_flags & CLONE_PARENT_SETTID)
1667			put_user(nr, parent_tidptr);
1668
1669		if (clone_flags & CLONE_VFORK) {
1670			p->vfork_done = &vfork;
1671			init_completion(&vfork);
1672			get_task_struct(p);
1673		}
1674
1675		wake_up_new_task(p);
1676
1677		/* forking complete and child started to run, tell ptracer */
1678		if (unlikely(trace))
1679			ptrace_event_pid(trace, pid);
1680
1681		if (clone_flags & CLONE_VFORK) {
1682			if (!wait_for_vfork_done(p, &vfork))
1683				ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
1684		}
1685
1686		put_pid(pid);
1687	} else {
1688		nr = PTR_ERR(p);
1689	}
1690	return nr;
1691}
1629行出现的一个task_struct指针,可以看到是通过copy_process()函数来完成赋值的,这个copy_process()函数,下面我还会介绍。这个task_struct *p,这个指针p也就是提到的新进程的进程描述符,对应的结构是这样子的 task_struct

接下来再贴一下copy_process()的代码:因为这个copy_process()代码太长,只贴一下部分代码吧:

1239	retval = -ENOMEM;
1240	p = dup_task_struct(current);
1241	if (!p)
这里的dup_task_struct(current)就是为子进程获取进程描述符:

static struct task_struct *dup_task_struct(struct task_struct *orig)
306{
307	struct task_struct *tsk;
308	struct thread_info *ti;
309	int node = tsk_fork_get_node(orig);
310	int err;
311
312	tsk = alloc_task_struct_node(node); //这个宏是为新进程获取进程描述符,并将描述符地址保存在tsk中。
313	if (!tsk)
314		return NULL;
315
316	ti = alloc_thread_info_node(tsk, node); //分配一块内存区域,放threadinfo以及内核栈,这个大小为8k
317	if (!ti)
318		goto free_tsk;
319
320	err = arch_dup_task_struct(tsk, orig);
321	if (err)
322		goto free_ti;
323
324	tsk->stack = ti; //这时候就可以把新进程的进程描述符的stack指针指向刚刚分配的内存区域了,也就是ti
325#ifdef CONFIG_SECCOMP
326	/*
327	 * We must handle setting up seccomp filters once we're under
328	 * the sighand lock in case orig has changed between now and
329	 * then. Until then, filter must be NULL to avoid messing up
330	 * the usage counts on the error path calling free_task.
331	 */
332	tsk->seccomp.filter = NULL;
333#endif
334
335	setup_thread_stack(tsk, orig);
336	clear_user_return_notifier(tsk);
337	clear_tsk_need_resched(tsk);
338	set_task_stack_end_magic(tsk);
339
340#ifdef CONFIG_CC_STACKPROTECTOR
341	tsk->stack_canary = get_random_int();
342#endif
343
344	/*
345	 * One for us, one for whoever does the "release_task()" (usually
346	 * parent)
347	 */
348	atomic_set(&tsk->usage, 2); //描述进程描述符正在被使用,而且处于Active的状态,所以置计数器为2
349#ifdef CONFIG_BLK_DEV_IO_TRACE
350	tsk->btrace_seq = 0;
351#endif
352	tsk->splice_pipe = NULL;
353	tsk->task_frag.page = NULL;
354
355	account_kernel_stack(ti, 1);
356
357	return tsk;
358
359free_ti:
360	free_thread_info(ti);
361free_tsk:
362	free_task_struct(tsk);
363	return NULL;
364}
接着来看copy_process(),
1394	if (retval)
1395		goto bad_fork_cleanup_namespaces;
1396	retval = copy_thread(clone_flags, stack_start, stack_size, p);
1397	if (retval)
来看copy_thread()这个函数:
<pre name="code" class="plain">132int copy_thread(unsigned long clone_flags, unsigned long sp,
133	unsigned long arg, struct task_struct *p)
134{
135	struct pt_regs *childregs = task_pt_regs(p);
136	struct task_struct *tsk;
137	int err;
138
139	p->thread.sp = (unsigned long) childregs;
140	p->thread.sp0 = (unsigned long) (childregs+1);
141	memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
142
143	if (unlikely(p->flags & PF_KTHREAD)) {
144		/* kernel thread */
145		memset(childregs, 0, sizeof(struct pt_regs));
146		p->thread.ip = (unsigned long) ret_from_kernel_thread;
147		task_user_gs(p) = __KERNEL_STACK_CANARY;
148		childregs->ds = __USER_DS;
149		childregs->es = __USER_DS;
150		childregs->fs = __KERNEL_PERCPU;
151		childregs->bx = sp;	/* function */
152		childregs->bp = arg;
153		childregs->orig_ax = -1;
154		childregs->cs = __KERNEL_CS | get_kernel_rpl();
155		childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
156		p->thread.io_bitmap_ptr = NULL;
157		return 0;
158	}
159	*childregs = *current_pt_regs();
160	childregs->ax = 0;
161	if (sp)
162		childregs->sp = sp;
163
164	p->thread.ip = (unsigned long) ret_from_fork;
165	task_user_gs(p) = get_user_gs(current_pt_regs());
166
167	p->thread.io_bitmap_ptr = NULL;
168	tsk = current;
169	err = -ENOMEM;
170
171	if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
172		p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
173						IO_BITMAP_BYTES, GFP_KERNEL);
174		if (!p->thread.io_bitmap_ptr) {
175			p->thread.io_bitmap_max = 0;
176			return -ENOMEM;
177		}
178		set_tsk_thread_flag(p, TIF_IO_BITMAP);
179	}
180
181	err = 0;
182
183	/*
184	 * Set a new TLS for the child thread?
185	 */
186	if (clone_flags & CLONE_SETTLS)
187		err = do_set_thread_area(p, -1,
188			(struct user_desc __user *)childregs->si, 0);
189
190	if (err && p->thread.io_bitmap_ptr) {
191		kfree(p->thread.io_bitmap_ptr);
192		p->thread.io_bitmap_max = 0;
193	}
194	return err;
195}

 在发生fork系统调用的时候,cpu保存的寄存器的值,也就是ptreg这个结构体中的值用来初始化这个子进程的内核栈,我们还可以观察到: 
childregs->ax = 0;
childregs->sp = sp;
p->thread.ip = (unsigned long) ret_from_fork;
上面三条执行语句分别描述了新进程的返回值也就是在eax中的值为0,esp为内核栈的esp,eip为 ret_from_fork这个宏所对应的地址。

290ENTRY(ret_from_fork)
291	CFI_STARTPROC
292	pushl_cfi %eax
293	call schedule_tail
294	GET_THREAD_INFO(%ebp)
295	popl_cfi %eax
296	pushl_cfi $0x0202		# Reset kernel eflags
297	popfl_cfi
298	jmp syscall_exit
299	CFI_ENDPROC
300END(ret_from_fork)
可以看到,ret_from_fork中调用schedule_tail函数也就是调度函数,完成进程的切换,jmp syscall_exit就是退出系统调用,开始返回用户态。
三、总结

通过上面的分析,我们可以看到,在dofork之后,就紧接着创建新进程的进程描述符以及子进程执行所需要的其他内核数据结构,也就是copy_process函数里所做的东西。由于将父进程的当前堆栈都拷贝到了子进程的堆栈中,在上述分析中,当子进程的进程描述符中的已经将eip、esp以及各寄存器和内核栈、数据段的值都准备好了,进程调度之后,便可以开始执行子进程了。








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值