• 进程表示之进程ID号


    UNIX进程总是会分配一个号码用于在其命名空间总唯一地标识它们,该号码称作进程ID号,简称PID

    1、进程ID

     但每个进程除了PID外,还有其他的ID,有下列几种可能的类型

    (1)处于某个线程组中的所有进程都有统一的线程组ID(TGID)。若进程没有使用线程,则其PID和TGID相同。线程组中主进程被称作组长(group leader)。通过clone创建的所有线程的task_struct的group_leader成员,会指向组长task_struct实例。

    (2)独立进程可以合并为进程组(使用setpgrp系统调用)。进程组简化了向组的所有成员发送信号的操作,有助于各种系统程序设计应用,用管道连接的进程包含在同一个进程组。

    (3)几个进程组可以合并为一个会话。会话中所有进程都有同样的会话ID,保存在task_struct的session中。SID可通过setsid系统调用设置。用于终端程序设计。

    2、全局ID和局部ID

    PID Namespace使得父命名空间可以看见所有子命名空间PID,但子命名空间无法看到父命名空间的PID,这意味着某些进程具有多个PID,凡可以看到该进程的命名空间,都会为其分配一个PID,由此需要区分全局ID和局部ID:

    (1)全局ID:在内核本身和初始命名空间的唯一ID号,在系统启动期间开始的init进程即属于初始命名空间。对每个ID类型,都有一个给定的全局ID,保证在整个系统中是唯一的。

    (2)局部ID:属于某个特定命名空间,不具备全局有效性。

    3、数据结构

     首先给出一个总图,其中红线是结构描述,黑线是指向。其中进程A,B,C是一个进程组的,A是组长进程,所以B和C的task_struct结构体中的pid_link成员的node字段就被邻接到进程A对应的struct pid中的tasks[1]。

     

    以下用ID指代提到的任何进程ID。

    struct task_struct {
        ...
        pid_t pid;  //全局PID
        pid_t tgid;  //线程组ID
        struct task_struct *group_leader;  //指向线程组组长task_struct实例
        struct pid_link pids[PIDTYPE_MAX];  //PID和PID散列表的联系,将所有共享同一ID的task_struct实例都按进程存储在一个散列表中
        ...
    };

     其中PIDTYPE_MAX表示ID类型的数目,枚举类型中定义的ID类型不包括线程组ID,因为线程组ID即为线程组组长的PID:

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

    task_struct中的辅助结构pid_link可以将task_struct连接到表头在struct pid中的散列表上:

    struct pid_link
    {
        struct hlist_node node;  //散列表元素
        struct pid *pid;  //指向进程所属pid结构实例
    };

    为了在给定命名空间中查找对应于指定PID数值的pid结构实例,使用了一个散列表:static struct hlist_head *pid_hash; hlist_head是内核标准数据结构,用于建立双链散列表。

    假如已经分配了struct pid的一个新实例,并设置用于给定的ID类型。它会通过如下附加到task_struct(kernel/pid.c):

    void attach_pid(struct task_struct *task, enum pid_type type,
            struct pid *pid)
    {
        struct pid_link *link;
    
        link = &task->pids[type];
        link->pid = pid;
        hlist_add_head_rcu(&link->node, &pid->tasks[type]);
    }

    这里建立双向链表:task_struct可以通过task_struct->pids[type]->pid访问pid实例;而从pid实例开始,可以遍历task[type]散列表找到task_struct。hlist_add_head_rcu是遍历散列表的标准函数。

    PID的管理围绕两个数据结构:struct pid是内核对PID的内部表示,struct upid表示特定的命名空间中可见的信息:

    struct pid
    {  //内核对PID的内部表示
        atomic_t count;  //引用计数
        unsigned int level;  //这个pid所在的层级
        /* 使用该pid的进程的列表 */
        struct hlist_head tasks[PIDTYPE_MAX];  //每个数组项都是一个散列表表头,对应一个ID类型。因为一个ID可能用于几个进程,所有共享同一给定ID的task_struct实例,都通过该列表连接起来
        struct rcu_head rcu;
        struct upid numbers[1];  //这个pid对应的命名空间,一个pid不仅要包含当前的pid,还要包含父命名空间,默认大小为1,所以就处于根命名空间中,可添加附加项扩充
    };
    struct upid {  //包装命名空间所抽象出来的一个结构体
        /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
        int nr;  //pid在该命名空间中的pid数值
        struct pid_namespace *ns;  //对应的命名空间
        struct hlist_node pid_chain;  //通过pidhash将一个pid对应的所有的命名空间连接起来(所有upid实例被保存在一个散列表中)
    };
    struct pid_namespace {
        struct kref kref;
        struct pidmap pidmap[PIDMAP_ENTRIES];  //一个pid命名空间应该有其独立的pidmap
        int last_pid;  //上次分配的pid
        unsigned int nr_hashed;
        struct task_struct *child_reaper;  //每个PID命名空间都需要一个作用相当于全局init进程的进程,init的一个目的是对孤儿进程调用wait4,此保存了指向该进程的task_struct的指针
        struct kmem_cache *pid_cachep;
        unsigned int level;  //所在的命名空间层次,初始为0,子空间level为1。level较高命名空间的PID对level较低的命名空间的PID是可见的,从给定level设置,可知进程会关联多少个PID
        struct pid_namespace *parent;  //指向父命名空间,构建命名空间的层次关系
            ...
    };

    4、函数操作

     本质上内核需要完成两个任务:

    (1)给出局部数字ID和对应命名空间,查找此二元组描述的task_struct。

    (2)给出task_struct、ID类型、命名空间,取得命名空间局部数组ID。

    对于(1)分解为两步:

    a.由局部PID和ns,确定pid实例。内核采用标准散列方式,首先,根据PID和ns指针计算在pid_hash数组中索引,然后遍历散列表直至找到所要的upid实例,而由于这些实例直接包含在struct pid中,所以通过使用container_of机制可推断出pid实例(kernel/pid.c):

    struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
    {
        struct upid *pnr;
    
        hlist_for_each_entry_rcu(pnr,
                &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;
    }

    b.pid_task取出pid->task[type]散列表中的第一个task_struct实例(kernel/pid.c):

    struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
    {
        rcu_lockdep_assert(rcu_read_lock_held(),
                   "find_task_by_pid_ns() needs rcu_read_lock()"
                   " protection");
        return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
    }

    对于(2)也分为两步:

    a.获得与task_struct关联的pid实例:

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

    还可通过task_tgid、task_pgrp和task_session分别用于取得不同类型的ID:

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

    b.从struct pid的numbers数组中upid信息,即可获得数字ID:

    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) {  //由于父ns可以看到子ns的PID,反过来不行,内核必须确保当前ns的level小于或等于产生局部ID的ns的level
            upid = &pid->numbers[ns->level];
            if (upid->ns == ns)
                nr = upid->nr;
        }
        return nr;
    }

    以下函数用于返回该ID所属的ns所看到的局部PID:

    pid_t pid_vnr(struct pid *pid)
    {
        return pid_nr_ns(pid, task_active_pid_ns(current));
    }

    5、生成唯一PID

    在建立一个新进程时,进程可能在多个ns中可见,对每个这样的ns,都需要生成一个局部ID:

    struct pid *alloc_pid(struct pid_namespace *ns)
    {  //pid分配要依赖与pid namespace,也就是说这个pid是属于哪个pid namespace
        struct pid *pid;
        enum pid_type type;
        int i, nr;
        struct pid_namespace *tmp;
        struct upid *upid;
    
        pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);  //分配一个pid结构
        if (!pid)
            goto out;
    
        tmp = ns;
        pid->level = ns->level;  //初始化level
        for (i = ns->level; i >= 0; i--) {   //递归到上面的层级进行pid的分配和初始化
            nr = alloc_pidmap(tmp);  //从当前pid namespace开始直到全局pid namespace,每一个层级都分配一个pid
            if (nr < 0)
                goto out_free;
    
            pid->numbers[i].nr = nr;  //初始化upid结构
            pid->numbers[i].ns = tmp;
            tmp = tmp->parent;  //递归到父亲pid namespace
        }
    
        if (unlikely(is_child_reaper(pid))) {  //如果是init进程需要做一些设定,为其准备proc目录
            if (pid_ns_prepare_proc(ns))
                goto out_free;
        }
    
        get_pid_ns(ns);
        atomic_set(&pid->count, 1);
        for (type = 0; type < PIDTYPE_MAX; ++type)  //初始化pid中的hlist结构
            INIT_HLIST_HEAD(&pid->tasks[type]);
    
        upid = pid->numbers + ns->level;  //定位到当前namespace的upid结构
        spin_lock_irq(&pidmap_lock);
        if (!(ns->nr_hashed & PIDNS_HASH_ADDING))
            goto out_unlock;
        for ( ; upid >= pid->numbers; --upid) {
            hlist_add_head_rcu(&upid->pid_chain,
                    &pid_hash[pid_hashfn(upid->nr, upid->ns)]);  //建立pid_hash,让pid和pid namespace关联起来
            upid->ns->nr_hashed++;
        }
        spin_unlock_irq(&pidmap_lock);
    
    out:
        return pid;
    
    out_unlock:
        spin_unlock_irq(&pidmap_lock);
    out_free:
        while (++i <= ns->level)
            free_pidmap(pid->numbers + i);
    
        kmem_cache_free(ns->pid_cachep, pid);
        pid = NULL;
        goto out;
    }

    参考:

    linux-3.10.1内核源码

    《深入Linux内核架构》

    https://blog.csdn.net/zhangyifei216/article/details/49926459

    很清晰的一个讲解

  • 相关阅读:
    IPC对象的持续性
    数据结构C++版-图
    数据结构C++版-树
    数据结构C++版-队列
    数据结构C++版-栈
    数据结构C++版-线性表
    转-二值图像连通域标记
    AdaBoost笔记之通俗易懂原理介绍
    AdaBoost笔记之原理
    C++之多态
  • 原文地址:https://www.cnblogs.com/beixiaobei/p/9317976.html
Copyright © 2020-2023  润新知