暴灵珊 发表于 2025-6-8 12:16:50

Linux应用——进程基础

谁来调用 main 函数

在运行 main 函数之前,会有一段引导代码,最终由这段代码调用 main 函数,这段引导代码不需要自己编写,而是在编译、链接中由链接器将这段程序链接到应用程序中,构成最终的可执行文件,加载器会将可执行文件加载到内存中
进程的终止

正常终止


[*]在 main 函数中通过 return 返回,终止进程
[*]调用库函数 exit 终止进程
[*]调用系统调用_exit/_Exit
异常终止


[*]调用 abort 函数终止进程
[*]被信号终止
终止进程:exit()和_exit()

exit()和_exit()用法


[*]void _exit(int status):终止进程的运行,参数 status 表示进程终止时的状态,通常 0 表示正常终止,非零值表示发证了错误,如 open 打开文件失败(不是 abort 所表示的异常)
[*]void exit(int status):参数 status 含义同上
exit()和_exit()区别


[*]exit() 是库函数,_exit() 是一个系统调用,他们所需要包含的头文件不同
[*]这两个函数的终止目的相同,都是终止进程,但在终止过程前需要做的处理不一样

exit()在终止进程的时候会调用终止处理函数:int atexit(void (*function)(void));,可以调用多个 atexit,调用顺序和注册顺序相反
标准输出默认是行缓存,检测到"\n"后才会把该行输出,_exit()不会刷新 IO 缓存,因此没有"\n"的情况时该行不会输出


[*]不会刷新 stdio 缓冲的情况

[*]_exit()/_Exit()
[*]被信号终止

exit()和 return 的区别


[*]exit()为库函数,return 为 C 语言的语句
[*]exit()函数最终会进入到内核,把控制权交给内核,最终由内核去终止进程;return 并不会进入内核,只是一个函数的返回,返回到它的上层调用,最终由上层调用终止进程


[*]return 和 exit 同样会调用终止处理函数、刷新 IO 缓存
exit()和 abort 区别


[*]exit 函数用于正常终止进程(执行一些清理工作),abort 用于异常终止进程(不会执行清理工作,会直接终止进程),abort 本质上是直接执行 SIGABRT 信号的系统默认处理操作
进程的环境变量

环境变量的概念


[*]环境变量是指在进程运行环境中定义一些变量,类似于进程的全局变量,可以在程序的任何地方获取,只需声明即可。但与全局变量不同的是,这些环境变量可以被其他子进程所继承,也就是具有继承性
[*]环境变量的本质还是变量,不过这些变量没有类型,都是以字符串的形式存储在一个字符串数组当中,称为环境表(以 NULL 结尾),数组中的每个环境变量都是以 name = value 这种形式定义的,name 表示变量名称,value 表示变量值
环境变量相关命令


[*]env:使用命令查看环境变量
[*]echo $name:查看环境变量
[*]export name=value:自定义/修改环境变量(注意等号前后不要有空格)
[*]unset name:删除环境变量
常见的环境变量


[*]PATH:用于指定可执行程序的搜索路径
[*]HOME:当前用户的家目录
[*]LOGNAME:指定当前登录的用户
[*]HOSTNAME:指定主机名
[*]SHELL:指定当前 shell 解析器
[*]PWD:指定进程的当前工作目录
环境变量的组织形式


在应用程序中获取环境变量

在每个应用程序中都有一组环境变量,是在进程创建中从父进程中继承过来的

[*]environ 变量获取:全局变量,可以直接在程序中使用,只需要申明就好,environ 实际上是一个指针,指向环境表
extern char **environ;// 申明一下,即可使用environ
[*]通过 main 函数获取(尽量不要使用这种方式,有的系统可能不支持)
int main(int argc, char *argv[], char *env[]);   // 第三个参数为进程的环境表
[*]通过 getenv 获取指定的环境变量(库函数)
#include <stdlib.h>
char *getenv(const char *name);// 如果存放该环境变量,则返回该环境变量的值对应字符串的指针;如果不存在该环境变量,则返回NULL#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
// 测试
int main(int argc, char *argv[]){
    char *get_str;
    get_str = getenv(argv);
    if(get_str == NULL){
      printf("error!\n");
      exit(1);
    }
    printf("%s\n", get_str);
    exit(0);
}使用 getenv()需要注意,不应该去修改其返回的字符串,修改该字符串意味着修改了环境变量对应的值,Linux 提供了相应的修改函数,如果需要修改环境变量的值应该使用这些函数,不应直接改动该字符串。
添加/修改/删除环境变量


