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 函式的使用