一、实验目的
1、了解什么是信号。
2、熟悉LINUX系统中进程之间软中断通信的基本原理。
3、理解进程的同步关系。
4、掌握用信号实现进程间的同步操作。
5、了解什么是管道。
6、熟悉UNIX/LINUX支持的管道通信方式。
二、实验内容
1、阅读下列程序,执行程序并给出结果截屏,分析程序功能。(2分)
(1)
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int k;
void func()
{ k=0; }
main()
{ int p;
while((p=fork())==-1);
if(p==0) //子进程
{ k=1;
signal(10,func);
while(k!=0);
printf("Child process is killed by parent!\n");
}
else //父进程
{ sleep(1);
kill(p,10);
wait(0);
printf("Parent process has killed!\n");
}
}
编译及执行过程和结果截屏:
程序实现功能(父进程和子进程分别做了什么?):
父进程先执行,创建子进程后休眠 1 秒,然后向子进程发送信号,并等待子进程退出。在这种情况下,子进程接收到信号后,执行 func() 函数,将 k 置为 0,然后打印 "Child process is killed by parent!"。随后父进程继续执行,打印 "Parent process has killed!"。
子进程先执行,在接收到信号后执行 func() 函数,将 k 置为 0,然后退出。父进程接着执行,休眠 1 秒,然后尝试向已经退出的子进程发送信号,但是由于子进程已经退出,所以 kill() 函数失败。父进程继续执行,打印 "Parent process has killed!"。
父子进程竞争情况下,可能会出现不确定的结果。例如,父进程发送信号前子进程已经退出,这种情况下父进程会等待一个不存在的进程退出,可能会产生错误。
(2)
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
int k=1;
void func()
{ k=0; }
main()
{
signal(SIGINT,func);
while(k!=0);
printf("\n main process is killed by keybreak!\n");
}
编译及执行过程和运行结果截屏:
解释程序实现的功能:
1. 在程序运行期间,通过 `signal(SIGINT,func)` 函数调用注册了一个信号处理函数 `func()`。这意味着当程序接收到 `SIGINT` 信号时(通常由键盘输入 Ctrl+C 产生),将执行 `func()` 函数。
2. `func()` 函数被定义为将全局变量 `k` 的值设为 0。这表明当程序接收到 `SIGINT` 信号时,会将 `k` 的值修改为 0。
3. 主函数中的 `while(k!=0)` 循环会不断检查 `k` 的值是否为 0。如果 `k` 的值变为 0(即接收到了 `SIGINT` 信号),则循环结束。
4. 当循环结束时,程序会打印一条消息:"main process is killed by keybreak!"。这条消息表明主进程由于键盘输入而被终止。
因此,该程序的功能是当用户在程序运行过程中按下 Ctrl+C 时,主进程会接收到 `SIGINT` 信号,执行 `func()` 函数将全局变量 `k` 的值设为 0,然后退出循环并打印提示消息,程序结束运行。
2、编写一段程序,使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按ctrl+c键),当捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后,分别输出下列信息后终止:
Child process 1 is killed by parent!
Child process 2 is killed by parent!
父进程等待两个子进程终止后,输出以下信息后终止:
Parent process is killed!
要求给出编译及执行过程和运行结果截屏。(2分)
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int k=1;
void func()
{
k=0;
}
int main()
{
signal(SIGINT, func);
while (k != 0);
int p1, p2;
while ((p1=fork()) == -1);
if (p1 == 0) // 第一个子进程
{
k=1;
signal(10, func);
while (k != 0);
printf("Child process 1 is killed by parent!\n");
exit(0);
}
else // 父进程
{
sleep(1);
while ((p2=fork()) == -1);
if (p2 == 0) // 第二个子进程
{
k=1;
signal(10, func);
while (k != 0);
printf("Child process 2 is killed by parent!\n");
exit(0);
}
else // 父进程
{
sleep(1);
kill(p1, 10);
kill(p2, 10);
printf("Parent process is killed!\n");
}
}
return 0;
}
在父进程中,使用 signal(SIGINT, func) 注册了 SIGINT 信号的处理函数 func,这表示当父进程收到 SIGINT 信号(通常是通过键盘输入 Ctrl+C 产生)时,会将全局变量 k 设为 0。
父进程进入了一个无限循环,等待 k 的值变为 0。这意味着父进程会一直等待直到收到 SIGINT 信号,或者其他方式修改了 k 的值。
父进程创建了两个子进程 p1 和 p2。每个子进程执行类似的操作:在无限循环中等待 k 的值变为 0,然后输出相应的消息并终止。
在父进程中,先创建并杀死了第一个子进程 p1,然后创建并杀死了第二个子进程 p2。之后输出了 "Parent process is killed!" 消息。
为了演示父子进程之间的信号通信。父进程通过键盘输入 Ctrl+C 触发 SIGINT 信号,子进程通过 SIGUSR1 信号接收到父进程的通知,然后输出相应的消息。这种方式可以通过信号来实现进程之间的通信和协作。
3、编写程序,利用信号实现司机售票员同步操作问题。要求给出编译及运行过程和结果截图。(2分)
参考程序框架:
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int k;
void func()
{ k=0; }
main()
{ int p;
int count=3;
signal(12,func);
while((p=fork())==-1);
if(p>0) //司机进程
{ while(count)
{ k=1;
sleep(1);
printf("driver:drive\n");
printf("driver:stop\n");
kill(p,10); //发送信号给售票员
while(k);
printf("driver:start\n");
count--;
}
}else //售票员进程
{ signal(10,func);
while(count)
{ k=1;
printf("conductor:sell ticket.\n");
while(k);
printf("conductor:open door.\n");
printf("conductor:close door.\n");
int ppid = getppid(); //取得司机进程的识别码
kill(ppid,12); //发送信号给司机
count--;
}
}
}
编译及执行过程和结果截屏:
1. 首先,在主函数中定义了两个变量 `p` 和 `count`。`p` 用于保存 `fork()` 函数的返回值,`count` 用于表示循环执行的次数。
2. 使用 `signal(12, func)` 注册了信号处理函数 `func()`,该函数在接收到编号为 12 的信号时会将全局变量 `k` 设为 0。编号为 12 的信号在程序中用于司机和售票员之间的通信。
3. 在一个循环中,通过 `fork()` 创建了子进程。父进程中 `p` 的值大于 0,而子进程中 `p` 的值等于 0。
4. 如果是父进程(司机进程),则执行如下操作:
- 将 `k` 设为 1,表示司机正在驾驶。
- 打印 "driver:drive",表示司机正在驾驶。
- 打印 "driver:stop",表示司机停车。
- 通过 `kill(p, 10)` 向售票员进程发送信号 10(`SIGUSR1`),告知售票员停车。
- 进入循环,等待售票员处理完毕(`k` 变为 0),然后再次开始驾驶。
5. 如果是子进程(售票员进程),则执行如下操作:
- 使用 `signal(10, func)` 注册了信号处理函数,该函数在接收到编号为 10 的信号时将 `k` 设为 0。
- 进入循环,等待司机停车信号。
- 收到司机停车信号后,将 `k` 设为 1,表示售票员正在处理。
- 打印 "conductor:sell ticket.",表示售票员正在售票。
- 发送信号 12(`SIGUSR2`)给司机进程,告知司机售票员已经完成任务。
- 等待司机开始行驶的信号。
6. 程序最终会按照循环的次数执行上述操作,直到循环结束。
4、编制一段程序,实现进程的管道通信。使用pipe()建立一条管道线。两个子进程p1和p2分别向管道各写一句话:
Child 1 is sending message!
Child 2 is sending message!
而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。(1分)
<参考程序>
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
int pid1,pid2;
main()
{ int fd[2];
char OutPipe[100],InPipe[100];
pipe(fd);
while((pid1=fork())== -1);
if(pid1==0)
{ sprintf(OutPipe,"child 1 process is sending message!");
write(fd[1],OutPipe,50);
sleep(5);
exit(0);
}
else
{ while((pid2=fork())==-1);
if(pid2==0)
{ sprintf(OutPipe,"child 2 process is sending message!");
write(fd[1],OutPipe,50);
sleep(5);
exit(0);
}
else
{ wait(0);
read(fd[0],InPipe,50);
printf("%s\n",InPipe);
wait(0);
read(fd[0],InPipe,50);
printf("%s\n",InPipe);
exit(0);
}
}
}
编译及执行过程和运行结果截屏。
这段代码实现了一个父进程创建两个子进程的过程,其中每个子进程都向父进程通过管道发送一条消息,然后父进程接收这两条消息并输出
1. 父进程创建了一个管道 `fd`,用于父子进程之间的通信。
2. 父进程通过 `fork()` 创建了第一个子进程 `pid1`。如果创建失败,则继续尝试创建,直到成功为止。
3. 如果当前进程是第一个子进程,则向管道中写入一条消息 "child 1 process is sending message!",然后睡眠 5 秒后退出。
4. 如果当前进程是父进程,则继续创建第二个子进程 `pid2`,并进行类似的操作。
5. 如果当前进程是第二个子进程,则向管道中写入一条消息 "child 2 process is sending message!",然后睡眠 5 秒后退出。
6. 父进程在两个子进程都退出后,使用 `wait()` 函数等待子进程退出,并通过管道读取两个子进程发送的消息。
7. 父进程读取管道中的消息,并打印输出。
总之该程序通过管道实现了父进程与两个子进程之间的通信,子进程向父进程发送了各自的消息,而父进程则接收并打印输出这些消息。
5、在父进程中用pipe()建立一条管道线,往管道里写信息,两个子进程接收父进程发送的信息。(2分)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
int main() {
int fd[2];
pid_t pid1, pid2;
char message[] = "父进程发送给子进程的消息";
char buffer[100];
if (pipe(fd) < 0) {
perror("管道创建失败");
exit(EXIT_FAILURE);
}
if ((pid1 = fork()) < 0) {
perror("子进程1创建失败");
exit(EXIT_FAILURE);
}
if (pid1 == 0) {
// 子进程1
close(fd[1]); // 关闭管道写端
read(fd[0], buffer, sizeof(buffer));
printf("子进程1接收到: %s\n", buffer);
close(fd[0]); // 关闭管道读端
exit(EXIT_SUCCESS);
}
if ((pid2 = fork()) < 0) {
perror("子进程2创建失败");
exit(EXIT_FAILURE);
}
if (pid2 == 0) {
// 子进程2
close(fd[1]); // 关闭管道写端
read(fd[0], buffer, sizeof(buffer));
printf("子进程2接收到: %s\n", buffer);
close(fd[0]); // 关闭管道读端
exit(EXIT_SUCCESS);
}
// 父进程
close(fd[0]); // 关闭管道读端
write(fd[1], message, strlen(message) + 1);
write(fd[1], message, strlen(message) + 1);
close(fd[1]); // 关闭管道写端
wait(NULL);
wait(NULL);
return 0;
}
父进程通过管道向两个子进程发送消息,并且两个子进程分别接收并输出这些消息。
1. 创建了一个管道 `fd`,用于父子进程之间的通信。
2. 父进程通过 `fork()` 创建了两个子进程 `pid1` 和 `pid2`。
3. 子进程1中,关闭了管道的写端,然后通过管道的读端接收父进程发送的消息,并打印输出。
4. 子进程2中,同样关闭了管道的写端,然后通过管道的读端接收父进程发送的消息,并打印输出。
5. 父进程中,关闭了管道的读端,然后通过管道的写端向管道中写入两条消息,然后关闭了写端。
6. 父进程使用 `wait()` 函数等待两个子进程退出。
总之该程序通过管道实现了父进程向两个子进程发送消息的功能,而两个子进程分别接收并输出了父进程发送的消息。
6、编程用管道实现父子进程间的双向通信。要求给出设计思路,调试通过的程序和编译执行过程以及结果截屏。(附加题)
设计思路:
父进程创建两个管道,一个用于父进程向子进程发送消息,另一个用于接收子进程发送的消息。父进程创建子进程。子进程同样创建两个管道,一个用于子进程向父进程发送消息,另一个用于接收父进程发送的消息。父进程和子进程可以根据需要进行读取和写入管道。当父进程需要向子进程发送消息时,它将消息写入第一个管道,子进程从该管道读取消息。当子进程需要向父进程发送消息时,它将消息写入第二个管道,父进程从该管道读取消息。父子进程之间需要遵循一定的通信协议,以确保消息的正确传递和处理。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#define BUFFER_SIZE 100
int main() {
int parent_to_child[2]; // 父进程向子进程发送消息的管道
int child_to_parent[2]; // 子进程向父进程发送消息的管道
pid_t pid;
// 创建父进程向子进程发送消息的管道
if (pipe(parent_to_child) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// 创建子进程向父进程发送消息的管道
if (pipe(child_to_parent) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
close(parent_to_child[1]); // 子进程关闭向子进程发送消息的写端
close(child_to_parent[0]); // 子进程关闭向父进程发送消息的读端
// 子进程从父进程发送消息的管道读取消息并输出
char buffer[BUFFER_SIZE];
read(parent_to_child[0], buffer, BUFFER_SIZE);
printf("子进程接收到父进程的消息:%s\n", buffer);
// 子进程向父进程发送消息
const char *msg_to_parent = "Hello, parent!";
write(child_to_parent[1], msg_to_parent, strlen(msg_to_parent) + 1);
exit(EXIT_SUCCESS);
} else { // 父进程
close(parent_to_child[0]); // 父进程关闭向子进程发送消息的读端
close(child_to_parent[1]); // 父进程关闭向父进程发送消息的写端
// 父进程向子进程发送消息
const char *msg_to_child = "Hello, child!";
write(parent_to_child[1], msg_to_child, strlen(msg_to_child) + 1);
// 父进程从子进程发送消息的管道读取消息并输出
char buffer[BUFFER_SIZE];
read(child_to_parent[0], buffer, BUFFER_SIZE);
printf("父进程接收到子进程的消息:%s\n", buffer);
// 等待子进程退出
wait(NULL);
}
return 0;
}
三、实验总结和体会(1分)
实验涉及了进程间通信的不同机制,分别是信号处理和管道通信。通过这些实验,你学习了如何在进程之间传递信息,以及如何利用不同的通信方式实现进程之间的协作和交互
进程间通信: 通过管道、信号等机制,你了解了不同进程间通信的方式和原理。通过这些实验,你掌握了进程间通信的基本概念,并且能够使用这些通信机制在多个进程之间进行数据交换和同步。
信号处理: 你研究了信号的基本概念和处理机制,包括注册信号处理函数、发送信号、处理信号等。通过信号处理,你可以实现进程间的异步通信和事件处理,对于编写并发和异步程序非常重要。
创建管道:使用 pipe() 系统调用创建管道,获取用于通信的文件描述符。
创建子进程:使用 fork() 系统调用创建子进程,使得父子进程共享同一份代码和数据,但各自拥有独立的地址空间。
父子进程间通信:父子进程通过管道进行通信。父进程向管道写入数据,子进程从管道读取数据;子进程向管道写入数据,父进程从管道读取数据。
关闭文件描述符:在通信完成后,需要关闭未使用的文件描述符,以释放资源。
子进程退出:子进程在完成任务后通过 exit() 函数退出,避免成为僵尸进程。