进程提供程序所需要的资源,如:数据、代码等等
进程扮演的角色仅仅是为当前程序提供资源,或者代码,这就是进程所提供的,当时程序运行的状态和进程没有关系,进程可以看做空间的概念
例子:
进程相当于一个房子,房子里面的东西,这些东西就是进程提供的;房子里面走来走去的人,和使用东西的人,就是线程
我们打开od,可以看到的这些东西就是进程
找到最后发现到 0x7FFEF000
结束(这里是个小Tips了)
一、内核空间分布:
在4GB内存的操作系统中,高2G的给内存空间操作系统(也就是内核)使用,这部分内存空间所有进程共享。
低2G的内存给各个进程使用,每个进程占有独立的内存空间,相互进程其内存之间并不影响。
所谓的进程不是一个文件组成,而是多个文件组成
1)、任何进程都是别的进程创建的
系统中的程序都是通过explorer.exe
创建的,这个程序调用了CreateProcess()
函数
2)、进程的创建过程
1、映射EXE文件
2、创建内核对象`EPROCESS`
3、映射系统DLL(ntdll.dll)
4、创建线程内核对象`ETHREAD`
5、系统启动线程
映射DLL(ntdll.LdrlnitializeThunk)
线程开始执行
举个例子,我们点击 A.exe 就会调用 CreateProcess() 函数
第一步:映射EXE文件
通过上面的图片知道我们程序映射的话不能放前64k,也不能在后64k,但是其实PE中就有写到需要放到哪里
假设从这开始,然后我们画图,写入进程A
第二步:创建内核对象 EPROCESS
每个对象都有自己的 EPROCESS 进程对象,也就是结构体
(无需深究)
第三步:映射系统DLL(ntdll.dll)
也就是说,不管什么进程创建的时候,都会有个 ntdll.dll,然后继续把ntdll映射进去
第四步:创建线程内核对象 ETHREAD
每个进程中都会自动创建一个线程,跟进程一样,因为一个进程就得有一个线程,启动一个进程就有EPROCESS 结构体
然后进程自动创建一个线程对象 ETHREAD
第五步:系统启动线程
每一个进程都是由一堆PE来的,比如一个EXE
带了一堆DLL
,然而dll
不是随便乱带的
因为PE创建中可能只使用到了 test.dll
中的某个函数,但是 test.dll
可能又带了 love.dll
中的东西
这时候发现进程只是一个空间的概念,真正用的人是线程
因为创建进程的时候提到了PROCESS_INFORMATION
这个结构体
这个结构体里面存在两个值,一个是句柄一个是id
什么是句柄?什么是id?就得深入学习句柄表了
像进程、线程、文件、互斥体、事件等在内核中都有一个对应的结构体,由内核负责管理。我们管这样的对象叫做内核对象
比如说我们创建了一个进程,如何内核层,也就是0环那边给了 EPROCESS 结构体
然后我们在这个进程内 再创建 进程,线程,事件,文件,然后又分别分配了各自的内核结构
在0环中有个EPROCESS结构体,结构体里面有个指针,指向了句柄表,我们创建了之后会在EPROCESS结构体中的句柄表写入地址
然后我们想用A的时候,就输入1,我们想用B的话就输入2,这就是句柄
注意:不是每个内核对象都有表的,只有内核对象有句柄表,简单的说,只有EPROCESS
这个才有
A进程通过CreateProcess创建了一个进程,B进程通过OpenProcess打开别人创建好的进程
两个进程用的是同一个内核对象,A进程中有个句柄表,然后A内核对象索引为 1,就可以索引使用,C进程也是通过句柄表的值
如果再有创建,那么A内核对象计数器会 加 1;CloseHandle其实就是减计数器,当计数器为0的时候就减小了
注意:句柄表是私有的
微软除了使用上面那个方式共享内核对象,还可以使用另外一种方式进行共享
实际上句柄表有三列,而这还有一列是代表着是否可以被继承
从这边可以知道子进程可以继承父进程的句柄表,但并不是句柄表全部都能继承,而是允许继承的才会继承
这边就可以知道 索引为 2和4 的可以被继承,当不被继承的时候,就会在子进程填0
什么是线程?
1、线程是附属在进程上的执行实体,是代码的执行流程
进程 本身是空间上的概念,代表4GB的虚拟内存,线程代表着时间概念,也就是说,线程是当前运行的代码
在某个时间点只能有一段代码执行,但是cpu切换的快,所以看着像同时运行,这是单核的,所以单核的情况下并不是真正的多线程
因为线程执行的话是需要一套寄存器的,比如eax,ecx,ebx…
2、一个进程可以保护多个线程,但一个进程至少要包含一个线程
我们现在想创建一个线程了,其实main函数这边就是一个线程了,但是我们需要再创建一个呢?
需要使用到新的api,CreateThread
CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全描述符,判断子进程是否可以继续父进程的句柄表,NULL就可以了
IZE_T dwStackSize, //初始堆栈,如果不填写就会自动填写默认的
LPTHREAD_START_ROUTINE lpStartAddress, //当前的线程要执行的代码
__drv_aliasesMem LPVOID lpParameter, //要创建的线程的参数,这是个指针
DWORD dwCreationFlags, //创建线程的标识
LPDWORD lpThreadId //这个是OUT类型参数,这个参数说明是往外传递结果的,这个就是返回创建的线程ID,返回值是当前线程的句柄
);
dwCreationFlags标识:
由于是返回值是句柄,所以我们要声明一个句柄变量进行接收,也可以不接收CloseHandle()
不想用的话可以用这个函数关闭,但是这里的关闭其实是减掉一个计数器
线程的内核对象计数器为0也不会关闭,关闭线程的两个必要条件
①、线程的内核对象计数器为 0
②、线程的执行代码执行完毕了
#include <stdio.h>
#include <windows.h>
int main()
{
HANDLE hThread = CreateThread(NULL, 0, 0, NULL, 0, NULL);
CloseHandle(hThread);
for (int i = 0; i < 100; i++)
{
Sleep(500);
printf("------- %d --------\n", i);
}
getchar();
return 0;
}
所以我们这边CloseHandle
掉线程也是没有影响的,并不会影响到线程,因为当前代码没执行完后是没有人能关掉的
然后看向CreateThread的第三个参数:lpStartAddress
而这个参数是要执行的代码,然而这个代码是不能随便乱写的,是有格式的
#include <stdio.h>
#include <windows.h>
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
return 0;
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, 0, NULL, 0, NULL);
CloseHandle(hThread);
for (int i = 0; i < 100; i++)
{
Sleep(500);
printf("------- %d --------\n", i);
}
getchar();
return 0;
}
然后我们在这个ThreadProc
函数中写上代码,我们依然写入for循环来
然后基本就类似是同时运行的,所以以后每创建一个线程就必须要使用 CreateThread函数来
这个函数就是告诉代码在哪里,返回值啥的就需要强转就可以了
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(500);
printf("++++++++++++++ %d \n", i);
}
return 0;
}
在ThreadProc() 函数内小改动一下,main函数中指定一下要几次
int main()
{
int n;
n = 10;
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL);
CloseHandle(hThread);
for (int i = 0; i < 100; i++)
{
Sleep(500);
printf("------- %d \n", i);
}
getchar();
return 0;
}
线程参数可以传任何参数,传进去后转型一下就好了,然后运行一下,发现成功了
因为昨天看到有人对这个进程创建有点迷茫,大家学到的东西都是基于有用的情况下,而不是扯到什么硬件(我也有点懵)
学python创建进程等等 都是这个意思,希望能帮助各位大表哥
用户名 | 金币 | 积分 | 时间 | 理由 |
---|---|---|---|---|
veek | 150.00 | 0 | 2020-10-30 10:10:03 | 一个受益终生的帖子~~ |
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.