UNIX环境高级编程 - 共享内存
0. 概述
共享内存允许两个或多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间内存的一部分,因此这种IPC 机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。
为使用一个共享内存段通常需要执行下面的步骤。
-
调用 shmget()创建一个新共享内存段或取得一个既有共享内存段的标识符(即由其他进程创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。
-
使用 shmat()来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分。
-
此刻在程序中可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由 shmat()调用返回的 addr 值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针。
1. System V 共享内存相关API
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int semget(key_t key, int size, int flag);
void *shmat(int shmid, void *addr, int flag);
int shmdt(void *addr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmget
获得或创建一个共享内存标识符。
int semget(key_t key, size_t size, int shmflag);
// 成功返回一个共享内存标识符,失败返回-1;
- 第一个参数key为共享内存段命名;
- 第二个参数size为需要共享的内存容量。(如果共享内存已存在时,不能不大于该共享内存段的大小);
- 第三个参数设置访问权限(低9位)与IPC_CREAT, IPC_EXCL 的按位或。
shmat
将共享内存段连接到一个进程的地址空间中。
void *shmat(int shm_id, const void *addr, int shmflg) ;
// 成功返回共享存储段连接的实际地址,失败返回-1
- 第一个参数shm_id为shmget返回的共享内存标识符。
- 第二个参数addr指明共享内存段要连接到的地址(进程空间内部地址),通常指定为空指针(NULL),表示让系统来选择共享内存在进程地址空间中出现的地址。
- 第三个参数shmflg可以设置为两个标志位(通常设置为0)
- SHM_RND( 表示第二个参数指定的地址应被向下靠拢到内存页面大小的整数倍)
- SHM_RDONLY,要连接的共享内存段是只读的。
shmdt
将共享内存从当前进程中分离。
int shmdt(const void *shmaddr) ; //其中shmaddr为shmat返回的地址。
shmctl
查看及修改共享内存段的shmid_ds结构,删除该结构以及相连的共享存储段标识。
int shmctl(int shm_id, int command, struct shmid_ds *buf) ;
// 成功返回0,失败返回-1
-
第一个参数shm_id为shmget返回的共享内存标识符。
-
第二个参数commad取值:
-
IPC_STAT 获取当前共享内存段的shmid_ds结构
-
IPC_SET 把共享内存段的当前关联值设置为shmid_ds结构给出的值
-
IPC_RMID 从系统中删除该共享存储段。
-
-
第三个参数buf是一个结构指针,它指向共享内存模式和访问权限的结构
struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };
2. 例子-生产者-消费者问题
使用共享内存充当生产者-消费者问题的缓冲区。
生产者:producer.c
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#define NUMBER 300
#define BUFSIZE 10
// critical resource
int* buffer;
// semaphore
sem_t* empty;
sem_t* full;
sem_t* mutex;
void producer() {
int buf_in = 0;
for (int i = 0 ; i < NUMBER; i++) {
sem_wait(empty);
sem_wait(mutex);
buffer[buf_in] = i;
buf_in = (buf_in + 1) % BUFSIZE;
printf("produce %d\n", i);
fflush(stdout);
sem_post(mutex);
sem_post(full);
}
}
void quitHandler(int i) {
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
exit(-1);
}
int main() {
signal(SIGINT, quitHandler);
mutex = sem_open("mutex", O_CREAT , 0644, 1);
full = sem_open("full", O_CREAT, 0644, 0);
empty = sem_open("empty", O_CREAT, 0644, BUFSIZE);
if (mutex == NULL || full == NULL || empty == NULL) {
printf("error in sem_open %p %p %p\n", mutex, full, empty);
return -1;
}
int shmid = shmget((key_t)1121, sizeof(int) * BUFSIZE, 0666 | IPC_CREAT);
void* shm = shmat(shmid, 0, 0);
buffer = (int*)shm;
producer();
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
return 0;
}
消费者:consumer.c
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#define NUMBER 300
#define BUFSIZE 10
// critical resource
int* buffer = NULL;
// semaphore
sem_t* empty;
sem_t* full;
sem_t* mutex;
void consumer() {
int buf_out = 0;
int data = 0;
for (int k = 0; k < NUMBER; k++) {
sem_wait(full);
sem_wait(mutex);
data = buffer[buf_out];
printf("comsume %d\n", data);
buf_out = (buf_out + 1) % BUFSIZE;
fflush(stdout);
sem_post(mutex);
sem_post(empty);
}
}
void quitHandler(int i) {
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
exit(-1);
}
int main() {
signal(SIGINT, quitHandler);
mutex = sem_open("mutex", O_CREAT, 0644, 1);
full = sem_open("full", O_CREAT, 0644, 0);
empty = sem_open("empty", O_CREAT , 0644, BUFSIZE);
if (mutex == NULL || full == NULL || empty == NULL) {
printf("error in sem_open %p %p %p\n", mutex, full, empty);
return -1;
}
int shmid = shmget((key_t)1121, sizeof(int) * BUFSIZE, 0666 | IPC_CREAT);
if (shmid == -1) {
exit(-1);
}
void* shm = shmat(shmid, 0, 0);
buffer = (int*)shm;
consumer();
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
return 0;
}
编译命令:
gcc consumer.c -o comsumer -lpthread
gcc consumer.c -o consumer -lpthread
让消费者先在后台执行,执行命令:
./consumer &
./producer
如何杀后台任务?
jobs -l
kill -9 pid