您的位置:首页 > 运维架构 > Linux

Linux内核 do_fork 函数源代码浅析

2016-11-16 00:00 375 查看
在 Linux 内核中,供用户创建进程的系统调用fork()函数的响应函数是 sys_fork()、sys_clone()、sys_vfork()。这三个函数都是通过调用内核函数 do_fork() 来实现的。根据调用时所使用的 clone_flags 参数不同,do_fork() 函数完成的工作也各异。

这部分内容简单,我不打算就此而展开分析。下面我们重点来讲解以下 do_fork() 函数的工作原理。

我们知道 do_fork() 函数生成一个新的进程,大致分为三个步骤。

1)建立进程控制结构并赋初值,使其成为进程映像。这个过程完成以下内容。
在内存中分配一个 task_struct 数据结构,以代表即将产生的新进程。把父进程 PCB 的内容复制到新进程的 PCB 中。为新进程分配一个唯一的进程标识号 PID 和 user_struct 结构。然后检查用户具有执行一个新进程所必须具有的资源。重新设置 task_struct 结构中那些与父进程值不同的数据成员。设置进程管理信息,根据所提供的 clone_flags 参数值,决定是否对父进程 task_struct 中的指针 fs 、files 指针等所选择的部分进行拷贝,如果 clone_flags 参数指明的是共享而不是拷贝,则将其计数器 count 的值加 1 ,否则就拷贝新进程所需要的相关信息内容 PCB 。这个地方是区分 sys_fork() 还是 sys_clone() 。

2) 必须为新进程的执行设置跟踪进程执行情况的相关内核数据结构。包括 任务数组、自由时间列表 tarray_freelist 以及 pidhash[] 数组。这部分完成如下内容:

(1) 把新进程加入到进程链表中
(2) 把新进程加入到 pidhash 散列表中,并增加任务计数值。
(3) 通过拷贝父进程的上、下文来初始化硬件的上下文(TSS段、LDT以及 GDT)。

3) 启动调度程序,使子进程获得运行的机会。
这部分完成以下动作:

(1) 设置新的就绪队列状态 TASK_RUNING , 并将新进程挂到就绪队列中,并重新启动调度程序使其运行。
(2) 向父进程返回子进程的 PID,设置子进程从 do_fork() 返回 0 值。

(3) 下面就具体的 do_fork() 函数程序代码进行分析(该代码位于 kernel/fork.c 文件中)

/*
*  Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
long nr;

/*
* Do some preliminary argument and permissions checking before we
* actually start allocating stuff
*/
if (clone_flags & CLONE_NEWUSER) {
if (clone_flags & CLONE_THREAD)
return -EINVAL;
/* hopefully this check will go away when userns support is
* complete
*/
if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) ||
!capable(CAP_SETGID))
return -EPERM;
}

/*
* When called from kernel_thread, don't do user tracing stuff.
*/
if (likely(user_mode(regs)))
trace = tracehook_prepare_clone(clone_flags);

p = copy_process(clone_flags, stack_start, regs, 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;

trace_sched_process_fork(current, p);

nr = task_pid_vnr(p);

if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);

if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}

audit_finish_fork(p);
tracehook_report_clone(regs, clone_flags, nr, p);

/*
* We set PF_STARTING at creation in case tracing wants to
* use this to distinguish a fully live task from one that
* hasn't gotten to tracehook_report_clone() yet.  Now we
* clear it and set the child going.
*/
p->flags &= ~PF_STARTING;

wake_up_new_task(p);

tracehook_report_clone_complete(trace, regs,
clone_flags, nr, p);

if (clone_flags & CLONE_VFORK) {
freezer_do_not_count();
wait_for_completion(&vfork);
freezer_count();
tracehook_report_vfork_done(p, nr);
}
} else {
nr = PTR_ERR(p);
}
return nr;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: