linux_fork_多重fork/getpid=1孤儿进程/exit(n)&wait(wstatus)技术/使用双管道子进程之间通信)/unix_linux经典书籍(TLPI/APUE/UNP)

references

试验代码

本试验分为多个部分(头文件和函数是公用的)
有多个版本来改进

prints.h

调试宏

// 数值调试宏
#ifndef CXXU
#define CXXU 1
// 修改sizeint来指定打印宽度:(注意,必须以字符串的形式修改,(数字要包裹双引号))
// 负数,就是左对齐
#define sizeint__ "25"
#define sizestr__ "%" sizeint__ "s"
#define dprint(expr) printf(sizestr__ " = %d @%%d\n", #expr, expr)
#define ldprint(expr) printf(sizestr__ " = %ld @%%ld\n", #expr, expr)
#define cprint(expr) printf(sizestr__ " = %c @%%c\n", #expr, expr)
#define sprint(expr) printf(sizestr__ " = %s @%%s\n", #expr, expr)
#define gprint(expr) printf(sizestr__ " = %g\n", #expr, expr)
#define fprint(expr) printf(sizestr__ " = %f\n", #expr, expr)
// #define sprint(expr) printf("\t@sprint"#expr " = %s\n", expr)
// #define sprint(expr) printf(expr)
#define sprintln(expr) printf(expr "\n")
#define pre_print(expr) printf(expr)
// 直接传递变量给pprint(取地址操作包含在了宏中)
#define pprint(expr) printf(sizestr__ " = %p &var%%p\n", "&" #expr, &expr)
// 直接打印传入的地址(指针变量)
#define pprinta(expr) printf(sizestr__ " = %p %%p (pointer:" #expr ")\n", #expr, expr)
// extern void func();
// extern int multiply(int a, int b);
// extern char *str_multiplier;
#endif

common_fun.c

包含常用函数的源文件

#ifndef common_func
#define common_func
#include "prints.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <time.h>
void send_char_by_char(char *str, int pipefd_1)
{
// char *str = "msg!";
int len = 0;
// write(pipefd[1], str, strlen(str));
dprint(len = strlen(str));
for (int i = 0; i < len; i++)
{
// 我们可以让写入管道的操作以每秒一个字符的速度写入,观测子进程是否会因此阻塞(读取字符的速度不会超过每秒一个!)
// sleep(1);
extern void msleep(int tms);
write(pipefd_1, str + i, 1);
msleep(200);
}
close(pipefd_1); /* Reader will see EOF */
}
void msleep(int tms)
{
struct timeval tv;
tv.tv_sec = tms / 1000;
tv.tv_usec = (tms % 1000) * 1000;
select(0, NULL, NULL, NULL, &tv);
}
void timer(char *invoker, int seconds)
{
printf("%s:sleeping(%d)s\n", invoker, seconds);
for (int i = 0; i < seconds; i++)
{
// printf("i=%d\n", i);
printf("\t\t%s:%d\n", invoker, i + 1);
sleep(1);
// sprintln("hh");
}
}
#endif

多重fork(code1)

