Linux下进程概述

来源:互联网 发布:魔兽世界老版本数据库 编辑:程序博客网 时间:2024/05/16 10:40

1、进程概述

1.1、进程概念

        进程是一个动态实体,是程序的一次执行过程。进程是操作系统资源分配的基本单位,它与程序、线程的概念是不一样的。进程和程序的的区别在于进程是动态的,程序是静态的,进程是运行中的程序,程序是一些保存在硬盘上的可执行代码。

        为了让计算机在同一时间内执行更多的任务,进程又可以创建许多线程,它是比进程更小的能独立运行的基本单位,线程基本上不拥有系统资源,它与同属一个进程的其它线程共享进程拥有的全部资源。进程在执行过程中拥有独立的内存单元,其内部的线程共享这些内存。一个线程可以创建和撤销另一个线程,同一个进程内的多个线程可以并行执行。

Linux下可通过命令ps或pstree查看当前系统中的进程。

1.2、进程标识

        Linux系统中,每个进程都是通过唯一的进程ID标识的,进程ID是一个非负数。每个进程除了进程ID外还有其它一些标识信息,它们都可以通过相应的函数获得。这些函数的声明在unistd.h头文件中。

用户ID和组ID的相关概念如下所示:

  • 实际用户ID(uid):标识运行该进程的用户
  • 有效用户ID(euid):标识以什么用户身份来运行进程。例如:某个普通用户A,运行了一个程序,而这个程序是以root身份来运行的,那么这个程序运行时就有root权限。此时实际用户ID是用户A的ID,而有效用户ID是root用户ID。
  • 实际组ID(gid):它是实际用户所属的组的组ID。
  • 有效组ID(egid):有效组ID是有效用户所属组的组ID。
1.3、Linux进程的结构
        Linux中一个进程由3部分组成:代码段、数据段、堆栈段。
代码段存放程序的可执行代码,数据段存放程序的全局变量、常量、静态变量。堆栈段中的堆用于存放动态分配的内存变量;堆栈段中的栈用于函数调用,它存放着函数的参数、函数内部定义的局部变量。
1.4、Linux进程状态
        Linux系统中进程有以下几种状态。
  • 运行状态:进程正在运行或在运行队列中等待运行
  • 可中断等待状态:进程正在等待某个事件完成(如等待数据到达),等待过程中可以被信号或定时器唤醒。
  • 不可中断等待状态:进程也正在等待某个事件完成,在等待过程中不可以被信号或定时器唤醒,必须等待,直到等待的事件发生。
  • 僵死状态:进程已终止,但进程描述符依然存在,直到父进程调用wait()函数后释放。
  • 停止状态:进程因为收到SIGSTOP、SIGSTP、SIGTIN、SIGTOU信号后停止运行或者该进程正在被跟踪(调试程序时,进程处于被跟踪状态)。
        用ps命令可以查看进程的当前状态。运行状态为R(running),可中断等待状态为S(sleeping),不可等待中断状态D(uninterruptible sleep),僵死状态为Z(zombile),停止状态为T(traced or stopped)。
        在运行结果中可以看到一些后缀字符。其意义分别为:<(高优先级进程),N(低优先级进程),L(内存锁页,即页不可以被换出内存),s(该进程为会话首进程),l(多线程进程),+(进程位于前台进程组)。例如Ssl说明该进程处于可中断等待状态,且该进程为会话首进程,而且是一个多线程的进程。
1.5、进程控制
        Linux进程控制包括 创建进程、执行新程序、退出进程以及改变进程优先级等。Linux系统为了对进程进行控制而提供了一系列的系统调用,用户可以使用这些系统调用来完成创建一个新进程、终止一个进程、改变进程的优先级别等操作。
        在Linux系统中,用于对进程进行控制的主要系统调用以下所示:
  • fork:用于创建一个新进程
  • exit:用于终止进程
  • exec:用于执行一个应用程序
  • wait:将父进程挂起,等待子进程终止
  • getpid:获取当前进程的进程ID
  • nice:改变进程的优先级
