Linux:历程地点空间、历程控制(一.历程创建、历程终止、历程等待) ...

打印 上一主题 下一主题

主题 1851|帖子 1851|积分 5553

上次介绍了环境变量:Linux:历程概念(四.main函数的参数、环境变量及其相关操作)


  

1.程序地点空间


   牵涉到内存,肯定有事这张图啦。这次我们写段代码验证一下
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int a;
  4. int init_a = 0;
  5. int main()
  6. {
  7.         printf("code:%p\n", main);//代码
  8.         printf("uninit data:%p\n", &a);//未初始化变量
  9.         printf("init data:%p\n", &init_a);//初始化变量
  10.         char* arr = (char*)malloc(10);
  11.         printf("heap:%p\n", arr);//堆
  12.         printf("stack:%p\n", &arr);//栈
  13.         return 0;
  14. }
复制代码

知识点总结


  • 字符串常量

    • 字符串常量通常存储在高地点,其地点是固定的,不可更改。
    • 字符串的下标从0到n一直在增长,即字符串中的每个字符在内存中是连续存储的。

  • main函数地点

    • main 函数地点通常是程序中最底部的地点,由于它是程序的入口点。

  • 初始化数据和未初始化数据

    • 初始化数据(如全局变量、静态变量)存储在比 main 函数地点高的位置,由于它们在程序启动时必要被初始化。
    • 未初始化数据(如全局未初始化变量、静态变量)存储在比初始化数据更高的位置,由于它们在程序启动时不必要被初始化。

  • 堆区

    • 堆区是用于动态内存分配的地区,在堆区中存储动态分配的内存。
    • 堆区是向上增长的,即分配的内存地点逐渐增长,地点比未初始化数据高。

  • 栈区

    • 栈区用于存储函数的参数值、局部变量和函数调用返回地点等信息。
    • 栈区是向下增长的,即栈顶地点逐渐减小,团体比堆区的地点要高。

