[转载] linux 进程管理-----pid哈希链表

为了较快的从给定的pid值得到相应的宿主结构(进程描述符)指针,内核采用了pid哈希链表结构。

首先,以下的问题要理解:

1)为什么pid哈希链表只定义2048或者4096项(根据你的内存大小确定)?直接定义为pid最大值不是最好吗?

我们都知道,查找的最快方式就是数组了,可以在常数的时间内完成查找。假如我们的pid最大值为32768,那么我们完全可以定义一个struct task_struct* name[32768];进而可以最快速的从给定的pid值中找到其相应的宿主结构。也就是指向该pid进程的进程描述符指针。

然而,这确实一种不理智的方法。虽然进程PID的最大值为32768,但是,我们在实际应用中所能用的pid值要平均值要远远低于这个值。这样的话,会造成大量的物理空间浪费。假如我们用到了2768个pid,那么还有30000*4bytes的空间浪费。

2)特殊情况的需求

设想这样一种情况:假设内核必须回收一个给定pid值的线程组内的所有线程,(线程组内的所有线程的pid值都是相同的)在上述数组形式的定义中,是无法完成的。

而我们要做的是必须为这种形式的线程组(pid值相同)组织成一个链表。这个链表的节点,就是这个线程组内的全部线程。

而pid哈希链表能很好的完成上述的1)2)两点。

下面我们看一下内核对pid哈希链表的定义和初始化过程以及相关的操作。

static struct hlist_head *pid_hash[PIDTYPE_MAX];
系统在初始化期间手动的创建了一个含有PIDTYPE_MAX个元素的指针数组。
用于存放指向hash桶的指针。
并且在start_kernel期间调用pidhash_init函数对hash桶进行初始化。
303 void __init pidhash_init(void)
     /*  */
 304 {
 305         int i, j, pidhash_size;
 306         unsigned long megabytes = nr_kernel_pages >> (20 - PAGE_SHIFT);
 307 
 308         pidhash_shift = max(10, fls(megabytes * 4));
 309         pidhash_shift = min(12, pidhash_shift);
 310         pidhash_size = 1 << pidhash_shift;
 311 
 312         printk("PID hash table entries: %d (order: %d, %Zd bytes)\n",
 313                 pidhash_size, pidhash_shift,
 314                 PIDTYPE_MAX * pidhash_size * sizeof(struct hlist_head));
 315        #######以上为桶的大小设置和桶中含有的entry个数设置
     #######dmesg结果显示:PID hash table entries: 4096 (order: 12, 16384 bytes)
     #######pidhash_size(桶大小):4096*4bytes entries:4096
     #######也就是说桶中存有4096个指针,指针指向struct hlist_node结构 
 316         for (i = 0; i < PIDTYPE_MAX; i++) {
 317                 pid_hash[i] = alloc_bootmem(pidhash_size *
 318                                         sizeof(*(pid_hash[i])));
     #######在系统启动期间slab和alloc分配器都还没有建立好
     #######采用allocbootmem内存分配器对系统初始化期间的内存进行分配
 319                 if (!pid_hash[i])
 320                         panic("Could not alloc pidhash!\n");
 321                 for (j = 0; j < pidhash_size; j++)
 322                         INIT_HLIST_HEAD(&pid_hash[i][j]);
     #######将桶中指向struct hlist_node 结构的指针first初始化为NULL
 323         }
经过这个函数 pid哈希链表 被初始化完成。

与此同时,start_kernel期间也对pid位图进行了相应的初始化。利用pid位图对进程pid号进行管理是一种简单并且高效的形式。(bit:0代表这个pid可以使用bit:1代表这个pid已经被占用)

系统在初始化期间手动的为表示进程pid号的位图分配空间并且初始化。

  1. 50 typedef struct pidmap {
  2. 51 atomic_t nr_free;
  3. 52 void *page;
  4. 53 } pidmap_t;
  1. 55 static pidmap_t pidmap_array[PIDMAP_ENTRIES] =
  2. 56 { [ 0 ... PIDMAP_ENTRIES-1 ] = { ATOMIC_INIT(BITS_PER_PAGE), NULL } };

位图初始化期间:利用get_zeroed_page函数为位图提供一个物理上的页框,并且此页框被实现初始化为0.

这样,一个大小为4K的物理页框,可以表示的进程号个数为:4*1024*8=32768;

但是有一点我们要主要:由于进程号0的特殊性,我们事先将其设置为不可用,也就是将位图的0号位设置为1,并把相应表示目前可用的pid号的个数减1.

  1. 326 void __init pidmap_init(void)
  2. /* */
  3. 327 {
  4. 328 int i;
  5. 329
  6. 330 pidmap_array->page = (void *)get_zeroed_page(GFP_KERNEL);
  7. 331 set_bit(0, pidmap_array->page);
  8. 332 atomic_dec(&pidmap_array->nr_free);
  9. 333
  10. 334 /*
  11. 335 * Allocate PID 0, and hash it via all PID types:
  12. 336 */
  13. 337
  14. 338 for (i = 0; i < PIDTYPE_MAX; i++)
  15. 339 attach_pid(current, i, 0);
  16. 340 }

上述代码通过attach_pid函数将0号进程进行hash,将其hash到上面已经初始化好的hash链表桶中。

我们知道每个进程中都有:struct pid pids[PIDTYPE_MAX];而hash链表正是通过这个结构进行链接。

上述函数中,首先通过find_pid函数在指定类型和nr号的哈希链表中查找pid结构。如果找到则返回指向struct pid的指针 否则返回NULL。

接下来的代码根据find_pid的返回值,做出相应的动作。

1)pid为NULL,说明新attach的包含struct pid的进程描述符是哈希链表的第一个元素,将其插入头结点之后,并将struct pid的pid_list字段设置为NULL。struct list_head pid_list是由于连接具有相同nr号的进程描述符的连接件。