#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "prints.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include "common_fun.c"
/* 用C语言写一个程序名字为procs4,该程序运行过程中共有4个进程
procs4程序(父进程)创建2个子进程p1和p2,
p1子进程再创建一个子进程p3。
4个进程完成如下工作:
父进程打印字符串“I am main process”;
p1子进程打印“I am child process p1”字符串;
p2子进程打印“I am child process p2”;
子进程p3打印字符串“I am child process p3”,然后使用exec(族)系统调用打印当前目录下文件和子目录的详细信息。
并且每个进程都要打印自己的pid。
*/
/*
完成本任务需要知道:
主进程如果在子进程之前结束,那么子进程中执行getppid()的结果可能会是1
(即,子进程变成了孤儿进程,托交给init)
PID TTY TIME CMD
1 ? 00:00:10 systemd
// pid=1进程详情
┌─[cxxu@cxxuAli] - [~] - [2022-04-28 08:09:38]
└─[0] <> ps -p 1 u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 77828 7248 ? Ss Apr11 0:10 /sbin/init noibrs splash
为例放置子进程变成孤儿,可以通过让各级父进程sleep()一段时间,让子进程先执行完
*/
int counter = 0;
int main(int argc, char const *argv[])
{
pid_t fp1;
pid_t fp2;
sprintln("---before fork---,this line should just be print once");
sprintln("~~~fork p1");
fp1 = fork();
// fp2 = fork();//本语句会被主进程和p1进程分别执行一次!(实际效果将不是我们想要的)
// sprintln("---after fork---");
// counter += 1;
// dprint(counter);
dprint(fp1);
// dprint(fp2);
if (fp1 == 0)
{
printf("\t");
sprintln("I am child process p1");
dprint(getpid());
dprint(getppid());
/* code */
pid_t fp3 = fork();
if (fp3 == 0)
{
pre_print("\t\t");
sprintln("I am child process p3");
dprint(getpid());
dprint(getppid());
// execl("/bin/ls", "ls", "-l", (void *)NULL);
}
else{
sleep(1);
}
}
/* 正因为将多个多个fork直接写在一起(同一层),那么第一个子进程和主进程一样,会执行第二个fork
所以为例只让主进程执行第二个或者之后的fork操作,应当将后续的fork写在只有主进程才会进入的判断分支中
*/
// else if (fp2 == 0)
// {
// printf("\t");
// pre_print("I am child prcess p2");
// dprint(getpid());
// dprint(getppid());
// }
else
{
sprintln("I am main process");
dprint(getpid());
sprintln("~~~fork p2---");
fp2 = fork();
if (fp2 == 0)
{
printf("\t");
sprintln("I am child prcess p2");
dprint(getpid());
dprint(getppid());
}
sleep(1);
}
return 0;
}

在这里插入图片描述

使用exit(n)&wait(wstatus)技术

下面结合linux帮助手册和试验代码来说明这一种父子进程通信技术

