linux命名空间(namespace)学习(四)

7 篇文章 2 订阅
2 篇文章 0 订阅

Linux的PID命名空间的学习

上一篇博客更新了linux的PID命名空间的数据结构,本节讲一下关于PID命名空间的相关操作的函数。会先从需求开始讲起来,慢慢串联成一个线。
回想一下,我们在应用空间中获取到的是一个PID号来管理操作系统的所有PID,但是内核是使用task_struct来管理我们的PID的。现在提出来第一个问题:通过一个PID号怎么索引到task_struct结构的?
再回想一下我们在多进程编程的时候,如果设置多个进程共享同一信号的,如果信号发生在某一共享线程,那么信号需要找到共享这个信号的所有进程所在的组并且发送信号给其他组。这就提出来两个问题:在PID管理中怎么根据一个进程找到所属组的所有进程?内核怎么找到线程的组ID?
另外一种情况是:我们知道,再父命名空间中是可以看到子命名空间的起的进程以及进程号的,父命名空间是如何看到子命名空间的进程的?
最后我们想一下:再启动新进程的时候,是怎么申请新的PID号的?
下面通过回答以上四个问题,我们对于PID命名空间以及PID的申请管理有一个大概的认知。

通过一个PID号怎么索引到task_struct结构?

假设在没有一个命名空间的内核中可以通过pid_hash索引到一个struct pid结构的,再通过struct pid结构我们可以索引到struct task_struct结构。
如果再一个有命名空间的内核中这个应该怎么去处理呢?我们知道命名空间的地址和PID结合可以唯一的表示一个PID。那么我们可以利用这个特性来进行索引:我们通过一个命名空间的地址和PID相加之和来索引一个唯一的pid结构。函数如下:

struct pid *find_vpid(int nr)
{
        return find_pid_ns(nr, current->nsproxy->pid_ns);
}
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
        struct hlist_node *elem;
        struct upid *pnr;

        hlist_for_each_entry_rcu(pnr, elem,
                        &pid_hash[pid_hashfn(nr, ns)], pid_chain)
                if (pnr->nr == nr && pnr->ns == ns)
                        return container_of(pnr, struct pid,
                                        numbers[ns->level]);

        return NULL;
}

以上函数的大体流程如下:通过获取当前进程的pid_namespace来查找要查找的PID号的(为什么要查找当前进程的pid_namespace?这是因为只有对于当前命名空间可见的进程号才是可以查找的,换句话来说是可以hash,在本命名空间中才是可见的)通过find_pid_ns函数来查找pid结构。
find_pid_ns大致的流程:通过遍历pid_hash[]中的某一个元素来查找相应的struct upid结构,然后根据命名空间中level使用container_of找到相应的struct pid 结构的。
如下代码所示:

struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{
        struct task_struct *result = NULL;
        if (pid) { 
                struct hlist_node *first;
                first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
                                              rcu_read_lock_held() ||
                                              lockdep_tasklist_lock_is_held());
                if (first)
                        result = hlist_entry(first, struct task_struct, pids[(type)].node);
        }
        return result;
}

以上函数是一个比较好的接口,通过PID_TYPE来索引相应的task_struct结构。
以下函数则是已知task_struct 来查找相应的struct pid结构:

static inline struct pid *task_pid(struct task_struct *task)
{
        return task->pids[PIDTYPE_PID].pid;
}

在PID管理中怎么根据一个进程找到所属组的所有进程?内核怎么找到线程的组ID?

根据PID查找task_struct结构上述有介绍,不再赘述。
这两个问题大体本质是一个问题,我们知道所有组的task_struct都指向一个组ID对应的task_struct我们可以根据这个来查找,也就是如下task_struct->group_leader,这个group_leader成员也是一个task_struct * 结构,指向组ID对应的task_struct结构,然后根据这个来查找组ID和查找所有的进程。
内核查找线程组pid结构,进程组PID结构,组PID结构代码:

static inline struct pid *task_tgid(struct task_struct *task)
{                  
        return task->group_leader->pids[PIDTYPE_PID].pid;
}

/*
 * Without tasklist or rcu lock it is not safe to dereference
 * the result of task_pgrp/task_session even if task == current,
 * we can race with another thread doing sys_setsid/sys_setpgid.
 */
