进程调度与管理2-task_struct 与pid 命名空间组织管理

1-命名空间

 1. UTS命名空间
 2.IPC命名空间;
 3.PID命名空间;
 4.文件命名空间;
 5.网络命名空间;
 6.用户命名空间;

通过程序理解上面的命名空间

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sched.h>
#define STACK_SIZE (1024*1024)

static char container_stack[STACK_SIZE];
const char * args  = "/bin/bash";

int contain_func(void * arg)
{
        printf("this is in %s, and pid : %d \n", __func__, getpid());

        sethostname("alexander", 10);
        system("mount -t proc proc /proc");
        execv(args, arg);
        printf("this is in %s end\n", __func__);
        return 1;
}


int main(void)
{
        int clone_pid = clone(contain_func, container_stack + STACK_SIZE,
                              CLONE_NEWPID | CLONE_NEWUTS| CLONE_NEWIPC|CLONE_NEWNS |SIGCHLD , NULL);

        waitpid(clone_pid, NULL, 0);
        printf("this is in %s\n", __func__);
        return 0;
}

1.1-UTS命名空间

root@ubuntu:/usr/src/linux-2.6.39/driver/namespace/clone# gcc ipc_clone.c -o clone
root@ubuntu:/usr/src/linux-2.6.39/driver/namespace/clone# ./clone 
this is in contain_func, and pid : 1 
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# hostname
alexander
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# exit
exit
this is in main
root@ubuntu:/usr/src/linux-2.6.39/driver/namespace/clone# hostname
ubuntu
root@ubuntu:/usr/src/linux-2.6.39/driver/namespace/clone# 

可以通过上面的例子可以看出,hostname 在clone 运行期间有独立的空间

1.2IPC命名空间
ipcs -q: 查看系统的message queue
ipcmk -Q: 常见message queue

root@ubuntu:/usr/src/linux-2.6.39/driver/namespace/clone# ./clone 
this is in contain_func, and pid : 1 
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ipcmk -Q
Message queue id: 0
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x5f9a3379 0          root       644        0            0           

root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ./clone 
this is in contain_func, and pid : 1 
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# exit
exit
this is in main
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x5f9a3379 0          root       644        0            0           

root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ipcrm 0x5f9a3379
ipcrm: unknown argument: 0x5f9a3379
usage: ipcrm [ [-q msqid] [-m shmid] [-s semid]
          [-Q msgkey] [-M shmkey] [-S semkey] ... ]
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ipcrm -Q 0x5f9a3379
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# exit
exit
this is in main

./clone IPC 空间看到创建的message queue 并不一样。

1.3 PID命名空间

root@ubuntu:/usr/src/linux-2.6.39/driver/namespace/clone# ./clone 
this is in contain_func, and pid : 1 
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.5  0.0   5748  1956 pts/15   S    04:35   0:00 [bash]
root        13  0.0  0.0   5216  1144 pts/15   R+   04:35   0:00 ps aux
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 04:35 pts/15   00:00:00 [bash]
root        14     1  0 04:35 pts/15   00:00:00 ps -ef
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# ls /proc/
1          cgroups   diskstats    fs          kallsyms    kpageflags     misc     pagetypeinfo  slabinfo       sysvipc      version_signature
15         cmdline   dma          interrupts  kcore       latency_stats  modules  partitions    softirqs       timer_list   vmallocinfo
acpi       consoles  driver       iomem       keys        loadavg        mounts   sched_debug   stat           timer_stats  vmstat
asound     cpuinfo   execdomains  ioports     key-users   locks          mpt      schedstat     swaps          tty          zoneinfo
buddyinfo  crypto    fb           ipmi        kmsg        mdstat         mtrr     scsi          sys            uptime
bus        devices   filesystems  irq         kpagecount  meminfo        net      self          sysrq-trigger  version
root@alexander:/usr/src/linux-2.6.39/driver/namespace/clone# exit
exit
this is in main

在创建新的IPC命名空间过程中必须指定创建文件系统命名空间,并且挂载新的proc文件系统,否则会使用上一层命名空间中的proc文件系统,这样显示的PID(无论是ps 命令还是top命令)就如上一层命名空间中显示的PID一样的。

