泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

泰晓Linux实验盘,即刻上手内核与嵌入式开发
请稍侯

IPC Namespace 详解

Peng Weilin 创作于 2021/05/26

By pwl999 of [TinyLab.org][1] Mar 23, 2021

1 简介

进程间通讯的机制称为 IPC(Inter-Process Communication)。Linux 下有多种 IPC 机制:管道(PIPE)、命名管道(FIFO)、信号(Signal)、消息队列(Message queues)、信号量(Semaphore)、共享内存(Share Memory)、内存映射(Memory Map)、套接字(Socket)。

其中的三种消息队列(Message queues)、信号量(Semaphore)、共享内存(Share Memory)被称为 XSI IPC,他们源自于 UNIX System V IPC。

Linux 的 IPC Namespace 主要就是针对 XSI IPC 的,和其他 IPC 机制无关。

我们用简单操作来熟悉一下 IPC Namespace 的概念:

1、查看普通进程的 IPC namespace :

  1. pwl@ubuntu:~$ sudo ls -l /proc/$$/ns
  2. [sudo] password for pwl:
  3. total 0
  4. lrwxrwxrwx 1 root root 0 3 14 10:53 cgroup -> 'cgroup:[4026531835]'
  5. lrwxrwxrwx 1 root root 0 3 14 10:53 ipc -> 'ipc:[4026531839]'
  6. lrwxrwxrwx 1 root root 0 3 14 10:53 mnt -> 'mnt:[4026531840]'
  7. lrwxrwxrwx 1 root root 0 3 14 10:53 net -> 'net:[4026531993]'
  8. lrwxrwxrwx 1 root root 0 3 14 10:53 pid -> 'pid:[4026531836]'
  9. lrwxrwxrwx 1 root root 0 3 14 10:53 pid_for_children -> 'pid:[4026531836]'
  10. lrwxrwxrwx 1 root root 0 3 14 10:53 user -> 'user:[4026531837]'
  11. lrwxrwxrwx 1 root root 0 3 14 10:53 uts -> 'uts:[4026531838]'
  12. pwl@ubuntu:~$ ipcs
  13. ------ Message Queues --------
  14. key msqid owner perms used-bytes messages
  15. ------ Shared Memory Segments --------
  16. key shmid owner perms bytes nattch status
  17. 0x00000000 262144 pwl 600 67108864 2 dest
  18. 0x00000000 360449 pwl 600 524288 2 dest
  19. ------ Semaphore Arrays --------
  20. key semid owner perms nsems
  21. pwl@ubuntu:~$

2、创建新的 IPC namespace 并查看:

  1. pwl@ubuntu:~$ sudo unshare --ipc
  2. [sudo] password for pwl:
  3. root@ubuntu:~#
  4. root@ubuntu:~# ls -l /proc/$$/ns
  5. total 0
  6. lrwxrwxrwx 1 root root 0 3 14 10:55 cgroup -> 'cgroup:[4026531835]'
  7. lrwxrwxrwx 1 root root 0 3 14 10:55 ipc -> 'ipc:[4026532643]'
  8. lrwxrwxrwx 1 root root 0 3 14 10:55 mnt -> 'mnt:[4026531840]'
  9. lrwxrwxrwx 1 root root 0 3 14 10:55 net -> 'net:[4026531993]'
  10. lrwxrwxrwx 1 root root 0 3 14 10:55 pid -> 'pid:[4026531836]'
  11. lrwxrwxrwx 1 root root 0 3 14 10:55 pid_for_children -> 'pid:[4026531836]'
  12. lrwxrwxrwx 1 root root 0 3 14 10:55 user -> 'user:[4026531837]'
  13. lrwxrwxrwx 1 root root 0 3 14 10:55 uts -> 'uts:[4026531838]'
  14. root@ubuntu:~# ipcs
  15. ------ Message Queues --------
  16. key msqid owner perms used-bytes messages
  17. ------ Shared Memory Segments --------
  18. key shmid owner perms bytes nattch status
  19. ------ Semaphore Arrays --------
  20. key semid owner perms nsems
  21. root@ubuntu:~#

2 源码分析

2.1 copy_ipcs()

