原理:
[进程1] [进程2]
strcpy ↓ ↑ printf
________↓__________↑_____
| | 共享内存 | |
| |(PAGE_SIZE) | | (内存空间)
|_____|______________|__|
(共享内存原理)
(1).共享内存概述
1.管道、 FIFO 和消息队列,任意两个进程之间想要交换信息,都必须通过内核,
内核在其中发挥了中转站的作用:
-------------------------------------------------------------------------
| [进程A] [进程B] |
| \write(msgsnd) /read/msgrcv 用户层 |
|-----------------------------------------------------------------------|
| |
| \ / 内核层 | | |
| ↓ ↑ | | |
| [IPC(管道.FIFO.消息队列)] |
|-------------------------------------------------------------------------
(管道、 FIFO 和消息队列 )
缺点: 一个通信周期内,上述过程至少牵扯到两次内存拷贝和两次系统调用
2.共享内存: 内核搭台, 进程唱戏。
步骤:1.内核负责构建出一片内存区域
2.两个或多个进程可以将这块内存区域映射到自己的虚拟地址空间
3.从此之后内核不再参与双方通信,进程之间使用共享内存通信
(当进程使用共享内存时,可能会发生缺页,引发缺页中断,这种情况下,内核还是会参与进来的。)
4.共享内存所有进程间通信方式中效率最高的一种.
5.进程可以像操作普通进程的地址空间一样操作这块共享内存,一个进程可以将信息写入这片内存区域,
而另一个进程也可以看到共享内存里面的信息,从而达到通信的目的
6.注意:
要防止多个进程同时操作同一块共享内存(使用信号量(生产者-消费者模型)搭配使用,来控制共享内存资源的互斥访问)
--------------------------------------------------
| 用户空间: |
| |--------| |--------| |
| | 进程A | |进程B | |
| |--------| |--------| |
| | map | | map | |
| |--------| |--------| |
| ↓ ↑ ↓ ↑ |
| [ 共 享 内 存 ] |
--------------------------------------------------
(2)创建或打开共享内存
int shmget(key_t key, size_t size, int shmflg);
size: 必须是正整数,实际 size 会被向上取整为页面大小的整数倍。
shmflg:支持 IPC_CREAT 和 IPC_EXCL 标志位
单个共享内存段的最大字节数为 SHMMAX //cat /proc/sys/kernel/shmmax
(3)使用共享内存: 将共享内存映射到进程的虚拟地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
通常会将第二个参数设置为 NULL,表示由内核分配共享内存区域
shmid: 为shmget返回的共享内存标识符ID
shmflg: 如果进程仅仅是读取共享内存段的内容,并不修改,则可以指定 SHM_RDONLY 标志位。
// SHM_KEY所在的共享内存不存在时创建他, 若存在返回错误
第一个进程: shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0777);
// 打开SHM_KEY所在的共享内存
第二个进程: shmid = shmget(SHM_KEY, SHM_SIZE, 0777);
(4) 分离共享内存
int shmdt(const void *shmaddr);
1.shmdt 函数仅仅是使进程和共享内存脱离关系,并未删除共享内存
2.shmdt 函数的作用是将共享内存的引用计数减 1
3.只有共享内存的引用计数为 0 时,调用 shmctl 函数的 IPC_RMID 命令才会真
正地删除共享内存。
(5)控制共享内存:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
IPC_STAT: 获取 shmid 对应的共享内存的信息(struct shmid_ds信息)
IPC_SET: 只能修改 shm_perm 中的 uid 、 gid 及 mode 。
IPC_RMID: 如果共享内存的引用计数 shm_nattch 等于 0 ,则可以立即删除共享内存。但是如果仍然存在进程
attach 该共享内存,则并不执行真正的删除操作,而仅仅是设置 SHM_DEST 标记。待所有进程都执行过
分离操作之后,再执行真正的删除操作。
//,共享内存处于 SHM_DEST 状态的情况下,依然允许新的进程调用 shmat 函数来 attach该共享内存。
...........
(6)key的获取方法(密钥)
方法1: (1-5000)内的任意整数
方法2: key_t ftok(const char *pathname, int proj_id); // 任意合法路径,任意整数
(7)程序范例(使用共享内存搭配信号量, 构造两个进程的聊天程序)
1.semaphore_lib.c是对System v型号量的常用封装
2.shm1.c shm2.c 是两个聊天进程
3.semaphore_lib.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/sem.h>
/* 控制信号量 */
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */
};
// 获取信号量集中指定信号量的值
int sema_getval(int semid, int index)
{ union semun ignore_arg;
return semctl(semid, index, GETVAL, ignore_arg);
}
// 设置信号量集中指定信号量为value
int sema_setval(int semid, int index, int val)
{ union semun arg;
arg.val = val;
return semctl(semid, index, SETVAL, arg);
}
// 立即删除信号量
int sema_rmid(int semid)
{ union semun ignore_arg;
int ignore_index;
return semctl(semid, ignore_index, IPC_RMID, ignore_arg);
}
/* 操作信号量 */
// 申请资源(操作一个信号量)
int semaphore_wait (int semid, int index)
{
struct sembuf operations[1];
operations[0].sem_num = index; // 操作一组信号量中的哪一个信号量
operations[0].sem_op = -1; // 信号量0的值 + 1
operations[0].sem_flg |= SEM_UNDO; // 进程退出后恢复信号量(成对出现)
return semop(semid, operations, 1); // 信号量集合操作
}
// 释放资源(操作一个信号量)
int semaphore_post(int semid, int index)
{
struct sembuf operations[1];
operations[0].sem_num = index; // 操作一组信号量中的哪一个信号量
operations[0].sem_op = 1; // 信号量0的值 - 1
operations[0].sem_flg |= SEM_UNDO; // 进程退出后恢复信号量(成对出现)
return semop(semid, operations, 1);
}
void test(void)
{
printf("test\n");
}
5.shm1.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include "semaphore_lib.h"
#define SHM_KEY 0x123
#define SEM_KEY 0x234
#define SHM_SIZE 1024
#define SEM0 0
#define SEM1 1
#define CUR printf("cur: %s %d\n", __FILE__, __LINE__)
#define ERR printf("Err in: %s %d\n", __FILE__, __LINE__)
int main(int argc, char **argv) // 简化代码,不判断返回值
{ test();
int shmid, semid, ret;
unsigned char *addr;
char buf[50];
// 创建共享内存
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0777);
if (shmid < 0)
{ shmid = shmget(SHM_KEY, SHM_SIZE, 0777);
if (shmid < 0)
ERR;
}
// 映射共享内存到主进程虚拟地址空间
addr = shmat(shmid, NULL, 0);
// 创建信号量
semid = semget(SEM_KEY, 2, IPC_CREAT | IPC_EXCL | 0777); // 创建信号量:信号量级中有2个信号量
if (semid < 0) {
semid = semget(SEM_KEY, 0, 0777); // 打开信号量
if (semid < 0)
ERR;
}
// 初始化信号量的资源
sema_setval(semid, SEM0, 0); // 消费者初始状态没有资源
sema_setval(semid, SEM1, 0); // 消费者初始状态没有资源
ret = fork();
if (ret < 0)
//ERR;
CUR;
else if (!ret) //child
{
// 映射共享内存到子进程虚拟地址空间
addr = shmat(shmid, NULL, 0); // addr和主进程addr的可不一样
bzero(buf, 50);
while(1) // 存储数据(生产者模型)
{ strcpy(addr, fgets(buf, 50, stdin)); // 未在终端键入时,陷入阻塞
semaphore_post(semid, SEM0); // 生产资源
}
exit(0); //这里往下的代码不会被子进程执行, 所以主进程没有判断返回值
}
// parent
while(1) //读出数据(消费者模型)
{ semaphore_wait(semid, SEM1); // 消费资源(没有资源时,陷入阻塞)
printf("shm1 recv: %s\n", addr);
}
return 0;
}
6.shm2.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include "semaphore_lib.h"
#define SHM_KEY 0x123
#define SEM_KEY 0x234
#define SHM_SIZE 1024
#define SEM0 0
#define SEM1 1
#define CUR printf("cur: %s %d\n", __FILE__, __LINE__)
#define ERR printf("Err in: %s %d\n", __FILE__, __LINE__)
int main(int argc, char **argv) // 简化代码,不判断返回值
{ test();
int shmid, semid, ret;
unsigned char *addr;
char buf[50];
// 创建共享内存
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0777);
if (shmid < 0)
{ shmid = shmget(SHM_KEY, SHM_SIZE, 0777);
if (shmid < 0)
ERR;
}
// 映射共享内存到主进程虚拟地址空间
addr = shmat(shmid, NULL, 0);
// 创建信号量
semid = semget(SEM_KEY, 2, IPC_CREAT | IPC_EXCL | 0777); // 创建信号量:信号量级中有2个信号量
if (semid < 0) {
semid = semget(SEM_KEY, 0, 0777); // 打开信号量
if (semid < 0)
ERR;
}
// 初始化信号量的资源
sema_setval(semid, SEM0, 0); // 消费者初始状态没有资源
sema_setval(semid, SEM1, 0); // 消费者初始状态没有资源
ret = fork();
if (ret < 0)
//ERR;
CUR;
else if (!ret) //child
{
// 映射共享内存到子进程虚拟地址空间
addr = shmat(shmid, NULL, 0); // addr和主进程addr的可不一样
bzero(buf, 50);
while(1) // 存储数据(生产者模型)
{ strcpy(addr, fgets(buf, 50, stdin)); // 未在终端键入时,陷入阻塞
semaphore_post(semid, SEM0); // 生产资源
}
exit(0); //这里往下的代码不会被子进程执行, 所以主进程没有判断返回值
}
// parent
while(1) //读出数据(消费者模型)
{ semaphore_wait(semid, SEM1); // 消费资源(没有资源时,陷入阻塞)
printf("shm1 recv: %s\n", addr);
}
return 0;
}
7.编译执行结果
终端1 执行shm1, 终端2 执行shm2,
book@gui_hua_shu$ ./shm1
nihao woshi shm1
shm1 recv: nihao woshi shm2
book@gui_hua_shu$ ./shm2
shm1 recv: nihao woshi shm1
nihao woshi shm2