Linux共享内存及函数

时间:2022-07-22
本文章向大家介绍Linux共享内存及函数,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

共享内存的概念

共享内存是指多个进程可以把一段内存共同的内存映射到自己的进程空间中,从而实现数据的共享和传输,它是存在与内核级别的一种资源,是所有进程间通信中方式最快的一种。

在shell环境下可以使用ipcs查看当前系统IPC中的状态,例如当前的电脑中:

$ ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 2260992    deeplearni 600        524288     2          dest
0x00000000 2490369    deeplearni 600        67108864   2          dest
0x00000000 163842     root       777        7680       2
0x00000000 196611     root       777        8294400    2
0x00000000 229380     root       777        4096       2
0x00000000 262149     root       777        8192       2
0x00000000 294918     root       777        12288      2
...省略
0x00000000 2064444    root       777        233472     2
0x00000000 2097213    root       777        237568     2
0x00000000 2129982    root       777        241664     2
0x00000000 2162751    root       777        245760     2
0x00000000 2654272    deeplearni 600        524288     2          dest
0x00000000 2687041    deeplearni 600        524288     2          dest
0x00000000 2719810    deeplearni 600        524288     2          dest
0x00000000 2752579    deeplearni 600        524288     2          dest
0x00000000 2981956    deeplearni 600        524288     2          dest
0x00000000 2949189    deeplearni 600        524288     2          dest
0x00000000 3014726    deeplearni 600        67108864   2          dest

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

该命令使用附加参数可单独查看某一IPC:-m(共享内存),-q(消息队列),-s(信号量)。

由于多个进程对同一块内存区域具有访问权限,各进程间的同步问题需要解决,可以配合信号量进行控制。

对于每一个共享内存段,内核会为其维护一个shmid_ds类型的结构体:

// 摘自所用ubuntu18.04电脑中的/usr/include/i386-linux-gnu/bits/shm.h
struct shmid_ds
  {
    struct ipc_perm shm_perm;           /* operation permission struct */
    size_t shm_segsz;                   /* size of segment in bytes */
    __time_t shm_atime;                 /* time of last shmat() */
#ifndef __x86_64__
    unsigned long int __glibc_reserved1;
#endif
    __time_t shm_dtime;                 /* time of last shmdt() */
#ifndef __x86_64__
    unsigned long int __glibc_reserved2;
#endif
    __time_t shm_ctime;                 /* time of last change by shmctl() */
#ifndef __x86_64__
    unsigned long int __glibc_reserved3;
#endif
    __pid_t shm_cpid;                   /* pid of creator */
    __pid_t shm_lpid;                   /* pid of last shmop */
    shmatt_t shm_nattch;                /* number of current attaches */
    __syscall_ulong_t __glibc_reserved4;
    __syscall_ulong_t __glibc_reserved5;
  };

共享内存的相关操作

创建/打开共享内存

创建共享内存需要用到shmget()函数,原型如下:

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flag);

创建成功返回共享内存的ID,出错返回-1。

参数key为共享内存的键值,参数size为创建共享内存的大小,参数flag为调用函数的操作类型。参数key和参数flag共同决定的shmget()的作用

  • key为IPC_PRIVATE时,创建一个新的共享内存,flag取值无效。
  • key不为IPC_PRIVATE,且flag设置了IPC_CREAT位,而没有设置IPC_EXCL位时,如果key为内核中的已存在的共享内存键值,则打开,否则创建一个新的共享内存。
  • key不为IPC_PRIVATE,且flag设置了IPC_CREATIPC_EXCL位时,则只执行创建共享内存操作。如果key为内核中的已存在的共享内存键值,返回EEXIST错误。

共享内存的附加(映射)

创建一个共享内存后,某个进程若想使用,需要将此内存区域附加(attach)到自己的进程空间(或称地址映射),需要用到shmat()函数

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int *shmat(int shmid, const void *addr, int flag);

运行成功返回指向共享内存段的地址指针,出错返回-1。