1.4 Network Namespace
当调用clone时,设定了CLONE_NEWNET,就会创建一个新的Network Namespace。一个Network Namespace为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口,IPv4和IPv6协议栈,IP路由表,防火墙规则,sockets等等。一个Network Namespace提供了一份独立的网络环境,就跟一个独立的系统一样。一个物理设备只能存在于一个Network Namespace中,可以从一个Namespace移动另一个Namespace中。虚拟网络设备(virtual network device)提供了一种类似管道的抽象,可以在不同的Namespace之间建立隧道。利用虚拟化网络设备,可以建立到其他Namespace中的物理设备的桥接。当一个Network Namespace被销毁时,物理设备会被自动移回init Network Namespace,即系统最开始的Namespace。

2-UTS命名空间与PID 关联

struct task_struct{
    ......
    struct nsproxy * nsproxy;
    ......
};

struct nsproxy {
        atomic_t count;
        struct uts_namespace *uts_ns;
        struct ipc_namespace *ipc_ns;
        struct mnt_namespace *mnt_ns;
        struct pid_namespace *pid_ns;
        struct net           *net_ns;
        };
 
struct uts_namespace{
        struct kref kref;
        struct new_utsname name;
        struct user_namespace *user_ns;
};

CLONE_NEWUTS在kernel中如何使用的

struct uts_namespace *copy_utsname(unsigned long flags,
                                   struct task_struct *tsk)
{
        struct uts_namespace *old_ns = tsk->nsproxy->uts_ns;
        struct uts_namespace *new_ns;

        BUG_ON(!old_ns);
        get_uts_ns(old_ns);

        if (!(flags & CLONE_NEWUTS))
                return old_ns;

        new_ns = clone_uts_ns(tsk, old_ns);

        put_uts_ns(old_ns);
        return new_ns;
}
函数参数:
    flags:clone函数调用的参数;
    tsk:要克隆的主进程的task_struct;
函数返回值:
    struct uts_name* 类型,返回的是UTS命名空间的地址;
函数作用:
    先判断flags是否有CLONE_NEWUTS结构;
    1.没有,则返回老的uts_namespace;
    2.有,先申请一块内存再吧原来的uts_namespace拷贝给新的内存;
注意:在没有CLONE_NEWUTS结构情况下会把uts_namespace->kref加一,否则不加一;

3-PID组织与管理

enum pid_type
{
    PIDTYPE_PID,
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX
};

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

3.1PID命名空间
命名空间简单来说提供的是对全局资源的一种抽象,将资源放到不同的容器中(不同的命名空间),各容器彼此隔离。
命名空间有的还有层次关系,如PID命名空间


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

局部ID和全局ID
命名空间增加了PID管理的复杂性。回想一下,PID命名空间按层次组织。在建立一个新的命名空间时,该命名空间中的所有PID对父命名空间都是可见的,但子命名空间无法看到父命名空间的PID。但这意味着某些进程具有多个PID,凡可以看到该进程的命名空间,都会为其分配一个PID。 这必须反映在数据结构中。我们必须区分局部ID和全局ID

全局PID和TGID直接保存在task_struct中,分别是task_structpidtgid成员:

  • 全局ID 在内核本身和初始命名空间中唯一的ID,在系统启动期间开始的 init 进程即属于该初始命名空间。系统中每个进程都对应了该命名空间的一个PID,叫全局ID,保证在整个系统中唯一。
  • 局部ID 对于属于某个特定的命名空间,它在其命名空间内分配的ID为局部ID,该ID也可以出现在其他的命名空间中。
<sched.h> 
struct task_struct
{  
        //...  
        pid_t pid;  
        pid_t tgid;  
        //...  
}

两项都是pid_t类型,该类型定义为__kernel_pid_t,后者由各个体系结构分别定义。通常定义为int,即可以同时使用232个不同的ID。

会话session和进程group组ID不是直接包含在task_struct本身中,但保存在用于信号处理的结构中。

task_ struct->signal->__session表示全局SID,
而全局PGID则保存在task_struct->signal->__pgrp。
辅助函数set_task_session和set_task_pgrp可用于修改这些值。

struct pid_namespace:

include/linux/pid_namespace.h
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;
}


