1.进程创建

       Linux/Unix中进程创建分为两部分:1)fork(),拷贝当前进程,创建一个子进程。(父进程与子进程的区别只有PID、PPID和某些资源和统计量);2)exec(),读取可执行的文件并将其载入地址空间开始运行。

       fork采用了写时拷贝(copy-on-write),即在创建子进程时,并不立即将父进程的数据直接拷贝给子进程,而是父子进程以只读的方式共享一份数据(这里的数据并不是进程描述符)。只有在需要写入数据时,数据才会被复制。如果fork后,立即调用了exec,则无需复制。这样可以减少无意义的拷贝操作。

       Linux中通过clone实现fork操作,而clone则是调用的do_fork()完成进程的创建。如下是do_fork()实现的伪代码,从代码中可以很直观的看出:thread_info位于栈的底端(顶端),这样可以很方便的通过当前栈的指针获取thread_info的指针,从而确定当前task_struct的指针。

kernel/fork.c
    do_fork() :
    1.copy_process();                                /*根据父进程,创建子进程的进程描述符*/
            1.do_task_struct();                      /*创建内核栈与thread_info、task_struct*/
                    1.tsk = alloc_task_struct_node();      
                    2.ti = alloc_thread_info_node(); /*会创建一个栈,thread_info位于栈低*/
                    3.arch_dup_task_struct();
                    4.tsk->stack = ti;
            2.xxx_init_task();                       /*初始化task_struct*/
            3.copy_xxx();                            /*根据传递的参数标志,更新task_struct的flag成员*/
    2.get_task_pid();                                /*获取pid*/
    3.wake_up_new_task();                            /*唤醒子进程*/
    4.return pid;                                    /*返回pid*/

       vfork和fork的功能相同,除了不拷贝父进程的页表项。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec()。子进程不能向地址空间写入。

       在linux中,线程其实是一种特殊的进程,它被视为一个与其它进程共享某些资源的进程。线程拥有自己的task_struct,因此在内核中它看起来是一个普通的进程。这样创建一个线程与创建一个进程没有太大区别,通过传递给clone(),不同的参数实现。

       因此可以通过传递给clone()不同的参数,来实现fork(),vfork()、以及线程的创建。

clone(CLONE_SIGHAND, 0);
clone(CLONE_VFORK | CLONE_VM | CLONE_SIGHAND, 0);
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

2.进程终结

       进程的终结,一般是通过调用exit结束。而exit则是通过调用do_exit()完成的。要注意的是,进程结束时,需要为其子进程寻找一个父进程(线程组其他进程或init进程),而进程本身会进入TASK_ZOMBIE状态。该进程会等待其父进程调用wait4(),删除其进程描述符以及一些独享的资源。

kernel/exit.c
do_exit()
    1.exit_signals();		/*tsk->flags |= PF_EXITING*/
    2.exit_xxx();			/*释放各种资源*/
    3.exit_notify();		/*将子进程的父进程设置为线程组其他线程或init进程,并将进程状态改为TASK_ZOMBIE*/