【万字详解Linux系列】进程控制

文章目录

  • 一、环境变量
    • 1.基本概念
    • 2.常见的环境变量
      • (1)PATH
      • (2)HOME
      • (3)SHELL
      • (4)HISTSIZE
      • (5)SSH_TTY
    • 3.与环境变量相关的指令
    • 4.在代码中获取环境变量
    • (1)argc和argv
    • (2)envp
  • 二、进程地址空间
    • 1.不同数据的分布
    • 2.虚拟地址和物理地址
    • 3.页表
    • 4.为什么需要进程地址空间
  • 三、进程创建
    • 1.再识fork
      • (1)返回值
      • (2)fork失败
    • 2.写时拷贝
  • 四、进程终止
    • 1.进程退出码
    • 2.进程退出
      • (1)进程退出有三种情况
      • (2)查看进程退出码
      • (3)退出码描述
      • (4)进程常见退出情况
        • ①从main退出
        • ②调用exit
        • ③_exit
      • (5)进程异常退出
  • 五、进程等待
    • 1.进程等待的必要性
    • 2.代码实现进程等待
      • (1)wait
      • (2)waitpid
        • ①函数说明
        • ②函数调用
        • ④WNOHANG
        • ⑤创建多个进程
  • 感谢阅读,如有错误请批评指正


一、环境变量

Windows下的环境变量

【万字详解Linux系列】进程控制_第1张图片

Linux下的环境变量(用env命令查看)
【万字详解Linux系列】进程控制_第2张图片

1.基本概念

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

例如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是仍然可以链接成功,生成可执行程序,这就是因为有相关环境变量帮助编译器进行查找。

环境变量通常具有特殊用途,在系统当中通常具有全局特性。

2.常见的环境变量

(1)PATH

查看环境变量只需要在命令行中echo $环境变量名 即可(如果不带 $ 会被当做字符串打印)。
在这里插入图片描述

可以看到出现的是一组组用冒号分割的路径,Linux下执行命令时默认从左向右在各个PATH路径下查找要执行的命令并执行。

下图从左向右找到第二条路径时发现路径下有ls,然后执行之。
在这里插入图片描述
而为什么执行ls、pwd等等命令时前面不用带路径呢?因为直接执行命令时默认先到PATH下的路径找是否存在该命令,找到之后就执行,而PATH的路径是启动操作系统时默认生成的。


下面看一个例子:
【Linux小练习】进度条程序 中的可执行程序需要加./才能运行,因为它不在PATH路径中,而在当前目录。

【万字详解Linux系列】进程控制_第3张图片

但通过将该可执行程序拷贝(需要sudo权限)到任意一条PATH下的路径后即可直接执行,而不需要./(./本质其实是告诉操作系统要执行的命令在当前目录下)。

【万字详解Linux系列】进程控制_第4张图片

这样myproc也可以像ls、pwd等命令一样直接执行,这波操作以后相当于我自己写了一款应用,功能是一个进度条。

但是,上面是十分不推荐的做法,因为这样大概率会污染命令池(自己写的命令很强除外)。


(2)HOME

指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)。

【万字详解Linux系列】进程控制_第5张图片


(3)SHELL

显示当前Shell,它的值通常是/bin/bash。
在这里插入图片描述
shell跑起来之后就可以对命令行进行解释(执行命令行输入的指令)。


(4)HISTSIZE

用history可以查看之前在命令行输入的内容。
【万字详解Linux系列】进程控制_第6张图片
HISTSIZE就是保存历史命令数量的最大值。例如我的系统最多可以保存1000条历史命令。
在这里插入图片描述


(5)SSH_TTY

表示终端号。
【万字详解Linux系列】进程控制_第7张图片

3.与环境变量相关的指令

echo: 显示某个环境变量值(注意一定要加$)
export: 设置一个新的环境变量
env: 显示所有环境变量
unset: 清除环境变量
set: 显示本地定义的shell变量和环境变量

下面以先设置然后取消一个环境变量为例进行说明。
【万字详解Linux系列】进程控制_第8张图片


4.在代码中获取环境变量

首先要声明一点,main函数是可以带参数的。

