三态进程模型根据进程在执行过程中的不同情况,至少要定义三种状态:运行(running)状态:进程占据所在的状态处理器正在运行。该进程已获得CPU,其程序正在执行。在单处理器系统中,只有一个进程在执行;在多处理器系统中,多个进程正在执行。就绪(ready)状态:进程具备运行条件,等待系统分配处理器运行的状态。当进程已经分配了除CPU以外的所有必要资源后,只要重新获得CPU就可以立即执行。进程此时的状态称为就绪状态。一个系统中可能有多个处于就绪状态的进程,它们通常排成一个队列,称为就绪队列。等待(wait)状态:也称为阻塞状态或睡眠状态,是指进程不具备运行条件,正在等待一定时间完成的状态。也称为等待或休眠状态,进程正在等待某个事件发生(如请求I/O并等待I/O完成等)而暂时停止运行。这时,即使给进程分配了处理器,它也不能运行,所以称为进程阻塞。进程状态转换的具体原因如下:运行状态→等待状态:等待使用资源;比如等待外设传输;等待人工干预。等待状态→就绪状态:资源已满足;比如外设传输结束;人工干预完成。运行状态→就绪状态:运行时间片已到;有更高优先级的进程。就绪态—→运行态:当CPU空闲时选择一个就绪进程。五状态过程模型五状态模型在三状态模型的基础上增加了一个新状态(new)和一个终止状态(exit)。新建状态:对应进程创建时的状态,还没有进入就绪队列。创建流程需要两个步骤:1.为新流程分配所需的资源并建立必要的管理信息。2.将进程设置为就绪状态,等待调度执行。终止状态:指进程完成任务到达正常终点,或因不可逾越的错误异常终止,或被操作系统和具有终止权的进程终止时所处的状态。处于终止状态的进程将不再被调度执行,下一步将被系统撤销,最终从系统中消失。终止一个进程需要两个步骤:1.等待操作系统或相关进程进行善后处理(如提取信息)。2、然后被占用的资源被系统回收并删除。进程状态转换的具体原因如下:NULL→新状态:执行程序,创建子进程。新状态→就绪状态:当操作系统已经完成创建进程的必要操作,且当前系统性能和虚拟内存容量允许时。运行态→终止态:当进程到达自然终点,或出现不可逾越的错误,或被操作系统终止,或被其他具有终止权的进程终止。运行状态→就绪状态:运行时间片已到;有更高优先级的进程。运行状态→等待状态:等待使用资源;比如等待外设传输;等待人工干预。Readystate→Terminatedstate:状态转换图中未显示,但有些操作系统允许父进程终止子进程。等待状态→终止状态:状态转换图中没有显示,但有些操作系统允许父进程终止子进程。终止状态→NULL:完成善后操作。linux的进程状态,无论是进程还是线程,其实都是由Linux内核中的结构体task_struct{}来表示的。它实际上是一个任务,是Linux中的基本调度单元。Linux进程状态有:TASK_RUNNING:就绪状态或运行状态,进程准备运行,但不一定占用CPU,对应进程状态的R。TASK_INTERRUPTIBLE:睡眠状态,但进程处于浅睡眠状态,可以响应信号。一般是进程主动休眠进入的状态,对应进程状态S。TASK_UNINTERRUPTIBLE:睡眠状态,深度睡眠,不响应信号,典型场景是进程获得信号量阻塞,对应进程状态D.TASK_ZOMBIE:Zombie状态,进程已经退出或者结束,但是父进程还不知道,没有回收时的状态,结束前的一个状态,对应进程状态Z.EXIT_DEAD:结束状态,processhasended,即进程结束退出时的状态,对应进程状态X。TASK_STOPED:已停止,调试状态,对应进程状态T。Orphanprocess一个父进程退出,其中一个或多个它的子进程还在运行,那么那些子进程就会变成孤儿进程。孤儿进程会被init进程收养(进程号为1,也可能是容器中的init),init进程会为他们完成状态收集工作。孤立进程是没有父进程的进程。孤儿进程的重任就落在了init进程身上。init进程就像一个民政局,负责处理孤儿进程的善后事宜。每当出现孤儿进程时,内核将孤儿进程的父进程设置为init,init进程会循环wait()其退出的子进程。这样,当一个孤儿进程灾难性地结束它的生命周期时,init进程将代表党和政府处理它的所有善后工作。所以孤儿进程没有害处。僵尸进程在类UNIX系统中,僵尸进程是指执行完成(通过exit系统调用,或运行时出现致命错误或终止信号),但其进程控制块仍然存在于进程表中操作系统。处于“终止状态”的进程。从上面的概念我们知道,进程表中仍然存在僵尸进程。进程会占用系统资源,僵尸进程过多会导致资源泄露,最重要的资源就是PID。我们来看看linux系统中的PID。这个最大值可以在/proc/sys/kernel/pid_max参数中看到。[root@k8s-dev]#cat/proc/sys/kernel/pid_max32768Linux内核在初始化的时候,会根据CPU的数量来设置pid_max。如果CPU数量小于等于32,那么pid_max会被设置为32768;如果CPU数量大于32,则pid_max=1024*(CPU数量)。所以如果超过了这个最大值,系统就不能创建新的进程。比如你想用SSH登录本机,是不行的。清理僵尸进程:了解了僵尸进程的危害,我们来看看如何清理僵尸进程:收割僵尸进程的方法是通过kill命令手动向其父进程发送SIGCHLD信号。如果父进程仍然拒绝收割僵尸进程,则终止父进程,使init进程采用僵尸进程。init进程周期性地执行wait系统调用来收割它所收养的所有僵尸进程。为了避免僵尸进程,在实际应用中一般采用的方法是:将父进程中SIGCHLD信号的处理函数设置为SIG_IGN(忽略该信号);fork两次,杀死一级子进程,使二级子进程成为孤儿进程,被init“收养”清理。docker容器中进程容器中的PID在Docker中,进程管理的基础是Linux内核中的PID命名空间技术。在不同的PID命名空间中,进程ID是独立的;也就是说,两个不同命名空间下的进程可以有相同的PID。Linux内核为所有的PID命名空间维护了一个树形结构:最顶层是系统初始化时创建的根命名空间(rootnamespace),新创建的PID命名空间称为子命名空间(subnamespace)。原来的PID命名空间是新创建的PID命名空间的父命名空间。这样,系统中的PID命名空间就形成了层次结构。父节点可以看到子节点中的进程,并可以通过信号等方式影响子节点中的进程。反过来,子节点在父节点的namespace中是看不到任何东西的,也不可能通过kill或ptrace影响父节点或其他namespace中的进程。在Docker中,每个Container都是DockerDaemon的子进程,每个Container进程默认有不同的PID命名空间。Docker通过命名空间技术,实现了容器之间的进程隔离。此外,DockerDaemon还会利用PID命名空间的树形结构,实现容器内的进程交互、监控和回收。注意:Docker还使用其他命名空间(UTS、IPC、USER)来实现各种系统资源的隔离。由于这些内容与进程管理无关,本文不予赘述。容器退出创建Docker容器时,会创建一个新的PID命名空间。容器启动进程在此命名空间中的PID为1。当PID1进程结束时,Docker会销毁对应的PID命名空间,并向容器中的所有其他子进程发送SIGKILL。在执行dockerstop命令时,docker会先向容器的PID1进程发送一个SIGTERM信号,让容器中的程序退出。如果容器在收到SIGTERM后没有结束,那么Doc??kerDaemon会等待一段时间(默认是10s),然后向容器发送SIGKILL信号,将容器kill转为退出状态。这种方式为Docker应用程序提供了一种优雅的退出(gracefulstop)机制,让应用程序在收到停止命令时能够清理并释放正在使用的资源。dockerkill可以向容器中的PID1进程发送任何信号。默认是发送SIGKILL信号强制应用程序退出。容器中僵尸进程产生的原因容器化之后,因为是单容器单进程,所以没有传统意义上的init进程。应用进程直接占用pid为1的进程号,这就导致了下面两个问题。一个常见的用途是dockerrunmy-containerscript。向dockerrun进程发送SIGTERM信号会杀死dockerrun进程,但容器仍在后台运行。2.当一个进程退出时,它成为僵尸进程,直到它的父进程调用wait()(或其变体)系统调用。它将在进程表中标记为已失效。一般来说,父进程应该立即调用wait(),以防僵尸进程耗时过长。如果父进程先于子进程退出,则子进程成为孤儿进程,其父进程成为PID1。因此,init进程负责这些进程,并在适当的时候调用wait()方法。通常情况下,大多数应用程序进程不会处理意外附加到自己进程的随机子进程,因此在容器中,会有很多僵尸进程。解决这个问题的办法是运行一个pid为1的进程,支持信号转发和回收孤儿僵尸进程。为此,有人开发了tini项目。有兴趣的可以去github上搜索一下。现在tini已经内置在docker中。您可以使用tini在docker运行时添加选项–init。我猜底层是将docker-init复制到容器的/dev/init路径下,然后启动entrypointcmd。你可以在运行时测试以上步骤,你会发现根本不会留下任何僵尸进程。这里不多说,如果想默认使用tini,可以将tini构建到镜像中(比如k8s目前不支持dockerrun的--init,所以需要将tini放入镜像中),参考到jenkins官方镜像dockerfile和tini的github地址文件https://github.com/krallin/tinik8spod中的僵尸进程k8s可以将多个容器排列成一个pod,共享同一个LinuxNamespace。这个技术的本质是利用k8s提供一个pauseimage,也就是说先启动一个pause容器,相当于实例化一个Namespace,然后其他容器加入这个Namespace,实现Namespace共享。让我们介绍暂停。Pause是k8s在1.16版本引入的技术。要使用pause,我们只需要在pod创建的yaml中指定shareProcessNamespace参数为true即可,如下:busyboxsecurityContext:capabilities:add:-SYS_PTRACEstdin:truetty:trueattach到pod,ps查看进程列表:/#kubectlattachPOD-cCONTAINER/#psaxPIDUSERTIMECOMMAND1root0:00/pause8root0:00nginx:masterprocessnginx-gdaemonx0:0:01ngin10workerprocess15root0:00sh21root0:00psax我们可以看到pod中的1号进程变成了/pause,其他容器的entrypoint进程变成了1号进程的子进程。这时候逐渐接近事情的本质:/pause进程如何处理将孤儿进程的父进程设置为1号进程,避免僵尸进程?暂停图像源码如下:pause.c#include
