****************************************下节开始中断级*************************************
同步(2)
这节我们来讨论中断级(IRQL)。
关于中断级的概念,还有一些基础的知识,请参考前面我写的章节。这里不再重复。
笔者以前说过,运行在低中断级的代码是没有能力打断运行在高中断级的代码的执行。那么为什么微软要有这个限制?这样的特性有什么用?为什么中断级和同步会扯上关系?这些问题我一会慢慢解释
我们知道,中断分硬件中断,和软件中断两类。硬件中断还分可屏蔽和不可屏蔽。不可屏蔽中断是由CPU引脚(NMI)来接受。而可屏蔽中断是通过CPU引脚(INTR)来接受。关于不可屏蔽中断,我们不研究。我们只研究可屏蔽中断。可能读者会问为什么不研究不可屏蔽中断呀?道理很简单,我也不会!关于可屏蔽中断,涉及到2个8259A芯片,这2个芯片属于主从关系连结在一起之后再与CPU的INTR引脚相连。这2个芯片可以接受16个中断信号(分别接收8个)。以上说的是传统的机器。(以上之作了解,不必记忆)
现代的机器使用的是APIC(高级可编程控制器),一方面它兼容传统的方式,另一方面它把中断信号的个数扩展到24个。一旦某个中断发生,就会触发相对应的中断处理程序。现在这里有24个中断,那么系统中就会有24个中断处理程序。需要重点注意的是:这24个中断处理程序的中断级都是不同的。
现代机器的系统中都规定有32个中断级(0~31),其中数值越大,中断级越高。其中:
0----PASSIVE_LEVEL
1----APC_LEVEL
2----DISPATCH_LEVEL
3..........25之间的中断级正好对应上面说的24个硬件中断
26-----PROFILE_LEVEL
27----CLOCK1_LEVEL
28----CLOCK2_LEVEL
29----IPI_LEVEL
30----POWER_LEVEL
31----HIGH_LEVEL
其中0,1,2这3个中断级和软件中断有关。3到25和可屏蔽硬件中断有关。26到31和不可屏蔽硬件中断有关
而驱动程序中,我们只关心0,1,2这3个软件中断。因此以上信息也是只作了解。懂这个意思就行啦。
下面内容请务必记熟,我们继续
现在PASSIVE,APC,DISPATCH这3个家伙逐渐的浮出了水面,进入了我们视野。
首先,凡是应用程序线程都运行在PASSIVE中断级。驱动程序线程也运行在这个中断级的(或许中途临时提高中断级,但是还必须降下来)。驱动程序中DriverEntry,AddDevice,派遣函数一般都运行在PASSIVE中断级上(也可以临时提高,但还必须降下来)
windows中负责线程调度的代码运行在DISSPATCH中断级上,驱动程序的完成函数和StartIo函数也运行在DISSPATCH中断级上。
我一直强调一个概念:“代码仅仅是代码,函数仅仅是函数,他们是不会分高级还是低级。请注意我最上面用红字写的那段话” 一会找个例子证明这个观点。
到目前为止,中断级这块知识可以算告一段落。还请读者回忆下我以前讲过的知识点(内存管理4)。
几个和中断级有关的函数:
1,KeGetCurrentIrql():无参数,得到当前线程,当前时刻,当前代码所处的中断级
2,KeRaiseIrql(ULONG TargetLevel,PKIRQL OldIrql)
3,KeLowerIrql(KIRQL OldIrql)
用法很简单:
VOID test()
{
KIRQL OldIrql;
ASSERT(KeGetCurrentIrql()<=DISPATCH_LEVEL); / 如果当前中断级小于DISPATCH的话,提醒一下
KeRaiseIrql(DISPATCH_LEVEL,&OldIrql); /提升中断级到DISPATCH,为了将来还原,保留原始的中断级
........
.......自己想要加入的代码
KeLowerIrql(OldIrql); /一定要还原。不然只要这个线程没有结束,其他线程将得不到运行,后果很严重
}
**************************************开始介绍自旋锁***************************************
驱动学习----同步(3)---内存管理之扩展知识
(2010-04-29 00:52:14)
同步(3)
今天我们来讨论自旋锁。自旋锁的原理比较的简单。具体说来是这样的:
当你初始化一个自旋锁对象之后,此时此刻这个锁就处在“解锁”状态,现在任何线程都可以申请"获得"这个锁。一旦有线程成功获得这个锁之后,其他线程再次打算获得就会失败,直到此锁被释放。为什么叫自旋呢?因为其他线程在获取不成功的时候会不停的询问,直到获取成功。
由上面可知自旋锁是可以用来作为线程同步的手段。请回忆下,大家应该还记得内存管理(6)中已经涉及到自旋锁。OK,把那段代码拖过来:
KSPIN_LOCK spin_lock; // 自旋锁
KIRQL irql; // 中断级别
KeInitializeSpinLock(&spin_lock);
KeAcquireSpinLock(&spin_lock, &irql);
while(!IsListEmpty(&linkListHead))
{
PLIST_ENTRY pEntry = RemoveTailList(&linkListHead);
pData = CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry);
KdPrint(("%d\n",pData->number));
ExFreePool(pData);
}
// 解锁
KeReleaseSpinLock(&spin_lock, irql);
.......
....... /其他代码
.......
上面代码很简单,就现在读者的能力,应该觉得小儿科。不过我要说下原理:
1,当你使用KeInitializeSpinLock()函数初始化自旋锁之后,此时此刻的锁处在解锁状态,任何线程都可以获取。
2,当你使用KeAcquireSpinLock()成功获取锁之后,此时此刻这个线程的中断级立刻提升至DISPATCH_LEVEL。也就是说我用黄色标注的代码都运行在DISPATCH_LEVEL级别上
3,当你使用KeReleaseSpinLock()之后,此时此刻这个线程又回到了原始的中断级,也就是PASSIVE_LEVEL
联系上节我讲的知识,你完全可以把代码改写成:
KIRQL OldIrql;
ASSERT(KeGetCurrentIrql()<=DISPATCH_LEVEL); / 如果当前中断级小于DISPATCH的话,提醒一下
KeRaiseIrql(DISPATCH_LEVEL,&OldIrql); /提升中断级到DISPATCH,为了将来还原,保留原始的中断级
while(!IsListEmpty(&linkListHead))
{
PLIST_ENTRY pEntry = RemoveTailList(&linkListHead);
pData = CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry);
KdPrint(("%d\n",pData->number));
ExFreePool(pData);
}
KeLowerIrql(OldIrql);
从上面代码可以证明前面我所说的:代码就是代码,没有高低之分。仅仅只是当前系统中断级会随时变化,如果变高了,那么之后的代码抗打断能力就强。就好像上面这个例子,如果没有调用KeRaiseIrql()这个函数,那么黄色部分代码没有什么特别之处。就是因为调用了这个函数,黄色部分的代码才具备了更强的抗打断能力。
不过需要注意的是,由于上面的黄色部分已经处在DISPATCH级别上,并且线程的切换代码也是运行在DISPATCH上。因此线程切换将失去作用。其他线程将得不到运行。如果说你的While循环的时间太长,那么其他进程将出现假死状态。
还有,当一个线程成功获得锁之后,其他线程再次获取将会失败,但是令人讨厌的是失败之后线程不会睡眠,而是会不停的询问下去,那么导致获取锁失败的线程全速运行。如果在单核的电脑上也就算了,反正压根得不到运行。但是多核电脑上就不同了,那些“询问”代码将会运行的很HIGHT。
还有个注意点忘记提醒了。获取锁的操作只等价于把中断级提升至DISPATCH_LEVEL。
自旋锁就介绍到这里。或许有些地方和书上写的不是太一样。但是从代码的分析和自己的理解。我感觉我的理解方式是正确的。反正原理不会错,理解方面的好坏靠读者去判断。
驱动学习----同步(4)---内存管理之扩展知识
(2010-04-29 01:37:51)
同步(4)
不知不觉我们已经介绍了2种同步手段。从今天开始我们来研究“事件”,“互斥”,“信号量”。和自旋锁一样,这3个都是对象。因此用之前必须先实例化,如果有必要还要初始化。学过应用程序编程的读者肯定也对这3个有所了解。需要说明的是,应用程序的同步对象都是靠内核的同步对象实现的。其实话又说回来,何止是同步对象!WIN32 API函数的实现也是靠内核中相对应的函数实现的。
既然上面说到了应用程序,并且应用程序也可以使用这3个对象。那么不妨我们联系起来作个比较。不过还要先等等。急不得!
接下来的东西有点难理解。希望大家做好心理准备。如果你觉得理解起来容易,那么请别得意,这其实是笔者我的功劳 ~~~~哈哈 。先倒杯咖啡~~~~~
先申明。下面有些东西我并不是很有把握。不过我已经尽力去寻找尽可能靠谱的答案了。等将来笔者有了正确的答案的时候会及时更新这部分的内容。其实笔者也经常抱怨为什么书上不能讲清楚点,没有办法。很多东西人家微软不公开!以下是我自己的理解。先来谈谈进程
进程分为普通的用户程序进程,系统进程,内核进程
用户程序进程很容易理解,比如QQ,word。
系统进程:这些进程提供了一些最基本的服务,比如Csrss等等。如果这些进程丢失或者出现问题,有些服务将无法提供给用户。但是至少不会影响内核行为,比如说不会影响到线程的切换功能等等。(关于系统进程有哪些,你可以百度下,写的还算详细,可以拿过来参考)
内核进程:这个名字是我自己起的。这个词我以前在其他书上从未看到过。但是从很多地方已经表明windows里肯定会有这个进程(除非我理解错了)。
既然我们研究的是内核,那么就不去研究系统进程。并且为了名字上的统一我会把内核进程改称为系统进程。那么现在我们研究的目标就是:系统进程!
我是这样理解的,电脑启动之后,系统会先创建一个进程,这个进程就是系统进程(内核进程),同时系统将保留一段空间用来映射Ntoskrnl.exe文件(作为函数库而不是作为可执行程序),之后还会继续保留空间来分别映射HAL.dll,Win32k.sys文件,接下来系统会再次为要加载的驱动程序(包括杀毒软件驱动)保留空间以便映射。这样一来,一个活生生的进程创建好了。为什么我会这样理解?当我刚学驱动的时候就有个问题困扰着我:驱动程序运行之后会不会产生一个进程?之后得到的结论是不会产生新的进程(我从某本书上看到的,书名一时想不起来了!)。那么这个驱动程序肯定属于某个进程,并且是作为一个线程而存在于那个进程之中。现在问题来了,系统进程是哪个呢,名字是什么呢?我估计八成是System。这个进程你可以在进程管理器中看到(前提是你要点击“显示所有进程”按钮)。上面这些步骤由ntldr实现的,这个文件固定在活动分区的第一个扇区中。大名鼎鼎的鬼影病毒就是在这个文件上做了手脚,使杀毒软件驱动程序无法加载。
还有个地方需要注意,系统中也会存在Ntoskrnl.exe这样的进程。这个进程是保护性的进程,在你计算机反复启动的情况下出现。一般情况下你是看不见的。我估计这个进程也是映射Ntoskrnl.exe文件(作为可执行程序)。
补充说明下,EXE文件也是可以导出函数的。
以上说了半天,感觉还算是比较圆满的。至少到目前为止我还没有发现有知识间冲突这个现象出现。进程这个问题说好了。接下来研究代码执行的上下文!
代码执行的上下文这句话很是抽象。但是其重要程度之高几乎贯穿整个学习之中。
在探讨代码执行上下文之前,我们有必要先探讨下函数的调用机制!在这里我只是稍微谈下,具体还请大家参阅windows内核情景这本书的第2章。
计算机启动的具体步骤我会在后面介绍。学完之后可以理解鬼影病毒的基本原理。
***********************************下节开始介绍函数调用机制***************************************