进程与线程

简介

一般将进程定义成一个正在运行的程序的一个实例,它由以下两部分构成。

  • 一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方。
  • 一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。

进程是有“惰性”的。进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。事实上,一个进程可以有多个线程,所有线程都在进程的地址空间中“同时”执行代码。为此,每个线程都有它自己的一组CPU寄存器和它自己的堆栈。每个进程至少要有一个线程来执行进程地址空间包含的代码。当系统创建一个进程的时候,会自动为进程创建第一个线程,这称为主线程。然后,这个线程再创建更多的线程,后者再创建更多的线程。。。如果没有线程要执行进程地址空间包含的代码,进程就失去了继续存在的理由。这时,系统会自动销毁进程及其地址空间。

对于所有要运行的线程,操作系统会轮流为每个线程调度一些CPU时间。它会采取循环(轮询或轮流)方式,为每个线程都分配时间片(称为“量程”),从而营造出所有线程都在“并发”运行的假象。

如果计算机配备了多个CPU,操作系统会采用更复杂的算法为线程分配CPU时间。

线程也有两个组成部分:

  • 一个是线程的内核对象,操作系统用它管理线程。系统还用内核对象来存放线程统计信息的地方。
  • 一个线程栈,用于维护线程执行时所需的所有函数参数和局部变量。

进程从来不执行任何东西,它只是一个线程的容器。线程必然是在某个进程的上下文中创建的,而且会在这个进程内部“终其一生”。这意味着线程要在其进程的地址空间内执行代码和处理数据。所以,假如一个进程上下文中有两个以上的线程运行,这些线程将共享同一个地址空间。这些线程可以执行同样的代码,可以处理相同的数据。此外,这些线程还共享内核对象句柄,因为句柄表是针对每一个进程的,而不是针对每一个线程。

可以看出,相较于线程,进程所使用的系统资源更多。其原因在于地址空间。为一个进程创建一个虚拟的地址空间需要大量系统资源。系统中会发生大量的记录活动,而这需要用到大量内存。而且,由于.exe和.dll文件要加载到一个地址空间,所以还需要用到文件资源。另一方面,线程使用的系统资源要少得多。事实上,线程只有一个内核对象和一个栈;几乎不涉及记录活动,所以不需要占用多少内存。

每个线程都有一个上下文,后者保存在线程的内核对象中。这个上下文反映了线程上一次执行时CPU寄存器的状态。大约每隔20ms,Windows都会查看所有当前存在的线程内核对象。在这些对象中,只有一些被认为是可调度的。Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的值载入CPU寄存器。这一操作被称为上下文切换。线程执行代码,并在进程的地址空间中操作数据。又过了大约20ms,Windows将CPU寄存器存回线程的上下文,线程不再运行。系统再次检查剩下的可调度线程内核对象,选择另一个线程的内核对象,将该线程的上下文载入CPU寄存器,然后继续。载入线程上下文、让线程运行、保存上下文并重复的操作在系统启动的时候就开始,然后这样的操作会不断重复,直至系统关闭。

系统只调度可调度的线程,但是事实上,系统中大多数线程都不是可调度的。例如有些线程对象的挂起计数大于0,这意味着该线程已被挂起,不应该给它调度任何CPU时间。可以通过调用CreateProcess或者CreateThread函数并传入CREATE_SUSPENDED标志来创建一个被挂起的线程。

除了被挂起的线程之外,还有其他很多线程无法调度,因为它们都在等待某种事件发生。例如,如果运行Notepad程序,但是并不输入任何东西,Notepad线程将什么都不做。系统不会给没有任务的线程分配任何CPU时间。当我们移动Notepad的窗口时,或者其窗口需要重绘内容时,或者我们在其中输入时,系统将自动使其线程变为可调度。这并不意味着Notepad线程将立即获得CPU时间。只不过Notepad线程有任务了,系统会在某个时刻抽时间调度它——当然,我们希望越快越好。
父进程结束不会使子进程结束,但是多线程中,主进程的退出,会使所有线程退出。

window进程与线程实例

