(转载)深入分析进程PID相同的奥秘

深入分析进程PID相同的奥秘

转自: http://www.xfocus.net/articles/200504/792.html
创建时间:2005-04-13 更新时间:2005-04-14
文章属性:原创
文章提交: sunwear (btwlu_at_163.com)

深入分析进程PID相同的奥秘

作者:sunwear
[email protected]
2005年4月
    不同的进程真的不能够拥有相同的PID么?我相信大部分人都会说,这是不可能的,因为PID是在操作系统中表示进程的唯一性标示,因此不可能出现不同的进程拥有相同的PID,否则在系统调度的时候就会出现混乱。可是真的是这样么?有这样一个程序xxxx,当我们用xxx工具来观察系统中的pid。我们发现,在这个程序运行时,系统中竟然出现了不同的进程拥有了相同的pid。这是为什么?我们的理解与我们看到的现象竟然出现了矛盾。    
首先让我们来了解一下EPROCESS结构。每个Windows 2000进程都由一个执行程序进程(EPROCESS)块表示,也就是说在内核中,进程是靠EPROCESS来识别的.下面是EPROCESS的结构定义
typedef struct _EPROCESS {
    KPROCESS Pcb;
    NTSTATUS ExitStatus;
    KEVENT LockEvent;
    ULONG LockCount;
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER ExitTime;
    PKTHREAD LockOwner;

    HANDLE UniqueProcessId;

    LIST_ENTRY ActiveProcessLinks;

    SIZE_T QuotaPeakPoolUsage[2];
    SIZE_T QuotaPoolUsage[2];

    SIZE_T PagefileUsage;
    SIZE_T CommitCharge;
    SIZE_T PeakPagefileUsage;

    SIZE_T PeakVirtualSize;
    SIZE_T VirtualSize;

    MMSUPPORT Vm;
    LIST_ENTRY SessionProcessLinks;

    PVOID DebugPort;
    PVOID ExceptionPort;
    PHANDLE_TABLE ObjectTable;

    PACCESS_TOKEN Token;        


    FAST_MUTEX WorkingSetLock;
    PFN_NUMBER WorkingSetPage;
    BOOLEAN ProcessOutswapEnabled;
    BOOLEAN ProcessOutswapped;
    UCHAR AddressSpaceInitialized;
    BOOLEAN AddressSpaceDeleted;
    FAST_MUTEX AddressCreationLock;
    KSPIN_LOCK HyperSpaceLock;
    struct _ETHREAD *ForkInProgress;
    USHORT VmOperation;
    UCHAR ForkWasSuccessful;
    UCHAR MmAgressiveWsTrimMask;
    PKEVENT VmOperationEvent;
    PVOID PaeTop;
    ULONG LastFaultCount;
    ULONG ModifiedPageCount;
    PVOID VadRoot;
    PVOID VadHint;
    PVOID CloneRoot;
    PFN_NUMBER NumberOfPrivatePages;
    PFN_NUMBER NumberOfLockedPages;
    USHORT NextPageColor;
    BOOLEAN ExitProcessCalled;

    BOOLEAN CreateProcessReported;
    HANDLE SectionHandle;

    PPEB Peb;
    PVOID SectionBaseAddress;

    PEPROCESS_QUOTA_BLOCK QuotaBlock;
    NTSTATUS LastThreadExitStatus;
    PPAGEFAULT_HISTORY WorkingSetWatch;
    HANDLE Win32WindowStation;
    HANDLE InheritedFromUniqueProcessId;
    ACCESS_MASK GrantedAccess;
    ULONG DefaultHardErrorProcessing;
    PVOID LdtInformation;
    PVOID VadFreeHint;
    PVOID VdmObjects;
    PVOID DeviceMap;

    ULONG SessionId;

    LIST_ENTRY PhysicalVadList;
    union {
        HARDWARE_PTE PageDirectoryPte;
        ULONGLONG Filler;
    };
    ULONG PaePageDirectoryPage;
    UCHAR ImageFileName[ 16 ];
    ULONG VmTrimFaultValue;
    BOOLEAN SetTimerResolution;
    UCHAR PriorityClass;
    union {
        struct {
            UCHAR SubSystemMinorVersion;
            UCHAR SubSystemMajorVersion;
        };
        USHORT SubSystemVersion;
    };
    PVOID Win32Process;
    struct _EJOB *Job;
    ULONG JobStatus;
    LIST_ENTRY JobLinks;
    PVOID LockedPagesList;
    PVOID SecurityPort ;              
    PWOW64_PROCESS Wow64Process;

    LARGE_INTEGER ReadOperationCount;
    LARGE_INTEGER WriteOperationCount;
    LARGE_INTEGER OtherOperationCount;
    LARGE_INTEGER ReadTransferCount;
    LARGE_INTEGER WriteTransferCount;
    LARGE_INTEGER OtherTransferCount;

    SIZE_T CommitChargeLimit;
    SIZE_T CommitChargePeak;

    LIST_ENTRY ThreadListHead;

    PRTL_BITMAP VadPhysicalPagesBitMap;
    ULONG_PTR VadPhysicalPages;
    KSPIN_LOCK AweLock;
} EPROCESS;

