Linux系统编程【进程控制】(4)

举报
xcc-2022 发表于 2022/10/24 20:47:27 2022/10/24
1.2k+ 0 0
【摘要】 12. 父子进程地址空间父子进程各自的地址空间是独立的int a = 10; // 全局变量int main(){ int b = 20; //局部变量 pid_t pid; pid = fork(); if (pid < 0) { // 没有创建成功 perror("fork"); } if (0 == pid) ...

12. 父子进程地址空间

父子进程各自的地址空间是独立的

int a = 10;     // 全局变量

int main()
{
    int b = 20; //局部变量
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {   // 没有创建成功
        perror("fork");
    }

    if (0 == pid)
    { // 子进程
        a = 111;
        b = 222;    // 子进程修改其值
        printf("son: a = %d, b = %d\n", a, b);
    }
    else if (pid > 0)
    { // 父进程
        sleep(1);   // 保证子进程先运行
        printf("father: a = %d, b = %d\n", a, b);
    }

    return 0;
}

运行结果如下:

image-20220923224723491

通过得知,在子进程修改变量 a,b 的值,并不影响到父进程 a,b 的值。

image-20220923224312636

堆区创建子进程

    pid_t pid;
    pid = fork();
    int *p = NULL;

    //在堆区分配内存空间
    p = malloc(sizeof(int));
    if( NULL == p )
    {
    
        printf("malloc failed...\n");
        return 1;
    }
    memset(p,0,sizeof(int));
    *p = 200;
    if (0 == pid)
    { // 子进程

        sleep(1);   
        printf("son:*p = %d\n", *p);
       // free(p);
       // p = NULL;
    }
    else if (pid > 0)
    { // 父进程
        (*p)++;
        printf("father:*p = %d\n",*p);
       // free(p);
       // p = NULL;
    }

运行结果:

image-20220923231751502

堆区创建的指针需要释放两次,不然会发生内存泄漏,检查内存泄漏(需安装sudo apt install valgrind /validlocale)

xcc@xccmachine:~/5th$ gcc adr.c
xcc@xccmachine:~/5th$ validlocale ./a.out

没释放之前:

image-20220923232246672

两次释放之后:

image-20220923232418174

总结:不管变量在局部还是全局或者堆区,子进程的值都不会变;所以子进程发生拷贝 地址空间也是不同的;

13. GDB调试多进程

使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。

  • set follow-fork-mode child 设置gdb在fork之后跟踪子进程。
  • set follow-fork-mode parent 设置跟踪父进程(默认)。

注意,一定要在fork函数调用之前设置才有效。

14. 进程退出函数

相关函数:

#include <stdlib.h>
void exit(int status);

#include <unistd.h>
void _exit(int status);
功能:
    结束调用此函数的进程。
参数:
    status:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。
返回值:
    无

image-20220924071443263

exit() 和 *exit() 函数功能和用法是一样的,无非时所包含的头文件不一样,还有的区别就是:exit()属于标准库函数,*exit()属于系统调用函数。

1527910170715

15. 等待子进程退出函数

15.1 概述

在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。

父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。

wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。

注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。

15.2 wait函数

函数说明:

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

pid_t wait(int *status);
功能:
    等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。
参数:
    status : 进程退出时的状态信息。
返回值:
    成功:已经结束子进程的进程号
    失败: -1

调用 wait() 函数的进程会挂起(阻塞),直到它的一个子进程退出或收到一个不能被忽视的信号时才被唤醒(相当于继续往下执行)。

若调用进程没有子进程,该函数立即返回;若它的子进程已经结束,该函数同样会立即返回,并且会回收那个早已结束进程的资源。

所以,wait()函数的主要功能为回收已经结束子进程的资源。

如果参数 status 的值不是 NULL,wait() 就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的。

示例代码:

    int i=0;
    pid_t pid = -1;
    int status = 0;
    int ret = -1;
    
    //创建子进程
    pid = fork();
    if(-1 == pid)
    {
    
        perror("fork");
        return 1;
    }
    //子进程执行
    if(pid == 0)
    {
        for(i=0; i<5; i++)
        {
        
           printf("child process do thing :%d\n",i);
           sleep(1);
        }
            exit(10);
    }

    //父进程执行
    printf("父进程等待了子进程退出,回收其资源\n");
   ret =  wait(&status);
    if(-1 == ret)
    {
    
        perror("wait");
        return 1;
    }
    printf("父进程回收了子进程资源....\n");

运行结果:

动画1

这个退出信息在一个 int 中包含了多个字段,直接使用这个值是没有意义的,我们需要用宏定义取出其中的每个字段。

宏函数可分为如下三组:

  1. WIFEXITED(status)

为非0 → 进程正常结束

WEXITSTATUS(status)

如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)

  1. WIFSIGNALED(status)

为非0 → 进程异常终止

WTERMSIG(status)

如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。

  1. WIFSTOPPED(status)

为非0 → 进程处于暂停状态

WSTOPSIG(status)

如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。

WIFCONTINUED(status)

为真 → 进程暂停后已经继续运行

下面代码是自动执行到结束的信号的编号结果,可手动输入kill编号命令终止

image-20220924082745837

15.3 waitpid函数

函数说明:

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

pid_t waitpid(pid_t pid, int *status, int options);
功能:
    等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。

参数:
    pid : 参数 pid 的值有以下几种类型:
      pid > 0  等待进程 ID 等于 pid 的子进程。
      pid = 0  等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它。
      pid = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。
      pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。

    status : 进程退出时的状态信息。和 wait() 用法一样。

    options : options 提供了一些额外的选项来控制 waitpid()0:同 wait(),阻塞父进程,等待子进程退出。
            WNOHANG:没有任何已经结束的子进程,则立即返回。
            WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)
                 
返回值:
    waitpid() 的返回值比 wait() 稍微复杂一些,一共有 3 种情况:
        1) 当正常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号;
        2) 如果设置了选项 WNOHANG,而调用中 waitpid() 发现没有已退出的子进程可等待,则返回 03) 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在,如:当 pid 所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid() 就会出错返回,这时 errno 被设置为 ECHILD;

image-20220924084431821

不阻塞:

image-20220924084708879

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。