重头戏来了!copy_process() 函数完成父进程描述符中相关资源到子进程中的复制工作,并对关键资源进行了修改,以标识新生成的子进程!

由于此函数非常之长,在此就不列出完整源代码,只对关键部分进行分析!

`</p>

static task_t *copy_process(unsigned long clone_flags,unsigned long stack_start,
                 struct pt_regs *regs,
                 unsigned long stack_size,
                 int __user *parent_tidptr,
                 int __user *child_tidptr,
                 int pid)
{
       首先对参数 clone_flags 的合理性进行验证!

       p = dup_task_struct(current);
       复制一份当前进程的描述符的拷贝!

       ...... 

       p->pid = pid;

       p->tgid = p->pid;
       新建进程的线程组号为新建进程的进程号!

这里有一个问题需要注意:
对于普通进程,线程组号 tgid 和进程号 pid 是相同的;对于拥有多个线程的进程来说,进程第一个创建的线程的线程组号 tgid 和进程号 pid 相同,而随后创建的其他线程的 tgid 为第一个创建线程的进程号 pid。当我们调用sys_getpid()取得进程的进程号时,此函数返回的是当前进程的线程组号 tgid!

       ......

       retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
       为新建进程设置进程描述符中成员变量 thread !该成员变量保存了重要的信息,进程使用的内核栈等信息

       ......

       sched_fork(p);
       初始化新建进程成员变量 sched_info,设置运行状态为 TASK_RUNNING ;进程抢占标志设为 1,禁止内核抢占,将父进程所剩时间片给子进程分配一半!

       attach_pid(p, PIDTYPE_PID, p->pid);
       attach_pid(p, PIDTYPE_TGID, p->tgid);
       将新创建进程描述符添加到系统进程号的哈希表中,使得可通过进程号快速找到相应进程描述符地址!

       ......

fork_out:
       if (retval)
            return ERR_PTR(retval);

       return p;
       最后返回新创进程的进程描述符!
}` 

就这样,copy_process 函数结束了它的使命 ……

`</p>

static struct task_struct *dup_task_struct(struct task_struct *orig)
{
       struct task_struct *tsk;
       struct thread_info *ti;

       prepare_to_copy(orig);
       在当前进程使用FPU的情况下,保存当前进程的 FPU 上下文内容到进程的成员变量 thread_info 中

       tsk = alloc_task_struct();
       申请 sizeof(struct task_struct) 大小内存空间,用作新创建进程的进程描述符!

       if (!tsk)
           return NULL;

       ti = alloc_thread_info(tsk);
       为新创建的进程申请 thread_info 结构所需的内存空间! 

       if (!ti) {
           free_task_struct(tsk);
           return NULL;
       }

       *ti = *orig->thread_info;
       将当前进程的 thread_info 结构中内容赋值给新创建的进程的 thread_info 结构中(进程1)!

       *tsk = *orig;
       同样道理赋值 task_struct 结构

       tsk->thread_info = ti;
       修改新创建进程进程描述符的 thread_info 字段,使其指向新创建的 thread_info 结构!

       ti->task = tsk;
       同样道理修改 thread_info 结构的 task 字段使其指向 tss_struct 结构!

       /* One for us, one for whoever does the "release_task()" (usually parent) */
       atomic_set(&tsk->usage,2);
       设置新创建子进程的进程描述符的引用计数usage为2。其中一个代表本进程对该进程描述符的引用计数;另一个是父进程的引用计数

       return tsk;
}`

`</p>

# define alloc_task_struct()    kmem_cache_alloc(task_struct_cachep,GFP_KERNEL)`

从 task_struct_cachep 高速缓存描述符中分配一个对象!

在 fork_init() 函数中 已经创建好了task_struct 结构专用的缓冲队列!

`</p>

#define alloc_thread_info(tsk)                  \
({                          \
        struct thread_info *ret;            \
                                \
        ret = kmalloc(THREAD_SIZE, GFP_KERNEL);     \
        if (ret)                    \
            memset(ret, 0, THREAD_SIZE);        \
        ret;                        \
})`

从普通缓存中申请 thread_info 大小的内存空间,并清 0

copy_thread 函数对于理解内核栈很重要,所以接下来分析!