这篇文章粗略地描述了Linux上一个进程的生死存亡。这只是一级近似。稍后的帖子将进一步细化这一点,并提供更精确的描述,添加有关PID名称空间、晦涩的syscall和鲜为人知的标志的详细信息。
每次创建进程时,实际上都会有另一个进程使用派生或克隆syscall进行自我拆分。派生之后,进程通常会运行execve syscall来将当前执行的二进制文件与另一个交换。例如,当您从bash Shell运行ls时,首先bash使用fork将其自身拆分成两个bash进程,然后子bash Shell使用exec将其自身更改为ls。当ls完成执行时,子进程就会死亡,只剩下原始的bash进程。
实际上,程序几乎从不直接调用syscall-它们使用libc包装器,或者使用类似system的libc函数,后者在幕后使用fork和execve(或几个execve变体之一)。
进程是如何死亡的?它们几乎总是调用EXIT或EXIT_GROUP系统调用。如果程序员没有显式调用exit,而是从main返回,那么无论如何都会调用exit,因为编译器用libc main包装了main,该libc main会为您调用exit。如果程序是在没有libc的情况下编译的,并且程序员没有显式调用exit,那么从main返回将导致段错误或另一个关键信号,因为Return将尝试从堆栈中弹出非法的返回地址。这就引出了倒数第二种进程死亡的方式,那就是通过SIGTERM或SIGKILL这样的信号。最后,进程死亡的最后一种方式是拔掉计算机上的插头。
每个进程都有一个唯一的PID--或者至少它是唯一的,直到内核在进程死后的某个时候回收该PID。在回收进程的PID之前,父进程应该对子进程调用WAIT、WAITPID或WAITID。(在前面的例子中,bash需要在ls上调用wait。)。如果父进程不伺候子进程,那么子进程就会变成僵尸,这意味着它会徘徊在内核的进程表中,浪费资源。如果父进程本身死了,那么进程会被重新分配一个具有PID1的父进程-即唯一的init进程的PID,它会自动调用Wait并释放该进程。
到底什么是线?在Linux上,线程或多或少是独立的进程,它们碰巧共享相同的内存和其他一些资源。它们是通过使用适当的标志调用clone来创建的。在内核术语中,每个线程都有自己唯一的PID,同一进程中的所有线程共享相同的tgid(线程组ID),这等于第一线程的PID。因此,从内核的角度来看,PID标识线程,TGID标识进程,而PID等于单线程进程的TGID。令人困惑的是,用户模式中的术语不同,如下表所示:
这是Linux上进程的概述。最近我写了很多关于Linux进程的文章。如果你喜欢这篇文章,你应该读一读我的其他文章--特别是我最受欢迎的关于在Linux上跟踪运行进程的困难的文章。我还写了关于Linux的其他主题,比如开发人员在使用BPF时遇到的最常见的bug。
这个博客不支持评论,但我想听听你的意见。我更喜欢更个人化的一对一联系,而不是你通常在评论区找到的联系。请随时在LinkedIn上与我联系,并将您对此帖子的想法发送给我。