C语言中main函数有三个参数,argc、argv和envp,envp环境变量,这里顺带提一下前两个参数,它们对理解Linux的命令行参数很有帮助(其实Linux的命令行参数就是通过它们实现的)。


(1)argc和argv

#include 
#include 

int main(int argc, char *argv[], char *envp[])
{
  int i = 0;
  for(; i < argc; i++)                                                                                                                                                 
  {
    printf("argv[%d] : %s\n", i, argv[i]);
  }
  return 0;
}

下面是直接执行可执行程序、带命令行参数执行可执行程序、执行Linux的指令。应该可以看出一些相似之处。
【万字详解Linux系列】进程控制_第9张图片

下图是对上面代码和命令行指令的部分解释。
【万字详解Linux系列】进程控制_第10张图片


下面再通过实现命令行参数的功能来进一步解释main函数的三个参数。

#include 
#include 
#include 
 
int main(int argc, char *argv[], char *envp[])
{
  if(argc == 2)//有一个以上命令行参数
  {
	  if(strcmp(argv[1], "-a") == 0)
	  {
	    printf("a : hello world!\n");
	  }
	  else if(strcmp(argv[1], "-b") == 0)
	  {
	    printf("b : hello Linux!\n");
	  }
	  else if(strcmp(argv[1], "-c") == 0)
	  {
	    printf("c : I love BUAA!\n");
	  }
	  else                                                                                                                                                                 
	  {
	    printf("d : hello default!\n");
	  }
  }
  return 0;
}

然后就可以像执行ls等命令一样实现各命令行参数的功能(这份代码只允许带一个参数,因为代码中设置argc==2)。

【万字详解Linux系列】进程控制_第11张图片
有了这个例子,读者应该对上面的内容理解的差不多了。

注意:上面的内容是C语言规定的,与系统无关,所以在Windows下也可同样实现。


(2)envp

envp的存储结构与argv差不多,但是没有参数说明envp的数量,所以可以以末尾的NULL来判断结束。
【万字详解Linux系列】进程控制_第12张图片
通过下面一段代码来使用envp:

#include 
#include 
 
int main(int argc, char *argv[], char *envp[])
{
  int i = 0;
  while(envp[i] != NULL)
  {
    printf("envp[%d] : %s\n", i, envp[i]);
    i++;                                                                                                                                                               
  }                                                                                                                                      
}

运行可发现打印出来的就是所有的环境变量,与env执行的结果相同。
【万字详解Linux系列】进程控制_第13张图片

环境变量是一个系统级别的全局变量,bash之下的所有进程都可以获取。


通过envp可以获取到环境变量,下面再介绍一种获取环境变量的方式。

可以通过libc库中定义的全局变量environ来了获取环境变量,environ指向环境变量表,它不包含在任何头文件中,所以在使用时要用extern声明。

#include  
#include 

int main()
{
    int i = 0;
    extern char** environ;
    for (i = 0; environ[i] != NULL; i++)
    {
        printf("%s\n", environ[i]);
    }
	return 0;
}

同样获取到了环境变量:
【万字详解Linux系列】进程控制_第14张图片


如果要在代码中获取单个环境变量,可以使用getenv函数。

【万字详解Linux系列】进程控制_第15张图片
以获取PATH的路径为例:

#include  
#include 

int main()
{
    printf("%s\n", getenv("PATH"));
	return 0;
}

可以看到,路径完全相同。
在这里插入图片描述


二、进程地址空间

1.不同数据的分布

【万字详解Linux系列】进程控制_第16张图片

下面自底向上打印个数据段变量的地址来验证:

#include 
#include 
#include 

int g_val;
int g_init_val = 100;

int main(int argc, char *argv[], char *envp[])
{
    printf("code addr:%p\n", main);//代码地址

    const char* str = "hello world";
    printf("read only:%p\n", str);//只读常量地址

    printf("init addr:%p\n", &g_init_val);//初始化全局变量地址
    printf("uninit addr:%p\n", &g_val);//未初始化全局变量地址

    int* p = (int*)malloc(10);
    printf("heap addr:%p\n", p);//堆的地址
    printf("stack addr:%p\n", &p);//栈的地址

    int i = 0;
    for (i = 0; i < argc; i++)
    {
        printf("args addr:%p\n", argv[i]);//命令行参数的地址
    }

    while (envp[i])
    {
        printf("env addr:%p\n", envp[i++]);//环境变量的地址
    }
    return 0;
}

