Windows的PID为什么是4的倍数

mallon 发布于 2011/10/13 19:12
阅读 3K+
收藏 4

开源软件供应链点亮计划,等你来!>>>

今天在使用OpenProcess函数的时候,偶然发现,如果某个pid能够打开,那么pid+1,pid+2,pid+3同样能打开,而且是同样的进程。打开任务管理器才发现进程的pid都是4的倍数,以前都没发现。

然后写了个简单的验证程序,结果很有意思。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <psapi.h>

int main()
{
    DWORD pid;
    HANDLE ph;
    char name[MAX_PATH];

    for (pid = 0; pid < 65535; pid++) {
        ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
        if (ph == NULL) {
            continue;
        }

        memset(name, 0, sizeof(name));
        if (0 != GetModuleBaseName(ph, NULL, name, sizeof(name))) {
            printf("%6ld: %s\n", pid, name);
        }

        CloseHandle(ph);
    }

    return 0;
}

在我的机器上运行结果为:


  1416: cmd.exe
  1417: cmd.exe
  1418: cmd.exe
  1419: cmd.exe
  1488: Explorer.EXE
  1489: Explorer.EXE
  1490: Explorer.EXE
  1491: Explorer.EXE
  1676: plugin-container.exe
  1677: plugin-container.exe
  1678: plugin-container.exe
  1679: plugin-container.exe
  1828: avastUI.exe
  1829: avastUI.exe
  1830: avastUI.exe
  1831: avastUI.exe
  1932: OpenProcessTest.exe
  1933: OpenProcessTest.exe
  1934: OpenProcessTest.exe
  1935: OpenProcessTest.exe
  1992: ctfmon.exe
  1993: ctfmon.exe
  1994: ctfmon.exe
  1995: ctfmon.exe
  2044: conime.exe
  2045: conime.exe
  2046: conime.exe
  2047: conime.exe
  2296: vmware.exe
  2297: vmware.exe
  2298: vmware.exe
  2299: vmware.exe
  2544: FirefoxPortable.exe
  2545: FirefoxPortable.exe
  2546: FirefoxPortable.exe
  2547: FirefoxPortable.exe
  2560: firefox.exe
  2561: firefox.exe
  2562: firefox.exe
  2563: firefox.exe

现象就一目了然了。

加载中
0
mallon
mallon

回家上网查原因,发现这里的一篇帖子:

http://www.cnblogs.com/Thriving-Country/archive/2011/09/18/2180143.html

今天看到一篇文章作者问为什么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。因此程序中用到的句柄值并不能直接用来索引句柄表,也就有了“伪索引”说法。
【作者】:GUO Xingwang
【来源】:http://thriving-country.cnblogs.com/
     本文版权归作者和博客园共同所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

0
mallon
mallon
所以以后用OpenProcess一定要注意了,送进来的pid先检查是否为4的倍数,否则直接返回失败,呵呵
0
开源中国射线科科长
开源中国射线科科长
是某种程度的HASH算法  N是键的区间大小 便于机器定位进程编号,操作进程和树 很多公司 特别大公司的题目 都有HASH的影子  JDK里也有 比如你放一个字符串对象到HashSet里  HASH就用字符串的HASHCODE(Java里的伪地址) 那么你有10000万个元素的时候就不需要迭代一万次   只需要找到他的hashcode所在的区间 然后迭代区间元素 甚至递归hash  但是老外思想就是多,还有人用斐波拉契堆和素数表来设计比hash更为惊叹的算法
0
QGB
QGB

引用来自“开源中国X科长”的评论

是某种程度的HASH算法  N是键的区间大小 便于机器定位进程编号,操作进程和树 很多公司 特别大公司的题目 都有HASH的影子  JDK里也有 比如你放一个字符串对象到HashSet里  HASH就用字符串的HASHCODE(Java里的伪地址) 那么你有10000万个元素的时候就不需要迭代一万次   只需要找到他的hashcode所在的区间 然后迭代区间元素 甚至递归hash  但是老外思想就是多,还有人用斐波拉契堆和素数表来设计比hash更为惊叹的算法
1Pando
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部