1.6、进程的内存映像
1.6.1、Linux下程序转化为进程
        Linux下的C程序的生成分为4个阶段:预编译、编译、汇编、链接。编译器gcc经过预编译、编译、汇编3个步骤将源程序文件转换为目标文件。如果程序有多个目标文件或者程序中使用了库函数,编译器还要将所有的目标文件或所需的库链接起来,最后生成可执行程序。当程序执行时,操作系统将可执行程序复制到内存中,程序转化为进程通常需要经过以下步骤:
  • 内核将程序读入内存,为程序分配内存空间
  • 内核为该进程分配进程标识符(pid)和其它所需资源
  • 内核为该进程保存pid及相应的状态信息,把进程放到运行队列中等待执行。程序转化为进程后就可以被操作系统的调度程序调度执行了
1.6.2、进程的内存映像
        进程的内存映像是指内核在内存中如何存放可执行程序文件。在将程序转化为进程的过程中,操作系统将可执行程序由硬盘复制到内存中。
Linux下的程序映像的一般布局,从内存的低地址到高地址依次如下:
  • 代码段:即二进制机器代码,代码段是只读的,可被多个进程共享。如果一个进程创建了一个子进程,父子进程共享代码段,此外子进程还能获得父进程数据段、堆、栈的复制
  • 数据段:存储已被初始化的变量,包括全局变量和已被初始化的静态变量
  • 未初始化数据段:存储未被初始化的静态变量,它也被称为bss段
  • 堆:用于存放程序运行中动态分配的变量
  • 栈:用于函数调用,保存函数的返回地址、函数参数、函数内部定义的局部变量
  • 另外:高地址还存储了命令行参数和环境变量。
可执行程序和内存映像的区别如下:
  • 可执行程序位于磁盘中,而内存映像位于内存中
  • 可执行程序没有堆栈,因为程序被加载到内存中才会分配堆栈
  • 可执行程序虽然也有未初始化数据段但它并不被存储在位于硬盘中的可执行文件中
  • 可执行程序是静态的、不变的,而内存映像随着程序的执行是在动态变化的。例如:数据段随着程序从执行要存储新的变量值,栈在函数调用时也是在不断变化中。
2、进程操作
2.1、创建进程
        每个进程由进程ID号标识,进程被创建时系统会为其分配一个唯一的进程ID。当一个进程向父进程(创建该进程的进程)传递其终止消息时,意味着这个进程的整个生命周期的结束,此时,该进程占用的所有资源包括进程ID被全部释放。
        创建进程有2种方式,一种由操作系统创建;二是由父进程创建。由操作系统创建的进程,它们之间是平等的,一般不存在资源继承关系。而对于由父进程创建的进程(通常称为子进程),它们和父进程存在隶属关系。子进程又可以创建进程,这样形成一个进程家族。子进程可以继承其父进程几乎所有的资源。在系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程。
        系统调用fork是创建一个新进程的唯一方法,除了init进程(它是内核启动时以特殊方式创建的),进程调用fork函数就创建了一个子进程。创建一个子进程后,父进程和子进程争夺CPU,抢到CPU者执行,另外一个挂起等待。如果想要父进程等待子进程执行完毕后再继续执行,可以在fork操作之后调用wait或waitpid。一个刚被fork的子进程会和它的父进程一样,继续执行当前的程序。几个进程同时执行一个应用程序通常用处不大。更常见的使用方法是子进程在被fork后可以通过调用exec函数执行其它程序。
2.2.1、fork函数
        fork函数是创建新进程的唯一方法,vfork函数虽然也可以创建进程,但它在创建进程时实际上还是调用fork函数。在命令行下输入 man 2 fork获得该函数的说明。一般情况下函数只有一个返回值,但fork函数非常特殊,它有2个返回值。成功调用fork函数后,当前进程实际上已经分裂为2个进程,一个是原来的父进程,另一个是刚刚创建的子进程。父子进程在调用fork函数的地方分开,fork函数有2个返回值,一个是父进程调用fork函数的返回值,该返回值是刚刚创建的子进程的ID;另一个是子进程中fork函数的返回值,该返回值是0。fork函数返回2次的前提是进程创建成功,如果进程创建失败,则只返回-1。父子进程的fork函数返回的值不同,这样可以利用返回值来区别父子进程。fork函数的用法示例如下:
