共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
1 #include
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr);
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
当用shmget
函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat
函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
shmdt
函数是用来断开shmat
建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
shmctl
函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID
(从系统中删除该共享内存)。
下面这个例子,使用了【共享内存+信号量+消息队列】的组合来实现服务器进程与客户进程间的通信。
server.c
#include
#include
#include // shared memory
#include // semaphore
#include // message queue
#include // memcpy
// 消息队列结构
struct msg_form {
long mtype;
char mtext;
};
// 联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
};
// 初始化信号量
int init_sem(int sem_id, int value)
{
union semun tmp;
tmp.val = value;
if(semctl(sem_id, 0, SETVAL, tmp) == -1)
{
perror("Init Semaphore Error");
return -1;
}
return 0;
}
// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = -1; /*P操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}
// 删除信号量集
int del_sem(int sem_id)
{
union semun tmp;
if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
{
perror("Delete Semaphore Error");
return -1;
}
return 0;
}
// 创建一个信号量集
int creat_sem(key_t key)
{
int sem_id;
if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
{
perror("semget error");
exit(-1);
}
init_sem(sem_id, 1); /*初值设为1资源未占用*/
return sem_id;
}
int main()
{
key_t key;
int shmid, semid, msqid;
char *shm;
char data[] = "this is server";
struct shmid_ds buf1; /*用于删除共享内存*/
struct msqid_ds buf2; /*用于删除消息队列*/
struct msg_form msg; /*消息队列用于通知对方更新了共享内存*/
// 获取key值
if((key = ftok(".", 'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 创建共享内存
if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
{
perror("Create Shared Memory Error");
exit(1);
}
// 连接共享内存
shm = (char*)shmat(shmid, 0, 0);
if((int)shm == -1)
{
perror("Attach Shared Memory Error");
exit(1);
}
// 创建消息队列
if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
// 创建信号量
semid = creat_sem(key);
// 读数据
while(1)
{
msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
if(msg.mtext == 'q') /*quit - 跳出循环*/
break;
if(msg.mtext == 'r') /*read - 读共享内存*/
{
sem_p(semid);
printf("%s\n",shm);
sem_v(semid);
}
}
// 断开连接
shmdt(shm);
/*删除共享内存、消息队列、信号量*/
shmctl(shmid, IPC_RMID, &buf1);
msgctl(msqid, IPC_RMID, &buf2);
del_sem(semid);
return 0;
}
client.c
#include
#include
#include // shared memory
#include // semaphore
#include // message queue
#include // memcpy
// 消息队列结构
struct msg_form {
long mtype;
char mtext;
};
// 联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
};
// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = -1; /*P操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}
int main()
{
key_t key;
int shmid, semid, msqid;
char *shm;
struct msg_form msg;
int flag = 1; /*while循环条件*/
// 获取key值
if((key = ftok(".", 'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 获取共享内存
if((shmid = shmget(key, 1024, 0)) == -1)
{
perror("shmget error");
exit(1);
}
// 连接共享内存
shm = (char*)shmat(shmid, 0, 0);
if((int)shm == -1)
{
perror("Attach Shared Memory Error");
exit(1);
}
// 创建消息队列
if ((msqid = msgget(key, 0)) == -1)
{
perror("msgget error");
exit(1);
}
// 获取信号量
if((semid = semget(key, 0, 0)) == -1)
{
perror("semget error");
exit(1);
}
// 写数据
printf("***************************************\n");
printf("* IPC *\n");
printf("* Input r to send data to server. *\n");
printf("* Input q to quit. *\n");
printf("***************************************\n");
while(flag)
{
char c;
printf("Please input command: ");
scanf("%c", &c);
switch(c)
{
case 'r':
printf("Data to send: ");
sem_p(semid); /*访问资源*/
scanf("%s", shm);
sem_v(semid); /*释放资源*/
/*清空标准输入缓冲区*/
while((c=getchar())!='\n' && c!=EOF);
msg.mtype = 888;
msg.mtext = 'r'; /*发送消息通知服务器读数据*/
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
break;
case 'q':
msg.mtype = 888;
msg.mtext = 'q';
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
flag = 0;
break;
default:
printf("Wrong input!\n");
/*清空标准输入缓冲区*/
while((c=getchar())!='\n' && c!=EOF);
}
}
// 断开连接
shmdt(shm);
return 0;
}
注意:当scanf()
输入字符或字符串时,缓冲区中遗留下了\n
,所以每次输入操作后都需要清空标准输入的缓冲区。但是由于 gcc 编译器不支持fflush(stdin)
(它只是标准C的扩展),所以我们使用了替代方案:
1 while((c=getchar())!='\n' && c!=EOF);
定义:
共享内存(Shared Memory)就是允许两个或多个进程访问同一个内存空间,是在多进程通信的最高效的方式。
操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。由于共享内存会成为进程用户空间的一部分,所有这种通信方式不需要内核介入。
共享内存并未提供锁机制,也就是说,在某一个进程对共享内存的进行读写的时候,不会阻止其它的进程对它的读写,可能会出现数据的错乱。
函数:
1、必备头文件
#include
#include
1
2
2、shmget() 获取或创建共享内存
// 获取或者创建共享内存
int shmget(key_t key, size_t size, int shmflg);
- key是共享内存的键值,是一个整数,是共享内存在系统中的编号,不同共享内存的编号不同。一般使用十六进制。
- size表示待创建共享内存的大小,以字节为单位
- shmflg表示共享内存的访问权限,与文件的权限一样,
0666 | IPC_CREAT 表示全部用户对它可读写
- 返回值:返回共享内存标识
3、shmat() 把共享内存连接到当前进程的地址空间
void *shmat(int shm_id, const void *shm_addr, int shmflg);
- shm_id表示由 shmget函数返回的 共享内存的标识号
- shm_addr指定共享内存连接到当前进程中的地址位置,通常为 NULL,表示让系统来选择共享内存的地址。
- shm_flg是一组标志位,通常为0
- 返回值:
调用成功:返回一个指向共享内存第一个字节的指针
调用失败:返回 -1
4、shmdt() 将共享内存从当前进程中分离,相当于 shmat()函数的反操作
int shmdt(const void *shmaddr);
- shmaddr是 shmat函数 返回的地址
- 返回值:
调用成功:返回 0
调用失败:返回 -1
5、shmctl() 删除共享内存int shmctl(int shm_id, int command, struct shmid_ds *buf);
- shm_id是共享内存的标识号
- command 填写 IPC_RMID
- buf 填写 0
- 返回值:
调用成功:返回 0
调用失败:返回 -1
使用步骤
调用 shmget() 函数创建一个新共享内存段或取得一个现有的共享内存段的标识号。返回后续可调用的需要用到的共享内存标识符。
在进程中使用 shmat() 函数附上共享内存段,即让该共享内存段成为调用进程的虚拟内存中的一部分(进程绑定共享内存段)
使用 shmat() 返回的内存地址指针,就可以在程序中对该共享内存段进行操作(写操作/读操作)。
调用 shmdt() 函数,让进程和共享内存段分离,进程无法再操作共享内存了。(只是分离,共享内存依然存在)
调用 shmctl() 函数,删除共享内存段,只有当与该共享内存段绑定好的进程都分离后,才会销毁,只有一个进程需要执行这一步。
举例
// write_shm.c
#include
#include
#include
#include
#include
int main()
{
int shmid; // 共享内存标识符
// 创建共享内存,键值为0x5005,共1024字节。
if ( (shmid = shmget((key_t)0x5005, 1024, 0666 | IPC_CREAT)) == -1)
{
printf("shmat(0x5005) failed\n");
return -1;
}
char *ptext = NULL; // 用于指向共享内存的指针
// 将共享内存连接到当前进程的地址空间,由ptext指针指向它
ptext = (char *)shmat(shmid, NULL, 0);
// 操作本程序的ptext指针,就是操作共享内存
char *str = "Hello world!";
memcpy(ptext, str, strlen(str));
// 把共享内存从当前进程中分离
shmdt(ptext);
return 0;
}
// read_shm.c
int main()
{
int shmid; // 共享内存标识符
// 创建共享内存,键值为0x5005,共1024字节。
if ( (shmid = shmget((key_t)0x5005, 1024, 0666 | IPC_CREAT)) == -1)
{
printf("shmat(0x5005) failed\n");
return -1;
}
char *ptext = NULL; // 用于指向共享内存的指针
// 将共享内存连接到当前进程的地址空间,由ptext指针指向它
ptext = (char *)shmat(shmid, NULL, 0);
// 操作本程序的ptext指针,就是操作共享内存
printf("%s\n", ptext);
// 把共享内存从当前进程中分离
shmdt(ptext);
// 删除共享内存
if (shmctl(shmid, IPC_RMID, 0) == -1)
{
printf("shmctl(0x5005) failed\n");
return -1;
}
}
read_shm.c的输出结果:
Hello world!
1
可以看出,write_shm.c 申请共享内存,并写入数据,read_shm获取共享内存标识号,并绑定进程,读取该共享内存中的数据。
查看共享内存
ipcs -m # 查看现有的所有共享内存段
ipcrm -m shmid # 根据共享内存段的标识号,删除共享内存
————————————————
版权声明:本文为CSDN博主「HDD615」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Sir666888/article/details/125460499
一、进程间通信
管道、信号:只能用于两个进程间进行通讯
共享内存、消息队列、信号量、套接字
二、IPC通信
1.相关知识
1)IPC可用于多个进程间进行通讯,包括共享内存、消息队列、信号量。
2)指令;
ipcs -m:查看共享内存
ipcs -q:查看消息队列
ipcs -s:查看信号量
3)键值:用于标识共享内存、消息队列以及信号量集,确保唯一性。
ftok():键值的创造
头文件:
#include
#include
原型:key_t ftok(const char *pathname, int proj_id);
参数:
const char *pathname:参考位置 (绝对路径)
int proj_id:参考的id (0-255)
返回值: 成功,返回键值(key_t) 失败,返回 -1
2.共享内存
1)什么是共享内存?
在操作系统内核开辟一个空间,每一个进程都可以将这个空间映射到自己的进程空间里面,多个进程可以共享这块内存,
对他的操作都可以立即实现。
范围:整个计算机的进程,都可以对它进行操作。
2)过程
创建共享内存
进程映射到内存进行读写
进程接触映射
删除共享内存
3)相关函数
shmget():打开或者创建一块共享内存
头文件:
#include
#include
原型:int shmget(key_t key, size_t size, int shmflg);
参数:
key_t key:键值
size_t size:共享内存的大小,以字节为单位
int shmflg:有则打开,没有则创建
IPC_CREAT | 0777
返回值: 成功,返回的是共享内存的id号 失败,返回 -1
shmat():把一个共享内存映射到进程空间
头文件:
#include
#include
原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
int shmid, :共享内存的id
const void *shmaddr, :你要将共享内存映射的位置一般填写0,表示由系统分配一块,可用的地址空间
int shmflg:一般填写0 表示共享内存可读写
返回值: 系统给分配的空间的首地址 失败返回-1
shmdt():解除共享内存的映射
头文件:
#include
#include
原型:int shmdt(const void *shmaddr);
参数:系统给分配的空间的首地址
返回值:成功 0 失败 -1
shmctl():删除共享内存
头文件:
#include
#include
原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
int shmid,:贡献内存的id
int cmd, :IPC_RMID
struct shmid_ds *buf:NULL
返回值:成功 0 失败 -1
下边是一个练习代码:
#include
#include
#include
#include
#include
int main()
{
key_t ft;
int shmid;
ft = ftok("/home/whs/002/7.4",5);
if(ft < 0)
{
perror("ftok");
return -1;
}
printf("ft=%d\n",ft);
shmid = shmget(ft,256,IPC_CREAT|0666); //创建共享内存
if(shmid < 0)
{
perror("shmget");
return -1;
}
void *p = shmat(shmid,0,0); //映射到进程空间,0,由系统分配;0,可读写
strcpy(p,"zhongguowansui!");//写内容到共享内存
shmdt(p); //解除映射
return 0;
}
文章知识点与官方知识档案匹配,可进一步学习相关知识
————————————————
版权声明:本文为CSDN博主「编程小白菜123」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wanghongshuai1/article/details/125604916