2.2.1 进程管理,以及父子进程共享同一个文件资源时,文件的‘读写位置’会相互影响

目录
  1. 等待一个进程(父进程等待子进程终止)
  2. 僵尸进程(defunct / zombie)
  3. 父子进程共享同一个文件资源时,‘读写指针’ 设置

1. 等待一个进程
  • 当用 fork 函数调用启动一个子进程时,子进程就有了它自己的生命周期并将独立运行。可以通过在父进程中调用 wait 函数让父进程等待子进程结束。
  1. wait 函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *stat_loc);

头文件 ‘sys/wait.h’ 中定义了用来解释状态信息的宏(每两行为一组,共 3 组):

说明
WIFEXITED(stat_val)如果子进程正常结束,它就取一个非零值
WEXITSTATUS(stat_val)如果 WIFEXITED 非零,它返回子进程的退出码
--
WIFSIGNALED(stat_val)如果子进程因为一个未捕获的信号而终止,它就取一个非零值
WTERNSIG(stat_val)如果 WIFSIGNALED 非零,它返回一个信号代码
--
WIFSTOPPED(stat_val)如果子进程意外终止,它就取一个非零值
WSTOPSIG(stat_val)如果 WIFSTOPPED 非零,它返回一个信号代码
  1. 父进程等待子进程终止,并获取/打印子进程的退出状态码,代码如下:
/* test3.c */
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

void main(){
    printf("main running.\n");
    int res;
    pid_t pid_res;
    pid_t pid = fork();
    switch(pid){
        case 0:
            // 子进程
            printf("child process running.\n");
            // 模拟耗时操作
            sleep(5);
            printf("child process done.\n");
            // 指定退出码为 69(父进程将获取该退出码)
            exit(69);
        case -1:
            // error
            exit(EXIT_FAILURE);
        default:
            // 父进程
            printf("parent process wait for child process.\n");
            // 父进程执行挂起(阻塞),等待子进程终止
            pid_res = wait(&res);
            if(pid_res == pid){
                if(WIFEXITED(res)){
                    printf("cp exit code(WIFEXITED): %d\n", WEXITSTATUS(res));
                }else if(WIFSIGNALED(res)){
                    printf("cp exit code(WIFSIGNALED): %d\n", WTERMSIG(res));
                }else if(WIFSTOPPED(res)){
                    printf("cp exit code(WIFSTOPPED): %d\n", WSTOPSIG(res));
                }
            }else{
                printf("error: wait for child process.\n");
            }

			printf("parent process done.\n");
            exit(EXIT_SUCCESS);
    }
}

运行 test3,执行结果如下(父进程等待子进程终止,并成功获取子进程退出码):

ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test3 test3.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test3
main running.
parent process wait for child process.
child process running.
child process done.
cp exit code(WIFEXITED): 69 // 表明:子进程正常结束,并且正确获取退出码为 69
parent process done.

将 switch 语句中的子进程执行代码由

        case 0:
            // 子进程
            printf("child process running.\n");
            // 模拟耗时操作
            sleep(5);
            printf("child process done.\n");
            // 指定退出码为 69(父进程将获取该退出码)
            exit(69);

修改为(其中 故意导致子进程异常终止):

        case 0:
            // 子进程
            printf("child process running.\n");
            // 模拟耗时操作
            sleep(5);
            printf("child process done.\n");
            //
            // 故意导致子进程异常终止
            //
            res = 1 / 0;
            // 指定退出码为 69(父进程将获取该退出码)
            exit(69);

再次运行 test3,执行结果如下(可见编译器报告‘除零’警告,忽略警告,继续运行 test3,发现子进程因未捕获的信号而终止(WIFSIGNALED),并且信号代码为 8):

ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test3 test3.c
test3.c: In function ‘main’:
test3.c:18:21: warning: division by zero [-Wdiv-by-zero]
             res = 1 / 0;
                     ^
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test3
main running.
parent process wait for child process.
child process running.
child process done.
cp exit code(WIFSIGNALED): 8 // 子进程因未捕获的信号而终止(WIFSIGNALED),8(SIGFPE) 表示算术运算错误
parent process done.

2. 僵尸进程
  • 子进程终止时,它与父进程之间的关系还会保持,直到父进程也正常终止或父进程调用 wait 函数才告结束。因此,进程表中代表子进程的表项不会立即释放。虽然子进程不再运行,但它仍然存在于系统中,因为它的退出码还需要保存起来,以备父进程今后的 wait 调用使用。这时它将成为一个死(defunct)进程或僵尸(zombie)进程。
  • 如果父进程异常终止,子进程将自动把 PID 为 1 的进程(即 init )作为自己的父进程。子进程现在是一个不再运行的僵尸进程,但因为其父进程异常终止,所以它由 init 进程接管。僵尸进程将一直保留在进程表中直到被 init 进程发现并释放。进程表越大,这一过程就越慢。应该尽量避免产生僵尸进程,因为在 init 清理它们之前,它们将一直消耗系统的资源。
  1. 模拟僵尸进程,代码如下(子进程先于父进程终止):
