驱动学习----同步(1)----内存管理之知识扩展

本文深入探讨驱动程序中的内存管理及同步技术,包括IRQL、自旋锁、事件、互斥、信号量等核心概念。通过具体实例解析,帮助读者理解如何在驱动程序中实现线程同步与互不影响,以及中断级(IRQL)在系统中的作用。内容覆盖自旋锁原理、中断级应用、事件、互斥和信号量的使用,旨在构建全面的驱动程序开发知识体系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

驱动学习----同步(1)----内存管理之知识扩展

(2010-04-27 02:25:13)

同步(1)

我们先暂停内存管制这方面的内容,先来看看几个我们必须关注的知识:IRQL(中断级),同步,异步。

这3个知识点我在前面已经稍微讲过。但是还不是很具体。这几节我们深入一些。等到第5章的时候,我们从代码的角度来分析这几节我们介绍的函数。需要说明的是,从内存管理这一章节开始,我的博客才是比较系统的介绍驱动方面的知识。因此我把内存管理作为第一章来规范起来。

想必读者突然感觉有点寒了,因为内存管理介绍的内容还不到其总共的三分之一就耗费了7节,那等到第5章的到来要猴年马月呀。呵呵,这点读者可以不必太担心,因为至少比我好多了,我写博客之前还要自己先学习,分析,研究,如果遇到不懂的地方还要百度搜索。等到确认不会出大错误后还要用大家最容易理解的方式写出来。这是我对自己的要求!

很多朋友劝我别把自己辛苦理解的东西都写出来,至少说在驱动这块,很多东西还是不是那么容易理解的。我也曾经有过自私的想法。因为我在学习中也走过无数的弯路,耗费大量的精力去理解,而我现在把我所理解的东西如此直白的公开出来的确需要很大的勇气。不过我觉得,公开出来还是必要的,一方面可以方便有需要的人,另一方面我在理解的时候肯定会出现错误,那么好心的读者可以提出来给予纠正。作为初学者,我的博客就是我的笔记,我写的如此详细也是为了我自己,为了我的朋友!我希望我能坚持走下去,我也一定会走下去!

很多读者可能会担心其进度。关于这一点我不敢保证。因为有些东西还真的不是那么容易理解。作为85年出生的我要关心的问题很多!总是感觉烦心的事天天有,这些年异常的多。呵呵。我曾经和我父母说过,如果我年轻两三年就好了。至少我烦心的事少很多。哎,人活着还是从现在做起,不要总是活在抱怨中吧!至少我现在才85年,才25岁!

既然谈到进度问题,我有自己的想法,我们现在详细介绍所遇到的所有函数和知识点并不是浪费时间,这是为以后打好基础。不信你随便翻到一页,比如第93页,第257页等等。你是不是觉得这些新函数都是由老函数所实现的?呵呵,毛德操这本书我已经略微的看过一遍了,看完之后我才下决定把笔记补上。这也就是为什么我很大程度上能把知识连贯起来。

能看到我博客的朋友都是有缘人,我不会故意把自己的博客散出去,我也不会在乎有多少人会发现我的博客,我同样也不会太在意别人对我博客的评价!当然我希望我的心血能有所回报----读者能真正看懂,并且能够有所收获!!

现在开始谈谈同步和异步:

什么是同步?什么是异步?我举个例子就很容易理解了,有两种情况:

1,比如说我和读者都生活在北京,我们打算约好去故宫玩。由于我们住的地方不同,那么我们先电话联系说:“咱们先在国贸集合,之后一起坐地铁到故宫吧”

2,同样都在北京,同样住的地方不同,但是电话中是这样说的:“我坐公交直接到故宫,而你坐地铁吧”

很明显第一种情况就有同步的意思在里面。第二种情况完全就是异步,因为我们两个人各走各的,互不影响。请读者重视“互不影响”这4个字。我们接下来所要研究的一切就是为了避免“相互影响”。因为我们绝不想看到一个线程的运行会影响到其他线程(这里的影响是无意的,是不希望发生的,还请读者明白这个意思)。

那么如何同步呢?有哪几种方法呢?

学过应用程序编程的读者肯定知道:EVET,MUTEX,SEMAPHORE.但是内核中不仅可以使用这3个,还可以使用自旋锁,中断级。前3个的中文名字分别是:时间,互斥,信号量。

从下节开始,我们分别这5种方法展开介绍。先看中断级

****************************************下节开始中断级*************************************


驱动学习----同步(2)---内存管理之扩展知识

(2010-04-27 07:12:15)

同步(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章。

计算机启动的具体步骤我会在后面介绍。学完之后可以理解鬼影病毒的基本原理。

***********************************下节开始介绍函数调用机制***************************************



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值