linux进程间通信
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
上机四:Linux 进程间通信
1. 目的
(1).理解信号和管道的概念及实现进程间通信的原理。 (2).掌握信号通信机制,学会通过信号实现进程间通信。 (3).掌握管道及命名管道通信机制,学会通过管道实现进程间通信。 (4). 掌握和使用消息队列实现进程间通信。 (5). 掌握和使用共亨主存实现进程间通信。 (6).了解和使用信号量实现进程同步。
7-5.c 7-7.c 7-8zhang.c 7-8li.c 7-10.c 7-12write.c 7-12read.c
3. 步骤
3.1. 信号通信 信号是一种简洁的通信方式,进程或内核均可使用信号通知一个进程有
某种 事件发生。信号全称为软中断信号,也称作软中断,它实质上是在软件层
1
次上对中断机制的一种模拟,一个进程收到一个信号与 CPU收到一个中断请 求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,用来通知 进程有异步事件发生。进程之间可以通过函数,如kill()和alarm()等传递软中 断信号。内核也可在发生内部事件时向进程发送信号,通知进程发生了某个事 件。信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
#include<sys/types.h>
/*文件预处理,包含进程控制函数库*/
#include<unistd.h>
/*文件预处理,包含进程控制函数库*/
void fun_ctrl_c();
/*自定义信号处理函数声明*/
/*C 程序的主函数,开始入口*/ int main () {
int i; sigset_t set,pendset; struct sigaction action;
3.3. 命名管道
匿名管道技术可以用于连接具有共同祖先的进程,例如父子进程间的通信, 但它无法实现不同用户的进程间的信息共享。匿名管道不能常设,当访问管道的进 程终止时,管道需要撤销。这些限制给匿名管道的使用带来不少限制,命名管道克 服了这些限制。
命名管道也称为 FIFO,是一种永久性的机构。FIFO 文件也具有文件名、文 件长度、访问许可权等属性,以 FIFO 的文件形式存在于文件系统中。它可以像其 它 Linux 文件那样被打开、关闭和删除,所以任何进程都通过路径找到它。进程间 彼此能够通过 FIFO 相互通信(能够访问该路径的进程以及 FIFO 的创建进程之间), 因此,通过 FIFO 不相关的进程也能交换数据。值得注意的是,FIFO 严格遵循“先 进先出”原则,对匿名管道及命名管道的读总是从开始处返回数据,对它们的写则 把数据添加到末尾。一般文件的 I/O 函数都可以用于命名管道,如 close()、read() 和 write()等。
父子进程之间的信号通信可分为阻塞型通信和非阻塞型通信两种情形。在阻 塞模式下,被阻塞的信号会一直保持挂起,直到该信号不再阻塞为止;但在非阻塞
3
模式下,信号将直接返回而不需等待处理。 在阻塞型通信情形中,父进程可以使用wait()或waitpid()函数等待子进程结
束。为避免产生僵死子进程,也可以循环调用wait()或watipid()函数来等待所有 子进程的结束。但此时最好的方法是让子进程在结束时,向父进程发送SIGCHLD 信号,父进程通过signal()或sigaction()函数来响应子进程的结束。
非阻塞型通信情形下父子进程共存,当产生相关信号,如SIGINT时,父 子进程都能接收到此信号,只是先由父进程响应,再由父进程把此信号传递给 子进程。但需要注意,如果父进程没有对该信号的自定义处理,则父子进程都 接受信号的默认处理。
3.2. 管道通信 管道技术是 Linux 操作系统中由来已久的一种进程间通信机制,分为匿名管
2. 内容
进程间通信机制可以主要两类:传统的进程间通信(包括信号、管道、命名 管道)和 System V 的进程间通信(包括消息队列、信号量、共享主存)。
a)根据实验原理描述的背景,编写基于 C 语言的程序。 b)在 Linux 操作系统中编译、运行、调试程序,并观察分析实验结果。 主要上机分析代码文件。
【例 7.7】设计一个程序,要求创建一个管道,复制进程,父进程往管道中 写入字符串,子进程从管道中读取并输出字符串。
分析 主程序调用 pipe 函数创建一个管道,调用 fork 函数创建进程; 父进程中先用 close(pipe_fd[0])关闭 pipe_fd[0],剩下的 pipe_fd[1]用来把数据
perror("加入信号集合错误");
2
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)/*把信号集合加入到当前进程的阻塞集合中 */
perror("往信号阻塞集增加一个信号集合错误"); else {
for(i=0;i<5;i++) {
printf("显示此文字,表示程序处于阻塞信号状态!\n"); sleep(2); } } if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)/*当前的阻塞集中删除一个信号集合*/
分析:为了清楚看到结果,主程序等待 2 秒后打印输出一条语句,共打印输 出 5 条语句;要使用户按下中断键跳到自定义函数,还是用比较简单的 signal 函 数,用户按 Ctrl+C 键不能影响正在运行的程序,就需要阻塞中断信号,用 sigemptyset、sigaddset 和 sigprocmask 三个函数。在程序主体运行完毕后,用 sigprocmask 函数解除中断信号的阻塞,转入自定义函数运行。
/*7-7.c 程序:管道的创建和读写*/
#include<stdio.h>
/*文件预处理,包含标准输入输出库*/
#include<stdlib.h>
/*文件预处理,包含 system、exit 等函数库*/
#include<sys/types.h>
/*文件预处理,包含 waitpid、kill、raise 等函数库*/
ห้องสมุดไป่ตู้
close(pipe_fd[0]);
exit(0);
}
else
/*父进程运行代码段*/
{
close(pipe_fd[0]);
printf("请从键盘输入写入管道的字符串\n");
scanf("%s",buf_w);
if(write(pipe_fd[1],buf_w,strlen(buf_w))!=-1)
perror("从信号阻塞集删除一个信号集合错误"); }
/*自定义信号处理函数*/
void fun_ctrl_c()
{
printf("\t 你按了 Ctrl+C 系统是不是很长时间没理你?\n");
printf("\t 信号处理函数:有什么要处理,在处理函数中编程!\n");
printf("\t 此例不处理,直接结束!\n");
命名管道以“先进先出”形式的文件存在于文件系统中。因此,只要可以访 问该文件路径,就能够彼此通过命名管道相互通信。为实现多进程间基于命名管道 的通信,首先使用 mkfifo()创建一个命名管道。随后可使用—般的文件 I/O 函数, 如 open()、close()、read()、write()等,来对它进行操作,从而实现多进程间的通信。
(void) signal(SIGINT,fun_ctrl_c); /*调用 fun_ctrl_c 函数*/
if(sigemptyset(&set)<0)
/*初始化信号集合*/
perror("初始化信号集合错误");
if(sigaddset(&set,SIGINT)<0)
/*把 SIGINT 信号加入信号集合*/
printf("父进程向管道写入:%s\n",buf_w);
5
close(pipe_fd[1]); waitpid(result,NULL,0);//调用 waitpid, 阻塞父进程,等待子进程退出 exit(0); } }
步骤 1 编辑源程序代码。 [root@localhost root]#vi 7-7.c 步骤 2 用 gcc 编译程序。 [root@localhost root]#gcc 7-7.c -o 7-7 步骤 3 运行程序。 [root@localhost root]#./7-7 请从键盘输入写入管道的字符串 aabbbcccc 父进程向管道写入:aabbbcccc 子进程从管道读取 9 个字符,读取的字符串是:aabbbcccc
exit(0) ;
}
else if (result==0)
/*子进程运行代码段*/
{
close(pipe_fd[1]);
if((r_num=read(pipe_fd[0],buf_r,100))>0)
printf("子进程从管道读取%d 个字符,读取的字符串是:%s\n",r_num,buf_r);
道(pipe)和命名管道(FIFO),它们都通过内核缓冲区按先进先出的方式传输数据。 简单的说,管道是它把一个进程的输出和另一个进程的输入连接在一起。一个进程 (写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
匿名管道只能用于具有亲缘关系的两进程间通信。一个进程在由 pipe()创建 管道后,一般再用 fork()函数复制一个子进程,然后通过管道实现父子进程间的通 信,也可通过 fork()函数复制多个进程实现兄弟进程之间的通信。管道两端可分别 用描述字 fd[0]以及 fd[1]来描述,其中一端只能用于读,由描述字 fd[0]表示,称为 管道读端;另一端则只能用于写,由描述字 fd[1]来表示,称为管道写端。
(void) signal(SIGINT,SIG_DFL);
/*重新恢复 SIGINT 信号的系统默认处理*/
}
步骤 1 编辑源程序代码。 步骤 2 用 gcc 编译程序。 [root@localhost root]#gcc 7-5.c -o 7-5 步骤 3 运行程序。 编译成功后,运行可执行文件 7-5,输入“./7-5”,此时系统会出现运行结果, 等待 2 秒打印输出一段相同的文字,共打印输出 5 条。 如果在打印输出文字的时候,用户没有按下 Ctrl+C 键,程序在打印输出完 5 条信息后结束运行。 如果在打印输出文字的时候,用户按下 Ctrl+C 键。程序没有马上进入信号处 理程序,而是仍然打印输出文字,等到打印输出完毕,程序才进入自定义信号处 理函数。
{
printf("创建管道失败");
return -1;
}
result=fork();
/*调用 fork 函数,复制进程,返回值存在变量 result 中*/
if(result<0) /*通过 result 的值来判断 fork 函数的返回情况,这儿进行出错处理*/
{
perror("创建子进程失败");
/*7-5.c 程序:先阻塞用户发出的中断信号,程序主体运行完毕后才进入自定义信号处理函数
*/
#include<stdio.h>
/*文件预处理,包含标准输入输出库*/
#include<stdlib.h>
/*文件预处理,包含进程控制函数库*/
#include<signal.h>
/*文件预处理,包含进程通信函数库*/
int pipe_fd[2];
char buf_r[100],buf_w[100];
memset(buf_r,0,sizeof(buf_r)); /*把 buf_r 所指的内存区域的前 sizeof(buf_r)得到的字节置为 0,初始化清空的操作*/
if(pipe(pipe_fd)<0)
/*调用 pipe 函数,创建一个管道*/
一个完整的信号生命周期从信号发送开始,结束于相应的处理函数执行完毕。 整个生命周期分为信号诞生、信号在进程中注册、信号在进程中注销和信号处理函 数执行完毕。
【例 7.5】设计一个程序,要求主程序运行时,即使用户按下中断键(Ctrl+C), 也不能影响正在运行的程序,即让信号处于阻塞状态,当主体程序运行完毕后才 进入自定义信号处理函数。
4
写入管道,利用 write 函数写入字符串,然后用 close(pipe_fd[1])关闭 pipe_fd[1]; 子进程是用 close(pipe_fd[1])关闭 pipe_fd[1],剩下的 pipe_fd[0]用来从管道读
取数据,利用 read 函数读取字符串,然后用 close(pipe_fd[0])关闭 pipe_fd[0]。
#include<sys/wait.h>
/*文件预处理,包含 waitpid 函数库*/
#include<unistd.h>
/*文件预处理,包含进程控制函数库*/
#include<string.h>
int main ()
/*C 程序的主函数,开始入口*/
{
pid_t result;
int r_num;
1. 目的
(1).理解信号和管道的概念及实现进程间通信的原理。 (2).掌握信号通信机制,学会通过信号实现进程间通信。 (3).掌握管道及命名管道通信机制,学会通过管道实现进程间通信。 (4). 掌握和使用消息队列实现进程间通信。 (5). 掌握和使用共亨主存实现进程间通信。 (6).了解和使用信号量实现进程同步。
7-5.c 7-7.c 7-8zhang.c 7-8li.c 7-10.c 7-12write.c 7-12read.c
3. 步骤
3.1. 信号通信 信号是一种简洁的通信方式,进程或内核均可使用信号通知一个进程有
某种 事件发生。信号全称为软中断信号,也称作软中断,它实质上是在软件层
1
次上对中断机制的一种模拟,一个进程收到一个信号与 CPU收到一个中断请 求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,用来通知 进程有异步事件发生。进程之间可以通过函数,如kill()和alarm()等传递软中 断信号。内核也可在发生内部事件时向进程发送信号,通知进程发生了某个事 件。信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
#include<sys/types.h>
/*文件预处理,包含进程控制函数库*/
#include<unistd.h>
/*文件预处理,包含进程控制函数库*/
void fun_ctrl_c();
/*自定义信号处理函数声明*/
/*C 程序的主函数,开始入口*/ int main () {
int i; sigset_t set,pendset; struct sigaction action;
3.3. 命名管道
匿名管道技术可以用于连接具有共同祖先的进程,例如父子进程间的通信, 但它无法实现不同用户的进程间的信息共享。匿名管道不能常设,当访问管道的进 程终止时,管道需要撤销。这些限制给匿名管道的使用带来不少限制,命名管道克 服了这些限制。
命名管道也称为 FIFO,是一种永久性的机构。FIFO 文件也具有文件名、文 件长度、访问许可权等属性,以 FIFO 的文件形式存在于文件系统中。它可以像其 它 Linux 文件那样被打开、关闭和删除,所以任何进程都通过路径找到它。进程间 彼此能够通过 FIFO 相互通信(能够访问该路径的进程以及 FIFO 的创建进程之间), 因此,通过 FIFO 不相关的进程也能交换数据。值得注意的是,FIFO 严格遵循“先 进先出”原则,对匿名管道及命名管道的读总是从开始处返回数据,对它们的写则 把数据添加到末尾。一般文件的 I/O 函数都可以用于命名管道,如 close()、read() 和 write()等。
父子进程之间的信号通信可分为阻塞型通信和非阻塞型通信两种情形。在阻 塞模式下,被阻塞的信号会一直保持挂起,直到该信号不再阻塞为止;但在非阻塞
3
模式下,信号将直接返回而不需等待处理。 在阻塞型通信情形中,父进程可以使用wait()或waitpid()函数等待子进程结
束。为避免产生僵死子进程,也可以循环调用wait()或watipid()函数来等待所有 子进程的结束。但此时最好的方法是让子进程在结束时,向父进程发送SIGCHLD 信号,父进程通过signal()或sigaction()函数来响应子进程的结束。
非阻塞型通信情形下父子进程共存,当产生相关信号,如SIGINT时,父 子进程都能接收到此信号,只是先由父进程响应,再由父进程把此信号传递给 子进程。但需要注意,如果父进程没有对该信号的自定义处理,则父子进程都 接受信号的默认处理。
3.2. 管道通信 管道技术是 Linux 操作系统中由来已久的一种进程间通信机制,分为匿名管
2. 内容
进程间通信机制可以主要两类:传统的进程间通信(包括信号、管道、命名 管道)和 System V 的进程间通信(包括消息队列、信号量、共享主存)。
a)根据实验原理描述的背景,编写基于 C 语言的程序。 b)在 Linux 操作系统中编译、运行、调试程序,并观察分析实验结果。 主要上机分析代码文件。
【例 7.7】设计一个程序,要求创建一个管道,复制进程,父进程往管道中 写入字符串,子进程从管道中读取并输出字符串。
分析 主程序调用 pipe 函数创建一个管道,调用 fork 函数创建进程; 父进程中先用 close(pipe_fd[0])关闭 pipe_fd[0],剩下的 pipe_fd[1]用来把数据
perror("加入信号集合错误");
2
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)/*把信号集合加入到当前进程的阻塞集合中 */
perror("往信号阻塞集增加一个信号集合错误"); else {
for(i=0;i<5;i++) {
printf("显示此文字,表示程序处于阻塞信号状态!\n"); sleep(2); } } if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)/*当前的阻塞集中删除一个信号集合*/
分析:为了清楚看到结果,主程序等待 2 秒后打印输出一条语句,共打印输 出 5 条语句;要使用户按下中断键跳到自定义函数,还是用比较简单的 signal 函 数,用户按 Ctrl+C 键不能影响正在运行的程序,就需要阻塞中断信号,用 sigemptyset、sigaddset 和 sigprocmask 三个函数。在程序主体运行完毕后,用 sigprocmask 函数解除中断信号的阻塞,转入自定义函数运行。
/*7-7.c 程序:管道的创建和读写*/
#include<stdio.h>
/*文件预处理,包含标准输入输出库*/
#include<stdlib.h>
/*文件预处理,包含 system、exit 等函数库*/
#include<sys/types.h>
/*文件预处理,包含 waitpid、kill、raise 等函数库*/
ห้องสมุดไป่ตู้
close(pipe_fd[0]);
exit(0);
}
else
/*父进程运行代码段*/
{
close(pipe_fd[0]);
printf("请从键盘输入写入管道的字符串\n");
scanf("%s",buf_w);
if(write(pipe_fd[1],buf_w,strlen(buf_w))!=-1)
perror("从信号阻塞集删除一个信号集合错误"); }
/*自定义信号处理函数*/
void fun_ctrl_c()
{
printf("\t 你按了 Ctrl+C 系统是不是很长时间没理你?\n");
printf("\t 信号处理函数:有什么要处理,在处理函数中编程!\n");
printf("\t 此例不处理,直接结束!\n");
命名管道以“先进先出”形式的文件存在于文件系统中。因此,只要可以访 问该文件路径,就能够彼此通过命名管道相互通信。为实现多进程间基于命名管道 的通信,首先使用 mkfifo()创建一个命名管道。随后可使用—般的文件 I/O 函数, 如 open()、close()、read()、write()等,来对它进行操作,从而实现多进程间的通信。
(void) signal(SIGINT,fun_ctrl_c); /*调用 fun_ctrl_c 函数*/
if(sigemptyset(&set)<0)
/*初始化信号集合*/
perror("初始化信号集合错误");
if(sigaddset(&set,SIGINT)<0)
/*把 SIGINT 信号加入信号集合*/
printf("父进程向管道写入:%s\n",buf_w);
5
close(pipe_fd[1]); waitpid(result,NULL,0);//调用 waitpid, 阻塞父进程,等待子进程退出 exit(0); } }
步骤 1 编辑源程序代码。 [root@localhost root]#vi 7-7.c 步骤 2 用 gcc 编译程序。 [root@localhost root]#gcc 7-7.c -o 7-7 步骤 3 运行程序。 [root@localhost root]#./7-7 请从键盘输入写入管道的字符串 aabbbcccc 父进程向管道写入:aabbbcccc 子进程从管道读取 9 个字符,读取的字符串是:aabbbcccc
exit(0) ;
}
else if (result==0)
/*子进程运行代码段*/
{
close(pipe_fd[1]);
if((r_num=read(pipe_fd[0],buf_r,100))>0)
printf("子进程从管道读取%d 个字符,读取的字符串是:%s\n",r_num,buf_r);
道(pipe)和命名管道(FIFO),它们都通过内核缓冲区按先进先出的方式传输数据。 简单的说,管道是它把一个进程的输出和另一个进程的输入连接在一起。一个进程 (写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
匿名管道只能用于具有亲缘关系的两进程间通信。一个进程在由 pipe()创建 管道后,一般再用 fork()函数复制一个子进程,然后通过管道实现父子进程间的通 信,也可通过 fork()函数复制多个进程实现兄弟进程之间的通信。管道两端可分别 用描述字 fd[0]以及 fd[1]来描述,其中一端只能用于读,由描述字 fd[0]表示,称为 管道读端;另一端则只能用于写,由描述字 fd[1]来表示,称为管道写端。
(void) signal(SIGINT,SIG_DFL);
/*重新恢复 SIGINT 信号的系统默认处理*/
}
步骤 1 编辑源程序代码。 步骤 2 用 gcc 编译程序。 [root@localhost root]#gcc 7-5.c -o 7-5 步骤 3 运行程序。 编译成功后,运行可执行文件 7-5,输入“./7-5”,此时系统会出现运行结果, 等待 2 秒打印输出一段相同的文字,共打印输出 5 条。 如果在打印输出文字的时候,用户没有按下 Ctrl+C 键,程序在打印输出完 5 条信息后结束运行。 如果在打印输出文字的时候,用户按下 Ctrl+C 键。程序没有马上进入信号处 理程序,而是仍然打印输出文字,等到打印输出完毕,程序才进入自定义信号处 理函数。
{
printf("创建管道失败");
return -1;
}
result=fork();
/*调用 fork 函数,复制进程,返回值存在变量 result 中*/
if(result<0) /*通过 result 的值来判断 fork 函数的返回情况,这儿进行出错处理*/
{
perror("创建子进程失败");
/*7-5.c 程序:先阻塞用户发出的中断信号,程序主体运行完毕后才进入自定义信号处理函数
*/
#include<stdio.h>
/*文件预处理,包含标准输入输出库*/
#include<stdlib.h>
/*文件预处理,包含进程控制函数库*/
#include<signal.h>
/*文件预处理,包含进程通信函数库*/
int pipe_fd[2];
char buf_r[100],buf_w[100];
memset(buf_r,0,sizeof(buf_r)); /*把 buf_r 所指的内存区域的前 sizeof(buf_r)得到的字节置为 0,初始化清空的操作*/
if(pipe(pipe_fd)<0)
/*调用 pipe 函数,创建一个管道*/
一个完整的信号生命周期从信号发送开始,结束于相应的处理函数执行完毕。 整个生命周期分为信号诞生、信号在进程中注册、信号在进程中注销和信号处理函 数执行完毕。
【例 7.5】设计一个程序,要求主程序运行时,即使用户按下中断键(Ctrl+C), 也不能影响正在运行的程序,即让信号处于阻塞状态,当主体程序运行完毕后才 进入自定义信号处理函数。
4
写入管道,利用 write 函数写入字符串,然后用 close(pipe_fd[1])关闭 pipe_fd[1]; 子进程是用 close(pipe_fd[1])关闭 pipe_fd[1],剩下的 pipe_fd[0]用来从管道读
取数据,利用 read 函数读取字符串,然后用 close(pipe_fd[0])关闭 pipe_fd[0]。
#include<sys/wait.h>
/*文件预处理,包含 waitpid 函数库*/
#include<unistd.h>
/*文件预处理,包含进程控制函数库*/
#include<string.h>
int main ()
/*C 程序的主函数,开始入口*/
{
pid_t result;
int r_num;