进一步提高 Linux 环境下 C 编程能力,了解和熟悉 Linux 支持的多种IPC 机制。Linux 作为一个多任务多进程的操作系统,各个进程间信息交互不可避免,进程间通信可分为本地进程间通信和远程进程间通信。本地进程间通信主要包括信号,管道,消息队列,信号量,共享内存等通信方式。
(1)阅读 Linux 进程间通信章节的相关参考资料。
(2)重点学习信号量、共享内存机制。
阅读参考资料,分析示例程序代码,用 C 语言编程实现以下要求。
Oracle VM VirtualBox、Ubuntu(64-bit)
// 本次实验实现了3种进程通信,1.命名管道FIFO 2.消息队列 3.共享内存&信号量 // my.h #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <sys/msg.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/shm.h> #include <sys/wait.h> #include <sys/stat.h> #include <sys/types.h> #include <error.h> #include <errno.h> #include <unistd.h> #include <linux/stat.h> #define BUFES 80 // 消息的组成 struct msgbuf { long int my_type; // 消息的类型域 char text[BUFES]; // 消息传递的数据域 }; union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *buf_info; void *pad; }; mode_t mode = 00666; // IPC设置为默认模式 char pathname1[] = "/tmp/myfifo"; // FIFO路径 char pathname2[] = "/tmp/my_shm"; // 共享内存路径 char pathname3[] = "/tmp/my_sem"; // 信号量路径 char proj_id = 'a'; // 用于生成key // p操作, 获取信号量 int sem_p(int semid, int index) { struct sembuf sbuf = {0, -1, IPC_NOWAIT}; // 每个sembuf结构描述了一个对信号量的操作 if(index < 0) { perror("index指定的信号量不存在!\n"); return -1; } sbuf.sem_num = index; if(semop(semid, &sbuf, 1) == -1) { perror("在对信号量进行 p 操作时出现了一个错误!\n"); return -1; } return 0; } // V操作, 释放信号量 int sem_v(int semid, int index) { struct sembuf sbuf = {0, 1, IPC_NOWAIT}; // 每个sembuf结构描述了一个对信号量的操作 if(index < 0) { perror("index指定的信号量不存在!\n"); return -1; } sbuf.sem_num = index; if(semop(semid, &sbuf, 1) == -1) { perror("在对信号量进行 v 操作时出现了一个错误!\n"); return -1; } return 0; } // 等待信号量 int wait_sem(int semid, int index) { while(semctl(semid, index, GETVAL, 0) == 0) ; return 1; }
// myclient.c #include "my.h" int running = 1; void stop() { running = 0; } int main(int argc, char *argv[]) { FILE *fp; //命名管道文件指针 const int flag1 = IPC_CREAT | IPC_EXCL | mode; // 创建对应IPC,若已存在则返回-1 const int flag2 = IPC_CREAT | mode; // 打开对应IPC,若不存在则创建 struct msgbuf msg_sbuf; // 消息队列所传递的消息 struct msqid_ds msg_info; // 用来设置或返回消息队列信息 union semun arg; // 函数semid的最后一个参数 key_t semkey, shmkey; // 信号量、共享内存键值 int semid, shmid,msgid; // 信号量id,共享内存id,消息队列的ID int sem_members=4; // 信号量数量为4,0号信号为是否有人占用client共享内存,1号信号为是否有人占用server共享内存,2号信号为client共享内存是否可读,3号信号为server共享内存是否可读 int reval; // 接受消息队列操纵函数msgctl的返回值 char writebuf[BUFES]; // 命名管道的写缓冲区 char write_str[BUFES]; // 共享内存的缓冲区 char *shmaddr; // 存放共享内存接口 signal(SIGINT, stop); // 注册SIGINT信号 while (running) { if((fp=fopen(pathname1, "w")) == NULL) { printf("打开命名管道失败 \n"); exit(1); } printf("请输入: "); fgets(writebuf, BUFES, stdin); if(strncmp(writebuf, "end", 3) == 0) { printf("接受到结束信号,命名管道通信结束 \n"); running = 0; } if(fputs(writebuf, fp) == EOF) { printf("写入命名管道失败 \n"); exit(1); } fclose(fp); } running = 1; while(running); running = 1; printf("接受到SIGINT信号,接下来开始进行消息队列通信 \n"); printf("-------------------------------------------------------------------------\n"); if((msgid=msgget((key_t)1111, flag2)) == -1) // 打开key值为1234的消息队列,如不存在则创建之 { printf("创建/打开消息队列失败! \n"); exit(1); } else printf("消息队列创建成功 \n"); while (running) { printf("请输入: "); fgets(msg_sbuf.text, BUFES, stdin); // 读入键盘输入的消息 msg_sbuf.my_type = 1; if(msgsnd(msgid, (void *) &msg_sbuf, BUFES, 0) == -1) // 发送消息 { printf("发送信息失败!\n"); exit(1); } if(strncmp(msg_sbuf.text, "end", 3) == 0) // 输入end表示程序结束 { printf("接受到结束信号,消息队列通信结束 \n"); running = 0; } } if((reval=msgctl(msgid, IPC_STAT, &msg_info)) == -1) { printf("获取消息队列信息失败! \n"); exit(1); } printf("消息队列信息: \n"); printf("消息队列的容量: %ld \n", msg_info.msg_qbytes); printf("最近一个执行 msgsnd 函数的进程ID是:%d \n", msg_info.msg_lspid); printf("最近一个执行 msgrcv 函数的进程ID是:%d \n", msg_info.msg_lrpid); if(msgctl(msgid, IPC_RMID, 0)==-1) // 删除消息队列 { printf("删除消息队列失败! \n"); exit(1); } else printf("删除消息队列成功\n"); printf("接下来开始共享内存&信号量通信 \n"); printf("-------------------------------------------------------------------------\n"); if((shmkey = ftok(pathname2, proj_id)) == -1) { perror("返回共享内存键值失败!\n"); exit(1); } else printf("获取共享内存键值成功!\n"); if((shmid = shmget(shmkey, BUFES, flag2)) == -1){ perror("创建/打开共享内存失败!\n"); exit(1); } else printf("打开共享内存成功!\n"); if((shmaddr=shmat(shmid, (char*)0, 0)) == (char *)-1) { perror("附加共享内存失败!\n"); exit(1); } else printf("附加共享内存成功!\n"); if((semkey = ftok(pathname3, proj_id)) == -1) { perror("返回信号量键值失败!\n"); exit(1); } else printf("获取信号量键值成功!\n"); if((semid = semget(semkey, sem_members, flag2)) == -1) // 打开键值为semkey的信号量集,如不存在则创建之,返回信号量集标识符。members为信号量集中含信号量的数目。 { perror("打开信号量失败!\n"); exit(1); } else printf("打开信号量成功!\n"); arg.val = 1; for(int index = 0; index < sem_members; index++) { semctl(semid, index, SETVAL, arg); if(index==0) arg.val = 0; } printf("初始化信号量成功!\n"); while(1) { wait_sem(semid, 0); // 等待client共享内存可写信号量可以被获取 sem_p(semid, 0); // 获取client可写信号量 printf("发送:"); fgets(write_str, BUFES, stdin); int len = strlen(write_str); write_str[len] = '\0'; strcpy(shmaddr, write_str); sem_v(semid, 1); // 向server传递共享内存可读信号量 if(strncmp(write_str, "end", 3) == 0) // 输入end表示程序结束 { printf("接受到结束信号,共享内存&信号量通信结束 \n"); break; } wait_sem(semid, 3); // 等待client共享内存可读信号量可以被获取 sem_p(semid, 3); // 获取client共享内存可读信号量 printf("接收:%s", shmaddr); sem_v(semid, 0); // 向client传递共享内存可写信号量 if(strncmp(shmaddr, "end", 3) == 0) // 输入end表示程序结束 { printf("接受到结束信号,共享内存&信号量通信结束 \n"); break; } } sleep(3); if((semctl(semid, 0, IPC_RMID)) == 0) printf("删除信号量成功 \n"); else printf("删除信号量失败"); if((shmctl(shmid, IPC_RMID, NULL)) == 0) printf("删除共享内存成功 \n"); else printf("删除共享内存失败"); return 0; }
// myserver.c #include "my.h" int running = 1; void stop() { running = 0; } int main(int argc, char *argv[]) { FILE *fp; //命名管道文件指针 const int flag1 = IPC_CREAT | IPC_EXCL | mode; // 创建对应IPC,若已存在则返回-1 const int flag2 = IPC_CREAT | mode; // 打开对应IPC,若不存在则创建 struct msgbuf msg_rbuf; // 消息队列所传递的消息 union semun arg; // 函数semid的最后一个参数 key_t semkey, shmkey; // 信号量、共享内存键值 long int msg_to_receive = 0; // 接受消息队列的第一条消息 int msgid,semid, shmid; // 信号量id,共享内存id,消息队列的ID int sem_members = 4; // 信号量数量为4,0号信号为是否有人占用client共享内存,1号信号为是否有人占用server共享内存,2号信号为client共享内存是否可读,3号信号为server共享内存是否可读 char readbuf[BUFES]; // 命名管道的读缓冲区 char write_str[BUFES]; // 共享内存的缓冲区 char *shmaddr; // 存放共享内存接口 signal(SIGINT, stop); // 注册SIGINT信号 if((mkfifo(pathname1, mode)) < 0) { perror("创建命名管道失败!\n"); exit(1); } else printf("你成功创建了一个命名管道 \n"); while(running) { if((fp = fopen(pathname1, "r")) < 0) { printf("打开命名管道失败 \n"); exit(1); } if(fgets(readbuf, BUFES, fp) != NULL) { printf("读取字符串: %s", readbuf); fclose(fp); } else { if(ferror(fp)) { printf("读取命名管道失败 \n"); exit(1); } } if(strncmp(readbuf, "end", 3) == 0) { printf("接受到结束信号,命名管道通信结束 \n"); running = 0; } } running = 1; while(running); running = 1; printf("接受到SIGINT信号,接下来开始进行消息队列通信 \n"); printf("-------------------------------------------------------------------------\n"); if((msgid=msgget((key_t) 1111, flag2)) == -1) // 打开key值为1234的消息队列,如不存在则创建之 { printf("创建/打开消息队列失败! \n"); exit(1); } while(running) { if(msgrcv(msgid, (void *) &msg_rbuf, BUFES, msg_to_receive, 0) == -1) //接收消息,最后一个参数为设置默认模式 { printf("获取消息失败!\n"); exit(1); } printf("接受到消息 : %s", msg_rbuf.text); if(strncmp(msg_rbuf.text, "end", 3) == 0) // 输入end表示程序结束 { printf("接受到结束信号,消息队列通信结束 \n"); running = 0; } } printf("接下来开始共享内存&信号量通信 \n"); printf("-------------------------------------------------------------------------\n"); if((shmkey = ftok(pathname2, proj_id)) == -1) { perror("返回共享内存键值失败!\n"); exit(1); } else printf("获取共享内存键值成功!\n"); if((shmid = shmget(shmkey, BUFES, flag2)) == -1){ perror("创建/打开共享内存失败!\n"); exit(1); } else printf("打开共享内存成功!\n"); if((shmaddr=shmat(shmid, (char*)0, 0)) == (char *)-1) { perror("附加共享内存失败!\n"); exit(1); } else printf("附加共享内存成功!\n"); sleep(2); if((semkey = ftok(pathname3, proj_id)) == -1) { perror("返回信号量键值失败!\n"); exit(1); } else printf("获取信号量键值成功!\n"); if((semid = semget(semkey, sem_members, flag2)) == -1) // 打开键值为semkey的信号量集,如不存在则创建之,返回信号量集标识符。members为信号量集中含信号量的数目。 { perror("打开信号量失败!\n"); exit(1); } else printf("打开信号量成功!\n"); while(1) { wait_sem(semid, 1); // 等待server共享内存可读信号量可以被获取 sem_p(semid, 1); // 获取server共享内存可读信号量 printf("接收:%s", shmaddr); sem_v(semid, 2); // 向server传递共享内存可写信号量 if(strncmp(shmaddr, "end", 3) == 0) // 输入end表示程序结束 { printf("接受到结束信号,共享内存&信号量通信结束 \n"); break; } wait_sem(semid, 2); // 等待server共享内存可写信号量可以被获取 sem_p(semid, 2); // 获取server共享内存可写信号量 printf("发送:"); fgets(write_str, BUFES, stdin); int len = strlen(write_str); write_str[len] = '\0'; strcpy(shmaddr, write_str); sem_v(semid, 3); // 向client传递共享内存可读信号量 if(strncmp(write_str, "end", 3) == 0) // 输入end表示程序结束 { printf("接受到结束信号,共享内存&信号量通信结束 \n"); break; } } return 0; }
本次实验编写了:client客户端程序与server服务器程序
主要目的是实现:
①server调用mkfifo创建命名管道
②client通过命名管道单向向server发送信息
③在接收到SIGINT信号后两个进程关闭命名管道进入消息队列通信阶段
④client单向通过消息队列发送信息给server
⑤在结束消息队列通信前调用msgctl函数查看消息队列部分信息
⑥client与server进入共享内存&信号量通信阶段,打开共享内存与信号量,信号量数量为4由client完成初始化操作
⑦调用封装在my.h的函数,实现clinet发送->server接收->server发送->client接收的顺序执行
⑧删除共享内存与信号量,进程结束。
mkfifo不能存在同名文件,所以在第二次执行时应该删去前一次执行生成的fifo文件。
解决:放弃在my.h里定义,而是在客户端与服务器程序中都定义。
解决:使用了上一次实验的方法,调用sleep函数。
解决:共享内存与信号量的shmget已经semget的参数pathname必须是现在存在的路径,可以创建对应文件名的文件夹。
解决:暂无