自学内容网 自学内容网

『 Linux 』System V共享内存


System V IPC

请添加图片描述

System v 是一种操作系统和相关技术的合集,最初由 AT&T 开发,主要用于Unix操作系统;

System vUnix的一个重要版本;

System v IPC则是其进程间通信机制,主要有:

  • 共享内存

    允许多个进程访问同一块内存区域以实现数据共享;

  • 信号量

    用于控制对共享资源的访问,提供进程间的同步和互斥;

  • 消息队列

    允许进程以消息的形式进行异步通信;


System V 共享内存的直接原理

请添加图片描述

每个进程中都有一个对应的task_struct内核数据结构用于管理进程;

每个进程都有一个进程地址空间为虚拟地址,虚拟地址通过页表映射至物理内存中;

进程间通信的本质是时多个进程看到同一份资源;

共享内存的直接原理即由操作系统内核申请出一块物理内存空间,并将该空间的使用权移交给多个进程,即将该空间以挂接至多个进程的进程地址空间中使得多个进程可以同时访问同一块内存空间 (看到同一份"资源");

其原理与动态库的加载机制类似;

主要的操作为如下:

  • 申请共享内存空间

    申请共享内存空间主要分为需求方和执行方;

    其中进程需要进行进程间通信,而内核不能直接将内存交给进程进行管理和使用所以进程将向操作系统内核申请;

  • 将共享内存空间挂接到进程地址空间当中

    当共享内存被创建后需要将共享内存挂接到进程地址空间中,即使进程与该共享内存进行关联;

如果需要释放共享内存则需要:

  • 去关联

    当需要释放共享内存时在此之前需要使得该共享内存没有再被某些进程进行关联,确保进程的读写操作不会因为共享内存的释放而发生读写错误;

    共享内存的内核结构描述中存在对应的变量以引用计数的方式来观察当前有多少个进程挂接了这个共享内存;

  • 释放共享内存

    当去关联完毕后则可以正常释放共享内存,对应的释放也是执行方进行释放,进程只是告诉了内核该共享内存已经使用完毕可以进行释放;

需求方(进程)无法直接对共享内存进行操作,一切的操作都是由执行方(操作系统内核)进行,需求方(进程)只能通过系统调用接口来间接访问共享内存;

在系统中必定存在不止一个共享内存,当出现多个共享内存时这些资源将被进行管理,管理的方式即为 “先描述,再组织” ;

共享内存结构的描述与组织由操作系统内核进行,当描述后需要进行组织时将会以一个特定的数据结构将多个资源进行管理;

最终对共享内存的管理将会间接的成为对数据结构的管理从而简化操作;


System V共享内存的创建

请添加图片描述

使用系统调用接口shmget()可以使进程向操作系统申请共享内存空间;

NAME
       shmget - allocates a System V shared memory segment

SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);

RETURN VALUE
       On success, a valid shared memory identifier is returned.  On errir, -1 is returned, and errno is set to indicate the error.

该系统调用接口的返回值是一个int类型的整数值;

当接口调用成功时将会返回一个int类型的值用来表明该共享内存的唯一值即共享内存标识符,调用失败则会返回-1并设置errno;

共享内存标识符与文件描述符具有相似性,其主要作用为对共享内存进行具体的操作;

