在linux中,命名空间主要提供一种轻量级的资源虚拟,可以从不同方面来查看系统的全局属性,不同命名空间可以互相不干扰,为进程的一部分嗯。例如:同一个进程pid,可以被多个进程共享使用,可以查看同一用户的所有资源消费情况等等。

总体定义

定义如下:

  1. struct nsproxy {
  2. atomit_t count;//指向同一个nsproxy的进程个数
  3. struct uts_namespace *uts_ns;//运行的内核
  4. struct ipc_namespace *ipc_ns;//进程通信ipc
  5. struct mnt_namespace *pid_ns;//文件系统
  6. struct user_namespace *user_ns;//用户的资源限制信息
  7. struct net *net_ns;//网络
  8. }

在创建进程(fork,clone系统调用)时,需要设置一些标志来指明命名空间的创建与否

  1. #define CLONE_NEWUTS 0x04000000
  2. #define CLONE_NEWIPC    0x08000000
  3. #define CLONE_NEWUSER 0x10000000
  4. #define CLONE_NEWPID 0x20000000
  5. #define CLONE_NEWNET 0x40000000

同时在每个任务的定义中也包含命名空间的相关域:

  1. struct task_struct{
  2. …..
  3. //指针形式,这样可以被多个进程共享,修改一个命名空间,其它进程就可见了
  4. struct nsproxy *nsproxy;
  5. …....
  6. }

值得注意的是,命名空间需要在编译其间进行选择,如果没有指明,则所有的属性都是全局的,即只存在一个命名空间,全局命名空间为init_proxy,指向每个子系统的对象:

  1. struct nsproxy init_nsproxy = INIT_NSPROXY(init_nsproxy);
  2. #define INIT_NSPROXY(nsproxy) {
  3. .pid_ns     =     &init_pid_ns,    \
  4. .count    =     ATOMIC_INIT(1), \
  5. .uts_ns    =    &init_uts_ns,     \
  6. .mnt_ns    =     NULL,         \
  7. INIT_NET_NS(net_ns),             \
  8. INIT_IPC_NS(ipc_ns),             \
  9. .user_ns    =     &init_user_ns,     \
  10. }

相对应的系统调用有unshare,可以将父子进程的命名空间进行分开或共享。

UTS命名空间

定义如下:

  1. struct uts_namespace {
  2. struct kref kref;//使用计数
  3. struct new_utsname name;
  4. }
  5. struct new_utsname
  6. {
  7. char sysname[65];//系统名称
  8. char nodename[65];//主机名
  9. char release[65];//内核版本号
  10. char version[65];//内核版本日期
  11. char machine[65];//体系结构
  12. char domainname[65];
  13. }

测试结果如下:

  1. sys:Linux
  2. node:ubuntu-laptop
  3. release:2.6.32-33-generic
  4. version:#68-Ubuntu SMP Fri Jun 17 16:25:19 UTC 2011
  5. machine:i686
  6. domain:(none)

init进程初始化时,utsname赋值如下:

  1. struct uts_namespace init_uts_ns     =     {
  2. .kref      = {
  3. .refcount         = ATOMIC_INIT(2),
  4. },
  5. .name = {
  6. .sysname        = UTS_SYSNAME,
  7. .nodename        = UTS_NODENAME,
  8. .release        = UTS_RELEASE,
  9. .version         = UTS_VERSION,
  10. .machine        = UTS_MACHINE,
  11. .domainname    = UTS_DOMAINNAME,
  12. },
  13. }

这些宏常量定义在内核的各个地方,有些通过编译内核形成的,如UTS_RElEASE,定义在文件

<utsrelease.h>中。有些域可以进行修改,但是有些域不能修改,如UTS_SYSNAME只能是Linux,而UTS_NODENAME则可以进行修改。

User namespace

