1)进程的状态的概述:
1.1)Running(R),运行或将要运行1.2)Interruptible(S),被阻断而等待一个事件,可能会被一个信号激活1.3)Uninterruptible(D),被阻断而等待一个事件,不会被信号激活1.4)Stopped(T),由于任务的控制或者外部的追踪而被终止,比如:strace1.5)Zombie(Z),僵死,但是它的父进程尚未调用wait函数.1.6)Deal(X),这个永远看不见
在内核源代码中的定义如下:
=====================================================/usr/src/linux/fs/proc/array.c static const char *task_state_array[] = { "R (running)", /* 0 */"S (sleeping)", /* 1 */"D (disk sleep)", /* 2 */"T (stopped)", /* 4 */"T (tracing stop)", /* 8 */"Z (zombie)", /* 16 */"X (dead)" /* 32 */};=====================================================在ps命令的帮助中定义如下:
PROCESS STATE CODESHere are the different values that the s, stat and state output specifiers (header "STAT" or "S") will display todescribe the state of a process.D Uninterruptible sleep (usually IO)R Running or runnable (on run queue)S Interruptible sleep (waiting for an event to complete)T Stopped, either by a job control signal or because it is being traced.W paging (not valid since the 2.6.xx kernel)X dead (should never be seen)Z Defunct ("zombie") process, terminated but not reaped by its parent.
For BSD formats and when the stat keyword is used, additional characters may be displayed:< high-priority (not nice to other users)N low-priority (nice to other users)L has pages locked into memory (for real-time and custom IO)s is a session leaderl is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)+ is in the foreground process group======================================================
关于D和Z一段有趣的解释:有一类垃圾却并非这么容易打扫,那就是我们常见的状态为 D (Uninterruptible sleep) ,以及状态为 Z (Zombie) 的垃圾进程。这些垃圾进程要么是求而不得,像怨妇一般等待资源(D),要么是僵而不死,像冤魂一样等待超度(Z),它们在 CPU run_queue 里滞留不去,把 Load Average 弄的老高老高,没看过我前一篇blog的国际友人还以为这儿民怨沸腾又出了什么大事呢。怎么办?开枪!kill -9!看你们走是不走。但这两种垃圾进程偏偏是刀枪不入的,不管换哪种枪法都杀不掉它们。无奈,只好reboot,像剿灭禽流感那样不分青红皂白地一律扑杀!怨妇 D,往往是由于 I/O 资源得不到满足,而引发等待,在内核源码 fs/proc/array.c 里,其文字定义为“ "D (disk sleep)", /* 2 */ ”(由此可知 D 原是Disk的打头字母),对应着 include/linux/sched.h 里的“ #define TASK_UNINTERRUPTIBLE 2 ”。举个例子,当 NFS 服务端关闭之时,若未事先 umount 相关目录,在 NFS 客户端执行 df 就会挂住整个登录会话,按 Ctrl+C 、Ctrl+Z 都无济于事。断开连接再登录,执行 ps axf 则看到刚才的 df 进程状态位已变成了 D ,kill -9 无法杀灭。正确的处理方式,是马上恢复 NFS 服务端,再度提供服务,刚才挂起的 df 进程发现了其苦苦等待的资源,便完成任务,自动消亡。若 NFS 服务端无法恢复服务,在 reboot 之前也应将 /etc/mtab 里的相关 NFS mount 项删除,以免 reboot 过程例行调用 netfs stop 时再次发生等待资源,导致系统重启过程挂起。
冤魂 Z 之所以杀不死,是因为它已经死了,否则怎么叫 Zombie(僵尸)呢?冤魂不散,自然是生前有结未解之故。在UNIX/Linux中,每个进程都有一个父进程,进程号叫PID(Process ID),相应地,父进程号就叫PPID(Parent PID)。当进程死亡时,它会自动关闭已打开的文件,舍弃已占用的内存、交换空间等等系统资源,然后向其父进程返回一个退出状态值,报告死讯。如果程序有 bug,就会在这最后一步出问题。儿子说我死了,老子却没听见,没有及时收棺入殓,儿子便成了僵尸。在UNIX/Linux中消灭僵尸的手段比较残忍,执行 ps axjf 找出僵尸进程的父进程号(PPID,第一列),先杀其父,然后再由进程天子 init(其PID为1,PPID为0)来一起收拾父子僵尸,超度亡魂,往生极乐。注意,子进程变成僵尸只是碍眼而已,并不碍事,如果僵尸的父进程当前有要务在身,则千万不可贸然杀之。
2)分析不可被中断的睡眠进程: 2.1)重现:终端1)
vi test.c#include <unistd.h>
void main() { if (!vfork()) sleep(100);} gcc test.c -o test
./test
终端2)ps aux|grep testroot 19884 0.0 0.0 3640 360 pts/0 D+ 16:38 0:00 ./testroot 19885 0.0 0.0 3640 360 pts/0 S+ 16:38 0:00 ./test
2.2)分析:系统进入这种不可中断是很少发生的,即使发生也是一个短暂的状态,引起这种状态的发生一般是驱动程序.例如:驱动程序可能正在特殊的设备上等待通过检测的响应,但又要保证自己不在可中断睡眠状态(S)被中断.所以驱动程序会把进程切换到不可中断的睡眠状态,直到硬件已返回到已知状态. 可以通过访问一个慢设备来观察不可中断的睡眠状态,比如CDROM这样的设备例如:dd if=/dev/cdrom f=/dev/null & 进程在一个不可中断的状态是十分危险的,你不能用kill -9杀掉它例如:一个有问题的驱动程序访问一个有问题的设备,设备不给驱动程序响应,驱动程序永远得不到响应,而又永远等待响应.
3)分析被跟踪或被停止的进程状态(T)
3.1)重现被跟踪时的状态:终端1)strace top
终端2)ps auxf|grep toproot 980 9.4 0.0 1716 608 pts/0 S 00:31 0:12 | \_ strace toproot 981 3.7 0.1 10084 7076 pts/0 T 00:31 0:05 | \_ top
在用strace跟踪top执行的时候,top进程为T的状态
3.2)重现被停止的进程状态:停止进程有三种手段:3.2.1)发送SIGTSTP信停止进程.-SIGTSTP的信号相当于CTRL+Z的组合键来终止正在前台运行的进程.
终端1)vi /etc/passwd
终端2)kill -SIGTSTP 12029
查看进程状态:ps auxf
root 10297 0.0 1.0 5124 2696 pts/0 Ss+ Dec16 0:00 \_ -bashroot 12029 0.0 0.8 5348 2200 pts/0 T 05:15 0:00 | \_ vi test.c
终端1)查看放到后台的作业jobs[1]+ Stopped vi test.c用fg将作业切换到前台fg
3.2.2)进程自已终止自己,标准输入引起进程停止一个终端利用常规的后台和前台进程管理进程,一个终端有且只有一个前台进程,只有这个进程可以接受键盘的输入,其它任何开始于终端的进程都被认为是后台进程,但是当后台进程试图从标准输入读取数据时,终端将发送一个SIGTTIN终端它,因为这里只有一个输入设备键盘,只能用于前台进程.这里的前后台进程概念仅限于终端的范围.
SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN 信号. 缺省时这些进程会停止执行.
终端1)尝试在后台运行read命令,因为后台进程不能从终端获取标准输入,所以进程将会收到信号SIGTTIN,使进程进入停止状态.read x &[1] 12057
[1]+ Stopped read x
终端2)jobs[1]+ Stopped read x查看进程,12057这个PID就是read x&,现在看到是-bash,它的状态已经是T了ps auxroot 12057 0.0 0.5 5124 1476 pts/0 T 05:26 0:00 -bash
用SIGCONT来唤醒kill -SIGCONT 12057
终端1)输入回车后,12057的进程依然会进入停止状态,也就是阻塞,只有会放到前台后,它才能完成输入.fgread xhello
3.2.3)进程自已终止自己,标准输出引起进程停止
终端有一个tostop(终端输出终止)设置,在大多数系统里默认是关闭.当是关闭的状态时,后台进程可以随时在终端写内容,如果是开启状态时,后台进程向标准输出写数据时就会被终止.
开启tostopstty tostop向标准输出写数据,被停止了echo hello world &[1] 12125
[1]+ Stopped echo hello world
jobs[1]+ Stopped echo hello world
关闭tostopstty -tostop
jobs[1]+ Stopped echo hello world
向标准输出写数据恢复正常了fgecho hello worldhello world
4)分析进程的可中断睡眠态与运行态
编写一个小程序测试睡眠态与运行态之后的转换 :=====================================================#include <stdio.h>#include <math.h>#include <unistd.h>#include <stdlib.h>voidrun_status(void){ double pi = M_PI;double pisqrt;long i;for (i=0; i<100000000; ++i){ pisqrt = sqrt(pi);}}
intmain (void){ run_status(); sleep(10);run_status();
exit(EXIT_SUCCESS);}======================================================编译链接后:gcc -Wall -o pisqrt a.c -lm
终端1)监控pisqrt进程watch -n 1 "ps aux|grep pisqrt|grep -v ps|awk '{print $2}'|sort -k 8" 终端2)strace ./pisqrt显示如下:read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200X\1"..., 512) = 512fstat64(3, {st_mode=S_IFREG|0755, st_size=1572440, ...}) = 0old_mmap(NULL, 1279916, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x49e000old_mmap(0x5d1000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x132000) = 0x5d1000old_mmap(0x5d4000, 10156, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x5d4000close(3) = 0set_thread_area({entry_number:-1 -> 6, base_addr:0xb75e3a60, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0munmap(0xb75e4000, 75579) = 0此时切换到终端1看pisqrt进程状态,此时为R状态:root 3792 99.9 0.0 1516 268 pts/2 R 02:40 0:01 ./pisqrt root 3801 0.0 0.0 3700 672 pts/1 S 02:40 0:00 grep pisqrtroot 3791 1.0 0.0 1728 564 pts/2 S 02:40 0:00 strace ./pisqr
之后pisqrt进程进入S状态,因为执行了sleep(10)函数,10秒之后pisqrt再次进入R状态.最终退出.
分析:pisqrt占用CPU时间片时状态为R,而在调用sleep函数后为S,系统大多数进程状态都为S,比如APACHE和ORACLE,而处于S状态不一定是调用了sleep函数,因为IO也会让进程处于睡眠态.而我们可以启动多个pisqrt程序,这时在系统中会有多个R状态的进程.也就是说CPU个各数与R进程是没有直接关联的.
5)分析进程的僵死态(Z)
当一个进程退出时,它并不是完全消失,而是等到它的父进程发出wait系统调用才会完全消失,除非父进程发出wait系统调用终止进程,否则该进程将一直处于所谓的僵死状态,等待它的父进程终止它.如果父进程终止了运行而没有撤销子进程,那么这些进程将被进程init收养.init进程定期调用wait来收养这些未被撤消的进程.
先制造一段僵尸程序,如下:=============================#include <stdio.h>#include <sys/types.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>
intmain (){ if(!fork()){ printf("child pid=%d\n", getpid());exit(5);}sleep(20);printf("parent pid=%d\n", getpid());exit(EXIT_SUCCESS);}===========================================编译gcc -Wall defunct.c -o defunct
终端1:watch -n 1 "ps auxf|grep defunct|grep -v ps|grep -v grep|awk '{print $2}'|sort -k 8"
终端2:执行./defunct查看终端1:root 7280 0.0 0.0 1380 240 pts/2 S 03:05 0:00 | \_ ./defunctroot 7281 0.0 0.0 0 0 pts/2 Z 03:05 0:00 | \_ [defunct <defunct>]
20秒后查看终端2:child pid=7281parent pid=7280
关于信号集的描述:/usr/include/bits/signum.h
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */#define SIGCHLD 17 /* Child status has changed (POSIX). */
在上面程序的基础上加入wait函数即可将SIGCHLD信号回收
修改后的程序如下:=================================#include <stdio.h>#include <sys/types.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>
intmain (){ int status,i;if(!fork()){ printf("child pid=%d\n", getpid());exit(5);}wait(&status);i = WEXITSTATUS(status);sleep(20);printf("parent pid=%d,child process exit/status=%d\n", getpid(),i);exit(EXIT_SUCCESS);}==========================================
6)最后的进程X (dead)指死掉的进程
最后4种附加的状态....W状态:不驻留内存<状态:nice小于0N状态:nice大于0L状态:有锁住的页面
这部分在ps的源代码(output.c)有描述:===================================static intpr_stat(void){ int end = 0;outbuf[end++] = pp->state;if (pp->rss == 0 && pp->state != 'Z')outbuf[end++] = 'W';if (pp->nice < 0)outbuf[end++] = '<';if (pp->nice > 0)outbuf[end++] = 'N';if (pp->vm_lock)outbuf[end++] = 'L';outbuf[end] = '\0';return end;}