static inline struct pid *task_pgrp(struct task_struct *task)
{
        return task->group_leader->pids[PIDTYPE_PGID].pid;
}

static inline struct pid *task_session(struct task_struct *task)
{
        return task->group_leader->pids[PIDTYPE_SID].pid;
}

父命名空间是如何看到子命名空间进程的?

在父命名空间是如何看到子命名空间进程的,这个与进程存储有关:我们在本博客一开始就讨论了可以通过namespace + pid号来索引所有的进程,这个就是我们索引的,我们可以遍历所有的namespace + pid号来进行查找。下面的关于如何申请PID有涉及对于PID的存储操作。

内核是如何申请PID进行存储的?

内核申请PID是和命名空间有关的,在上一篇博客中我们说过内核中的命名空间中存储pid_map表,这里面每一位都代表一个可用的pid号。只不过我们没有说明在某一命名空间申请pid号过程中,需要对此命名空间的所有父命名空间进行申请pid号操作的,在命名空间申请pid号的次数是根据namespace->level决定的。另外namespace->level决定的则是struct pid结构大小。

内核向命名空间申请PID号如下:

struct pid *alloc_pid(struct pid_namespace *ns)
{
        struct pid *pid;
        enum pid_type type;
        int i, nr;
        struct pid_namespace *tmp;
        struct upid *upid;
        申请pid内存,根据namespace的pid_cacep申请;                                 
        pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
        if (!pid)       
                goto out;
        给所有的命名空间申请pid号
        tmp = ns;
        for (i = ns->level; i >= 0; i--) {
                nr = alloc_pidmap(tmp);
                if (nr < 0)
                        goto out_free;struct upid.nr赋予申请的pid号;
                pid->numbers[i].nr = nr;
                命名空间指向;
                pid->numbers[i].ns = tmp;
                tmp = tmp->parent;
        }
 
        get_pid_ns(ns);
        pid->level = ns->level;
        atomic_set(&pid->count, 1);
        初始化pid->tasks结构
        for (type = 0; type < PIDTYPE_MAX; ++type)
                INIT_HLIST_HEAD(&pid->tasks[type]);

        upid = pid->numbers + ns->level;
        spin_lock_irq(&pidmap_lock);
        把命名空间 + pid号的hash值增加到pid_hash表中
        for ( ; upid >= pid->numbers; --upid)
                hlist_add_head_rcu(&upid->pid_chain,
                                &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
        spin_unlock_irq(&pidmap_lock);

out:
        return pid;

out_free:
        while (++i <= ns->level)
                free_pidmap(pid->numbers + i);

        kmem_cache_free(ns->pid_cachep, pid);
        pid = NULL;
        goto out;
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中的命名空间是一种隔离机制,可以将进程隔离到独立的环境中,包括网络、文件系统、进程等。在Linux中,可以使用命令行工具来切换命名空间。 1. 列出当前系统中的所有命名空间类型 ``` lsns ``` 2. 创建一个新的命名空间 ``` unshare [options] [command [arguments...]] ``` 这个命令会创建一个新的命名空间,并在其中执行指定的命令。可以使用多个选项来指定要创建的命名空间类型,如: ``` unshare --mount --uts --ipc --net --pid --fork bash ``` 这个命令会创建一个新的命名空间,并在其中启动一个新的bash shell。其中,--mount选项表示要创建一个新的文件系统命名空间,--uts选项表示要创建一个新的主机名和域名命名空间,--ipc选项表示要创建一个新的IPC命名空间,--net选项表示要创建一个新的网络命名空间,--pid选项表示要创建一个新的进程命名空间,--fork选项表示要在新的命名空间中启动一个新的进程。 3. 切换到另一个命名空间 ``` ip netns exec <namespace> <command> ``` 这个命令会在指定的网络命名空间中执行指定的命令。其中,<namespace>是要切换到的命名空间的名称,<command>是要在命名空间中执行的命令。 例如,要在名为netns1的网络命名空间中执行ping命令,可以使用以下命令: ``` ip netns exec netns1 ping 8.8.8.8 ``` 这个命令会在netns1命名空间中执行ping命令,并将结果返回到当前的命名空间中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值