『 Linux 』System V共享内存

news2024/9/22 17:41:45

文章目录

    • System V IPC
    • System V 共享内存的直接原理
    • System V共享内存的创建
    • 挂接共享内存
    • 取消挂接共享内存
    • 释放共享内存
    • 利用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夹心小面包 (半介莽夫)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1943121.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不懂这些,面试都不敢说自己熟悉Redis

点赞再看&#xff0c;Java进阶一大半 下面这位就是Redis的创始人&#xff0c;他叫antirez&#xff0c;让我们Java开发者又要多学一门Redis的始作俑者。 我们肯定很难想象Redis创始人竟然学的是是建筑专业&#xff0c;而当年antirez是为了帮网站管理员监控访问者的实时行为才开发…

22集 如何minimax密钥和groupid-《MCU嵌入式AI开发笔记》

22集 如何获取minimax密钥和groupid-《MCU嵌入式AI开发笔记》 minimax密钥获取 https://www.minimaxi.com/platform 进入minimax网站&#xff0c;注册登录后&#xff0c;进入“账户管理”&#xff0c; 然后再点击“接口密钥”&#xff0c;然后再点击“创建新的密钥”。 之…

linux系统安装python3和pip

一、安装python 1、安装依赖环境 yum install gcc -y yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel yum install zlib zlib-devel openssl -y yum install openssl…

什么是信创沙箱?信创沙箱的原理是什么?

在这个数字化高速发展的时代&#xff0c;信息安全问题愈发显得重要。我们每天都在为数据的安全性、隐私性和完整性操心。有时候&#xff0c;感觉就像是一场没有终点的马拉松。而在这场马拉松中&#xff0c;深信达信创沙箱&#xff08;Trusted Computing Sandbox&#xff09;无疑…

谷粒商城实战笔记-52~53-商品服务-API-三级分类-新增-修改

文章目录 一&#xff0c;52-商品服务-API-三级分类-新增-新增效果完成1&#xff0c;点击Append按钮&#xff0c;显示弹窗2&#xff0c;测试完整代码 二&#xff0c;53-商品服务-API-三级分类-修改-修改效果完成1&#xff0c;添加Edit按钮并绑定事件2&#xff0c;修改弹窗确定按…

Windows 11 家庭中文版 安装 VMWare 报 安装程序检测到主机启用了Hyper-V或Device

1、问题 我的操作系统信息如下&#xff1a; 我在安装 VMWare 的时候&#xff0c;报&#xff1a; 因为我之前安装了 docker 桌面版&#xff0c;所以才报这个提示。 安装程序检测到主机启用了 Hyper-v或 Device/credential Guard。要在启用了Hyper-或 Device/Credential Guard …

如何防止热插拔烧坏单片机

大家都知道一般USB接口属于热插拔&#xff0c;实际任意带电进行连接的操作都可以属于热插拔。我们前面讲过芯片烧坏的原理&#xff0c;那么热插拔就是导致芯片烧坏的一个主要原因之一。 在电子产品的整个装配过程、以及产品使用过程经常会面临接口热插拔或者类似热插拔的过程。…

领夹麦克风哪个品牌好,电脑麦克风哪个品牌好,热门麦克风推荐

​在信息快速传播的时代&#xff0c;直播和视频创作成为了表达与交流的重要方式。对于追求卓越声音品质的创作者而言&#xff0c;一款性能卓越的无线麦克风宛如一把利剑。接下来&#xff0c;我要为大家介绍几款备受好评的无线麦克风&#xff0c;这些都是我在实际使用中体验良好…

emr部署hive并适配达梦数据库

作者&#xff1a;振鹭 一、达梦 用户、数据库初始化 1、创建hive的元数据库 create tablespace hive_meta datafile /dm8/data/DAMENG/hive_meta.dbf size 100 autoextend on next 1 maxsize 2048;2、创建数据库的用户 create user hive identified by "hive12345&quo…

Covalent(CXT)运营商网络规模扩大 42%,以满足激增的需求