参数shmid为共享内存的ID,参数addr和参数flag共同说明要引入的地址值,通常只有2种用法

  • addr为0,表明让内核来决定第1个可引用的位置
  • addr非0,且flag中指定SHM_RND,则此段引入到addr所指向的位置。

shmat()函数执行成功后,会将shmid的共享内存段的shmid_ds结构的shm_nattch计数器值加1。

共享内存的分离

当进程使用完共享内存后,需要将共享内存从其进程空间中去除(detach),使用shmdt()函数

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *addr);

运行成功返回0,出错返回-1。

参数addr是调用shmat()函数的返回值,即共享内存段的地址指针。shmdt()函数执行成功后,shm_nattch计数器值减1。

共享内存的控制

使用shmctl()可以对共享内存段进行多种控制操作,函数原型:

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_s *buf);

运行成功返回0,出错返回-1。

参数shmid为共享内存的ID,参数cmd指明了所要进行的操作,与**参数*buf**配合使用:

取shmid指向的共享内存的shmid_ds
结构,对参数buf指向的结构赋值

编程示例

基本步骤:

  • 生成key,ftok()
  • 使用key创建/获得一个共享内存,shmget()
  • 映射共享内存,得到虚拟地址,shmat()
  • 使用共享内存,通过地址指针
  • 移除映射,shmdt()
  • 销毁共享内存,shmctl()

示例1

进程1创建共享内存并写入数据,shm1.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
    // generate key
    key_t key = ftok("./", 200);
    printf("key=%#xn", key);

    // create a share memory
    int shmid = shmget(key, 8, IPC_CREAT|0666|IPC_EXCL);
    if(shmid == -1)
    {
        perror("shmget failedn");
        exit(1);
    }
    printf("shmid=%#xn", shmid);

    // map share memory to get the virtual address
    void *p = shmat(shmid, 0, 0);
    if((void *)-1 == p)
    {
        perror("shmat failed");
        exit(2);
    }

    // write data to share memory
    int *pi = p;
    *pi = 0xaaaaaaaa;
    *(pi+1) = 0x55555555;

    // remove the map
    if(shmdt(p) == -1)
    {
        perror("shmdt failed");
        exit(3);
    }

    // delete the share memory
    printf("use Enter to destroy the share memoryn");
    getchar();
    if(shmctl(shmid, IPC_RMID, NULL) == -1)
    {
        perror("shmctl");
        exit(4);
    }

    return 0;
}

进程2读取共享内存中的内容,shm2.c;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
    // generate key
    key_t key = ftok("./", 200);
    printf("key=%#xn", key);

    // get the share memory
    int shmid = shmget(key, 0, 0);
    if(shmid == -1)
    {
        perror("shmget failedn");
        exit(1);
    }
    printf("shmid=%#xn", shmid);

    // map share memory to get the virtual address
    void *p = shmat(shmid, 0, 0);
    if((void *)-1 == p)
    {
        perror("shmat failed");
        exit(2);
    }

    // read: get data from the share memory
    int x = *((int *)p);
    int y = *((int *)p+1);
    printf("x=%#x y=%#xn", x, y);

    // remove the map
    if(shmdt(p) == -1)
    {
        perror("shmdt failed");
        exit(3);
    }

    return 0;
}

编译运行测试,在一个shell中先运行shm1程序:

$ ./shm1
key=0xc81102ed
shmid=0x2e8047
use Enter to destroy the share memory

在另一个shell中先运行shm2程序:

$ ./shm2
key=0xc81102ed
shmid=0x2e8047
x=0xaaaaaaaa y=0x55555555

可以看到通信成功,在第一个shell中按下“Enter”可以销毁共享内存。

示例2

示例1使用ftok()函数生成的key创建共享内存,本示例使用IPC_PRIVATE参数创建共享内存。

创建一个共享内存,并输出其ID号,create_shm.c:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#define BUFSZ 4096

