通信机制概述
管道pipe的通信是有操作系统帮助我们管理的,我们只需要创建管道即可,进程之间的互斥和管道的销毁有os自动完成。
而使用ipc机制需要我们手动考虑所有的情况:创建、互斥、销毁操作。
Linux 支持典型的三种进程间通信机制:
共享内存 (shared memory segment),
消息(message queue),
信号量(semaphores set) 。
这三种通信方法虽然操作在不同的数据结构上,但在概念和用法上与共享内存很相 似。它们每一种通信,都要先使用一个调用来创建用于通信的资源; 同样,它们也要使用 调用来进行控制操作和存取访问。
共享内存 | 信号量 | 消息 | |
---|---|---|---|
创建 | shmget() | semget() | msgget() |
控制 | shmctl() | semctl() | msgctl() |
访问 | shmat() | semop() | msgsnd() |
shmdt() | msgrcv() |
共享内存可以使得两个或多个进程都能使用的地址空间中使用同一块内存.这 就使得如果一个进程已经把信息写如到这一内存块中,马上就可以供其它共享这一块内 存的进程使用.这样就能够克服诸如使用管道(PIPE)时候的几个问题:传递数据的大小限 制(匿名管道大小为 4K,而共享内存大小可自定) ,传递数据的反复复制(可跟管道实验 中的程序做比较) ,环境的切换(例如:试图读空管道的进程会被阻塞) 。
消息队列则是一个由消息缓冲区构成的链表,它允许一个或多个进程写信息,将消息缓冲区挂在一个队列上;一个或多个进程读去消息,从队列上摘取消息缓冲区。消息 队列作为 System V 的 IPC 通信机制的一种,它的模型与共享内存有不少相似,通过一个 消息队列标识符来唯一标识和进程访问权限检查。
共享内存
函数介绍
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,int size,int shmflg)
shmget()用来取得参数 key 所关联的共享内存识别代号。
如果参数 key 为 IPC_PRIVATE 则会建立新的共享内存,其大小由参数 size 决定。
Shmflg 参数在实验程序 中,其值可以为0。
该 函数若成功,则返回共享内存识别代号,否则返回-1,错误原因存于 errno 中。
#include <sys/types.h>
#include <sys/shm.h>
void* shmat(int shmid,const void* shmaddr,int shmflg)
shmat()函数用来将参数 shmid 所指的共享内存和目前进程连接(attach)。
参数 shmid 为欲连接的共享内存识别代码
参数 shmaddr 有下列几种情况:
1).shmaddr 为 0,核心自动选择一个地址;
2).shmaddr 不为 0,参数 shmflg 也没有指定 SHM_RND 旗标,则参数 shmaddr 为连接 地址;
3).shmaddr 不为 0,但参数 shmflg 设置了 SHM_RND 旗标,则参数 shmaddr 会自动调 整SHMLAB 的整数倍。
参数 shmflg 还可以有 SHM_RDONLY 旗标,代表此连接只是用来读取该共享内存。
该函数若成功,则返回共享内存识别 代号,否则返回-1,错误原因存于 errno 中:
附加说明:在经过 fork()后,子进程将继承已连接的共享内存地址;在经过 exec() 后,已连接的共享内存地址将会自动脱离(detach);在结束进程后,已连接的共享内存 地址将会自动脱离(detach)。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void* shmaddr)
shmdt()用来将先前用 shmat()连接(attach)好的共享内存脱离(detach)目前的进 程。
参数 shmaddr 为先前 shmat()返回的共享内存地址。
该函数若成功,则返回 0,否则 返回-1,错误原因存于 errno 中:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf)
shmctl()提供了几种方式来控制共享内存的操作。
参数 shmid 为欲处理的共享内存 识别代码,
参数 cmd 为欲控制的操作,有以下几种数值:
1).IPC_STAT 把共享内存的 shmid_ds 数据结构复制到引数 buf;
2).IPC_SET 将参数 buf 所指的 shmid_ds 结构中的 shm_perm.uid、 shm_perm.gid、 shm_perm.mode 复制到共享内存的 shmid_ds 结构内;
3).IPC_RMID 删除共享内存和其数据结构;
4).SHM_LOCK 不让此共享内存置换到 swap;
5).SHM_UNLOCK 允许此共享内存置换到 swap;(SHM_LOCK 和 SHM_UNLOCK 为 Linux 特有,且只有超级用(root)户允许使用) 。
共享内存的代码实现demo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 3456
#define SIZE 1024
int main(){
int p, shmid;
char *shmaddr=NULL;
shmid=shmget(KEY, SIZE, IPC_CREAT|0600);
p=fork();
if (p){
//parent
shmaddr=(char *)shmat(shmid, NULL, 0);
strcpy(shmaddr, "This is a message");
shmdt(shmaddr);
wait(NULL);
}else{
//child
sleep(1);
shmaddr=(char *)shmat(shmid, NULL, 0);
printf("%s\n", shmaddr);
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL);
}
return 0;
}
运行效果
说明
key这里随便取一个数
IPC_CREAT|0600 作用是让该用户具有可读可写的权限。
这里的0600中的0是指定8进制格式。
将上述程序拆分为三个源文件
shm_cw.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define KEY 3456
#define SIZE 1024
int main(){
int shmid;
char *shmaddr=NULL;
shmid=shmget(KEY, SIZE, IPC_CREAT|0600);
shmaddr=(char*)shmat(shmid, NULL, 0);
strcpy(shmaddr, "This is a message\n");
shmdt(shmaddr);
return 0;
}
shm_r.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 3456
#define SIZE 1024
int main(){
int shmid;
char *shmaddr=NULL;
shmid=shmget(KEY, SIZE, IPC_EXCL);
shmaddr=(char *)shmat(shmid, NULL, 0);
printf("%s\n", shmaddr);
shmdt(shmaddr);
return 0;
}
shm_rm.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 3456
#define SIZE 1024
int main(){
int shmid;
shmid=shmget(KEY, SIZE, IPC_EXCL);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
此时运行shm_cw后再运行多次shm_r,则每次运行后都会读取共享内存段中的内容。
当运行shm_rm后,在运行shm_r时就会出现段错误。
所以有以下结论:共享内存段是一次写入多次读取
通过这种拆分文件的方式可以使多个可执行文件之间进行通信。
通过修改shm_cw.c中的权限为666后,其他用户也可以执行shm_r的程序读取共享内存段的内容
正常来讲key这个随机值因该是通过ftok函数来获取的
ftok有两个参数,第一个参数是一个可访问的文件路径;第二个参数是一个project_id,通常是随便定义的一个模256不为0的值
第一个参数的路径可以用各类系统都会有的绝对路径,也可以是该目录下的一定有的文件的相对路径
示例
shm_cw.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define SIZE 1024
int main(){
int shmid, key;
char *shmaddr=NULL;
key=ftok("/boot", 'c');
shmid=shmget(key, SIZE, IPC_CREAT|0666);
shmaddr=(char*)shmat(shmid, NULL, 0);
strcpy(shmaddr, "This is a message\n");
shmdt(shmaddr);
return 0;
}
shm_r.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SIZE 1024
int main(){
int shmid, key;
char *shmaddr=NULL;
key=ftok("/boot", 'c');
shmid=shmget(key, SIZE, IPC_EXCL);
shmaddr=(char *)shmat(shmid, NULL, 0);
printf("%s\n", shmaddr);
shmdt(shmaddr);
return 0;
}
shm_rm.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SIZE 1024
int main(){
int shmid, key;
key=ftok("/boot", 'c');
shmid=shmget(key, SIZE, IPC_EXCL);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
通过让key的值足够随机,可以确保os中不同用户以及不同进程使用的ipc通信界治的编号不同。
shmid=shmget(key, SIZE, IPC_EXCL);中key是全局的,而返回的id是进程局部的。
消息队列
注意
消息队列和共享内存段的一个不同在于,消息队列的读是一次性的,即读取后消息队列中的消息就会被取出。
使用示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MYSIZE 512
struct msgbuf{
long mytype;
char mytext[MYSIZE];
};
int main(){
int p, msgqid, key;
struct msgbuf mybuf;
key=ftok("/boot", 'c');
msgqid=msgget(key, IPC_CREAT|0600);
p=fork();
if (p){
//parent
mybuf.mytype=1;
strcpy(mybuf.mytext, "This is a message");
msgsnd(msgqid, &mybuf, MYSIZE, 0);
wait(NULL);
}else{
//child
msgrcv(msgqid, &mybuf, MYSIZE, 0, 0);
printf("%s\n", mybuf.mytext);
msgctl(msgqid, IPC_RMID, NULL);
}
return 0;
}
拆分成多文件形式
msg_c.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MYSIZE 512
struct msgbuf{
long mytype;
char mytext[MYSIZE];
};
int main(){
int msgqid, key;
struct msgbuf mybuf;
key=ftok("/boot", 'c');
msgqid=msgget(key, IPC_CREAT|0600);
return 0;
}
msg_snd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MYSIZE 512
struct msgbuf{
long mytype;
char mytext[MYSIZE];
};
int main(){
int msgqid, key, i;
struct msgbuf mybuf;
key=ftok("/boot", 'c');
msgqid=msgget(key, IPC_EXCL);
for (i=1; i<=6; i++){
mybuf.mytype=i;
strcpy(mybuf.mytext, "This is message ");
mybuf.mytext[strlen(mybuf.mytext)-1]=i+'0';
msgsnd(msgqid, &mybuf, MYSIZE, 0);
}
return 0;
}
msg_rcv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MYSIZE 512
struct msgbuf{
long mytype;
char mytext[MYSIZE];
};
int main(int argc, char* argv[]){
int p, msgqid, key;
struct msgbuf mybuf;
key=ftok("/boot", 'c');
msgqid=msgget(key, IPC_EXCL);
msgrcv(msgqid, &mybuf, MYSIZE, atoi(argv[1]), 0);
printf("%s\n", mybuf.mytext);
return 0;
}
通常让msgrcv的第4个参数为0,表示摘取消息队列中的队首信息
msg_rm.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MYSIZE 512
struct msgbuf{
long mytype;
char mytext[MYSIZE];
};
int main(){
int p, msgqid, key;
struct msgbuf mybuf;
key=ftok("/boot", 'c');
msgqid=msgget(key, IPC_EXCL);
msgctl(msgqid, IPC_RMID, NULL);
return 0;
}
信号量集
简单p v操作demo
sem1.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int P(int semid, int semnum){
struct sembuf sops={semnum, -1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int V(int semid, int semnum){
struct sembuf sops={semnum, +1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int main(){
int semid, key, ret;
union semun arg;
key=ftok("/boot", 'c');
semid=semget(key, 3, IPC_CREAT|0600);
if (!semid==-1){
//....
}
arg.val=1;
ret=semctl(semid, 0, SETVAL, arg);
if (ret==-1){
//...
}
printf("Set sem[0].val OK\n");
ret=semctl(semid, 0, GETVAL, arg);
if (ret==-1){
//...
}
printf("After semctl setval, sem[0].val=%d\n", ret);
printf("P operate begin at:\n");
system("date");
ret=P(semid, 0);
if (ret==-1){
//...
}
printf("P operate end at:\n");
system("date");
ret=semctl(semid, 0, GETVAL, arg);
if (ret==-1){
//...
}
printf("After P operate, sem[0].val=%d\n", ret);
printf("Start sleep 20 seconds, sleeping\n");
sleep(20);
printf("V operate begin at:\n");
system("date");
ret=V(semid, 0);
if(ret==-1){
//...
}
printf("V operate end at:\n");
system("date");
return 0;
}
sem2.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int P(int semid, int semnum){
struct sembuf sops={semnum, -1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int V(int semid, int semnum){
struct sembuf sops={semnum, +1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int main(){
int semid, key, ret;
union semun arg;
key=ftok("/boot", 'c');
semid=semget(key, 3, IPC_EXCL);
if (!semid==-1){
//....
}
ret=semctl(semid, 0, GETVAL, arg);
if (ret==-1){
//...
}
printf("Before P operate, sem[0].val=%d\n", ret);
printf("P operate begin at:\n");
system("date");
ret=P(semid, 0);
if (ret==-1){
//...
}
printf("P operate end at:\n");
system("date");
ret=semctl(semid, 0, GETVAL, arg);
if (ret==-1){
//...
}
printf("After P operate, sem[0].val=%d\n", ret);
printf("Start sleep 5 seconds, sleeping\n");
sleep(5);
printf("V operate begin at:\n");
system("date");
ret=V(semid, 0);
if(ret==-1){
//...
}
printf("V operate end at:\n");
system("date");
ret=semctl(semid, 0, GETVAL, arg);
if (ret==-1){
//,,,
}
printf("After V operate, sem[0].val=%d\n", ret);
semctl(semid, 0, IPC_RMID, arg);
return 0;
}
先运行sem1,在运行sem2时的结果
综合题目
三个进程,子进程1产生随机字符,放到共享内存1,子进程2从共享内存1取出放到共享内存2,子进程3从共享内存2取出,输出。
arg.array[0]控制子进程1和子进程2的互斥访问共享内存段1
arg.array[1]控制子进程1和子进程2的同步访问共享内存段1
arg.array[2]控制子进程2和子进程3的互斥访问共享内存段2
arg.array[3]控制子进程2和子进程3的同步访问共享内存段2
sem0.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int main(int argc, char* argv[]){
int sem_key, shm1_key, shm2_key;
int semid, shm1id, shm2id;
int p1, p2, p3;
union semun arg;
sem_key=ftok("/boot", 'X');
shm1_key=ftok("/boot", 'Y');
shm2_key=ftok("/boot", 'Z');
semid=semget(sem_key, 4, IPC_CREAT|0600);
shm1id=shmget(shm1_key, sizeof(char)*10, IPC_CREAT|0600);
shm2id=shmget(shm2_key, sizeof(char), IPC_CREAT|0600);
arg.array=(unsigned short*)malloc(4*sizeof(unsigned short));
arg.array[0]=1;
arg.array[1]=0;
arg.array[2]=1;
arg.array[3]=0;
semctl(semid, 0, SETALL, arg);
printf("The count of letters:%d\n", atoi(argv[1]));
p1=fork();
if (p1){
//parent
p2=fork();
if (p2){
//parent
p3=fork();
if (p3){
//parent
waitpid(p1, NULL, 0);
waitpid(p2, NULL, 0);
waitpid(p3, NULL, 0);
semctl(semid ,0, IPC_RMID);
shmctl(shm1id, IPC_RMID, NULL);
shmctl(shm2id, IPC_RMID, NULL);
free(arg.array);
printf("\n");
}else{
//child_3
execl("./sem3", argv[1], NULL);
}
}else{
//child_2
execl("./sem2", argv[1], NULL);
}
}else{
//child_1
execl("./sem1", argv[1], NULL);
}
return 0;
}
sem1.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
int P(int semid, int semnum){
struct sembuf sops={semnum, -1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int V(int semid, int semnum){
struct sembuf sops={semnum, +1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int main(int argc, char* argv[]) {
int sem_key, shm1_key;
int semid, shm1id, i;
char ch, *shm1addr=NULL;
sem_key=ftok("/boot", 'X');
shm1_key=ftok("/boot", 'Y');
semid=semget(sem_key, 0, IPC_EXCL);
shm1id=shmget(shm1_key, sizeof(char), IPC_EXCL);
for (i=1; i<=atoi(argv[0]); i++){
do{
ch=(char)(rand()%128);
}while (!(ch>='a' && ch<='z' || ch>='A' && ch<='Z'));
printf("The random letter is: %c\n", ch);
P(semid, 0);
shm1addr=(char*)shmat(shm1id, NULL, 0);
shm1addr[0]=ch;
//printf("%c----\n", ch);
shmdt(shm1addr);
V(semid, 0);
V(semid, 1);
sleep(rand()%3+1);
}
return 0;
}
sem2.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int P(int semid, int semnum){
struct sembuf sops={semnum, -1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int V(int semid, int semnum){
struct sembuf sops={semnum, +1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int main(int argc, char* argv[]){
int sem_key, shm1_key, shm2_key;
int semid, shm1id, shm2id, i;
char ch, *shm1addr=NULL, *shm2addr=NULL;
sem_key=ftok("/boot", 'X');
shm1_key=ftok("/boot", 'Y');
shm2_key=ftok("/boot", 'Z');
semid=semget(sem_key, 0, IPC_EXCL);
shm1id=shmget(shm1_key, sizeof(char), IPC_EXCL);
shm2id=shmget(shm2_key, sizeof(char), IPC_EXCL);
for (i=1; i<=atoi(argv[0]); i++){
P(semid, 1);
P(semid, 0);
shm1addr=(char *)shmat(shm1id, NULL, 0);
ch=shm1addr[0];
shmdt(shm1addr);
V(semid, 0);
if (ch>='a' && ch<='z') ch=ch-'a'+'A';
else if (ch>='A' && ch<='Z') ch=ch-'A'+'a';
P(semid, 2);
shm2addr=(char *)shmat(shm2id, NULL, 0);
shm2addr[0]=ch;
shmdt(shm2addr);
V(semid, 2);
V(semid, 3);
}
return 0;
}
sem3.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int P(int semid, int semnum){
struct sembuf sops={semnum, -1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int V(int semid, int semnum){
struct sembuf sops={semnum, +1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int main(int argc, char* argv[]){
int sem_key, shm2_key;
int semid, shm2id, i;
char ch, *shm2addr=NULL;
sem_key=ftok("/boot", 'X');
shm2_key=ftok("/boot", 'Z');
semid=semget(sem_key, 0, IPC_EXCL);
shm2id=shmget(shm2_key, sizeof(char), IPC_EXCL);
for (i=1; i<=atoi(argv[0]); i++){
P(semid, 3);
P(semid, 2);
shm2addr=(char *)shmat(shm2id, NULL, 0);
ch=shm2addr[0];
shmdt(shm2addr);
V(semid, 2);
printf("After transform: %c\n", ch);
}
return 0;
}