Linux 程式間通訊之System V 共享記憶體

Wu_XMing發表於2018-10-24

1.概述

共享記憶體允許兩個或多個程式共享實體記憶體的同一塊區域(通常稱之為段)。
複製程式碼

由於一個共享記憶體段會成為一個程式使用者空間記憶體的一部分,因此這種IPC機制無需核心介入。所有需要做的就是讓一個程式將資料複製進共享記憶體中,並且這部分資料會對其他所有共享同一個段的程式可用。與管道或訊息佇列要求傳送程式將資料從使用者空間的緩衝區複製進核心緩衝區和接收程式將資料從核心記憶體複製進使用者空間的緩衝區的做法相比,這種IPC技術的速度更快。(每個程式也存在通過系統呼叫來執行復制操作的開銷。)

另一方面,共享記憶體這種IPC機制不由核心控制意味著通常需要通過某些同步方法是的程式不會出現同時訪問共享記憶體的情況。

2.建立或開啟一個共享記憶體段

#include<sys/types.h>
#include<sys/shm.h>
int shmget(key_t key,size_t size,int shmflg);
//return shared memory segment identifier on success,or -1 on error
複製程式碼
  • key: IPC_PRIVATE或ftok()返回的鍵

  • size: 是一個正整數,它表示需分配的段的位元組數。核心是以系統分頁大小的整數倍來分配共享記憶體的,因此實際上size會被提升到最近的系統分頁大小的整數倍。如果使用shmget()來獲取一個既有段的識別符號,那麼size對段不會產生任何效果,但它必須要小於等於段的大小

  • shmflg:指定施加於新共享記憶體段上的許可權或需檢查的既有記憶體段的許可權。標記可以零個或多個取OR來控制操作。

    • IPC_CREAT:如果不存在與指定的key對應的段,那麼就建立一個新段。

    • IPC_EXCL:如果同時指定了IPC_CREAT並且與指定的key對應的段已經存在,那麼返回EEXIST錯誤。

3.使用共享記憶體

#include<sys/types.h>
#include<sys/shm.h>
void *shmat(int shmid,const void *shmaddr,int shmflg);
//return address at which shared memory is attached on success, or (void*) -1 on error
複製程式碼
  • shmaddr:

    • 為NULL時,那麼段會被附加到核心所選擇的一個合適的地址處。這是附加一個段的優選方法。
    • 不為NULL並且沒有設定SHM_RND(shmflg),那麼段會被附加到由shmaddr指定的地址處,它必須是系統分頁大小的一個倍數(否則會發生EINVAL錯誤)。
    • 不為NULL並設定了SHM_RND(shmflg),那麼段會被對映到的地址為在shmaddr中提供的地址被舍入到最近的常量SHMLBA(shared memory low boundary address)的倍數。這個常量等於系統分頁大小的某個倍數。

    將一個段附加到值為SHMLBA的倍數的地址處在一些架構上是有必要的,因為這樣才能夠提升CPU的快速緩衝效能和防止出現同一個段的不同附加操作在CPU快速緩衝效能和防止同一個段的不同附加操作在CPU快速緩衝中存在不一致的檢視的情況。

  • shmflg: 是一個位掩碼值

Linux 程式間通訊之System V 共享記憶體

 如果沒有指定SHM_RDONLY,那麼就即可以讀取記憶體又可以修改記憶體。
複製程式碼

shmat()的函式結果是返回附加共享記憶體段的地址。開發人員可以像對待普通的C指標那樣對待這個值,段與程式的虛擬記憶體的其他部分看起來毫無差異。通常會將shmat()的返回值賦給一個指向某個由程式設計師定義的結構的指標以便在該段上設定該結構。

在一個程式中可以多次附加同一個共享記憶體段,即使一個附加操作是隻讀的而另一個是讀寫的也沒有關係。

Linux 程式間通訊之System V 共享記憶體

4. 共享記憶體控制操作

#include<sys/types.h>
#include<sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
//return 0 on success,or -1 on error

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;
  };

複製程式碼
  • cmd:引數規定了待執行的控制操作。

      常規控制操作
    複製程式碼
    • IPC_RMID: 標記這個共享記憶體段及其關聯shmid_ds資料結構以便刪除。如果當前沒有程式附加該段,那麼就會執行刪除操作,否則就在所有程式都已經與該段分離(即當shmid_ds資料結構中shm_nattch欄位的值為0時)之後再執行刪除操作。

    • IPC_STAT:將與這個共享記憶體段關聯的shmid_ds資料結構的一個副本防止到buf指向的緩衝區中。

    • IPC_SET:使用buf指向的緩衝區中的值來更新與這個共享記憶體段相關聯的shmid_ds資料結構中被選中的欄位。

        加鎖與解鎖共享記憶體
      複製程式碼

      一個共享記憶體段可以被鎖進RAM中,這樣它就永遠不會被交換出去了。這種做法能夠帶來效能上的提升,因為一旦段中的所有分頁都駐留在記憶體中,就能夠確保一個應用程式在訪問分頁時永遠不會因為分頁故障而被延遲。通過shmctl()可以完成兩種鎖操作。

    • SHM_LOCK:操作將一個共享記憶體段鎖進記憶體。

    • SHM_UNLOCK:操作為共享記憶體段解鎖以允許它被交換出去。

5.獲取共享記憶體的限制

struct shminfo buf;
shmctl(0,IPC_INFO,(struct shmid_ds *)&buf);

struct	shminfo
  {
 
    __syscall_ulong_t shmmax; //這是一個共享記憶體段的最大大小(位元組數)。(shmget(),EINVAL)
    
    __syscall_ulong_t shmmin; //這是一個共享記憶體段的最小大小(位元組數)。這個限制的值被定義成了1(無法修改),實際的限制是系統分頁的大小(shmget(),EINVAL)
   
    __syscall_ulong_t shmmni;  //系統級別限制,限制了所能建立的共享記憶體識別符號的數量(shmget(),ENOSPC) 
  
    __syscall_ulong_t shmseg;   //程式級別限制,限制了所能附加的共享記憶體段數量
   
    __syscall_ulong_t shmall;    //系統級別限制,限制了共享記憶體中的分頁總數。(shmget(),ENOSPC)
    __syscall_ulong_t __glibc_reserved1;
    __syscall_ulong_t __glibc_reserved2;
    __syscall_ulong_t __glibc_reserved3;
    __syscall_ulong_t __glibc_reserved4;
  };

複製程式碼

相關文章