fork系统调用的执行过程
1.Linux提供了三个系统调用用于创建进程,分别是fork,vfork,clone:fork系统调用: 内核采用写时复制技术对传统的fork函数进行了下面的优化.即子进程创建后,父子进程以只读的方式共享父进程的资源(并不包括父进程的页表项).当子进程需要修改进程地址空间的某一页时,才为子进程复制该页.采用这样的技术可以避免对父进程中某些数据不必要的复制.vfork系统调用: 使用vfork函数创
1.Linux提供了三个系统调用用于创建进程,分别是fork,vfork,clone:
fork系统调用: 内核采用写时复制技术对传统的fork函数进行了下面的优化.即子进程创建后,父子进程以只读的方式共享父进程的资源(并不包括父进程的页表项).当子进程需要修改进程地址空间的某一页时,才为子进程复制该页.采用这样的技术可以避免对父进程中某些数据不必要的复制.
vfork系统调用: 使用vfork函数创建的子进程会完全共享父进程的地址空间,包括父进程的页表项.父子进程任意一方对任何数据的修改使得另一方都可以感知到.为了使得双方不受这种影响,vfork函数创建了子进程后,父进程便被阻塞,直至子进程调用了exec()或exit().由于现在fork函数引入了写时复制技术,在不考虑复制父进程页表项的情况下,vfork函数几乎不会被使用.
clone系统调用: clone函数创建子进程时灵活度比较大,因为它可以通过传递不同的参数来选择性的复制父进程的资源.
系统调用fork,vfork和clone在内核中对应的服务例程分别为sys_fork(),sys_vfork()和sys_clone():
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, 0, NULL, NULL);
}
asmlinkage int sys_clone(struct pt_regs regs)
{
unsigned long clone_flags;
unsigned long newsp;
int __user *parent_tidptr, *child_tidptr;
clone_flags = regs.ebx;
newsp = regs.ecx;
parent_tidptr = (int __user *)regs.edx;
child_tidptr = (int __user *)regs.edi;
if (!newsp)
newsp = regs.esp;
return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, 0, NULL, NULL);
}
可以看到do_fork()均被上述三个服务例程调用.三个系统调用的执行过程如下图所示:
2.do_fork()函数的参数解释:
do_fork()函数的原型:
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
clone_flags: 该标志位的4个字节分为两部分.最低的一个字节为子进程结束时发送给父进程的信号代码,通常为SIGCHLD(用于wait系统调用).剩余的三个字节则是各种clone标志的组合.通过clone标志可以有选择的对父进程的资源进行复制.
statck_start: 子进程用户态堆栈的地址
stack_size:未被使用,通常被赋值为0
parent_tidptr:父进程在用户态下pid的地址,该参数在CLONE_PARENT_SETTID标志被设定时有意义
child_tidptr:子进程在用户态下pid的地址,该参数在CLONE_CHILD_SETTID标志被设定时有意义
3.do_fork()函数 部分代码:
long 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;
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); //得到一个进程描述符, 内核栈, thread_info与父进程一样的子进程描述符
if (!IS_ERR(p)) {
struct completion vfork; //定义了一个完成变量, 以便clone_flags标志设置了CLONE_VFORK时, 父进程阻塞, 直到子进程调用exec或exit时
trace_sched_process_fork(current, p);
nr = task_pid_vnr(p); //返回子进程的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); //将子进程加入红黑树, 并判断子进程是否可以抢占父进程, 此时子进程已经处于运行状态
if (unlikely(trace))
ptrace_event(trace, nr);
//如果设置了CLONE_VFORK标志, 则父进程使用完成变量阻塞, 直到子进程调用exec或exit
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event(PTRACE_EVENT_VFORK_DONE, nr);
}
} else {
nr = PTR_ERR(p);
}
return nr; //返回子进程的进程描述符
}
4.copy_process()函数 部分代码:
static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *child_tidptr,
struct pid *pid,
int trace)
{
int retval;
struct task_struct *p;
//检查clone_flags标志的设置是否正确
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_PARENT) &&
current->signal->flags & SIGNAL_UNKILLABLE)
return ERR_PTR(-EINVAL);
if (clone_flags & CLONE_SIGHAND) {
if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
(task_active_pid_ns(current) !=
current->nsproxy->pid_ns_for_children))
return ERR_PTR(-EINVAL);
}
p = dup_task_struct(current); //新创建一个进程描符, 线程描述符, 内核栈
//此时父子进程的内容完全一样, 接下来就是重新设置一些子进程的值了
//判断系统中的进程数是不是超过了限制
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
copy_flags(clone_flags, p); //将clone_flags复制给子进程
sched_fork(clone_flags, p); //该函数调用task_fork_fair函数, 设置子进程的vruntime值
//根据clone_flags值, 拷贝或共享父子进程之间的一些数据结构
retval = perf_event_init_task(p);
retval = audit_alloc(p);
retval = copy_semundo(clone_flags, p);
retval = copy_files(clone_flags, p); //文件描述符
retval = copy_fs(clone_flags, p); //进程当前工作目录信息
retval = copy_sighand(clone_flags, p); //信号处理表
retval = copy_signal(clone_flags, p); //信号值的处理
retval = copy_mm(clone_flags, p); //内存描述符
retval = copy_namespaces(clone_flags, p); //命名空间
retval = copy_io(clone_flags, p);
retval = copy_thread(clone_flags, stack_start, stack_size, p); //dup_task_struct函数中会在创建子进程的进程描符之前, 把父进程此时的寄存器环境保存在父进程的内核栈中, 子进程的内核栈再复制父进程的内核栈值, 此函数就是用这些值更新子进程寄存器的值. 另外, 子进程对应的thread_info结构中的esp字段会被初始化为子进程内核栈的基址
if (pid != &init_struct_pid) {
retval = -ENOMEM;
pid = alloc_pid(p->nsproxy->pid_ns_for_children); //分配子进程的进程描述符 号
}
return pid;
}
5.dup_task_struct()函数 部分代码:
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
struct task_struct *tsk;
struct thread_info *ti;
unsigned long *stackend;
int node = tsk_fork_get_node(orig);
int err;
tsk = alloc_task_struct_node(node); //申请进程描述符
if (!tsk)
return NULL;
ti = alloc_thread_info_node(tsk, node); // 申请线程描述符
if (!ti)
goto free_tsk;
err = arch_dup_task_struct(tsk, orig); //将父进程的进程描述符,线程描述符,内核栈的值赋值给子进程
//此时, 父子进程的内容完全一样
if (err)
goto free_ti;
tsk->stack = ti; //子进程的进程描述符的stack指针设为子进程的线程描述符
return tsk;
free_ti:
free_thread_info(ti);
free_tsk:
free_task_struct(tsk);
return NULL;
}
本文引述自:
http://edsionte.com/techblog/archives/2103
http://edsionte.com/techblog/archives/2131
http://edsionte.com/techblog/archives/2141
更多推荐
所有评论(0)