当需要一个新用户命名空间时,当前用户命名空间就会被复制,并与当前任务的nsproxy相关联,声明如下:

  1. struct user_namespace {
  2. struct kref         kref;
  3. struct hlist_head uidhash_table[UIDHASH_SZ];
  4. struct user_struct *root_user;
  5. }

其中root_user用于记录单个用户资源情况,而uidhash_table将所有user_struct连接起来.

  1. struct user_struct {
  2. atomic_t __count;//该结构体引用计数
  3. atomic_t processes;//该用户拥有的进程数量
  4. atomic_t sigpending;//该用户拥有的悬而未决的信号量数目
  5. ….
  6. unsigned long locked_shm;//锁住的共享页个数
  7. //hash表所维护的信息
  8. struct hlist_node uidhash_node;
  9. uid_t uid;
  10. …...
  11. }

当设置新的用户时,用户命名空间创建如下:

  1. static struct user_namespace *clone_user_ns(struct user_namespace *old_ns)
  2. {
  3. struct user_namespace *ns;
  4. struct user_struct *new_user;
  5. int n;
  6. ns = kmalloc(sizeof(struct user_namespace),GFP_KERNEL);
  7. if(!ns)
  8. return ERR_PTR(-ENOMEM);
  9. kref_init(&ns->kref);
  10. for(n=0; n< UIDHASH_SZ;++n)
  11. INIT_HLIST_HEAD(ns->uidhash_table+n);
  12. /*insert into new root user*/
  13. ns->root_user = alloc_uid(ns,0);
  14. if(!ns->root_user) {
  15. kfree(ns);
  16. return ERR_PTR(-ENOMEM);
  17. }
  18. new_user = alloc_uid(ns,current->uid);
  19. if(!new_user) {
  20. free_uid(ns->root_user);
  21. kfree(ns);
  22. return ERR_PTR(-ENOMEM);
  23. }
  24. switch(new_user);
  25. return ns;
  26. }

命名空间增加了pid管理的复杂性,pid命名空间按照树型层次化管理。当一个新的命名空间创建,该命名空间内所有的pid都对其父命名空间可见,但是子命名空间却不能看到父命名空间中pid,这样就意味着有些任务包含有多个pid:父命名空间,子命名空间。这样就产生了两种类型的pid:

Global pid:内核本身包含有效的pid,init任务中可见的全局唯一的pid,也就是系统唯一的pid。

Local pid:在该命名空间内部的pid,全局不是唯一的,在不同的命名空间中,可能存在相同的pid。

task的pid_namespace描述

task_struct 中结构体部分结构如下:

  1. struct task_struct {
  2. …..
  3. pid_t pid;
  4. pid_t tgid;
  5. struct task_struct *group_leader;
  6. struct list_head thread_group;
  7. …..
  8. }

而session id和进程组id没有直接包含在task_struct内部,而是存放在signal结构体中

  1. struct signal_struct {
  2. ….....
  3. struct task_struct *curr_target;
  4. union {
  5. pid_t pgrp __deprecated;
  6. pid_t __pgrp;
  7. };
  8. union {
  9. pid_t session __deprecated;
  10. pid_t __session;
  11. };
  12. ….....
  13. }

对应的修改函数有:set_task_session,set_task_pgrp。

pid的管理

对应的pid_namespace定义如下:

  1. [include/linux/pid_namespace.h]
  2. //每个命名空间都包含一个所有pid的分配图,系统中各个命名空间互不交叉
  3. struct pid_namespace {
  4. struct kref kref;//进程的引用计数
  5. struct pidmap pidmap[PID_ENTRIES];//pid分配图
  6. int last_pid;
  7. struct task_struct *child_reaper;//当前命名空间中waitpid角色的task
  8. struct kmem_cache *pid_cachep;
  9. int level;//当前pid_namespace的深度
  10. struct pid_namespace *parent;//父pid_namespace
  11. }