/* test3.c */
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

void main(){
    printf("parent running.\n");
    int res;
    pid_t pid = fork();
    switch(pid){
        case 0:
            // child process
            printf("child process running.\n");
            printf("child process done.\n");
            exit(0);
        case -1:
            // error
            exit(EXIT_FAILURE);
        default:
        	// 父进程等待子进程终止
            sleep(5);
            printf("parent process done.\n");
            exit(EXIT_SUCCESS);
    }
}

运行 test3 程序,执行结果如下(正如期待的那样,子进程先行终止,父进程随后才结束运行):

ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test3 test3.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test3
parent running.
child process running.
child process done. 	// 子进程终止
parent process done.	// 父进程终止

在子进程终止后,父进程终止之前,查询进程信息,如下(子进程 pid:11353 为僵尸进程):

ubuntu@cuname:~$ ps l
F   UID    PID   PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1000  11352   3665  20   0   4352   680 hrtime S+   pts/18     0:00 ./test3
1  1000  11353  11352  20   0      0     0 -      Z+   pts/18     0:00 [test3] <defunct>
  1. 怎样避免僵尸进程?除了在父进程中调用 wait 函数,还有可以使用信号处理,在父进程中处理 或 忽略子进程的 SIGCHLD 信号。修改代码如下(忽略子进程的 SIGCHLD 信号):
/* test3.c */
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void main(){
    printf("parent running.\n");
    // 忽略子进程的 SIGCHLD 信号
    signal(SIGCHLD, SIG_IGN);
    pid_t pid = fork();
    switch(pid){
        case 0:
            // child process
            printf("child process running.\n");
            printf("child process done.\n");
            exit(0);
        case -1:
            // error
            exit(EXIT_FAILURE);
        default:
        	// 父进程等待子进程终止
            sleep(5);
            printf("parent process done.\n");
            exit(EXIT_SUCCESS);
    }
}

运行 test3:

ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test3
parent running.
child process running.
child process done.
parent process done.

并查看进程信息如下(已经没有僵尸进程了):

ubuntu@cuname:~$ ps l
F   UID    PID   PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1000  11455   3665  20   0   4352   640 hrtime S+   pts/18     0:00 ./test3

3. 父子进程共享同一个文件资源时,‘读写指针’ 设置
  • fork 或 exec 函数调用中的文件描述符(在原进程中已打开的文件描述符在新进程中仍将保持打开(这不是绝对的,这里暂不讨论))
  • 父子进程都有各自的文件描述符,它们都指向同一个文件资源,它们各自独立。但是父子进程它们对同一个文件资源进行读写时,文件的 ‘读写位置’ 是相互影响的。
  • 每个进程都有一些与之关联的文件描述符。这是一些小值整数,可以通过它们访问打开的文件或设备。每个进程持有的文件描述符个数是有限的,取决于系统的配置情况。当一个程序开始运行时,它一般会有 3 个已经打开的文件描述符
文件描述符说明
0标准输入
1标准输出
2标准错误

模拟程序:在父进程中打开文件,然后在子进程中执行写入操作,最后父进程读取文件内容。父进程读取之前,设置了 ‘读写指针’ 的位置,否则无法读取子进程写入的内容,因为 ‘读写指针’ 处于文件末尾。代码如下:

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>