[*]putenv:添加/修改环境变量(有对应的 name 则修改,没有则添加)
#include <stdlib.h>
int putenv(char *string);// string是一个字符串指针,指向name=value形式的字符串;成功返回 0,失败将返回非0值,并设置 errno该函数调用成功之后,参数 string 所指向的字符串就成为了进程环境变量的一部分了,换言之,putenv()函数将设定 environ 变量中的某个元素指向该 string 字符串,而不是指向它的复制副本,这里需要注意!因此,不能随意修改参数 string 所指向的内容,这将影响进程的环境变量,参数 string 不应为自动变量(即在栈中分配的字符数组),因为自动变量的生命周期是函数内部,出了函数之后就不存在了(可以使用 malloc 分配堆内存,或者直接使用全局变量)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
// 测试
int main(int argc, char *argv[]){
    char *get_str;
    if(putenv(argv)){
      printf("error!\n");
      exit(-1);
    }
    for(int i = 0; environ != NULL; i++){
      printf("%s\n", environ);
    }
    return 0;
}上述代码 putenv 在本进程范围内修改了环境变量,进程结束后,原来的环境变量不变

[*]setenv:添加/修改环境变量(推荐使用这个)(可替代 putenv 函数)
#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);// name为环境变量的名称,value为环境变量的值
// overwrite:若name标识的环境变量已经存在,在参数overwrite为0的情况下,setenv()函数将不改变现有环境变量的值,也就是说本次调用没有产生任何影响;如果参数overwrite的值为非0,若参数name标识的环境变量已经存在,则覆盖,不存在则表示添加新的环境变量。

[*]setenv 和 putenv 的区别:

[*]setenv 会将用户传入的 name=value 字符串拷贝到自己的缓冲区中,而 putenv 不会
[*]setenv()可通过参数 overwrite 控制是否需要修改现有变量的值而仅以添加变量为目的,显然 putenv()并不能进行控制


[*]name=value ./test:在进程执行时添加环境变量(可同时添加多个环境变量,用空格隔开)
[*]unsetenv:从环境表中移除参数 name 标识的环境变量
#include <stdlib.h>
int unsetenv(const char *name);清空环境变量


[*]将 environ 设置为 NULL
[*]通过 clearenv 来清空环境变量
#include <stdlib.h>
int clearenv(void);创建子进程

所有的子进程都是由父进程创建出来的


[*]比如在终端执行./test,这个进程是由 shell 进程(bash、sh 等 shell 解析器)创建出来的
[*]最原始的进程为 init 进程,它的 PID 为 1,由它创建出其他进程
getpid()获取当前进程的 PID,getppid()获取当前进程父进程的 PID,命令行中通过 ps-aux/pstree -T 命令查看 PID
父子进程间的文件共享


[*]文件共享:多个进程、多个线程对同一文件进行读写操作
[*]子进程会拷贝父进程打开的所有文件描述符(fd)


[*]验证父子进程间的文件共享是按照接续写(使用这个)还是分别写:
接续写:两个文件描述符指向同一个文件表,使用同一个读写指针
分别写:可能会出现数据覆盖的情况
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 测试
int main(void){
    int fd;
    int pid;
    fd = open("./test.txt", O_WRONLY | O_TRUNC);
    if(fd == -1){
      printf("error");
      return 1;
    }
    pid = fork();
    if(pid > 0){
      printf("parent %d %d\n", pid, getppid());
      write(fd, "123456", 6);
      close(fd);
    }else if(pid == 0){
      printf("child %d %d\n", getpid(), getppid());
      write(fd, "Hello World", 11);
      close(fd);
    }else{
      printf("build error\n");
      exit(-1);
    }
    exit(0);
}父子进程间的竞争关系

fork 之后不知道是父进程先返回还是子进程先返回,由测试结果来看,绝大部分情况下是父进程先返回
父进程监视子进程


[*]父进程需要知道子进程的状态改变:

[*]子进程终止
[*]子进程因为收到停止信号而停止运行(SIGSTOP、SIGTSTP)
[*]子进程在停止状态下因为收到恢复信号而恢复运行(SIGCONT)