该系统调用接口的参数分别为:

  • key_t key

    用于在内核中标定该共享内存的唯一性;

    其中key_t类型是对int类型的一个typedef;

    用户可直接自定义对应的key,也可使用ftok()系统调用接口生成对应的key以保证减少key值的冲突概率;

    • ftok()系统调用接口

      该接口用于为shmget()系统调用接口生成一个更加不容易冲突的key值;

      NAME
             ftok - convert a pathname and a project identifier to a System V IPC key
      
      SYNOPSIS
             #include <sys/types.h>
             #include <sys/ipc.h>
      
             key_t ftok(const char *pathname, int proj_id);
             
      RETURN VALUE
             On success, the generated key_t value is returned.  On failure -1 is returned, with errno indicating the error as for the stat(2) system call.
      
      
      

      该函数调用成功后将会返回一个key_t类型的key值;

      调用失败则会返回-1并设置errno;

      其接口参数:

      const char *pathname:该参数为路径,路径在内核中具有唯一性;

      int proj_id:该参数由用户自行指定的一个值;

      该系统调用接口将使用一套算法对pathnameproj_id进行数值计算;

    shmget()系统调用接口中key的数值是多少并不重要,重要的是该值必须能够在内核中具有唯一性,能够让不同的进程进行唯一性标志;

    第一个进程可以通过key创建共享内存,而第一个以后的进程只需要使用同样的key就可以和第一个进程看到同一个共享内存;

    对于已经创建好的共享内存,其key将在共享内存的描述对象中;

  • size_t size

    该参数用于标定需要申请的共享内存的大小,一般为4096的倍数(单位为字节);

    假设所申请的共享内存空间大小为4097,内核将会为共享内存空间实际分配4096*2大小的空间,但实际上能合法使用的空间只有4097;

    这表明共享内存的空间大小是按 的大小来进行分配的;

  • int shmflg

    用于指明在创建共享内存中的选项以及所创建共享内存空间的权限;

    选项使用的方式类似于open()系统调用接口的选项参数,以位的方式进行传输,通过按位或|进行不同选项的组合;

    其中选项为:

       IPC_CREAT   to create a new segment.  If this flag is not used, then shmget() will find the   segment associated with key and check to see if the user has permission to    access the segment.
    
           IPC_EXCL    used with IPC_CREAT to ensure failure if the segment already exists.
    
    • IPC_CREAT

      该选项为创建一个共享内存空间,若是对应key的共享内存存在则返回对应的共享内存标识符;

    • IPC_CREAT|IPC_EXCL

      两个选项的组合使用则为,创建一个共享内存空间,若是对应key的共享内存空间存在则出错;

      两个选项的组合使用确保了所申请的共享内存空间是一个全新的,而不会以外覆盖现有的段;

    • IPC_EXCL

      该选项不单独使用,只是配合IPC_CREAT选项进行使用;

编码准备(下文不赘述):

由于System V共享内存是用来多个进程间通信,此处创建两个毫不相关的进程即两个.cc文件(processa.cc,processb.cc)即一个.hpp文件comm.hpp并且引入[简单日志插件]中的日志插件;

  • comm.hpp

    #ifndef __COMM_HPP_ //包含卫士 用于防止头文件重复包含
    #define __COMM_HPP_
    
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/types.h>
    
    #include <cstring>
    #include <iostream>
    
    #include "log.hpp"
    using namespace std;
    
    #define PATHNAME "/home/" 
    #define SIZE 4096
    const int proj_id = 0x7878;
    Log log;
    
    key_t GetKey() { //调用ftok()系统调用接口生成一个不容易重复的key值
      key_t key = ftok(PATHNAME, proj_id);
      if (key < 0) {
        log(FATAL, "ftok error : %s\n", strerror(errno));
         // 通过日志插件将可能出现的内容打印出来
        exit(-1);
      }
      log(INFO, "ftok sucess , key@ 0x%x\n", key);
      return key;
    }
    
    int GetShareMem() { //使用key值地调用shmget()系统调用接口创建一个共享内存 并返回对应的shmid
      key_t k = GetKey();
      int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);
      if (shmid < 0) {
        log(FATAL, "Creat shared memory fail : %s\n", strerror(errno));
        exit(2);
      }
      log(INFO, "Creat shared memory sucess , shmid@ %d\n", shmid);
      return shmid;
    }
    
    #endif
    

    封装两个函数分别用于获取key值与创建共享内存;

    GetShareMem()函数中使用IPC_CREAT|IPC_EXCL|0666的选项来保证每次创建的共享内存将是全新的同时对应的权限为0666;

  • processa.cc

    #include "comm.hpp"
    
    int main() {
    
      int shmid = GetShareMem();
      log(DEBUG, "ProcessA quit....\n");
      sleep(10);
      
      return 0;
    }
    

    processa.cc文件中直接调用GetShareMem()函数用来直接创建共享内存;

编译并运行processa对应的可执行程序;

$ ./processa
[INFO][2024-07-20 10:10:05] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 10:10:05] Creat shared memory sucess , shmid@ 65536


[DEBUG][2024-07-20 10:10:05] ProcessA quit....

结果为成功创建出共享内存,其对应的key0x78010002,shmid65536;

keyshmid的差别在于:

  • key

    key值在操作系统内核范围内标定唯一性;

    该值用于多个进程能够访问到同一块共享内存;

  • shmid

    shmid只在进程内表示资源的唯一性;

    该值为共享内存的对应id值,用于对共享内存进行具体操作,例如挂接,释放等操作;

当再次运行processa时:

$  ./processa
[INFO][2024-07-20 10:20:43] ftok sucess , key@ 0x78010002


