本文介绍如何通过信号实现子进程按序(同步)向管道中写入数据。


要求: 编写程序实现进程的管道通信。用系统调用pipe()建立一管道,二个子进程P1和P2分别向管道各写一句话:

Child 1 is sending a message!
Child 2 is sending a message!

父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。

实验指导书上的例子修改成能直接编译运行的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void main(){
int pid1,pid2;//由于需要两个子进程,所以定义两个pid
int fd[2];//pipe管道两端
char outpipe[100],inpipe[100];
pipe(fd); //使用pipe函数创建管道
while ((pid1=fork()) == -1);//尝试新建子进程,如果成功才往下执行
if(pid1 == 0){//pid为0时为子进程
lockf(fd[1],1,0);//锁定写入端
sprintf(outpipe,"child 1 process is sending message!"); /把串放入数组outpipe中/
write(fd[1],outpipe,50); /向管道写长为50字节的串/
sleep(5); /自我阻塞5秒/
lockf(fd[1],0,0);//解除输入端锁定
exit(0);//正常退出程序
} else {
while((pid2=fork()) == -1);
if(pid2 == 0) {
lockf(fd[1],1,0); /互斥/
sprintf(outpipe,"child 2 process is sending message!");
write(fd[1],outpipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
} else {
wait(0); /同步/
read(fd[0],inpipe,50); /从管道中读长为50字节的串/
printf("%s\n",inpipe);
wait(0);//等待子进程结束
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);
}
}
}

代码分析:

两个子进程中代码逻辑都一样,只是写入的字符串不一样。对于两个子进程,谁先执行完lockf,谁就先写入数据到管道中。这和实验要求先读出子进程1的数据再读出子进程2的数据不符。

下面是根据上面的例子修改过后完全符合实验要求的例子,可以直接编译运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int flag = 0;
int pid1,pid2;//由于需要两个子进程,所以定义两个pid

void parent(){
kill(pid2,SIGUSR1);
}
void child2(){
flag = 1;
}

void main(){

sigset_t set;
sigset_t old;
sigemptyset(&set);
sigaddset(&set,SIGUSR1);
sigprocmask(SIG_BLOCK,&set,&old);

int fd[2];//pipe管道两端
char outpipe[100],inpipe[100];
pipe(fd); //使用pipe函数创建管道
while ((pid1=fork()) == -1);//尝试新建子进程,如果成功才往下执行
if(pid1 == 0){//pid为0时为子进程
//lockf(fd[1],1,0);//锁定写入端
sprintf(outpipe,"child 1 process is sending message!"); /把串放入数组outpipe中/
write(fd[1],outpipe,50); /向管道写长为50字节的串/
//sleep(5); /自我阻塞5秒/
//lockf(fd[1],0,0);//解除输入端锁定
kill(getppid(),SIGUSR1);
exit(0);//正常退出程序
} else {
while((pid2=fork()) == -1);
if(pid2 == 0) {
signal(SIGUSR1,child2);
sigprocmask(SIG_UNBLOCK,&set,&old);
while(!flag);
//lockf(fd[1],1,0); /互斥/
sprintf(outpipe,"child 2 process is sending message!");
write(fd[1],outpipe,50);
//sleep(5);
//lockf(fd[1],0,0);
exit(0);
} else {
signal(SIGUSR1,parent);
sigprocmask(SIG_UNBLOCK,&set,&old);
wait(0); /同步/
read(fd[0],inpipe,50); /从管道中读长为50字节的串/
printf("%s\n",inpipe);
wait(0);//等待子进程结束
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);
}
}
}

代码解析:

代码在原来的基础上用信号机制实现同步,即子进程1在子进程2前执行。当然如果只是简单的使用sleep()函数使子进程2的睡眠时间比子进程1睡眠时间长的话确实可以满足实验要求,但是我觉得出题者应该不是这个意思。

对于子进程1,由于其先执行,所以直接让其向管道中写入信息然后向父进程发送完成的信号即可。

为什么不直接向子进程2发送信号呢?因为子进程1拿不到子进程2的pid,但是父进程可以,所以子进程1借助父进程实现向子进程2发送信号。

对于子进程2,由于其要在子进程1之后执行,所以要让其卡在while循环那里,只有父进程向其发送子进程1已经完成数据写入的操作后,才可以往下执行。

对于父进程来说,其作为中间人,当接收到子进程1发送过来的信号后就直接转发给子进程2,让子进程2往下执行,向管道写入数据。

main函数中的这段代码的作用是屏蔽SIGUSR1信号,因为有可能在信号还没有注册(signal)的情况下就已经发送(kill)信号了,导致信号丢失。

下面的代码表示如果出现发送信号在注册信号之前那么就可以将信号屏蔽,然后等到注册完信号后将其解除屏蔽即可对信号进行处理,而不会丢失。

1
2
3
4
5
sigset_t set;
sigset_t old;
sigemptyset(&set);
sigaddset(&set,SIGUSR1);
sigprocmask(SIG_BLOCK,&set,&old);

下面这段代码就表示注册以后将信号解除屏蔽,然后就可以对信号进行捕获处理的,即执行parent函数。

1
2
signal(SIGUSR1,parent);
sigprocmask(SIG_UNBLOCK,&set,&old);

还有就是修改后的代码将lockf函数注释掉了,是因为可以通过信号实现同步(子进程1向管道写入数据后子进程2才能写入),所以不存在两个子进程同时向管道写入的问题,也就不需要lockf

最后修改日期:2020年5月14日

留言

撰写回覆或留言