上述空间排布结构是在内存吗?(历程地点空间引入)

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. int main()
  6. {
  7.     pid_t id = fork();
  8.     if (id == 0)
  9.     {
  10.         int cnt = 0;
  11.         while (1)
  12.         {
  13.             printf("child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
  14.             sleep(1);
  15.             cnt++;
  16.             if (cnt == 3)
  17.             {
  18.                 g_val = 200;
  19.                 printf("child change g_val: 100->200\n");
  20.             }
  21.         }
  22.     }
  23.     else
  24.     {
  25.         while (1)
  26.         {
  27.             printf("father, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
  28.             sleep(1);
  29.         }
  30.     }
  31.     return 0;
  32. }
复制代码

   我们都知道父子历程共享代码段,那么一开始二者访问的g_vla值和地点均相同
  后体面历程中改变g_val的值后,二者值不同,但是地点照旧一样
  

  • 这就说明,我们看到的这个地点绝对不是物理地点。我们程这些地点为捏造地点/线性地点
  • 上面那些空间排布结构也不是在物理内存里。我们称之为:历程地点空间
  
2.历程地点空间


历程地点空间是操作系统中一个告急的概念,它形貌了一个历程在运行时所能访问的捏造地点范围。每个历程都有自己独立的地点空间,使得多个历程可以同时运行而互相不干扰
   地点空间是指一个历程可以使用的内存范围,通常由连续的地点构成。对于32位系统,历程地点空间通常是从0到4GB,这个范围内包含了代码段、数据段、堆、栈等部分,用于存放程序的指令、数据以及动态分配的内存(就是我们上面那个图)
  每个历程都有自己独立的地点空间,使得历程间可以互相隔离,不会相互干扰。在这个地点空间内,操作系统会进行地点映射,将历程的捏造地点映射到物理内存上,以实现对内存的访问和管理。
  当一个历程被创建时,操作系统会为该历程分配一块内存空间,用来存放历程的地点空间。这个地点空间是捏造的,由于它并不直接对应物理内存中的连续空间,而是通过页表和页表项来映射到物理内存中的不同位置。
  页表(Page Table)是操作系统中用于管理捏造内存的告急数据结构之一。它将历程的捏造地点映射到物理内存中的实际地点,实现了捏造内存的地点转换和管理
明确几个点


  • 程序与历程的区别
           在操作系统中,程序和历程是两个不同的概念。
        程序(Program)是一组指令的聚集,是静态的代码文件,通常以可执行文件的形式存在。程序自己并不具有执行本事,只有当程序加载到内存中,并由操作系统创建一个历程来执行时,程序的指令才会被解释和执行。程序可以被多个历程同时执行,由于每个历程都有自己独立的地点空间,程序的指令在不同历程中是相互隔离的。
        历程(Process)是操作系统中的一个执行实体,是程序在运行过程中的一个实例。历程包含了程序的代码、数据、堆栈等信息,以及操作系统为其分配的资源。每个历程都有自己独立的地点空间和执行流,可以独立运行、调治和管理。历程是操作系统中的基本执行单位,是程序在执行过程中的动态表现。
        可以说历程是运行和执行的主体,程序只是历程执行的指令聚集。程序必要被加载到内存中,由历程来执行,历程才是真正执行代码、管理资源、与其他历程交互的实体。
  •        历程地点空间不直接生存代码和数据自己,而是提供了一种逻辑上的组织和管理方式,用于标识和访问这些代码和数据在物理内存中的位置。
        当程序执行时,CPU会根据指令从历程地点空间中读代替码和数据,并进行相应的处理。这些代码和数据实际上是存储在物理内存中的,但通过地点映射机制,它们被映射到了历程地点空间中的对应位置,使得程序可以方便地访问和操作这些内容。
        当我们说历程地点空间用于存储“不同类型的数据”时,实际上是指它组织和标识了这些数据和代码在物理内存中的位置。历程地点空间中的每个部分都通过捏造地点来标识,这些捏造地点在运行时会被操作系统和硬件转换为实际的物理地点,以便访问对应的内存位置
        因此,可以说历程地点空间是用于组织和管理代码和数据的捏造内存地区,而代码和数据自己实际存储在物理内存中。历程地点空间提供了一个抽象的视图,使得程序可以像访问内存一样访问代码和数据,而无需关心它们的实际存储位置。
  • 捏造地点并不是真实存在的物理内存地点,而是逻辑上的地点空间。捏造地点空间是操作系统为每个历程提供的一个假象,使得历程仿佛拥有整个内存空间


  • 历程地点空间可以理解成是一套规范,大概是一套边界,可以方便我们系统进行编辑性查抄的一个东西
  • 历程地点空间并不会把每个捏造地点都显式地存储起来,而是存储了地点空间的区间范围以及相关的管理信息。这些区间范围定义了捏造地点的边界,以及每个区间对应的内存属性(如可读、可写、可执行等)
           历程地点空间不会存储每个使用的捏造地点,而是会维护每个内存范围的开始与竣事地点
        当历程必要访问某个捏造地点时,操作系统会根据该地点所属的内存范围,查找相应的页表或其他内存管理数据结构,以确定该地点对应的物理地点
  • 历程地点空间中的捏造地点是通过程序计数器、指令集和其他相关机制来使用的。当CPU执行历程中的指令时,它会根据程序计数器的值来获取下一条要执行的指令的捏造地点
历程地点空间实质

代码和数据实际上是存储在物理内存中的,而历程空间(或称为捏造地点空间)里存储的是代码和数据的捏造地点。这些捏造地点通过页表等机制映射到物理内存中的实际地点。
每个历程都有自己的捏造地点空间,这个空间是逻辑上连续的,但并不肯定在物理内存中连续。操作系统负责维护页表,将捏造地点转换为物理地点,从而实现历程对内存的访问。
   操作系统肯定也要对历程地点空间进行管理,那就说明也必要:先形貌再组织
  历程地点空间是数据结构,详细到历程中就是特定数据结构的对象。必要注意的是:这个结构体里不生存代码和数据
  

图示过程


2.1历程地点空间意义

地点空间和页表的结合是操作系统中实现捏造内存管理的关键机制,它们的存在有助于解耦历程管理和内存管理,并提供了保护内存安全的告急本事。

  • 统一的内存视图: 地点空间和页表的结合使得每个历程都拥有一个统一的内存视图,无论实际物理内存的情况如何,历程都可以将内存看作是一段连续的地点空间。这种统一的内存视图简化了程序的编写和调试过程,程序员可以使用相对地点来编写程序,而不必担心物理内存的实际情况。
  • 解耦历程管理和内存管理: 地点空间和页表的存在使得历程管理和内存管理可以相互独立地进行,历程的创建、烧毁和切换与物理内存的分配、接纳和调治等操作是相互独立的。这种解耦合的设计有助于提高系统的模块化和可扩展性,使得系统更轻易进行维护和升级。
  • 保护内存安全: 页表是保护内存安全的告急本事之一,它通过设置页面的访问权限和保护位,可以防止程序对内存的非法访问和修改。例如,可以将某些页面设置为只读或只执行,防止程序对其进行写操作或执行恶意代码,从而提高了系统的安全性和稳定性。
  • 内存管理的有效性: 通过地点空间和页表,操作系统可以实现捏造内存管理,将逻辑地点映射到物理内存中,实现了内存的动态分配和管理。通过页面置换算法和页面驻留计谋,可以实现对内存资源的有效利用,提高了系统的性能和服从。
  • 地点空间共享和隔离: 地点空间的存在允许多个历程共享相同的代码和数据,从而节流内存空间,提高系统的资源利用率。同时,地点空间的隔离性保证了每个历程都拥有独立的地点空间,互不干扰,确保了系统的稳定性和安全性。

3.创建历程

3.1fork()函数创建子历程补充

   我们之前已经讲了在代码里可以使用fork()函数来。创建子历程规则是:子历程与父历程共享代码,写时拷贝
  历程调用fork,当控制转移到内核中的fork代码后,内核做:
  

  • 分配新的内存块和内核数据结构给子历程
  • 将父历程部分数据结构内容拷贝至子历程
  • 添加子历程到系统历程列表当中
  • fork()函数返回,开始调治器调治
  当一个历程调用fork之后,就有两个二进制代码相同的历程。而且它们都运行到相同的地方。但每个历程都将可以开始它们自己的路程

  • 共享代码怎么做到的?
           子历程创建后,会拷贝父历程的历程地点空间和页表内容(相当于浅拷贝),页表内容相同。那么映射到的物理内存也是相同的,这样就做到了共享代码
写时拷贝

   通常,父子代码共享,父子再不写入时,数据也是共享的,当恣意一方试图写入,便以写时拷贝的方式各自一份副本
  Linux系统中,当使用fork()系统调用创建子历程时,子历程会继承父历程的地点空间的一个副本。但实际上,在fork()之后到子历程开始写数据之前,父历程和子历程所共享的是同一个物理内存页面。只有当其中一个历程实验修改写入时,操作系统才会进行页面复制,确保每个历程都有自己的数据副本,从而避免了不必要的内存复制开销

页表除了有一系列对应的捏造地点和物理地点以外,还有一列代表权限(针对物理地点的内容的权限)
详细来说,权限字段通常包含以下几种权限:

  • 读权限(r):当某个页表项的读权限被设置时,拥有该页表项的历程可以读取该页面上的数据。如果读权限未被设置,任何试图读取该页面的操作都会引发非常或错误。
  • 写权限(w):写权限决定了历程是否可以修改页面上的数据。如果页表项的写权限被设置,历程可以对该页面进行写操作。否则,任何写操作都会被阻止。
除了读和写权限外,页表的权限字段还可能包含其他类型的权限,例如执行权限(x),它决定了历程是否可以在该页面上执行代码。在某些系统中,还可能存在特殊的权限字段,如用于控制页面共享、缓存计谋等的字段。
   所以上面写时拷贝的过程里:可以看到在修改内容之前,数据段里的权限也都是只读,这不对吧?(由于,全局变量我们是可以修改的啊)这是在创建子历程后,数据段的页表映射权限由rw权限变为r
  为什么要改啊:改后,如果我们实验写入,会发生错误,这时操作系统就会来完成写入拷贝,又发现你是数据段的本该可以写入,就又把必要写入的历程对应的页表映射由r权限改为rw了
  
4.历程终止

4.1历程退出场景

   

  • 代码运行完毕,效果正确
  • 代码运行完毕,效果不正确
  • 代码非常终止
  退出码

   main函数的返回值通常被称为历程退出码或返回状态码。在C、C++等编程语言中,main函数是程序的入口点,当程序执行完毕后,main函数会返回一个整数值给操作系统,这个整数值就是历程退出码。操作系统会根据这个退出码来判定程序是正常竣事照旧出现了某种错误。
  我们自己写main函数时,总是写一个return 0
  

  • 返回0表示程序乐成执行
  • 非0值表示出现了某种错误。详细的非0值可能由程序员定义,用于表示不同的错误类型或状态。
    Linux系统中,你可以使用echo $?下令来检察上一个执行的下令或历程的退出码
  

   但是光看一个数字,我们怎么能知道错误的原因呢? 这就必要把错误码转换为错误形貌
  错误码就是函数的
  strerror()函数是一个C库函数,用于将错误代码转换为对应的错误信息字符串。它接受一个整数参数errno,返回一个指向错误信息字符串的指针。strerror函数的在头文件string.h中,
errno是一个全局变量,用于在C语言中表示发生错误时的错误码。当函数或系统调用发生错误时,errno会被设置为相应的错误码,以便程序可以根据错误码进行得当的错误处理。error是近来一次函数进行调用的返回值
  1. char *strerror(int errnum);
复制代码
其中,errnum参数是一个整数,代表特定的错误码。strerror函数会根据错误码在系统的错误码表中查找对应的错误信息,并将其作为字符串返回。
历程出现非常

   历程出现非常说明历程收到了非常信号,每种信号都有自己的编号(宏编号),而不同的信号编号能表明非常的原因
  kill -l 下令在 Unix 和 Linux 系统中用于列出全部可用的信号。执行这个下令将表现系统支持的全部信号的列表以及它们的编号。这对于相识不同信号的含义和用途非常有效,特殊是在处理历程和历程间通讯时。
下面是一个 kill -l 下令的典型输出示例:
  1. $ kill -l  
  2. 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL  
  3. 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE  
  4. 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2  
  5. 13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT  
  6. 17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP  
  7. 21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU  
  8. 25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH  
  9. 29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN  
  10. 35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  ...  
  11. ...  
  12. 63) SIGRTMAX-1  64) SIGRTMAX