[FATAL][2024-07-20 10:20:43] Creat shared memory fail : File exists

出现报错,报错信息为File exists;

可用ipcs -m命令查看当前共享内存情况:

$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 65536      _LJP       666        4096       0     

进程已经结束但对应的共享内存依旧存在;

故可以得出结论:

  • 共享内存的生命周期是随内核的,与进程无关

    用户不主动释放共享内存,共享内存将一直存在,除非内核重启或用户释放;

    在会话中可以使用picrm -m (shmid)选项释放对应共享内存,如picrm -m 65536;

可将GetShareMem()接口再次封装使得其通过传入不同的参数能够分别进行创建共享内存与获取共享内存shmid的操作;

int GetShareMem(int flag) {
  key_t k = GetKey();
  int shmid = shmget(k, SIZE, flag);
  if (shmid < 0) {
    log(FATAL, "Creat shared memory fail : %s\n", strerror(errno));
    exit(2);
  }
  log(INFO, "Creat shared memory sucess , shmid@ %d\n", shmid);
  return shmid;
}

int CreatShm() { return GetShareMem(IPC_CREAT | IPC_EXCL | 0666); }

int GetShm() { return GetShareMem(IPC_CREAT); }

此处的key值采用的都是相同的参数,两个进程将通过同一个key值来看到同一块共享内存;


挂接共享内存

请添加图片描述

当共享内存被创建后需要将共享内存挂接到进程地址空间中才能被进程所给使用;

挂接共享内存通常使用系统调用接口shmat();

NAME
       shmat, shmdt - System V shared memory operations

SYNOPSIS
       #include <sys/types.h>
       #include <sys/shm.h>

       void *shmat(int shmid, const void *shmaddr, int shmflg);
       
RETURN VALUE
       On success shmat() returns the address of the attached shared memory segment; on error (void *)   -1 is returned, and errno is  set to indicate the cause of the error.

当该系统调用接口才调用成功时将会返回一个void*的值,该返回值值类比于malloc()函数的返回值,即表示共享内存空间;

调用成功时将返回-1并设置errno;

其中参数为:

  • int shmid

    该参数为一个或多个进程需要挂接的共享内存的shmid(见上文);

  • const void*shmaddr

    该参数为需要将共享内存挂接在进程地址空间的位置,设置为nullptr时将为默认(即操作系统自行决定);

  • int shmflg

    DESCRIPTION
    
    If  SHM_RDONLY is specified in shmflg, the segment is attached for reading and the process must  have read permission for the seg‐ment.Otherwise the segment is attached for read and write and the process must have read and write permission for the  segment.There is no notion of a write-only shared memory segment.
    

    该参数表示如果指定了SHM_RDONLY选项且共享内存具有读的权限情况下,该段将被用于只读;

    默认则可读可写;

默认情况下使用shmat()系统调用接口时只需要传入shmid参数即可,其他参数可设置为默认值,即nullptr0;

/* processa.cc */
#include "comm.hpp"

int main() {

  int shmid = CreatShm(); // 创建共享内存并获取shmid
  sleep(5);
  char* shmaddr = (char*)shmat(shmid,nullptr,0); // 挂接共享内存
  log(DEBUG,"ProcessA attach shared memory done\n");
  sleep(5);
  log(DEBUG, "ProcessA quit....\n");

  return 0;
}

//---------------------
/* processb.cc */
#include "comm.hpp"

int main() {

  int shmid = GetShm();// 获取相同key下已经存在的共享内存的shmid
  char* shmaddr = (char*)shmat(shmid, nullptr, 0); // 挂接共享内存
  sleep(5);
  log(DEBUG, "ProcessB attach shared memory done\n");
  sleep(5);
  log(DEBUG, "ProcessB quit....\n");
  
  return 0;
}

可复制会话并采用shell脚本监控程序执行过程中共享内存的情况;

$ while :; do ipcs -m ;sleep 1;done

当分别在另外两个会话中运行processaprocessb;

# processa 所在会话
$ ./processa
[INFO][2024-07-20 11:19:50] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 11:19:50] Creat shared memory sucess , shmid@ 98304


[DEBUG][2024-07-20 11:19:55] ProcessA attach shared memory done


[DEBUG][2024-07-20 11:20:00] ProcessA quit....
# processb 所在会话
$ ./processb
[INFO][2024-07-20 11:19:52] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 11:19:52] Creat shared memory sucess , shmid@ 98304


[DEBUG][2024-07-20 11:19:57] ProcessB attach shared memory done


