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 函数的使用
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值