1. 程式人生 > >57-System V 共享記憶體-shmctl

57-System V 共享記憶體-shmctl

在很久以前,我們學過一個函式 fcntl,當時學習它時挺費勁,因為一個函式,竟然有如此多的功能。今天要學習 shmctl 函式,也是一樣。這種以 cntl/ctl 為字尾的函式,往往都具備這樣的特性:它們都有一個命令控制引數,你傳遞不同的命令,這個函式有具備著不同的功能,可謂身兼數職說的就是它吧。

1. shmctl 函式

1.1 函式原型

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

這個函式的引數 shmid 是 ipc 核心物件的 id。cmd 是命令,這裡只介紹 IPC_STAT, IPC_SET 和 IPC_RMID 三個命令。其中 IPC_RMID 命令表示刪除核心物件。實際上不只是針對 shmctl 這個函式,訊息佇列和訊號量的控制函式(msgctl 和 semctl) 的命令也是這樣。

先看看第三個引數 shmid_ds 結構體。

1.2 shmid_ds 結構體

有時候,我們需要獲取 IPC 核心物件的相關資訊,這時候要怎麼辦呢? 畢竟 ipc 核心物件它位於核心空間中,普通使用者程式根本無法直接訪問,為此 linux 提供了這樣的結構體 shmid_ds 給使用者態的程式使用。shmctl 函式可以將位於核心空間的 ipc 核心物件的資訊拷貝一份給使用者空間的 shmid_ds,也可以通過將此結構體中的資訊拷貝到核心空間用來設定 ipc 核心物件,具體是哪種情況依賴於引數 cmd。

當 cmd = IPC_STAT 的時候, 第三個引數 buf 用於接收返回值,當 cmd = IPC_SET 的時候,第三個引數用於傳遞值並設定核心物件。當 cmd = IPC_RMID 的時候,第三個引數被忽略,可以直接傳 NULL.

該結構體的詳細內容見註釋。

struct shmid_ds {
    struct ipc_perm shm_perm;    /* 所有權和許可權位 */
    size_t          shm_segsz;   /* 段大小 */
    time_t          shm_atime;   /* 最後掛接時間 */
    time_t          shm_dtime;   /* 最後解除安裝時間 */
    time_t          shm_ctime;   /* 最後改變當前結構體的時間(由IPC_SET命令改變) */
    pid_t           shm_cpid;    /* 建立 ipc 核心物件的程序 pid */
pid_t shm_lpid; /* 最後執行 shmat/shmdt 的程序 pid */ shmatt_t shm_nattch; /* 掛接程序個數 */ ... };

其中成員 shm_perm 是所有 System V IPC 核心物件都包含的,它的結構如下:

struct ipc_perm {
    uid_t          uid;      /* 所有者有效使用者 id */
    gid_t          gid;      /* 所有者有效使用者組 id */
    uid_t          cuid;     /* 建立者有效使用者 id */
    gid_t          cgid;     /* 建立者有效使用者組 id */
    unsigned short mode;     /* 許可權位*/
};

2. 實驗

下面的程式 shmctl 可以用來建立、刪除核心物件,也可以掛接、解除安裝共享記憶體,還可以列印、設定核心物件資訊。具體使用方法具體見下面的說明:

  • ./shmctl -c : 建立核心物件。
  • ./shmctl -d : 刪除核心物件。
  • ./shmctl -v : 顯示核心物件資訊。
  • ./shmctl -s : 設定核心物件(將許可權設定為 0600)。
  • ./shmctl -a : 掛接和解除安裝共享記憶體(掛接 5 秒後,再執行 shmdt,然後退出)。

shmctl 函式的程式碼見 2.5 節。

2.1 建立核心物件


這裡寫圖片描述
圖1 建立核心物件,列印核心物件資訊

2.2 設定核心物件


這裡寫圖片描述
圖2 將核心物件許可權設定為 0600

2.3 掛接後核心物件資訊

先在另一個終端執行 ./shmctl -a,然後在當前終端執行./shmctl -v(注意手速,5秒內要搞定)。