[DEBUG][2024-07-20 11:20:02] ProcessB quit....

两个会话中日志所显示的shmidkey值相同;

# 监控脚本所在会话

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 98304      _LJP       666        4096       0                       

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 98304      _LJP       666        4096       1                       

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 98304      _LJP       666        4096       2                       

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 98304      _LJP       666        4096       1                       

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 98304      _LJP       666        4096       0            

对应的监控脚本会话中数据nattch为引用计数,挂接数量由0变成1,1变成2,最后逐渐减回0,原因为由于sleep()函数与启动时间的差异,先创建共享内存,两个进程在分别挂接上共享内存,最后因为进程退出挂接被自动取消;


取消挂接共享内存

请添加图片描述

进程退出时将自动去关联,调用系统调用接口shmdt()则可以在进程未退出时进行去关联操作;

NAME
       shmat, shmdt - System V shared memory operations

SYNOPSIS
       #include <sys/types.h>
       #include <sys/shm.h>

       int shmdt(const void *shmaddr);
       
RETURN VALUE

       On success shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of    the error.

该函数调用成功时将返回0,调用失败时将返回-1并设置errno;

  • const void* shmaddr

    该参数为需要去关联的共享内存,其机制与free()类似,只需要知道去关联共享内存的起始位置即可;

    享内存中的内核数据结构将存储该空间实际的大小,同时该空间为一块连续不断的空间,传入起始位置即可在内核中自动计算终止位置从而去关联;

修改代码,调用shmdt():

/* processa.cc */
#include "comm.hpp"

int main() {

  int shmid = CreatShm();  // 创建共享内存并获取shmid
  sleep(5);
  char* shmaddr = (char*)shmat(shmid, nullptr, 0);  // 挂接共享内存
  log(DEBUG,"ProcessA attach shared memory done\n");
  sleep(5);
  shmdt(shmaddr);
  log(DEBUG, "ProcessA disattach shared memory done\n");
  sleep(100);
  log(DEBUG, "ProcessA quit....\n");

  return 0; 
}
//---------------------
/* processb.cc */
#include "comm.hpp"

int main() {

  int shmid = GetShm();  // 获取相同key下已经存在的共享内存的shmid
  char* shmaddr = (char*)shmat(shmid, nullptr, 0);  // 挂接共享内存
  sleep(5);
  log(DEBUG, "ProcessB attach shared memory done\n");
  sleep(10);
  shmdt(shmaddr);
  log(DEBUG, "ProcessB disattach shared memory done\n");
  sleep(100);
  log(DEBUG, "ProcessB quit....\n");
    
  return 0;
}

两个.cc文件中设置sleep(100)使进程休眠且不退出;

重新编译,使用shell脚本观察共享内存情况并运行两个程序;

# processa 所在会话
$ ./processa
[INFO][2024-07-20 11:43:06] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 11:43:06] Creat shared memory sucess , shmid@ 131072


[DEBUG][2024-07-20 11:43:11] ProcessA attach shared memory done


[DEBUG][2024-07-20 11:43:16] ProcessA disattach shared memory done


# processb 所在会话
$ ./processb
[INFO][2024-07-20 11:43:08] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 11:43:08] Creat shared memory sucess , shmid@ 131072


[DEBUG][2024-07-20 11:43:13] ProcessB attach shared memory done


[DEBUG][2024-07-20 11:43:23] ProcessB disattach shared memory done


两个会话中keyshmid相同且进程未退出;

# 监控脚本所在会话
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 131072     _LJP       666        4096       0                       

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 131072     _LJP       666        4096       1  

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 131072     _LJP       666        4096       2                       

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 131072     _LJP       666        4096       1                  

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 131072     _LJP       666        4096       0    

结果为进程未退出同时两个进程分别挂接共享内存而后分别去关联;


释放共享内存

请添加图片描述

共享内存的生命周期随内核,用户若是不主动释放,共享内存将持续存在;

在命令行中可使用ipcrm -m选项释放共享内存;

# 发出命令会话
ipcrm -m 163840

# shell脚本会话
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 163840     _LJP       666        4096       0                       

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

通过命令并传入shmid,共享内存被释放;

在程序中可调用系统调用接口shmctl()释放共享内存;

NAME
       shmctl - System V shared memory control

SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
       
