什么是共享存储
顾名思义,共享存储段允许多个进程访问同一个存储区域。使用时,将共享的存储空间的地址连接到需要通信的进程中。但是,共享存储段并没有实现同步机制,需要自行使用信号量作为同步。
内核为每个共享存储都维护着一个结构:
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
获取共享存储段
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
- key: 关键值。
- size: 共享存储的长度,以字节为单位。
- 创建存储段:通常取系统页长PAGE_SIZE的整数倍。
- 引用存储段:赋值为0即可。
- shmflg:
- IPC_CREAT: 创建新的共存储段或者引用已存在的存储段。
- 如果不存在,则创建新的共享内存段,并返回段的ID。(表示创建)
- 如果已存在且未指定IPC_EXCL,则返回段的ID。(表示引用)
- 如果已存在且制定了IPC_EXCL,则返回-1并设置errno。(表示关键值正在使用,创建失败)
- IPC_EXCL: 配合IPC_CREAT使用。
- 权限: 八进制权限。
- IPC_CREAT: 创建新的共存储段或者引用已存在的存储段。
返回值:
- 成功:返回共享存储段的ID。
- 失败:返回-1,并设置errno。
连接共享存储段
#include <sys/types.h>
#include <sys/shm.h>
void* shmat(int shmid, const void* shmaddr, int shmflg);
参数:
- shmid: 共享存储段的ID
- shmaddr:
- 为0,内核分配一个可用地址。
- 不为0,并且没有指定SHM_RND,则连接到shmaddr指定的地址上。
- 不为0,并且指定了SHM_RND,则连接到shmaddr mod SHMLBA所表示的地址上。
- shmflg:
- SHM_RND: 取整。
- SHM_RDONLY: 以只读的方式连接。
- SHM_REMAP: take-over region on attach
- SHM_EXEC: execution access
返回值:
- 成功:连接地址。
- 失败:(void*)-1,设置errno。
操作共享存储段
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct semid_ds* buf);
参数:
- shmid: 共享存储段的ID。
- cmd:操作指令。
- IPC_STAT: 取此段的shmid_ds结构,并将它存储在由buf指向的结构体中。
- IPC_SET: 按buf指向的结构中的值设置与此相关的shmid_ds结构中的shm_perm.uid, shm_perm.gid, shm_perm.mode。该命令只能由具有超级用户特权的用户或者有效用户ID等于shm_perm.cuid或shm_perm.uid的进程。
- IPC_RMID: 从系统中删除该共享存储段。因为每个共享存储段维护着一个连接计数(shmid_ds结构中的shm_nattach字段),所以除非使用该段的最后一个j进程终止或与该段分离,否则不会实际上删除该存储段。不管此段是否仍在使用,该段的标识符立即被删除,所以不能再连接。该命令只能由具有超级用户特权的用户或者有效用户ID等于shm_perm.cuid或shm_perm.uid的进程。
返回值:
- 成功:根据cmd的值返回不同的值:
- IPC_INFO、SHM_INFO: 内核中共享内存段的索引
- SHM_STAT: shmid
- 其它:0
- 失败:-1,设置errno
分离共享存储段
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void* shmaddr);
参数:
- shmaddr:要删除的连接到共享存储段的地址,也就是shmat()返回的地址。
返回值:
- 成功:0。
- 失败:-1,设置errno。
示例
//sem_writer.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <signal.h>
#define SHM_SIZE 4096
int shm_id;
int sem_id;
union semun
{
int val;
struct semid_ds* buf;
unsigned short* array;
struct seminfo* __buf;
void* __pad;
};
int sig_handler(int signum)
{
shmctl(shm_id, IPC_RMID, NULL);
semctl(sem_id, 0, IPC_RMID);
exit(0);
}
int main()
{
// 注册信号
signal(SIGINT, sig_handler);
signal(SIGKILL, sig_handler);
// 获得关键值
key_t shm_key = ftok(".", 1);
key_t sem_key = ftok(".", 2);
// 创建共享存储段
shm_id = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0600);
if(shm_id < 0)
{
perror("shmget error");
exit(-1);
}
printf("shm id: %d\n", shm_id);
// 创建信号量
sem_id = semget(sem_key, 1, IPC_CREAT | 0600);
if(sem_id < 0)
{
perror("semget error");
exit(-1);
}
printf("sem id: %d\n", sem_id);
// 操作信号量
union semun sem_un;
sem_un.val = 1;
semctl(sem_id, 0, SETVAL, sem_un);
struct sembuf sem_v = {0, 1, 0};
// 连接共享存储段
void* shm_addr = shmat(shm_id, NULL, SHM_RND);
if(shm_addr == (void*)-1)
{
perror("shmat error");
shmctl(shm_id, IPC_RMID, NULL);
exit(-1);
}
// 操作共享存储段
while(1)
{
int n = read(0, (char*)shm_addr, SHM_SIZE - 1);
*(char*)(shm_addr + n) = '\0';
semop(sem_id, &sem_v, 1);
sleep(1);
}
return 0;
}
//shm_writer.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <signal.h>
#define SHM_SIZE 4096
int shm_id;
int sem_id;
union semun
{
int val;
struct semid_ds* buf;
unsigned short* array;
struct seminfo* __buf;
void* __pad;
};
int sig_handler(int signum)
{
shmctl(shm_id, IPC_RMID, NULL);
semctl(sem_id, 0, IPC_RMID);
exit(0);
}
int main()
{
// 注册信号
signal(SIGINT, sig_handler);
signal(SIGKILL, sig_handler);
// 获得关键值
key_t shm_key = ftok(".", 1);
key_t sem_key = ftok(".", 2);
// 创建共享存储段
shm_id = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0600);
if(shm_id < 0)
{
perror("shmget error");
exit(-1);
}
printf("shm id: %d\n", shm_id);
// 创建信号量
sem_id = semget(sem_key, 1, IPC_CREAT | 0600);
if(sem_id < 0)
{
perror("semget error");
exit(-1);
}
printf("sem id: %d\n", sem_id);
// 操作信号量
union semun sem_un;
sem_un.val = 1;
semctl(sem_id, 0, SETVAL, sem_un);
struct sembuf sem_p = {0, -1, 0};
// 连接共享存储段
void* shm_addr = shmat(shm_id, NULL, SHM_RND);
if(shm_addr == (void*)-1)
{
perror("shmat error");
shmctl(shm_id, IPC_RMID, NULL);
exit(-1);
}
// 操作共享存储段
while(1)
{
semop(sem_id, &sem_p, 1);
printf("%s", (char*)shm_addr);
sleep(1);
}
return 0;
}