复制代码
一些常见的信号及其用途包括:


  • SIGTERM:请求历程终止。历程可以捕捉这个信号并清算资源后正常退出。
  • SIGINT:通常由用户按下 Ctrl+C 产生,用于停止前台历程。
  • SIGKILL:欺压终止历程,不能被历程捕捉或忽略。
  • SIGHUP:当控制终端(controlling terminal)被关闭时发送给历程,常用于让历程重新读取配置文件。
4.2历程常见退出方法

4.2.1正常退出


  • 正常的从main()函数返回
  • 调用exit()函数
          
    1. #include <unistd.h>
    2. void exit(int status);
    复制代码
       参数status定义了历程的终止状态,也就是程序的退出码用于表示程序的执行状态,并帮助调用程序理解程序竣事的原因

    • 在历程代码中,恣意地方调用exit()函数都表示历程退出(不肯定非要在main()函数里)
       
    1. #include<stdio.h>
    2. #include<unistd.h>
    3. #include<stdlib.h>
    4. void Print()
    5. {
    6.     printf("Now it's calling the Print function\n");
    7.     exit(10);
    8. }
    9. int main()
    10. {
    11.     while(1)
    12.     {
    13.         printf("I'm a process:%d\n",getpid());
    14.         sleep(3);
    15.         Print();
    16.     }
    17.     return 0;
    18. }
    复制代码
       

       
  • 调用_exit()函数
           与exit()函数的不同:

    • exit()是一个标准C库函数,而_exit()通常被视为一个系统调用(可以知道exit底层肯定封装了__exti函数)
    • 调用exit()时,它会执行一些清算工作,包括检测历程打开的文件情况,并将处于文件缓冲区的内容写入到文件中,然后才退出
    • 而_exit()则直接退出,不会执行这些清算工作,也不会将缓冲区中的内容写入文件
       