每个PID命名空间都具有一个进程,其发挥的作用相当于全局的init进程。init的一个目的是对孤儿进程调用wait4,命名空间局部的init变体也必须完成该工作。

3.2 pid结构描述

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;  
}; 
字段 描述
nr 表示ID具体的值
ns 指向命名空间的指针
pid_chain 指向PID哈希列表的指针,用于关联对于的PID

所有的upid实例都保存在一个散列表中,稍后我们会看到该结构。

struct pid  
{  
        atomic_t count;  
        /* 使用该pid的进程的列表, lists of tasks that use this pid  */
        struct hlist_head tasks[PIDTYPE_MAX];  
        int level;  
        struct upid numbers[1];    ---->这个虽然只有1个numbers , 但是在使用中,如果多个upid 是继续增加的。地址以struct upid  依次增加的
};

tasks是一个数组,每个数组项都是一个散列表头,对应于一个ID类型,PIDTYPE_PID, PIDTYPE_PGID, PIDTYPE_SID( PIDTYPE_MAX表示ID类型的数目)这样做是必要的,因为一个ID可能用于几个进程(不同的命名空间)。所有共享同一给定ID的task_struct实例,都通过该列表连接起来。
这个枚举常量PIDTYPE_MAX,正好是pid_type类型的数目,这里linux内核使用了一个小技巧来由编译器来自动生成id类型的数目

pidmap用于分配pid的位图

struct pidmap
{  
    atomic_t nr_free;  
    void *page; 
};
字段 描述
nr_free 表示还能分配的pid的数量
page 指向的是存放pid的物理页

pidmap[PIDMAP_ENTRIES]域表示该pid_namespace下pid已分配情况

pid_link哈希表存储
pids[PIDTYPE_MAX]指向了和该task_struct相关的pid结构体。

struct task_struct  
{  
    struct pid_link pids[PIDTYPE_MAX];  
};
struct pid_link  
{  
    struct hlist_node node;  
    struct pid *pid;  
};

task_struct中进程ID相关数据结构

struct task_struct  
{  
    //...  
    pid_t pid;  
    pid_t tgid;  
    struct task_struct *group_leader;  
    struct pid_link pids[PIDTYPE_MAX];  
    struct nsproxy *nsproxy;  
    //...  
};
字段 描述
pid 指该进程的进程描述符。在fork函数中对其进行赋值的
tgid 指该进程的线程描述符。在linux内核中对线程并没有做特殊的处理,还是由task_struct来管理。所以从内核的角度看, 用户态的线程本质上还是一个进程。对于同一个进程(用户态角度)中不同的线程其tgid是相同的,但是pid各不相同。 主线程即group_leader(主线程会创建其他所有的子线程)。如果是单线程进程(用户态角度),它的pid等于tgid。
group_leader 除了在多线程的模式下指向主线程,还有一个用处, 当一些进程组成一个群组时(PIDTYPE_PGID), 该域指向该群组的leader

对于用户态程序来说,调用getpid()函数其实返回的是tgid,因此线程组中的进程id应该是是一致的,但是他们pid不一致,这也是内核区分他们的标识

  • 多个task_struct可以共用一个PID
  • 一个PID可以属于不同的命名空间
  • 当需要分配一个新的pid时候,只需要查找pidmap位图即可
    那么最终,linux下进程命名空间和进程的关系结构如下:


4-内核是如何设计task_struct中进程ID相关数据结构的

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

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

4.1 一个PID对应一个task时的task_struct设计

一个PID对应一个task_struct如果先不考虑进程之间的关系,不考虑命名空间,仅仅是一个PID号对应一个task_struct,那么我们可以设计这样的数据结构。

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--->这个是重点, 为了找到pid_link
    int nr; //PID
    struct hlist_node pid_chain; //pid hash 散列表结点
};

每个进程的 task_struct 结构体中有一个指向 pid 结构体的指针,pid结构体包含了PID号。


如何快速地根据局部ID、命名空间、ID类型找到对应进程的 task_struct
图中还有两个结构上面未提及:
pid_hash[]这是一个hash表的结构,根据pid的nr值哈希到其某个表项,若有多个 pid 结构对应到同一个表项,这里解决冲突使用的是散列表法。

