读核日记fork上篇知识介绍
读核日记fork上篇知识介绍
作者:handi
星期四, 十月 28, 2004
读核日记fork上篇
中间会参考到一些资料和文章,我也会在日记头一一列出
1. lion's unix 源代码分析
2. kernel 2.6.8 /kernel/fork.c
3. kernel 2.4.18 /kernel/fork.c
4. linux情景分析(上、下)
基本上没有找到以fork命名的函数,而且还符合
match case and match whold word only
于是sys_fork --> do_fork,就像socket一样只有sys_socket
至于为什么系统调用的时候是fork()和socket(),我想可能是函数map的
原因吧
先说大体流程,之后再解说代码
first,检查一些状态,获取一些必要的信息
--> 进入copy_process实体,复制进程(中间有一个很重要的一点,就是
为什么子进程返回值是0)
--> 设置一些信息
--> 让进程开始run
比较于unix的实现,我只能说linux真复杂和罗嗦,lion's unix
中的实现很简单清晰,如果只是要了解原理的,那份fork就足够了,而且
很老实的,没有sys_fork, v_fork, do_fork之类的嵌套,就一个fork
当然不清楚目前unix的版本中这是怎样实现的,所以过些天会找些minix
或bsd中实现的代码来参考,然后补充到日记中来
前面罗嗦了这么多,还是来分析一下源代码吧.
/kernel/fork.c
kernel_thread(),sys_fork(),sys_clone(),sys_vfork()
这几个函数都是调用do_fork()函数,主要参数区别都是集中在
clone_flags上面,在/arch/i386/kernel/process.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; /* PCB结构 */
int trace = 0;
long pid;
/* CLONE_PTRACE 这个宏表示什么意思[set if we want to let tracing continue on the child too]
* 翻译过来的意思是如果想跟踪子进程的信息,就设置这一位
* 是否跟踪由do_fork的参数clone_flags确定的
* 也就是说是否跟踪子进程的信息是由我们在调用的时候确定 */
/* current这个是指当前的父进程吗?
* unlikely原型:
* #include <linux/compiler.h> */
if (unlikely(current->ptrace)) {/* unlikely(x),对x两次取反,具体实现取决于gcc的版本,gcc 2.9.6是界点,其实就是取x的值 */
trace = fork_traceflag (clone_flags);/* fork_traceflag inline在文件fork.c中,2.4.18版本没有独立出去 */
if (trace)
clone_flags |= CLONE_PTRACE;
}
/* 检查clone_flags,创建一个新的进程,拷贝由clone_flags指定的信息,但新进程不运行 */
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/
pid = IS_ERR(p) ? PTR_ERR(p) : p->pid;
/* 进程复制成功 */
if (!IS_ERR(p)) {
struct completion vfork;
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)) {
/*
* Do the wakeup last. On SMP we treat fork() and
* CLONE_VM separately, because fork() has already
* created cache footprint on this CPU (due to
* copying the pagetables), hence migration would
* probably be costy. Threads on the other hand
* have less traction to the current CPU, and if
* there's an imbalance then the scheduler can
* migrate this fresh thread now, before it
* accumulates a larger cache footprint:
*/
/* 唤醒新fork的进程 */
if (clone_flags & CLONE_VM)
wake_up_forked_thread(p);
else
wake_up_forked_process(p);
} else {
int cpu = get_cpu();
p->state = TASK_STOPPED;
if (cpu_is_offline(task_cpu(p)))
set_task_cpu(p, cpu);
put_cpu();
}
++total_forks;
if (unlikely (trace)) {
current->ptrace_message = pid;
ptrace_notify ((trace << 8) | SIGTRAP);
}
if (clone_flags & CLONE_VFORK) {
wait_for_completion(&vfork);
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
} else
/*
* Let the child process run first, to avoid most of the
* COW overhead when the child exec()s afterwards.
*/
/* 让fork的子进程先运行 */
set_need_resched();
}
return pid;
}