4.2.2非常退出

   使用ctrl + c,能使非常信号终止
  历程终极执行情况

Linux系统中,任何历程终极执行完毕后都会返回一个状态码,这个状态码通常被称为“退出码”或“返回码”(exit code)。这个退出码是一个整数,用于表示历程执行的效果或状态。根据惯例,退出码0通常表示乐成,而非零值表示出现了某种错误。
Linux的上下文中,我们通常讨论的是“信号”(signal),这些信号用于在历程之间通报信息或通知历程发生了某种变乱(如停止、终止等)


  • 退出码(exit code):一个整数,用于表示历程执行的效果或状态。0通常表示乐成,非零值表示错误或非常情况。
  • 信号(signal):用于在历程之间通报信息或通知历程发生了某种变乱的机制。历程可以发送和吸收信号,并对某些信号进行特定的处理。(就是我们上面讲的历程出现非常时收到的非常信号
4.3 OS会做什么

当历程创建和历程终止时,操作系统会执行一系列的操作来确保系统的稳定性和资源管理的有效性。
历程创建时:


  • 资源分配:操作系统为新历程分配必要的资源,如内存空间、文件形貌符、打开的文件等。
  • 复制父历程数据:新创建的子历程是父历程的副本,所以操作系统会复制父历程的部分数据结构内容到子历程,包括代码、数据、堆、栈等内容。然而,这种复制通常是“写时复制”(Copy-On-Write)的,即实际的物理内存页并不会立即复制,而是在子历程初次对这些页进行修改时才会进行复制。
  • 设置历程ID:操作系统为每个新历程分配一个唯一的历程ID(PID),用于在系统中唯一标识该历程。
  • 添加到历程列表:新创建的历程会被添加到系统的历程列表中,以便操作系统可以对其进行管理和调治。
  • 执行fork系统调用:当父历程调用fork()函数时,操作系统会处理这个系统调用,完成上述操作,并返回相应的值给父历程和子历程。父历程收到的是子历程的PID,而子历程收到的是0。
历程终止时:


  • 执行清算工作:历程在终止前会执行一些清算工作,比如关闭打开的文件、释放占用的内存等。如果历程是正常终止(比如调用exit()函数),操作系统还会捕捉历程的退出状态码。
  • 接纳资源:操作系统接纳历程占用的全部资源,包括内存、文件形貌符、信号处理程序等。
  • 处理僵尸历程:当一个历程终止时,它并不会立即从系统中消失。相反,它会变成一个僵尸历程(Zombie Process),直到其父历程调用wait()或waitpid()系统调用来接纳它。操作系统会维护这些僵尸历程的信息,直到它们被父历程接纳。
  • 更新历程列表:操作系统会从历程列表中移除已终止的历程。

5.历程等待

5.1必要性



  • 在Unix/Linux系统中,当子历程退出时,它的历程形貌符仍然保留在系统中,直到父历程通过某种方式获取其退出状态。这个已经退出但历程形貌符仍然保留在系统中的历程就被称为“僵尸历程”
  • 一旦历程变成僵尸状态,纵然是使用kill -9这样的欺压终止下令也无法直接“杀死”它。由于僵尸历程自己已经终止,只是其退出状态还没有被父历程读取
  • 而且父历程派给子历程的任务完成的如何,我们必要知道。如,子历程运行完成,效果对照旧不对, 大概是否正常退出
  • 为了接纳子历程的资源并获取其退出信息,父历程必要调用wait()或waitpid()系统调用(进行历程等待)。这些调用会阻塞父历程,直到有子历程退出,并返回已退出子历程的PID和退出状态
5.2历程等待的方法

5.2.1 wait()方法

wait 方法在Linux 编程中是一个告急的系统调用,它主要用于监视先前启动的历程,并根据被等待的历程的运行效果返回相应的 Exit 状态。在父历程中,wait 方法常被用来接纳子历程的资源并获取子历程的退出信息,从而避免产生僵尸历程。
wait 函数允许父历程等待其子历程竣事,并可以获取子历程的退出状态。在C语言中的用法和参数:
函数原型
  1. #include <sys/types.h>  
  2. #include <sys/wait.h>  
  3.   
  4. pid_t wait(int *status);
复制代码
参数status:这是一个指向整数的指针,用于存储子历程的退出状态。如果父历程不关心子历程的退出状态,可以将这个参数设为 NULL。
返回值


  • 返回值大于零时乐成,返回已终止子历程的历程ID。
  • 失败时,返回 -1,并设置全局变量 errno 以指示错误原因。
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <sys/types.h>  
  5. #include <sys/wait.h>
  6. int main()
  7. {
  8.         pid_t id = fork();//创建子进程
  9.         if (id == 0)
  10.         {
  11.                 //这里面是子进程
  12.                 int count = 5;
  13.                 while (count--)
  14.                 {
  15.                         printf("child is running. pid:%d , ppid:%d\n", getpid(), getppid());
  16.                         sleep(1);
  17.                         //这里循环5秒
  18.                 }
  19.                 printf("子进程将退出,马上就变成僵尸进程\n");
  20.                 exit(0);//子进程退出了
  21.         }
  22.         //这里是父进程
  23.         printf("父进程休眠\n");
  24.         sleep(10);
  25.         printf("父进程开始回收了\n");
  26.         pid_t rid = wait(NULL);//让父进程进程阻塞等待
  27.         if (rid > 0)
  28.         {
  29.                 printf("wait successfully, rid:%d\n", rid);
  30.         }
  31.         printf("父进程回收了\n");
  32.         sleep(5);
  33.         return 0;
  34. }
复制代码
  代码一共15秒
  

  • 0~5秒内:子历程与父历程都存在,5秒后子历程竣事
  • 5~10秒内:父历程正常运行,子历程在僵尸。10秒后父历程开始接纳
  • 10~15秒:父历程正常运行,15秒后父历程竣事
  

5.2.2waitpid()方法

waitpid 是 Unix 和 Linux 系统编程中用于等待子历程竣事并获取其状态的系统调用。它的原型如下:
  1. pid_t waitpid(pid_t pid, int *status, int options);
复制代码
返回值


  • 当正常返回的时候waitpid返接纳集到的子历程的历程ID;
  • 如果 options 参数中设置了 WNOHANG,而且没有已退出的子历程可收集,则 waitpid 返回0。
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
参数

  • pid:

    • Pid=-1,等待任一个子历程。与wait等效。
    • Pid>0.等待其历程ID与pid相等的子历程

  • status:这是一个指向整数的指针,用于存储子历程的退出状态。如果不必要这个信息,可以通报 NULL。

    • WIFEXITED(status):宏函数,如果子历程正常退出,返回非零值;否则返回0。
    • WEXITSTATUS(status):宏函数,如果 WIFEXITED(status) 为真,则返回子历程的退出码。(后面就能理解这两个用处)

  • options:这是一个位掩码,用于修改 waitpid 的行为。

    • WNOHANG:如果指定了此选项,waitpid 将不会阻塞,而是立即返回(父历程不会等待子历程了)。如果指定的子历程没有竣事,则 waitpid 返回0;如果子历程已竣事,则返回子历程的ID。
    • 通报 0 作为 options 参数时,你实际上是在告诉 waitpid使用最传统的阻塞方式等待子历程终止,而且只关心那些已经终止的子历程

   如果子历程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,而且释放资源,获得子历程退出信息。
  如果在恣意时刻调用wait/waitpid,子历程存在且正常运行,则历程可能阻塞。
  如果不存在该子历程,则立即出错返回。
  获取子历程status

   

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果通报NULL,表示不关心子历程的退出状态信息。
  • 否则,操作系统会根据该参数,将子历程的退出信息反馈给父历程。
  • 我们上面说:任何历程终极的执行情况,我们可以使用两个数字表明详细执行的情况——退出码和退出信号。这里status就是将两者结合起来的,使用位图结合
  • status不能简单的当作整形来对待,可以当作位图来对待,详细细节如下图:
  

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id = fork();
  9.     if(id == 0)
  10.     {
  11.         // child
  12.         int cnt = 5;
  13.         while(cnt)
  14.         {
  15.             printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());
  16.             sleep(1);
  17.             cnt--;
  18.         }
  19.         exit(1);
  20.     }
  21.     int status = 0;
  22.     pid_t rid = waitpid(id, &status, 0); // 阻塞等待
  23.     if(rid > 0)
  24.     {
  25.         printf("wait successfully, rid: %d, status: %d\n", rid, status);
  26.     }
  27.     return 0;
  28. }
