概述:知识点:

  • Linux 共享内存方式原理介绍
  • Linux 共享内存相关 API 介绍

0x01 Linux 进程间通信方式之共享内存概述

在 Linux 中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。如下图所示:

图片描述

当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。

0x02 Linux 共享内存使用步骤

  • 创建共享内存。也就是从内存中获得一段共享内存区域,这里用到的函数是 shmget()
  • 映射共享内存。也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数是 shmat() 。到这一步就可以使用这段共享内存了,也就是可以使用不带缓冲的 I/O 读写命令对其进行操作。
  • 撤销映射。使用完共享内存就需要撤销,用到的函数是 shmdt()

其中用到的 Linux 系统函数介绍如下:

  1. shmget() 函数创建共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
int shmget(key_t key, int size, int shmflg);
/*
参数:
    key:共享内存的键值,多个进程可以通过它访问同一个共享内存,其中 IPC_PRIVATE 用于创建当前进程私有的共享内存
    size:共享内存的大小
    shmflg:同 open() 函数的权限位
返回值:
    成功:共享内存段标识符
    失败:-1
*/
  1. shmat() 函数映射共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
char *shmat(int shmid, const void *shmaddr, int shmflg);
/*
参数:
    shmid:要映射的共享内存标识符
    shmaddr:将共享内存映射到指定地址,注意这里的是 void 型指针,如果是 0 则系统将自动分配地址并将共享内存映射到调用进程的地址空间
    shmflg:SHM_RDONLY(共享内存只读),0(可读写)
返回值:
    成功:被映射的段地址
    失败:-1
*/

实验一 shmem_demo

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define BUFFER_SIZE     256
 
int main(int argc, char *argv[])
{
    pid_t pid;
    int shmid;//共享内存标识符
    char *shm_addr = NULL;
    char flag[] = "SHIYANLOU";//标志字符串
    char buf[BUFFER_SIZE] = {0};
 
    /* 创建共享内存 */
    shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666);
    if(shmid < 0)
    {
        perror("shmget error");
        exit(1);
    }
    printf("Create shared-memory: %d \n", shmid);
 
    /* 显示共享内存情况 */
    system("ipcs -m");
 
    /* 创建子进程 */
    pid = fork();
    if(pid < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid == 0)
    {
        /* 子进程 */
        printf("[%d]Child prograss ...\n", getpid());
        //映射共享内存
        shm_addr = shmat(shmid, 0, 0);
        if(shm_addr == (char *)-1)
        {
            perror("Child: shmat");
            exit(1);
        }
        printf("[%d]Child : Attach shared-memory [%p]\n", getpid(), shm_addr);
        /* 显示内存情况 */
        system("ipcs -m");
        while(strncmp(shm_addr, flag, strlen(flag)))
        {
            printf("[%d]Child : Wait for enable data...\n", getpid());
            sleep(5);
        }
        /* 共享内存有效数据显示 */
        strcpy(buf, shm_addr+strlen(flag));
        printf("[%d]Child : Shared-memory: %s\n", getpid(), buf);
 
        /* 解除共享内存映射 */
        if((shmdt(shm_addr)) < 0)
        {
            perror("shmdt error");
            exit(1);
        }
        printf("[%d]Child : Deattach shared-memory\n", getpid());
        system("ipcs -m");
 
        /* 删除共享内存 */
        if(shmctl(shmid, IPC_RMID, NULL) == -1)
        {
            perror("Child : shmctl(IPC_RMID");
            exit(1);
        }
        printf("[%d]Child : Delete shared-memory\n", getpid());
        system("ipcs -m");
 
        /* 子进程退出 */
        exit(0);
    }
    else
    {
        /* 父进程 */
        shm_addr = shmat(shmid, 0, 0);
        if(shm_addr == (char *)-1)
        {
            perror("Parent: shmat");
            exit(1);
        }
        printf("[%d]Parent : Attach shared-memory : [%p]\n", getpid(), shm_addr);
        sleep(1);
 
        printf("\nInput some string:\n");
        fgets(buf, BUFFER_SIZE, stdin);
        strncpy(shm_addr + strlen(flag), buf, strlen(buf));
        strncpy(shm_addr, flag, strlen(flag));
 
        /* 解除共享内存映射 */
        if((shmdt(shm_addr)) < 0)
        {
            perror("Parent : shmdt error");
            exit(1);
        }
        printf("[%d]Parent : Deattach shared-memory\n", getpid());
        system("ipcs -m");
    }
 
    waitpid(pid, NULL, 0);
    printf("\nFinished...\n");
 
    return 0;
}

在 VS Code 平台下的终端窗口使用 gcc 工具完成可执行文件编译:

gcc shmem_demo.c -o shmem_demo
./shmem_demo

执行结果如下:

图片描述

实验二——shmem_sem.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define BUFFER_SIZE     256
 
/* 定义联合体 */
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};
 
 
 
int init_sem(int sem_id, int init_val)
{
    union semun sem_union;
    sem_union.val = init_val;
    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
    {
        perror("init semaphore error.\n");
        return -1;
    }
    return 0;
}
 
int del_sem(int sem_id)
{
    union semun sem_union;
    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
    {
        // perror("Delete semaphore error.\n");
        return -1;
    }
    return 0;
}
 
int sem_p(int sem_id)
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = -1;
    sem_b.sem_flg = SEM_UNDO;
 
    if(semop(sem_id, &sem_b, 1) == -1)
    {
        perror("P error.\n");
        return -1;
    }
    return 0;
}
 
int sem_v(int sem_id)
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;
    sem_b.sem_flg = SEM_UNDO;
 
    if(semop(sem_id, &sem_b, 1) == -1)
    {
        perror("P error.\n");
        return -1;
    }
    return 0;
}
 
int main(int argc, char *argv[])
{
    pid_t pid;
    int sem_id;
    int shmid;
    char *shm_addr = NULL;
    char buf[BUFFER_SIZE];
 
    //创建信号量
    sem_id = semget(ftok(".", 'a'), 1, 0666|IPC_CREAT);
    init_sem(sem_id, 0);
    //创建共享内存
    shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666);
    if(shmid < 0)
    {
        perror("shmget");
        exit(1);
    }
    printf("Create Shared-memory : %d\n", shmid);
 
    //显示共享内存情况
    system("ipcs -m");
    pid = fork();
    if(pid < 0)
    {
        perror("fork() error");
        exit(1);
    }
    else if(pid == 0)
    {
        //子进程
        //映射共享内存
        shm_addr = shmat(shmid, 0, 0);
        if(shm_addr == (char *)-1)
        {
            perror("Child: shmat");
            exit(1);
        }
        printf("[%d]Child : Attach shared-memory [%p]\n", getpid(), shm_addr);
        /* 显示内存情况 */
        system("ipcs -m");
 
        printf("[%d]Child : Wait for enable data...\n", getpid());
 
        /* 共享内存有效数据显示 */
        while(1)
        {
            memset(buf, 0, BUFFER_SIZE);
            sem_p(sem_id);
            strcpy(buf, shm_addr);
            memset(shm_addr, 0, BUFFER_SIZE);
            printf("[%d]Child : Shared-memory: %s\n", getpid(), buf);
 
            if((!strncmp(buf, "q", 1)) || (!strncmp(buf, "Q", 1)))
            {
                printf("[%d]Child Will Quit.\n", getpid());
                break;
            }
        }
 
        sem_v(sem_id);
        del_sem(sem_id);
        /* 解除共享内存映射 */
        if((shmdt(shm_addr)) < 0)
        {
            perror("shmdt error");
            exit(1);
        }
        printf("[%d]Child : Deattach shared-memory\n", getpid());
        system("ipcs -m");
 
        /* 删除共享内存 */
        if(shmctl(shmid, IPC_RMID, NULL) == -1)
        {
            perror("Child : shmctl(IPC_RMID");
            exit(1);
        }
        printf("[%d]Child : Delete shared-memory\n", getpid());
        system("ipcs -m");
 
        /* 子进程退出 */
        exit(0);
    }
    else
    {
        //父进程
        shm_addr = shmat(shmid, 0, 0);
        if(shm_addr == (char *)-1)
        {
            perror("Parent: shmat");
            exit(1);
        }
        printf("[%d]Parent : Attach shared-memory : [%p]\n", getpid(), shm_addr);
        sleep(1);
 
        while(1)
        {
            memset(buf, 0, BUFFER_SIZE);
            printf("\nInput some string:\n");
            fgets(buf, BUFFER_SIZE, stdin);
            strncpy(shm_addr, buf, strlen(buf));
            sem_v(sem_id);
            if((!strncmp(buf, "q", 1)) || (!strncmp(buf, "Q", 1)))
            {
                printf("[%d]Parent Will Quit.\n", getpid());
                break;
            }
        }
 
        /* 解除共享内存映射 */
        if((shmdt(shm_addr)) < 0)
        {
            perror("Parent : shmdt error");
            exit(1);
        }
        printf("[%d]Parent : Deattach shared-memory\n", getpid());
        system("ipcs -m");
    }
 
    waitpid(pid, NULL, 0);
    printf("\nFinished...\n");
 
    return 0;
}