1)进程的状态的概述:
1.1)Running(R),运行或将要运行
1.2)Interruptible(S),被阻断而等待一个事件,可能会被一个信号激活
1.3)Uninterruptible(D),被阻断而等待一个事件,不会被信号激活
1.4)Stopped(T),由于任务的控制或者外部的追踪而被终止,比如:strace
1.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 CODES
Here are the different values that the s, stat and state output specifiers (header "STAT" or "S") will display to
describe 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 leader
l 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
void main() {
if (!vfork()) sleep(100);
}
gcc test.c -o test
./test
终端2)
ps aux|grep test
root 19884 0.0 0.0 3640 360 pts/0 D+ 16:38 0:00 ./test
root 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 top
root 980 9.4 0.0 1716 608 pts/0 S 00:31 0:12 | \_ strace top
root 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 \_ -bash
root 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 aux
root 12057 0.0 0.5 5124 1476 pts/0 T 05:26 0:00 -bash
用SIGCONT来唤醒
kill -SIGCONT 12057
终端1)
输入回车后,12057的进程依然会进入停止状态,也就是阻塞,只有会放到前台后,它才能完成输入.
fg
read x
hello
3.2.3)进程自已终止自己,标准输出引起进程停止
终端有一个tostop(终端输出终止)设置,在大多数系统里默认是关闭.
当是关闭的状态时,后台进程可以随时在终端写内容,如果是开启状态时,后台进程向标准输出写数据时就会被终止.
开启tostop
stty tostop
向标准输出写数据,被停止了
echo hello world &
[1] 12125
[1]+ Stopped echo hello world
jobs
[1]+ Stopped echo hello world
关闭tostop
stty -tostop
jobs
[1]+ Stopped echo hello world
向标准输出写数据恢复正常了
fg
echo hello world
hello world
4)分析进程的可中断睡眠态与运行态
编写一个小程序测试睡眠态与运行态之后的转换 :
=====================================================
#include
#include
#include
#include
void
run_status(void)
{
double pi = M_PI;
double pisqrt;
long i;
for (i=0; i<100000000; ++i){
pisqrt = sqrt(pi);
}
}
int
main (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) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1572440, ...}) = 0
old_mmap(NULL, 1279916, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x49e000
old_mmap(0x5d1000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x132000) = 0x5d1000
old_mmap(0x5d4000, 10156, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x5d4000
close(3) = 0
set_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}) = 0
munmap(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 pisqrt
root 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
#include
#include
#include
#include
int
main (){
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 | \_ ./defunct
root 7281 0.0 0.0 0 0 pts/2 Z 03:05 0:00 | \_ [defunct
20秒后查看终端2:
child pid=7281
parent 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
#include
#include
#include
#include
int
main (){
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小于0
N状态:nice大于0
L状态:有锁住的页面
这部分在ps的源代码(output.c)有描述:
===================================
static int
pr_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;
}