进程复制的三个机制fork
、vfork
和clone
最终都是调用do_fork
来实现子进程的产生的,不同的产生方式通过传递给do_fork的不同参数来控制。其代码执行流程如下:
代码:
if (unlikely(clone_flags & CLONE_STOPPED)) {
static int __read_mostly count = 100;
if (count > 0 && printk_ratelimit()) {
char comm[TASK_COMM_LEN];
count--;
printk(KERN_INFO "fork(): process `%s' used deprecated "
"clone flags 0x%lx\n",
get_task_comm(comm, current),
clone_flags & CLONE_STOPPED);
}
}
if (unlikely(current->ptrace)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
主要是实现对传递给do_fork的flag参数进行处理和检查当前进程是否设置了跟踪标记ptrace
下面便进入了copy_process函数,该函数实现将父进程的寄存器以及所有进程执行环境的相关部分复制给子进程,但并不实际运行该复制后的子进程。copy_process
函数的执行流程如下图所示:
下面对copy_process
代码作详细解读:
int retval;
struct task_struct *p;
int cgroup_callbacks_done = 0;
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
/*
* Thread groups must share signals as well, and detached threads
* can only be started up within the thread group.
*/
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
/*
* Shared signal handlers imply shared VM. By way of the above,
* thread groups also imply shared VM. Blocking this case allows
* for various simplifications in other code.
*/
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
retval = security_task_create(clone_flags);
if (retval)
goto fork_out;
retval = -ENOMEM;
以上代码主要是对一些标志进行合法性检查。
各标志检查完之后便调用dup_task_struct
函数来为新进程创建一个内核栈、thread_info和task_struct,这里完全copy父进程的内容,所有到目前为止,父进程和子进程没有任何区别。从这里可以看出,这个版本的内核在创建进程时需要为自己分配内核栈。
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
struct task_struct *tsk;
struct thread_info *ti;
int err;
/*some operations related to FPU, call __unlazy_fpu() to store FPU MMX SSE/SSE2 registers to the fathers progress' thread_info*/
prepare_to_copy(orig);
tsk = alloc_task_struct(); /*系统从专业高速缓存中分配task_struct空间*/
if (!tsk)
return NULL;
/*ti指向thread_info的首地址,同时也是系统为新进程分配的两个连续的页面的首地址*/
ti = alloc_thread_info(tsk);
if (!ti) {
free_task_struct(tsk);
return NULL;
}
/*copy parent's task_struct to child's task_struct*/
err = arch_dup_task_struct(tsk, orig);
if (err)
goto out;
/*将子进程的task_struct的stack指针指向子进程的thread_info的首地址,即指向内核为进程分配
的两个连续页面的首地址*/
tsk->stack = ti;
err = prop_local_init_single(&tsk->dirties);
if (err)
goto out;
/*将父进程的thread_info的内容完全复制到子进程中,并建立thread_info与task_struct之间的联系*/
setup_thread_stack(tsk, orig);
/* init stack_canary*/
#ifdef CONFIG_CC_STACKPROTECTOR
tsk->stack_canary = get_random_int();
#endif
/* One for us, one for whoever does the "release_task()" (usually parent) */
atomic_set(&tsk->usage,2);
atomic_set(&tsk->fs_excl, 0); /*禁止该进程独占文件系统*/
#ifdef CONFIG_BLK_DEV_IO_TRACE
tsk->btrace_seq = 0;
#endif
tsk->splice_pipe = NULL;
return tsk;
out:
free_thread_info(ti);
free_task_struct(tsk);
return NULL;
}
dup_task_struct
函数的执行流程为:
if (atomic_read(&p->user->processes) >=
p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
p->user != current->nsproxy->user_ns->root_user)
goto bad_fork_free;
}
第一个if语句里面的rlim数组包含在task_struct数组中,对进程占用的资源做出限制,rlim[RLIMIT_NPROC]限制了改进用户可以拥有的总进程的数量,如果当用户所拥有的进程数量超过了规定的最大拥有进程数量,在内核中直接goto_bad_fork_free了。除非当前用户是root用户或分配了特别的权限(CAP_SYS_ADMIN或 CAP_SYS_RESOURCE)。检测root用户比较有趣,因为每个PID命名空间都有各自的root用户。接下来就是初始化task_struct结构中的各个成员了,这里主要涉及到拷贝clone_flags、初始化相关链表等。
接下来就是调用sched_fork函数设置处理器的相关标记,并分配特定标号的处理器给该进程。其中的处理器相关标记包括:分配处理器标号、设置进程的优先级、设置与该进程调度相关的调度类等。
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;
if ((retval = copy_io(clone_flags, p)))
goto bad_fork_cleanup_namespaces;
这段代码主要实现复制/共享进程的各个部分,某一部分是否父子进程共享主要由clone_flags中的相关标志位决定的。
接下来就是一系列的设置各个ID、进程关系
copy_thread
函数设置子进程task_struct中的thread字段alloc_pid(task_active_pid_ns(p))
,首先获取进程p所在的namespace,再使用alloc_pid来在该namespace下分配pid号 如果copy_process
函数返回的子进程的task_struct
指针无误,则执行如下代码
struct completion vfork;
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);
}
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else
__set_task_state(p, TASK_STOPPED);
if (unlikely (trace)) {
current->ptrace_message = nr;
ptrace_notify ((trace << 8) | SIGTRAP);
}
//check weather set CLONE_VFORK, if yes, call wait_for_completion
if (clone_flags & CLONE_VFORK) {
freezer_do_not_count();
wait_for_completion(&vfork);
freezer_count();
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) {
current->ptrace_message = nr;
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
}
}
这段代码完成的过程包括:
task_pid_nr(p)
返回进程p的PIDwake_up_new_task
函数。该函数起第一次唤醒新创建的进程的作用,主要过程为:初始化调度器的一些基本参数,接着把该任务放入到就绪队列中等待调度器的调度,并设置该任务的运行状态为TASK_RUNNING.但要记住:将进程排到调度器数据结构中并不意味着该子进程可以立即执行,而是调度器此时可以选择它运行。