2)pid不为NULL,说明相应的哈希链表中已经存在了nr号为“nr”的进程描述符,我们只需将我们的进程描述符attach到相应的哈希链表的进程链表中。

通过上面的两个函数我们还可以得知:所有桶中(一般为4个)0号索引对应的哈希链表的第一个进程描述符元素都为0号进程的进程描述符。

而与attach_pid函数对应的操作为dettach_pid:将pid所在的进程描述符从对应的pid哈希链表中删除,这个函数的定义如下:

  1. 192 void fastcall detach_pid(task_t *task, enum pid_type type)
  2. /* */
  3. 193 {
  4. 194 int tmp, nr;
  5. 195
  6. 196 nr = __detach_pid(task, type);
  7. 197 if (!nr)
  8. 198 return;
  9. 199
  10. 200 for (tmp = PIDTYPE_MAX; --tmp >= 0; )
  11. 201 if (tmp != type && find_pid(tmp, nr))
  12. 202 return;
  13. 203
  14. 204 free_pidmap(nr);
  15. 205 }

核心函数为__detach_pid(task,type)

  1. 166 static fastcall int __detach_pid(task_t *task, enum pid_type type)
  2. /* */
  3. 167 {
  4. 168 struct pid *pid, *pid_next;
  5. 169 int nr = 0;
  6. 170
  7. 171 pid = &task->pids[type];
  8. 172 if (!hlist_unhashed(&pid->pid_chain)) {
  9. 173
  10. 174 if (list_empty(&pid->pid_list)) {
  11. 175 nr = pid->nr;
  12. 176 hlist_del_rcu(&pid->pid_chain);
  13. 177 } else {
  14. 178 pid_next = list_entry(pid->pid_list.next,
  15. 179 struct pid, pid_list);
  16. 180 /* insert next pid from pid_list to hash */
  17. 181 hlist_replace_rcu(&pid->pid_chain,
  18. 182 &pid_next->pid_chain);
  19. 183 }
  20. 184 }
  21. 185
  22. 186 list_del_rcu(&pid->pid_list);
  23. 187 pid->nr = 0;
  24. 188
  25. 189 return nr;
  26. 190 }