void main(){
    printf("parent running.\n");
    int res;
    char buf_wr[] = "9ijghjkk9988";
    char buf_rd[sizeof(buf_wr)];
    buf_rd[0] = '\0';
    int fd = open("/tmp/data.txt", O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if(fd < 0){
        printf("error: open file:/tmp/data.txt failed.\n");
        exit(EXIT_FAILURE);
    }

    pid_t pid = fork();
    switch(pid){
        case 0:
            // 子进程
            printf("child process running.\n");
            res = write(fd, buf_wr, sizeof(buf_wr));
            if(res != sizeof(buf_wr)){
                printf("error: child process write failed.\n");
                exit(EXIT_FAILURE);
            }
            close(fd); // 子进程先关闭自己的文件描述符
            printf("child process done.\n");
            exit(0);
        case -1:
            // error
            exit(EXIT_FAILURE);
        default:
        	// 父进程
        	// 父进程等待子进程终止
            wait(0);
            //
			// 设置文件的‘读写位置’,使其偏移到子进程执行写入操作时的位置
			//
            lseek(fd, 0 - sizeof(buf_wr), SEEK_CUR);

            res = read(fd, buf_rd, sizeof(buf_wr));
            if(res == 0){
                // EOF
                printf("EOF\n");
            }else if(res > 0){
                printf("parent process done. readed string is '%s'\n", buf_rd);
            }else{
                // error
                printf("error: parent process read failed.\n");
                exit(EXIT_FAILURE);
            }
            close(fd);
            exit(EXIT_SUCCESS);
    }
}

运行结果,如下:

ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test3 test3.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test3
parent running.
child process running.
child process done.  	// 子进程终止
parent process done. readed string is '9ijghjkk9988'  // 随后父进程终止

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: pdfwdit2.2.1是一款专业的PDF编辑软件。该软件具有多种功能,可以帮助用户轻松编辑和修改PDF文件。 首先,pdfwdit2.2.1可以实现文本编辑功能。用户可以通过该软件添加、删除或修改PDF文件中的文本内容,同还可以更改字体、大小、颜色等格式设置。这使得用户可以根据需要对PDF文件进行定制和修改。 其次,pdfwdit2.2.1具备图片编辑功能。用户可以直接在PDF文件中插入图片,也可以对已有的图片进行裁剪、旋转、调整大小等操作。这使得PDF文件的制作过程更加灵活和丰富。 此外,pdfwdit2.2.1还支持注释和标记功能。用户可以在PDF文件中添加批注、标签、高亮和下划线等,以便于进行重点标记和想法记录。这对于专业人士和学生来说是非常有用的。 另外,pdfwdit2.2.1还具备合并和拆分PDF文件的功能。用户可以将多个PDF文件合并成一个文件,或者将一个大的PDF文件拆分成多个小的文件。这让文件管理和分享变得更加方便和高效。 总之,pdfwdit2.2.1是一款功能强大且易于使用的PDF编辑软件。它可以满足用户对PDF文件编辑的各种需求,无论是文字、图片还是标记注释。通过这款软件,用户可以轻松地修改和定制自己的PDF文件,提高工作和学习效率。 ### 回答2: PDFwdit2.2.1是一种用于PDF文件编辑和转换的软件。它提供了一系列功能,帮助用户在PDF文档中进行各种操作。 首先,PDFwdit2.2.1允许用户编辑PDF文件的内容。用户可以通过添加、删除或修改文字来对PDF文档进行编辑,并且可以轻松调整文本的大小、字体和颜色。此外,它还提供了对图像、表格和图表的编辑功能,使用户可以在PDF中进行必要的修改。 其次,PDFwdit2.2.1还支持PDF文件的转换。用户可以将PDF文件转换为其他格式(如Word、Excel、PowerPoint、图像等),以便于进一步编辑和处理。同,它也允许用户将其他格式的文件转换为PDF,方便文档的存储和共享。 此外,PDFwdit2.2.1还具有一些附加功能,使用户能够更加方便地使用PDF文档。例如,它提供了PDF文件合并和拆分的功能,允许用户将多个PDF文件合并为一个或者将一个PDF文件拆分为多个。此外,它还支持对PDF文件进行密保护和数字签名,以确保文档的安全性。 总之,PDFwdit2.2.1是一款功能强大的PDF编辑和转换软件。通过它,用户可以方便地编辑和转换PDF文件,满足各种文档处理的需求。无论是个人用户还是企业用户,都可以从PDFwdit2.2.1中获得便捷和高效的体验。 ### 回答3: pdfwdit2.2.1是一个软件版本号。这个版本的软件是用于处理PDF文件的工具。PDF文件是一种可移植文档格式,通常用于存储和共享电子文档。pdfwdit2.2.1可以帮助用户编辑和修改PDF文件,包括添加、删除和重新排列页面,插入图像和文本,更改字体和格式等。此外,它还可以对PDF文件进行转换,将其转换为其他格式,如Word、Excel、HTML等。pdfwdit2.2.1还具有OCR(光学字符识别)功能,可以将扫描的纸质文档转换为可编辑的文本。此外,它还支持加密和解密PDF文件,以及添加数字签名来确保文件的安全性和完整性。pdfwdit2.2.1具有直观的用户界面,易于使用,适合个人用户和企业用户使用。它是一个功能强大且多功能的PDF编辑工具,可以提高用户的工作效率和生产力。无论是在个人生活中需要编辑PDF文件,还是在工作中需要处理大量的电子文档,pdfwdit2.2.1都是一个非常实用的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值