运行结果如下:
【万字详解Linux系列】进程控制_第17张图片


2.虚拟地址和物理地址

先看一段代码:

#include 
#include 
#include 

int g_val = 100;

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //child
        g_val = 200;
        printf("child---pid:%d ppid:%d g_val:%d &g_val:%p\n", getpid(), getppid(), g_val, &g_val);
    }
    else if (id > 0)
    {
        //parent
        //由于不确定是id==0先执行还是id>0先执行,在这里sleep 2秒保证id==0先执行
        sleep(2);
        printf("parent---pid:%d ppid:%d g_val:%d &g_val:%p\n", getpid(), getppid(), g_val, &g_val);
    }
    else
    {
        ;
    }

	sleep(1);

    return 0;
}

结果如下:

在这里插入图片描述

按理说id==0先执行,g_ val被改为200而且两个判断条件内显然g_ val地址相同,所以打印出来的值应该都是200,但parent里的g_ val是100,为什么呢?

通过页表可以更详细地解释上面的现象。请往下看。


3.页表

页表本质上就是将虚拟地址映射到物理内存中,通过页表可以更详细地解释上面的现象。

因为上面g_ val的地址0x60104c 不是物理地址,而是虚拟地址。 实际上,在语言层面上见到的地址都是虚拟地址,物理地址一概看不到,由操作系统统一管理。上面g_ val的两个虚拟地址虽然相同,但经过页表转化后的物理地址是不一样的,所以它们的值不一样也就很正常了。

上面进程地址的分布图其实都对应的是虚拟地址。

详见下图。
【万字详解Linux系列】进程控制_第18张图片


4.为什么需要进程地址空间

由于页表是将进程地址空间的地址映射到物理内存,所以它们是一起使用的。

有了进程地址空间,就不会再有任何系统级别的越界问题存在。

如果现在非法访问某地址,访问到页表时,由于从前没有这样的访问,也就不存在这样的映射,所以首先页表不会允许继续访问;其次,访问时能看到的只有该进程的页表映射到的物理内存,其他的物理内存对该进程是不可见的,也就没有机会再访问到。


三、进程创建

1.再识fork

在进程概念 中初步介绍了fork的使用,这里再进一步介绍fork。

(1)返回值

为何要给子进程返回0,而给父进程返回子进程的pid?

因为一个父进程可能有多个子进程,为了找到各个子进程,就需要知道各个子进程的pid,所以给父进程返回子进程的pid;
而一个子进程永远只有一个父进程,所以子进程不需要返回pid。

进程调用fork,当控制转移到内核中的fork代码后,内核做的事情有:分配新的内存块和内核数据结构给子进程、将父进程部分数据结构内容拷贝至子进程、添加子进程到系统进程列表当中、fork返回并开始调度器调度。


(2)fork失败

在系统层面,当系统中有太多进程时,内存资源可能会不够用,这时就可能会fork失败。


2.写时拷贝

通常情况下父子进程的代码是共享的,父子进程在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自拷贝一份副本。

(下图中以子进程写入为例)

【万字详解Linux系列】进程控制_第19张图片

那么父子进程的数据为什么不在创建子进程时就全部分开呢?因为子进程不一定会修改父进程的所有数据,写时拷贝只在修改数据时发生,不会浪费任何内存。

之前谈论的都是数据,那么代码有没有写时拷贝呢?事实上是有的,但这里先不深入讨论。


四、进程终止

进程终止后,操作系统会释放曾经申请的数据结构(mm_struct、task_struct等等)、释放申请的内存、从各种数据结构中移除与子进程相关的内容。

1.进程退出码

只有main函数的返回值才是进程退出码,其它函数的返回值不是进程退出码。

在C/C++里,main函数最后总会return 0,为什么要有这个语句呢?
从语言角度来看,因为main函数的返回值要求是int类型,从操作系统角度来看,可以从进程退出码得到一定的信息。

