通常使用fork创建进程, 也可以用vfork()和clone()。
fork、vfork和clone三个用户态函数均由libc库提供,它们分别会调用Linux内核提供的同名系统调用fork,vfork和clone。
vfork与fork的区别在于创建进程时, vfork完全共享父进程的地址空间,包括页表项。vfork在早期用来替代fork以避免复制地址空间的耗时操作,
但是现在fork已经使用了写时复制技术,在进程创建时,只复制父进程的页表项,用只读的方式共享父进程地址空间,在写入时再复制数据。
fork有了这个优化之后, vfork已较少使用。
fork调用链:
| libc::fork() | --->int$0×80软中断--->| kernel: ENTRY(system_call)--->ENTRY(sys_call_table)--->sys_fork()--->do_fork() |
//----------------------------------------- file: process.c -------------------------------------------- asmlinkage long sys_fork(void) { struct pt_regs *regs = task_pt_regs(current); return do_fork(SIGCHLD, regs->gprs[15], regs, 0, NULL, NULL); }
//----------------------------------------- file: fork.c ------------------------------------------------- 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; long nr; ... p = copy_process(clone_flags, stack_start, regs, stack_size, child_tidptr, NULL); //最核心的工作:通过copy_process()创建子进程的描述符,分配pid等 if (!IS_ERR(p)) { ... if (!(clone_flags & CLONE_STOPPED)) wake_up_new_task(p, clone_flags); //将进程放到运行队列上,从而让调度器进行调度运行 else p->state = TASK_STOPPED; ... } else { nr = PTR_ERR(p); } return nr; }
//----------------------------------------- file: fork.c ------------------------------------------------- static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *child_tidptr, struct pid *pid) { int retval; struct task_struct *p; int cgroup_callbacks_done = 0; //... //检查clone_flags //... p = dup_task_struct(current); //创建内核栈, thread_info, task_struct, 里面的值是完全复制的父进程的值 if (!p) goto fork_out; ... /* * If multiple threads are within copy_process(), then this check * triggers too late. This doesn't hurt, the check is only there * to stop root fork bombs. */ //检查当前进程总数是否超出最大进程数 if (nr_threads >= max_threads) goto bad_fork_cleanup_count; //... //复制、更新子进程描述符的flags成员, copy_flags(clone_flags, p); //... //初始化子进程描述符的各个字段,很多被清零,使得子进程和父进程逐渐区别出来。 不过大部分数据仍然未被修改 //... //调度器设置。调用sched_fork函数执行调度器相关的设置 /* Perform scheduler related setup. Assign this task to a CPU. */ sched_fork(p, clone_flags); //-------------------------start 根据clone_flags的具体取值来为子进程拷贝或共享父进程的某些数据结构--------// if ((retval = security_task_alloc(p))) goto bad_fork_cleanup_policy; if ((retval = audit_alloc(p))) goto bad_fork_cleanup_security; /* copy all the process information */ if ((retval = copy_semundo(clone_flags, p))) goto bad_fork_cleanup_audit; if ((retval = copy_files(clone_flags, p))) //打开的文件信息 goto bad_fork_cleanup_semundo; if ((retval = copy_fs(clone_flags, p))) //所在文件系统信息 goto bad_fork_cleanup_files; if ((retval = copy_sighand(clone_flags, p))) goto bad_fork_cleanup_fs; if ((retval = copy_signal(clone_flags, p))) //信号处理函数 goto bad_fork_cleanup_sighand; if ((retval = copy_mm(clone_flags, p))) //地址空间信息 goto bad_fork_cleanup_signal; if ((retval = copy_keys(clone_flags, p))) goto bad_fork_cleanup_mm; if ((retval = copy_namespaces(clone_flags, p))) //命名空间 goto bad_fork_cleanup_keys; //复制线程。通过copy_threads()函数更新子进程的内核栈和寄存器中的值 retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_namespaces; //-----------------------------------------end -------------------------------------------------// //分配pid if (pid != &init_struct_pid) { retval = -ENOMEM; pid = alloc_pid(task_active_pid_ns(p)); if (!pid) goto bad_fork_cleanup_namespaces; if (clone_flags & CLONE_NEWPID) { retval = pid_ns_prepare_proc(task_active_pid_ns(p)); if (retval < 0) goto bad_fork_free_pid; } } p->pid = pid_nr(pid); p->tgid = p->pid; if (clone_flags & CLONE_THREAD) p->tgid = current->tgid; p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; ... //更改p的其他一些字段 ... if (likely(p->pid)) { add_parent(p); if (unlikely(p->ptrace & PT_PTRACED)) __ptrace_link(p, current->parent); if (thread_group_leader(p)) { if (clone_flags & CLONE_NEWPID) p->nsproxy->pid_ns->child_reaper = p; p->signal->tty = current->signal->tty; set_task_pgrp(p, task_pgrp_nr(current)); set_task_session(p, task_session_nr(current)); attach_pid(p, PIDTYPE_PGID, task_pgrp(current)); attach_pid(p, PIDTYPE_SID, task_session(current)); list_add_tail_rcu(&p->tasks, &init_task.tasks); __get_cpu_var(process_counts)++; } attach_pid(p, PIDTYPE_PID, pid); nr_threads++; } total_forks++; spin_unlock(¤t->sighand->siglock); write_unlock_irq(&tasklist_lock); proc_fork_connector(p); cgroup_post_fork(p); return p; //... //错误处理 //... }
看到这里应该可以知道刚开始提出的第一个问题, 子进程是否能使用父进程的文件描述符?
答案是肯定的,因为task_struct里面成员struct files_struct *files 保存了所有进程打开的文件描述符。
创建子进程的时候,会将files复制一份到子进程files结构体里, 里面的值跟父进程的值是一样的,可以用来操作父进程打开的文件。
如下面代码里的:newf = dup_fd(oldf, &error);
如果调用do_fork的时候传入clone_flags: CLONE_FILES, 那么子进程直接共享父进程files成员,不会复制, 这时候当然更可以用来操作父进程的文件。
线程就是这么做的。
static int copy_files(unsigned long clone_flags, struct task_struct * tsk) { struct files_struct *oldf, *newf; int error = 0; /* * A background process may not have any files ... */ oldf = current->files; if (!oldf) goto out; if (clone_flags & CLONE_FILES) { atomic_inc(&oldf->count); goto out; } /* * Note: we may be using current for both targets (See exec.c) * This works because we cache current->files (old) as oldf. Don't * break this. */ tsk->files = NULL; newf = dup_fd(oldf, &error); if (!newf) goto out; tsk->files = newf; error = 0; out: return error; }
子进程复制父进程的所有文件描述符: