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*/