Covalent Network&#xff08;CXT&#xff09;是领先的人工智能模块化数据基础设施&#xff0c;网络集成了超过 230 条链并积累了数千名客户&#xff0c;目前 Covalent Network&#xff08;CXT&#xff09;网络迎来了五位新运营商的加入&#xff0c;包括 Graphyte Labs、PierTw…

【BUG】已解决:ModuleNotFoundError: No module named ‘tensorboard‘

ModuleNotFoundError: No module named ‘tensorboard‘ 目录 ModuleNotFoundError: No module named ‘tensorboard‘ 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#…

【状态机动态规划 状态压缩】1434. 每个人戴不同帽子的方案数

本文涉及知识点 位运算、状态压缩、枚举子集汇总 动态规划汇总 LeetCode 1434. 每个人戴不同帽子的方案数 总共有 n 个人和 40 种不同的帽子&#xff0c;帽子编号从 1 到 40 。 给你一个整数列表的列表 hats &#xff0c;其中 hats[i] 是第 i 个人所有喜欢帽子的列表。 请你…

SQL实战宝典:快速上手数据库查询与优化

文章目录 SQL 速成手册SQL 的主要功能1、基本查询语句2、表操作语句3、数据操作语句4、函数与聚合操作5、子查询与联接6、高级操作7、性能优化与安全性 基本查询语句表操作语句数据操作语句函数与聚合操作子查询与联接高级操作性能优化与安全性 SQL 速成手册 SQL&#xff08;S…

DataX 本地调试配置

简要说明 根据自己的开发需求&#xff0c;完成了reader、writer、transformer开发后&#xff0c;在ide内通过Engine入口&#xff0c;调试自己的插件和job的json。 前置条件 已在系统安装了datax&#xff0c;本例子是在windows环境下&#xff0c;安装包地址https://github.co…

204、【动态规划】牛客网 ——DP3 跳台阶扩展问题(Python版本)

题目描述 原题链接&#xff1a;DP3 跳台阶扩展问题 解题思路 一个DP问题&#xff0c;相比于普通爬楼&#xff08;只能爬一层或者两层&#xff09;对应的状态函数为 d p [ i ] d p [ i − 1 ] d p [ i − 2 ] dp[i] dp[i - 1] dp[i - 2] dp[i]dp[i−1]dp[i−2]。本题的dp…

什么是倾斜45度的火山图?

一位老师聊起火山图&#xff08;Volcano plot | 别再问我这为什么是火山图 &#xff08;在线轻松绘制&#xff09;&#xff09;&#xff0c;说见过倾斜45度的类似图&#xff0c;可否演示怎么画&#xff1f;想了下&#xff0c;可能是下面这种图&#xff0c;绘起来看看。 检查和安…

零代码GIS场景视效升级:支持TMS/WMS/WMTS协议

首先和大家聊聊为什么现在很多人都在追求GIS场景的视效提升。第一是因为GIS场景本身需要包含多种自然信息&#xff0c;越是优秀的视觉效果&#xff0c;就越能直观反应出真实的地理信息&#xff0c;增强系统的实用性&#xff1b;第二则是因为能够极大降低系统的使用门槛&#xf…

MinIO使用基础教程

MinIO使用基础教程 一、背景二、快速安装2.1 虚拟机安装2.2 Windows安装2.2.1 下载MinIO服务器2.2.2 启动 MinIO Server2.2.3 通过浏览器访问MinIO服务控制台 三、使用介绍3.1 创建存储桶3.2 上传和下载文件3.3 设置文件公开访问 四、实战SpringBoot Minio实现文件上传和查询五…

2024春秋杯网络安全联赛夏季赛Crypto(AK)解题思路及用到的软件

2024春秋杯网络安全联赛夏季赛Crypto(AK) 2024春秋杯网络安全联赛夏季赛Crypto解题思路以及用到的软件 所有题用到的软件 1.vm(虚拟机kali)和Ubuntu&#xff0c;正常配置即可B站有很多。 2.Visual Studio Code(里面要配置python&#xff0c;crypto库和Sagemath数学软件系统S…

uni-app框架+vue 实现上拉加载和下拉刷新功能

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;我是码喽的自我修养&#xff01;今天给大家分享uni-app框架vue实现上拉加载和下拉刷新功能&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到带大家&#xff0…