在进程创建或者 unshare()/setns() 系统调用时,如果设置了 CLONE_NEWIPC 标志会调用 copy_ipcs() 创建一个新的 IPC namespace。其中的核心是创建一个新的 struct ipc_namespace 结构,相当于创建了一个新的 XSI IPC 域:

  1. copy_ipcs() create_ipc_ns():
  2. static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns,
  3. struct ipc_namespace *old_ns)
  4. {
  5. struct ipc_namespace *ns;
  6. struct ucounts *ucounts;
  7. int err;
  8. err = -ENOSPC;
  9. ucounts = inc_ipc_namespaces(user_ns);
  10. if (!ucounts)
  11. goto fail;
  12. err = -ENOMEM;
  13. /* (1) 分配新的ipc_namespace数据结构 */
  14. ns = kmalloc(sizeof(struct ipc_namespace), GFP_KERNEL);
  15. if (ns == NULL)
  16. goto fail_dec;
  17. err = ns_alloc_inum(&ns->ns);
  18. if (err)
  19. goto fail_free;
  20. ns->ns.ops = &ipcns_operations;
  21. refcount_set(&ns->count, 1);
  22. ns->user_ns = get_user_ns(user_ns);
  23. ns->ucounts = ucounts;
  24. /* (2) 初始化信号量sem对应的idr池和一些参数 */
  25. err = sem_init_ns(ns);
  26. if (err)
  27. goto fail_put;
  28. /* (3) 初始化消息msg对应的idr池和一些参数 */
  29. err = msg_init_ns(ns);
  30. if (err)
  31. goto fail_destroy_sem;
  32. /* (4) 初始化共享内存shm对应的idr池和一些参数 */
  33. err = shm_init_ns(ns);
  34. if (err)
  35. goto fail_destroy_msg;
  36. /* (5) 初始化消息队列mq对应的一些参数 */
  37. err = mq_init_ns(ns);
  38. if (err)
  39. goto fail_destroy_shm;
  40. return ns;
  41. }

2.2 ipcget()

三种 XSI IPC 的实现机制也是非常类似的,IPC namespace 提供了对应的3个 idr 池 ns->ids[3] ,每新建一个 XSI IPC 对象,会从对应的 idr 池中分配一个 key

