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
posted @ 2022-04-24 15:31  zju_cxl  阅读(45)  评论(0编辑  收藏  举报