int main(void)
{
    int shm_id;

    shm_id = shmget(IPC_PRIVATE, BUFSZ, 0666);
    if(shm_id < 0)
    {
        printf("shmget failed!n");
        exit(1);
    }
    printf("create a shared memory segment successfully: %dn", shm_id);

    system("ipcs -m");

    exit(0);
}

打开指定ID的共享内存,写入内容,write_shm.c:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
{
   char name[4];
   int age;
}people;

int main(int argc, char **argv)
{
   int shm_id, i;
   char temp;
   people *p_map;

   if(argc != 2)
   {
       printf("USAGE:atshm <identifier>n");
       exit(1);
   }

   // get share memory ID from input
   shm_id = atoi(argv[1]);// str to int

   // map the share memory to get the virtul address
   p_map = (people *)shmat(shm_id, NULL, 0);

   // write
   temp = 'a';
   for(i=0; i<10; i++)
   {
       temp+=1;
       memcpy((*(p_map+i)).name, &temp, 1);
       (*(p_map+i)).age=20+i;
   }

   // remove map
   if(shmdt(p_map)==-1)
       perror("detach error!n");

   return 0;
}

打开指定ID的共享内存,读取内容,read_shm.c:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct
{
   char name[4];
   int age;
}people;

int main(int argc, char** argv)
{
   int shm_id, i;
   people *p_map;

   if(argc != 2)
   {
       printf("USAGC: atshm <identifier>n");
       exit(1);
   }

   // get the share memory ID from input
   shm_id = atoi(argv[1]);// str to int

   // map the share memory to get the virtual address
   p_map = (people*)shmat(shm_id, NULL, 0);

   // read data
   for(i=0; i<10; i++)
   {
       printf("name:%s   ", (*(p_map+i)).name);
       printf("age %dn", (*(p_map+i)).age);
   }

   // remove the map
   if(shmdt(p_map)==-1)
       perror("detach error!n");

   return 0;
}

编译上述3个程序,首先运行create_shm程序创建一个共享内存:

$ ./create_shm
create a shared memory segment successfully: 3080264

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 2260992    deeplearni 600        524288     2          dest
0x00000000 2490369    deeplearni 600        67108864   2          dest
0x00000000 163842     root       777        7680       2
0x00000000 196611     root       777        8294400    2
0x00000000 229380     root       777        4096       2
...省略
0x00000000 2031675    root       777        229376     2
0x00000000 2064444    root       777        233472     2
0x00000000 2097213    root       777        237568     2
0x00000000 2129982    root       777        241664     2
0x00000000 2162751    root       777        245760     2
0x00000000 2654272    deeplearni 600        524288     2          dest
0x00000000 2687041    deeplearni 600        524288     2          dest
0x00000000 2719810    deeplearni 600        524288     2          dest
0x00000000 2752579    deeplearni 600        524288     2          dest
0x00000000 2981956    deeplearni 600        524288     2          dest
0x00000000 2949189    deeplearni 600        524288     2          dest
0x00000000 3014726    deeplearni 600        67108864   2          dest
0xc81102ed 3047495    deeplearni 666        8          0
0x00000000 3080264    deeplearni 666        4096       0

可以看到程序创建了一个shmid为3080264的共享内存,然后可以运行write_shm程序,需要将ID号作为参数:

$ ./write_shm 3080264

虽然没有输出,但程序已将内容写入共享内存,并且共享内存没有被删除,可以运行read_shm程序读取共享内存中的内容:

$ ./read_shm 3080264
name:b   age 20
name:c   age 21
name:d   age 22
name:e   age 23
name:f   age 24
name:g   age 25
name:h   age 26
name:i   age 27
name:j   age 28
name:k   age 29

可以看到,程序读取到了共享内存中存储的10个字符。

另外,无用的共享内存也可以通过命令的方式收到删除,使用ipcrm -m 共享内存id的形式,如:

$ ipcrm -m 3080264

此时再用ipcs -m命令查看,已经没有shmid为3080264的共享内存。

参考:

  1. 《精通Linux C编程》- 程国钢
  2. 《Linux C编程完全解密》- 闫敬 吴淑坤