3,进程的创建.
可以使用fork()和vfork()来创建一个进程.
fork():在传统的系统中,fork()后出来的新进程是父进程的完全拷贝,只是PID不同而已.由于我们创建一个进程后,并不是希望想要一个父进程的完全拷贝,而是要执行另外的程序,所以之后会接着调用exec族函数,让子进程执行新的程序,这样的做法是很低效的,所以现代的主流linux操作系统引入了Copy-on-Write技术.Copy-on-Write技术的意思是指:在复制一个对象时,并不是真的在内存中把原来对象的数据复制到另外一个地址空间,而是在新对象的内存映射表中指向同原对象相同的位置.在对这个对象进行读操作的时候,内存数据没有变动,直接读就可以.而在写数据的时候,才真正将数据复制到新的地址,修改新对象的内存映射表到这个位置,然后进行写操作.由于fork()之后紧接着会执行exec函数族,所以采用Copy-on-Write技术的fork()函数跟传统的fork()相比,会节省很大的存储空间,提高了效率.
vfork():使用vfork()创建子进程后,父进程会被阻塞,直到子进程调用了exec或者_exit函数退出才会继续进行.虽然效果上和采用了Copy-on-write技术的fork()差不多,但还是存在一定的问题,比如说当子进程执行exec()失败的时候,父进程就会一直阻塞在那了,不知道如何处理,所以综合来看,为了避免不必要的麻烦,平时还是使用fork()函数来创建新进程会好一些.
无论fork()或者vfork()都是通过调用clone()实现的.通过传递给clone()不同的参数而执行不同的工作.
比如说:
普通的fork()的实现是:
clone(SIGCHLD,0)
而vfork()的实现是:
clone(CLONE_VFORK | CLONE_VM | SIGCHLD,0)
可见vfork比普通的fork()多了两个参数标识,即CLONE_VFORK和CLONE_VM,意义如下所示:
CLONE_VFORK:调用vfork(),所以父进程准备睡眠等待子进程将其唤醒.
CLONE_VM:父子进程共享地址空间.
4,线程相关.
从内核的角度来看,它并没有线程这个概念.linux把所有的线程都当作进程来实现(从实现的角度来看).这和别的操作系统,比如windows或者Sun Solaris等os的实现不一样.
线程的创建和普通进程的创建类似,只不过在调用clone()的时候需要传递一些参数标识来标明需要共享的资源:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,0);
内核线程:
内核经常需要在后台执行一些操作.这种任务可以通过内核线程来完成.内核线程和普通进程间的区别在于内核线程没有独立的地址空间(实际上它的mm指针被设置为NULL).
内核线程只能由其他内核线程创建,方法如下:
int kernel_thread(int (*fn)(void *),void *arg,unsigned lone flags);
在上面的函数返回时,父进程退出,并且返回一个指向子进程task_struct的指针.子进程开始运行fn指向的函数,arg是传递的参数,通常为CLONE_KERNEL,等同于CLONE_FS + CLONE_FILES + CLONE_SIGHAND.
5,进程结束与删除进程描述符
当进程终结时,内核必须释放它所占有的资源并把这一不幸告诉给其父进程.进程终结的大部分任务最后由do_exit实现.当调用了do_exit之后,尽管线程已经僵死不能在运行了,但是系统还保留了它的进程描述符.这样做是因为可以让父进程在子进程终结了以后还可以获得其相关信息.当父进程获得信息或者通知内核它并不关注这些信息后,通过release_task()函数释放进程的进程描述符.
如果父进程在子进程之前退出,则会在子进程的当前线程组里找一个线程作为父线程,如果不行,则会交由init做其父线程.这样做的目的是因为防止成为孤儿的进程在退出时永远处于僵死状态,白白的耗费内存,即成为所谓的僵尸进程.