wait_exit.c
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
#include "common_fun.c"
#include "prints.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
/* 任务1: */
/* 用C语言写一个程序名字为procs4,该程序运行过程中共有4个进程
procs4程序(父进程)创建2个子进程p1和p2,
p1子进程再创建一个子进程p3。
4个进程完成如下工作:
父进程打印字符串“I am main process”;
p1子进程打印“I am child process p1”字符串;
p2子进程打印“I am child process p2”;
子进程p3打印字符串“I am child process p3”,然后使用exec(族)系统调用打印当前目录下文件和子目录的详细信息。
并且每个进程都要打印自己的pid。
*/
/* 任务2:
p0等待p1,p2任务全部完成
p1睡眠5秒钟,然后创建p3,p3也睡眠5秒钟,p1待p3结束退出后p1也一并退出(整个过程耗时10秒)
p2和p1是兄弟级别,p2和p1几乎同时开始睡眠,都是5秒钟(这五秒钟内和p1的睡眠时间是重叠的,故不会加长总时长)
综上,全部任务大约耗时10s;(但是三个进程各自睡眠5秒;其中p1,p2在都在前5秒内睡眠,p3单独睡眠,后5秒)
*/
/*
完成本任务需要知道:
主进程如果在子进程之前结束,那么子进程中执行getppid()的结果可能会是1
(即,子进程变成了孤儿进程,托交给init)
PID TTY TIME CMD
1 ? 00:00:10 systemd
// pid=1进程详情
┌─[cxxu@cxxuAli] - [~] - [2022-04-28 08:09:38]
└─[0] <> ps -p 1 u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 77828 7248 ? Ss Apr11 0:10 /sbin/init noibrs splash
为例放置子进程变成孤儿,可以通过让各级父进程sleep()一段时间,让子进程先执行完
*/
/* 使用exit()/wait() 技术进行父子进程间接通信 */
/*
NAME
wait, waitpid, waitid - wait for process to change state
SYNOPSIS top
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
This is the glibc and POSIX interface; see
NOTES for information on the raw system call.
*/
/* wait 记录结果的解析宏 */
/*
WIFEXITED(wstatus)
returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main().
WEXITSTATUS(wstatus)
returns the exit status of the child. This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or
as the argument for a return statement in main(). This macro should be employed only if WIFEXITED returned true.
WIFSIGNALED(wstatus)
returns true if the child process was terminated by a signal.
*/
/* If WIFEXITED(STATUS), the low-order 8 bits of the status.
通过wait(wstatus)向内核拿到子进程的exit()的弥留之言并记录在wstatus中时,其值和子进程传入exit()的值已经发生了变化,
想要取得传入exit()中的值,可以用WEXITSTATUS(wstatus)来解析这个被wait()修改过的状态值
*/
// #define __WEXITSTATUS(status) (((status)&0xff00) >> 8)
/* All of these system calls are used to wait for state changes in a child of the calling process,
and obtain information about the child whose state has changed.
A state change is considered to be:
the child terminated;
the child was stopped by a signal;
or the child was resumed by a signal.
In the case of a terminated child, performing a wait allows the system to release the resources associated with the child;
if a wait is not performed, then the terminated child remains in a "zombie" state (see NOTES below).
If a child has already changed state, then these calls return immediately.
Otherwise, they block until either a child changes state or a signal handler interrupts the call
(assuming that system calls are not automatically restarted using the SA_RESTART flag of sigaction(2)).
In the remainder of this page,
a child whose state has changed and which has not yet been waited upon by one of these system calls is termed `waitable`. */
int counter = 0;
int wstatus = -1;
void p0_code();
void p1_code();
void p2_code();
void p3_code();
int main(int argc, char const *argv[])
{
pid_t fp1;
// pid_t fp2;
sprintln("---before fork---,this line should just be print once");
sprintln("~~~fork p1");
fp1 = fork();
// fp2 = fork();//本语句会被主进程和p1进程分别执行一次!(实际效果将不是我们想要的)
// sprintln("---after fork---");
// counter += 1;
// dprint(counter);
// dprint(fp1);
// dprint(fp2);
if (fp1 < 0)
{
perror("waitprocess::fork error!");
}
if (fp1 == 0)
{
p1_code();
}
else
{
p0_code();
}
}
void p0_code()
{
printf("I am main process,p0:%d", getpid());
printf("\n");
// dprint(getpid());
sprintln("~~~fork p2---");
int stauts_;
int fp2 = fork();
if (fp2 < 0)
{
perror("waitprocess::fork error!");
}
if (fp2 == 0)
{
p2_code();
}
// sleep(1);
// p0 later code
// 使用wait()来改进本程序
// wait(&status_);
/* 等待所有子进程exit,可以让父进程不停调用wait(),当只有当所有子进程都exit,wait()在返回-1 */
while (wait(&wstatus) != -1)
{
pre_print("😂❤️");
dprint(WEXITSTATUS(wstatus));
dprint(WIFEXITED(wstatus));
}
sprintln("p0的子进程p1,p2都已经结束!");
// 查看子进程传递给exit()的遗言
sprintln("p0 exit!");
exit(EXIT_SUCCESS);
}
void p1_code()
{
printf("\t");
printf("I am child process p1:%d,pp1:%d\n", getpid(), getppid());
printf("\n");
// dprint(getpid());
// dprint(getppid());
sprintln("p1 will sleep, (before fork p3)...");
timer("p1", 5);
/* code */
sprintln("~~~fork p3~~~");
pid_t fp3 = fork();
if (fp3 == 0)
{
p3_code();
}
else
{
// p1_code() later;
/* p1等待p3结束
(父进程p0则需要等待p1,p2) */
// sprintln("p1进程将睡眠3s..");
// sleep(3);
/* Wait for a child to die. When one does, put its status in *STAT_LOC
and return its process ID. For errors, return (pid_t) -1. */
// 不在乎子进程p3的遗言,则传递NULL给wait()
sprintln("p1 waiting for p3 exit...");
wait(NULL);
// dprint(WIFEXITED(status_));
sprintln("wow,p3 exited, p1's wait is ended!");
sprintln("p1 exit!");
// 结束p1
exit(EXIT_SUCCESS);
// exit(15);
}
}
void p2_code()
{
printf("\t");
printf("I am child prcess p2:%d;pp2:%d", getpid(), getppid());
printf("\n");
// dprint(getpid());
// dprint(getppid());
sprintln("p2 is sleeping...");
timer("p2", 5);
/* 结束p2 */
sprintln("p2 exit!");
exit(EXIT_SUCCESS);
}
void p3_code()
{
// p3_code();
pre_print("\t\t");
printf("I am child process p3:%d;pp3:%d", getpid(), getppid());
printf("\n");
// dprint(getpid());
// dprint(getppid());
// execl("/bin/ls", "ls", "-l", (void *)NULL);
sprintln("p3 is sleeping..");
// sleep(1);
timer("p3", 5);
/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered,
perform stdio cleanup, and terminate program execution with STATUS. */
sprintln("p3 exit!");
exit(EXIT_SUCCESS);
}
comon_fun.c
#include "prints.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
void timer(char *invoker, int seconds)
{
printf("%s:sleeping(%d)s\n", invoker, seconds);
for (int i = 0; i < seconds; i++)
{
// printf("i=%d\n", i);
printf("\t\t%s:%d\n", invoker, i);
sleep(1);
// sprintln("hh");
}
}
prints.h
// 数值调试宏
#ifndef CXXU
#define CXXU 1
// 修改sizeint来指定打印宽度:(注意,必须以字符串的形式修改,(数字要包裹双引号))
// 负数,就是左对齐
#define sizeint__ "25"
#define sizestr__ "%" sizeint__ "s"
#define dprint(expr) printf(sizestr__ " = %d @%%d\n", #expr, expr)
#define ldprint(expr) printf(sizestr__ " = %ld @%%ld\n", #expr, expr)
#define cprint(expr) printf(sizestr__ " = %c @%%c\n", #expr, expr)
#define sprint(expr) printf(sizestr__ " = %s @%%s\n", #expr, expr)
#define gprint(expr) printf(sizestr__ " = %g\n", #expr, expr)
#define fprint(expr) printf(sizestr__ " = %f\n", #expr, expr)
// #define sprint(expr) printf("\t@sprint"#expr " = %s\n", expr)
// #define sprint(expr) printf(expr)
#define sprintln(expr) printf(expr "\n")
#define pre_print(expr) printf(expr)
// 直接传递变量给pprint(取地址操作包含在了宏中)
#define pprint(expr) printf(sizestr__ " = %p &var%%p\n", "&" #expr, &expr)
// 直接打印传入的地址(指针变量)
#define pprinta(expr) printf(sizestr__ " = %p %%p (pointer:" #expr ")\n", #expr, expr)
// extern void func();
// extern int multiply(int a, int b);
// extern char *str_multiplier;
#endif
结果
  • 其中,p1和p2几乎同时运行
  • p1稍慢一步才开始执行,导致了结果中,p2的倒计时计数在p1成功创建前就开始进行了,但后期两者呈现交叉打印的形式