函数首先判断task指向的进程是否已经被hash进pid哈希链表,注意有可能是哈希链表中的进程链表。

如果不是哈希链表的节点,直接将其从所在的进程链表中删除。此时返回nr=0,我们后面可以看到,detach_pid函数根据nr值来判断是否需要将pid位图中相应的pid位清0,以此来使继续可以被使用。

如果是hash链表中的节点,还要判断

1)是否此哈希链表节点所在的进程链表只有这个节点。如果是将nr设置为pid->nr 并将这个节点删除

2)如果哈希链表节点所在的进程链表还有别的节点。那么用下一个节点replace这个节点。注意此时并未设置nr的值。也就是说返回的nr=0

当从__datach_pid返回后,接下来的任务就是判断是否需要将pid对应的pid位图中相应的位清0

1)如果nr=0,不需要清0 函数直接返回。通过上面我们知道,函数在两种情况下会返回nr=0 :

一,进程链表非空(此时暗指的含义就是这个pid还被其余的进程/线程使用着,不能在位图中将其释放;

二,删除的就是nr=0的进程描述符。这是也不需要在位图中将其释放;因为0号pid比较特殊是不允许其他进程使用的。

2)如果nr不等于0,还要判断在其他类型的哈希链表中是否存在这样的进程描述符,如果存在也表明其正在使用不能释放。

如果上述两步都没有return那么的确是需要将pid位图中想的pid表示位清0,以供系统使用。

上述的代码中有一处理解容易出现问题:

  1. 200 for (tmp = PIDTYPE_MAX; --tmp >= 0; )
  2. 201 if (tmp != type && find_pid(tmp, nr))
  3. 202 return;

为什么只搜索其他类型的hash链表而不处理我们刚刚将其删pid的类型的hash链表呢?因为对于刚刚删除的进程描述符的类型的hash链表 我们应经用nr的值来表明是否需要清位图位了。

如果nr=0那么删除的进程描述符所在的进程链表是非空的---不必删除

如果进程链表是空的那么nr=pid-->nr我们不考虑0号pid那么这个nr一定为非0就说明我这个类型的hash链表不用这个进程号了 你查查其他类型的hash链表是否还使用吧。

如果其他有一个在使用,OK 不删返回。如果都不用,那么我就清理门户pidbitmap 相应位设置为0了。

文章出处:http://blog.chinaunix.net/uid-25538637-id-271546.html

时间: 2024-11-10 08:03:20

[转载] linux 进程管理-----pid哈希链表的相关文章

Linux进程管理知识整理

Linux进程管理知识整理 1.进程有哪些状态?什么是进程的可中断等待状态?进程退出后为什么要等待调度器删除其task_struct结构?进程的退出状态有哪些? TASK_RUNNING(可运行状态) TASK_INTERRUPTIBLE(可中断等待状态) TASK_UNINTERRUPTIBLE(不可中断等待状态) TASK_STOPPED(进程被其它进程设置为暂停状态) TASK_TRACED(进程被调试器设置为暂停状态) TASK_DEAD(退出状态) 进程由于所需资源得不到满足,从而进入

Linux进程管理简谈

Linux系统进程管理简谈 进程是什么? 简单来说进程是一个正在执行的程序的一个副本,存在生命周期,有段指令和代码在不断运行. linux内核存储信息的固定格式:task struct 进程的相关信息存储在链表中 多个任务的task struct组件的链表:task list 进程的创建:父进程创建子进程(内核创建init进程,剩余一切进程有init及其子进程进程创建) 父进程创建子进程时向内核调用fork()来创建子进程并且通过调用clone()复制父进程的信息给子进程 Linux进程的优先级

linux进程管理(5)---进程消亡

