代码改变世界

WINDOWS进程或线程号为什么是4的倍数

2011-09-18 09:53  GUO Xingwang  阅读(3242)  评论(0编辑  收藏  举报
  今天看到一篇文章作者问为什么System进程号是4.记得之前在《windows内核原理与实现》里面看过,但是就是想不起来了。搜集了一些资料解释了原因。

  原来进程Id和线程Id都是基于全局的句柄表PspCidTable生成,也就是句柄表的索引号。句柄表除了作为对象引用的容器以外,还有另一个用法:作为分配进程和线程的唯一ID 的有效手段。进程有一个唯一ID,称为UniqueProcessId;线程有一个CLIENT_ID 成员Cid,其中包含了所属进程的唯一ID 和线程自身的唯一ID。这些唯一ID 是怎么生成的呢?它们是通过调用ExCreateHandle 函数,在一个全局的句柄表PspCidTable 中创建的句柄索引值。此句柄表也称为CID 句柄表(Client ID handle table),它没有被加入到系统的句柄表链表中。CID 句柄表中的每个句柄表项都包含了进程或线程的对象地址。

因此,进程和线程的唯一ID 值都是4 的倍数,其中4 是第一个句柄索引值,它被分配给System 进程的唯一ID(因为System 进程是第一个通过PspCreateProcess 函数创建的进程)。0 是专门保留给空闲进程的,它并非通过ExCreateHandle 函数而获得。CID 句柄表严格按照FIFO 来重用句柄表项,所以,一个句柄表项被释放以后,要等到其他的空闲句柄表项都被重用一遍以后才会被再次使用。由于CID 句柄表项保存了进程或线程的对象地址,所以,在内核中,根据进程或线程的唯一ID 值,总是可以很方便地找到相应的对象地址,函数PsLookupProcessThreadByCid、PsLookupProcessByProcessId 和PsLookup-ThreadByThreadId 正是利用了CID 句柄表的这一能力,参见base\ntos\ps\pscid.c 文件中的代码。

PspCidTable的说明如下:

PspCidTable也是一个句柄表,其格式与普通的句柄表是完全一样的.但它与每个进程私有的句柄表有以下不同:
1.PspCidTable中存放的对象是系统中所有的进线程对象,其索引就是PID和TID
2.PspCidTable中存放的直接是对象体(EPROCESS和ETHREAD),而每个进程私有的句柄表则存放的是对象头(OBJECT_HEADER)
3.PspCidTable是一个独立的句柄表,而每个进程私有的句柄表以一个双链连接起来。

至于为什么句柄表的号都是4的倍数呢?

一个进程的句柄表包含了所有已被该进程打开的那些对象的指针。对象句柄是用来检索句柄表的一个“伪索引”。对于句柄表机制,achillis <<Windows句柄表>>系列文章已经分析得很透彻了,只是对“句柄以4为步进”来源不明。经查,根源如下:

typedef struct _EXHANDLE
{
 union
 {
  struct
  {
   ULONG TagBits:2;
   ULONG Index:30;
  }
  HANDLE GenericHandleOverlay;
  #define HANLE_VALUE_INC 4
  ULONG_PTR Value;
 }

}EXHANDLE,*PEXHANDLE;
此结构正是用来定义句柄类型。低2位TagBits为标志位Windows用于其它用途,故句柄值低2位对其作为句柄表索引本身无意义,所以等于4的倍数。有了以上分析,自然,在用句柄值为索引取句柄表项时,句柄值必须/4。因此程序中用到的句柄值并不能直接用来索引句柄表,也就有了“伪索引”说法。