Linux中线程与进程的区别
转载自:https://zhuanlan.zhihu.com/p/93553600
fork与vfork
Linux有两种不同的函数来创建进程:fork函数,vfork函数。两个函数都是从父进程拷贝出一个新进程,但是也有区别。下面是fork和vfork的定义。定义于<kernel/fork.c>中。本段代码源于kernel 4.4版本。
//fork系统调用 SYSCALL_DEFINE0(fork) { return do_fork(SIGCHLD, 0, 0, NULL, NULL); } SYSCALL_DEFINE0(vfork) { return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 0, NULL, NULL, 0); } long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { return _do_fork(clone_flags, stack_start, stack_size, parent_tidptr, child_tidptr, 0); } long _do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr, unsigned long tls) { // .... 省略大量代码 p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace, tls); // .... 省略大量代码 }
我们可以看到fork和vfork最终都是通过调用do_fork来实现的,只不过传入的参数值不一致。第一个参数传入了clone_flags,并且这个clone_flags最终被copy_process所使用。copy_process这个方法是真正的执行拷贝的地方,有兴趣的同学可以研究研究。那么这些flag有什么含义呢?下面是linux中的flag定义(定义于<include/linux/sched.h>)。


参考这里传入的Flags的区别,我们可以进一步给出结论:
- fork会拷贝父进程的页表,而vfork永远不会复制,直接让子进程共用父进程的页表(页表实现从页号到物理块号的地址映射)。这是vfork优于当前的fork的地方
- vfork创建完子进程后,子进程作为父进程的
线程在地址空间中运行,同时阻塞父进程,直到子进程退出或者执行exec()。并且子进程不能像地址空间写入。这一点在fork没有支持写时拷贝前是很有意义的,但是由于fork后来引入了写时拷贝页并且明确了子进程先执行,vfork的好处就只限于不拷贝页表项了。
2.3 线程
创建线程和创建普通的进程类似,只不过需要在调用do_fork的时候需要传递不同的flag来指明需要共享的资源。使用pthread_create方法来进行创建,最终会调用到do_fork方法。传入的flag为
CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND
共享地址空间、文件系统信息、打开的文件、信号处理函数
从上面的flag我们可以看出,创建出来的线程从内核层面来看其实也是一种特殊的进程,它跟父进程共享了打开的文件和文件系统信息,共享了地址空间和信号处理函数,这也跟我们传统印象中的线程和进程的关系是符合的。Linux的实现和windows之类的操作实现差异很大,假设一个进程有四个线程,在其它提供了专门线程支持的系统中,系统会在进程中增加一个包含指向该进程所有线程的指针的进程描述符,每个线程再去描述自己独占的资源,但是linux就仅仅创建四个进程并分配四个普通的进程描述符,指定他们共享某些资源,这样更为简单。
线程又分为内核线程和用户线程,内核线程是独立运行于内核空间的标准进程,他们没有独立地址空间,从来不会切换到用户空间去。用户线程就是咱们所认知的普通线程了。

浙公网安备 33010602011771号