一.目的 本文将讲述进程是如何消亡的.一个进程既有父进程又有子进程,因此进程消亡时,既要通知父进程,也要安排好子进程. 当前进程消亡时主要做了三件大事:释放当前进程占用的资源:为当前进程的子进程重新寻找"养父":通知当前进程的父进程,释放当前进程剩下的资源. 当前进程释放掉大多数进程资源后,只保留内核栈.structtask_struct数据结构:剩下的资源由父进程负责释放. linux调用sys_exit().sys_wait4()实现进程的消亡,代码可以在kernel/exit.c

Linux进程管理四大工具ps \dstat\ top\ htop

Linux进程管理之“四大名捕” 一.四大名捕 四大名捕,最初出现于温瑞安创作的武侠小说,是朝廷中正义力量诸葛小花的四大徒弟,四人各怀绝技,分别是轻功暗器高手“无情”.内功卓越的高手“铁手”.腿功惊人的“追命”和剑法一流的“冷血”本文四大名捕由linux命令所出演      无情:ps     出演      铁手:dstat  出演      追命:top    出演      冷血:htop   出演 二.进程相关基础知识 介绍四大名捕之前先介绍一下进程相关的基础知识,话不多说,看图. 三.

Linux进程管理与调度-之-目录导航【转】

转自:http://blog.csdn.net/gatieme/article/details/51456569 版权声明:本文为博主原创文章 && 转载请著名出处 @ http://blog.csdn.net/gatieme 目录(?)[-] 项目链接 进程的描述 进程的创建 进程的加载与运行 进程的退出 进程的调度 调度普通进程-完全公平调度器CFS 日期 内核版本 架构 作者 GitHub CSDN 2016-07-21 Linux-4.6 X86 & arm gatieme

linux进程管理(四)

[教程主题]:进程管理 [1]进程介绍 程序和进程 程序是为了完成某种任务而设计的软件,比如OpenOffice是程序.什么是进程呢?进程就是运行中的程序. 一个运行着的程序,可能有多个进程. 比如自学it网所用的WWW服务器是apache服务器,当管理员启动服务后,可能会有好多人来访问,也就是说许多用户来同时请求httpd服务,apache服务器将会创建有多个httpd进程来对其进行服务. 进程分类: 进程一般分为交互进程.批处理进程和守护进程三类. 值得一提的是守护进程总是活跃的,一般是后台

Linux进程管理(3):总结

7. exit与_exit的差异    为了理解这两个系统调用的差异,先来讨论文件内存缓存区的问题. 在linux中,标准输入输出(I/O)函数都是作为文件来处理.对应于打开的每个文件,在内存中都有对应的缓存,每次读取文件时,会多读一些记录到缓存中,这样在下次读文件时,就在缓存中读取:同样,在写文件时也是写在文件对应的缓存中,并不是直接写入硬盘的文件中,等满足了一定条件(如达到一定数量,遇到换行符\n或文件结束标志EOF)才将数据真正的写入文件.这样做的好处就是加快了文件读写的速度.但这样也带来

linux进程管理命令总结

linux进程管理的相关命令: 1.pstree  --以树状形式显示当前系统进程,和命令tree相似. 2.ps命令 命令格式:ps [options] 常用组合:aux: u:以用户为中心组织的进程状态信息显示 a:与终端相关的进程 x:与终端无关的进程 #ps aux USER : 用户名 PID : 进程号 %CPU :占用的cpu百分比 %MEM  :内存的使用率 VSZ :  virtual memory size ,虚拟内存集 RSS : resident size ,常驻内存集

mschedule 简单linux进程管理(树莓派)

树莓派是神奇的机器,CPU和内存都少的可怜,但体积小功耗低,在上面搞些动搞些西其实也挺有意思,挺好玩的.装的是pidara,基本服务没有精简多少,先cat一下CPU和RAM. [[email protected] ~]$ cat /proc/cpuinfo Processor : ARMv6-compatible processor rev 7 (v6l) BogoMIPS : 464.48 Features : swp half thumb fastmult vfp edsp java tls