title: 实验3 进程之间的通信
date: 2020-12-03 20:09:11
tags:
实验3 进程之间的通信
题目一
编写程序实现以下功能:
-
利用匿名管道实现父子进程间通信,要求
-
父进程发送字符串“hello child”给子进程;
-
子进程收到父进程发送的数据后,给父进程回复“hello farther”;
-
父子进程通信完毕,父进程依次打印子进程的退出状态以及子进程的pid。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { int fd1[2],fd2[2]; pipe(fd1); pipe(fd2); int pid; pid = fork(); if(pid < 0) perror("fork"); else if(pid == 0) { close(fd1[0]); close(fd2[1]); char str[12]; printf("This is the child!\n"); if(read(fd2[0],str,12) > 0) { printf("Received the news: %s\n",str); if(write(fd1[1],"hello father",12) < 0) perror("write"); } else perror("read"); exit(5); } else { int status; printf("This is the father!\n"); close(fd1[1]); close(fd2[0]); char buf[24] = "hello child"; if(write(fd2[1],buf,12) < 0) perror("write"); else { printf("Send news successful!\n"); } wait(&status); if(WIFEXITED(status)) { printf("The child's pid is: %d\n",pid); printf("The child's exited status is: %d\n",WEXITSTATUS(status)); } } return 0; }
运行截图
结果分析
管道作为较为原始的进程间的通信方式,在进程间的通信中被广泛使用。管道可分为有名管道和匿名管道,两者有相似之处又有一定的区别。
匿名管道的特点如下:
1.匿名管道是半双工的,数据只能朝一个放向流动,因此要实现两个进程的通信,就必须建立两个匿名管道;
2.匿名管道只能在管道尾部写入数据,从管道头部读取数据;fd[0]用于进程读取数据,fd[1]用于进程写入数据;
3.匿名管道不具备存储数据的能力,数据一旦被读取,其它进程将不能从该管道中读取数据;
4.匿名管道只能在有血缘关系的进程间通信;如父子进程和兄弟进程。
题目二
编写程序实现以下功能:
-
利用匿名管道实现兄弟进程间通信,要求
-
兄进程发送字符串“This is elder brother ,pid is (兄进程进程号)”给第进程;
-
第进程收到兄进程发送的数据后,给兄进程回复“This is younger brother ,pid is(第进程进程号)”;
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> int main() { int fd1[2],fd2[2]; pipe(fd1); pipe(fd2); int pid; pid = fork(); if(pid == 0) { printf("This is the elder brother!\n"); printf("The elder's father's pid is: %d\n",getppid()); close(fd1[1]); close(fd2[0]); char str1[64],str2[64]; sprintf(str1,"This is the elder brother,pid is %d",getpid()); if(write(fd2[1],str1,64) < 0) perror("write"); if(read(fd1[0],str2,64) < 0) perror("read"); else printf("The news from younger is: %s\n",str2); } else { if(fork() == 0) { printf("This is the younger brother!\n"); printf("The younger's father's pid is: %d\n",getppid()); close(fd1[0]); close(fd2[1]); char buf1[64],buf2[64]; if(read(fd2[0],buf1,64) > 0) { printf("The news form elder is: %s\n",buf1); sprintf(buf2,"This is the younger brother,pid is %d",getpid()); if(write(fd1[1],buf2,64) < 0) perror("write"); } else perror("read"); } } }
运行截图
结果分析
我们都知道利用fork函数能创建一个子进程,但是如何利用fork函数创建兄弟进程呢?
我们可以利用fork函数先建立一个子进程,在子进程中,将要发送的信息写入管道,然后再在父进程中再次调用fork函数,那么父进程里创建的子进程就是先前创建的进程的弟进程。我们可以让子进程将自己的父进程的pid打印,验证两进程是否是兄弟进程。
题目三
编写程序实现以下功能:
-
利用有名管道文件实现进程间通信,要求
-
写进程向有名管道文件写入10次“hello world”;
-
读进程读取有名管道文件中的内容,并依次打印。
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> int main() { int pid,fd,i; if(mkfifo("fifotest",0666) < 0) perror("mkfifo"); pid = fork(); if(pid < 0) perror("fork"); else if(pid == 0) { printf("This is the write process!\n"); int fd = open("fifotest",0666); for(i = 0; i < 10; i++) { if(write(fd,"hello world",12) < 0) perror("write"); sleep(1); } close(fd); } else { char str[128]; printf("This is the read process!\n"); int fd1 = open("fifotest",0666); for(i = 0; i < 10; i++) { if(read(fd1,str,128) < 0) perror("read"); else printf("%s\n",str); } system("rm -f fifotest"); } }
运行截图
结果分析
有名管道的特点:
1.有名管道支持读写操作,并且存在于文件系统中
2.能够使用使用read和write直接对有名管道进行操作。
3.有名管道是双向管道。
4.可以用于任意进程间的通信,不像匿名管道具有局限性。
我们能够像操作一个文件一样操作有名管道。
题目四
编写程序实现以下功能:
-
进程A向进程B发送SIGUSR1信号;
-
进程B收到信号后,打印字符串“receive SIGUSR1”;
-
要求用kill函数和signal函数实现以上功能;
kill.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> void fun(int sig) { if(sig == SIGUSR1) printf("Reseived SIGUSR1!\n"); } int main() { int pid; if(signal(SIGUSR1,fun) < 0) perror("signal"); pid = fork(); if(pid < 0) perror("fork"); else if(pid == 0) { printf("This is B process!\n"); sleep(2); } else { printf("This is A process!\n"); if(kill(pid,SIGUSR1) < 0) perror("kill"); return 0; } }
signal.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> void fun(int sig) { if(sig = SIGUSR1) printf("Received SIGUSR1!\n"); } int main() { printf("This is A process,mypid is: %d\n",getpid()); signal(SIGUSR1,fun); pause(); return 0; }
运行截图
结果分析
信号是比较复杂的通信方式,用于通知进程中某种事件的发生。除了进程间的通信之外,进程还能发送信号给进程本身;每种信号类型都有对应信号处理程序。大多数的信号的系统默认操作是结束进程,当然,进程同样可以向系统请求采取某些代替的操作。
例如:忽略信号、恢复信号的默认操作和执行一个预先设定的信号处理函数。
信号的本质是在软件层次上对进程的中断机制的一种模拟。在原理上,一个进程收到某种信号和处理器收到中断请求是一样的。
信号是所有的进程间的通信机制中唯一一个异步通信机制,可以看作是异步通知。
信号的生命周期如下:
我们通过signal和kill两种方式分别实现进程间的通信。
题目五
编写程序实现以下功能:
-
调用setitimer函数分别触发SIGALRM信号,SIGVTALRM信号,SIGPROF信号 ;(可以由多进程分别触发每个信号)
-
编写信号安装函数,在该函数内部能判断接受到的是什么信号,并把信号打印出来。
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <sys/time.h> #include <unistd.h> #include <sys/types.h> void fun(int sig) { if(sig == SIGALRM) printf("Received the SIGALRM!\n"); else if(sig == SIGVTALRM) printf("Receive the SIGVTALRM!\n"); else if(sig == SIGPROF) printf("Receive the SIGPROf!\n"); } int main() { if(signal(SIGALRM,fun) < 0) perror("signal"); if(signal(SIGVTALRM,fun) < 0) perror("signal"); if(signal(SIGPROF,fun) < 0) perror("signal"); struct itimerval new_value1,new_value2,new_value3; new_value1.it_value.tv_sec = 1; new_value1.it_value.tv_usec = 0; new_value1.it_interval.tv_sec = 2; new_value1.it_interval.tv_usec = 0; setitimer(ITIMER_REAL,&new_value1,NULL); new_value2.it_value.tv_sec = 1; new_value2.it_value.tv_usec = 0; new_value2.it_interval.tv_sec = 2; new_value2.it_interval.tv_usec = 0; setitimer(ITIMER_VIRTUAL,&new_value2,NULL); new_value3.it_value.tv_sec = 1; new_value3.it_value.tv_usec = 0; new_value3.it_interval.tv_sec = 2; new_value3.it_interval.tv_usec = 0; setitimer(ITIMER_PROF,&new_value3,NULL); while(1); return 0; }
运行截图
结果分析
setitimer函数的作用是提供精确的定时功能。通过改变settitime函数的第一个参数就能够改变函数触发的信号。
题目六
编写程序实现以下功能:
-
进程A向进程B发送SIGUSR1信号;
-
进程B收到信号后,打印字符串“receive SIGUSR1”;
-
要求用sigqueue函数和sigaction函数实现以上功能;
sigaction.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <signal.h> void fun(int sig) { if(sig == SIGUSR1) printf("Received SIGUSR1!\n"); } int main() { printf("This is the receive process!\n"); printf("The process pid is: %d\n",getpid()); struct sigaction act,oldact; act.sa_handler = fun; act.sa_flags = 0; sigaction(SIGUSR1,&act,&oldact); pause(); return 0; }
sigqueue.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> void handler(int sig,siginfo_t* p,void* q) { if(sig == SIGUSR1) printf("Received SIGUSR1!\n"); } int main() { union sigval mysigval; struct sigaction act; int pid; pid = fork(); if(pid < 0) perror("fork"); else if(pid == 0) { printf("This is the received process!\n"); act.sa_sigaction = handler; act.sa_flags = SA_SIGINFO; if(sigaction(SIGUSR1,&act,NULL) < 0) perror("sigaction"); while(1); } else { printf("This is the send process!\n"); sleep(1); if(sigqueue(pid,SIGUSR1,mysigval) < 0) perror("sigqueue"); } return 0; }
运行截图
题目七
编写程序实现以下功能:
-
进程A向进程B发送信号,该信号的附带信息为一个值为20的整数;
-
进程B完成接收信号的功能,并且打印出信号名称以及随着信号一起发送过来的整形变量值。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> void handler(int sig,siginfo_t* info,void *p) { printf("The num is: %d\n",info->si_value.sival_int); } int main() { int pid; struct sigaction act; act.sa_sigaction = handler; act.sa_flags = SA_SIGINFO; pid = fork(); if(pid < 0) perror("fork"); else if(pid == 0) { printf("This is the receive process!\n"); if(sigaction(SIGUSR1,&act,NULL) < 0) perror("sigaction"); while(1); } else { printf("This is the send process!\n"); union sigval mysigval; mysigval.sival_int = 20; sleep(1); if(sigqueue(pid,SIGUSR1,mysigval) < 0) perror("sigqueue"); } return 0; }
运行截图
结果分析
信号发送进程通过sigqueue函数能够将更多的信息发送给信号接受进程。
题目八
编写代码完成以下功能:
-
创建共享内存,写进程通过键盘不断向内存写入“hello world”;
-
如果结束写操作,则通过键盘输入“end”;
-
读进程从共享内存读取数据,并打印。直到读到“end”为止。
read.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/shm.h> #define MAXSIZE 1024 struct shm { int write; //记录读进程是否已经将内容读取 char buffer[MAXSIZE]; }; int main() { int shmid; struct shm *share; void *shmptr = NULL; if((shmid = shmget(0X44,MAXSIZE,0666|IPC_CREAT)) < 0) perror("shmget"); if((shmptr = shmat(shmid,0,0)) == (void *)-1) perror("shmat"); printf("This is the read process!!!\n"); share = (struct shm *)shmptr; while(1) { if(share->write != 0) { if(!strncmp(share->buffer,"end",3) == 0) { printf("%s",share->buffer); share->write = 0; } else break; } } if(shmdt(shmptr) < 0) perror("shmdt"); exit(0); }
write.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/shm.h> #define MAXSIZE 1024 struct shm { int write; //记录读进程是否已经将内容读取 char buffer[MAXSIZE]; }; int main() { int shmid; void *shmptr = NULL; char str[MAXSIZE]; //存储输入的内容 struct shm *share; if((shmid = shmget(0X44,MAXSIZE,0666|IPC_CREAT)) < 0) perror("shmget"); if((shmptr = shmat(shmid,0,0)) == (void *)-1) perror("shmat"); printf("This is the write process!!!\n"); share = (struct shm *)shmptr; while(1) { if(share->write == 1) { sleep(1); printf("Waiting the read process!!!\n"); } printf("please input hello world!!!\n"); fgets(str,MAXSIZE,stdin); sprintf(share->buffer,"%s",str); share->write = 1; if(strncmp(str,"end",3) == 0) break; sleep(1); } if(shmdt(shmptr) < 0) perror("shmdt"); exit(0); }
运行截图
结果分析
共享内存就是多个进程同时访问一个逻辑内存区域,共享内存是两个不相关的进程传递数据的重要方式。进程将同一段物理内存连接到他们自己的地址空间之后,所有连接的进程都能访问这块内存。如果一个进程对这段内存进行更改,所做的更改将影响更改之后访问这段内存的进程。需要注意的是,共享内存并没有设置同步机制,也就是说,在上一个进程对内存进行更改操作完成之后 ,并没有机制阻止下一个进程对这段内存的更改。因此,我们需要利用其它的机制对共享内存来同步进程对共享内存的访问。例如:信号量。
因为是直接对内存进行操作,省去了数据传输这一步骤,因此共享内存的速度最快。
题目九
编写代码完成以下功能:
-
进程A向消息队列发送消息“hello,world”
-
进程B从消息队列读取消息,并打印。
-
进程C向消息队列发送“自己的姓名”
-
进程D从消息队列中取出姓名字符串,并打印
#include <stdio.h> #include <stdlib.h> #include <sys/ipc.h> #include <unistd.h> #include <sys/msg.h> #include <sys/types.h> struct msg { char msg_str[128]; }; int main() { int qid; struct msg mymsg; if(qid = msgget(0x66,0666|IPC_CREAT) < 0) perror("msgget"); int pid; pid = fork(); if(pid < 0) perror("fork"); else if(pid == 0) { printf("This is A process!\n"); sprintf(mymsg.msg_str,"hello world"); if(msgsnd(qid,&mymsg,128,0) < 0) perror("msgsnd"); } else { if(fork() == 0) { printf("This is B process!\n"); if(msgrcv(qid,&mymsg,128,0,0) < 0) perror("msgrcv"); printf("The msg is: %s\n",mymsg.msg_str); } else if(fork() == 0) { printf("This is the C process!\n"); sprintf(mymsg.msg_str,"Mamingyuan"); if(msgsnd(qid,&mymsg,128,0) < 0) perror("msgsnd"); } else { printf("This is D process!\n"); if(msgrcv(qid,&mymsg,128,0,0) < 0) perror("msgrcv"); printf("The msg is: %s\n",mymsg.msg_str); } } return 0; }
运行截图
结果分析
消息队列也叫报文队列,是一个消息的链表。可以把消息看作是一个记录,具有特定的格式以及优先级。对消息队列具有写权限的进程可以按照一定的规则向消息队列中添加消息,而对消息队列具有写权限的进程可以从消息队列中读走消息。和管道相似的是,消息一旦从消息队列中被读走,则消息队列中便不在存在此条消息。
IPC消息队列的缺省最大数为16;
每个消息缺省最大值为8192字节;
队列中的最大值缺省为16384字节;
每个消息队列都有其对应的属性信息,存储在struct_msqid_ds结构体中。
每个消息队列都有一个对应的id,标识消息队列的唯一性。