那么return 1、2、3、-1、-2、100……可以吗?答案是可以。

这两个问题只是个引子,具体答案请往下看。


2.进程退出

(1)进程退出有三种情况

代码跑完且结果对、代码跑完但结果不对、代码没跑完时程序退出(进程崩溃后错误)。


(2)查看进程退出码

可以用echo $?来查看最近一次进程退出码。

随便运行一个正常的代码,结束后查看到进程退出码为0。

#include 

int main()
{
	int a = 0;
	int b = 0;
	int c = a + b;
	return 0;
}

在这里插入图片描述


如果将main函数的返回值修改为10,则查看到的进程退出码也就变为10。

#include 

int main()
{
	int a = 0;
	int b = 0;
	int c = a + b;
	return 10;
}

在这里插入图片描述

再查看一次进程退出码,发现结果是0,因为对第二次echo $?来说,最近一次运行的进程是第一次echo $?,这显然是正常运行的,所以返回0.
【万字详解Linux系列】进程控制_第20张图片


下面是以系统的指令ls为例测试进程退出码。

【万字详解Linux系列】进程控制_第21张图片


(3)退出码描述

退出码的含义是人为定义的,但0一般都表示成功,非0表示失败,每种退出码都有对应的字符串含义,帮助用户确认任务成功或确认任务失败的原因。

下面以strerror(还有perror等等)为例查看(部分)退出码描述。

#include 
#include 

int main()
{
	int i = 0;
	for (i = 0; i <= 100; i++)//这里只是部分退出码描述,不是全部
	{
		printf("%d : %s\n", i, strerror(i));
	}
	return 0;
}

可以看到退出码描述非常多。
【万字详解Linux系列】进程控制_第22张图片


(4)进程常见退出情况

①从main退出

从main函数退出是最常见的了,上面也举了许多例子,不再赘述。


②调用exit

在这里插入图片描述

void exit(int status);中status就是进程退出码,等价于main函数的返回值。

#include   
#include   
#include   
                                                                                                                                                                      
int main()                              
{                                       
  printf("exit\n");                     
  exit(12);                                                                                                                          
}

可以看到查看进程退出码时是12,也即调用exit时传入的值。
在这里插入图片描述


exit函数和main函数的返回值在进程退出码上没有区别,但是exit在任何位置调用都可以结束进程

#include 
#include 
#include 

int test()
{
	printf("test!\n");
	exit(12);

	return 1;
}

int main()
{
	test();
	printf("process is not done!\n");
	return 0;
}

运行结果如下。可以看到首先没有打印process is not done!,说明在程序运行到test就结束了,实际上连test内的return 1都没有执行,只要遇到exit立即退出,而进程退出码也是exit传入的12,而不是main函数的return 0。
【万字详解Linux系列】进程控制_第23张图片


③_exit

_exit与exit相似但也有些不同。
【万字详解Linux系列】进程控制_第24张图片
只把上面代码中的exit改为_exit。

#include 
#include 
#include 
#include 

int test()
{
	printf("test!\n");
	_exit(12);//修改

	return 1;
}

int main()
{
	test();
	printf("process is not done!\n");
	return 0;
}

结果与上面exit相同。
在这里插入图片描述


exit与_exit区别在于exit在进程退出时会释放进程曾经占用的资源(比如缓冲区),而_exit直接终止进程,不会做任何首尾。

#include 
#include 
#include 
#include 

int main()
{
	printf("hello world");
	sleep(3);
	exit(10);
	return 0;
}

结果如下,hello world被打印出来。
【万字详解Linux系列】进程控制_第25张图片


下面使用_exit。

#include 
#include 
#include 
#include 

int main()
{
	printf("hello world");
	sleep(3);
	_exit(10);//修改
	return 0;
}

可以看到hello world没有打印出来。
【万字详解Linux系列】进程控制_第26张图片


(5)进程异常退出

比如一个死循环的进程,如果不手动退出它永远不会停下来,这时通过Ctrl+C使它停下来对进程来说就是异常退出。

问题是进程异常退出后得到的“进程退出码“”还有意义吗?

答案是没有意义,因为既然进程异常退出,它一定没有走到main函数的return值或是exit的位置,那么它返回的值就不是进程退出码,所以是没有意义的。