RETURN VALUE
       A successful IPC_INFO or SHM_INFO operation returns the index of the high‐
       est used entry in the kernel's internal array recording information  about
       all  shared  memory segments.  (This information can be used with repeated
       SHM_STAT operations to obtain information about all shared memory segments
       on the system.)  A successful SHM_STAT operation returns the identifier of
       the shared memory segment whose index was given in  shmid.   Other  opera‐
       tions return 0 on success.

       On error, -1 is returned, and errno is set appropriately.

调用成功时返回0,调用失败时返回-1并设置errno;

  • int shmid

    该参数传入需要释放的共享内存的shmid;

  • struct shmid_ds *buf

    该参数为一个结构体,该结构体包含了共享内存的所有属性,可通过该结构体对共享内存进行获取属性,查看属性,修改属性等操作;

    此处为传入了对应共享内存的属性;

    The buf argument  is  a  pointer  to  a  shmid_ds  structure,  defined  in
           <sys/shm.h> as follows:
    
               struct shmid_ds {
                   struct ipc_perm shm_perm;    /* Ownership and permissions */
                   size_t          shm_segsz;   /* Size of segment (bytes) */
                   time_t          shm_atime;   /* Last attach time */
                   time_t          shm_dtime;   /* Last detach time */
                   time_t          shm_ctime;   /* Last change time */
                   pid_t           shm_cpid;    /* PID of creator */
                   pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
                   shmatt_t        shm_nattch;  /* No. of current attaches */
                   ...
               };
    
           The  ipc_perm  structure is defined as follows (the highlighted fields are
           settable using IPC_SET):
    
               struct ipc_perm {
                   key_t          __key;    /* Key supplied to shmget(2) */
                   uid_t          uid;      /* Effective UID of owner */
                   gid_t          gid;      /* Effective GID of owner */
                   uid_t          cuid;     /* Effective UID of creator */
                   gid_t          cgid;     /* Effective GID of creator */
                   unsigned short mode;     /* Permissions + SHM_DEST and
                                               SHM_LOCKED flags */
                   unsigned short __seq;    /* Sequence number */
               };
    

    在释放共享内存时不需要关注其共享内存属性,可传入nullptr;

  • int cmd

    该参数为传入一个选项使能够对共享内存进行对应操作;

    参数如下:

       IPC_STAT  Copy information from the kernel data structure associated with  shmid  into
                     the shmid_ds structure pointed to by buf.  The caller must have read permis‐
                     sion on the shared memory segment.
    
           IPC_SET   Write the values of some members of the shmid_ds structure pointed to by buf
                     to  the  kernel  data  structure associated with this shared memory segment,
                     updating also its shm_ctime member.  The following fields  can  be  changed:
                     shm_perm.uid,   shm_perm.gid,   and   (the  least  significant  9  bits  of)
                     shm_perm.mode.  The effective UID of the  calling  process  must  match  the
                     owner  (shm_perm.uid)  or  creator (shm_perm.cuid) of the shared memory seg‐
                     ment, or the caller must be privileged.
    
           IPC_RMID  Mark the segment to  be  destroyed.   The  segment  will  only  actually  be
                     destroyed after the last process detaches it (i.e., when the shm_nattch mem‐
                     ber of the associated structure shmid_ds is zero).  The caller must  be  the
                     owner  or  creator,  or  be  privileged.   If  a segment has been marked for
                     destruction, then the (nonstandard) SHM_DEST flag of the shm_perm.mode field
                     in the associated data structure retrieved by IPC_STAT will be set.
    
                     The caller must ensure that a segment is eventually destroyed; otherwise its
                     pages that were faulted in will remain in memory or swap.
    
                     See also the description of /proc/sys/kernel/shm_rmid_forced in proc(5).
     ...... # 具体参考 man shmctl 手册
    

    其中释放共享内存的选项为IPC_RMID选项;

/* processa.cc */

#include "comm.hpp"

int main() {

  int shmid = CreatShm();  // 创建共享内存并获取shmid
  sleep(2);
  char* shmaddr = (char*)shmat(shmid, nullptr, 0);  // 挂接共享内存
  log(DEBUG,"ProcessA attach shared memory done\n");
  sleep(2);
  shmdt(shmaddr);
  log(DEBUG, "ProcessA disattach shared memory done\n");
  sleep(2);
  int n = shmctl(shmid,IPC_RMID,nullptr);
  if(n<0){
    log(FATAL,"ProcessA shmctl faile : %s\n",strerror(errno));
    exit(3);
  }
  log(DEBUG,"ProcessA free the shared memory sucess\n");
  sleep(2);
  log(DEBUG, "ProcessA quit....\n");

  return 0; 
}