#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(void){pid_t pid;printf("Process Creation Study.\n");pid = fork();switch(pid){case 0:printf("curPid is %d,parent pid is %d\n", pid, getppid());break;case -1:printf("Process creation failed.\n");break;default:printf("child pid is %d,parent pid is %d\n", pid, getpid());break;}//exit(0);}
程序运行结果如下:

一般来说,fork之后是父进程先执行,还是子进程先执行是不确定的,这取决于内核所使用的调度算法,操作系统一般让所有的进程都享有同等执行权,除非某进程的优先级比其它的高。示例代码如下:
#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(void){pid_t pid;char* msg;int k;printf("Process Creation Study.\n");pid = fork();switch(pid){case 0:msg = "child process is running.\n";k=3;break;case -1:printf("Process creation failed.\n");break;default:msg = "parent process is running.\n";k=5;break;}while(k>0){puts(msg);sleep(1);k--;}exit(0);}

        父进程输出5条消息,子进程输出3条消息,可以看到父子进程交替执行。进程创建失败时,返回-1,失败的原因通常是父进程拥有的子进程个数超过了规定的限制,此时errno值为EAGAIN。如果可供使用的内存不足也会导致内存创建失败,测试errno值为ENOMEM。
2.2孤儿进程
      如果一个子进程的父进程优先于子进程结束,子进程就成为一个孤儿进程,它由init进程收养,成为init进程的子进程,示例代码如下:
#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(void){pid_t pid;pid = fork();switch(pid){case 0:while(1){printf("I am child process,PID:%d,Parent ID is%d\n", getpid(), getppid());sleep(3);}break;case -1:printf("Process creation failed.\n");break;default:printf("I am parent process ,my pid is %d\n",getpid());break;}//exit(0);}
输出结果如下:

        从结果可以看出,调用fork函数后,子进程先执行,打印出自己的ID号和父进程的ID号,之后父进程结束。此后,子进程就成了孤儿进程,由init进程收养,可以看到此时子进程的父进程ID号变为1.
2.3、vfork函数
        vfork也可以用来创建一个新进程,与fork相比,它有自己独特的用处。两者的异同点对比如下:
  • vfork和fork函数一样都是调用一次,返回2次
  • 使用fork创建一个子进程时,子进程完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。而使用vfork创建一个子进程时,操作系统并不将父进程的地址空间完全复制到子进程,用vfork创建的子进程共享父进程的地址空间,也就是说子进程完全运行在父进程的地址空间中,子进程对该地址空间中任何数据的修改同样为父进程所见。
  • 使用fork创建一个子进程时,哪个进程先运行取决于系统的调度算法,而vfork一个进程时,vfork保证子进程先运行,当它调用exec或exit后,父进程才可能被调度运行,如果在调用exec或exit之前子进程要依赖父进程的某个行为,就会导致死锁。
        使用fork创建一个子进程时,子进程几乎要复制父进程的所有资源,所以fork是一个开销很大的系统调用,这些开销并不是所有情况都需要。比如fork一个进程后,立即调用exec执行另外一个应用程序,那么fork过程中子进程对父进程地址空间的复制将是一个多余的过程。vfork不会复制父进程的地址空间,这样大大减小了系统开销。下面的代码通过观察父子进程的执行顺序和对父进程变量的修改说明两者的区别,代码如下:
#include <stdio.h>#include <sys/types.h>#include <unistd.h>int globVar= 5;int main(void){pid_t pid;int var = 0,i;printf("fork is different with vfork \n");//pid = fork();pid = vfork();switch(pid){case 0:i = 3;while(i-->0){printf("child process is running\n");globVar++;var++;sleep(1);}printf("child globVar=%d,var=%d\n",globVar,var);break;case -1:printf("Process creation failed.\n");break;default:i=5;while(i-->0){printf("parent process is running\n");globVar++;var++;sleep(1);}printf("parent globVar=%d,var=%d\n",globVar,var);break;}exit(0);}
当调用fork创建子进程时,运行结果如下:

