调用KeResetEvent函数(在低于或等于DISPATCH_LEVEL级)可以立即获得事件对象的当前状态,但该函数会把事件对象重置为非信号状态。
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG signalled = KeResetEvent(event);
如果你对事件的上一个状态不感兴趣,可以调用KeClearEvent函数,象下面这样:
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); KeClearEvent(event);
KeClearEvent函数执行得更快,因为它在读取事件的当前状态后不设置事件为非信号态。
内核信号灯
信号灯 信号灯有一个初始值,表示有多少进程/线程可以进入,当信号灯的值大于 0 时为有信号状态,小于等于 0时为无信号状态,所以可以利用 waitforsingleobject 进行等待,当 waitforsingleobject等待成功后信号灯的值会被减少 1,直到释放时信号灯会被增加 1。sem_wait()为等待灯亮操作,等待灯亮(信号灯值大于0),然后将信号灯原子地减1,并返回。信号量控制线程数量:这里我用最通俗的话来讲解怎么用信号量控制线程数量,信号量首先我们可以把他当做一个容器,那么我们给他指定的多少个空间(线程数),当我们启动一个线程(给信号量资源数减一),那个空间就让他少一个,我们结束一个线程那个空间就多一个(给信号量资源数加一),当空间全部没有了的话,我们此时是在给信号量资源数减一的代码处,那么它就不会减,因为没有空间了,在这时他就会阻塞在那个代码处,等待到有空间能减一时,就自动通过并减一,如此循环那么线程数始终不会超过我们设定的最大线程数——信号量的这个解释只适用于控制线程数,其实信号量并不完全是这样(详细自己百度)。
内核提供了三个服务函数来控制信号灯对象的状态。(见表4-3) 信号灯对象应该在PASSIVE_LEVEL级上初始化:
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); KeInitializeSemaphore(semaphore, count, limit);
在这个调用中,semaphore参数指向一个在非分页内存中的KSEMAPHORE对象。count是信号灯计数器的初始值,limit是计数器能达到的最大值,它必须与信号灯计数器的初始值相同。
表4-3. 内核信号灯对象服务函数
服务函数描述
KeInitializeSemaphore