修改代码,在processa.cc中添加shmctl()系统调用接口并重新编译,打开shell脚本进行观察并运行processa.cc;

# processa所在会话
$ ./processa
[INFO][2024-07-20 12:28:19] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 12:28:19] Creat shared memory sucess , shmid@ 196608


[DEBUG][2024-07-20 12:28:21] ProcessA attach shared memory done


[DEBUG][2024-07-20 12:28:23] ProcessA disattach shared memory done


[DEBUG][2024-07-20 12:28:25] ProcessA free the shared memory sucess


[DEBUG][2024-07-20 12:28:27] ProcessA quit....

# shell脚本所在会话
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      


------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 196608     _LJP       666        4096       0                       


------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 196608     _LJP       666        4096       1                       


------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x78010002 196608     _LJP       666        4096       0                       


------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      


结果为一系列流程即 创建共享内存,挂接共享内存,去关联,释放共享内存 ;


利用System V共享内存进行进程间通信

请添加图片描述

此处利用两个进程,即processaprocessb;

思路为processa创建共享内存并挂接共享内存,processb获取共享内存shmid进行挂接,从而建立通信信道,即 “使不同进程看到同一份资源” ;

在使用共享内存时可直接以内存的形式进行访问而不需要其他操作;

  • processa.cc

    #include "comm.hpp"
    
    int main() {
      //   cout << "hello world processa" << endl;
    
      int shmid = CreatShm();  // 创建共享内存并获取shmid
      // sleep(2);
      char* shmaddr = (char*)shmat(shmid, nullptr, 0);  // 挂接共享内存
      log(DEBUG,"ProcessA attach shared memory done\n");
      // sleep(2);
    
      //ipc code
      while(true){
        cout<<"processa get massage@ "<<shmaddr<<endl;
        sleep(2);
      }
    
      shmdt(shmaddr);
      log(DEBUG, "ProcessA disattach shared memory done\n");
      // sleep(2);
      int n = shmctl(shmid,IPC_RMID,nullptr);
      if(n<0){
        log(FATAL,"ProcessA shmctl faile : %s\n",strerror(errno));
        exit(3);
      }
      log(DEBUG,"ProcessA free the shared memory sucess\n");
      // sleep(2);
      log(DEBUG, "ProcessA quit....\n");
    
      return 0; 
    }
    

    每隔2s从共享内存中读取数据并打印;

  • processb.cc

    #include "comm.hpp"
    
    int main() {
      // cout << "hello world processb" << endl;
      int shmid = GetShm();  // 获取相同key下已经存在的共享内存的shmid
      char* shmaddr = (char*)shmat(shmid, nullptr, 0);  // 挂接共享内存
      // sleep(5);
      log(DEBUG, "ProcessB attach shared memory done\n");
      // sleep(10);
    
      //ipc code
      while(true){
        cout<<"Please enter$ ";
        gets(shmaddr);
      }
    
      shmdt(shmaddr);
      log(DEBUG, "ProcessB disattach shared memory done\n");
      // sleep(100);
      log(DEBUG, "ProcessB quit....\n");
      return 0;
    }
    

    调用gets()直接向共享内存中写入;

    当共享内存挂接至进程地址空间后可直接以内存的方式使用共享内存而不需要调用系统调用接口;

编译后分别运行两个程序;

# processa所在会话
$ ./processa
[INFO][2024-07-20 12:43:17] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 12:43:17] Creat shared memory sucess , shmid@ 229376


[DEBUG][2024-07-20 12:43:17] ProcessA attach shared memory done

processa get massage@ 
processa get massage@ 
processa get massage@ hello
processa get massage@ hello
processa get massage@ hello
processa get massage@ hello world
processa get massage@ hello world
processa get massage@ hello world

# processb所在会话
$ ./processb
[INFO][2024-07-20 12:43:24] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 12:43:24] Creat shared memory sucess , shmid@ 229376


[DEBUG][2024-07-20 12:43:24] ProcessB attach shared memory done


Please enter$ hello
Please enter$ hello world
Please enter$ ^C

# 该程序在 Ctrl C结束后并不会释放共享内存 需要在命令行中手动释放

结果显示无论有没有向共享内存中写入数据,processa都会正常写入;

证明共享内存不存在同步互斥的保护机制;


共享内存的特性

