Linux ns 5. IPC Namespace 详解
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 :
pwl@ubuntu:~$ sudo ls -l /proc/$$/ns
[sudo] password for pwl:
total 0
lrwxrwxrwx 1 root root 0 3月 14 10:53 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 3月 14 10:53 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 3月 14 10:53 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 3月 14 10:53 net -> 'net:[4026531993]'
lrwxrwxrwx 1 root root 0 3月 14 10:53 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 3月 14 10:53 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 3月 14 10:53 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 3月 14 10:53 uts -> 'uts:[4026531838]'
pwl@ubuntu:~$ ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 262144 pwl 600 67108864 2 dest
0x00000000 360449 pwl 600 524288 2 dest
------ Semaphore Arrays --------
key semid owner perms nsems
pwl@ubuntu:~$
2、创建新的 IPC namespace 并查看:
pwl@ubuntu:~$ sudo unshare --ipc
[sudo] password for pwl:
root@ubuntu:~#
root@ubuntu:~# ls -l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 3月 14 10:55 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 3月 14 10:55 ipc -> 'ipc:[4026532643]'
lrwxrwxrwx 1 root root 0 3月 14 10:55 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 3月 14 10:55 net -> 'net:[4026531993]'
lrwxrwxrwx 1 root root 0 3月 14 10:55 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 3月 14 10:55 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 3月 14 10:55 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 3月 14 10:55 uts -> 'uts:[4026531838]'
root@ubuntu:~# ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
root@ubuntu:~#
2. 源码分析
2.1 copy_ipcs()
在进程创建或者 unshare()/setns() 系统调用时,如果设置了 CLONE_NEWIPC
标志会调用 copy_ipcs() 创建一个新的 IPC namespace。其中的核心是创建一个新的 struct ipc_namespace
结构,相当于创建了一个新的 XSI IPC 域:
copy_ipcs() → create_ipc_ns():
static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns,
struct ipc_namespace *old_ns)
{
struct ipc_namespace *ns;
struct ucounts *ucounts;
int err;
err = -ENOSPC;
ucounts = inc_ipc_namespaces(user_ns);
if (!ucounts)
goto fail;
err = -ENOMEM;
/* (1) 分配新的ipc_namespace数据结构 */
ns = kmalloc(sizeof(struct ipc_namespace), GFP_KERNEL);
if (ns == NULL)
goto fail_dec;
err = ns_alloc_inum(&ns->ns);
if (err)
goto fail_free;
ns->ns.ops = &ipcns_operations;
refcount_set(&ns->count, 1);
ns->user_ns = get_user_ns(user_ns);
ns->ucounts = ucounts;
/* (2) 初始化信号量sem对应的idr池和一些参数 */
err = sem_init_ns(ns);
if (err)
goto fail_put;
/* (3) 初始化消息msg对应的idr池和一些参数 */
err = msg_init_ns(ns);
if (err)
goto fail_destroy_sem;
/* (4) 初始化共享内存shm对应的idr池和一些参数 */
err = shm_init_ns(ns);
if (err)
goto fail_destroy_msg;
/* (5) 初始化消息队列mq对应的一些参数 */
err = mq_init_ns(ns);
if (err)
goto fail_destroy_shm;
return ns;
}
2.2 ipcget()
三种 XSI IPC 的实现机制也是非常类似的,IPC namespace 提供了对应的3个 idr 池 ns->ids[3]
,每新建一个 XSI IPC 对象,会从对应的 idr 池中分配一个 key
。
以信号量 sem 为例,来分析一下这个过程:
SYSCALL_DEFINE3(semget, key_t, key, int, nsems, int, semflg)
{
struct ipc_namespace *ns;
/* (1) 构造 ipc_ops 参数 */
static const struct ipc_ops sem_ops = {
.getnew = newary,
.associate = sem_security,
.more_checks = sem_more_checks,
};
struct ipc_params sem_params;
/* (2) 构造 ipc namespace 参数 */
ns = current->nsproxy->ipc_ns;
if (nsems < 0 || nsems > ns->sc_semmsl)
return -EINVAL;
/* (3) 构造 ipc_params 参数 */
sem_params.key = key;
sem_params.flg = semflg;
sem_params.u.nsems = nsems;
/* (4) 根据key查询已有的ipcp,或者创建新的ipcp */
return ipcget(ns, &sem_ids(ns), &sem_ops, &sem_params);
}
↓
ipcget() → ipcget_public()
↓
static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids,
const struct ipc_ops *ops, struct ipc_params *params)
{
struct kern_ipc_perm *ipcp;
int flg = params->flg;
int err;
/*
* Take the lock as a writer since we are potentially going to add
* a new entry + read locks are not "upgradable"
*/
down_write(&ids->rwsem);
/* (4.1) 根据key在信号量 idr 池 `ns->ids[IPC_SEM_IDS]`中查找对应的ipcp */
ipcp = ipc_findkey(ids, params->key);
if (ipcp == NULL) {
/* key not used */
if (!(flg & IPC_CREAT))
err = -ENOENT;
else
/* (4.2) 如果key对应的ipcp不存在,且设置了IPC_CREAT标志
则根据key创建一个新的ipcp
*/
err = ops->getnew(ns, params);
} else {
/* ipc object has been locked by ipc_findkey() */
if (flg & IPC_CREAT && flg & IPC_EXCL)
err = -EEXIST;
else {
err = 0;
/* (4.3) 如果key对应的ipcp存在,则针对参数进行第一步的检查 */
if (ops->more_checks)
err = ops->more_checks(ipcp, params);
if (!err)
/*
* ipc_check_perms returns the IPC id on
* success
*/
/* (4.4) ipcp存在,针对参数做第二步的权限检查 */
err = ipc_check_perms(ns, ipcp, ops, params);
}
ipc_unlock(ipcp);
}
up_write(&ids->rwsem);
return err;
}
↓
ops->getnew() → newary()
↓
static int newary(struct ipc_namespace *ns, struct ipc_params *params)
{
int retval;
struct sem_array *sma;
key_t key = params->key;
int nsems = params->u.nsems;
int semflg = params->flg;
int i;
if (!nsems)
return -EINVAL;
if (ns->used_sems + nsems > ns->sc_semmns)
return -ENOSPC;
/* (4.2.1) 分配nsems个信号量的数据结构sem_array[] */
sma = sem_alloc(nsems);
if (!sma)
return -ENOMEM;
/* (4.2.2) 初始化其中的ipcp成员,保存UGO模式,保存对应的key */
sma->sem_perm.mode = (semflg & S_IRWXUGO);
sma->sem_perm.key = key;
sma->sem_perm.security = NULL;
retval = security_sem_alloc(sma);
if (retval) {
kvfree(sma);
return retval;
}
for (i = 0; i < nsems; i++) {
INIT_LIST_HEAD(&sma->sems[i].pending_alter);
INIT_LIST_HEAD(&sma->sems[i].pending_const);
spin_lock_init(&sma->sems[i].lock);
}
sma->complex_count = 0;
sma->use_global_lock = USE_GLOBAL_LOCK_HYSTERESIS;
INIT_LIST_HEAD(&sma->pending_alter);
INIT_LIST_HEAD(&sma->pending_const);
INIT_LIST_HEAD(&sma->list_id);
sma->sem_nsems = nsems;
sma->sem_ctime = ktime_get_real_seconds();
/* ipc_addid() locks sma upon success. */
/* (4.2.3) 赋值 ipcp 中的 uid、gid
并将新分配的ipcp加入信号量 idr 池 `ns->ids[IPC_SEM_IDS]`中
*/
retval = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni);
if (retval < 0) {
call_rcu(&sma->sem_perm.rcu, sem_rcu_free);
return retval;
}
ns->used_sems += nsems;
sem_unlock(sma, -1);
rcu_read_unlock();
return sma->sem_perm.id;
}
↓
int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int limit)
{
kuid_t euid;
kgid_t egid;
int id, err;
if (limit > IPCMNI)
limit = IPCMNI;
if (!ids->tables_initialized || ids->in_use >= limit)
return -ENOSPC;
idr_preload(GFP_KERNEL);
refcount_set(&new->refcount, 1);
spin_lock_init(&new->lock);
new->deleted = false;
rcu_read_lock();
spin_lock(&new->lock);
/* (4.2.3.1) 获取当前进程的uid/gid,赋值给 ipcp 的相关成员,
类似文件的 `chown uid:gid ` 操作
*/
current_euid_egid(&euid, &egid);
new->cuid = new->uid = euid;
new->gid = new->cgid = egid;
id = ipc_idr_alloc(ids, new);
idr_preload_end();
/* (4.2.3.2) 加入信号量 idr 池 `ns->ids[IPC_SEM_IDS]`中 */
if (id >= 0 && new->key != IPC_PRIVATE) {
err = rhashtable_insert_fast(&ids->key_ht, &new->khtnode,
ipc_kht_params);
if (err < 0) {
idr_remove(&ids->ipcs_idr, id);
id = err;
}
}
if (id < 0) {
spin_unlock(&new->lock);
rcu_read_unlock();
return id;
}
ids->in_use++;
if (id > ids->max_id)
ids->max_id = id;
new->id = ipc_buildid(id, ids, new);
return id;
}
2.3 ipc_check_perms()
从上一节可以看到 key 被包含在 ipcp 即 struct kern_ipc_perm
结构中:
struct kern_ipc_perm {
spinlock_t lock;
bool deleted;
int id;
key_t key;
kuid_t uid;
kgid_t gid;
kuid_t cuid;
kgid_t cgid;
umode_t mode;
unsigned long seq;
void *security;
struct rhash_head khtnode;
struct rcu_head rcu;
refcount_t refcount;
} ____cacheline_aligned_in_smp __randomize_layout;
在 sem,shm,mq 对象中都有这个成员的存在:
struct sem_array {
struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
...
}
struct shmid_kernel /* private to the kernel */
{
struct kern_ipc_perm shm_perm;
...
}
struct msg_queue {
struct kern_ipc_perm q_perm;
...
}
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 权限检查:
ipc_check_perms() → ipcperms()
↓
int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flag)
{
kuid_t euid = current_euid();
int requested_mode, granted_mode;
audit_ipc_obj(ipcp);
/* (1) 或 请求的 UGO 操作 */
requested_mode = (flag >> 6) | (flag >> 3) | flag;
/* (2) 获取 ipcp 的 UGO 规则 */
granted_mode = ipcp->mode;
/* (2.1) 如果当前进程 和 ipcp 对象 uid 相等,则取用 U 规则 */
if (uid_eq(euid, ipcp->cuid) ||
uid_eq(euid, ipcp->uid))
granted_mode >>= 6;
/* (2.2) 如果当前进程 和 ipcp 对象 gid 相等,则取用 G 规则 */
else if (in_group_p(ipcp->cgid) || in_group_p(ipcp->gid))
granted_mode >>= 3;
/* (2.3) 如果当前进程 和 ipcp 对象 uid gid 都不相等,则取用 O 规则 */
/* is there some bit set in requested_mode but not in granted_mode? */
/* (3) 判断请求的操作是否适配对应的规则 */
if ((requested_mode & ~granted_mode & 0007) &&
!ns_capable(ns->user_ns, CAP_IPC_OWNER))
return -1;
return security_ipc_permission(ipcp, flag);
}
2.4 相关系统调用
sem,shm,mq 对象通用部分的解析如上所示,除此以外 XSI IPC 还有其他的操作系统调用,这里就不一一解析:
Module | Syscall | Descript |
---|---|---|
sem | semget() | 创建信号量 |
- | semctl() | 初始化信号量 |
- | semop() | 信号量的PV操作 |
msg | msgget() | 创建消息队列 |
- | msgctl() | 获取和设置消息队列的属性 |
- | msgsnd() | 将消息写入到消息队列 |
- | msgrcv() | 从消息队列读取消息 |
shm | shmget() | 创建共享内存对象 |
- | shmctl() | 共享内存管理 |
- | shmat() | 把共享内存区对象映射到调用进程的地址空间 |
- | shmdt() | 断开共享内存连接 |
参考文档:
1.ipc_namespace
2.Linux内核命名空间之(2) ipc namespace
3.linux进程间通信(IPC)机制总结
4.POSIX:XSI Interprocess Communication
Linux ns 5. IPC Namespace 详解的更多相关文章
- Linux ns 4. UTS Namespace 详解
目录 1. 使用简介 1.1 hostname 1.2 domainname 1.3 uname 2. 代码分析 2.1 copy_utsname() 2.2 sethostname() 2.3 ge ...
- Linux ns 6. Network Namespace 详解
文章目录 1. 简介 1.1 Docker Network 桥接模式配置 2. 代码解析 2.1 copy_net_ns() 2.2 pernet_list 2.2.1 loopback_net_op ...
- Linux ns 3. Mnt Namespace 详解
1. 文件系统层次化 对 Linux 系统来说一切皆文件,Linux 使用树形的层次化结构来管理所有的文件对象. 完整的Linux文件系统,是由多种设备.多种文件系统组成的一个混合的树形结构.我们首先 ...
- Linux下DNS服务器搭建详解
Linux下DNS服务器搭建详解 DNS 即Domain Name System(域名系统)的缩写,它是一种将ip地址转换成对应的主机名或将主机名转换成与之相对应ip地址的一种机制.其中通过域名解析 ...
- Linux上的free命令详解、swap机制
Linux上的free命令详解 解释一下Linux上free命令的输出. 下面是free的运行结果,一共有4行.为了方便说明,我加上了列号.这样可以把free的输出看成一个二维数组FO(Free ...
- linux PHP 编译安装参数详解
linux PHP 编译安装参数详解 ./configure --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc -- ...
- Linux Shell数组常用操作详解
Linux Shell数组常用操作详解 1数组定义: declare -a 数组名 数组名=(元素1 元素2 元素3 ) declare -a array array=( ) 数组用小括号括起,数组元 ...
- Linux CAT与ECHO命令详解 <<EOF EOF
Linux CAT与ECHO命令详解 cat命令是Linux下的一个文本输出命令,通常是用于观看某个文件的内容的: cat主要有三大功能: .一次显示整个文件. $ cat filename .从键盘 ...
- linux:SUID、SGID详解
linux:SUID.SGID详解 文章转载至:http://tech.ccidnet.com/art/2583/20071030/1258885_1.html 如果你对SUID.SGID仍有迷惑可以 ...
随机推荐
- 用 openresty 编写 lua
""" #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/erro ...
- 利用Qt中的ui文件生成PyQt5程序,自定义槽函数
1.在Qt Creator4.8.0上面设计如上.ui文件 2.点击上方图标,可以建立信号-槽连接,button_click()为自定义槽函数 3.设计目的:点击clear按钮,可消除上方文本框中的内 ...
- Microfacet模型采样下的brdf
本文前言 在学习图形学(games101 from bilibili)的时候,也遇到了像这样的问题,Cook-Torrance模型无法实现粗糙度为0时,物体微表面呈现绝对镜面的效果(呈现出一面镜子), ...
- Python中open和with open有什么区别?怎么用?
open 打开文件 file=open("文件名","读写模式") 操作文件 代码段 关闭文件 file.close() 注意事项:使用open方法,文件操作完 ...
- Feign 400错误引发的一系列问题
Feign 400错误引发的一系列问题 问题介绍 在使用Feign进行远程调用的时候出现非常奇怪的400错误,错误信息大概如下: feign.FeignException: status 400 re ...
- Idea生成JavaDoc文档
什么是JavaDoc javadoc是Sun公司提供的一个技术 它从程序源代码中抽取类.方法.成员等注释形成一个和源代码配套的API帮助文档 实现方式 命令行方式 javadoc -encoding ...
- WPF实现聚光灯效果
WPF开发者QQ群: 340500857 | 微信群 -> 进入公众号主页 加入组织 前言 效果仿照 CSS聚光灯效果 实现思路: 1. 设置底部Canvas背景色 #222222 . 2. ...
- HPE ProLiant 系列服务器Microsoft Windows 2008 R2系统下网卡绑定方法
HPE Network Configuration Utility(以下简称NCU) 网卡绑定工具,用户可以通过该工具很方便的把服务器的多个网卡捆绑到一起以达到容错和增加可用带宽的目的. 1.打开NC ...
- 从零入门 Serverless | Serverless 应用如何管理日志 & 持久化数据
作者 | 竞霄 阿里巴巴开发工程师 本文整理自<Serverless 技术公开课>,关注"Serverless"公众号,回复"入门",即可获取 Se ...
- 2019 年 CNCF 中国云原生调查报告
中国 72% 的受访者生产中使用 Kubernetes 在 CNCF,为更好地了解开源和云原生技术的使用,我们定期调查社区.这是第三次中国云原生调查,以中文进行,以便更深入地了解中国云原生技术采用的步 ...