接下来的两个函数分别介绍的是PID的HASH表和位码表的初始化,在说程序之前,我先说说这两个表有什么用处。PID是process id的缩写,我们也把它称为进程标示符。PID位于struct task_struct里的PID字段里。PID是顺序编码的,新创建进程的PID通常是前一个进程的PID加1.位码表的每一位表示这个PID是否可用,为1表示正在被使用,为0表示空闲。对于32位系统需要32767位即1页物理内存来存放PID位码表。系统里总是有个全局变量pidmap_array[].是struct pidmap结构数组。那我们先要看看struct pidmap是一个什么结构体啦。
typedef struct pidmap {
atomic_t nr_free;//这个变量是用来统计这个结构体对应的一页物理内存中有多少个位是0的,即空闲PID数量是多少。
void *page;//这个就是指向存放这个位码表的内存页的指针了。
} pidmap_t;
现在明白这个数组的用途把,其实这个数组里面的每个成员都是表示一页大小的位码表。对于32位及其以下的,他们的位码表的大小只有一页物理页那么大,所以我们只是需要1个成员就够了。我们来看看这个数组的初始化是怎样的
static pidmap_t pidmap_array[PIDMAP_ENTRIES] =
{ [ 0 ... PIDMAP_ENTRIES-1 ] = { ATOMIC_INIT(BITS_PER_PAGE), NULL } };//nr_free为一页位的数量,而page指向空。
我们现在来看看位码表的初始化函数pidmap_init()的具体代码吧
void __init pidmap_init(void)
{
int i;
pidmap_array->page = (void *)get_zeroed_page(GFP_KERNEL);//get_zeroed_page()函数一上来就要求一定要在低端物理地址内申请内存,接着就调用alloc_pages()函数向伙伴系统申请一页物理内存。接着就得到虚拟地址,还将虚拟地址开始的一页空间清0.最后返回这个虚拟地址,使pidmap_array->page 指向它。
set_bit(0, pidmap_array->page);//将第0位设置为1,表示当前进程使用PID为0。即现在就是0号进程。
atomic_dec(&pidmap_array->nr_free);//同时更新nr_free统计空闲PID的值
for (i = 0; i < PIDTYPE_MAX; i++)
attach_pid(current, i, 0);//看下面详解。
}
我们可以看下PIDTYPE_MAX的相关宏定义:
enum pid_type
{
PIDTYPE_PID,//0,表示进程PID
PIDTYPE_TGID,//1,表示线程组领头的进程的PID
PIDTYPE_PGID,//2,表示进程组领头的进程的PID
PIDTYPE_SID,//3,表示会话组领头的进程的PID
PIDTYPE_MAX//4,
};
在说这块宏定义之前,我必须要提到PID的HASH表是什么回事,同样,我们系统一样有一个全局变量在管理HASH表。pid_hash[PIDTYPE_MAX]是struct hlist_head结构数组。 PIDTYPE_MAX=4,说明系统有4中类型PID。也就是说这个数组的每个成员代表一个类型PID的HASH表。其实这个数组是指针数组,都是指向这些表的表头,表里面是连续存放着2的pidhash_shift次方个hash链表。意思就是每个表项又是一个链表。这个hash链表是struct hlist_head结构类型。对于每个hash表的表项数是由实际系统的物理内存大小决定的。比如一个系统的物理内存大小事512MB,每个hash表项的大小为8个字节(sizeof(struct hlist_head)=8),同时每个hash表要2048个表项数(这个就是实际物理内存决定的)。这样我们就16KB大小来存放着4个hash表了,也就是4页物理内存。
上面说的是全局变量,其实每个进程都是会有一个struct pid pids[PIDTYPE_MAX]数组,进程根据不同的情况将这4个struct pid变量成员连接到pid_hash[]全局数组上,现在我们结合代码,看看它是如何连接的。
for (i = 0; i < PIDTYPE_MAX; i++)
attach_pid(current, i, 0);//这个循环是把不同类型PID的0号pid链接到pid_hash[]上。
int fastcall attach_pid(task_t *task, enum pid_type type, int nr)
{
struct pid *pid, *task_pid;
task_pid = &task->pids[type];//task_pid指向进程的pids[]中的某一个PID类型的struct pid结构变量。
pid = find_pid(type, nr);//通过PID类型和nr=pid号,我们可以找到pid_hash[]中某个表的某一表项(就是一个链表头),然后遍历链表,通过struct pid中的pid_chain成员可以找到对应struct pid变量,然后看看它的PID号和传进来的PID号是否一致,如果找到的话,返回的pid就不为NULL,反之为NULL。
if (pid == NULL) {//如果为NULL,说明PID进程号是第一次被分配的
hlist_add_head(&task_pid->pid_chain,
&pid_hash[type][pid_hashfn(nr)]);//这个函数的作用就是把该struct pid的pid_chain挂在全局数组pid_hash[]对应的表中对应的表项的链表上,是挂在前头的哦。
INIT_LIST_HEAD(&task_pid->pid_list);//其实这里初始化的含义就是删除链表链接。就是把该struct pid得pid_list从原来的链表上删除。
} else {//这里就表明这个进程号以前就被其他的进程获得过了。
INIT_HLIST_NODE(&task_pid->pid_chain);//这里就把该struct pid的pid_chain从pid_hash[]的对应表的对应表项的链表上删除。
list_add_tail(&task_pid->pid_list, &pid->pid_list);//把该struct pid的pid_list挂在原来获得该进程号的进程的struct pid的pid_list的后面。
}
task_pid->nr = nr;//赋给该struct pid的nr值,nr表示该类PID的pid号。
return 0;
}
这样我们就把pidmap_ini()函数分析完了,接下来我们就顺理成章的把pidhash_init()函数也分析了
void __init pidhash_init(void)
{
int i, j, pidhash_size;
unsigned long megabytes = nr_kernel_pages >> (20 - PAGE_SHIFT);//nr_kernel_pages是表示内核内存总页数,就是系统所有节点的的DMA和NORMAL内存页区的实际物理内存总页数。这里不包括内存孔洞。其实这里就是统计系统内存为多少MB。
pidhash_shift = max(4, fls(megabytes * 4));//fls()这个函数就是求第一位为1的位号,这里就是要取4与fls中的最大值。
pidhash_shift = min(12, pidhash_shift);//这里要取12和刚才上面取的最大值中获取最小值。可以看出我们的pidhash_shift是在4~12之间的。
pidhash_size = 1 << pidhash_shift;//这里的pidhash_size就是一个hash表的表项数量,这里可以看出,我们的hash表项的个数是由实际物理内存的大小决定的。
printk("PID hash table entries: %d (order: %d, %Zd bytes)/n",
pidhash_size, pidhash_shift,
PIDTYPE_MAX * pidhash_size * sizeof(struct hlist_head));//打印信息:pidhash_shift表示2的几次方,pidhash_size表示一个表有多少表项,PIDTYPE_MAX * pidhash_size * sizeof(struct hlist_head)这个就是表示这4张页表一共占用多少空间。这里是按字节为单位的。
for (i = 0; i < PIDTYPE_MAX; i++) {
pid_hash[i] = alloc_bootmem(pidhash_size *
sizeof(*(pid_hash[i])));//看到alloc_bootmem就知道是在低端物理内存申请的,由于pidhash_init()函数是在mem_init()函数执行之前被调用的,所以这里申请的内存是不会被收回的。这里可以看出是为每一张表进行空间的申请的。pidhash_size *sizeof(*(pid_hash[i]))其中sizeof(*(pid_hash[i]))=sizeof(struct hlist_head).可以很明显的看到每个pid_hash[]其实就是一个指针,指向一块连续空间的首地址,该空间存放了pidhash_size 数量的struct hlist_head的结构体。
if (!pid_hash[i])
panic("Could not alloc pidhash!/n");
for (j = 0; j < pidhash_size; j++)
INIT_HLIST_HEAD(&pid_hash[i][j]);//这里就是初始化每个表的每个表项的链表。方便以后使用。
}