[root
emr-header-1~]#psauxUSERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMAND
root10.10.?SsMar:04/usr/lib/systemd/systemd--switched-root--system--de
root20.00.?SMar:05[kthreadd]
root30.00.?SMar:01[ksoftirqd/0]
root50.00.?SMar:00[kworker/0:0H]
root70.00.?SMar:41[migration/0]
root80.00.?SMar:00[rcu_bh]
root90.00.?SMar:19[rcu_sched]
root.00.?SMar:50[watchdog/0]
root.00.?SMar:39[watchdog/1]
root.00.?SMar:51[migration/1]
root.00.?SMar:44[ksoftirqd/1]
root.00.?SMar:00[kworker/1:0H]
我们可以看到排在第一位的就是前面说到的1号进程systemd。其中的STAT那一列就是进程状态,这里的状态都是和S有关的,但是正常还有R、D、Z等状态。各个状态的含义简单描述如下:S:InterruptibleSleep,中文可以叫做可中断的睡眠状态,表示进程因为等待某个资源或者事件就绪而被系统暂时挂起。当资源或者事件Ready的时候,进程轮转到R状态。
R:也就是Running,有时候也可以指代Runnable,表示进程正在运行或者等待运行。
Z:Zombie,也就是僵尸进程。我们知道每个进程都是会占用一定的资源的,比如pid等,如果进程结束,资源没有被回收就会变成僵尸进程。
D:DiskSleep,也就是UninterruptibleSleep,不可中断的睡眠状态,一般是进程在等待IO等资源,并且不可中断。D状态相信很多人在实践中第一次接触就是ps卡住。D状态一般在IO等资源就绪之后就会轮转到R状态,如果进程处于D状态比较久,这个时候往往是IO出现问题,解决办法大部分情况是重启机器。
I:Idle,也就是空闲状态,不可中断的睡眠的内核线程。和D状态进程的主要区别是可能实际上不会造成负载升高。
关于僵尸进程,这里继续讨论一下。对于正常的使用情况,子进程的创建一般需要父进程通过系统调用wait()或者waitpid()来等待子进程结束,从而回收子进程的资源。除了这种方式外,还可以通过异步的方式来进行回收,这种方式的基础是子进程结束之后会向父进程发送SIGCHLD信号,基于此父进程注册一个SIGCHLD信号的处理函数来进行子进程的资源回收就可以了。记住这两种方式,后面还会涉及到。僵尸进程的最大危害是对资源的一种永久性占用,比如进程号,系统会有一个最大的进程数n的限制,也就意味一旦1到n进程号都被占用,系统将不能创建任何进程和线程(进程和线程对于OS而言,使用同一种数据结构来表示,task_struct)。这个时候对于用户的一个直观感受就是shell无法执行任何命令,这个原因是shell执行命令的本质是fork。[root
emr-header-1~]#ulimit-acorefilesize(blocks,-c)0
datasegsize(kbytes,-d)unlimited
schedulingpriority(-e)0
filesize(blocks,-f)unlimited
pendingsignals(-i)
maxlockedmemory(kbytes,-l)64
maxmemorysize(kbytes,-m)unlimited
openfiles(-n)
pipesize(bytes,-p)8
POSIXmessagequeues(bytes,-q)
real-timepriority(-r)0
stacksize(kbytes,-s)
cputime(seconds,-t)unlimited
maxuserprocesses(-u)
virtualmemory(kbytes,-v)unlimited
filelocks(-x)unlimited
孤儿进程前面说到如果子进程先于父进程退出,并且父进程没有对子进程残留的资源进行回收的话将会产生僵尸进程。这里引申另外一种情况,父进程先于子进程退出的话,那么子进程的资源谁来回收呢?父进程先于子进程退出,这个时候我们一般将还在运行的子进程称为孤儿进程,但是实际上孤儿进程并没有一个明确的定义,他的状态还是处于上面讨论的几种进程状态中。那么孤儿进程的资源谁来回收呢?类Unix系统针对这种情况会将这些孤儿进程的父进程置为1号进程也就是systemd进程,然后由systemd来对孤儿进程的资源进行回收。单进程模型的本质看完上面两节大家应该知道了虚拟机或者一个完整的OS是如何避免僵尸进程的。但是,在容器中,1号进程一般是entrypoint进程,针对上面这种将孤儿进程的父进程置为1号进程进而避免僵尸进程处理方式,容器是处理不了的。进而就会导致容器中在孤儿进程这种异常场景下僵尸进程无法彻底处理的窘境。所以说,容器的单进程模型的本质其实是容器中的1号进程并不具有管理多进程、多线程等复杂场景下的能力。如果一定在容器中处理这些复杂情况的,那么需要开发者对entrypoint进程赋予这种能力。这无疑是加重了开发者的心智负担,这是任何一项大众技术或者平台框架都不愿看到的尴尬之地。如何避免除了第二节讨论的开发者自己赋予entrypoint进程管理多进程的能力,这里我更推荐借助Kubernetes来做这件事情。我想现在应该也没有人对容器进行人工管理了,大部分人应该都转向了容器编排和调度工具Kubernetes阵营了(对于那些还在使用Swarm的一小波人,我劝你们早日弃暗投明:))。Kubernetes中可以将多个容器编排到一个Pod里面,共享同一个LinuxNameSpace。这项技术的本质是使用Kubernetes提供一个pause镜像,展开来说就是先用pause镜像实例化出NameSpace,然后其他容器加入这个NameSpace从而实现NameSpace共享。突然意识到这块需要有容器和NameSpace的技术背景,限于篇幅,希望你可以自行搜索这种技术背景。或者我下一篇文章讨论一下容器技术的本质。言归正传,我们来介绍一下pause。pause是Kubernetes在1.16版本引入的技术,要使用pause,我们只需要在Pod创建的yaml中指定shareProcessNamespace参数为true,如下:apiVersion:v1
kind:Pod
metadata:
name:nginx
spec:
shareProcessNamespace:true
containers:
-name:nginx
image:nginx
-name:shell
image:busybox
securityContext:
capabilities:
add:
-SYS_PTRACE
stdin:true
tty:true
创建Pod:kubectlapply-fshare-process-namespace.yaml
attach到Pod中,ps查看进程列表:/#psax
PIDUSERTIMECOMMAND
1root0:00/pause
8root0:00nginx:masterprocessnginx-gdaemonoff;
:00nginx:workerprocess
15root0:00sh
21root0:00psax
我们可以看到Pod中的1号进程变成了/pause,其他容器的entrypoint进程都变成了1号进程的子进程。这个时候开始逐渐逼近事情的本质了:/pause进程是如何处理将孤儿进程的父进程置为1号进程进而避免僵尸进程的呢?我们看一下源码,gitrepo:pause.c:#defineSTRINGIFY(x)#x
#defineVERSION_STRING(x)STRINGIFY(x)
#ifndefVERSION
#defineVERSIONHEAD
#endif
staticvoidsigdown(intsigno){
psignal(signo,"Shuttingdown,gotsignal");
exit(0);
}
staticvoidsigreap(intsigno){
while(waitpid(-1,NULL,WNOHANG)0)
;
}
intmain(intargc,char**argv){
inti;
for(i=1;iargc;++i){
if(!strcasecmp(argv[i],"-v")){
printf("pause.c%s\n",VERSION_STRING(VERSION));
return0;
}
}
if(getpid()!=1)
/*Notanerrorbecausepauseseesuseoutsideofinfracontainers.*/
fprintf(stderr,"Warning:pauseshouldbethefirstprocess\n");
if(sigaction(SIGINT,(structsigaction){.sa_handler=sigdown},NULL)0)
return1;
if(sigaction(SIGTERM,(structsigaction){.sa_handler=sigdown},NULL)0)
return2;
if(sigaction(SIGCHLD,(structsigaction){.sa_handler=sigreap,
.sa_flags=SA_NOCLDSTOP},
NULL)0)
return3;
for(;;)
pause();
fprintf(stderr,"Error:infiniteloopterminated\n");
return42;
}
重点