这样,就能解决开始提出的第2个问题了,根据PID值怎样快速地找到task_struct结构体:

  • 首先通过 PID 计算 pid 挂接到哈希表 pid_hash[] 的表项
  • 遍历该表项,找到 pid 结构体中 nr 值与 PID 值相同的那个 pid
  • 再通过该 pid 结构体的 tasks 指针找到 node
  • 最后根据内核的 container_of 机制就能找到 task_struct 结构体

如何快速地给新进程在可见的命名空间内分配一个唯一的 PID

  • pid_map 这是一个位图,用来唯一分配PID值的结构,图中灰色表示已经分配过的值,在新建一个进程时,只需在其中找到一个为分配过的值赋给 pid 结构体的 nr,再将pid_map 中该值设为已分配标志。这也就解决了上面的第3个问题——如何快速地分配一个全局的PID

如何快速地根据进程的 task_struct、ID类型、命名空间找到局部ID
至于上面的第1个问题就更加简单,已知 task_struct 结构体,根据其 pid_link 的 pid 指针找到 pid 结构体,取出其 nr 即为 PID 号。

4.2 带进程ID类型的task_struct设计

如果考虑进程之间有复杂的关系,如线程组、进程组、会话组,这些组均有组ID,分别为 TGID、PGID、SID,所以原来的 task_struct 中pid_link 指向一个 pid 结构体需要增加几项,用来指向到其组长的 pid 结构体,相应的 struct pid 原本只需要指回其 PID 所属进程的task_struct,现在要增加几项,用来链接那些以该 pid 为组长的所有进程组内进程。数据结构如下:

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 pid_link pids[PIDTYPE_MAX];    
    struct task_struct *group_leader; // threadgroup leader
    //...
    struct pid_link pids[PIDTYPE_MAX];  
    struct nsproxy *nsproxy;  
};

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 散列表结点
};

上面 ID 的类型 PIDTYPE_MAX 表示 ID 类型数目。之所以不包括线程组ID,是因为内核中已经有指向到线程组的 task_struct 指针 group_leader,线程组 ID 无非就是 group_leader 的PID。
假如现在有三个进程A、B、C为同一个进程组,进程组长为A,这样的结构示意图如图



上面图有一点点不对的地方,已经标出
关于上图有几点需要说明:
图中省去了 pid_hash 以及 pid_map 结构,因为第一种情况类似;
进程B和C的进程组组长为A,那么 pids[PIDTYPE_PGID] 的 pid 指针指向进程A的 pid 结构体;
进程A是进程B和C的组长,进程A的 pid 结构体的 tasks[PIDTYPE_PGID] 是一个散列表的头,它将所有以该pid 为组长的进程链接起来

4.3 进一步增加进程PID命名空间的task_struct设计

若在第二种情形下再增加PID命名空间
一个进程就可能有多个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;
};

在 pid 结构体中增加了一个表示该进程所处的命名空间的层次level,以及一个可扩展的 upid 结构体。对于struct upid,表示在该命名空间所分配的进程的ID,ns指向是该ID所属的命名空间,pid_chain 表示在该命名空间的散列表。
举例来说,在level 2 的某个命名空间上新建了一个进程,分配给它的 pid 为45,映射到 level 1 的命名空间,分配给它的 pid 为 134;再映射到 level 0 的命名空间,分配给它的 pid 为289,对于这样的例子,下图所示为其表示:



可以看出上图中的numbers 是依次排列的, 内核设计这样不会内存被踩么 ?

4.4 进程ID管理函数

pid号到struct pid实体

struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
struct pid *find_vpid(int nr)
struct pid *find_get_pid(pid_t nr)

find_pid_ns获得 pid 实体的实现原理,主要使用哈希查找。内核使用哈希表组织struct pid,每创建一个新进程,给进程的struct pid都会插入到哈希表中,这时候就需要使用进程
的进程pid和命名ns在哈希表中将相对应的struct pid索引出来,现在可以看下find_pid_ns的传入参数,也是通过nr和ns找到struct pid。

根据局部PID以及命名空间计算在 pid_hash 数组中的索引,然后遍历散列表找到所要的 upid, 再根据内核的 container_of 机制找到 pid 实例。

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;
}

find_vpid&find_get_pid

