如果设置了同在一个线程组则继承TGID。对于普通进程来说TGID和PID相等,对于线程来说,同一线程组内的所有线程的TGID都相等,这使得这些多线程可以通过调用getpid()获得相同的PID。

再继续

      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;
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
if (retval)
goto bad_fork_cleanup_namespaces;

对task_struct结构的初始化完了就该继续copy其他的资源了,这部分调用的函数较多,基本都是在fork.c中定义的,比如copy_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;
}

task_struct结构中有一个指针flies指向一个file_struct结构,因为是从当前进程复制到子进程,所以oldf = current->files;

然后

      if (clone_flags & CLONE_FILES) {
atomic_inc(&oldf->count);
goto out;
}

如果设置了CLONE_FILES,也就是CLONE_FILES=1,就只是共享,通过调用atomic_inc(这个函数之前说过了)增加共享计数,之前复制整个task_struct结构时,把flies指针也复制给子进程了,所以子进程可以通过指针共享file_sturct结构,不要忘记fork()函数调用传递的clone_flags都为0,既不是简单共享而是全部复制。

接着调用dup_fd函数来进行复制。庆幸的是该函数定义也在fork.c中,不幸的是该函数又是疯狂调用其他函数…

由于代码长不全部列举了,进入dup_fd函数中去:

     newf = alloc_files();

调用了alloc_files(),跟进alloc_files()函数:

      static struct files_struct *alloc_files(void)
{
struct files_struct *newf;
struct fdtable *fdt;
newf = kmem_cache_alloc(files_cachep, GFP_KERNEL);
if (!newf)
goto out;
atomic_set(&newf->count, 1);
spin_lock_init(&newf->file_lock);
newf->next_fd = 0;
fdt = &newf->fdtab;
fdt->max_fds = NR_OPEN_DEFAULT;
fdt->close_on_exec = (fd_set *)&newf->close_on_exec_init;
fdt->open_fds = (fd_set *)&newf->open_fds_init;
fdt->fd = &newf->fd_array[0];
INIT_RCU_HEAD(&fdt->rcu);
fdt->next = NULL;
rcu_assign_pointer(newf->fdt, fdt);
out:
return newf;
}

调用kmem_cache_alloc函数来为子进程分配一个file_struct结构,接着设置这个新的file_struct结构的count成员

      fdt->close_on_exec = (fd_set *)&newf->close_on_exec_init;
fdt->open_fds = (fd_set *)&newf->open_fds_init;
fdt->fd = &newf->fd_array[0];

这3个指针分别指向:位图close_on_exec_init,位图open_fds_init,数组fd_array[],这3个成员大小都是固定的。

出来后就开始进行copy,把oldf的内容copy到新创建的newf中。

中间继续复制其他资源,只有当clone_flags为0时才是真正的复制

Do_fork()之前调用了dup_task_struct函数分配了2个连续页面,低端存放task_strust结构,高端作为系统空间堆栈,由copy_thread来完成。该函数复制父进程的系统空间堆栈,堆栈中有完整路线指明父进程通过系统调用进入内核空间的过程,子进程退出时需要按照完整路线返回。

struct pt_regs * regs结构存放着进入内核空间前各寄存器的内容。如果完全复制父进程的系统空间堆栈则无法区分子进程和父进程,所以要对子进程的相关内容进行调整。

      struct pt_regs * childregs;
struct task_struct *tsk;
int err;
childregs = task_pt_regs(p);
*childregs = *regs;
childregs->eax = 0;
childregs->esp = esp;

首先将eax设0,作为系统调用结束时的返回值

     hildregs->esp = esp;

指出进程在用户态的堆栈地址,该值在fork()中为传递进去的regs.esp

      p->thread.esp = (unsigned long) childregs;
p->thread.esp0 = (unsigned long) (childregs+1);
p->thread.eip = (unsigned long) ret_from_fork;
savesegment(gs,p->thread.gs);

P指向的task_struct结构中有一个thread指针,指向一个thread_struct结构,里面记录着进程切换时的堆栈指针,在子进程中也需要进行调整

     p->thread.esp = (unsigned long) childregs;

指向子进程的pt_regs结构起始地址

     p->thread.esp0 = (unsigned long) (childregs+1);

指向子进程的系统空间栈顶,当进程被调度运行时,内核会将这个值写入esp0字段,标志该进程在ring0运行时的堆栈地址。

     p->thread.eip = (unsigned long) ret_from_fork;

指向当进程下一次被切换运行时的入口处

     savesegment(gs,p->thread.gs);

把当前段寄存器的gs的值保存在thread.gs中

        p->parent_exec_id = p->self_exec_id;
/* ok, now we should be set up.. */
p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
p->pdeath_signal = 0;
p->exit_state = 0;
p->parent_exec_id = p->self_exec_id;

设置子进程的执行域

    p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);

设置子进程退出时要象父进程发送的信号

最后将子进程连入进程队列等待被唤醒,再处理其他的一些收尾工作然后返回一个指向子进程的指针

回到do_fork()函数中

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

调用vfork

      if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else
p->state = TASK_STOPPED;

唤醒子进程并开始运行。

至此,一个进程创建就完成了。


相关内容