首页 > 代码库 > linux的进程状态与管理

linux的进程状态与管理

进程状态:

 

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 <unistd.h>

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 <stdio.h>
#include <math.h>
#include <unistd.h>
#include <stdlib.h>

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 <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

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 <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 <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

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;
}
=====================================

 

进程管理:

fg、bg、jobs、&、ctrl+z命令

一、 &
加在一个命令的最后,可以把这个命令放到后台执行 ,如sh start.sh &


二、ctrl + z
可以将一个正在前台执行的命令放到后台,并且处于暂停状态,不可执行。


三、jobs:查看当前有多少在后台运行

jobs -l选项可显示所有任务的PID,jobs的状态可以是running, stopped, Terminated,但是如果任务被终止了(kill)

四、fg:将后台中的命令调至前台运行
如果后台中有多个命令,可以用 fg %jobnumber将选中的命令调出,%jobnumber是通过jobs命令查到的后台正在执行的命令的序号,而不是pid


五、bg:将一个在后台暂停的命令,变成继续执行 (后台执行
如果后台中有多个命令,可以用bg %jobnumber将选中的命令调出,%jobnumber是通过jobs命令查到的后台正在执行的命令的序号(不是pid)
将任务转移到后台运行:先ctrl + z;再bg,这样进程就被移到后台运行,终端还能继续接受命令。


当前任务
如果后台的任务号有2个,[1],[2];如果当第一个后台任务顺利执行完毕,第二个后台任务还在执行中时,当前任务便会自动变成后台任务号码“[2]” 的后台任务。所以可以得出一点,即当前任务是会变动的。当用户输入“fg”、“bg”和“stop”等命令时,如果不加任何引号,则所变动的均是当前任务

进程的终止
后台进程的终止:
方法一:
通过jobs命令查看job号(假设为num),然后执行kill %num


方法二:
通过ps命令查看job的进程号(PID,假设为pid),然后执行kill pid


前台进程的终止:
ctrl+c

kill的其他作用
kill除了可以终止进程,还能给进程发送其它信号,使用kill -l 可以察看kill支持的信号。
SIGTERM是不带参数时kill发送的信号,意思是要进程终止运行,但执行与否还得看进程是否支持。如果进程还没有终止,可以使用kill -SIGKILL pid,这是由内核来终止进程,进程不能监听这个信号。

后台进程的挂起:
在redhat中,不存在stop命令,可通过执行命令kill -stop PID,将进程挂起;

当要重新执行当前被挂起的任务时,通过bg %num 即可将挂起的job的状态由stopped改为running,仍在后台执行;当需要改为在前台执行时,执行命令fg %num即可。

前台进程的挂起:
ctrl+Z