每个Windows进程都会由系统空间中的一个EPROCESS块来标示。其中有一个UniqueProcessId的属性存储了在系统空间中唯一的进程ID,也就是我们常说的PID。
下面我们在来看看进程的创建,正如文章开始所说的例子,一个父进程不断创建子进程,子进程结束但是HANDLE并未关闭,WIN32子系统进程(csrss)不断创建新进程。
进程创建过程可以通过跟踪分析CreateProcess来了解。我简单的说一下。
首先CreateProcess找到执行程序对应的WIN32映射执行程序后,创建执行程序对象。
首先就是设置EPROCESS块其中包括把进程和会话ID存储到对应的字段中,设置进程退出状态,并创建访问令牌。
然后创建初始地址空间和内核进程块与地址空间的设置以及PEB的设置。
再创建线程和堆栈环境。
下面的一部就是向WIN32子系统传递信息,包括新建的进程线程句柄。创建标志中的项以及ID和确认其属于WIN32应用程序的标志。后面就是初始化线程并完成整个进程的初始化。

下面我们来看看程序退出的方面。
ExitProcess函数在结束进程的时候 并没有释放EPROCESS在内存中的数据,导致进程结束,但EPROCESS还存在.

通过上面的分析,可以知道子进程的EPROCESS并不会同进程一起消失,因为EPROCESS必须等到所有的Handle关闭后才会关闭。也就是说进程的EPROCESS Handle count为0的时候,EPROCESS才会被关闭。说白了是,一旦有进程拥有EPROCESS的句柄,那么即使进程退出,那么EPROCESS也暂时不会被清除出内存,直到所有的Handle都关闭后,才会被清除。上面提到的进程关闭而HANDLE未关闭的进程就称做僵尸进程(tombie process)。这样的情况下win32子系统无法意识到有些进程已成为僵尸。
一般查看进程信息的程序均调用 NtQuerySystemInformation函数来显示出进程的信息。
NTSTATUS
NtQuerySystemInformation (
    IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
    OUT PVOID SystemInformation,
    IN ULONG SystemInformationLength,
    OUT PULONG ReturnLength OPTIONAL
    )
SystemInformationClass 信息的类别,SystemInformation 一个指向函数输出缓冲区的指针,SystemInformationLength 是这个缓冲区的长度,ReturnLength是写入字节的数目。
NtQuerySystemInformation函数 是直接枚举EPROCESS,所以,如果直接通过NtQuerySystemInformation枚举进程的话,会出现有些相同的PID。原因就是CSRSS根本就不考虑已死的进程。
通过上面的信息也可以了解,只有在进程结束后,EPROCESS从内存中释放前,才会出现这种情况。
如果文中有错误或不足请通知作者 sunwear  [email protected]  :)

你可能感兴趣的:((转载)深入分析进程PID相同的奥秘)