以信号量 sem 为例,来分析一下这个过程:

  1. SYSCALL_DEFINE3(semget, key_t, key, int, nsems, int, semflg)
  2. {
  3. struct ipc_namespace *ns;
  4. /* (1) 构造 ipc_ops 参数 */
  5. static const struct ipc_ops sem_ops = {
  6. .getnew = newary,
  7. .associate = sem_security,
  8. .more_checks = sem_more_checks,
  9. };
  10. struct ipc_params sem_params;
  11. /* (2) 构造 ipc namespace 参数 */
  12. ns = current->nsproxy->ipc_ns;
  13. if (nsems < 0 || nsems > ns->sc_semmsl)
  14. return -EINVAL;
  15. /* (3) 构造 ipc_params 参数 */
  16. sem_params.key = key;
  17. sem_params.flg = semflg;
  18. sem_params.u.nsems = nsems;
  19. /* (4) 根据key查询已有的ipcp,或者创建新的ipcp */
  20. return ipcget(ns, &sem_ids(ns), &sem_ops, &sem_params);
  21. }
  22. ipcget() ipcget_public()
  23. static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids,
  24. const struct ipc_ops *ops, struct ipc_params *params)
  25. {
  26. struct kern_ipc_perm *ipcp;
  27. int flg = params->flg;
  28. int err;
  29. /*
  30. * Take the lock as a writer since we are potentially going to add
  31. * a new entry + read locks are not "upgradable"
  32. */
  33. down_write(&ids->rwsem);
  34. /* (4.1) 根据key在信号量 idr 池 `ns->ids[IPC_SEM_IDS]`中查找对应的ipcp */
  35. ipcp = ipc_findkey(ids, params->key);
  36. if (ipcp == NULL) {
  37. /* key not used */
  38. if (!(flg & IPC_CREAT))
  39. err = -ENOENT;
  40. else
  41. /* (4.2) 如果key对应的ipcp不存在,且设置了IPC_CREAT标志
  42. 则根据key创建一个新的ipcp
  43. */
  44. err = ops->getnew(ns, params);
  45. } else {
  46. /* ipc object has been locked by ipc_findkey() */
  47. if (flg & IPC_CREAT && flg & IPC_EXCL)
  48. err = -EEXIST;
  49. else {
  50. err = 0;
  51. /* (4.3) 如果key对应的ipcp存在,则针对参数进行第一步的检查 */
  52. if (ops->more_checks)
  53. err = ops->more_checks(ipcp, params);
  54. if (!err)
  55. /*
  56. * ipc_check_perms returns the IPC id on
  57. * success
  58. */
  59. /* (4.4) ipcp存在,针对参数做第二步的权限检查 */
  60. err = ipc_check_perms(ns, ipcp, ops, params);
  61. }
  62. ipc_unlock(ipcp);
  63. }
  64. up_write(&ids->rwsem);
  65. return err;
  66. }
  67. ops->getnew() newary()
  68. static int newary(struct ipc_namespace *ns, struct ipc_params *params)
  69. {
  70. int retval;
  71. struct sem_array *sma;
  72. key_t key = params->key;
  73. int nsems = params->u.nsems;
  74. int semflg = params->flg;
  75. int i;
  76. if (!nsems)
  77. return -EINVAL;
  78. if (ns->used_sems + nsems > ns->sc_semmns)
  79. return -ENOSPC;
  80. /* (4.2.1) 分配nsems个信号量的数据结构sem_array[] */
  81. sma = sem_alloc(nsems);
  82. if (!sma)
  83. return -ENOMEM;
  84. /* (4.2.2) 初始化其中的ipcp成员,保存UGO模式,保存对应的key */
  85. sma->sem_perm.mode = (semflg & S_IRWXUGO);
  86. sma->sem_perm.key = key;
  87. sma->sem_perm.security = NULL;
  88. retval = security_sem_alloc(sma);
  89. if (retval) {
  90. kvfree(sma);
  91. return retval;
  92. }
  93. for (i = 0; i < nsems; i++) {
  94. INIT_LIST_HEAD(&sma->sems[i].pending_alter);
  95. INIT_LIST_HEAD(&sma->sems[i].pending_const);
  96. spin_lock_init(&sma->sems[i].lock);
  97. }
  98. sma->complex_count = 0;
  99. sma->use_global_lock = USE_GLOBAL_LOCK_HYSTERESIS;
  100. INIT_LIST_HEAD(&sma->pending_alter);
  101. INIT_LIST_HEAD(&sma->pending_const);
  102. INIT_LIST_HEAD(&sma->list_id);
  103. sma->sem_nsems = nsems;
  104. sma->sem_ctime = ktime_get_real_seconds();
  105. /* ipc_addid() locks sma upon success. */
  106. /* (4.2.3) 赋值 ipcp 中的 uid、gid
  107. 并将新分配的ipcp加入信号量 idr 池 `ns->ids[IPC_SEM_IDS]`中
  108. */
  109. retval = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni);
  110. if (retval < 0) {
  111. call_rcu(&sma->sem_perm.rcu, sem_rcu_free);
  112. return retval;
  113. }
  114. ns->used_sems += nsems;
  115. sem_unlock(sma, -1);
  116. rcu_read_unlock();
  117. return sma->sem_perm.id;
  118. }
  119. int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int limit)
  120. {
  121. kuid_t euid;
  122. kgid_t egid;
  123. int id, err;
  124. if (limit > IPCMNI)
  125. limit = IPCMNI;
  126. if (!ids->tables_initialized || ids->in_use >= limit)
  127. return -ENOSPC;
  128. idr_preload(GFP_KERNEL);
  129. refcount_set(&new->refcount, 1);
  130. spin_lock_init(&new->lock);
  131. new->deleted = false;
  132. rcu_read_lock();
  133. spin_lock(&new->lock);
  134. /* (4.2.3.1) 获取当前进程的uid/gid,赋值给 ipcp 的相关成员,
  135. 类似文件的 `chown uid:gid ` 操作
  136. */
  137. current_euid_egid(&euid, &egid);
  138. new->cuid = new->uid = euid;
  139. new->gid = new->cgid = egid;
  140. id = ipc_idr_alloc(ids, new);
  141. idr_preload_end();
  142. /* (4.2.3.2) 加入信号量 idr 池 `ns->ids[IPC_SEM_IDS]`中 */
  143. if (id >= 0 && new->key != IPC_PRIVATE) {
  144. err = rhashtable_insert_fast(&ids->key_ht, &new->khtnode,
  145. ipc_kht_params);
  146. if (err < 0) {
  147. idr_remove(&ids->ipcs_idr, id);
  148. id = err;
  149. }
  150. }
  151. if (id < 0) {
  152. spin_unlock(&new->lock);
  153. rcu_read_unlock();
  154. return id;
  155. }
  156. ids->in_use++;
  157. if (id > ids->max_id)
  158. ids->max_id = id;
  159. new->id = ipc_buildid(id, ids, new);
  160. return id;
  161. }