這裡寫圖片描述
圖3 掛接共享記憶體後 ipc 核心物件的資訊

2.4 解除安裝後核心物件資訊

當 2.3 節上的 ./shmctl -a 結束後,再執行一次 ./shmctl -v.


這裡寫圖片描述
圖4 解除安裝共享記憶體後 ipc 核心物件的資訊

2.5 shmctl 程式程式碼

這段程式碼比較長,你可以直接複製過去進行編譯。

// shmctl.c
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#define ASSERT(res) if((res)<0){perror(__FUNCTION__);exit(-1);}

// 列印 ipc_perm
void printPerm(struct ipc_perm *perm) {
  printf("euid of owner = %d\n", perm->uid);
  printf("egid of owner = %d\n", perm->gid);
  printf("euid of creator = %d\n", perm->cuid);
  printf("egid of creator = %d\n", perm->cgid);
  printf("mode = 0%o\n", perm->mode);
}

// 列印 ipc 核心物件資訊
void printShmid(struct shmid_ds *shmid) {
  printPerm(&shmid->shm_perm); 
  printf("segment size = %d\n", shmid->shm_segsz);
  printf("last attach time = %s", ctime(&shmid->shm_atime));
  printf("last detach time = %s", ctime(&shmid->shm_dtime));
  printf("last change time = %s", ctime(&shmid->shm_ctime));
  printf("pid of creator = %d\n", shmid->shm_cpid);
  printf("pid of last shmat/shmdt = %d\n", shmid->shm_lpid);
  printf("No. of current attaches = %ld\n", shmid->shm_nattch);
}

// 建立 ipc 核心物件
void create() {
  int id = shmget(0x8888, 123, IPC_CREAT | IPC_EXCL | 0664);
  printf("create %d\n", id);
  ASSERT(id);
}

// IPC_STAT 命令使用,用來獲取 ipc 核心物件資訊
void show() {
  int id = shmget(0x8888, 0, 0); 
  ASSERT(id);
  struct shmid_ds shmid;
  ASSERT(shmctl(id, IPC_STAT, &shmid));
  printShmid(&shmid);
}

// IPC_SET 命令使用,用來設定 ipc 核心物件資訊
void set() {
  int id = shmget(0x8888, 123, IPC_CREAT | 0664);
  ASSERT(id);
  struct shmid_ds shmid;
  ASSERT(shmctl(id, IPC_STAT, &shmid));
  shmid.shm_perm.mode = 0600;
  ASSERT(shmctl(id, IPC_SET, &shmid));
  printf("set %d\n", id);
}

// IPC_RMID 命令使用,用來刪除 ipc 核心物件
void rm() {
  int id = shmget(0x8888, 123, IPC_CREAT | 0664);
  ASSERT(id);
  ASSERT(shmctl(id, IPC_RMID, NULL));
  printf("remove %d\n", id);
}

// 掛接和解除安裝
void at_dt() {
  int id = shmget(0x8888, 123, IPC_CREAT | 0664);
  ASSERT(id);
  char *buf = shmat(id, NULL, 0);
  if (buf == (char*)-1) ASSERT(-1);
  printf("shmat %p\n", buf);
  sleep(5); // 等待 5 秒後,執行 shmdt
  ASSERT(shmdt(buf));
  printf("shmdt %p\n", buf);
}



int main(int argc, char *argv[]) {
  if (argc < 2) {
    printf("usage: %s <option -c -v -s -d -a>\n", argv[0]);
    return -1;
  }

  printf("I'm %d\n", getpid());

  if (!strcmp(argv[1], "-c")) {
    create();
  }
  else if (!strcmp(argv[1], "-v")) {
    show();
  }
  else if (!strcmp(argv[1], "-s")) {
    set();
  }
  else if (!strcmp(argv[1], "-d")) {
    rm();
  }
  else if (!strcmp(argv[1], "-a")) {
    at_dt();
  }

  return 0;
}

3. 總結

  • 知道如何獲取 ipc 核心物件的資訊
  • 掌握 shmctl 函式的使用