五、进程等待

1.进程等待的必要性

之前讲过,子进程退出,父进程如果不管不顾,就可能产生“僵尸进程”(进程变成Z状态),而这种进程又会造成内存泄漏。

另外,进程一旦变成僵尸状态,那就不会被杀死(即使是kill -9也无能为力,因为没有办法杀死一个已经死去的进程)。

这时就需要由父进程回收子进程的资源并获取子进程的退出信息。

2.代码实现进程等待

用到的是wait和waitpid函数,下面依次来说明。
【万字详解Linux系列】进程控制_第27张图片

(1)wait

wait成功则返回被等待进程的pid,失败则返回-1。传入的参数属于输出型参数,可以获取子进程退出的状态,不需要时可设为NULL,这个参数在waitpid那里说明。

一般需要保证子进程先于父进程结束,因为父进程需要处理子进程结束后的信息。

#include 
#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //child
        int count = 3;
        while (count--)
        {
            printf("I am a child, pid:%d ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
    }
    else
    {
        //father
        printf("I am a father, pid:%d ppid:%d\n", getpid(), getppid());
        pid_t ret = wait(NULL);
        if (ret >= 0)
        {
            printf("wait child success, child pid:%d\n", ret);
        }
        printf("father running ... \n");
        sleep(10);
    }
    return 0;
}

现象及说明见下图:
【万字详解Linux系列】进程控制_第28张图片

在子进程运行期间,父进程走到wait后,父进程什么都不做,一直等子进程退出,这时父进程的状态叫做阻塞等待。


(2)waitpid

①函数说明

pid_ t waitpid(pid_t pid, int *status, int options);

返回值: 正常返回时waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:
①pid:若pid设置为-1,则等待任意一个子进程(与wait等效)。若pid>0,等待其进程ID与pid相等的子进程(即等待指定的子进程)。

②status: 下面是#define的两个宏
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(可以查看进程是否正常退出,本质是查看信号)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

③options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待(不会进程阻塞,而是继续去执行父进程的代码)。若正常结束,则返回该子进程的ID。


②函数调用

进程等待成功只能说明子进程退出了,但子进程运行情况如何,则可以通过status来判断。

#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //child
        int count = 3;
        while (count--)
        {
            printf("I am a child, pid:%d ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(11);//随便设置一个奇怪的退出码
    }
    else
    {
        //father
        int status = 0;
        printf("I am a father, pid:%d ppid:%d\n", getpid(), getppid());
        //status由于是传地址,所以waitpid里对它的修改在这里是可见的
        //option暂时先传入0
        pid_t ret = waitpid(id, &status, 0);
        if (ret >= 0)
        {
            printf("wait child success, child pid:%d\n", ret);
            printf("status : %d\n", status);
        }
    }
    return 0;
}

结果如下:
【万字详解Linux系列】进程控制_第29张图片


status是退出结果(不是退出码),下面介绍它的构成。

【万字详解Linux系列】进程控制_第30张图片

下面获取status中包含的退出码和信号来具体看一下。

#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //child
        int count = 3;
        while (count--)
        {
            printf("I am a child, pid:%d ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(11);//随便设置一个奇怪的退出码
    }
    else
    {
        //father
        int status = 0;
        printf("I am a father, pid:%d ppid:%d\n", getpid(), getppid());
        pid_t ret = waitpid(id, &status, 0);
        if (ret >= 0)
        {
            printf("wait child success, child pid:%d\n", ret);
            printf("status : %d\n", status);
            printf("child exit code : %d\n", (status >> 8) & 0xFF);//打印进程退出码
            printf("child get signal : %d\n", status & 0x7F);//打印信号
        }
    }
    return 0;
}

【万字详解Linux系列】进程控制_第31张图片


下面制造一个崩溃的错误使进程收到信号。