init_ns定义如下:

  1. [kernel/pid.c]
  2. struct pid_namespace init_pid_ns = {
  3. .kref = {
  4. .refcount = ATOMIC_INIT(2),
  5. },
  6. //可用pid:4KB 4*1024*8=32768
  7. .pidmap = {
  8. .pidmap = {
  9. [ 0 … PIDMAP_ENTRIES – 1] = {
  10. ATOMIC_INIT(BITS_PER_PAGE),NULL}
  11. },
  12. .last_pid = 0,
  13. .level = 0,
  14. .child_reaper = &init_task,//init_task监听该命名空间的所有task
  15. }

pid的管理主要集中在两个数据结构:struct pid为内核形式的pid,struct upid代表在特定命名空间中可见的信息,定义如下:

  1. [include/linux/pid.h]
  2. struct upid
  3. {
  4. int nr;//真正的pid值
  5. struct pid_namespace *ns;//该nr属于哪个pid_namespace
  6. struct hlist_node pid_chain;//所有upid的hash链表 find_pid
  7. }
  8. //一个pid可以属于不同的级别,每一级别又包含一个upid
  9. struct pid
  10. {
  11. atomic_t count;//引用计数
  12. struct hlist_head tasks[PIDTYPE_MAX];//该pid被使用的task链表
  13. struct rcu_head rcu;//互斥访问
  14. int level;//该pid所能到达的最大深度
  15. struct upid numbers[1];//每一层次(level)的upid
  16. }

一个进程可以在多个命名空间中,但是每个命名空间的local id却不相同,numbers则表示每一层level的upid实例,这里的数组只包含一个元素,如果系统只有一个进程,这是可行的,但如果包含多个进程的话,就需要进行分配更多的空间,这个放在结构体中最后一个元素就是方便扩容。

其中PIDTYPE_MAX定义如下:

  1. enum pid_type
  2. {
  3. PIDTYPE_PID,
  4. PIDTYPE_PGID,
  5. PIDTYPE_SID,
  6. PIDTYPE_MAX
  7. }

线程组id没有包含在内,因为它与thread group leader的pid一样,没有必要放在里面。

一个任务可以包含多个命名空间,task_struct的结构体中显示如下:

  1. struct task_struct {
  2. …...
  3. struct pid_link pids[PIDTYPE_MAX];
  4. …...
  5. }

pid_link就是链接所有的pid:

  1. struct pid_link {
  2. struct hlist_node node;//由于每个task包含多个pid(多个命名空间可见),指向的是自己
  3. struct pid    *pid;
  4. }

而实现upid中的数值nr到pid对象的hash映射如下:

  1. static struct hlist_head *pid_hash;//双向hash链表
  2. pid_hash是一个hlist_head数组,大小根据机器的内存配置,从16到4096,初始化代码如下:
  3. [kernel/pid.c]
  4. void __init pidhash_init(void)
  5. {
  6. int i, pidhash_size;
  7. //#define PAGE_SHIFT 12
  8. unsigned long megabytes = nr_kernel_pages >> (20-PAGE_SHIFT);
  9. pidhash_shift = max(4,fls(megabytes * 4));
  10. pidhash_shift = min(12,pidhash_shift);
  11. //16项到4096项
  12. pidhash_size = 1 << pidhash_shift;
  13. ….....
  14. pid_hash = alloc_bootmem(pidhash_size*sizeof(*(pid_hash)));
  15. if(!pid_hash)
  16. panic(“Could not alloc pidhash”);
  17. for(i=0;i< pidhash_size;++i)
  18. INIT_HLIST_HEAD(&pid_hash[i]);
  19. }

当struct pid已经分配,需要链接到具体task中时,执行代码如下:

  1. int fastcall attach_pid(struct task_struct *task,enum pid_type type,
  2. struct pid *pid)
  3. {
  4. struct pid_link *link;
  5. //从指定的task[type]中取出函数指针
  6. link = &task->pids[type];
  7. //将task赋值pid
  8. link ->pid = pid;
  9. //将该task放入pid的tasks链表中
  10. hlist_add_head_rcu(&link->node,&pid->tasks[type]);
  11. return 0;
  12. }

内核提供了大量的函数来进行pid到task之间的映射管理及维护,主要包括两部分:

1.指定局部数值pid,对应的命名空间,查找到对应的进程。

这种情况主要有以下几种情况:

1.获取与pid相关联的task,task_pid,task_tgid,task_pgrp,task_session用于不同类型的ID。

  1. //每一task有四种不同类型的type,每一种type中包含有一个pid
  2. static inline struct pid *task_pid(struct task_struct *task)
  3. {
  4. return task->pids[PIDTYPE_PID].pid;
  5. }

其它的也与上面的类似。

2.通过pid和命名空间namespace来查找进程的pid_t

  1. [kernel/pid.c]
  2. pid_t pid_nr_ns(struct pid *pid,struct pid_namespace *ns)
  3. {
  4. struct upid *upid;
  5. pid_t nr = 0;
  6. //指定的命名空间深度必须比pid高
  7. if(pid && ns->level <= pid->level)
  8. {
  9. upid = & pid->numbers[pid->level];
  10. if(upid->ns == ns)
  11. nr = upid->nr;
  12. }
  13. return nr;
  14. }

内核还采用了另外的方法来访问进程的pid:

1.pid_vnr从id所属的命名空间中返回局部pid,如:

  1. pid_t task_pid_vnr(struct task_struct*);
  2. pid_t task_tgid_vnr(struct task_struct*);
  3. pid_t task_pgrp_vnr(struct task_struct*);
  4. pid_t task_session_vnr(struct task_struct*);

这些函数都是通过pid_nr_ns来实现的

2.pid_nr从init进程中获取全局pi

这两个函数实际都通过指明level级别(0表示全局)调用了函数pid_nr_ns,它则是通过nr_ns系列函数来进行访问,如:

  1. pid_t task_pid_nr_ns(struct task_struct *,struct pid_namespace *);
  2. pid_t task_tgid_nr_ns(struct task_struct *,struct pid_namespace*);
  3. pid_t task_pgrp_nr_ns(struct task_struct*,struct pid_namespace*);
  4. pid_t task_session_nr_ns(struct task_struct *,struct pid_namespace *);

这些函数也是通过pid_nr_ns来实现的。

另外,还可以通过pid中的数值id-nr和命名空间来获取对应的pid,如下:

  1. struct pid *fastcall find_pid_ns(int nr,struct pid_namespace *ns)
  2. {
  3. struct hlist_node *elem;
  4. struct upid *pnr;
  5. //通过nr进行hash查找到对应的struct upid,pid_hash为全局唯一
  6. hlist_for_each_entry_rcu(pnr,elem,
  7. &pid_hash[pid_hashfn(nr,ns)],pid_chain)
  8. if(pnr->nr == ns)
  9. //通过pid中的成员变量upid来查找对应的struct pid
  10. return container_of(pnr,struct pid,    numbers[ns->level]);
  11. }

2.指定一个进程,id类型,及命名空间,查找到对应的进程pid,如find_task系列函数:

  1. [kernel/pid.c]
  2. struct task_struct *find_task_by_pid_type_ns(int type,int nr,
  3. struct pid_namespace *ns);
  4. struct task_struct *find_task_by_pid(pid_t nr);
  5. struct task_struct *find_task_by_vpid(pid_t vnr);
  6. struct task_struct *find_task_by_pid_ns(pid_t nr,struct namespace *ns);

这些函数都是通过find_task_by_pid_type_ns来实现的

  1. struct task_struct *find_task_by_pid_type_ns(int type,int nr,
  2. struct pid_namspace *ns)
  3. {
  4. return pid_task(find_pid_ns(nr,ns),type);
  5. }

而pid_task实现如下:

  1. struct task_struct *fastcall pid_task(struct pid *pid,enum pid_type type)
  2. {
  3. struct task_struct *result = NULL;
  4. if(pid){
  5. struct hlist_node *first;
  6. //获取指定type的task_struct
  7. first = rcu_dereference(pid->tasks[type].node);
  8. if(first)
  9. //获取first中的pid_link域的pids[(type)].node值
  10. //因为pid_link中node域就是自己所以就直接获取,
  11. //那里指向自己的hlist_head就是为了满足这里的统一
  12. result = hlist_entry(first,struct task_struct,pids[(type)].node)
  13. }
  14. return result;
  15. }

pid的分配

为了记录pid的分配与释放情况,内核使用了一张pid位图,可以从pid位图中的位置来获取对应的pid值,同时将pid值从0改为1,相反释放时只需修改1为0

  1. static int alloc_pidmap(struct pid_namespace *pid_ns)
  2. {
  3. int i,offset,max_scan,pid,last = pid_ns ->last_pid;//上一次
  4. struct pidmap *map;
  5. pid = last + 1;
  6. if(pid >= pid_max)
  7. pid = RESERVED_PIDS;
  8. //从pid中获取具体位数的偏移量,位图即每一位一个pid
  9. offset = pid & BITS_PER_PAGE_MASK;
  10. map = &pid_ns->pidmap[pid/BITS_PER_PAGE];
  11. max_scan= (pid_max + BITS_PER_PAGE – 1)/BITS_PER_PAGE - !offset;
  12. for(i = 0;i<= max;++i)
  13. {
  14. //如果内存页没有分配就分配一项
  15. if(unlikely(!map->page)) {
  16. void *page = kzalloc(PAGE_SIZE,GFP_KERNEL);
  17. spin_lock_irq(&pidmap_lock);
  18. if(map->page) kfree(page);
  19. else map->page = page;
  20. spin_unlock_irq(&pidmap_lock);
  21. if(unlikely(!map->page))
  22. break;
  23. }
  24. if(likely(atomic_read(&map->nr_free))){
  25. do{
  26. //扫描到空位即可
  27. if(!test_and_set_bit(offset,map->page)){
  28. atomic_dec(&map->nr_free);
  29. pid_ns->last_pid = pid;
  30. return pid;
  31. }
  32. offset = find_next_offset(map,offset);
  33. pid = mk_pid(pid_ns,map,offset);
  34. }while(offset < BITS_PER_PAGE && pid<pid_max &&
  35. (i != max_scan || pid<last ||
  36. !((last+1)&&BITS_PER_PAGE_MASK)));
  37. }
  38. if(map < &pid_ns->pidmap[(pid_max-1)/BITS_PER_PAGE]) {
  39. ++map;
  40. offset = 0;
  41. }else {
  42. map = &pid_ns->pidmap[0];
  43. offset = RESERVED_PIDS;
  44. if(unlikely(last = offset)) break;
  45. }
  46. //通过一个扫描来的偏移量,生成一个pid
  47. pid = mk_pid(pid_ns,map,offset);
  48. }
  49. return -1;
  50. }

参考资料

linux2.6.24内核源码

http://lxr.linux.no

professional Linux architecture

understanding linux kernel

转载于:https://www.cnblogs.com/lucelujiaming/p/9480822.html

Linux 命名空间相关推荐

  1. Linux命名空间cgroups简介

    Linux命名空间cgroups 一.cgroups概念 二.cpu子系统 2.1.CFS 2.2.RT 2.3.示例 三.cpuset子系统 四.cpuacct子系统 五.memory子系统 六.b ...

  2. 【笔记】Linux命名空间—实验

    文章目录 实验一.UTS命名空间 实验二.IPC命名空间 实验三.PID命名空间 实验1:PID命名空间创建 实验2:proc文件系统 实验3:嵌套的PID命名空间 实验4:init进程和信号量 实验 ...

  3. linux 命名空间Namespace机制【转】

    原文:http://blog.csdn.net/preterhuman_peak/article/details/40857117 Linux Namespaces机制提供一种资源隔离方案.PID,I ...

  4. 云原生之Linux命名空间和docker容器隔离

    Docker容器隔离 一.根目录RootFs概述 二.Linux Namespace 2.1.进程命名空间 2.1.1.lsns 命令 2.1.2.查看元祖进程命名空间 2.1.3.查看当前用户进程命 ...

  5. Linux环境使用命名空间编写一个简单的容器应用程序:namespace,container,cgroups

    目录 使用命名空间编写一个简单的容器应用程序 创建一个子进程– fork vs clone 具有clone(2)的命名空间 简单示例 - CLONE_NEWPID 隔离网络接口 - CLONE_NEW ...

  6. Linux 网络命名空间

    虚拟网络基础 bridge TAP 设备与 VETH 设备 netns 网络命名空间 虚拟网络创建 iproute2 创建新的网络命名空间 显示所有的虚拟网络命名空间 进入虚拟网络环境 设置虚拟网络环 ...

  7. linux 网络命名空间 Network namespaces

    Linux命名空间是一个相对较新的内核功能,对于实现容器至关重要. 命名空间将全局系统资源包装到一个抽象中,该抽象只会与命名空间中的进程绑定,从而提供资源隔离. 在本文中,我将讨论网络命名空间并展示一 ...

  8. linux中的网络命名空间的使用

    背景 项目中使用了网络命名空间,用来隔离不同空间中的应用. 命名空间的使用,类似虚拟化技术,在同一台物理机上,创建的多个命名空间相互独立,各个空间的进程独立运行,互不干扰. 在此作一总结,学习加深理解 ...

  9. linux磁盘分配最佳实践,构建容器应用的8个最佳实践

    导读 容器是未来在共有云和私有云进行应用开发的主要趋势,但是容器到底是什么,为什么它们成为了一种广受欢迎的部署机制,而且你需要怎样来修改你的应用来为容器化的环境优化它? 容器是未来在共有云和私有云进行 ...

最新文章

  1. iOS开发——XML/JSON数据解析
  2. 在'for'循环中获取索引
  3. 从淘宝第1位程序员, 到阿里合伙人,20多年了,非科班出身的他还在编程,程序员的榜样
  4. Windows下WSL的root密码忘记解决办法
  5. pjproject编译方法与相关错误解决
  6. vue跨域使用websocket_在vue中使用SockJS实现webSocket通信的过程
  7. 面试题:用两个队列实现一个栈
  8. PHP168整站数据库结构说明,PHP168
  9. 如何读取 PEM 文件以获取公钥和私钥
  10. 审计小trick结合
  11. 功能丰富强大的开源HEVC分析软件 “ Gitl HEVC Analyzer ”
  12. MOS在锂电池的核心原理
  13. 统计思维(实例5)——变量之间的关系
  14. 台式计算机怎么开声音,台式电脑没有声音【设置办法】
  15. postgreSQL安装成功后打开pgadmin4出现错误:Fatal error:The pgAdmin 4 server could not be contacted:
  16. LoadRunner技巧之思考时间设置
  17. 1.2 cuda相关常见错误记录(不断更新)
  18. 2021年高考成绩查询湖北状元,2020年湖北高考状元名单资料,湖北高考状元分数学校名单介绍...
  19. Blender 制作柱体骨架
  20. messageBox的入门学习

热门文章

  1. day12【过渡】SpringCloud
  2. mysql 1054 42s22_MySQL 触发器的坑:ERROR 1054 (42S22): Unknown column 'xxx' in 'field list'
  3. python人脸识别库_基于facenet的实时人脸识别系统
  4. 李少华 linux内核,腾讯云CMQ消息队列在Linux环境下的使用
  5. crammd5 php,使用CRAMMD5的SMTP身份验证
  6. 切换计算机用户指,切换用户提示计算机锁定
  7. lamp 配置mysql_LAMP安装配置超详细讲解
  8. vue 3.2 的 script setup 语法
  9. Web Components 入门
  10. 分析reduce()的原理