文章

Linux - 进程与线程

关于进程和线程。

  1. 进程和线程的区别
  2. 孤儿进程 vs. 僵尸进程
    1. 危害
    2. 如何规避
    3. 产生僵尸进程怎么办

进程和线程的区别

在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。调用时机有两个:

  1. 父进程监听子进程退出信号。这就涉及到父进程怎么知道子进程结束了的问题。使用signal,类似回调函数。父进程接收SIGCHLD信号,调用waitpid;
  2. double fork。父进程fork子进程,子进程fork孙子进程,然后子进程啥也不干,结束。父进程在子进程结束的代码后面串行调用waitpid(此时子进程肯定结束了)。至于孙子进程,才是真正干原来的子进程该干的活的进程。它现在已经是孤儿进程,自会有新的爸爸init收留它,跟父进程已经无关了,不用操心了。

产生僵尸进程怎么办

生产环境中碰到不断产生的子进程怎么办?一定是因为它的父进程写挫了,找到他的父进程,kill。

Ref:

  • https://www.cnblogs.com/Anker/p/3271773.html
本文由作者按照 CC BY 4.0 进行授权