博主很想详细的介绍一下进程神马的、内存神马的,但是真的整理起来发现要做到让有C基础的学生能很好理解,一看就懂还是感觉很有难度。也许是博主水平还不够吧。
作为90后程序员,博主还是不走寻常路吧,到了进程这里开始,我们反过来学一下,顺便补一补一些基础知识。
上一节文件编程的时候,有说过文件流的状态,当我们开始操作一个文件,这个文件的打开关闭都由程序控制,而其中的内容是可以动态增减的,这样的情况类似水流一般,我们把这样的文件状态称作文件流。
那么实际上,用户的输入、输出这一类的“缓存”也就是这样的状态,通常是叫做输入流和输出流,除了这两个还有一个错误流。C语言中的标准库(stdio.h)中有定义这三个常用的流,也即stdin(标准输入流)、stdout(标准输出流)和stderr(标准错误流)。如果大家仔细深究 stdio.h 的头文件会发现,这些流的定义跟文件的定义实际上是同样的。
好了,回到终点,那么既然这些东西都已经是 “流” 的状态了,那么也就意味着我们不需要去打开什么文件,来生成什么文件流,直接可以用文件操作的函数来操作这些输入输出流。
例如:
1 2 | scanf ( "%d" , &i); 实际上就是 fscanf (stdin, "%d" , &i); 的简写而已。 |
PS:这实际上应该算是C语言的基础知识,不能理解为什么大学都不教(博主观点)
那么,开始有讲过的,windows编程其实就是去查API,然后用API。这一节讲进程,我们就通过编写一个学习用的cmd工具集来一步步走向进程编程。
首先我们要从输入流来读取用户的输入,这一操作在标准库中可以使用 fgets 函数来实现,调用 windows API 的话我们就用 ReadFile 就可以了。
1 2 3 4 5 6 7 | BOOL WINAPI ReadFile( _In_ HANDLE hFile, // 读取的文件句柄 _Out_ LPVOID lpBuffer, // 保存读取缓冲字符数组 _In_ DWORD nNumberOfBytesToRead, // 缓冲数组的大小 _Out_opt_ LPDWORD lpNumberOfBytesRead, // 实际读出的大小 _Inout_opt_ LPOVERLAPPED lpOverlapped // 异步IO文件结构体 ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include <Windows.h>; #include <stdio.h>; void welcome(); int main() { char Command_str[MAX_PATH]; DWORD Command_len; HANDLE hConsoleInput; // 获取输出流的句柄 hConsoleInput = GetStdHandle(STD_INPUT_HANDLE); // 输出欢迎信息 welcome(); while (1) { // 清空命令字符串 memset (&Command_str, 0, MAX_PATH); // 输出提示符 printf ( "\nLscmd>;" ); // 读取输入流 ReadFile( hConsoleInput, // 文件句柄 Command_str, // 获取内容的缓冲字符数组 MAX_PATH, // 缓冲数组大小 &Command_len, // 实际读出的大小 NULL); printf ( "接收到命令:[%s]" , Command_str); } } void welcome() { printf ( "Lellansin's CMD Tool [版本 0.0.1]\n" ); printf ( "学习自制 (c) www.lellansin.com 欢迎交流\n" ); } |
可以简单的看到,我们的输入都有获取到,并且输出的时候还连带我们输的回车(换行符)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #include <Windows.h>; #include <stdio.h>; #include <string.h>; // for strcmp #include <stdlib.h>; // for exit void welcome(); void command_switch( char *cmd_str); int main() { char Command_str[MAX_PATH]; DWORD Command_len; HANDLE hConsoleInput; // 获取输出流的句柄 hConsoleInput = GetStdHandle(STD_INPUT_HANDLE); // 输出欢迎信息 welcome(); while (1) { // 清空命令字符串 memset (&Command_str, 0, MAX_PATH); // 输出提示符 printf ( "\nLscmd>;" ); // 读取输入流 ReadFile( hConsoleInput, // 文件句柄 Command_str, // 获取内容的缓冲字符数组 MAX_PATH, // 缓冲数组大小 &Command_len, // 实际读出的大小 NULL); command_switch(Command_str); } } void command_switch( char *cmd_str) { char cmd_tmp[MAX_PATH]={0}; char *pstr = cmd_str, *ptmp = cmd_tmp; // 一直赋值到换行之前 while (*pstr != '\r' && *pstr != '\n' ) { *ptmp++ = *pstr++; } // printf("收到命令:[%s]\n", cmd_tmp); // 判断命令 if ( strcmp (cmd_tmp, "hi" ) == 0 ) { printf ( "你好~" ); } else if ( strcmp ( cmd_tmp, "exit" ) == 0 ) { exit (0); } else { printf ( "Error: 命令未找到\n" ); } } void welcome() { printf ( "Lellansin's CMD Tool [版本 0.0.1]\n" ); printf ( "学习自制 (c) www.lellansin.com 欢迎交流\n" ); } |
那么到现在为止,我们的cmd工具已经有了一个基本的雏形,接下来要做的就是调用我们原来写的命令。
有的同学可能会想到直接用 stdlib.h 里的 system 函数来解析命令,这个是可以的。但是这个函数是C标准库的,并不是windows系统的API,它的效率是非常的低的。而且,这里是在在讲的是 windows 编程所以读者请不要偷懒哦。
首先,我们来了解一下进程的概念。当一个程序运行起来的时候,操作系统一定要为这个程序(Program)创建一个进程(Process),以方便管理。有些同学也知道每个程序跑起来之后都被分配了一个进程ID,实际上在进程调度的时候,操作系统还会为我们的程序分配进程的一些列事务:资源、虚拟内存地址空间、系统调用接口、优先级、环境变量等等。
所以进程实际上可以理解成一个运行起来的程序,就如进程的英文原本的意思(Process)一样这是个运行过程。每个运行起来的程序都要被操作系统安排进程(过程)的来管理。
如果你能想象这一过程,那么就不难理解,想要运行一个程序必须要创建一个进程。所以,这里我们就需要为我们原本所写的程序创建一个进程,就可以调用了。
1 2 3 4 5 6 7 8 9 10 11 12 | BOOL WINAPI CreateProcess( _In_opt_ LPCTSTR lpApplicationName, // 启动程序路径 _Inout_opt_ LPTSTR lpCommandLine, // 启动程序的命令行代码 _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程属性 _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程属性 _In_ BOOL bInheritHandles, // 是否继承句柄 _In_ DWORD dwCreationFlags, // 标识和优先级 _In_opt_ LPVOID lpEnvironment, // 环境变量设置 _In_opt_ LPCTSTR lpCurrentDirectory, // 当前目录设置 _In_ LPSTARTUPINFO lpStartupInfo, // 启动信息设置 _Out_ LPPROCESS_INFORMATION lpProcessInformation // 新进程的信息 ); |
MSDN 文档: CreateProcess function
返回值
如果函数执行成功,返回非零值。如果函数执行失败,返回零,可以使用GetLastError函数获得错误的附加信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | #include <Windows.h>; #include <stdio.h>; #include <string.h>; // for strcmp #include <stdlib.h>; // for exit void welcome(); void command_switch( char *cmd_str); BOOL CreateChildProcess( char *cmd_str); int main() { char Command_str[MAX_PATH]; DWORD Command_len; HANDLE hConsoleInput; // 获取输出流的句柄 hConsoleInput = GetStdHandle(STD_INPUT_HANDLE); // 输出欢迎信息 welcome(); while (1) { // 清空命令字符串 ZeroMemory(&Command_str, MAX_PATH); // 输出提示符 printf ( "\nLscmd>;" ); // 读取输入流 ReadFile( hConsoleInput, // 文件句柄 Command_str, // 获取内容的缓冲字符数组 MAX_PATH, // 缓冲数组大小 &Command_len, // 实际读出的大小 NULL); command_switch(Command_str); } } void command_switch( char *cmd_str) { char *pstr = cmd_str; // 遍历到换行之前 while (*pstr != '\r' && *pstr != '\n' ) { *pstr++; } // 覆盖换行 *pstr = '\0' ; // printf("收到命令:[%s]\n", cmd_str); // 判断命令 if ( strcmp (cmd_str, "hi" ) == 0 ) { printf ( "你好~ 欢迎使用 Lellansin 的cmd工具\n" ); } else if ( strcmp ( cmd_str, "exit" ) == 0 ) { exit (0); } else { // 创建子进程 CreateChildProcess(cmd_str); } } BOOL CreateChildProcess( char *cmd_str) { STARTUPINFO start_info; PROCESS_INFORMATION process_info; BOOL flag; // 将启动信息结构清零 ( 相当于 memset 0, 不过效率更高 ) ZeroMemory( &start_info, sizeof (start_info) ); // 设置结构大小,cb属性应为结构的大小 start_info.cb = sizeof (start_info); // 将进程信息结构清零 ZeroMemory( &process_info, sizeof (process_info) ); flag = CreateProcess( NULL, // 不传程序路径, 使用命令行 cmd_str, // 命令行命令 NULL, // 不继承进程句柄(默认) NULL, // 不继承线程句柄(默认) FALSE, // 不继承句柄(默认) 0, // 没有创建标志(默认) NULL, // 使用默认环境变量 NULL, // 使用父进程的目录 &start_info, // STARTUPINFO 结构 &process_info ); // PROCESS_INFORMATION 保存相关信息 if ( !flag ) { // 创建失败 printf ( "Error: 命令未找到 (%d).\n" , GetLastError() ); return 0; } // 等待子进程结束 // 使用到了通过 PROCESS_INFORMATION 结构体获取子进程的句柄 hProcess WaitForSingleObject( process_info.hProcess, INFINITE ); // 关闭进程句柄和线程句柄 CloseHandle( process_info.hProcess ); CloseHandle( process_info.hThread ); return 1; } void welcome() { printf ( "Lellansin's CMD Tool [版本 0.0.1]\n" ); printf ( "学习自制 (c) www.lellansin.com 欢迎交流\n" ); } |
获取当前的系统快照
1 2 3 4 | HANDLE WINAPI CreateToolhelp32Snapshot( _In_ DWORD dwFlags, _In_ DWORD th32ProcessID ); |
dwFlags [输入参数]
指明所需的系统快照。该参数可以是如下列表中的一个或多个值。
Value | Meaning |
---|---|
TH32CS_INHERIT (0x80000000) | 表明快照 (snapshot) 句柄是可以被继承 (inheritable) 。 |
TH32CS_SNAPALL | 包含所有系统中的所有进程和线程,加上指定进程中堆和模块的信息( 通过th32ProcessID特别指明的进程id,如果为0则无)。相当于指定 TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE, TH32CS_SNAPPROCESS, 和 TH32CS_SNAPTHREAD 通过或运算('|') 联合使用 |
TH32CS_SNAPHEAPLIST (0x00000001) | 快照中包含通过 th32ProcessID 指定进程的所有堆 (heaps) 信息。 想要列举堆的信息,请查询 Heap32ListFirst。 |
TH32CS_SNAPMODULE (0x00000008) | 快照中包含通过 th32ProcessID 指定进程的所有modules信息。 想要列举modules信息,请查询 Module32First 。 如果函数失败报错ERROR_BAD_LENGTH,请重试此函数直到成功。 |
TH32CS_SNAPMODULE32 (0x00000010) | 快照中包含所有通过 th32ProcessID 指定进程的32位模块(modules)信息(通过64位调用也是返回32位)。 该flag可以与 TH32CS_SNAPMODULE 或者 TH32CS_SNAPALL 联合使用。如果函数失败报错 ERROR_BAD_LENGTH ,请重试此函数直到成功。 |
TH32CS_SNAPPROCESS (0x00000002) | 快照中包含系统中的所有进程 (processes) 信息。 想要列举 processes 信息,请查询 Process32First。 |
TH32CS_SNAPTHREAD (0x00000004) | 快照中包含系统中的所有线程 (threads) 信息。想要列举 threads 信息, 请查询 Thread32First。 确认是否属于某个指定的进程,可以在列举线程信息的时候,拿进程标识与 THREADENTRY32 结构体的 th32OwnerProcessID 成员相比较来判断 。 |
th32ProcessID [输入参数]
如果为0则获取所有进程的快照,如果不为零则获取该进程id的信息
成功则返回该快照的句柄
1 2 3 4 | BOOL WINAPI Process32First( _In_ HANDLE hSnapshot, _Inout_ LPPROCESSENTRY32 lppe ); |
1 2 3 4 | BOOL WINAPI Process32Next( _In_ HANDLE hSnapshot, _Out_ LPPROCESSENTRY32 lppe ); |
与文件目录类似, Process32First 用于获取第一个进程的信息, Process32Next 用于获取下一个进程的信息。参数一 hSnapshot 是指获取的进程快照,参数二 lppe 则是一个指向 PROCESSENTRY32 结构体的指针(LP + PROCESSENTRY32)。
PROCESSENTRY32 结构体
系统进程快照中某一个进程信息的具体结构
1 2 3 4 5 6 7 8 9 10 11 12 13 | typedef struct tagPROCESSENTRY32 { DWORD dwSize; DWORD cntUsage; DWORD th32ProcessID; // this process ULONG_PTR th32DefaultHeapID; DWORD th32ModuleID; // associated exe DWORD cntThreads; DWORD th32ParentProcessID; // this process's parent process LONG pcPriClassBase; // Base priority of process's threads DWORD dwFlags; CHAR szExeFile[MAX_PATH]; // Path } PROCESSENTRY32; |
MSDN 文档 :
CreateToolhelp32Snapshot
Process32First
Process32Next
PROCESSENTRY32 结构体
再来新建一个项目名字叫做 ps ,记得要是空项目随后添加如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #include <Windows.h> #include <stdio.h> #include <TlHelp32.h> /* TlHelp32.h for PROCESSENTRY32 CreateToolhelp32Snapshot() Process32First() Process32Next() */ int main( int argc, char const *argv[]) { HANDLE hSnapshot; HANDLE hProcess; PROCESSENTRY32 pe32; // 获取进程快照 hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); if ( hSnapshot == INVALID_HANDLE_VALUE ) { printf ( "CreateToolhelp32Snapshot (of processes) 失败" ); return ; } // 设置输入参数,结构的大小 pe32.dwSize = sizeof ( PROCESSENTRY32 ); // 开始列举进程信息 if ( !Process32First( hSnapshot, &pe32 ) ) { printf ( "Process32First() 失败" ); CloseHandle( hSnapshot ); // 关闭句柄 return ; } printf ( "进程ID\t父进程\t线程数\t优先级\t进程名" ); // 基本优先级 do { // 打印进程相关信息 printf ( "\n%u" , pe32.th32ProcessID ); // 进程id printf ( "\t%u" , pe32.th32ParentProcessID ); // 父进程id printf ( "\t%d" , pe32.cntThreads ); // 线程数 printf ( "\t%d" , pe32.pcPriClassBase ); // 基本优先级 printf ( "\t%s" , pe32.szExeFile ); // 进程名 } while ( Process32Next( hSnapshot, &pe32 ) ); CloseHandle( hSnapshot ); //关闭句柄 return ; } |
因为是在命令行的环境下运行,所以直接F7生成exe即可,不需要直接执行。如果是拿lscmd来做实验的话,程序写好了还要找到exe复制到path目录下,感觉有点麻烦,为了方便大家也可以直接将程序的生成目录,改成博主开始使用的 F:\mytools目录 (Visual Studio 如何设置生成目录)
测试数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | Lellansin's CMD Tool [版本 0.0.1] 学习自制 (c) www.lellansin.com 欢迎交流 Lscmd>ps 进程ID 父进程 线程数 优先级 进程名 0 0 2 0 [System Process] 4 0 106 8 System 292 4 2 11 smss.exe 392 380 49 8 avgrsa.exe 432 392 10 8 avgcsrva.exe 640 632 10 13 csrss.exe 704 632 3 13 wininit.exe 720 696 13 13 csrss.exe 764 696 3 13 winlogon.exe 812 704 10 9 services.exe 824 704 7 9 lsass.exe 832 704 11 8 lsm.exe 940 812 10 8 svchost.exe 1020 812 9 8 svchost.exe 724 812 19 8 svchost.exe 888 812 22 8 svchost.exe 1732 812 33 8 avgwdsvc.exe 1816 812 4 8 sqlwriter.exe 1852 812 6 8 svchost.exe 1924 812 4 8 vmware-usbarbitrator64.exe 1976 812 6 8 vmnat.exe ...... 省略N条 ...... 7008 1676 3 8 notepad++.exe 6204 1500 11 6 chrome.exe 6600 1500 11 6 chrome.exe 6628 1500 11 6 chrome.exe 6920 1500 11 6 chrome.exe 7716 3800 10 8 MSBuild.exe 5980 724 5 8 audiodg.exe 4132 1500 11 8 chrome.exe 5048 1500 11 8 chrome.exe 4976 2052 6 8 vcpkgsrv.exe 8072 7196 5 8 mspdbsrv.exe 4724 3800 8 8 vcpkgsrv.exe 3844 720 5 8 conhost.exe 7124 3400 1 8 ps.exe Lscmd> |
1 2 3 4 5 | HANDLE WINAPI OpenProcess( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId ); |
dwDesiredAccess [输入参数]
进程句柄对该进程的访问权限。详见进程访问权限。
bInheritHandle [输入参数]
句柄是否继承(填写 TRUE 或者 FALSE),如果继承(TRUE)那么如果该进程创建子进程的时候这个句柄也会被继承到子进程。
dwProcessId [输入参数]
将要打开(open)的进程ID。
如果指定进程是系统进程 (0x00000000),该函数会失败并且最后的错误会是ERROR_INVALID_PARAMETER。如果指定进程是系统空闲进程(原文:Idle process 博主备注:一种内存管理进程,准确的来讲名字叫做 System Idle Process)或者某个子系统进程(原文:CSRSS processes 博主备注:准确的说是 Client Server Runtime Process 任务管理器里可以看到它 csrss.exe),该函数会失败并且最后的错误会是ERROR_ACCESS_DENIED 因为其访问限制会阻止用户级别(user-level)的代码获取其句柄。
如果函数成功,返回值为指定进程的句柄。
如果函数失败,返回值为NULL。可以使用 GetLastError 函数获得错误的附加信息。
MSDN: OpenProcess
终止(Terminate) + 进程(Process) = 终止进程(TerminateProcess)
1 2 3 4 | BOOL WINAPI TerminateProcess( _In_ HANDLE hProcess, _In_ UINT uExitCode ); |
参数一 hProcess [输入参数]
待终止的进程句柄。该句柄必须拥有 PROCESS_TERMINATE (进程终止) 的权限。更多信息,请查看进程的安全与访问权限。
参数二 uExitCode [输入参数]
设置通过使用该方法退出的进程与线程的退出码?(exit code 嘛,博主觉得返回值更好理解一些)。 使用GetExitCodeProcess函数可以可以获取到进程退出时返回的值。使用GetExitCodeThread函数可以获取到线程退出时返回的值。
返回值
函数执行成功,返回值为非零。
函数执行失败,返回值为零。更多信息可以通过GetLastError函数获取。
新建一个空项目名为 kill ,随后代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #include <windows.h> #include <stdio.h> void help(); int main( int argc, char const *argv[]) { int ProcessID; HANDLE hProcess; // 如果只有一个参数 if (argc == 1) { help(); return 0; } // 如果有两个参数 if (argc == 2) { // 获取进程id ProcessID = atoi (argv[1]); // 获取进程句柄 hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, ( DWORD )ProcessID ); // 终止进程 TerminateProcess(hProcess, 0); } } void help() { printf ( "终止进程\n" ); printf ( "kill <进程id>\n" ); } |
使用演示:
打开两个lscmd,第一个执行
1 2 3 4 5 6 7 8 | Lellansin's CMD Tool [版本 0.0.1] 学习自制 (c) www.lellansin.com 欢迎交流 Lscmd>cmd.exe Microsoft Windows [版本 6.1.7601] 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。 C:\Users\Lellansin> |
执行cmd.exe之后发现程序切换到了cmd之下,这个时候我们在打开第二个 lscmd ,(注:如果你的 lscmd 放在path路径之下的话,博主的也就是F:\mytools,可以直接 win+r 调出运行,输入 lscmd 回车即可调用出来)
在我们的第二个CMD工具集中使用ps查看进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Lellansin's CMD Tool [版本 0.0.1] 学习自制 (c) www.lellansin.com 欢迎交流 Lscmd>ps 进程ID 父进程 线程数 优先级 进程名 ... 其余省略 ... 1676 1956 36 8 explorer.exe 7904 1676 1 8 lscmd.exe 5844 7904 1 8 cmd.exe 4180 1676 1 8 lscmd.exe 5568 4180 1 8 ps.exe #通过观察进程列表可以发现我们使用lscmd打开的cmd.exe #其进程id是5844,那么我们用新鲜的kill.exe来试试它 Lscmd>kill 5844 #运行之后发现第一个lscmd中的cmd.exe退出来了,牛刀小试、程序ok |
1.首先工具集本身还缺乏一些过多的指令,比如我们常用的cd(切换目录)命令,我们可以继续自定义一些命令
2.实际上关于一个进程我们还可以再获取更多的信息,也即我们的ps命令可以修改一下多加一个参数。形如: ps 1676 这样调用时则列举出进程id为1676的搜有信息,这个信息可以有很多,包括其进程具体的:
3.kernel32.dll是windows的核心DLL,很多内核级别的API都需要从其中导出,其实上述的例子中关于查看进程信息的大部分介绍的都是该DLL导出的函数(如果上一个问题又解决的,仔细查看模块信息会可以找到到程序调用掉用的每一个DLL),不过不是在Windows API中而是在Tool help API中,需要引用的是 Tlhelp32.h (不是Windows.h)
关于进程一些信息操作,除了kernel32.dll中的Tool help API还有一个 PS API (从Psapi.dll中导出头文件为 psapi.h ),大家有兴趣的可以搜搜看,然后尝试用其中的函数来改写。
4.kill 命令需要我们通过一个进程的id来杀死它,有的时候找一个进程的id有些麻烦,不过找名字却很容易所以你可以考虑改写这个程序,让它可以通过名称来(etProcessIdByName函数)终止一个进程。
5.设置与获取环境变量( GetEnvironmentStrings,GetEnvironmentVariable 和 SetEnvironmentVariable等)属于支线部分,大家可以研究使用这个API来跳过设置PATH系统环境变量,直接弄一个程序内部的环境变量就可以了,这样程序的可移植性更高。
任何一个程序,想要运行那么必须为这个进程分配空间并且分配一个唯一的进程标识(进程ID),在上面的例子中我们有看到这样的额一串数据:
1 2 3 4 5 6 | 进程ID 父进程 线程数 优先级 进程名 1676 1956 36 8 explorer.exe 7904 1676 1 8 lscmd.exe 5844 7904 1 8 cmd.exe 4180 1676 1 8 lscmd.exe 5568 4180 1 8 ps.exe |
在其中一个lscmd中打开cmd,我们很直观的就能看到cmd.exe的父进程就是该lscmd.exe。
仔细看我们可以发现,我打开了两个lscmd程序,而这个两个进程的父进程则是explorer.exe(1676)即我们的桌面,不论是通过win+r调用还是双击打开,实际上都需要explorer为其新建一个进程来运行我们打开的程序。
概念上理解之后,剩下的就是熟悉API了,各位可以参照上面的 "更多改进" 来编写一些程序提高API的熟练度
大部分与进程或线程有关的函数可以在MSND的 Process and Thread Functions 中被找到。小部分,例如 PS API 中的一些函数就找不到了。
联系客服