当用vfork创建子进程时,可以使用命令 ps aux查看父子进程的状态分别是S(阻塞状态)和D(不可中断)状态,当调用vfork创建子进程时,运行结果如下:


3、创建守护进程
        守护进程(daemon)是指在后台运行,没有控制终端与其相连的进程。它独立于控制终端,通常周期性的执行某项任务。守护进程是一种很有用的进程,Linux大多数服务器就是用守护进程的方式实现的,如Internet服务器进程inetd,Web服务器进程httpd等。守护进程在后台运行,类似于windows中的系统服务。
        守护进程的启动方式有多种:它可以在Linux系统启动时从启动脚本/etc/rc.d中启动;也可以由作业规划进程crond进程启动;还可以由用户终端(通常是shell)执行。编写创建守护进程的程序时,要尽量避免产生不必要的交互。编写创建守护进程的程序有如下特点:
  • 让进程在后台执行。方法是调用fork产生一个子进程,然后使得父进程退出。
  • 调用setsid创建一个新对话期,控制终端。登录会话和进程组通常是从父进程继承下来的,守护进程要摆脱它们,不受它们的影响,其方法是调用setsid使进程成为一个会话组长。注意:当进程是会话组长时,调用setsid会失败。但第一点已经保证进程不会是会话组长,setsid调用成功后,进程成为新的会话组长和进程组长,并与原来的登录会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
  • 禁止进程重新打开控制终端,经过以上步骤,进程已经成为一个无终端的会话组长,但它可以重新申请打开一个终端。为了避免这种情况的发生,可以通过使进程不再是会话组长来实现,再一次通过fork创建新的子进程,使调用fork的进程退出。
  • 关闭不再需要的文件描述符。新创建的子进程从父进程继承打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在文件系统无法卸下以及引起无法预料的错误。先得到最高文件描述符值,然后用一个循环程序,关闭0到最高文件描述符值的所有文件描述符。
  • 将当前目录更改为根目录。当守护进程的当前目录在一个装配文件系统中时,该文件系统不能被卸载。一般需要将工作目录改为根目录
  • 将文件创建时使用的屏蔽字设置为0,子进程从创建它的父进程那里继承的文件创建屏蔽字可能会拒绝某些许可权。为防止这一点,使用umask(0)将屏蔽字清零。
  • 处理SIGCHLD信号。这一步不是必须的。但对于某些进程,特别是服务进程往往在请求到来时生成子进程处理请求。如果父进程不等子进程结束,子进程将成为僵尸进程(zombie),从而占用系统资源。如果父进程等待子进程结束,将增加父进程负担,映像服务器进程的并发性。在linux中可以简单的将SIGCHLD信号的操作设置为SIG_IGN。这样,子进程结束时不会产生僵尸进程
        示例代码如下所示:
#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <sys/param.h>#include <sys/stat.h>#include <time.h>#include <syslog.h>int init_daemon(void){pid_t pid;int i;//忽略终端I/O信号,STOP信号signal(SIGTTOU, SIG_IGN);signal(SIGTTIN, SIG_IGN);signal(SIGTSTP, SIG_IGN);signal(SIGHUP, SIG_IGN);pid = fork();if(pid > 0){exit(0);//结束父进程,使得子进程成为后台进程}else if(pid < 0){return -1;}//建立一个新的进程组,在这个新的进程组中,子进程成为这个进程组的首进程,以使该进程脱离所有终端setsid();//再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端pid = fork();if(pid > 0){exit(0);}else if(pid < 0){return -1;}//关闭所有从父进程继承的不再需要的文件描述符for(i=0; i<NOFILE; close(i++));//改变工作目录,使得进程不与任何文件系统联系chdir("/");//将文件屏蔽字设置为0umask(0);//忽略SIGCHLD信号signal(SIGCHLD, SIG_IGN);return 0;}int main(void){time_t now;init_daemon();syslog(LOG_USER|LOG_INFO, "Test deamon process!\n");while(1){sleep(8);time(&now);syslog(LOG_USER|LOG_INFO, "System time: \t%s\t\t\n", ctime(&now));}}
3、进程退出
        进程退出表示进程结束运行,在Linux系统中进程退出的方法分为正常退出和异常退出。其中正常退出的方法有3种,异常退出发方法有2种。