进程的创建主要函数是CreateProcess()

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
#include <windows.h>
#include <stdio.h>
#define EXEC_FILE "c:\\windows\\system32\\notepad.exe"
#define EXEC_FILE1 "c:\\windows\\system32\\calc.exe"
int main()
{
PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { 0 };
si.cb = sizeof(STARTUPINFO);
BOOL bRet = CreateProcess(EXEC_FILE,
NULL, NULL, NULL, FALSE,
NULL, NULL, NULL, &si, &pi);
BOOL bRet1 = CreateProcess(EXEC_FILE1,
NULL, NULL, NULL, FALSE,
NULL, NULL, NULL, &si, &pi);
if ( bRet == FALSE )
{
printf("CreateProcess Error ! \r\n");
return -1;
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
Sleep(500000);
return 0;
}

通过上面的代码,我们可以创建了两个子进程,运行这个可执行文件之后,通过Process Explorer查看进程树

如上图所示,可以看到calc.exe和notepad.exe是CreateProcessTest创建的两个子进程。当父进程结束的时候,两个子进程并没有结束。
线程的创建主要函数是CreateThread()

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
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
printf("ThreadProc \r\n");
Sleep(500000);
return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
printf("ThreadProc1 \r\n");
Sleep(500000);
return 0;
}
int main()
{
HANDLE hThread = CreateThread(NULL,
0,
ThreadProc,
NULL,
0,
NULL);
HANDLE hThread1 = CreateThread(NULL,
0,
ThreadProc1,
NULL,
0,
NULL);
WaitForSingleObject(hThread, INFINITE);
WaitForSingleObject(hThread1, INFINITE);
printf("main \r\n");
CloseHandle(hThread);
CloseHandle(hThread1);
return 0;
}

通过上面的代码,我们创建了两个线程hThread和hThread1,通过pslist命令可以查看相应进程中的线程信息