[*]以上也是 SIGCHLD 信号的三种触发情况,当子进程发生状态改变时,内核会向父进程发送这个 SIGCHLD 信号
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
intwaitid(idtype_tidtype, id_t id, siginfo_t *infop, int options);wait 函数


[*]wait 函数为系统调用,可以等待进程的任一子进程终止,同时获取子进程的终止信息(监视子进程第一种状态的改变),作用:
[*]监视子进程什么时候被终止,以及获取子进程终止时的状态信息
[*]回收子进程的一些资源(俗称“收尸”)

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);// wstatus用于存放子进程终止时的状态信息,可以设置为NULL,表示不接收子进程终止时的状态信息
// 返回值:若成功返回终止的子进程对应的进程号,失败则返回-1

[*]进程调用 wait()函数的情况:
[*]如果该进程没有子进程(即没有需要等待的子进程),那么 wait()将返回-1,并且将 errno 设置为 ECHILD
[*]如果该进程所有子进程都还在运行,则 wait()会一直阻塞等待,直到某个子进程终止
[*]如果调用 wait()之前该进程已经有一个或多个子进程终止了,那么调用 wait()不会阻塞,会回收子进程的一些资源,注意一次 wait 调用只能为一个已经终止的子进程“收尸”
[*]status 为 NULL 或者 (int *)0 时,返回该退出的子进程的 PID 号
[*]如果父进程关注子进程的退出时状态,可以使用如下方式,status 将保存子进程结束时的状态信息(子进程退出时 exit 里的参数会被保存到 status 中)


int status;
wait(&status);

[*]可以通过以下宏来检查 status 参数:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(void){
    int pid = fork();
    int ret;
    int status;
    if(pid > 0){
      printf("parent %d %d\n", pid, getppid());
      ret = wait(&status);
      printf("wait return %d %d\n", ret, WEXITSTATUS(status));
      exit(0);
    }else if(pid == 0){
      printf("child %d %d\n", getpid(), getppid());
      exit(3);
    }else{
      printf("build error\n");
      exit(-1);
    }
    exit(0);
}

[*]使用 wait()的限制:

[*]如果父进程创建了多个子进程,使用 wait()将无法等待某个特定的子进程的完成,只能按照顺序等待下一个子进程的终止,一个一个来、谁先终止就先处理谁;
[*]如果子进程没有终止,正在运行,那么 wait()总是保持阻塞,有时我们希望执行非阻塞等待,是否有子进程终止,通过判断即可得知;
[*]使用 wait()只能发现那些被终止的子进程,对于子进程因某个信号(譬如 SIGSTOP 信号)而停止(注意这里停止指的暂停运行),或是已停止的子进程收到 SIGCONT 信号后恢复执行的情况就无能为力了(没法监视后两种状态改变)

waitpid 函数

waitpid 函数没有 wait 函数存在的限制
#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *wstatus, int options);// wstatus的含义同wait里的
// 返回值:与wait基本相同,但参数options包含了WNOHANG标志时,返回值可能会出现0

[*]参数 pid 表示需要等待的某个具体子进程,取值如下:

[*]pid > 0:等待进程号为 pid 的子进程
[*]pid = 0:等待该父进程的所有子进程
[*]pid < -1:等待进程组标识符与 pid 绝对值相等的所有子进程(特殊情况可能为负数)
[*]pid = -1:等待任意子进程

[*]参数 options 是一个位掩码,设置为 0 时功能和 wait 相同(pid 为-1 时):

[*]WNOHANG:如果子进程没有发生状态改变(终止、暂停),则立即返回,也就是执行非阻塞等待,通过返回值可以判断是否有子进程发生状态改变,若返回值等于 0 表示没有发生改变(可以实现轮询 poll 来监视子进程的状态)
[*]WUNTRACED:除了返回终止的子进程的状态信息外,还返回因信号而停止(暂停运行)的子进程状态信息
[*]WCONTINUED:返回那些因收到 SIGCONT 信号而恢复运行的子进程的状态信息

异步方式监视子进程