2.3 ipc_check_perms()

从上一节可以看到 key 被包含在 ipcp 即 struct kern_ipc_perm 结构中:

  1. struct kern_ipc_perm {
  2. spinlock_t lock;
  3. bool deleted;
  4. int id;
  5. key_t key;
  6. kuid_t uid;
  7. kgid_t gid;
  8. kuid_t cuid;
  9. kgid_t cgid;
  10. umode_t mode;
  11. unsigned long seq;
  12. void *security;
  13. struct rhash_head khtnode;
  14. struct rcu_head rcu;
  15. refcount_t refcount;
  16. } ____cacheline_aligned_in_smp __randomize_layout;

在 sem,shm,mq 对象中都有这个成员的存在:

  1. struct sem_array {
  2. struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
  3. ...
  4. }
  5. struct shmid_kernel /* private to the kernel */
  6. {
  7. struct kern_ipc_perm shm_perm;
  8. ...
  9. }
  10. struct msg_queue {
  11. struct kern_ipc_perm q_perm;
  12. ...
  13. }

struct kern_ipc_perm->key 成员保存了key值;->mode 成员保存了 UGO 操作权限,类似文件的 chmod 777 属性;->uid/gid/cuid/cgid 成员保存了属主 uid/gid,类似文件的 chown uid:gid 操作。

在 ipc_check_perms() 函数中,会对被操作的 sem,shm,mq 对象,进行 UGO 权限检查:

  1. ipc_check_perms() ipcperms()
  2. int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flag)
  3. {
  4. kuid_t euid = current_euid();
  5. int requested_mode, granted_mode;
  6. audit_ipc_obj(ipcp);
  7. /* (1) 或 请求的 UGO 操作 */
  8. requested_mode = (flag >> 6) | (flag >> 3) | flag;
  9. /* (2) 获取 ipcp 的 UGO 规则 */
  10. granted_mode = ipcp->mode;
  11. /* (2.1) 如果当前进程 和 ipcp 对象 uid 相等,则取用 U 规则 */
  12. if (uid_eq(euid, ipcp->cuid) ||
  13. uid_eq(euid, ipcp->uid))
  14. granted_mode >>= 6;
  15. /* (2.2) 如果当前进程 和 ipcp 对象 gid 相等,则取用 G 规则 */
  16. else if (in_group_p(ipcp->cgid) || in_group_p(ipcp->gid))
  17. granted_mode >>= 3;
  18. /* (2.3) 如果当前进程 和 ipcp 对象 uid gid 都不相等,则取用 O 规则 */
  19. /* is there some bit set in requested_mode but not in granted_mode? */
  20. /* (3) 判断请求的操作是否适配对应的规则 */
  21. if ((requested_mode & ~granted_mode & 0007) &&
  22. !ns_capable(ns->user_ns, CAP_IPC_OWNER))
  23. return -1;
  24. return security_ipc_permission(ipcp, flag);
  25. }

2.4 相关系统调用

sem,shm,mq 对象通用部分的解析如上所示,除此以外 XSI IPC 还有其他的操作系统调用,这里就不一一解析:

ModuleSyscallDescript
semsemget()创建信号量
-semctl()初始化信号量
-semop()信号量的PV操作
msgmsgget()创建消息队列
-msgctl()获取和设置消息队列的属性
-msgsnd()将消息写入到消息队列
-msgrcv()从消息队列读取消息
shmshmget()创建共享内存对象
-shmctl()共享内存管理
-shmat()把共享内存区对象映射到调用进程的地址空间
-shmdt()断开共享内存连接

3 参考文档

  1. ipc_namespace
  2. Linux内核命名空间之(2) ipc namespace
  3. linux进程间通信(IPC)机制总结
  4. POSIX:XSI Interprocess Communication


Read Album:

Read Related:

Read Latest: