Linux - 进程与线程
关于进程和线程。
进程和线程的区别
在Linux,进程和线程共用了同一个struct:task_struct
。它有两个重要属性:
struct mm_struct *mm
:记录进程的(虚拟)内存结构体;struct fs_struct *fs
:记录进程所有打开的文件信息(文件描述符表)的结构体;
进程和线程的区别在于:
- 线程共用内存,所以使用
pthread()
创建线程时,两个线程指向同一个mm
;进程不公用,所以使用fork()
创建进程时,mm
会被复制一份。当然之前介绍redis创建子进程的时候说过,进程复制是copy on write,需要写的部分才会去复制,所以并不是完全无脑复制一整个内存区域,不会很慢。否则内存占用直接就double了!; - 线程共用文件描述符表,同理进程也是复制一份。
共享所以节约资源,换句话说,可用的资源少,所以会竞争,所以多线程要加锁啊!
进程和线程都对应到实际代码,形象不?
Ref:
- https://zhuanlan.zhihu.com/p/105086274
孤儿进程 vs. 僵尸进程
从task_struct
的结构可以看出,进程持有一堆资源。当进程退出,资源(fd、memory)会被释放(close会被调用),但仍然会保留基本信息(pid、status)等,需要父进程使用wait/waitpid手动释放掉这些东西。
核心问题:
- 父进程先于子进程结束了呢?
- 父进程怎么知道子进程结束了呢?
以上两个问题都会导致父进程没法使用wait/waitpid处理残留的子进程信息。
孤儿进程:父进程先挂了,子进程就是孤儿了。没关系,init进程会收留它,并接替原来父进程的工作,使用wait/waitpid在子进程结束后释放子进程的资源。
僵尸进程:子进程结束了,父进程没有那些处理步骤,残余信息永远释放不掉了。
- 子进程一定会先经过僵尸进程状态(进程结束后,没有被wait前),然后如果有一个好爸爸,就会彻底消失,如果父进程不wait,就成了永远的僵尸进程;
- 如果父进程先结束,子进程最终一定不会变成僵尸进程,因为init这个新的爸爸一定会处理好子进程;
所以:
- 父进程先于子进程结束了,没问题,不用管;
- 父进程怎么知道子进程结束了呢?子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号就行了。
危害
僵尸进程的危害很明显:占着pid,时间长了,没法创建新进程了。这不是危言耸听,Linux经常好几年不关机,一天创建几个僵尸进程出来,慢慢就挂了。
如何规避
规避方法就是父进程一定要调用waitpid。调用时机有两个:
- 父进程监听子进程退出信号。这就涉及到父进程怎么知道子进程结束了的问题。使用
signal
,类似回调函数。父进程接收SIGCHLD
信号,调用waitpid; - double fork。父进程fork子进程,子进程fork孙子进程,然后子进程啥也不干,结束。父进程在子进程结束的代码后面串行调用waitpid(此时子进程肯定结束了)。至于孙子进程,才是真正干原来的子进程该干的活的进程。它现在已经是孤儿进程,自会有新的爸爸init收留它,跟父进程已经无关了,不用操心了。
产生僵尸进程怎么办
生产环境中碰到不断产生的子进程怎么办?一定是因为它的父进程写挫了,找到他的父进程,kill。
Ref:
- https://www.cnblogs.com/Anker/p/3271773.html
本文由作者按照 CC BY 4.0 进行授权