[*]可以为 SIGCHLD 信号(子进程退出时发给父进程的信号)绑定一个信号处理函数(为父进程绑定),然后在信号处理函数中调用 wait/waitpid 函数回收子进程(针对第一种状态,其他两种状态可以进行相应处理)
[*]这样可以使得父进程做自己的事情(异步),不用阻塞或者轮询等待子进程结束(也可以通过多线程来实现)
[*]使用这一方法的注意事项:

[*]当调用信号处理函数时,会暂时将引发调用的信号添加到进程的信号掩码中(除非 sigaction()指定了 SA_NODEFER 标志),这样一来,当 SIGCHLD 信号处理函数正在为一个终止的子进程“收尸”时,如果相继有两个子进程终止,即使产生了两次 SIGCHLD 信号,父进程也只能捕获到一次 SIGCHLD 信号,结果是,父进程的 SIGCHLD 信号处理函数每次只调用一次 wait(),那么就会导致有些僵尸进程成为“漏网之鱼”
[*]解决方案就是:在 SIGCHLD 信号处理函数中循环以非阻塞方式来调用 waitpid(),直至再无其它终止的子进程需要处理为止,所以,通常 SIGCHLD 信号处理函数内部代码为:

while (waitpid(-1, NULL, WNOHANG) > 0)
continue;上述代码一直循环下去,直至 waitpid()返回 0,表明再无僵尸进程存在;或者返回-1,表明有错误发生。
僵尸进程和孤儿进程


[*]孤儿进程:父进程先于子进程结束,在 Linux 系统当中,所有的孤儿进程都自动成为 init 进程(进程号为 1)的子进程,换言之,某一子进程的父进程结束后,init 进程变成了孤儿进程的“养父”;这是判定某一子进程的“生父”是否还“在世”的方法之一
[*]僵尸进程:子进程先于父进程结束,此时父进程还未来得及给子进程“收尸”,那么此时子进程就变成了一个僵尸进程。

[*]当父进程调用 wait()(或waitpid、waitid等)为子进程“收尸”后,僵尸进程就会被内核彻底删除
[*]如果父进程并没有调用 wait()函数然后就退出了,那么此时 init 进程将会接管它的子进程并自动调用 wait(),故而从系统中移除僵尸进程
[*]如果父进程创建了某一子进程,子进程已经结束,而父进程还在正常运行,但父进程并未调用 wait()回收子进程,此时子进程变成一个僵尸进程。首先来说,这样的程序设计是有问题的,如果系统中存在大量的僵尸进程,它们势必会填满内核进程表,从而阻碍新进程的创建。需要注意的是,僵尸进程是无法通过信号将其杀死的,即使是SIGKILL信号也不行,那么这种情况下,只能杀死僵尸进程的父进程或者等待其父进程终止,init 进程将会接管这些僵尸进程,从而将它们从系统中清理掉)
[*]所以,在我们的一个程序设计中,一定要监视子进程的状态变化,如果子进程终止了,要调用 wait()将其回收,避免僵尸进程

执行新程序


[*]子进程和父进程运行的不是同一个程序,比如test进程通过fork函数创建子进程后,这个子进程也运行test这个程序,当这个子进程启动后,通过调用库函数或者系统调用用一个新的程序去替换test程序,然后从main函数开始执行这个新程序
execve函数


[*]execve为系统调用,可以将新程序加载到某一进程的内存空间,通过调用 execve()函数将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的main()函数开始执行
#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);

[*]参数及返回值含义:

[*]pathname:指向新程序的路径名(绝对路径/相对路径),对应 main(int argc,char *argv[])的 argv
[*]argv[]:传递给新程序的命令行参数(字符串数组,以 NULL 结束),对应 main 函数的 argv 参数
[*]envp:指定了新程序的环境变量列表,对应新程序的 environ 数组,以 NULL 结束
[*]返回值:调用成功不会返回(执行新程序去了),失败返回-1,并设置 errno

注意 pathname 可以是路径:./test,也可以是可执行文件名称:test(一切接文件)(在同一个目录下)
exec 族库函数


[*]exec 族库函数基于 execve 系统调用来实现
#include <unistd.h>
extern char **environ;
// execl("/bin/ls", "ls", "-a", "-l", NULL);
int execl(const char *pathname, const char *arg, .../* (char*) NULL */);
//execlp("ls", "ls", "-a", "-l", NULL);
int execlp(const char *file, const char *arg, .../* (char*) NULL */);
// execle("/bin/ls", "ls", "-a", "-l", NULL, environ);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);