请添加图片描述

  • 共享内存没有同步互斥之类的保护机制

    上文使用共享内存进行进程间通信过程中读写端不会因为对端因正在被读或是正在被写而进行阻塞等待;

    在使用共享内存若是需要添加同步互斥类的保护机制应该额外增加并管理(如管道);

  • 共享内存是所有进程间通信中速度最快的

    共享内存在进程间通信的速度是最快的,原因是它不需要额外的拷贝造成的开销;

    以管道为例:

    管道需要将从写端写入用户自定义的缓冲区中,再将用户自定义缓冲区中的数据拷贝至内核缓冲区中;

    读端才能从内核缓冲区中(管道文件)获取写端写入管道的文件,这几个步骤涉及了多次拷贝;

  • 共享内存内部数据由用户自行维护

    操作系统内核只为用户分配并维护共享内存这块空间,而空间内的数据是需要


共享内存的属性

请添加图片描述

一般情况下在操作系统内核中存在一个 struct shmid_ds类型的结构体;

该结构体存储着共享内存的基本属性;

 struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };
  • struct ipc_perm shm_perm

    该参数为该结构体中嵌套了的一个结构体变量;

    其中该参数用来存储共享内存的所有权和权限等信息;

               struct ipc_perm {
                   key_t          __key;    /* Key supplied to shmget(2) */
                   uid_t          uid;      /* Effective UID of owner */
                   gid_t          gid;      /* Effective GID of owner */
                   uid_t          cuid;     /* Effective UID of creator */
                   gid_t          cgid;     /* Effective GID of creator */
                   unsigned short mode;     /* Permissions + SHM_DEST and
                                               SHM_LOCKED flags */
                   unsigned short __seq;    /* Sequence number */
               };
    

    其中key_t __key即为该共享内存的key;

    其他不同进程可通过该key来找到该共享内存;

  • size_t shm_segsz

    共享内存的大小(单位字节);

  • time_t shm_atime

    共享内存上一次的连接时间;

  • time_t shm_dtime

    共享内存的上一次的去关联时间;

  • time_t shm_ctime

    共享内存上一次的修改时间;

  • pid_t shm_cpid

    申请创建该共享内存(第一个)进程的PID;

可通过调用系统调用接口shmctl()并传递IPC_STAT选项来查看该共享内存的对应信息,即struct shmid_ds中的数据;

/* processa.cc */

#include "comm.hpp"

int main() {

  log(DEBUG, "The PID of the processa:%d\n", getpid());
  // 调用日志log打印当前进程的PID
  
  int shmid = CreatShm();  

  char* shmaddr = (char*)shmat(shmid, nullptr, 0);  // 挂接共享内存
  log(DEBUG, "ProcessA attach shared memory done\n");

  while (true) {
    cout << "processa get massage@ " << shmaddr << endl;
// 查看共享内存信息代码
    struct shmid_ds shmds;
    shmctl(shmid, IPC_STAT, &shmds);
    printf("The shm key :0x%x\n", shmds.shm_perm.__key);
    cout << "The shm size :" << shmds.shm_segsz << endl;
    cout << "PID of creator :" << shmds.shm_cpid << endl;
    cout << "===========================" << endl;

    sleep(2);
  }
// 退出清理
  shmdt(shmaddr);
  log(DEBUG, "ProcessA disattach shared memory done\n");

  int n = shmctl(shmid, IPC_RMID, nullptr);
  if (n < 0) {
    log(FATAL, "ProcessA shmctl faile : %s\n", strerror(errno));
    exit(3);
  }
  log(DEBUG, "ProcessA free the shared memory sucess\n");

  log(DEBUG, "ProcessA quit....\n");

  return 0;
}

运行结果为:

$ ./processa

[DEBUG][2024-07-20 14:56:07] The PID of the processa:9923


[INFO][2024-07-20 14:56:07] ftok sucess , key@ 0x78010002


[INFO][2024-07-20 14:56:07] Creat shared memory sucess , shmid@ 327680


[DEBUG][2024-07-20 14:56:07] ProcessA attach shared memory done


processa get massage@ 
The shm key :0x78010002
The shm size :4096
PID of creator :9923
===========================

通过命名管道为共享内存添加同步互斥机制

请添加图片描述

命名管道具有同步互斥的保护机制;

