精华内容
下载资源
问答
  • 记得看《深入Linux内核架构》时,里面有讲到关于命名空间的概念,但是现在却基本上弯光了,唉。所以今天特意自己翻看一下内核4.2代码,整理一下命名空间中关于pid管理的部分,等什么时候有空了,再去翻看那本著作吧...

    记得看《深入Linux内核架构》时,里面有讲到关于命名空间的概念,但是现在却基本上弯光了,唉。所以今天特意自己翻看一下内核4.2代码,整理一下命名空间中关于pid管理的部分,等什么时候有空了,再去翻看那本著作吧(写的比较乱〒_〒)。
    命名空间的概念网上有很多,关于pid管理的话,大致思想应该就是各个task_struct的pid在各自的命名空间里面是唯一的,但是在全局不唯一。
    通过一个函数find_task_by_vpid(pid_t vnr),就可以知道一个pid怎么联系到自己命名空间里面的task_struct结构体:

    参数pid_t定义为(其实就是int):

    27 typedef int             __kernel_pid_t;
    21 typedef __kernel_pid_t          pid_t;

    那么这个pid_t是放在哪的呢?

    1344 struct task_struct {
    ......
    1444         pid_t pid;
    ......
    1474         struct pid_link pids[PIDTYPE_MAX];
    ......
    }

    其中,struct pid_link是一个很重要的结构,定义为:

    69 struct pid_link
     70 {
     71         struct hlist_node node;
     72         struct pid *pid;
     73 };

    struct pid_link的结构体成员struct pid定义则为:

     57 struct pid
     58 {
     59         atomic_t count;
     60         unsigned int level;
     61         /* lists of tasks that use this pid */
     62         struct hlist_head tasks[PIDTYPE_MAX];
     63         struct rcu_head rcu;
     64         struct upid numbers[1];
     65 }

    该结构体成员struct upid定义为:

     50 struct upid {
     51         /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
     52         int nr;
     53         struct pid_namespace *ns;
     54         struct hlist_node pid_chain;
     55 };

    struct upid包含了该task_struct所属的命名空间结构体struct pid_namespace。
    现在看看find_task_by_vpid具体实现:

    460 struct task_struct *find_task_by_vpid(pid_t vnr)
    461 {
    462         return find_task_by_pid_ns(vnr, task_active_pid_ns(current));
    463 }

    其中task_active_pid_ns(current)是找到当前进程所属的命名空间:

    546 struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)
    547 {
    548         return ns_of_pid(task_pid(tsk));
    549 }
    /*调用的函数*/
    1833 static inline struct pid *task_pid(struct task_struct *task)
    1834 {
    1835         return task->pids[PIDTYPE_PID].pid;
    1836 }
    134 static inline struct pid_namespace *ns_of_pid(struct pid *pid)
    135 {
    136         struct pid_namespace *ns = NULL;
    137         if (pid)
    138                 ns = pid->numbers[pid->level].ns;
    139         return ns;
    140 }

    从上面的函数可知:命名空间的结构体信息就放在task->pids[PIDTYPE_PID].pid->numbers[pid->level].ns。
    找到命名空间就调用函数find_task_by_pid_ns()在该命名空间里面查找该pid的值:

    452 struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
    453 {
    /*判读调用该函数时是否持有rcu_read_lock锁*/
    454         rcu_lockdep_assert(rcu_read_lock_held(),
    455                            "find_task_by_pid_ns() needs rcu_read_lock()"
    456                            " protection");
    457         return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
    458 }
    /*调用函数*/
    /*该函数根据传入的nr值和ns值,找到其所属的struct pid*/
    366 struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
    367 {
    368         struct upid *pnr;
    369 
    /*遍历pid_hash链表,找到一个struct upid,其nr为传入的pid值,ns为传入的命名空间*/
    370         hlist_for_each_entry_rcu(pnr,
    371                         &pid_hash[pid_hashfn(nr, ns)], pid_chain)
    372                 if (pnr->nr == nr && pnr->ns == ns)
    /*找到对应的struct upid后,因为它是在pid的numbers[ns->level]中,
    所以使用container_of宏可以找到所属的struct pid结构体*/
    373                         return container_of(pnr, struct pid,
    374                                         numbers[ns->level]);
    375 
    376         return NULL;
    377 }
    /*根据前面找到的strcut pid得到task_strcut结构体*/
    435 struct task_struct *pid_task(struct pid *pid, enum pid_type type)
    436 {
    437         struct task_struct *result = NULL;
    438         if (pid) {
    439                 struct hlist_node *first;
    /*取hash链表pid->tasks[type]的第一个元素*/
    /*这里有些疑惑,如果链表元素不止一个,那返回值不是不唯一了么???
    我个人觉得和当前进程的命名空间的struct pid的level有关,因为一个任务
    可以处于不同命名空间中,但是其命名空间结构体信息和nr值是不一样,但是任
    务的结构体信息是一样的,对于这些结构体,都放在pid->tasks[type]链表
    中,因为取任何一个task_struct都是符合要求的。感觉如果只取第一个元素
    的话,可以用别的task_struct先放在链表首部,实现暂时取代原来那个任务
    的作用,不知道是不是有这种做法。什么时候看书了,明白了,再来写吧。*/
    
    440                 first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
    441                                               lockdep_tasklist_lock_is_held());
    /*这边可以娶到task_struct,是因为attch_pid函数将其挂上去的*/
    442                 if (first)
    443                         result = hlist_entry(first, struct task_struct, pids[(type)].node);
    444         }
    445         return result;
    446 }
    /*attach_pid函数如下*/
    389 void attach_pid(struct task_struct *task, enum pid_type type)
    390 {
    391         struct pid_link *link = &task->pids[type];
    392         hlist_add_head_rcu(&link->node, &link->pid->tasks[type]);
    393 }
    /*关于一个任务在不同的命名空间中都有对应的信息的代码片段如下*/
    297 struct pid *alloc_pid(struct pid_namespace *ns)
    {
    ......
    /从当前命名空间往父命名空间,循环分配nr和ns*/
    312         for (i = ns->level; i >= 0; i--) {
    313                 nr = alloc_pidmap(tmp);
    314                 if (IS_ERR_VALUE(nr)) {
    315                         retval = nr;
    316                         goto out_free;
    317                 }
    318 
    319                 pid->numbers[i].nr = nr;
    320                 pid->numbers[i].ns = tmp;
    321                 tmp = tmp->parent;
    322         }
    ......
    /*这里把upid加入hash链表pid_hash[]*/
    338         for ( ; upid >= pid->numbers; --upid) {
    339                 hlist_add_head_rcu(&upid->pid_chain,
    340                                 &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
    341                 upid->ns->nr_hashed++;
    342         
    }
    ......
    }

    一些结构示意图(摘自《深入Linux内核架构》):
    这里写图片描述
    这里写图片描述

    展开全文
  • linux命名空间(namespace)学习(三)

    千次阅读 2018-12-08 11:09:40
    LinuxPID命名空间学习 通过对于前两节的学习我们知道Linux内核使用task_struct结构来表示和管理进程,这个数据结构里面存放了很多有关于PID如何管理的数据,可以这么说,Linux内核所有有关进程管理的数据结构都和此...

    LinuxPID命名空间学习

    通过对于前两节的学习我们知道Linux内核使用task_struct结构来表示和管理进程,这个数据结构里面存放了很多有关于PID如何管理的数据,可以这么说,Linux内核所有有关进程管理的数据结构都和此数据结构有关。该数据结构存放在include/linux/sched.h头文件里,并且这个数据结构比较大,就不一一列举了。实际上一一列举也没有什么意思,因这个数据结构过于庞大导致列举出来连笔者都感到头大----可以举出这么一个例子:这个数据结构占用内核1.7K的空间,可想有多么庞大。但是不需要担心,可以把此数据结构分块划分的话,就简单很多了。话不多说我会根据如下来结构体系来介绍:

     1. 进程类型
     2. PID的命名空间;
     3.管理 PID有关的数据结构
    

    进程类型

    进程如何划分呢?我们可以把进程划分为如下四种类型:

     1.普通PID:
     	这是Linux对于每一个进程都使用的划分,每一个进程都分配给一个PID,每一个PID都对应一个task_struct,每一个task_struct对应着相应的命名空间,和PID类型(先做了解)。
     2.TGID:
     	线程组ID,这个是线程的定义,这个定义在clone时候使用CLONE_THREAD函数调用的时候进行:在一个进程中,如果以CLONE_THREAD标志来调用clone建立的进程就是该进程的一个线程,它们处于一个线程组,该线程组的ID叫做TGID。处于相同的线程组中的所有进程都有相同的TGID;线程组组长的TGID与其PID相同;一个进程没有使用线程,则其TGID与PID也相同。
     3.PGID:
     	另外,独立的进程可以组成进程组(使用setpgrp系统调用),进程组可以简化向所有组内进程发送信号的操作,例如用管道连接的进程处在同一进程组内。进程组ID叫做PGID,进程组内的所有进程都有相同的PGID,等于该组组长的PID。
     4.SID:
     	几个进程组可以合并成一个会话组(使用setsid系统调用),可以用于终端程序设计。会话组中所有进程都有相同的SID。
    

    PID命名空间

    命名空间是为操作系统层面的虚拟化机制提供支撑,目前实现的有六种不同的命名空间,分别为mount命名空间、UTS命名空间、IPC命名空间、用户命名空间、PID命名空间、网络命名空间。命名空间简单来说提供的是对全局资源的一种抽象,将资源放到不同的容器中(不同的命名空间),各容器彼此隔离。命名空间有的还有层次关系,如PID命名空间,图1 为命名空间的层次关系图。

    图1 命名空间的层次关系
    在上图有四个命名空间,一个父命名空间衍生了两个子命名空间,其中的一个子命名空间又衍生了一个子命名空间。以PID命名空间为例,由于各个命名空间彼此隔离,所以每个命名空间都可以有 PID 号为 1 的进程;但又由于命名空间的层次性,父命名空间是知道子命名空间的存在,因此子命名空间要映射到父命名空间中去,因此上图中 level 1 中两个子命名空间的六个进程分别映射到其父命名空间的PID 号5~10。

    命名空间增大了 PID 管理的复杂性,对于某些进程可能有多个PID——在其自身命名空间的PID以及其父命名空间的PID,凡能看到该进程的命名空间都会为其分配一个PID。因此就有:

    全局ID:

    在内核本身和初始命名空间中具有唯一的ID表示唯一的进程。内核中的init进程就是初始命名空间。系统为每一个进程分配了ID号来标示不同的进程,无论是在一级命名空间还是在二级命名空间中的进程,都在初始命名空间进程中都申请了一个ID号用于管理。这样在父命名空间中就可以看到子命名空间中的进程了。
    

    局部ID

    也可以说是子命名空间中看到的ID号,这个ID只能在子命名空间中有用,在父命名空间中没有作用。
    

    进程ID管理数据结构

    Linux 内核在设计管理ID的数据结构时,要充分考虑以下因素:

    1.如何快速地根据进程的 task_struct、ID类型、命名空间找到局部ID
    2.如何快速地根据局部ID、命名空间、ID类型找到对应进程的 task_struct
    3.如何快速地给新进程在可见的命名空间内分配一个唯一的 PID
    如果将所有因素考虑到一起,将会很复杂,下面将会由简到繁设计该结构。
    介绍的结构如下:

     1. 一个PID只对应一个task_struct结构;
     2. 加入TGID/PGID/SID管理的PID管理;
     3. 加入命名空间的PID管理
    

    一个PID对应一个task_struct结构

    如果不考虑一个进程对应的TGID/PGID/SID,也不考虑一个进程对应的命名空间设计,我们可以对于进程的数据结构进行如下设计:
    一个进程对应了一个task_struct结构,其中每一个PID 中的nr表示PID号即为进程号,PID 结构中的pid_chain代表PID散列表的节点。
    进程的设计一

    上述设计核心思想如下:
    1.一个task_struct 中存放着pid_link结构体,指向struct pid结构。
    2.PID结构里面存放着PID 号(即为nr),也存放着指向pid_link的指针和PID散列表的节点的节点。
    3.每一个PID的申请和释放都是通过pid_hash(PID散列表)和pid_map来进行管理的。
    4.对于每一个PID的查找也是通过pid_hash来管理的;
    数据结构如下:

    struct task_struct {
        //...
        struct pid_link pids;
        //...
    };
    
    struct pid_link {
        struct hlist_node node;  
        struct pid *pid;          
    };
    
    struct pid {
        struct hlist_head tasks;        //指回 pid_link 的 node
        int nr;                       //PID
        struct hlist_node pid_chain;    //pid hash 散列表结点
    };
    

    上述两个主要的数据结构还有两个没有介绍:
    pid_hash[]:是PID的hash散列表,用于管理和查找pid结构,主要通过pid号来进行关键索引找到PID结构。然后找到task_struct结构,主要查找有一下四步:

     1. 通过PID号,索引pid_hash[],找到struct pid的pid_chain结构;
     2. 通过pid_chain找到struct pid结构;
     3. struct pid结构中有tasks指向,task_struct->plink.node;
     4. 通过container_of查找到struct task_struct结构;
    

    pid_map:PID位图表示,主要用于申请和释放未使用的PID号,这样不用遍历pid_hash结构也能够找到未使用的PID号。

    加入TGID/PGID/SID管理的PID管理

    加入TGID/PGID/SID管理的PID管理稍微比较复杂一点,在上述基础上我们知道struct pid结构可以索引到task_struct结构,但是如何加入TGID/PGID/SID的管理呢?可以从如下角度考虑下问题:

     1. 如何通过进程本身的task_struct 索引到自身的所属TGID/PGID/SID的struct PID 结构?
     2. 一个线程组或者一个进程组或者一个组里面的线程主ID或者进程组ID或者组ID怎么索引到其下所有的线程,进程,组中所有的进程?
    

    以上两点的解决方案如下图:

    加入TGID/PGID/SID管理的PID管理ruc
    对于上述的两点疑问可以做如下解答:

     1. 在task_struct结构里面增加struct pid_link数组到至少四个,第一个索引自身进程的struct pid结构,第二个索引线程组ID,第三个索引PGID,第四个索引SID。可以通过一次索引查询到自身task_struct的PID,TGID,PGID,SID结构的task_struct结构。
     2. 每一个自身的进程的PID结构把tasks数组增加至至少四个,第一个索引自身的task_struct ,第二个索引以自己为主进程的下面挂载多少个线程的task_struct,第三个索引以自己为主进程下面挂载多少个gid..... 其中hlist_head tasks[index]以struct task_struct->pid_link[index].hlist_node为节点.
    

    数据结构设计如下:

    enum pid_type
    {
        PIDTYPE_PID,
        PIDTYPE_PGID,
        PIDTYPE_SID,
        PIDTYPE_MAX
    };
    
    struct task_struct {
        //...
        pid_t pid;     //PID
        pid_t tgid;    //thread group id
    
        struct task_struct *group_leader;   // threadgroup leader
    
        struct pid_link pids[PIDTYPE_MAX];
    
        //...
    };
    
    struct pid_link {
        struct hlist_node node;  
        struct pid *pid;          
    };
    
    struct pid {
        struct hlist_head tasks[PIDTYPE_MAX];
        int nr;                         //PID
        struct hlist_node pid_chain;    // pid hash 散列表结点
    };
    

    **

    增加PID命名空间的PID表示结构

    **
    再回到PID命名空间的讨论范畴,通过本文中对于PID命名空间的介绍我们知道每一个命名空间中其PID分配是相对独立的,在父命名空间中可以看到子命名空间中的进程,父命名空间中看到的进程号是父命名空间分配的,子命名空间中看到的进程号是子命名空间分配的。
    可能会有一下疑问:

     1. 子命名空间中的进程怎么会索引到父命名空间中?
     2. 子命名空间中怎么会感知父命名空间的存在呢?
     3. 父命名空间如何会知道子命名空间中的进程呢?
     4. 父命名空间中的进程如何给子命名空间中的进程分配PID呢?
    

    为了回答以上问题,我们先从第四个问题开始讨论:
    为了使父命名空间给子命名空间中的进程分配进程号,Linux内核在命名空间设计中把pid_map结构放入到命名空间中结构,这样每一个父命名空间中的结构就能够给子命名空间中的进程分配ID了,如下所示:

    struct pid_namespace {
            struct kref kref;
            struct pidmap pidmap[PIDMAP_ENTRIES];
            int last_pid;
            struct task_struct *child_reaper;
            struct kmem_cache *pid_cachep;
            unsigned int level;
            struct pid_namespace *parent;
    #ifdef CONFIG_PROC_FS
            struct vfsmount *proc_mnt;
    #endif
    #ifdef CONFIG_BSD_PROCESS_ACCT
            struct bsd_acct_struct *bacct;
    #endif
    };
    以上数据结构还回答了我们第二个问题:子命名空间怎么感知父命名空间的存在
    

    父命名空间如何会知道子命名空间中的进程呢?

    我们知道同一个父命名空间中的进程ID和子命名空间中的ID互相不影响,而且敷命名空间的和子命名空间是不相同的,这样我们就可以在设计数据结构的时候把两者设计在一起,只要找到设计的数据结构就可以通过pid_hash得到struct upid结构。
    上面提到的pid_hash索引的功能有所变化,之前提交通过pid_hash表通过PID值可以索引到struct pid结构,但是现在我们通过hash表先索引到struct upid结构,再通过upid结构和namespace的level值所引到pid结构。进一步所引到task_struct结构
    如下所示:

    struct upid {
            /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
            int nr;
            struct pid_namespace *ns;
            struct hlist_node pid_chain;
    };
    

    以下图片会帮助我们回答以上所有问题,举例来说,在level 2 的某个命名空间上新建了一个进程,分配给它的 pid 为45,映射到 level 1 的命名空间,分配给它的 pid 为 134;再映射到 level 0 的命名空间,分配给它的 pid 为289,对于这样的例子,如图4所示为其表示:在这里插入图片描述
    图中关于如果分配唯一的 PID 没有画出,但也是比较简单,与前面两种情形不同的是,这里分配唯一的 PID 是有命名空间的容器的,在PID命名空间内必须唯一,但各个命名空间之间不需要唯一。
    数据结构设计如下:

    struct pid
    {
        unsigned int level;  
        /* lists of tasks that use this pid */
        struct hlist_head tasks[PIDTYPE_MAX];
        struct upid numbers[1];
    };
    
    struct upid {
        int nr;
        struct pid_namespace *ns;
        struct hlist_node pid_chain;
    };
    
    

    以上摘自:https://www.cnblogs.com/hazir/p/linux_kernel_pid.html

    展开全文
  • Linux 网络命名空间

    千次阅读 2017-04-09 09:59:58
    网络命名空间 虚拟网络创建 iproute2 创建新的网络命名空间 显示所有的虚拟网络命名空间 进入虚拟网络环境 设置虚拟网络环境net0的veth0设备处于激活状态 为虚拟网络环境net0的veth0设备增加IP地址 连接两个网络环境...

    虚拟网络基础

    和磁盘设备类似,Linux 用户想要使用网络功能,不能通过直接操作硬件完成,而需要直接或间接的操作一个 Linux 为我们抽象出来的设备,既通用的Linux网络设备来完成。一个常见的情况是,系统里装有一个硬件网卡,Linux 会在系统里为其生成一个网络设备实例,如eth0,用户需要对eth0发出命令以配置或使用它了。更多的硬件会带来更多的设备实例,虚拟的硬件也会带来更多的设备实例。
    随着网络技术,虚拟化技术的发展,更多的高级网络设备被加入了到了Linux中,使得情况变得更加复杂。
    在本节中,将一一分析在虚拟化技术中经常使用的几种 Linux 网络设备抽象类型:Bridge、802.1.q VLAN device、VETH、TAP,详细解释如何用它们配合 Linux 中的 Route table、IP table简单的创建出本地虚拟网络。
    Linux的网络虚拟化是LXC项目中的一个子项目,LXC包括文件系统虚拟化,进程空间虚拟化,用户虚拟化,网络虚拟化,等等。

    bridge

    Bridge(桥)是 Linux 上用来做 TCP/IP 二层协议交换的设备,与现实世界中的交换机功能相似。Bridge 设备实例可以和 Linux 上其他网络设备实例连接,既attach一个从设备,类似于在现实世界中的交换机和一个用户终端之间连接一根网线。当有数据到达时,Bridge会根据报文中的MAC信息进行广播、转发、丢弃处理。
    图 1.Bridge 设备工作过程
    Bridge 设备工作过程
    如图所示,Bridge 的功能主要在内核里实现。当一个从设备被 attach 到 Bridge 上时,相当于现实世界里交换机的端口被插入了一根连有终端的网线。这时在内核程序里,netdev_rx_handler_register()被调用,注册一个用于接受数据的回调函数。以后每当这个从设备收到数据时都会调用这个函数可以把数据转发到 Bridge 上。当 Bridge 接收到此数据时,br_handle_frame()被调用,进行一个和现实世界中的交换机类似的处理过程:
    1. 判断包的类别(广播/单点)
    1. 查找内部 MAC 端口映射表
    1. 定位目标端口号
    1. 将数据转发到目标端口或丢弃
    1. 自动更新内部 MAC 端口映射表以自我学习

    Bridge和现实世界中的二层交换机有一个==区别==,图中左侧画出了这种情况:数据被直接发到Bridge上,而不是从一个端口接受。这种情况可以看做Bridge自己有一个MAC可以主动发送报文,或者说Bridge自带了一个隐藏端口和寄主 Linux 系统自动连接,Linux 上的程序可以直接从这个端口向 Bridge 上的其他端口发数据。所以当一个 Bridge 拥有一个网络设备时,如 bridge0 加入了 eth0 时,实际上 bridge0 拥有两个有效 MAC 地址,一个是 bridge0 的,一个是 eth0 的,他们之间可以通讯。
    由此带来一个有意思的事情是,Bridge 可以设置 IP 地址。通常来说 IP 地址是三层协议的内容,不应该出现在二层设备 Bridge 上。但是 Linux 里 Bridge 是通用网络设备抽象的一种,只要是网络设备就能够设定 IP 地址。当一个 bridge0 拥有 IP 后,Linux 便可以通过路由表或者IP表规则在三层定位bridge0,此时相当于Linux拥有了另外一个隐藏的虚拟网卡和 Bridge 的隐藏端口相连,这个网卡就是名为bridge0的通用网络设备,IP可以看成是这个网卡的。当有符合此 IP 的数据到达 bridge0 时,内核协议栈认为收到了一包目标为本机的数据,此时应用程序可以通过 Socket接收到它。
    一个更好的对比例子是==现实世界中的带路由的交换机设备==,它也拥有一个隐藏的 MAC 地址,供设备中的三层协议处理程序和管理程序使用。设备里的三层协议处理程序,对应名为 bridge0 的通用网络设备的三层协议处理程序,即寄主Linux系统内核协议栈程序。设备里的管理程序,对应bridge0寄主Linux系统里的应用程序。
    Bridge 的实现当前有一个==限制==:当一个设备被 attach 到 Bridge 上时,那个设备的 IP 会变的无效,Linux 不再使用那个 IP 在三层接受数据。举例如下:如果 eth0 本来的 IP 是 192.168.1.2,此时如果收到一个目标地址是 192.168.1.2 的数据,Linux 的应用程序能通过 Socket 操作接受到它。而当 eth0 被 attach 到一个 bridge0 时,尽管 eth0 的 IP 还在,但应用程序是无法接受到上述数据的。此时应该把 IP 192.168.1.2 赋予 bridge0。
    另外需要注意的是==数据流的方向==。对于一个被attach到Bridge上的设备来说,只有它收到数据时,此包数据才会被转发到Bridge上,进而完成查表广播等后续操作。当请求是发送类型时,数据是不会被转发到 Bridge 上的,它会寻找下一个发送出口。用户在配置网络时经常忽略这一点从而造成网络故障。

    TAP 设备与 VETH 设备

    TUN/TAP 设备是一种让用户态程序向内核协议栈注入数据的设备,一个工作在三层,一个工作在二层,使用较多的是 TAP 设备。VETH设备出现较早,它的作用是反转通讯数据的方向,需要发送的数据会被转换成需要收到的数据重新送入内核网络层进行处理,从而间接的完成数据的注入。
    图 2 .TAP 设备和 VETH 设备工作过程

    当一个TAP设备被创建时,在Linux设备文件目录下将会生成一个对应char设备,用户程序可以像打开普通文件一样打开这个文件进行读写。当执行 write()操作时,数据进入 TAP 设备,此时对于 Linux 网络层来说,相当于 TAP 设备收到了一包数据,请求内核接受它,如同普通的物理网卡从外界收到一包数据一样,不同的是其实数据来自Linux上的一个用户程序。Linux收到此数据后将根据网络配置进行后续处理,从而完成了用户程序向Linux内核网络层注入数据的功能。当用户程序执行read()请求时,相当于向内核查询 TAP 设备上是否有需要被发送出去的数据,有的话取出到用户程序里,完成 TAP 设备的发送数据功能。
    针对 TAP 设备的一个形象的比喻是:使用 TAP 设备的应用程序相当于另外一台计算机,TAP 设备是本机的一个网卡,他们之间相互连接。应用程序通过 read()/write()操作,和本机网络核心进行通讯。

    VETH 设备总是成对出现,送到一端请求发送的数据总是从另一端以请求接受的形式出现。该设备不能被用户程序直接操作,但使用起来比较简单。创建并配置正确后,向其一端输入数据,VETH 会改变数据的方向并将其送入内核网络核心,完成数据的注入。在另一端能读到此数据。

    netns

    netns是在linux中提供网络虚拟化的一个项目,使用netns网络空间虚拟化可以在本地虚拟化出多个网络环境,目前netns在lxc容器中被用来为容器提供网络。
    使用netns创建的网络空间独立于当前系统的网络空间,其中的网络设备以及iptables规则等都是独立的,就好像进入了另外一个网络一样。不同网络命名空间中的设备是不同的,之间不能互相直接通讯。

    网络命名空间

    在 Linux 中,网络名字空间可以被认为是隔离的拥有单独网络栈(网卡、路由转发表、iptables)的环境。网络名字空间经常用来隔离网络设备和服务,只有拥有同样网络名字空间的设备,才能看到彼此。

    虚拟网络创建

    iproute2

    iproute2 is usually shipped in a package called iproute or iproute2 and consists of several tools, of which the most important are ip and tc. ip controls IPv4 and IPv6 configuration and tc stands for traffic control. Both tools print detailed usage messages and are accompanied by a set of manpages.

    创建新的网络命名空间

    使用命令

    $ ip netns add net0

    可以创建一个完全隔离的新网络环境,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables,等等。总之,与网络有关的组件都是独立的。

    ip命令需要root权限的,但是由于本文大量使用ip命令,于是笔者给ip命令添加了capability,使普通用户也能使用ip命令

    显示所有的虚拟网络命名空间

    ip netns list

    或者

    ls /var/run/netns/

    使用命令

    $ ip netns list
    net0

    可以看到我们刚才创建的网络环境

    通常情况下,一般我们用 ip netns add 添加新的 netns,然后我们可以用 ip netns list 查看所有的 netns。但有的时候进程的 netns 却并没有显式导出,ip netns list 无法列出它,比如 docker 的 container的网络空间。怎么办呢?答案是做个符号链接就可以了。

    ln -sf /proc/<pid>/ns/net /var/run/netns/$ns

    简要说明一下这个命令:每个进程的网络命名空间都是通过 proc 文件系统导出来了的,位于 /proc//ns/net (这个文件不可读,它只是相当于一个访问点);而 ip netns list 命令是从 /var/run/netns 这个路径读取netns列表的,因此直接将进程的命名空间链接到 /var/run/netns 目录下就可以了。

    进入虚拟网络环境

    使用命令

    $ ip netns exec net0 `command`

    我们可以在 net0 虚拟环境中运行任何命令

    $ ip netns exec net0 bash
    $ ip ad
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

    这样我们可以在新的网络环境中打开一个shell,可以看到,新的网络环境里面只有一个lo设备,并且这个lo设备与外面的lo设备是不同的,之间不能互相通讯。

    设置虚拟网络环境net0的veth0设备处于激活状态

    ip netns exec net0 ip link set veth0 up

    为虚拟网络环境net0的veth0设备增加IP地址

    ip netns exec net0 ip address add 10.0.1.1/24 dev veth0

    连接两个网络环境

    新的网络环境里面没有任何网络设备,并且也无法和外部通讯,就是一个孤岛,通过下面介绍的这个方法可以把两个网络环境连起来,简单的说,就是在两个网络环境之间拉一根网线

    $ ip netns add net1

    先创建另一个网络环境net1,我们的目标是把net0与net1连起来

    $ ip link add type veth
    $ ip ad # address, show protocol (IP or IPv6) address on a device.
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    81: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
        link/ether 12:39:09:81:3a:dd brd ff:ff:ff:ff:ff:ff
    82: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
        link/ether 32:4f:fd:cc:79:1b brd ff:ff:ff:ff:ff:ff

    这里创建连一对veth虚拟网卡,类似pipe,发给veth0的数据包veth1那边会收到,发给veth1的数据包veth0会收到。就相当于给机器安装了两个网卡,并且之间用网线连接起来了

    $ ip link set veth0 netns net0
    $ ip link set veth1 netns net1

    这两条命令的意思就是把veth0移动到net0环境里面,把veth1移动到net1环境里面,我们看看结果

    $ ip ad
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    $ ip netns exec net0 ip ad
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    81: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
        link/ether 12:39:09:81:3a:dd brd ff:ff:ff:ff:ff:ff
    $ ip netns exec net1 ip ad
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    82: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
        link/ether 32:4f:fd:cc:79:1b brd ff:ff:ff:ff:ff:ff

    veth0 veth1已经在我们的环境里面消失了,并且分别出现在net0与net1里面。下面我们简单测试一下net0与net1的联通性

    $ ip netns exec net0 ip link set veth0 up
    $ ip netns exec net0 ip address add 10.0.1.1/24 dev veth0
    $ ip netns exec net1 ip link set veth1 up
    $ ip netns exec net1 ip address add 10.0.1.2/24 dev veth1

    分别配置好两个设备,然后用ping测试一下联通性:

    $ ip netns exec net0 ping -c 3 10.0.1.2
    PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
    64 bytes from 10.0.1.2: icmp_req=1 ttl=64 time=0.101 ms
    64 bytes from 10.0.1.2: icmp_req=2 ttl=64 time=0.057 ms
    64 bytes from 10.0.1.2: icmp_req=3 ttl=64 time=0.048 ms
    
    --- 10.0.1.2 ping statistics ---
    3 packets transmitted, 3 received, 0% packet loss, time 1999ms
    rtt min/avg/max/mdev = 0.048/0.068/0.101/0.025 ms

    实践:一个稍微复杂的网络环境

    graph TD
    A[bridge] -->|10.0.1.1| B[net0]
    A[bridge] -->|10.0.1.2| C[net1]

    创建虚拟网络环境并且连接网线

    ip netns add net0
    ip netns add net1
    ip netns add bridge
    
    ip link add type veth
    ip link set dev veth0 name net0-bridge netns net0
    ip link set dev veth1 name bridge-net0 netns bridge
    
    ip link add type veth
    ip link set dev veth0 name net1-bridge netns net1
    ip link set dev veth1 name bridge-net1 netns bridge

    在bridge中创建并且设置br设备

    ip netns exec bridge brctl addbr br
    ip netns exec bridge ip link set dev br up
    ip netns exec bridge ip link set dev bridge-net0 up
    ip netns exec bridge ip link set dev bridge-net1 up
    ip netns exec bridge brctl addif br bridge-net0
    ip netns exec bridge brctl addif br bridge-net1

    然后配置两个虚拟环境的网卡

    ip netns exec net0 ip link set dev net0-bridge up
    ip netns exec net0 ip address add 10.0.1.1/24 dev net0-bridge
    
    ip netns exec net1 ip link set dev net1-bridge up
    ip netns exec net1 ip address add 10.0.1.2/24 dev net1-bridge

    测试

    $ ip netns exec net0 ping -c 3 10.0.1.2
    PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
    64 bytes from 10.0.1.2: icmp_req=1 ttl=64 time=0.121 ms
    64 bytes from 10.0.1.2: icmp_req=2 ttl=64 time=0.072 ms
    64 bytes from 10.0.1.2: icmp_req=3 ttl=64 time=0.069 ms
    
    --- 10.0.1.2 ping statistics ---
    3 packets transmitted, 3 received, 0% packet loss, time 1999ms
    rtt min/avg/max/mdev = 0.069/0.087/0.121/0.025 ms

    配置lldpd检查线路链接情况

    随着虚拟网络环境增加,环境中网卡数量也在不断增加,经常会忘记环境中哪些网卡连接到哪里,通过 Link Layer Discovery Protocol,我们可以清楚看到每个网卡连接到了哪些环境中的哪个网卡。

    github 上有一个 lldp 在 linux 下的开源实现 implementation of IEEE 802.1ab (LLDP),通过在每个环境中起一个 lldp daemon,我们就可以实时查看每个网卡的连接情况。

    Bridge 上 lldp 的数据

    $ lldpcli show neighbors
    
    LLDP neighbors:
    
    Interface:    bridge-net0, via: LLDP, RID: 2, Time: 0 day, 00:06:53
      Chassis:
        ChassisID:    mac 82:be:2a:ec:70:69
        SysName:      localhost
        SysDescr:     net0
        Capability:   Bridge, off
        Capability:   Router, off
        Capability:   Wlan, off
      Port:
        PortID:       mac 82:be:2a:ec:70:69
        PortDescr:    net0-bridge
    
    Interface:    bridge-net1, via: LLDP, RID: 1, Time: 0 day, 00:06:53
      Chassis:
        ChassisID:    mac b2:34:28:b1:be:49
        SysName:      localhost
        SysDescr:     net1
        Capability:   Bridge, off
        Capability:   Router, off
        Capability:   Wlan, off
      Port:
        PortID:       mac b2:34:28:b1:be:49
        PortDescr:    net1-bridge

    参考文献

    1. Linux容器
    2. 网络虚拟化技术(一): Linux网络虚拟化
    3. Linux 上的基础网络设备详解-抽象网络设备的原理及使用
    4. Linux命名空间学习教程(一) UTS
    5. Linux命名空间学习教程(二) IPC
    6. Linux命名空间学习教程(三) PID
    7. Linux命名空间学习教程(四)NS(FS)
    8. Linux命名空间学习教程(五)NET
    9. iproute2
    10. Introduction to Linux namespaces – Part 5: NET
    展开全文
  • 作为开源Container技术代表的Docker,它跟Linux内核的Namespace和Cgroup两大特性密不可分。物有本末,事有终始。... 在本文中我们将会使用unshare命令来演示Linux内核的pid Namespace是如何动作的。

    作为开源Container技术代表的Docker,它跟Linux内核的Namespace和Cgroup两大特性密不可分。物有本末,事有终始。知所先后,则近道矣。理解Linux的这两大特性将有助于我们更深入的理解Docker。
    在本文中我们将会使用unshare命令来演示Linux内核的pid Namespace是如何动作的。

    Namespace的历史

    Namespace并不是Linux才推出的东西,早在很久之前,Unix上就有类似的东西,而HPUX和Solaris商用的Conatiner更是以前就有推出。而在Linux的2.6之后的版本Namespace就逐步的被加了进来。

    Linux Namespace的6大类型

    项番类型功能说明
    No.1MNT Namespace提供磁盘挂载点和文件系统的隔离能力
    No.2IPC Namespace提供进程间通信的隔离能力
    No.3Net Namespace提供网络隔离能力
    No.4UTS Namespace提供主机名隔离能力
    No.5PID Namespace提供进程隔离能力
    No.6User Namespace提供用户隔离能力

    Ubuntu版本

    root@ubuntu:~# uname -a
    Linux ubuntu 4.4.0-31-generic #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
    root@ubuntu:~#

    事前确认

    确认当前进程PID

    root@ubuntu:~# echo $$
    32968
    root@ubuntu:~#

    确认当前进程的各个namespace

    root@ubuntu:~# ls -l /proc/$$/ns
    total 0
    lrwxrwxrwx 1 root root 0 Sep 15 10:23 cgroup -> cgroup:[4026531835]
    lrwxrwxrwx 1 root root 0 Sep 15 10:23 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Sep 15 10:23 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Sep 15 10:23 net -> net:[4026531957]
    lrwxrwxrwx 1 root root 0 Sep 15 10:23 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Sep 15 10:23 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Sep 15 10:23 uts -> uts:[4026531838]
    root@ubuntu:~#

    内容说明

    项番内容说明
    No.1linux会在/proc下创建所对应的进程相关的信息,ns则为Namespace的信息
    No.2$$为当前进程PID,/proc/$$/ns下的Namespace的个数回随着Linux内核的高低不同显示的个数不同,因为Linux所支持的Namespace不是一次到位的
    No.3pid:[4026531836],不同的Namespace都有不同的编号,比如32968的pid的namespace的编号就是4026531836
    No.4关于Cgroup会单独在后面进行说明

    事前确认

    确认当前进程PID

    root@ubuntu:~# echo $$
    32968
    root@ubuntu:~#

    使用unshare隔离pid namespace

    root@ubuntu:~# echo $$
    32968
    root@ubuntu:~# unshare --fork --pid /bin/bash
    root@ubuntu:~#

    好像没有任何变化,其实这个已经不是刚才我们的32968进程了

    root@ubuntu:~# echo $$
    1
    root@ubuntu:~#

    进程号是1,难道进程号1不是init么?在起一个终端确认一下情况吧. 发现init还是好好的在用进程号1在那里存在着。

    admin01@ubuntu:~$ ps -ef |grep init |grep -v grep
    root          1      0  0 Sep14 ?        00:00:04 /sbin/init
    admin01@ubuntu:~$

    再来确认一下,整体的进程关系,从这里可以清晰地看到整体从进程1到启动的这个相对进程号为1的进程其实真正的PID应该是34221
    因为它是32968使用unshare所生成的子进程。

    这里写图片描述

    其他相关

    内容URL
    在CentOS7上使用LXC管理容器http://blog.csdn.net/liumiaocn/article/details/52348219
    如何使用RHEL/CentOS 7安装创建和管理LXC (Linux Containers)http://blog.csdn.net/liumiaocn/article/details/52337479
    展开全文
  • linux 网络命名空间 Network namespaces

    万次阅读 2017-04-19 16:20:01
    Linux命名空间是一个相对较新的内核功能,对于实现容器至关重要。 命名空间将全局系统资源包装到一个抽象中,该抽象只会与命名空间中的进程绑定,从而提供资源隔离。 在本文中,我将讨论网络命名空间并展示一个实际...
  • Linux命名空间概述

    2019-08-22 13:41:47
    Linux命名空间机制提供了一种资源隔离的解决方案。PID,IPC,Network等系统资源不再是全局性的,而是属于特定的Namespace。Linux Namespace机制为实现基于容器的虚拟化技术提供了很好的基础,LXC(Linux containers...
  • linux命名空间(namespace)学习(四)

    千次阅读 2018-12-09 11:03:30
    LinuxPID命名空间的学习 上一篇博客更新了linuxPID命名空间的数据结构,本节讲一下关于PID命名空间的相关操作的函数。会先从需求开始讲起来,慢慢串联成一个线。 回想一下,我们在应用空间中获取到的是一个PID号...
  • linux命名空间

    2018-10-08 15:22:29
    PID: &quot;chroot&quot;进程树(之后的文章会讲到) NS: 挂载点,首次登陆Linux(之后的文章会讲到) NET: 网络访问,包括接口(之后的文章会讲到) USER: 将本地的虚拟 user-id映射到真实的user-id ...
  • linux命名空间

    2014-12-28 23:15:56
    Linux Namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的,而是属于特定的Namespace。每个Namespace里面的资源对其他Namespace都是透明的。要创建新的Namespace,只需要在调用cl
  • linux命名空间UTS 总结

    千次阅读 2017-06-06 01:21:37
    本文介绍linux上域名命名空间的例子和试验结果。
  • linux命名空间(namespace)学习(二)

    千次阅读 2018-12-08 08:29:14
    UTS命名空间,是关于linux主机命名或者内核版本命名的一套命名空间,在此命名空间中用户感知的是一个单独命名的的linux主机名称,注意:仅仅是主机名称,如果没有结合其他命名空间操作的话,用户是感知不到一个单独...
  • 从逻辑上说,网络命名空间是网络栈的副本,有自己的网络设备、路由选择表、邻接表、Netfilter表、网络套接字、网络procfs条目、网络sysfs条目和其他网络资源。网络命名空间可以营造出多个内核网络栈实例的假象。网络...
  • 17 July 2019查看 docker 进程的 piddocker inspect -f {{.State.Pid}} xxxlsnslsns -h-n, --noheadings don't print headings-p, --task print process namespaces-t, --type namespace type (mnt, n...
  • linux命名空间(namespace)学习(一)

    千次阅读 2018-12-05 20:42:53
    关于linux命名空间网络上有很多是关于docker 的,也有关于linux的专门的linux的namespace介绍的,没有专门介绍Linux命名空间的应用的。所以我想先介绍一下linux命名空间的应用,然后再介绍linux内核对于命名空间的...
  • linux内核PID管理--命名空间

    千次阅读 2016-03-08 16:00:40
    PID即进程描述符在linux kernel中的分配和管理比较复杂。 本文分析了其相关数据结构以及函数。 (代码基于v3.0.3) 和PID相关的数据结构有   [cpp] view plaincopy struct pid  {  ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 27,359
精华内容 10,943
关键字:

linuxpid命名空间

linux 订阅