复制代码


那我们怎么直接获得退出码和信号编号呢?

  • 我们能自己针对status进行位运算
  • 使用上面的WIFEXITED(status)、WEXITSTATUS(status)
    1. int main()
    2. {
    3.         pid_t id = fork();
    4.         if (id == 0)
    5.         {
    6.                 int cnt = 5;
    7.                 while (cnt)
    8.                 {
    9.                         printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());
    10.                         sleep(1);
    11.                         cnt--;
    12.                 }
    13.                 exit(1);
    14.         }
    15.         int status = 0;
    16.         pid_t rid = waitpid(id, &status, 0);
    17.         if (rid > 0)
    18.         {
    19.                 if (WIFEXITED(status))
    20.                 {
    21.                         printf("wait successfully, rid: %d, status: %d\n", rid, status);
    22.                         printf("wait successfully, rid: %d, status: %d, exit code: %d\n",
    23.                                 rid, status,WEXITSTATUS(status));
    24.                 }
    25.                 else
    26.                 {
    27.                         printf("wait wrongly\n");
    28.                 }
    29.                
    30.         }
    31.         return 0;
    32. }
    复制代码

5.3阻塞等待与非阻塞等待

阻塞等待(wait()与waitpid( , , 0)):


  • 当历程执行某个操作时,如果该操作必要等待子历程竣事,历程会进入阻塞状态。
  • 在阻塞状态下,历程会暂停执行,释放CPU资源,将历程状态生存起来,以便在条件满意后能够恢复执行。
  • 阻塞等待期间,历程无法执行其他任务,只能等待条件满意或变乱发生。