该思路为使两个进程创建共享内存用于传输数量较大的数据,同时为其创建命名管道用于传输简单消息提醒对端接收数据;

  • comm.hpp

    #ifndef __COMM_HPP_
    #define __COMM_HPP_
    
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/types.h>
    
    #include <cstring>
    #include <iostream>
    
    #include "log.hpp"
    using namespace std;
    
    #define PATHNAME "/home/"
    #define SIZE 4096
    const int proj_id = 0x7878;
    Log log;
    
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    #include <cstdlib>
    #include <iostream>
    #include <string>
    
    #define PIPEFILE "./myfifo"
    #define MODE 0666
    
    #define SIZE 1024
    
    using namespace std;
    
    enum { FIFO_CREATE_ERR = 1, FIFO_DELETE_ERR, FIFO_OPEN_ERR };
    
    class PipeInit {
     public:
      PipeInit() {
        int n = mkfifo(PIPEFILE, MODE);
        if (n == -1) {
          cerr << "CREATE PIPE FAIL" << endl;
          exit(FIFO_CREATE_ERR);
        }
      }
      ~PipeInit() {
        int m = unlink(PIPEFILE);
        if (m == -1) {
          cerr << "DEL PIPE FAIL" << endl;
          exit(FIFO_DELETE_ERR);
        }
      }
    };
    
    key_t GetKey() {
      key_t key = ftok(PATHNAME, proj_id);
      if (key < 0) {
        log(FATAL, "ftok error : %s\n", strerror(errno));
        exit(-1);
      }
      log(INFO, "ftok sucess , key@ 0x%x\n", key);
      return key;
    }
    
    int GetShareMem(int flag) {
      key_t k = GetKey();
      int shmid = shmget(k, SIZE, flag);
      if (shmid < 0) {
        log(FATAL, "Creat shared memory fail : %s\n", strerror(errno));
        exit(2);
      }
      log(INFO, "Creat shared memory sucess , shmid@ %d\n", shmid);
      return shmid;
    }
    
    int CreatShm() { return GetShareMem(IPC_CREAT | IPC_EXCL | 0666); }
    
    int GetShm() { return GetShareMem(IPC_CREAT); }
    
    #endif
    

    封装一个类用于管道的创建以及销毁;

  • processa.cc

    #include "comm.hpp"
    
    int main() {
      PipeInit pi;
      log(DEBUG, "The PID of the processa:%d\n", getpid());
    
      int shmid = CreatShm(); 
      char* shmaddr = (char*)shmat(shmid, nullptr, 0);  
      log(DEBUG, "ProcessA attach shared memory done\n");
    
    
      // ipc code 通信代码
      int fd = open(PIPEFILE, O_RDONLY);
      if (fd < 0) {
        log(FATAL, "open pipefile error :%s\n", strerror(errno));
        exit(FIFO_OPEN_ERR);
      }
      log(INFO, "open pipefile sucess\n");
    
      while (true) {
        char c;
        read(fd, &c, sizeof(c));
        if (c == 'c') {
          cout << "processa get massage@ " << shmaddr << endl;
        }
      }
    
      shmdt(shmaddr);
      log(DEBUG, "ProcessA disattach shared memory done\n");
    
      int n = shmctl(shmid, IPC_RMID, nullptr);
      if (n < 0) {
        log(FATAL, "ProcessA shmctl faile : %s\n", strerror(errno));
        exit(3);
      }
      log(DEBUG, "ProcessA free the shared memory sucess\n");
    
      log(DEBUG, "ProcessA quit....\n");
    
      return 0;
    }
    

    processa作为读端,每当从管道读取到字符'c'时从共享内存中读取数据并进行打印;

  • processb.cc

    #include "comm.hpp"
    
    int main() {
    
      int shmid = GetShm(); 
      char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    
      log(DEBUG, "ProcessB attach shared memory done\n");
    
      // ipc code
      int fd = open(PIPEFILE, O_WRONLY);
      if (fd < 0) {
        log(FATAL, "open pipefile error :%s\n", strerror(errno));
        exit(FIFO_OPEN_ERR);
      }
      log(INFO, "open pipefile sucess\n");
    
      while (true) {
        cout << "Please enter$ ";
        gets(shmaddr);
        write(fd, "c", 1);
      }
    
      shmdt(shmaddr);
      log(DEBUG, "ProcessB disattach shared memory done\n");
      log(DEBUG, "ProcessB quit....\n");
      return 0;
    }
    

    processb作写端,每当从写端写入数据时向管道写入一个字符"c";

利用管道的同步互斥机制优化System V共享内存;

完整代码(供参考):

[参考代码(gitee) - DIo夹心小面包 (半介莽夫)]


原文地址:https://blog.csdn.net/2202_75303754/article/details/140572137

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!