// execv("/bin/ls", argv);
int execv(const char *pathname, char *const argv[]);
// execvp("ls", argv);
int execvp(const char *file, char *const argv[]);
// execvp("ls", argv, environ);
int execvpe(const char *file, char *const argv[], char *const envp[]);

[*]参数含义

[*]pathname 同 execve,指向新程序的路径名,file 参数指向新程序文件名,它会去进程的 PATH 环境变量所定义的路径寻找这个新程序(兼容绝对路径和相对路径)
[*]arg 参数将传递给新程序的参数列表依次排列,通过多个字符串来传递,以 NULL 结尾
[*]默认情况下,新程序保留原来程序的环境表

system 函数


[*]system 为库函数,可以很方便地在程序中执行任意 shell 命令
[*]system 内部实现原理:system()函数其内部的是通过调用 fork()、execl()以及 waitpid()这三个函数来实现它的功能。首先 system()会调用 fork()创建一个子进程,然后子进程会调用 execl()加载一个shell 解释器程序(通常是/bin/sh程序),这是子进程就是一个 shell 进程了,这个 shell 子进程解析 command 参数对应的命令,并创建一个或多个子进程执行命令(命令时可执行程序,每执行一个命令,shell 子进程就需要创建一个进程然后加载命令/可执行程序),当命令执行完后 shell 子进程会终止运行,system()中的父进程会调用 waitpid()等待回收shell 子进程,直到 shell 子进程退出,父进程回收子进程后,system 函数返回。
[*]system 每执行一个 shell 命令,system 函数至少要创建两个子进程:

[*]system 函数创建 shell 子进程
[*]shell 子进程根据命令创建它的子进程(一个或多个,根据命令而定)

#include <stdlib.h>
int system(const char *command);   // system("ls -al")

[*]参数及返回值含义:

[*]command:指向需要执行的 shell 命令,如"ls -al"
[*]返回值:

[*]当参数 command 为 NULL,如果 shell 可用则返回一个非 0 值,若不可用则返回 0;针对一些非 UNIX 系统,该系统上可能是没有 shell 的(bash/sh/csh),这样就会导致 shell 不可用
[*]如果 command 不为 NULL,则:

[*]如果 system 无法创建子进程(fork 失败)或无法获取子进程的终止状态(waitpid 返回-1),那么 system()返回-1
[*]如果子进程不能执行 shell(execl 执行不成功),则 system()的返回值就是子进程通过调用_exit(127)终止了
[*]如果所有的系统调用都成功,system()函数会返回执行 command 的 shell 进程的终止状态(执行最后一个命令的终止状态)



// 根据system函数的功能以及该函数在不同情况下的返回值所实现的一个简易的system函数
int system(const char *command){
    if(command == NULL){   // 返回值1
      if("当前系统中存在可用的shell解析器程序 bash/sh/csh")
            return "非零值";
      else
            return 0;
    }
    pid_t pid = fork();// 创建子进程,该子进程会变为shell子进程
    switch(pid){
      case -1:      // 创建子进程失败,返回值2
            return -1;
      // 子进程
      case 0:
            excel("/bin/sh", "sh", "-c", command, NULL);// 加载shell解析器,如果成功不会返回
            _exit(127);// 加载shell解析器失败,调用_exit(127),返回值3
      // 父进程
      default:
            int status;
            int ret;

            ret = waitpid(pid, &status, NULL); // 等待回收子进程
            if(ret == -1)
                return -1;   // 无法获取子进程的状态信息,返回值1
            return status;   // 返回子进程的状态信息
    }   // 如果所有系统调用都成功,那么system返回shell子进程的终止状态信息
      // 即返回执行最后一个命令的终止信息,返回值4
}

[*]system()在使用上简单,但是是以牺牲效率为代价的
vfork 函数

fork 系统调用使用场景


[*]父进程希望子进程复制自己,父子进程执行相同的程序,各自在自己的进程空间中运行
[*]子进程执行一个新的程序,从该程序的 main 函数开始运行,调用 exec 函数
fork 函数的缺点

fork+exec 配合使用时,效率比较低
vfork 函数

vfork 为系统调用,也是用来创建一个进程,返回值也是一样的
fork 与 vfork 不同点