初始化信号灯对象
KeReadStateSemaphore
取信号灯当前状态
KeReleaseSemaphore
设置信号灯对象为信号态
如果你创建信号灯时指定limit参数为1,则该对象与仅有一个线程的互斥对象类似。但内核互斥对象有一些信号灯没有的特征,这些特征用于防止死锁。所以,没有必要创建limit为1的信号灯。
两个子线程在运行过程中,通过调用waitforsingleobject等待该互斥量,如果此时互斥量处于无信号态,则在等待线程被系统挂起,一直等到互斥量变为信号态才继续运行,waitforsingleobject返回的同时,将使互斥量再次变为无信号态。它会让服务线程在队列为空是时,进行等待,当有新的消息进入队列后,自动将线程唤醒。posix标准建议在调用sigwait()等待信号以前,进程中所有线程都应屏蔽该信号,以保证仅有sigwait()的调用者获得该信号,因此,对于需要等待同步的异步信号,总是应该在创建任何线程以前调用pthread_sigmask()屏蔽该信号的处理。
信号灯的所有者可以调用KeReleaseSemaphore函数释放信号灯:
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG wassignalled = KeReleaseSemaphore(semaphore, boost, delta, wait);
waitforsingleobject函数用来检测 hhandle事件的信号状态,当函数的执行时间超过dwmilliseconds就返回,但如果参数dwmilliseconds为infinite时 函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到waitforsingleobject有返回直才执行后面的代码。waitforsingleobject函数用来检测hhandle事件的信号状态,当函数的执行时间超过dwmilliseconds就返回,但如果参数dwmilliseconds为infinite时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到waitforsingleobject有返回值才执行后面的代码。 若type为一个符合二元函数对象的类型参数,则可通过typename type::first_argument_type表示它的第一个参数类型 对于普通函数,使用ptr_fun也可得到具备类型参数的函数对象 * 深度探索 迭代器的类型特征 若t为一迭代器类型,则可通过“iterator_traits ::特征名”得到相关类型参数,迭代器有以下特征: difference_type:表示两迭代器举例的类型 value_type:迭代器所指向数据的类型 pointer:迭代器所指向数据的指针类型 reference:迭代器所指向数据的引用类型 iterator_category:迭代器的分类标签 * 深度探索 例10-26 利用类型特征实现算法: * //将来自输入迭代器p的n个数值排序,将结果通过输出result输出 template class inputiterator, class outputiterator void mysort inputiterator first, inputiterator last, outputiterator result //通过输入迭代器p将输入数据存入向量容器s中 vector typename iterator_traits ::value_type s first, last 。
且由于其形成了链结构,还可据此为一个线程指定多个异常处理函数,异常发生后,操作系统会沿此链表调用各异常处理函数,直到某个异常处理函数修复了该异常或已达末尾。2.通过值捕获异常时系统将对异常对象拷贝两次(一次建立临时对象,一次拷贝--条款12),而且它会产生slicing problem,即派生类的异常对象被做为基类异常对象捕获时,那它的派生类的行为就被切掉了(sliced off),这样的sliced对象实际上是一个基类对象(当一个对象通过传值方式传递给函数,也会发生一样的情况)。有时不将“调用函数名字+各参数值,进入函数后各参数值,中间变量值,退出函数前准备返回的值,返回函数到调用处后函数名字+各参数值+返回值”这些信息写日志到文件中是无论如何也发现不了问题在哪里的,包括捕获各种异常、写日志到屏幕、单步或设断点或生成core文件、……这些方法都不行。
下面调用读取信号灯的当前状态:
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG signalled = KeReadStateSemaphore(semaphore);
非0返回值表示信号灯处于信号态,0返回值代表信号灯为非信号态。不要把该返回值假定为计数器的当前值。
内核同步对象(下)
互斥(mutex)就是互相排斥(mutual exclusion)的简写。内核互斥对象为多个竞争线程串行化访问共享资源提供了一种方法(不一定是最好的方法)。如果互斥对象不被某线程所拥有,则它是信号态,反之则是非信号态。当线程为了获得互斥对象的控制权而调用KeWaitXxx例程时,内核同时也做了一些工作以帮助避免可能的死锁。同样,互斥对象也需要与KeWaitForSingleObject类似的附加动作。内核可以确保线程不被换出,并且阻止所有APC的提交,内核专用APC(如IoCompleteRequest用以完成I/O请求的APC)除外。
互斥体mutex和事件对象eventwaithandler属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。std::recursive_mutex 与 std::mutex 一样,也是一种可以被上锁的对象,但是和 std::mutex 不同的是,std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。线程池在系统启动时即创建大量空闲的线程,程序将一个runnable对象 或callable对象传给线程池,线程池就会启动一个线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个runnable对象的run()或call()方法。
如果你需要长时间串行化访问一个对象,你应该首先考虑使用互斥(而不是依赖提升的IRQL和自旋锁)。利用互斥对象控制资源的访问,可以使其它线程分布到多处理器平台上的其它CPU中运行,还允许导致页故障的代码仍能锁定资源而不被其它线程访问。表4-4列出了互斥对象的服务函数。
表4-4. 互斥对象服务函数
服务函数描述
KeInitializeMutex
初始化互斥对象
KeReadStateMutex
取互斥对象的当前状态
KeReleaseMutex
设置互斥对象为信号态
为了创建一个互斥对象,你需要为KMUTEX对象保留一块非分页内存,然后象下面这样初始化:
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); KeInitializeMutex(mutex, level);
6.对象句柄继承:创建内核对象的时候可以指定security_attributes.binherithandle表示可继承(任何时候可以使用sethandleinformation修改可继承性等属性),创建子进程时指定createprocess的参数binherithandles为true,则子进程从父进程的对象表中拷贝所有可继承的对象到自己的对象表的相同表位置中(并增加引用计数),因为表项结构被完全拷贝且内核对象实际地址在地址空间后2g的内核地址段中,所以拷贝过来的表项完全有效,进而父子进程的可继承内核对象的句柄值完全相同,于是只要以任何方式将要继承的对象的句柄值跨进程交给子进程(创建子进程时的命令行参数、环境变量、共享内存、消息等手段),则后者可以使用。死锁主要发生在有多个依赖锁存在时,会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生.如何避免死锁是使用互斥量应该格。上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。
互斥对象的初始状态为信号态,即未被任何线程拥有。KeWaitXxx调用将使调用者接管互斥对象的控制并使其进入非信号态。
利用下面函数可以获取互斥对象的当前状态:
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG signalled = KeReadStateMutex(mutex);
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-112548-3.html
故以老旧舰试探