1)正常退出
  • 在main函数中调用return函数
  • 调用exit函数
  • 调用_exit函数
        2)异常退出
  • 调用abort函数
  • 进程收到某个信号,而该信号使程序终止
不管哪种退出方式,最终都会执行内核中的同一段代码。这段代码用来关闭进程所有已打开的文件描述符,释放他所占用的内存和其它资源。以下是各种退出方式的比较:
  • exit和return的区别:exit是一个函数,有参数;而return是函数执行完后的返回。exit把控制权交给系统,而return把控制权交给调用函数。
  • exit和abort的区别:exit是正常终止进程,而abort是异常终止
  • exit(int exit_code):exit函数的参数,为0代表进程正常终止,若为其它值表示程序执行过程中有错误发生,比如溢出、除数为0
  • exit()和_exit()的区别:exit在头文件stdlib.h中声明,而_exit在头文件unistd.h中声明,两个函数均能正常终止进程,但是_exit执行后会立即返回给内核,而exit要先执行一些清除操作,然后将控制权交给内核。
父子进程终止的先后顺序不同会产生不同的结果,在子进程退出前父进程先退出,则系统会让init进程接管子进程。当子进程先与父进程结束,而父进程又没有调用wait函数等待子进程结束,子进程进入僵尸状态,并且会一直保持下去除非系统重启。子进程处于僵尸状态时,内核只保存该进程的一些必要信息以备父进程所需,此时子进程始终占着系统资源,同时也减少了系统可以创建的最大进程数。如果子进程先于父进程终止,且父进程调用了wait或waitpid函数,则父进程会等待子进程结束。

4、执行新程序
使用fork或vfork函数创建子进程后,子进程通常会调用exec函数来执行另外一个程序。系统调用exec用于执行一个可执行程序以代替当前进程的执行映像。注意:exec调用并没有生成新进程。一个进程一旦调用exec函数,它本身就死亡了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一保留的就是进程ID,也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。
        Linux下exec函数有6种不同的调用形式,它们的声明在头文件unistd.h中,我们主要使用execve这个函数,其声明如下: 
int execve(const char* path, char* const argv[], char* const envp[]);
        为了更好的理解exec函数的使用,首先要理解环境变量这个概念。为了便于用户灵活的使用shell,Linux引入了环境变量的概念,包括用户的主目录,终端类型,当前目录等,它们定义了用户的工作环境,所以称为环境变量。可以使用env命令查看环境变量值,用户也可以修改这些变量以定制自己的工作环境,下面的代码演示了环境变量的使用。
#include <stdio.h>#include <malloc.h>extern char **environ;int main(int argc, char* argv[], char** envp){int i;printf("Argument:\n");for(i=0; i<argc; i++){printf("argv[%d] is %s\n",i,argv[i]);}printf("Environment:\n");for(i=0; envp[i]!=NULL; i++){printf("%s\n",envp[i]);}return 0;}
程序输入如下:

输出说明:
Argument:后显示的是程序的命令行参数。Environment:后显示的是当前系统中各个环境变量的值,在命令行用env命令可以得到相同的结果。程序中通过系统预定义的全局变量environ显示各个环境变量的值,还可以通过另外一个方法得到环境变量的值。事实上main函数的完整形式应该是:
int main(int argc, char* argv[], char** envp);
通过打印main函数的envp参数,同样可以得到环境变量。事实上无论哪个exec函数,都是将可执行程序的路径、命令行参数、环境变量3个参数传递给main函数的。
        Linux系统下,exec可以执行二进制的可执行文件,也可以执行shell脚本程序,但shell脚本必须下面所示的格式开头:第一行必须为#!interpretername [arg]。其中interpretername 可以是shell或其它解释器,例如:/bin/sh或/usr/bin/perl,arg是传递给解释器的参数。