[*]对于 fork 函数,fork 会为子进程创建一个新的地址空间(也就是进程空间),子进程几乎完全拷贝了父进程,包括数据段、代码段、堆、栈等;而对于 vfork 函数,子进程在终止或者成功调用 exec 函数之前,子进程与父进程共享地址空间,共享所有内存,包括数据段、堆栈等,所以在子进程在终止或成功调用 exec 函数前,不要去修改除 vfork 的返回值的 pid_t 类型的变量之外的任何变量(父进程的变量)、也不要调用任何其它函数(除 exit 和 exec 函数之外的任何其它函数),否则将会影响到父进程(vfork 函数的正确使用方法就是创建子进程后立马调用 exec 加载新程序,所以没有意义去调用其他函数或者修改变量)
注意:vfork 创建的子进程如果要终止应调用 exit,而不能调用 exit 或 return 返回,因为如果子进程调用 exit 或 return 终止,则会调用父进程绑定的终止处理函数以及刷新父进程的 stdio 缓冲,影响到父进程

[*]对于 fork 函数,fork 调用之后,父、子进程的执行次序不确定;而对于 vfork 函数,vfork 函数会保证子进程先运行,父进程此时处于阻塞、挂起状态,在子进程终止或成功调用 exec 函数之后,父进程才会被调度运行
注意:如果子进程在终止或成功调用 exec 函数之前,依赖于父进程的进一步动作,将会导致死锁!

[*]vfork 函数在创建子进程时,不用拷贝父进程的数据段、代码段、堆栈等,所以 vfork 函数的效率要高于 fork 函数
目前的 fork 函数使用了写时复制技术,效率还算可以,所以尽量不要用 vfork,以免产生一些难以排查的问题
进程状态和进程间的关系

进程状态

<ul>进程状态有六种:

[*]R(TASK_RUNNING):运行状态或可执行状态(就绪态):正在运行的进程或者在进程队列中等待运行的进程都处于该状态,所以该状态实际上包含了运行态和就绪态这两个基本状态
[*]S(TASK_INTERRUPTIBLE):可中断睡眠状态(浅度睡眠):可中断睡眠状态也被称为浅度睡眠状态,处于这个状态的进程由于在等待某个事件(等待资源有效)而被系统挂起,譬如等待 IO 事件、主动调用 sleep 函数等。一旦资源有效时就会进入到就绪态,当然该状态下的进程也可被信号或中断唤醒(所以可中断的意思就是,即使未等到资源有效,也可被信号中断唤醒,譬如 sleep(5)休眠 5 秒钟,通常情况下 5 秒未到它会一直睡眠、阻塞,但在这种情况下,收到信号就会让它结束休眠、被唤 )
[*]D(TASK_UNINTERRUPTIBLE):不可中断睡眠状态(深度睡眠):不可中断睡眠状态也被称为深度睡眠状态,该状态下的进程也是在等待某个事件、等待资源有效,一旦资源有效就会进入到就绪态;与浅度睡眠状态的区别在于,深度睡眠状态的进程不能被信号或中断唤醒,只有当它所等待的资源有效时才会被唤醒(一般该状态下的进程正在跟硬件交互、交互过程不允许被其它进程中断)
[*]T(TASK_STOPPED):停止状态(暂停状态):当进程收到停止信号时(譬如 SIGSTOP、SIGTSTP 等停止信号),就会由运行状态进入到停止状态。当处于停止状态下,收到恢复信号(譬如 SIGCONT 信号)时就会进入到就绪态
[*]Z(TASK_ZOMBIE):**僵尸状态 **:表示进程已经终止了,但是并未被其父进程所回收,也就是进程已经终止,但并未彻底消亡。需要其父进程回收它的一些资源,归还系统,然后该进程才会从系统中彻底删除
[*]X(TASK_DEAD): 死亡状态:此状态非常短暂、ps 命令捕捉不到。处于此状态的进程即将被彻底销毁,可以认为就是僵尸进程被回收之后的一种状态

ps 命令查看到的进程状态信息中,除了第一个大写字母用于表示进程状态外,还有其他一些字符:
<ul>s:表示当前进程是一个会话的首领进程
l:表示当前进程包含了多个线程
N:表示低优先级
页: [1]
查看完整版本: Linux应用——进程基础