如何通过进程id查找进程的描述符task_struct?在实际的工作中,我的驱动中需要通过读取写进内存中的进程id值查找对应的进程是否还在运行,或者是已经退出。通过阅读内核代码,内核中已有相应的API函数来供我们使用。下面是我在使用过程中的一些笔记总结。

    linux系统上运行的进程可能成百上千的,或者更多,如何能够快速的通过pid值反向的查找task_struct,内核的方法是通过Hash散列表的方式。下面结合3.16.7内核版本来分析pid hash表的实现。

void __init pidhash_init(void)  

{  

        unsigned int i, pidhash_size;  

  

        pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,  

                                                                        HASH_EARLY | HASH_SMALL,  

                                                                        &pidhash_shift, NULL,  

                                                                        0, 4096);  

        pidhash_size = 1U << pidhash_shift;  

  

        for (i = 0; i < pidhash_size; i++)  

                INIT_HLIST_HEAD(&pid_hash[i]);  

}

    pidhash_init函数在系统启动时由start_kernel函数调用。Pid hash表的大小由运行机器的总内存数决定,范围在16~4096个之间。在之前老的内核(如2.6.12.6)pid hash表包含四个表,分别针对enum pid_type的四种不同类型PID的情况。

    进程创建时,是如何将task_struct与pid_hash散列表关联起来的?在copy_process函数中会判断参数pid指针是否等于init_struct_pid,如果不相等,就会调用alloc_pid函数分配一个新的struct pid类型的pid实例,并初始化。如果相等,说明是idle进程的创建。一般情况下,pid指针为空。下面看下alloc_pid函数的实现:

struct pid *alloc_pid(struct pid_namespace *ns)  

{  

        pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);   

  

        tmp = ns;  

        pid->level = ns->level;  

        for (i = ns->level; i >= 0; i--) {  /*遍历pid namsespace*/

                nr = alloc_pidmap(tmp);  

  

                pid->numbers[i].nr = nr;  

                pid->numbers[i].ns = tmp;  

                tmp = tmp->parent;  

        }  

 

        for (type = 0; type < PIDTYPE_MAX; ++type)  

                INIT_HLIST_HEAD(&pid->tasks[type]);  /*初始化每个id类型的散列表头*/

 

        upid = pid->numbers + ns->level;  

        spin_lock_irq(&pidmap_lock);  

        for ( ; upid >= pid->numbers; --upid) {  

                hlist_add_head_rcu(&upid->pid_chain,  

                                                   &pid_hash[pid_hashfn(upid->nr, upid->ns)]);  

                upid->ns->nr_hashed++;  

        }  

        spin_unlock_irq(&pidmap_lock);  

    参数ns参数是创建新进程时父进程task_struct描述符的命名空间指针nsproxy的pid namespace,一般情况下是指向init_pid_ns,如果在clone时候有指定CLONE_NEWPID标志,则会在copy_process中调用alloc_pid之前调用copy_namespaces创建一个新的pid namespace。

    函数首先调用kmem_cache_alloc从slab中分配pid内存。上面讲到如果clone时有指定标志CLONE_NEWPID的情况下,会创建一个新的pid namespace,此新建的pid namespace的parent指向父进程的pid namespace,相应的level也加一。这个新建的进程对于新创建的pid namespace和父进程的pid namespace,以及更初始的pid namespace都是可见的

    在第一个for循环中,从新创建PID namespace的level(此成员表示可以看到该进程的命名空间的数目)到0,给每个可见此进程的命名空间都分配一个局部的pid值。并将新分配的局部pid值nr赋给PID描述符的对应的命名空间numbers的nr字段,将pid namespace指针tmp赋给对应的命名空间numbers的ns字段。

    struct pid结构体中成员tasks是一个数组,每个数组成员是一个散列表的表头,对应于pid_type枚举中的一个id类型。在第二个for循环中,初始化tasks数组对应的每个id类型的散列表头。

    Pid结构体的成员numbers,虽然是一个长度为1的数组,但是因为它是在结构体的最后,可以根据pid所属的空间的个数分配更多的空间。numbers数组的大小由pid->level字段决定。Level值越大的命名空间级别越低,它能够被此pid的level值更小的命名空间看见,也就是说父命名空间能给看到子命名空间,反之子命名空间不能看见父命名空间。

    代码这里首先将upid指向此pid描述符的级别最低的命名空间,然后在for循环中遍历每层的命名空间,以当前pid namespace的进程id值nr和命名空间指针ns为key值,调用pid_hashfn函数生成pid_hash全局数组中的bucket id值,并将此进程链入此bucket id所在的hash散列表中。


    上面是alloc_pid函数分配一个新的pid描述符的大致过程,接着回到copy_process函数看看创建新进程的余下的和pid hash相关的代码。

copy_process函数代码片段

if (likely(p->pid)) {  

        init_task_pid(p, PIDTYPE_PID, pid);  

        if (thread_group_leader(p)) {  /*进程是线程组的组长*/

                init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));  

                init_task_pid(p, PIDTYPE_SID, task_session(current));  

 

                attach_pid(p, PIDTYPE_PGID);  

                attach_pid(p, PIDTYPE_PGID);   

        } else {  

 

        }  

        attach_pid(p, PIDTYPE_PID);  /*PIDTYPE_PID类型的pid插入到相应的链表中*/

}

    如果前面alloc_pid创建新的pid成功或者本身没有指定CLONE_NEWPID标志,就会进入上述代码片段执行。前面讲到枚举类型pid_type的不同id类型,在我分析的新内核(3.16.7)中是三种:

PIDTYPE_PID     对应进程PID

PIDTYPE_PGID    进程组领头进程PID

PIDTYPE_SID    会话领头进程PID

    首先调用函数init_task_pid初始化新创建进程的类型PIDTYPE_PID的pid实例,如果此新创建的进程是线程组的组长,则初始化此进程的PIDTYPE_PGID类型和PIDTYPE_SID类型的实例,并且调用attach_pid函数将此进程进行Hash,加入到已经初始化好的pid_hash对应bucket的散列表中。

    这样就建立起了一个双向链表的关系,进程可以通过task_struct的task->pids[type]的成员pid访问pid实例,pid实例也可以通过遍历tasks[type]链表来查找进程task_struct。

    经过上面的分析,一个进程可以属于多个不同的命名空间,同一个进程在不同的命名空间中有不同的局部pid值,多个进程task_struct可以共用一个pid描述符。


GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