【操作系统】进程之间的通信机制

来源:互联网 发布:地籍数据标准 编辑:程序博客网 时间:2024/05/29 14:47

一、Linux进程间通信:管道、消息队列、共享内存;

       进程是一个独立的资源分配单位,不同进程之间的资源是相互独立的,没有关联,不能在一个进程中直接访问另一个进程中的资源。

       所以进程需要一些手段来进行进程间数据传递、同步与异步的机制。

====================================================================================

       ① 管道

          ② 消息队列

  ③ 共享内存

=============================================================================

二、编写程序实现匿名管道、FIFO、共享内存、消息队列的通信示例

① 匿名管道

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>int main(){pid_t pid;char buf[128];//发送的消息int fds_f[2];//子进程联系父进程管道 fds_f[0]读 fds_f[1]写int fds_c[2];//父进程联系子进程管道 fds_c[0]读 fds_c[1]写pipe(fds_f);//给fds_f数组建立管道pipe(fds_c);//给fds_c数组建立管道pid = fork();//生成子进程if(pid){//父进程printf("1. 这里是父进程\n");strcpy(buf,"来自父进程的信息。");write(fds_c[1],buf,sizeof(buf));//父进程向子进程发送消息read(fds_f[0],buf,sizeof(buf));//接收来自子进程的消息printf("1. 父进程收到消息:%s\n",buf);}else if(pid==0){//子进程sleep(1);//休眠1秒printf("2. 这里是子进程\n");strcpy(buf,"来自子进程的信息。");write(fds_f[1],buf,sizeof(buf));//子进程向父进程发送消息read(fds_c[0],buf,sizeof(buf));//接收来自父进程的消息printf("2. 子进程收到消息:%s\n",buf);}}

1编号代表父进程,2编号代表子进程。子进程休眠1秒。所以父进程发送消息给子进程,子进程立刻收到父进程信息,输出后,发送消息给父进程。

====================================================================================

②  FIFO

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <string.h>#include <time.h>int main(){pid_t pid;char buffer[80];//传输的信息int fd;//操作编号char *FIFO = "fifo1";//FIFO有名管道文件名称mkfifo(FIFO,0666);//创建有名管道文件 pid = fork();if(pid){//父进程printf("1. 父进程。\n");//将“来自父进程的信息”复制给bufferstrcpy(buffer,"来自父进程的信息。");fd = open(FIFO,O_WRONLY);//打开管道文件,只写 write(fd,buffer,sizeof(buffer));//往管道文件中写入信息close(fd);//关闭管道文件fd = open(FIFO,O_RDONLY);//打开管道文件,只读sleep(1);//沉睡一秒钟read(fd,buffer,sizeof(buffer));//读取管道文件的信息printf("1. 父进程收到信息 :%s\n",buffer);close(fd);//关闭管道文件}else if(pid==0){//子进程printf("2. 子进程。\n");fd = open(FIFO,O_RDONLY);//打开管道文件,只读read(fd,buffer,80);//读取管道文件的信息printf("2. 子进程收到信息:%s\n",buffer);close(fd);//关闭管道文件fd = open(FIFO,O_WRONLY);//打开管道文件,只写//将“来自子进程的信息”复制给bufferstrcpy(buffer,"来自子进程的信息。");write(fd,buffer,sizeof(buffer));//往管道文件中写入信息close(fd);//关闭管道文件}}

====================================================================================

③ 共享内存

#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#define BUFSZ 4096int main(void){int shm_id = shmget(IPC_PRIVATE,BUFSZ,0666);//分配共享内存//判断分配是否成功,如果成功,则编号大于等于0if(shm_id < 0){perror("shm_id");exit(1);}printf("共享内存编号:%d\n",shm_id);char* shm_buf;//获取该共享内存编号上的地址if((shm_buf = shmat(shm_id,0,0))<(char*)0){perror("shm_id");exit(1);}pid_t pid;pid = fork();if(pid){//父进程//父进程向子进程发送消息。sprintf(shm_buf,"来自父进程的消息~!");sleep(2);//父进程接受来自子进程的消息。printf("父进程接受消息:%s\n",shm_buf);//解除映射if(shmdt(shm_buf)<0){perror("shmdt");exit(1);}printf("父进程\n");sleep(1);}else if(pid==0){//子进程//子进程接受来自父进程的消息。printf("子进程接受消息:%s\n",shm_buf);sleep(1);//子进程向父进程发送消息。sprintf(shm_buf,"来自子进程的消息~!");//解除映射if(shmdt(shm_buf)<0){perror("shmdt");exit(1);}printf("子进程\n");char s[8];char str[20] = {"ipcrm -m "};//将int转成char数组sprintf(s,"%d",shm_id);//连接str和s。strcat(str,s);sleep(1);printf("%s\n",str);//执行命令,删除该共享内存区system(str);}}

首先创建共享内存,获得共享内存的编号 1867793

然后fork()生成子进程。

首先父进程通过共享内存发送消息“来自父进程的消息~!”给子进程。

其次子进程通过共享内存发送消息“来自子进程的消息~!”给父进程。

输出“子进程”,“父进程”表示对应进程的任务结束。

ipcrm –m 1867793 撤销指定编号的共享内存。


====================================================================================

④ 消息队列

#include <stdio.h>#include <stdlib.h>#include <ctype.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#define MAX_SEND_SIZE 80struct mymsgbuf{/** * mtype的值。 * 如果为0,则接受该队列中的第一条信息。 * 如果小于0,则接受小于该值的绝对值的消息类型。 * 如果大于0,接受指定类型的消息,即该值消息。 */long mtype;//消息类型char mtext[MAX_SEND_SIZE];//消息文本};int type = 1;int main(){int key = ftok(".",'m');//将一个文件路径名转成key值//创建一个消息队列int msgqueue_id = msgget(key,IPC_CREAT|0660);printf("消息队列编号:%d\n",msgqueue_id);//输出消息队列编号struct mymsgbuf qbuf;pid_t pid = fork();if(pid){//父进程qbuf.mtype = type;//为消息类型赋值strcpy(qbuf.mtext , "来自父进程的消息...");//为消息文本赋值//父进程向消息队列发送消息msgsnd(msgqueue_id,(struct msgbuf *)&qbuf,strlen(qbuf.mtext)+1,0);sleep(2);//父进程从消息队列中获取信息,即获取子进程发送给消息队列的信息。msgrcv(msgqueue_id,(struct msgbuf *)&qbuf,MAX_SEND_SIZE,type,0);printf("父进程接收消息:%s\n",qbuf.mtext);sleep(1);}else if(pid == 0){//子进程sleep(1);//从消息队列中读取消息。msgrcv(msgqueue_id,(struct msgbuf *)&qbuf,MAX_SEND_SIZE,type,0);//子进程输出从父进程获得的消息。printf("子进程接收消息:%s\n",qbuf.mtext);qbuf.mtype = type;strcpy(qbuf.mtext , "来自子进程的消息...");//子进程向消息队列发送消息msgsnd(msgqueue_id,(struct msgbuf *)&qbuf,MAX_SEND_SIZE,0);sleep(1);//撤销邮箱msgctl(msgqueue_id,IPC_RMID,0);}return 0;}


创建一个消息队列,然后不撤销的话会在MessageQueues里面出现一个消息队列。


这次调用了撤销消息队列的函数msgctl(msgqueue_id,IPC_RMID,0);所以ipcs–q查不到该消息队列的信息。

通过消息队列,子进程接收信息“来自父进程的消息…”,父进程接收消息“来自子进程的消息…”。

=============================================================================

三、 设计以下代码:

      1. 进程A创建子进程B, 子进程B创建孙进程C,AB间用管道1通信,BC间用管道2通信,实现进程A将一个消息字符串发送到C进程,并有C进程打印出来以验证通信成功。要求:管道2只对BC可见,即对A是不可见的。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <time.h>int main(){//进程Apid_t pid;char buf[128];//消息的字符数组//管道1:进程A 和 进程Bint fds_A_B[2];pipe(fds_A_B);//进程A生成子进程B,A和B都有管道fds_A_Bpid = fork();if(pid){printf("1. 这里是进程A。\n");//给字符数组赋值 strcpy(buf,"来自进程A的消息~!\n");printf("1. 进程A发送消息给进程B。\n");//进程A通过管道1与进程B通信write(fds_A_B[1],buf,sizeof(buf));sleep(2);}else if(pid==0){//进程Bprintf("2. 这里是进程B。\n");//创建管道2:进程B 和 进程C通信。管道2只有B和C可见int fds_B_C[2];pipe(fds_B_C);//进程B创建子进程Cpid = fork();if(pid){printf("2. 进程B接收来自进程A的信息。\n");//进程B 通过管道1 从 父进程A那里接收信息。read(fds_A_B[0],buf,sizeof(buf));printf("2. 进程B转递消息给进程C...\n");//进程B 通过管道2 向 子进程C 传递 父进程A的消息。write(fds_B_C[1],buf,sizeof(buf));sleep(2);}else if(pid==0){sleep(2);//进程Cprintf("3. 这里是进程C。\n");printf("3. 进程C接收来自进程B的信息...\n");//进程C 通过管道2 从 父进程B那里接收信息。read(fds_B_C[0],buf,sizeof(buf));printf("3. 进程C接收信息:%s\n",buf);}}}

进程A创建管道fds_A_B,生成子进程B,通过管道fds_A_B发送消息给进程B。

进程B创建管道fds_B_C,生成子进程C,通过管道fds_B_C传递消息给进程C。

fds_A_B在进程A,B,C都可见,fds_B_C在进程A中不可见。

===================================================================================

      2.   A进程创建一个消息队列(邮箱);B进程向邮箱按顺序发送三条类型分别为111/222/333的消息,内容不作限定;C进程按333/111/222的顺序读取消息;D进程删除该消息队列(邮箱)。

一、 A进程

#include <stdio.h>#include <string.h>#include <sys/msg.h>int main(){printf("这里是A进程。\n");int key = ftok(".",'m');printf("创建消息队列。\n");//创建消息队列,获得消息队列编号int msgqueue_id = msgget(key,IPC_CREAT | 0660);printf("消息队列编号:%d\n",msgqueue_id);//运行进程B,将消息队列编号传给进程B。char s[8];char str[] = {"./msgB.out "};sprintf(s,"%d",msgqueue_id);strcat(str,s);printf("%s\n", str);system("ipcs -q");system(str);}

二、 B进程

#include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#define MAX_SEND_SIZE 4struct mymsgbuf{/** * mtype的值。 * 如果为0,则接受该队列中的第一条信息。 * 如果小于0,则接受小于该值的绝对值的消息类型。 * 如果大于0,接受指定类型的消息,即该值消息。 */long mtype;//消息类型char mtext[MAX_SEND_SIZE];//消息文本};int main(int argc,char* argv[]){if(argc!=2){printf("只允许一个参数");return 0;}printf("这里是进程B。\n");//从传参数中获取消息队列编号int msgqueue_id = atol(argv[1]);struct mymsgbuf qbuf;printf("向消息队列中写入信息。\n");int i = -1;//写入信息for(;++i<3;){qbuf.mtype = i + 1;//消息编号//消息内容,分别是"111","222","333"qbuf.mtext[0]   = qbuf.mtext[1]  = qbuf.mtext[2]  = (char)(48+i+1);qbuf.mtext[3] = '\0';printf("%s\n",qbuf.mtext);//将消息文本发送给消息队列中。msgsnd(msgqueue_id,(struct msgbuf*)&qbuf,strlen(qbuf.mtext)+1,0);}//将消息队列编号传给进程Cchar str[] = {"./msgC.out "};strcat(str,argv[1]);printf("%s\n",str);system("ipcs -q");system(str);return 0;}
三、 C进程
#include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#define MAX_SEND_SIZE 4struct mymsgbuf{/** * mtype的值。 * 如果为0,则接受该队列中的第一条信息。 * 如果小于0,则接受小于该值的绝对值的消息类型。 * 如果大于0,接受指定类型的消息,即该值消息。 */long mtype;//消息类型char mtext[MAX_SEND_SIZE];//消息文本};int main(int argc,char* argv[]){if(argc!=2){printf("只允许一个参数。\n");return 0;}printf("这里是进程C。\n");struct mymsgbuf qbuf;//获取消息队列编号int msgqueue_id = atol(argv[1]);int i = -1;int que[] = {3,1,2};//获取消息的顺序printf("读取消息队列中的信息。\n");for(;++i<3;){//从消息队列里按编号获取信息msgrcv(msgqueue_id,(struct msgbuf *)&qbuf,MAX_SEND_SIZE,que[i],0);printf("%s\n",qbuf.mtext);}//将消息队列编号传给进程Dchar str[] = {"./msgD.out "};strcat(str,argv[1]);printf("%s\n",str);system("ipcs -q");system(str);}
四、D进程

#include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int main(int argc,char* argv[]){if(argc!=2){printf("只允许一个参数\n");return 0;}printf("这里是进程D。\n");//获取消息队列编号int msgqueue_id = atol(argv[1]);printf("删除消息队列:%d\n",msgqueue_id);//删除消息队列msgctl(msgqueue_id,IPC_RMID,0);system("ipcs -q");}

1.          首先A进程创建消息队列,然后将消息队列的编号传给B进程msgB.out。system(“ipcs –q”)后发现Message Queues中多了一个消息队列。

2.          B进程获取消息队列的编号后,往该消息队列写入三个信息“111”,“222”,“333”。

此时发现Message Queues中该消息队列的used-bytes为12,messages为3。说明有三个信息,用了12个字节。

之所以有12个字节是因为“111”,“222”,“333”后面都有个‘\0’,所以总共有4 * 3 = 12个字符。

3.          C进程读取该消息队列的三个信息,按照3、1、2的顺序读取消息。

读取完消息以后发现,消息队列的三个消息消失了。说明消息队列中的消息只允许读取一次。

4.          D进程删除掉该消息队列。Message Queues没有该消息队列了。

=============================================================================

   3

       a.  一个程序(进程)从客户端读入按键信息,一次将“一整行”按键信息保存到一个共享存储的缓冲区内并等待读取进程将数据读走,不断重复上面的操作;

       b.  另一个程序(进程)生成两个进程/线程,用于显示缓冲区内的信息,这两个线程并发读取缓冲区信息后将缓冲区清空(一个线程的两次显示操作之间可以加入适当的时延以便于观察)。

   c.  在两个独立的终端窗口上分别运行上述两个程序,展示其同步与通信功能,要求一次只有一个任务在操作缓冲区。

① 读取一整行按键信息的程序(readStr):

#include <stdio.h>#include <sys/ipc.h>#include <sys/shm.h>#include <semaphore.h>#include <fcntl.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#define BUFSZ 4096int main(){//创建共享内存,获得共享内存IDint shm_id = shmget(IPC_PRIVATE,BUFSZ,0666);if(shm_id < 0){perror("shm_id");exit(1);}//获得共享内存地址char* shm_buf;if((shm_buf = shmat(shm_id,0,0))<(char*)0){perror("shm_id");exit(1);}char ch[128];//输入的信息保存在该char数组中//创建信号量sem_t* sem = sem_open("semname",O_CREAT,0644,1);char c;int i;//输出共享内存地址。printf("%d\n",shm_id);//打开信号量sem = sem_open("semname",0);while(1){i = -1;//读入一整行按键信息while((c=getchar())!='\n'){ch[++i] = c;}ch[++i] = '\0';//将按键信息发送到共享内存sprintf(shm_buf,ch);//发送信号量sem_post(sem);}return 0;}
② 第一个进程,读取缓冲区的信息。

#include <semaphore.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <string.h>#include <sys/shm.h>int main(int argc,char* argv[]){printf("进程1登录。\n");if(argc != 2){exit(1);}sem_t *sem;//cmd传参获取共享内存编号int shm_id = atol(argv[1]);//获取共享内存地址char* shm_buf;if((shm_buf = shmat(shm_id,0,0))<(char*)0){perror("shm_id");exit(1);}printf("进程1就绪。\n");//打开信号量sem = sem_open("semname",0);while(1){//等待信号量sem_wait(sem);printf("1. 进程1受到消息:%s\n",shm_buf);//将缓冲区里面的信息清空strcpy(shm_buf,"");}return 0;}
③ 第二个进程,读取缓冲区的信息。

#include <semaphore.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <string.h>#include <sys/shm.h>int main(int argc,char* argv[]){printf("进程2登录。\n");if(argc != 2){exit(1);}sem_t *sem;//cmd传参获取共享内存编号int shm_id = atol(argv[1]);//获取共享内存地址char* shm_buf;if((shm_buf = shmat(shm_id,0,0))<(char*)0){perror("shm_id");exit(1);}printf("进程2就绪。\n");//打开信号量sem = sem_open("semname",0);while(1){//等待信号量sem_wait(sem);printf("2. 进程2受到消息:%s\n",shm_buf);//将缓冲区里面的信息清空strcpy(shm_buf,"");}return 0;}

一、读取一行按键信息存放在缓冲区,由两个进程并发读取(readStr.out):

先输出共享内存的编号,用于手动传参给进程一和进程二。

按顺序输入信息“消息[1 – 20]”。


二、进程一,读取缓冲区数据的情况(getStr1.out)

进入进程一的时候输出“进程1登录”。

准备工作(读取共享内存地址,打开信号量)完成以后,输出“进程1就绪”,表示可以开始接受信息了。

随着readStr.out输入信息,进程1开始输出信息,但是都是奇数号的信息。


三、进程二,读取缓冲区数据的情况(getStr2.out)


进入进程二的时候输出“进程2登录”。

准备工作(读取共享内存地址,打开信号量)完成以后,输出“进程2就绪”,表示可以开始接受信息了。

随着readStr.out输入信息,进程2开始输出信息,但是都是偶数号的信息。


该代码出现的问题:


在这里,“消息[1-20]”这20行信息提前复制好,然后黏贴在输入中。


“消息[1-20]”黏贴上去后,所有信号量都被进程一getStr1.out获取了,但是输出的全部信息只有最后的“消息20”。


而进程二getStr2.out并没有获取到信号量。

0 0
原创粉丝点击