struct pid *find_vpid(int nr)
{      //current 这个就是当前运行的ns 
        return find_pid_ns(nr, current->nsproxy->pid_ns);
}
struct pid *find_get_pid(pid_t nr)
{ 
        struct pid *pid; 

        rcu_read_lock();
        pid = get_pid(find_vpid(nr)); 
        rcu_read_unlock();

        return pid; 
}

获得局部ID
根据进程的 task_struct、ID类型、命名空间,可以很容易获得其在命名空间内的局部ID
获得与task_struct 关联的pid结构体。辅助函数有 task_pid、task_tgid、task_pgrp和task_session,分别用来获取不同类型的ID的pid 实例,如获取 PID 的实例:

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

获取线程组的ID,前面也说过,TGID不过是线程组组长的PID而已,所以:

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

而获得PGID和SID,首先需要找到该线程组组长的task_struct,再获得其相应的 pid:

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;
}

获得 pid 实例之后,再根据 pid 中的numbers 数组中 uid 信息,获得局部PID。

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
    struct upid *upid;
    pid_t nr = 0;
    if (pid && ns->level <= pid->level)
    {
        upid = &pid->numbers[ns->level];
        if (upid->ns == ns)
            nr = upid->nr;
    }
    return nr;
}

这里值得注意的是,由于PID命名空间的层次性,父命名空间能看到子命名空间的内容,反之则不能,因此,函数中需要确保当前命名空间的level 小于等于产生局部PID的命名空间的level。

除了这个函数之外,内核还封装了其他函数用来从 pid 实例获得 PID 值,如 pid_nr、pid_vnr 等。在此不介绍了。

结合这两步,内核提供了更进一步的封装,提供以下函数:

pid_t task_pid_nr_ns(struct task_struct *tsk, struct pid_namespace *ns);
pid_t task_tgid_nr_ns(struct task_struct *tsk, struct pid_namespace *ns);
pid_t task_pigd_nr_ns(struct task_struct *tsk, struct pid_namespace *ns);
pid_t task_session_nr_ns(struct task_struct *tsk, struct pid_namespace *ns);

根据PID查找进程task_struct

  • 根据PID号(nr值)取得task_struct 结构体
  • 根据PID以及其类型(即为局部ID和命名空间)获取task_struct结构体

如果根据的是进程的ID号,我们可以先通过ID号(nr值)获取到进程struct pid实体(局部ID),然后根据局部ID、以及命名空间,获得进程的task_struct结构体
可以使用pid_task根据pid和pid_type获取到进程的task

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]),
        lockdep_tasklist_lock_is_held());
        if (first)
            result = hlist_entry(first, struct task_struct, pids[(type)].node);
    }

    return result;
}

那么我们根据pid号查找进程task的过程就成为

pTask = pid_task(find_vpid(pid), PIDTYPE_PID);  

内核还提供其它函数用来实现上面两步:

struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns);
struct task_struct *find_task_by_vpid(pid_t vnr);
struct task_struct *find_task_by_pid(pid_t vnr);

由于linux进程是组织在双向链表和红黑树中的,因此我们通过遍历链表或者树也可以找到当前进程

生成唯一的PID

static int alloc_pidmap(struct pid_namespace *pid_ns);
static void free_pidmap(struct upid *upid);

在这里我们不关注这两个函数的实现,反而应该关注分配的 PID 如何在多个命名空间中可见,这样需要在每个命名空间生成一个局部ID,函数 alloc_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;
    tmp = ns;
    pid->level = ns->level;
    // 初始化 pid->numbers[] 结构体
    for (i = ns->level; i >= 0; i--)
    {
        nr = alloc_pidmap(tmp); //分配一个局部ID
        pid->numbers[i].nr = nr;
        pid->numbers[i].ns = tmp;
        tmp = tmp->parent;
    }
    // 初始化 pid->task[] 结构体
    for (type = 0; type < PIDTYPE_MAX; ++type)
        INIT_HLIST_HEAD(&pid->tasks[type]);

    // 将每个命名空间经过哈希之后加入到散列表中
    upid = pid->numbers + ns->level;
    for ( ; upid >= pid->numbers; --upid)
    {
        hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
        upid->ns->nr_hashed++;
    }

    return pid;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343