如上图所示,CreateThreadTest进程id为6628,它产生了一个主线程和两个子线程,线程id分别为7996,1224和6452

  • 进程、进程中的DLL枚举以及进程的暂停与恢复
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
VOID CManageProcessDlg::InitProcessList()
{
m_ListProcess.SetExtendedStyle(
m_ListProcess.GetExtendedStyle()
| LVS_EX_FULLROWSELECT
| LVS_EX_GRIDLINES );
m_ListProcess.InsertColumn(0, "进程名");
m_ListProcess.InsertColumn(1, "进程ID");
m_ListProcess.SetColumnWidth(0, 150);
m_ListProcess.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER);
m_ListModule.SetExtendedStyle(
m_ListModule.GetExtendedStyle()
| LVS_EX_FULLROWSELECT
| LVS_EX_GRIDLINES );
m_ListModule.InsertColumn(0, "DLL名");
m_ListModule.InsertColumn(1, "DLL路径");
m_ListModule.SetColumnWidth(0, 100);
m_ListModule.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER);
}
VOID CManageProcessDlg::ShowProcess()
{
// 清空列表框内容
m_ListProcess.DeleteAllItems();
// 创建进程快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if ( hSnap == INVALID_HANDLE_VALUE )
{
AfxMessageBox("CreateToolhelp32Snapshot Error");
return ;
}
PROCESSENTRY32 Pe32 = { 0 };
Pe32.dwSize = sizeof(PROCESSENTRY32);
BOOL bRet = Process32First(hSnap, &Pe32);
int i = 0;
CString str;
// 循环获取进程快照中的每一项
while ( bRet )
{
m_ListProcess.InsertItem(i, Pe32.szExeFile);
str.Format("%d", Pe32.th32ProcessID);
m_ListProcess.SetItemText(i, 1, str);
i ++;
bRet = Process32Next(hSnap, &Pe32);
}
CloseHandle(hSnap);
}
VOID CManageProcessDlg::ShowModule()
{
// 清空列表框内容
m_ListModule.DeleteAllItems();
// 获取选中的进程号
int nPid = GetSelectPid();
// 进程ID为0则返回
if ( nPid == 0 )
{
return ;
}
MODULEENTRY32 Me32 = { 0 };
Me32.dwSize = sizeof(MODULEENTRY32);
// 创建模块快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, nPid);
if ( hSnap == INVALID_HANDLE_VALUE )
{
AfxMessageBox("CreateToolhelp32Snapshot Error");
return ;
}
BOOL bRet = Module32First(hSnap, &Me32);
int i = 0;
CString str;
// 循环获取模块快照中的每一项
while ( bRet )
{
m_ListModule.InsertItem(i, Me32.szModule);
m_ListModule.SetItemText(i, 1, Me32.szExePath);
i ++;
bRet = Module32Next(hSnap, &Me32);
}
CloseHandle(hSnap);
}
int CManageProcessDlg::GetSelectPid()
{
int nPid = -1;
POSITION Pos = m_ListProcess.GetFirstSelectedItemPosition();
int nSelect = -1;
while ( Pos )
{
nSelect = m_ListProcess.GetNextSelectedItem(Pos);
}
if ( -1 == nSelect )
{
AfxMessageBox("请选中要显示DLL的进程");
return -1;
}
char szPid[10] = { 0 };
m_ListProcess.GetItemText(nSelect, 1, szPid, 10);
nPid = atoi(szPid);
return nPid;
}
void CManageProcessDlg::OnBtnCreate()
{
// TODO: Add your control notification handler code here
}
void CManageProcessDlg::OnBtnTerminate()
{
// TODO: Add your control notification handler code here
}
void CManageProcessDlg::OnBtnStop()
{
// TODO: Add your control notification handler code here
int nPid = -1;
nPid = GetSelectPid();
// 进程ID为0则返回
if ( nPid == 0 )
{
return ;
}
// 创建线程快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nPid);
if ( hSnap == INVALID_HANDLE_VALUE )
{
AfxMessageBox("CreateToolhelp32Snapshot Error");
return ;
}
THREADENTRY32 Te32 = { 0 };
Te32.dwSize = sizeof(THREADENTRY32);
BOOL bRet = Thread32First(hSnap, &Te32);
// 循环获取线程快照中的每一项
while ( bRet )
{
// 得到属于选中进程的线程
if ( Te32.th32OwnerProcessID == nPid )
{
// 打开线程
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS,
FALSE, Te32.th32ThreadID);
// 暂停线程
SuspendThread(hThread);
CloseHandle(hThread);
}
bRet = Thread32Next(hSnap, &Te32);
}
CloseHandle(hSnap);
}
void CManageProcessDlg::OnBtnResume()
{
// TODO: Add your control notification handler code here
int nPid = -1;
nPid = GetSelectPid();
// 进程ID为0则返回
if ( nPid == 0 )
{
return ;
}
// 创建线程快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nPid);
if ( hSnap == INVALID_HANDLE_VALUE )
{
AfxMessageBox("CreateToolhelp32Snapshot Error");
return ;
}
THREADENTRY32 Te32 = { 0 };
Te32.dwSize = sizeof(THREADENTRY32);
BOOL bRet = Thread32First(hSnap, &Te32);
// 循环获取线程快照中的每一项
while ( bRet )
{
// 得到属于选中进程的线程
if ( Te32.th32OwnerProcessID == nPid )
{
// 打开线程
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS,
FALSE, Te32.th32ThreadID);
// 暂停线程
ResumeThread(hThread);
CloseHandle(hThread);
}
bRet = Thread32Next(hSnap, &Te32);
}
}
void CManageProcessDlg::OnBtnShowmodule()
{
// TODO: Add your control notification handler code here
ShowModule();
}
void CManageProcessDlg::OnBtnExit()
{
// TODO: Add your control notification handler code here
}
//调整权限使当前进程拥有"SeDebugPrivilege"权限
VOID CManageProcessDlg::DebugPrivilege()
{
HANDLE hToken = NULL;
BOOL bRet = OpenProcessToken(GetCurrentProcess(),
TOKEN_ALL_ACCESS, &hToken);
if ( bRet == TRUE )
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL,
SE_DEBUG_NAME,
&tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,
FALSE, &tp, sizeof(tp),
NULL, NULL);
CloseHandle(hToken);
}
}

  • 多线程以及临界区
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>
int g_Num_One = 0;
CRITICAL_SECTION g_cs;
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
int nTmp = 0;
for ( int i = 0; i < 10; i ++ )
{
// 进入临界区
EnterCriticalSection(&g_cs);
nTmp = g_Num_One;
nTmp ++;
Sleep(1);
g_Num_One = nTmp;
// 离开临界区
LeaveCriticalSection(&g_cs);
}
return 0;
}
int main()
{
InitializeCriticalSection(&g_cs);
HANDLE hThread[10] = { 0 };
int i;
for ( i = 0; i < 10; i ++ )
{
hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
}
WaitForMultipleObjects(10, hThread, TRUE, INFINITE);
printf("g_Num_One = %d \r\n", g_Num_One);
for ( i = 0; i < 10; i ++ )
{
CloseHandle(hThread[i]);
}
DeleteCriticalSection(&g_cs);
return 0;
}

linux的进程和线程

通过python可以轻易实现多线程和多进程的操作
通过pstree -p 命令查看父进程和子进程以及线程之前的关系

Reference

1.Windows和Linux下进程、线程理解