程序1:用来替换进程映像的程序
#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(int argc, char* argv[], char** environ){int i;printf("I am a process image!\n");printf("My pid=%d,parent pid=%d\n", getpid(),getppid());printf("uid=%d,gid=%d\n",getuid(), getgid());for(i=0; i<argc; i++){printf("argv[%d]:%s\n",i, argv[i]);}}
程序2:exec函数实例,这里使用execve函数
#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(int argc, char* argv[], char** environ){pid_t pid;int stat_val;printf("Exec example!\n");pid = fork();switch(pid){case 0:printf("Child process is running.\n");printf("My pid = %d,parent pid=%d\n",getpid(), getppid());printf("uid=%d,gid=%d\n",getuid(),getgid());execve("processimage", argv,environ);printf("process never go to here!\n");exit(0);break;case -1:perror("process creation failed.\n");exit(1);break;default:printf("Parent process is running\n");break;}wait(&stat_val);exit(0);}
运行结果为:

从运行结果可以看到执行新程序的进程保持了原来进程的进程ID,父进程ID,实际用户ID,实际组ID。当调用新的可执行程序后,原有的子进程映像被替代,不再被执行。执行新程序后的进程除了保持原来进程的进程ID,父进程ID,实际用户ID,实际组ID之外,进程还保持了许多原有特征,主要有:
  • 当前工作目录
  • 根目录
  • 创建文件时使用的屏蔽字
  • 进程信号屏蔽字
  • 未决警告
  • 和进程相关的使用处理器的时间
  • 控制终端
  • 文件锁
5、等待进程结束
        前面提到当子进程先于父进程退出时,如果父进程没有调用wait和waitpid函数,子进程就会进入僵死状态。如果父进程调用了wait或waitpid函数,就不会使子进程变为僵尸进程,这两个函数的声明如下:
#include <sys/types.h>#include <sys/wait.h>pid_t wait(int* statloc);pid_t waitpid(pid_t pid, int *statloc, int options);
        wait函数使父进程暂停执行,直到它的子进程结束为止。该函数的返回值是终止运行的子进程的PID。参数statloc所指向的变量存放子进程的退出码,即从子进程的main函数返回的值或子进程中exit函数的参数。如果statloc不是一个空指针,状态信息将被写入它指向的变量。
        waitpid也用来等待子进程的结束,但它用于等待某个特定进程结束。参数pid用于指明要等待的子进程的PID。statloc的含义与wait函数中statloc相同。options参数允许用户改变waitpid的行为,若将该参数赋值为WNOHANG,则使父进程不被挂起而立即返回并执行其后的代码。
        如果想让父进程周期性的检查某个特定的子进程是否已经退出,可以按如下方式调用waitpid:
waitpid(child_pid, (int*)0, WNOHANG);
如果子进程尚未退出,它将返回0;如果子进程已经结束,则返回child_pid。调用失败返回-1.失败的原因包括没有子进程,参数不合法等.....
注意wait等待第一个终止的子进程,而waitpid则可以指定等待特点的子进程。waitpid提供一个wiat的非阻塞版本,有时希望取得一个子进程的状态,但不想使父进程阻塞。waitpid提供一个这样的选项:WNOHANG,它可以使调用者不阻塞。如果一个没有任何子进程的进程调用wait函数,会立即出错返回。
        下面的代码演示了,wait函数的作用:
#include <stdio.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>int main(void){pid_t pid;char* msg;int k;int exit_code;printf("Stydy how to get exit code.\n");pid = fork();switch(pid){case 0:msg = "child process is running...";k=5;exit_code = 37;break;case -1:printf("Process creation failed.\n");exit(1);break;default:exit_code = 0;break;}if(pid != 0){int stat_val;pid_t child_pid;child_pid = wait(&stat_val);printf("child process has exited,pid = %d.\n",child_pid);if(WIFEXITED(stat_val))printf("child exited with code %d\n", WEXITSTATUS(stat_val));elseprintf("child exited abnormally.\n");}else{while(k-->0){puts(msg);sleep(1);}}exit(exit_code);}
程序执行结果如下:

        父进程调用wait函数后被挂起等待(如果此时打开另外一个终端,输入命令ps aux可以看到父进程的状态为S),直到子进程结束为止。子进程正常结束后,wait函数返回刚刚结束运行的子进程的pid。宏WEXITSTATUS获取子进程的退出码。














0 0
原创粉丝点击