Windows_内核对象
一、概念:
1.1、句柄是属于当前进程的。而句柄对应了一个内核对象。
1.2、任意一个进程都可以来使用任意的内核对象,进程只有使用的权限。
1.3、使用计数:为了使得多个进程可以使用同一个内核对象,节约系统资源。并且可以使得内核对象能够顺利的释放。
1.4、每个都有一个内核对象安全性的设置(一般设置成null,让他继承当前进程中的安全性):
能够决定哪些用户可以访问当前内核对象的权限(有不同的用户级)、当一个进程设置了一个内核对象的安全性设置之后,其他进程想要访问这个内核对象的话
必须要符合前一个进程所设置的安全性设置(比如说一个进程对file这个内核对象进行了写的设置,其他进程将无法进行读的操作)。
1.5、进程内核对象:在R3层中的进程仅仅是R0层中进程内核对象的一个镜像。
1.5.1、在进程内核对象中存在一个句柄表(索引,指向内核对象的指针,标识)。
1.5.2、R3层是全开放的。
1.5.3、R0层是系统及软件运行的地方
1.6、句柄是不能够夸进程而存在的。句柄只是在当前进程中有效。
1.7、API会发送一条指令,比如说CreateFile,会启动一个file的Obj(内核的Obj),这个Obj有自己的地址,而且会生成一个索引(以4字节的方式进行递增),
同一内核对象在不同进程中的索引值是不同的,句柄就是一个索引。
1.8、整个进程:当系统启动之后、进程会去指定自己的空间、同时会在自己的内核对象当中开辟一段空间(这段空间主要用于存放句柄表)。
1.9、当内核对象完成了初始化、当整个进程完成了初始化:此时操作系统会帮我们启动一条叫MainThread的主线程,最开始会来CreateThread。
1.10、会创建一个主线程内核对象,这时候会去句柄表中找空白的区域,当找到空白的区域之后,就会将主线程句柄的地址放入到当前句柄表里面、索引被填充。
1.11、CloseHandle可以将使用的对象进行关闭,对于R3层来说有意义的就只是索引。
句柄表存在的意义在于:当我们忘记关闭某一个在当前进程中使用到的内核对象的时候,操作系统可以根据句柄表来进行内核对象中使用计数进行减,如果此时
没有任何进程使用当前的内核对象的话,操作系统会对此内核对象进行回收。因为在当前进程中存在着这个句柄表,而句柄表记录着当前进程所使用到的所有的内核对象使用情况。
1.12、内核对象的Signal状态:初始化的时候一般FALSE。
1.13、内核对象有:进程、线程、标准输入输出流、事件内核对象、互斥体、信号、可等待计时器、作业。
1.14、所有的这些内核对象中都存在一个Signal(受信状态),有信号状态时,是可调度情况(在执行的线程是不可调度状态)。
1.15、Wait的三种结果:
case WAIT_TIMEOUT: printf("!等待超时\r\n"); break; case WAIT_OBJECT_0: printf("成功等待\r\n"); break; case WAIT_FAILED: printf("失败\r\n"); break;
1.16、WaitForMultipleObjects,是永远都获取不到二号线程的完成标识的。
WaitForSingleObject();函数会使得当前线程变成不可调度的状态。
1.17、事件内核对象:自动重置、手动重置。
1.18、
HANDLE WINAPI CreateEvent( _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, //安全扫描 _In_ BOOL bManualReset, //手动或自动重置 _In_ BOOL bInitialState, //有信号还是无信号 _In_opt_ LPCTSTR lpName //创建的事件内核对象的名字,可以为null );
1.19、CreateEvent配合WaitForSingleObject使用:
1.19.1、手动:当bManualReset参数设置为TRUE的话,WaitForSingleObject函数不能改变事件内核对象的信号状态。
此时我们只能手动的进行信号状态的改变,ResetEvent函数使得无信号,SetEvent函数使得有信号。
1.19.2、自动:当bManualReset参数设置为FALSE的话,WaitForSingleObject函数自动改变事件内核对象的信号状态。
1.20、SetEvent(HANDLE hEvent) //函数将该事件设为通知状态
ResetEvent(HANDLE hEvent)//函数将该事件设为未通知状态
已通知可执行,未通知等待
1.21、在我们开发的时候可能需要,在某一些时间点上启动某个线程。或者按照某些频率来启动某些线程。
1.22、可等待计时器内核对象:
HANDLE WINAPI CreateWaitableTimer( _In_opt_ LPSECURITY_ATTRIBUTES lpTimerAttributes, _In_ BOOL bManualReset, //一般设置为FALSE,自动 _In_opt_ LPCTSTR lpTimerName );
自动的时候,当每碰到一个Wait函数的时候就会改变当前信号状态。
1.22.1、创建完成,是没有信号的状态。
1.22.2、设置有信号:
BOOL WINAPI SetWaitableTimer( _In_ HANDLE hTimer, _In_ const LARGE_INTEGER *pDueTime, //UAC时间,指定被触发有信号的时间(纳秒)。 _In_ LONG lPeriod, //以毫秒为单位,每过多少时间触发有信号。 _In_opt_ PTIMERAPCROUTINE pfnCompletionRoutine, _In_opt_ LPVOID lpArgToCompletionRoutine, //传入pfnCompletionRoutine中的第一个参数 _In_ BOOL fResume );
pfnCompletionRoutine参数:
VOID CALLBACK TimerAPCProc(
_In_opt_ LPVOID lpArgToCompletionRoutine,
_In_ DWORD dwTimerLowValue,
_In_ DWORD dwTimerHighValue
);
这个函数会被压入SetWaitableTimer线程的APC中。当线程空闲下来的时候,会去清理APC中的一些东西
SleepEx(INFINITE, TRUE);//回调函数 TimerAPCProc会被调用,只有调用SleepEx函数才会被调用
1.22.3、while(WaitForSingleObject(hTimer, INFINITE) != WAIT_TIMEOUT){}:操作系统中的算法尽可能公平。
1.23、信号量:决定了线程可以启动多少次。
1.23.1、
HANDLE WINAPI CreateSemaphore(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
_In_ LONG lInitialCount,
_In_ LONG lMaximumCount,
_In_opt_ LPCTSTR lpName
);
1.23.2、这个内核对象中存在一个count,这个count决定了能够Wait多少次。
1.24.3、
BOOL WINAPI ReleaseSemaphore( //这个函数可以在初始化的基础上设置count的大小 _In_ HANDLE hSemaphore, _In_ LONG lReleaseCount, //在初始化的基础上加上多少count。 _Out_opt_ LPLONG lpPreviousCount );
1.24、互斥体(常用且特殊):
1.24.1、在每个互斥体中会保存一个线程ID的对象,而线程ID将决定这个内核对象时有信号状态还是无信号状态。
线程ID为0时是处于有信号状态,线程可以被激活,当这个线程被激活之后会将激活它的这个线程ID转递到内核对象当中。
此时使得这个内核对象变成无信号状态。
1.24.2、和关键段达到一样的效果,可以来做线程的同步。
1.24.3、
HANDLE WINAPI CreateMutex( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, _In_ BOOL bInitialOwner, //初始化这条线程的时候是否拥有它,换句话说:在初始化的时候是否将当前这条线程的线程ID专递进去。 //如果专递了这个线程ID,那创建处理的这个互斥体就是一个无信号状态。 _In_opt_ LPCTSTR lpName );
1.24.4、互斥体是和线程绑定的,对于当前创建这个Mutex这个线程来说,他已经拥有了这个Mutex的权限。所以在当前这个线程中不管来Wait多少次,都是有信号的状态。
1.24.5、线程ID -> 非0(无信号)、0(有信号):当某一个非当前线程的线程Wait之后就会对线程ID进行填充,使得这个Mutex变成无信号状态。
此时进行ReleaseMutex(hMutex);释放,这个Mutex又会变成有信号状态。
1.24.6、互斥体中还有一个等待计数:Wait多少次就要释放多少次。
1.24.7、哪个线程进行了Wait就需要在哪个线程进行释放,因为释放的时候会检查线程ID是否符合
1.24.8、当线程消亡了,Mutex会变成有信号状态。
1.25、内核对象时属于内核所有的,而内核属于操作系统的。
1.25.1、所有的内核对象都是能够跨进程的。如果在一个进程中使用了指定名称的Mutex,在另一个进程中就无法使用同一名称的Mutex,或延续使用