#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //child
        int count = 3;
        while (count--)
        {
            printf("I am a child, pid:%d ppid:%d\n", getpid(), getppid());
            sleep(1);
            if(count == 1)
            	count = 1 / 0;//制造一个除零错误
        }
        exit(11);//随便设置一个奇怪的退出码
    }
    else
    {
        //father
        int status = 0;
        printf("I am a father, pid:%d ppid:%d\n", getpid(), getppid());
        pid_t ret = waitpid(id, &status, 0);
        if (ret >= 0)
        {
            printf("wait child success, child pid:%d\n", ret);
            printf("status : %d\n", status);
            printf("child exit code : %d\n", (status >> 8) & 0xFF);//打印进程退出码
            printf("child get signal : %d\n", status & 0x7F);//打印信号
        }
    }
    return 0;
}

结果如下:
【万字详解Linux系列】进程控制_第32张图片


上面的位操作其实使用时可以用宏直接替换,下面写一份用宏替换的代码(其实更推荐用宏,但这里为了了解本质一直用的是位操作)

#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //child
        int count = 3;
        while (count--)
        {
            printf("I am a child, pid:%d ppid:%d\n", getpid(), getppid());
            sleep(1);
            if(count == 1)
            	count = 1 / 0;//制造一个除零错误
        }
        exit(11);//随便设置一个奇怪的退出码
    }
    else
    {
        //father
        int status = 0;
        printf("I am a father, pid:%d ppid:%d\n", getpid(), getppid());
        pid_t ret = waitpid(id, &status, 0);
        if (ret >= 0)
        {
            printf("wait child success, child pid:%d\n", ret);
            printf("status : %d\n", status);
            if(WIFEXITED(status))
            {
            	printf("child get signal : %d,child exit code : %d\n", WIFEXITED(status), WEXITSTATUS(status));//用宏
            } 
        }
    }
    return 0;
}

结果与使用位操作相同。
【万字详解Linux系列】进程控制_第33张图片


④WNOHANG

如果第三个参数option被设置为WNOHANG,如果子进程的状态没有改变(一般是正在运行,没有退出)就返回0,否则返回-1。

这时父进程在运行到waitpid时不会一直等待知道子进程结束(不阻塞),而是会立即拿到返回值继续运行父进程自己的代码。

#include 
#include 
#include 
#include 
#include 

int main()
{
	pid_t id = fork();
	if (id == 0)
	{
		//child
		int count = 3;
		while (count--)
		{
			printf("I am a child, pid:%d, ppid:%d\n", getpid(), getppid());
			sleep(1);
		}
		exit(1);
	}

	//基于非阻塞接口的轮询检测
	while (1)//father
	{
		int status = 0;
		pid_t ret = waitpid(id, &status, WNOHANG);
		if (ret > 0)
		{
			printf("wait success!\n");
			printf("exit code:%d\n", WEXITSTATUS(status));
			break;//等待成功,退出循环
		}
		else if (ret == 0)
		{
			//子进程没有退出
			//如果没有WNOHANG父进程会一直等待直到子进程退出
			printf("father do other things!\n");
			sleep(1);
		}
		else//ret < 0
		{
			printf("waitpid error!");
			break;//等待失败,退出循环
		}
		printf("ret:%d\n", ret);
	}
	
	return 0;
}

【万字详解Linux系列】进程控制_第34张图片


⑤创建多个进程

#include 
#include 
#include 
#include 

int main()
{
    pid_t ids[5];
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            //child
            int count = 3;
            while (count--)
            {
                printf("child do something, pid:%d, ppid:%d\n", getpid(), getppid());
                sleep(1);
            }
            exit(i);//子进程终止
        }
        //father
        ids[i] = id;
    }

    //ids内有10个子进程的pid
    for (i = 0; i < 5; i++)
    {
        int status = 0;
        pid_t ret = waitpid(ids[count], &status, 0);
        if (ret >= 0)
        {
			printf("child%d\n", i);  
            printf("status : %d  ", status);    
            printf("child exit code : %d  ", (status >> 8) & 0xFF);//打印进程退出码  
 	        printf("child get signal : %d\n", status & 0x7F);//打印信号
        }
    }
    return 0;
}

结果如下,创建了5个子进程,但暂时创建多进程不常用:
【万字详解Linux系列】进程控制_第35张图片


感谢阅读,如有错误请批评指正

你可能感兴趣的:(万字详解Linux系列,linux,运维,服务器)