全面解析Linux 内核 3.10.x - Pid hash 链表

来源:互联网 发布:asp.net 4.5 高级编程 编辑:程序博客网 时间:2024/04/28 05:00

From: 全面解析Linux 内核 3.10.x - 进程管理

不管千山万水,时间流逝,我们始终是有关系的 - 某某言情剧

何谓进程之间的关系?

在前面作总结的时候,说进程有一个标识ID,我们称之为进程描述符,描述符描述了进程的一些注册优先级,状态等一些值,其实这里也有给出字段描述了进程的一些关系。
程序创建的进程具体父/子关系,如果一个进程创建多个子进程,则子进程之间具有兄弟关系,在进程描述符中引入几个字段来表示这些关系,表示给定进程P的这些字段。进程0(swapper)和进程1是由内核创建的。而进程1(init)是所有进程的祖先!
进程0在很早的时候我就已经讲过了,关于1号进程我会在后面和文件系统衔接的时候来描述([]可以戳这里]())!
下面我重点去研究进程之间的各种亲属关系,到底在内核中是一张怎样的复杂关系表?
我们知道的最多是进程之间的父子关系,Ps.一个进程可能是一个进程组或登录会话的领头进程,也可能是一个线程组的领头进程,它还可能跟踪其它进程的执行。
五个亲属间的亲属关系 -

内核中的pid hash链表

内核在初始化的时候有一个函数为pidhash_init,这个函数要做什么呢?
我们都知道,我们可能在很多情况下,都想知道进程的一些状态,在这种情况,内核必须能够从进程的PID导出对应的进程描述符指针,这样我们才能获取我们想要的进程信息!
譬如.kill 这个动作,当进程P1希望对进程P2发送一个信号时,P1调用kill调用,参数为P2的PID,内核从这个PID导出其对应的进程描述符,取出描述符记录挂起信号的数据结构指针。这里提到了内核从PID,好,内核要去寻找PID?
我们在前面也说过,进程有自己单独的链表叫进程链表,其原理是一个双向链表!行,找PID,就是链表遍历么,咱们顺序遍历?No,因为这样做效率太低,而且浪费CPU,就这样我们的pidhash就出来,我们将散列表的方式来查找!
看看pidhash_init 的源码:

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); /*这里为项的大小设置和桶中含有的entry个数设置*/    pidhash_size = 1U << pidhash_shift;    for (i = 0; i < pidhash_size; i++)        INIT_HLIST_HEAD(&pid_hash[i]);}

上述代码阐述为2个步骤
第一:分配系统支持最大的hash_table,以及长度,在此期间内核动态的为4个散列表分配空间,分别是PID(进程ID),TGID(线程组领头进程的PID),PGID(进程组领头进程的PID),SID(会话领头进程的PID)。
第二:根据得到的长度初始化pid_hash.
好,至此效率问题已然解决。
但是你以为问题结束了吗? No.. 新的问题又出现了,我们都知道hash函数并不能总是确保PID与表的索引一一对应(会重复),我们使用hash当然是为了效率,但是我们的目地是导出进程描述符对应的指针,那么假如两个PID同时对应一个指针?那岂不是本末倒置么?效率倒是高了,可是问题没解决呀?
上述问题已然表明PID对应的指针并不是唯一,我们称他为冲突。
内核中使用链表来处理冲突的PID,每一个表项是由冲突的进程描述符的进程描述组成的双向链表。
为了解决此问题,内核中给出了一种方法,我们称它为pid hash 链表结构。
根据上述源码我们有几个疑惑:
a.为什么pid hash表只定义2048或者4096项?为何不能直接指定PID的大小?
这个其实主要是因为内核要考虑到世纪的情况,不能那么的任性,不能说你要多少,我就给你分配多少。假设可使用的内存有限,我还给你分配那么多,脑子又没有秀逗。
b.之前我说内核初始化的时候就已经将pid的最大值给确定了?这个是在哪里呢?

    void __init pidmap_init(void)    {        /* Veryify no one has done anything silly */        BUILD_BUG_ON(PID_MAX_LIMIT >= PIDNS_HASH_ADDING);        /* bump default and minimum pid_max based on number of cpus */        pid_max = min(pid_max_max, max_t(int, pid_max,                    PIDS_PER_CPU_DEFAULT * num_possible_cpus()));        pid_max_min = max_t(int, pid_max_min,                    PIDS_PER_CPU_MIN * num_possible_cpus());        pr_info("pid_max: default: %u minimum: %u\n", pid_max, pid_max_min);        init_pid_ns.pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);        /* Reserve PID 0. We never call free_pidmap(0) */        set_bit(0, init_pid_ns.pidmap[0].page);        atomic_dec(&init_pid_ns.pidmap[0].nr_free);        init_pid_ns.nr_hashed = PIDNS_HASH_ADDING;        init_pid_ns.pid_cachep = KMEM_CACHE(pid,                SLAB_HWCACHE_ALIGN | SLAB_PANIC);    }

pid_max: default: 32768 minimum: 301
上述代码的意思利用kzalloc函数为进程位图提供一个物理上的页框,并且此页框被实现初始化为0,这样,一个大小为4K的物理页框,可以表示的进程号个数为=4*1024*8=32768.但是由于进程号0的特殊性,我们事先将其设置为不可用,也就是将位图的0号位设置为1,并把相应表示目前可用的pid号再-1.

待续..


By: Keven - 点滴积累

0 0
原创粉丝点击