在这里插入图片描述

单管道父子进程间通信示例

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "../common_fun.c"
/* The following program creates a pipe, and then fork(2)s to create a child process; the child inherits a duplicate set
of file descriptors that refer to the same pipe. After the fork(2), each process closes the file descriptors that it
doesn't need for the pipe (see pipe(7)).
The parent then writes the string contained in the program's command-line
argument to the pipe, and the child reads this string a byte at a time from the pipe and echoes it on standard output.
*/
/* write() man 2 */
/*
WRITE(2) Linux Programmer's Manual WRITE(2)
NAME
write - write to a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
DESCRIPTION
write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.
The number of bytes written may be less than count if, for example, there is insufficient space on the underlying
physical medium, or the RLIMIT_FSIZE resource limit is encountered (see setrlimit(2)), or the call was interrupted by
a signal handler after having written less than count bytes. (See also pipe(7).)
For a seekable file (i.e., one to which lseek(2) may be applied, for example, a regular file) writing takes place at
the file offset, and the file offset is incremented by the number of bytes actually written. If the file was
open(2)ed with O_APPEND, the file offset is first set to the end of the file before writing. The adjustment of the
file offset and the write operation are performed as an atomic step.
POSIX requires that a read(2) that can be proved to occur after a write() has returned will return the new data. Note
that not all filesystems are POSIX conforming.
According to POSIX.1, if count is greater than SSIZE_MAX, the result is implementation-defined; see NOTES for the up‐
per limit on Linux. */
int main(int argc, char *argv[])
{
/* 定义pipe 链接的两端的文件文件描述符(file descriptor) */
int pipefd[2];
/* 记录准备创建进程pid */
pid_t cpid;
// 缓冲字符
char buf;
/* if (argc != 2)
{
// 将本程序的用法显示在屏幕上.
// 标准错误输出参考:(In the terminal, standard error defaults to the user's screen.)
// https://www.computerhope.com/jargon/s/stderr.htm
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
// 提示完用法后立即结束本程序,以便用户重新以正确的用法使用本程序
exit(EXIT_FAILURE);
} */
/* 调用pipe()系统函数,申请系统创建一个管道(将官道两端的描述符写入pipefd中) */
if (pipe(pipefd) == -1)
{
perror("pipe");
exit(EXIT_FAILURE);
}
// 创建子进程,此时父子进程都享有管道符(子进程通过继承享有)
// 我们打算让子进程操作读出端,父进程操作写入端
sprintln("fork child process...");
cpid = fork();
if (cpid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
// 子进程代码
/* Child reads from pipe */
if (cpid == 0)
{
/* 测试如果不关闭读管道的进程中的写入端,是否会阻塞进程 ,
经过测试,如果不关闭,会阻塞管道读取进程!*/
/* 读进程在读管道前,需要关闭没有用到的写入端
在另一个进程(写入进程中),也需要在写完之后(利用完管道写入端)将其写入端关闭 */
close(pipefd[1]); /* Close unused write end */
// 调用read()函数,从pipefd[0]这一端读走管道内容
/*
pipefd[0] refers to the read end of the pipe.
pipefd[1] refers to the write end of the pipe.
Data written to the write end of the pipe is buffered by
the kernel until it is read from the read end of the pipe.
For further details, see pipe(7). */
/* 每次读取一字节(char)
循环读取,结束为止 */
// while (read(pipefd[0], &buf, 1) > 0)
// { /* 每读取完一个字节,就打印到屏幕上 */
// // #define STDOUT_FILENO 1 /* Standard output. */
// write(STDOUT_FILENO, &buf, 1);
// // sleep(1); //调用sleep()让我们可以感受到,这个歌过程是逐字符(字节)读取和输出(到屏幕上的.)
// }
int rres = read(pipefd[0], &buf, 1);
// printf("%c", buf);
// putchar(buf);
while (rres > 0)
{ /* 每读取完一个字节,就打印到屏幕上 */
// #define STDOUT_FILENO 1 /* Standard output. */
write(STDOUT_FILENO, &buf, 1);
rres = read(pipefd[0], &buf, 1);
// dprint(rres);
// sleep(1); //调用sleep()让我们可以感受到,这个歌过程是逐字符(字节)读取和输出(到屏幕上的.)
}
write(STDOUT_FILENO, "\n", 1);
// 拖住2秒
// timer("child proc", 2);
// write操作完毕
/* pipe读操作完毕,关闭pipe读取端(整型值文件描述符)的连接 */
close(pipefd[0]);
// _exit():Terminate program execution with the low-order 8 bits of STATUS.
sprintln("pipefd[0] closed!,showing pid ppid infos:");
printf("child p:%d;pp:%d", getpid(), getppid());
sprintln("");
// sprintln("what fuck!");
_exit(EXIT_SUCCESS);
}
/* Parent writes argv[1] to pipe */
else
{
// 我们可以尝试阻塞将字符串写入到管道符中的操作,来看看读操作是怎么受影响的
// sleep(2);
close(pipefd[0]); /* Close unused read end */
/* 父进程中(写管道进程)不需要管道读取端,因而可以将读取端文件描述符关闭,使得文件描述符不在指向任何文件
又因为子进程中有一套指向同一个管道的文件描述符,子进程还是可以访问到管道符的读取端 */
char *str = "msg!";
int len = 0;
// write(pipefd[1], str, strlen(str));
dprint(len = strlen(str));
for (int i = 0; i < len; i++)
{
// 我们可以让写入管道的操作以每秒一个字符的速度写入,观测子进程是否会因此阻塞(读取字符的速度不会超过每秒一个!)
// sleep(1);
msleep(500);
write(pipefd[1], str + i, 1);
}
/* 测试不关闭fd[1]是否会阻塞reader进程
经验证,如果不关闭fd[1],会导致读进程被阻塞!*/
close(pipefd[1]); /* Reader will see EOF */
/* 等待子进程完成任务 */
wait(NULL); /* Wait for child */
sprintln("child proc exited!");
printf("parent pid:%d", getpid());
exit(EXIT_SUCCESS);
}
}
效果

在这里插入图片描述

使用管道通信(双管道子进程之间进行同行)版本

#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
#include "common_fun.c"
#include "prints.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
/* 任务1: */
/* 用C语言写一个程序名字为procs4,该程序运行过程中共有4个进程
procs4程序(父进程)创建2个子进程p1和p2,
p1子进程再创建一个子进程p3。
4个进程完成如下工作:
父进程打印字符串“I am main process”;
p1子进程打印“I am child process p1”字符串;
p2子进程打印“I am child process p2”;
子进程p3打印字符串“I am child process p3”,然后使用exec(族)系统调用打印当前目录下文件和子目录的详细信息。
并且每个进程都要打印自己的pid。
*/
/* 任务2:
p0等待p1,p2任务全部完成
p1睡眠time秒钟,然后创建p3,p3也睡眠time秒钟,p1待p3结束退出后p1也一并退出(整个过程耗时10秒)
p2和p1是兄弟级别,p2和p1几乎同时开始睡眠,都是time秒钟(这五秒钟内和p1的睡眠时间是重叠的,故不会加长总时长)
综上,全部任务大约耗时10s;(但是三个进程各自睡眠time秒;其中p1,p2在都在前time秒内睡眠,p3单独睡眠,后time秒)
*/
/*
完成本任务需要知道:
主进程如果在子进程之前结束,那么子进程中执行getppid()的结果可能会是1
(即,子进程变成了孤儿进程,托交给init)
PID TTY TIME CMD
1 ? 00:00:10 systemd
// pid=1进程详情
┌─[cxxu@cxxuAli] - [~] - [2022-04-28 08:09:38]
└─[0] <> ps -p 1 u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 77828 7248 ? Ss Apr11 0:10 /sbin/init noibrs splash
为例放置子进程变成孤儿,可以通过让各级父进程sleep()一段时间,让子进程先执行完
*/
/* 使用exit()/wait() 技术进行父子进程间接通信 */
/*
NAME
wait, waitpid, waitid - wait for process to change state
SYNOPSIS top
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
This is the glibc and POSIX interface; see
NOTES for information on the raw system call.
*/
/* wait 记录结果的解析宏 */
/*
WIFEXITED(wstatus)
returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main().
WEXITSTATUS(wstatus)
returns the exit status of the child. This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or
as the argument for a return statement in main(). This macro should be employed only if WIFEXITED returned true.
WIFSIGNALED(wstatus)
returns true if the child process was terminated by a signal.
*/
/* read() */
/* NAME
read - read from a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
DESCRIPTION
read() attempts to read up to count bytes from file descriptor fd into the buffer
starting at buf.
On files that support seeking, the read operation commences at the file offset,
and the file offset is incremented by the number of bytes read. If the file off‐
set is at or past the end of file, no bytes are read, and read() returns zero.
If count is zero, read() may detect the errors described below. In the absence
of any errors, or if read() does not check for errors, a read() with a count of 0
returns zero and has no other effects.
According to POSIX.1, if count is greater than SSIZE_MAX, the result is implemen‐
tation-defined; see NOTES for the upper limit on Linux.
RETURN VALUE
On success, the number of bytes read is returned (zero indicates end of file),
and the file position is advanced by this number.
It is not an error if this
number is smaller than the number of bytes requested;
this may happen for example because fewer bytes are actually available right now (maybe because we were close
to end-of-file, or because we are reading from a pipe, or from a terminal), or
because read() was interrupted by a signal. See also NOTES.
On error, -1 is returned, and errno is set appropriately. In this case, it is
left unspecified whether the file position (if any) changes. */
/* If WIFEXITED(STATUS), the low-order 8 bits of the status.
通过wait(wstatus)向内核拿到子进程的exit()的弥留之言并记录在wstatus中时,其值和子进程传入exit()的值已经发生了变化,
想要取得传入exit()中的值,可以用WEXITSTATUS(wstatus)来解析这个被wait()修改过的状态值
*/
// #define __WEXITSTATUS(status) (((status)&0xff00) >> 8)
/* All of these system calls are used to wait for state changes in a child of the calling process,
and obtain information about the child whose state has changed.
A state change is considered to be:
the child terminated;
the child was stopped by a signal;
or the child was resumed by a signal.
In the case of a terminated child, performing a wait allows the system to release the resources associated with the child;
if a wait is not performed, then the terminated child remains in a "zombie" state (see NOTES below).
If a child has already changed state, then these calls return immediately.
Otherwise, they block until either a child changes state or a signal handler interrupts the call
(assuming that system calls are not automatically restarted using the SA_RESTART flag of sigaction(2)).
In the remainder of this page,
a child whose state has changed and which has not yet been waited upon by one of these system calls is termed `waitable`. */
int counter = 0;
int wstatus = -1;
int time_ = 2;
int pipefd12[2];
int pipefd21[2];
char buf;
void p0_code();
void p1_code();
void p2_code();
void p3_code();
int main(int argc, char const *argv[])
{
/* 调用pipe()系统函数,申请系统创建一个管道(将官道两端的描述符写入pipefd中) */
if (pipe(pipefd12) == -1)
{
perror("pipe12");
exit(EXIT_FAILURE);
}
if (pipe(pipefd21) == -1)
{
perror("pipe21");
exit(EXIT_FAILURE);
}
pid_t fp1;
// pid_t fp2;
sprintln("---before fork---,this line should just be print once");
sprintln("~~~fork p1");
fp1 = fork();
// fp2 = fork();//本语句会被主进程和p1进程分别执行一次!(实际效果将不是我们想要的)
// sprintln("---after fork---");
// counter += 1;
// dprint(counter);
// dprint(fp1);
// dprint(fp2);
if (fp1 < 0)
{
perror("waitprocess::fork error!");
}
if (fp1 == 0)
{
p1_code();
}
else
{
p0_code();
}
}
void p0_code()
{
printf("I am main process,p0:%d", getpid());
printf("\n");
// dprint(getpid());
sprintln("~~~fork p2---");
int stauts_;
int fp2 = fork();
if (fp2 < 0)
{
perror("waitprocess::fork error!");
}
if (fp2 == 0)
{
p2_code();
}
/* 测试在子进程都利用完管道后,父进程中关闭写入管道端口!
使得对应管道写端口全部被关闭,正真释放写端口文件描述符
这样,管道的读取进程才可以遇到EOF,从而不会被阻塞!*/
close(pipefd21[1]);
close(pipefd12[1]);
// sleep(1);
// p0 later code
// 使用wait()来改进本程序
// wait(&status_);
/* 等待所有子进程exit,可以让父进程不停调用wait(),当只有当所有子进程都exit,wait()在返回-1 */
while (wait(&wstatus) != -1)
{
pre_print("😂❤️");
dprint(WEXITSTATUS(wstatus));
dprint(WIFEXITED(wstatus));
}
sprintln("p0的子进程p1,p2都已经结束!");
// 查看子进程传递给exit()的遗言
sprintln("p0 exit!");
exit(EXIT_SUCCESS);
}
/* p1读取p2发送的消息 */
void p1_code()
{
printf("\t");
printf("I am child process p1:%d,pp1:%d\n", getpid(), getppid());
printf("\n");
/* p1:
向p2发消息:关闭不用的读pipefd12[0];写入pipefd12[1](写完以后关闭pipefd12[1])
读p2的消息:关闭不用的写pipefd21[1];读出pipefd21[0](读完以后,关闭pipefd21[0]) */
// 我们可以尝试阻塞将字符串写入到管道符中的操作,来看看读操作是怎么受影响的
// sprintln("延迟2秒再发送信息给p2");
// timer("p1", 2);
close(pipefd12[0]); /* Close unused read end */
char *msg = "Child process p1 is sending a message!";
// 一口气将消息写入管道fd12
write(pipefd12[1], msg, strlen(msg));
/* Reader will see EOF */
close(pipefd12[1]);
sprintln("p1将信息发送完毕.");
// 关闭fd21[1](不用该管道的写入端口)
close(pipefd21[1]);
sprintln("P1读取p2发过来的信息");
/* 每次读取一字节(char)
循环读取,结束为止 */
int rres;
// int rres = read(pipefd21[0], &buf, 1);
// write(STDOUT_FILENO, &buf, 1);
int cnt = 0;
while (rres = read(pipefd21[0], &buf, 1) > 0)
{ /* 每读取完一个字节,就打印到屏幕上
这里采用的是write()调用来打印 */
// #define STDOUT_FILENO 1 /* Standard output. */
write(STDOUT_FILENO, &buf, 1);
// rres = read(pipefd21[0], &buf, 1);
// dprint(rres);
// write()会不会阻塞进程运行?
// dprint(rres);
// cprint(buf);
// sleep(1); //调用sleep()让我们可以感受到,这个歌过程是逐字符(字节)读取和输出(到屏幕上的.)
}
write(STDOUT_FILENO, "\n", 1);
dprint(rres);
// sprintln("");
sprintln("😎p1没有被阻塞!");
/* pipe读操作完毕,关闭pipe读取端(整型值文件描述符)的连接 */
close(pipefd21[0]);
// dprint(getpid());
// dprint(getppid());
// sprintln("p1 will sleep, (before fork p3)...");
// timer("p1", time_);
/* code */
sprintln("~~~fork p3~~~");
/* 试验p3对管道端口引用数目的影响.
这里处在p1进程中,且p1进程此时都close掉对主进程最初创建的两个管道符的引用,p3应该是无法继承到管道符了
但是在p1,p2进程都close管道符的引用,但是主进程任然保留着对两个管道符的引用,所以,为了让管道符的读进程能够正常结束(读到EOF)
我们需要在主进程内执行关闭管道的写入端的操作
(不可以执行的过早,(如果早于p1,p2的fork之前,那么p1,p2将使用不了管道符了!))
*/
pid_t fp3 = fork();
if (fp3 == 0)
{
p3_code();
}
else
{
// p1_code() later;
/* p1等待p3结束
(父进程p0则需要等待p1,p2) */
// sprintln("p1进程将睡眠3s..");
// sleep(3);
/* Wait for a child to die. When one does, put its status in *STAT_LOC
and return its process ID. For errors, return (pid_t) -1. */
// 不在乎子进程p3的遗言,则传递NULL给wait()
sprintln("p1 waiting for p3 exit...");
wait(NULL);
// dprint(WIFEXITED(status_));
sprintln("wow,p3 exited, p1's wait is ended!");
sprintln("p1 exit!");
// 结束p1
exit(EXIT_SUCCESS);
// exit(1time);
}
}
void p2_code()
{
/* p2:
关闭不用的读pipefd21[0];写入pipefd21[1](写完以后关闭pipefd21[1])
关闭不用的写pipefd12[1];读出pipefd12[0](读完以后,关闭pipefd12[0]) */
printf("\t");
printf("I am child prcess p2:%d;pp2:%d", getpid(), getppid());
printf("\n");
// 写入管道
// sleep(2);
// sprintln("p2延迟3秒再发送消息给p1");
// timer("p2", 3);
/* Close unused read end */
close(pipefd21[0]);
char *msg = "Child process p2 is sending a message!";
// msg = "short msg!";
// 发送信息给p1:
// write(pipefd21[1], msg, strlen(msg));
sprintln("p2逐字符的发送字符(体验管道在被写入彻底结束前(EOF)对读进程的阻塞)...");
// 但是在EOF前,读进程还是可以和写进程交替操作管道,读空管道后,就等待写进程继续写,或者管道写入端被彻底关闭(EOF)
send_char_by_char(msg, pipefd21[1]);
close(pipefd21[1]); /* Reader will see EOF */
sprintln("p2完成全部字符消息的发送!");
/* 每次读取一字节(char)
循环读取,结束为止 */
sleep(1);
close(pipefd12[1]);
sprintln("p2读取p1发送来的信息...");
while (read(pipefd12[0], &buf, 1) > 0)
{ /* 每读取完一个字节,就打印到屏幕上 */
// #define STDOUT_FILENO 1 /* Standard output. */
write(STDOUT_FILENO, &buf, 1);
// sleep(1); //调用sleep()让我们可以感受到,这个歌过程是逐字符(字节)读取和输出(到屏幕上的.)
}
// sprintln("sig");
write(STDOUT_FILENO, "\n", 1);
sprintln("😎p2没有被阻塞");
/* pipe读操作完毕,关闭pipe读取端(整型值文件描述符)的连接 */
close(pipefd12[0]);
// dprint(getpid());
// dprint(getppid());
// sprintln("p2 is sleeping...");
// timer("p2", time_);
/* 结束p2 */
sprintln("p2 exit!");
exit(EXIT_SUCCESS);
}
void p3_code()
{
// p3_code();
pre_print("\t\t");
printf("I am child process p3:%d;pp3:%d", getpid(), getppid());
printf("\n");
// dprint(getpid());
// dprint(getppid());
sprintln("p3 is sleeping..");
// sleep(1);
timer("p3", time_);
execl("/bin/ls", "ls", "-l", "/home/cxxu/djangoProjects", (void *)NULL);
/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered,
perform stdio cleanup, and terminate program execution with STATUS. */
sprintln("p3 exit!");
exit(EXIT_SUCCESS);
}
效果

在这里插入图片描述

参考书籍:linux_unix system programming references books

以下书籍电子版可以从zlibrary上下载(yandex->zlibrary)

有时候,仅仅靠man手册还是难以将某些问题搞清楚
使用搜索引擎搜索资料不够系统
阅读经典书记可以帮助我们理解细节,体验示例代码,系统的学习系统编程

posted @   xuchaoxin1375  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示