非阻塞等待


  • 与阻塞等待不同,非阻塞等待允许历程在等待子历程竣事期间继承执行其他任务。
  • 非阻塞等待通常通过轮询或异步通知机制实现,历程会定期查抄条件是否满意,大概在条件满意时吸收通知。
  1. int main()
  2. {
  3.         pid_t id = fork();
  4.         if (id == 0)
  5.         {
  6.                 int cnt = 5;
  7.                 while (cnt)
  8.                 {
  9.                         printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());
  10.                         sleep(1);
  11.                         cnt--;
  12.                 }
  13.                 exit(1);
  14.         }
  15.         int status = 0;
  16.         pid_t rid = waitpid(id, &status, WNOHANG);
  17.         while (1)
  18.         {
  19.                 if (rid > 0)
  20.                 {
  21.                         //子进程结束了
  22.                         printf("wait successfully, rid: %d, status: %d, exit code: %d\n",
  23.                                 rid, status, WEXITSTATUS(status));
  24.                         break;
  25.                 }
  26.                 else if (rid = 0)
  27.                 {
  28.                         //子进程还没结束
  29.                         //这里能写希望在子进程没结束期间希望父进程干什么
  30.                 }
  31.                 else
  32.                 {
  33.                         //到这里就说明出错了
  34.                         perror("waitpid");
  35.                         break;
  36.                 }
  37.         }
  38.         return 0;
  39. }
复制代码

今天的内容也是不少了,累死了。感谢大家支持!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表