【转帖】DDK学习笔记---入门

DDK学习笔记---入门

驱动程序的结构:
1.1 一个入口点(DriverEntry):用于创建设备对象及符号连接,以及其它初使化操作,如分配池内存等.
1.2 一个出口(DriverUnload):删除符号连接与设备对象,并释放已经分配的各种资源,如池内存等
1.3 几个DispatchHandler:用于响应Ring3程序的请求及其它驱动事件,并做相关处理

2.内存管理
2.1 分配系统池内存(ExAllocatePool):它有点像C中malloc,只不过存在分页和紧急选项
2.2 释放系统池内存(ExFreePool):它有点像C中的free
2.3 注意:Ring3中所有堆内存都是可分页的,实质上是因为Ring3程序的中断请求级非常低,当它访问分页内存时IO管理器有机会处理页错误中断,从而调 入需要的内存页,但在驱动程序中我们可以随时调整中断请求级以减少驱动程序运行的时间,这样当驱动程序的当前中断请求级高于DISPATCH_LEVEL 时,IO管理器没有机会处理页错误,导致内存访问异常(此时一般出现蓝屏)
2.4 如果需要频繁分配和释放相同大小的内存,可以使用后备列表来管理系统堆内存以提高性能

3.输出输出
3.1
IRP_MJ_DEVICE_CONTROL,响应系统的DeviceIocontrol
IRP_MJ_READ,响应系统的ReadFileEx
IRP_MJ_WRITE,响应系统的WriteFileEx

3.2访问输入输出参数:
3.2.1 Buffered方式:使用IoGetCurrentIrpStackLocation得到调用者堆栈区域指针IrpStack(PIO_STACK_LOCATION类型)
Irp->AssociatedIrp.SystemBuffer;输出输出缓冲
IrpStack->Parameters.DeviceIoControl.InputBufferLength;输入缓冲长度
IrpStack->Parameters.DeviceIoControl.OutputBufferLength;输出缓冲长度
IrpStack->Parameters.DeviceIoControl.IoControlCode;设备控制代码
如果需要输出参数,在填写完SystemBuffer后要设置IRP的IoStatus成员的Information指示输出数据的长度.
3.2.2 Direct方式:MmGetSystemAddressForMdlSafe映射IRP->MdlAddress地址到内核空间
MmGetMdlByteCount,得到MDL大小,以字节为单位.通常应该在IRQL<=DISPATCH_LEVEL的情况下使用MDL,那么如何输出呢?和Buffered方式一样从一个地方读,处理完再写到同一个地方吗?(问题1)
3.2.3 Neither方式:这个方式比恐怖,除非确定驱动不是分层的并且运行在PASSIVE_LEVEL级,一般不使用这种方式.比如写一个简单的Dump核心数据结构的驱动,该驱动只由我们的一个程序控制,那么可以直接把用户模式的地址传给驱动使用

注意:《Programming the Microsoft Windows Driver Model》一书说到"决不(或几乎从不)直接引用用户模式的内存地址"


4.安装KMD
4.1 需要Administrator权限,调用OpenSCManager打服务管理器,用CreateService以SERVICE_KERNEL_DRIVER类型将驱动安装为服务

5.启动KMD
5.1 可以在CreateService时指定SERVICE_AUTO_START自动加载,或是指定SERVICE_DEMAND_START安装类型再调用StartService手工加载

6.停止与卸载KMD
6.1 可以使用ControlService指定SERVICE_CONTROL_STOP停止驱动,然后可以使用DeleteService卸载驱动.最后CloseServiceHandle

7.Ring3访问KMD
7.1 需要Administrator权限,CreateFile打开设备对象
7.2 DeviceIoControl,与驱动进行交互操作,可以使用各种自定义的数据结构进行输入输出.
7.3 ReadFile,读驱动数据
7.4 Writefile,写数据给驱动
7.5 CloseHandle,关闭设备对象

8.过滤/挂钩IRP请求

8.1挂钩某个IRP处理函数:
a,调用IoGetDeviceObjectPointer返回的设备对象(PDEVICE_OBJECT)
b,由设备对象得到驱动对象PDEVICE_OBJECT->DriverObject(PDRIVER_OBJECT),这里是否应该调用 ObReferenceObjectByPointer函数增加驱动对象的引用计数,以防止该驱动程序在我们的驱动程序前被卸载呢?(问题2)
c,再由驱动对象得到中断请求派遣函数表(PDRIVER_OBJECT->MajorFunction)
d,保存PDRIVER_OBJECT->MajorFunction[IRP_MJ_XXXXXXX]值(原中断请求派遣函数地址)
e,修改PDRIVER_OBJECT->MajorFunction[IRP_MJ_XXXXXXX]值(让它指向我们自定义的函数),使用锁总线前缀lock的xchg指令进行赋值操作(让代码多线程和多处理器安全)
f,结束处理同System Service Hook

8.2过滤某个设备的IRP请求:
a,初使化IRP请求派遣函数表MajorFunction,将它们全都指向一个派遣函数DispatchAny
b,再为MajorFunction指定几个我们感兴趣的IRP请求派遣函数
c,得到设备对象指针:IoGetDeviceObjectPointer
d,将设备加到设备堆栈上:IoAttachDeviceToDeviceStack,并保存下层设备对象,以供IoCallDriver时使用.
e,在DispatchAny中将所有IRP传给下层驱动:IoSkipCurrentIrpStackLocation,IoCallDriver
f,在指定的几个请求派遣函数中对IRP进行处理,如果有必要,可以将IRP传给下层驱动.


思考:
a,知道了FileMon使用过滤型驱动原理后,发现它的确和我写的File监控驱动一样没有办法监视到USB和PGP盘的操作,因为都在程序中硬编码了 卷标名.在驱动中我挂接了所有的ZwXXXFile操作,然后使用ZwQueryObject得到句柄对应的对象属性,再得到卷标,最后匹配某个盘符(符 号链接)与该卷标对应,从而得到文件全路径名.


b,确定一个派遣函数在哪个驱动中:
   1.使用ZwQuerySystemInformation得到Module Lists
   2.使用8.1的方法得到某个设备派遣函数地址,比较函数地址是否在某个Driver Module的地址空间范围内.


c,挂接/Device/Tcp的IRP_MJ_DEVICE_CONTROL函数隐藏端口
   《Subverting the Windows Kernel》一书中使用了该方法,的确比较Cool(有点像我在Ring3挂COM接口的方法,都是通过修改某个FunctionTable中的项目来 实现),但是这是可以被b方法发现,如果用b方法查询某个函数地址没有存在对应的sys地址空间中,那么可以肯定rootkit也Hook了 ZwQuerySystemInformation使得我们得到了不真实的Module Lists.

想到一个方法可以针C对这样的完整性检测进行攻击,首先分配一块不可分页内存,在其中写入一些花指令和时间差反跟踪代码,然后再jmp到我们的函数中,最后让/Device/Tcp的IRP_MJ_DEVICE_CONTROL的函数指向这块内存.(可行吗?问题3)

初学驱动开发,感觉对386保护模式以及DDK还需要进一步了解,否则很多操作系统核心方面的东东无法深入了解,所以说基础很重要,Rootkit 或是Kernel Hacking只是这些基础技术的特殊应用.这两个星期中感觉自己学到了不少东西,得到了Eva无私帮助,无数次的问他都给我一一详细的解答,否则我没有 办法进步的这么快,虽然只是刚入门但至少可以看懂《Subverting the Windows Kernel》和《Windows Internals》中的大部分内容,在这里要特别感谢Eva和那些开放核心技术资料的人,你们才是真正的Hacker!

参考资料:
《MSDN 2001》
《The Undocumented Functions Microsoft Windows NT/2000》
《Windows NT(2000) Native API Reference》
《Four-F KMD教程》,罗云彬和刘松翻译
《Programming the Microsoft Windows Driver Model》
《Undocumented Windows NT》
《Undocumented Windows 2000 Secrets》
《Subverting the Windows Kernel》
《Windows Internals,4th》
《Developing Your Own Unix-Like OS On IBM PC》

转自:http://hi.baidu.com/thesum/blog/item/7c0a931b318bd61d8618bf93.html


///

DDK学习笔记---内核同步

同步--经常被人们所遗忘在角落的重要素.
同步真的有那么重要吗?很多人对此有疑问,我真的为此感到很不解,包括有人在多线程程序中使用全局变量,并使用非线程安全的语句来进行同步操作,虽然有了 那么些同步的意识,但做法还是不那么正确,所以觉得还是应该写一下,特别是在这篇笔记中写了些关于核心态代码同步的技巧.

现在思考一下一个爆破系统中的同步,假如有这样的程序(伪码),安置线程进入爆破现场安放爆破装置然后离开,引爆线程等安置线程离开后引爆爆破装置,使用一个全局BOOL型变量表示安放是否完成是否已经撤离爆破场(不考虑Sleep等让出CPU时间的操作).

BOOL bRetreat=FALSE;

IgniteThread;引爆线程
SetterThread;安置线程

SetterThread()
{
do
{
   bRetreat=FALSE;
   进入现场
   安放爆破装置;
   撤离现场;
   bRetreat=TRUE;
}
while(任务没有完成);

}

IgniteThread()
{
do
{
   if(!bRetreat)
    等待安置线程安放爆破装置
   引爆;
}
while(任务没有完成);

妈的while怎么显示不出来,Blogchina真有点奇怪
}

这段代码看起有问题吗?引爆线程会一直等待安置线程撤离现场才引爆啊!好像没有什么问题吧!如果这是核爆炸试验,而安置原子弹的是普通人而不是机器人,下面会让你浑身冒冷汗.

在x86系统上对于
if(!bRetreat)
这样的语句,特别当bRetreat是全局变量时,非常有可能生成这样的机器指令序列:
LTEST:
mov eax,bRetreat
test eax,eax
je LWAIT
引爆
LWAIT:
jmp LTEST

问题出来了,当
mov eax,bRetreat执行完成时,事实上线程是可能被打断的,而在这时bRetreat的值可能发生变化,然引爆线程却没有机会发现这些变化,
例如:
在mov eax,bRetreat执行时bRetreat的值为TRUE,而后在test eax,eax前引爆线程被打断,安放线程将bRetreat的值变设为FALSE,随后引爆线程又Resume了,它会直接执行引爆操作,因为此时 eax的值为TRUE,而此时安置线程(包括被安装线程控制的机器和人员)还没有离开现场.于是Boom.......一切灰飞烟灰.虽然可以直接使用 test bRetreat,FALSE代替前两条指令,但也不是多处理器安全的.

呵呵!当然在现实生活中我们不可能都成为原子弹引爆系统或神六飞船控制系统的设计师,但这还是会引起电脑上的核爆炸---BSOD!特别是在驱动程序中.

同步实例
我们经常要在KMD中做Hook操作,然后做一些操作过滤,和日志记录.而被Hook的函数在任何时间可能会被任何进程中的线程调用,这时我们的驱动在不 同进程的上下文里,同步不做好BSOD肯定是不可避免了.现在好像找到感觉了,不再那么怕在驱动使用多线程和动态内存,也不那么怕BSOD了.也许这些得 益于最近对内核态的同步的深入学习.

2.1 Hook系统函数后卸载驱动时产生BSOD?
通常在Hook系统函数时我们会这样做
NTSTATUS NTAPI HookedNtAPI(....)
{
NTSTATUS ns=RealNTAPI(...);
return ns;
}
当一个调用被阻塞时我们所在上下文的线程会被挂起等待函数调用完成,这时如果我们的驱动映像从内存中移除,那么当函数返回时肯定会出现一个内存访问引常引 起的BSOD.因为当RealNTAPI返回时所在的空间已经不可用,原来的HookedNTAPI点用的空间已经被释放.那么应该怎么做!当然是添加引 用计数,就像这个HookedNtAPI是一个COM对象一样,如果有调用被挂起就等待,直到引用计数为零时才允许退出.可以添加一个ULONG类型的全 局变量nSuspendCalls来表示全局的引用计数.在每个Hook函数中调用原系统函数前增加nSuspendCalls计数,函数退时减少计数, 当nSuspendCalls=0时允许卸载驱动.

ULONG nSuspendCalls=0;

....

NTSTATUS NTAPI HookedNtAPI1(....)
{
NTSTATUS ns=0;
InterlockedIncrement(&nSuspendCalls);
ns=RealNTAPI1(...);
InterlockedDecrement(&nSuspendCalls);
return ns;
}

NTSTATUS NTAPI HookedNtAPI2(....)
{
NTSTATUS ns=0;
InterlockedIncrement(&nSuspendCalls);
ns=RealNTAPI2(...);
InterlockedDecrement(&nSuspendCalls);
return ns;
}
....

而在驱动的Unload例程里我是这样做的
VOID
DriverUnload(
IN PDRIVER_OBJECT DriverObject
)
{
    UNICODE_STRING dosDeviceName;
    ULONG uWaits=100;//随便写
   
    //如果nSuspendCalls不等于0,就会一直等待
    while(InterlockedExchange(&uWaits,nSuspendCalls))
    {
KeSleep(200);
    }


    //
    // Delete the symbolic link
    //

    RtlInitUnicodeString(&dosDeviceName, DOS_DEVICE_NAME);

    IoDeleteSymbolicLink(&dosDeviceName);

    //
    // Delete the device object
    //

    IoDeleteDevice(DriverObject->DeviceObject);

    dprintf("[TdiMon] unloaded/n");
}
DriverUnload里使用多线程多处理器安全的互锁函数得到nSuspendCalls的值,如果不为0就让出CPU时间等待200毫秒,直到引用 计数为0驱动退出.通常不会出现不对称的调用情况,可以认为DriverUnload里的while不会产生死循环,因为当函数钩子被解除后引用计数就不 会再增加而是越来越少(当那些被挂起的函数返回后).KeSleep是怎么来的,好像没有类似Ring3里可用的Sleep函数啊!不知道有没有,反正我 没有找到,于是我就写了这样一个函数,它以毫秒为单位阻塞当前线程让出CPU.
void KeSleep(ULONG uMiniseconds)
{
KTIMER ktimer;
LARGE_INTEGER liTimerout;
liTimerout.QuadPart=-(LONG)(uMiniseconds*10000);
KeInitializeTimer(&ktimer);
KeWaitForSingleObject(&ktimer,Executive,KernelMode,FALSE,&liTimerout);
}
函数内的KeWaitForSingleObject会阻塞线程直到超时,其实使用KEvent之类的也行.

2.2 临界区
如果我们要在一个列表(由LIST_ENTRY组织起来的某种数据结构)中记录日志,同样的Hook函数也会在不同的进程/线程上下文中运行,所以对于列 表的操作也应该互斥,否则可能会在添加/摘除列表项时出现BSOD.这里可以使用的内核同步对象很多包括临界区对象,事件对象,互斥体,自旋锁...,需 要注意的是运行于DISPATCH_LEVEL级的代码是不应该被阻塞的.

2.3 生产者 VS 消费者
这种模型通常由一个线程/进程产生数据,另一个线程/进程负责对数据进行处理,在Ring3层我对于这些同步已经非常熟(早在1年多前就开始写多线程工作 代码),在Ring0层的一个应用实例是Eva给我的一个记录文件操作日志的驱动,其中Hook函数使用自旋锁对操作进行互斥并放入一个列表排队,而一个 系统工作线程(消费者)同样使用这个自旋锁将列表的操作进行互斥,并将列表中的记录写入文件.在写入线程中使用一个事件对象指示停止写入操作,同时又可以 将线程阻塞一秒,非常巧妙.然后进入对列表的互锁操作ExInterlockedRemoveHeadList,内核态有这么多进行同步的函数,这样的同 步方法明显不同于我在Ring3所做的,通常的Ring3层生产者/消费者模型都使用一个信号量来实现,可以初使化多个空位.这可以用来控制线程数量,或 是数据处理排队.

例如一个扫描程序可以由一个主调度程序来创建扫描线程,首先:
static HANDLE hSempMaxThread=CreateSemaphore(NULL,最大线程数,最大数程数,NULL);
然后在调度线程中:
void scheduler(...)
{
do
{
准备一下个IP地址及扫描参数;
WaitForSingleObject(hSempMaxThread,INFINITE);//减少信号量计数,原子操作
创建一个线程做为针对某个地址的扫描任务;
}(扫描任务没有完成);
}

在扫描线程中:
ScanTask(...)
{
取得扫描参数;
进行扫描工作;
ReleaseSemaphore(hSempMaxThread,1,NULL);//增加信号增计数,原子操作
}

程序退出时CloseHandle(hSempMaxThread);

当达到最大线程数时调度线程中的WaitForSingleObject会阻塞,直到有一个扫描任务子线程退出.当然这里应该考虑用户界面中退出应用程序的情况.

又比如一个流媒体传输程序:
static HANDLE hSempMaxQueue=CreateSemaphore(NULL,0,最大帧排队数,NULL);

void Capturer(...)
{
调用DirectShow CaptureGraphBuilder取得摄影头数据帧;
放入一个list容器,注意同步;
ReleaseSemaphore(hSempMaxQueue,1,NULL);//增加数据帧计数,原子操作
}

void FrameSender(...)
{
WaitForSingleObject(hSempMaxQueue,INFINITE);//减少数据帧计数,原子操作
从全局list容器里取像图像帧,注意同步;
发送到客户端;
}

上面的两个同步例子都比使用C代码直接操作全局变量要安全且高效,条件不满足时线程都会阻塞,而不会占用任何CPU时间.

在驱动层通常操作都需要同步,而不像Ring3程序那么随意,使用全局变量和随意的C代码进行同步的方法不可取,否则BSOD将常伴你左右,虽然内 核对象也以全局变量的形式出现,但对他们的访问都使用多线程多处理器安全的原子操作函数(KeWaitForSingleObject, KeReleaseSemaphore,ExInterlockedRemoveHeadList等).

下一篇DDK笔记打算写内存操作,

努力......

转自:http://hi.baidu.com/thesum/blog/item/1356b258cded9dda9d820464.html


///

Windows启动过程详解

一、Windows启动过程简版

Windows2000/XP的启动过程大致可分为5个步骤:预启动,启动,装载内核,初始化内核以及用户登录。

1.预启动
首先计算机通电进行自检,并由BIOS(即基本输入输出系统)完成基本硬件配置,然后读取MBR(主引导记录)检查硬盘分区表以确定引导分区,并将引导分区上的操作系统引导扇区调入内存中执行,此处即执行NTLDR(操作系统加载器)文件。
Windows2000/XP支持多重启动。它在安装时会首先将已存在的其它操作系统引导扇区保存为BOOTSECT.DOS文件(位于活动分区根目录 下),并修改系统引导扇区,以便系统启动时加载NTLDR文件,从而达到多重启动的目的。而Windows98则不具备这个功能,因此如果先装好 Windows2000/XP后再装Windows98会破坏掉Windows2000/XP的引导记录,导致2000/XP不能启动。
2.启动
1.首先进行初始化,NTLDR会把处理器从实模式转换为32位保护模式。
2.读取BOOT.INI文件。该文件位于活动分区根目录下,它的作用是使系统在启动过程中出现选择菜单,由用户选择希望启动的操作系统。如果选择启动 Windows2000/XP,NTLDR会继续引导进行以下过程;如果选择为非Windows2000/XP系统,NTLDR则会读取系统引导扇区副本 BOTSECT.DOS转入启动相应系统。其中[BOOT LOADER]即操作系统加载器,指定系统选择菜单默认等待时间和默认引导的操作系统。可手工修改或在控制面板中修改,为了保险起见,建议在控制面板中修 改。依次选择控制面板-〉系统-〉高级->启动和故障恢复,即可更改相关设置。(在WindowsXP中还有另一种方法,即运行msconfig (系统配置实用程序)。[OPERATING SYSTEMS]段指定操作系统列表,由双引号括起来的部分就是列表所显示的内容,可任意修改,使其更加个性化。形如MULTI(0)DISK(0) RDISK(0)PARTITION(1)格式的语句被称为ARC路径,它的格式为:MULTI()——指定磁盘控制器(若为SCSI控制器,则此处应替 换为SCSI());DISK()——指定SCSI设备编号(对于MULTI该处值始终为0);RDISK()——指定IDE设备编号(对于SCSI,此 处被忽略);PARTITION()——指定分区编号。除分区编号由1开始外,其余编号均从0开始。
参数/FASTDETECT表示禁用串行鼠标检测,是系统默认值。还有几个常见参数:MAXMEM——指定Windows2000/XP可用内存容量; BASEVIDEO——使用标准VGA显示驱动程序;NOGUIBOOT——启动过程中不显示图形屏幕;SOS——加载设备驱动程序时显示其名称。在操作 系统选择菜单中的中文字体由位于活动分区根目录下的BOOTFONT.BIN文件提供。
3.系统加载NTDETECT.COM文件。由它来检测机器硬件,如并行端口,显示适配器等等,并将收集到的硬件列表返回NTLDR用于以后在注册表中注册保存。
4.如果Windows2000/XP有多个硬件配置文件,此时会出现选择菜单,等待用户确定要使用的硬件配置文件,否则直接跳过此步,启用默认配置。硬 件配置文件是指保存计算机特定硬件配置的系统文件。可以创建多个不同的硬件配置文件以满足计算机在不同场合的应用。可以依次选择控制面板-〉系统- >硬件->硬件配置文件作出修改。
3.装载内核
引导过程开始装载Windows2000/XP内核NTOSKRNL.EXE。这个文件位于Windows2000/XP安装
文件夹下的SYSTEM32文件夹中。随后,硬件抽象层(HAL)被引导进程加载,完成本步骤。
硬件抽象层(HAL):隐藏特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多种平台上进行移植。
4.初始化内核
内核完成初始化,NTLDR将控制权转交Windows2000/XP内核,后者开始装载并初始化设备驱动程序,以及启动WIN32子系统和WINDOWS2000/XP服务。
5.用户登录
开始登录进程。由WIN32子系统启动WINLOGON.EXE,并由它启动LOCAL SECURITY AUTHORITY(LSASS.EXE)显示登录对话框。用户登录后,WINDOWS2000/XP会继续配置网络设备和用户环境。最后,伴随着微软之 声和我们熟悉的个性化桌面,WINDOWS2000/XP漫长的启动过程终于完成。

 

 

 

 

 

 

 

 

 

 

 

 

 

二、Windows启动过程详版

随 着技术的发展,我们能够见到的计算机硬件种类越来越多。以计算机上最重要的组件CPU来说,目前就有很多选择。当然,这里的选择并不是说AMD或者 Intel这种产品品牌,而是指其内部的体系结构。目前常见的CPU体系结构主要基于复杂指令集(Complex Instruction Set Computing,CISC)或者精简指令集(Reduced Instruction Set Computing,RISC),我们常用的Intel的Pentium、Celeron系列以及AMD的Athlon、Sempron系列都是基于复杂 指令集的,而这些基于复杂指令集的CPU还有32位和64位的寄存器数据带宽区别。关于这些指令集以及寄存器数据带宽之间的区别等内容比较繁杂,而且不是 本文的重点,感兴趣的朋友可以自己在网上搜索相关内容。因为CPU种类的不同,在不同CPU的系统中运行的Windows的启动过程也有一些小的不同。本 文将会以目前来说最普遍的,在x86架构的系统上安装的32位Windows XP Professional为例向您介绍。

基本上,操作系统的引导过程是从计算机通电自检完成之后开始进行的,而这一过程又可以细分为预引导、引导、载入内核、初始化内核,以及登录这五个阶段。

在继续阅读之前,首先请注意图1,这是Windows XP的操作系统结构,其中包括了一些在后台工作的组件以及经常和我们打交道的程序。在了解Windows XP的启动过程之前,对系统结构有一个初步概念是很重要的。

预引导阶段

当我们打开计算机电源后,预引导过程就开始运行了。在这个过程中,计算机硬件首先要完成通电自检(Power-On Self Test,POST),这一步主要会对计算机中安装的处理器、内存等硬件进行检测,如果一切正常,则会继续下面的过程。

 

如 果您的计算机BIOS(固化在计算机主板上芯片中的一些程序)是支持即插即用的(基本上,现阶段能够买到的计算机和硬件都是支持这一标准的),而且所有硬 件设备都已经被自动识别和配置,接下来计算机将会定位引导设备(例如第一块硬盘,设备的引导顺序可以在计算机的BIOS设置中修改),然后从引导设备中读 取并运行主引导记录(Master Boot Record,MBR)。物理硬盘是以扇区(sector)为单位来寻址的。Windows的安装程序会在安装的时候,将一些内容写入你 安装系统的那个硬盘的第一个扇区。这块内容就称为Master Boot Record(MBR).
MBR包括两块内容:
(1). Boot code;
(2). Partition table;
Boot code,也就是启动代码。这段代码是在系统启动的时候,BIOS完成了自检过程,选择了启动设备( 也就是你的某个硬盘),然后就将该磁盘的MBR读入内存,并且跳转到MBR所在地址,从而执行其Boot code.
Partition table,也就是分区表。该表只有4项(entry),因为MS的OS允许一个磁盘最多被分为4个主分 区(primary partition)。这里的分区表里面的内容就是这4个分区的相关信息,包括其起始的sector, 相应的标志等等。
对于启动过程而言,MBR的boot code会搜寻这个分区表,在其中查找带有可启动标志(也称为Active)的 分区,然后将该分区的第一个sector(也就是Boot sector)读入,并且执行其中的代码。
在安装程序写入Boot sector之前,需要获知其所在分区的文件系统类型(FAT? FAT32? NTFS?),然后写 入不同的Boot sector。为什么对于不同的文件系统需要不同的Boot sector呢?原因在于Boot sector的 任务,就是要载入OS的系统启动文件,而载入文件的过程,是需要文件系统参与的,所以对应于不同的文 件系统,在Boot sector里面就需要不同的文件系统支持代码,以次来完成系统文件的加载。

对于Windows 启动而言,需要加载的文件为Ntldr。
需要补充的是,boot sector里面对于文件系统的支持代码是“最小化”了的。毕竟boot sector的大小最多 也就只有512 bytes,要带有完整的文件系统是不大可能的。而且,我们的需求也很简单,只需要它能够 理解该文件系统,并且能够读取其中的文件就可以了,我们并没有写入文件的需求。
Boot sector将Ntldr加载完成之后,就跳转到Ntldr的入口处,接下去的任务,就交给Ntldr了。这时候, 系统还是运行在16位的实模式下,Ntldr会开启Paging,并转入32为保护模式。

引 导阶段又可以分为:初始化引导载入程序、操作系统选择、硬件检测、硬件配置文件选择这四个步骤。在这一过程中需要使用的文件包括:Ntldr、 Boot.ini、Ntdetect.com、Ntoskrnl.exe、Ntbootdd.sys、Bootsect.dos(非必须)。

初始化引导载入程序

在 这一阶段,首先出场的是ntldr,该程序会将处理器由实模式(Real Mode)切换为32位平坦内存模式(32-bit Flat Memory Mode)。不使用实模式的主要原因是,在实模式下,内存中的前640 KB是为MS-DOS保留的,而剩余内存则会被当作扩展内存使用,这样Windows XP将无法使用全部的物理内存。而32位平坦内存模式下就好多了,Windows XP自身将能使用计算机上安装的所有内存(其实最多也只能用2 GB,这是32位操作系统的设计缺陷。关于大内存的问题因为和本文的内容关系不大,因此这里不表,日后有机会再单独撰文介绍)。

接 下来ntldr会寻找系统自带的一个微型的文件系统驱动。大家都知道,DOS和Windows 9x操作系统是无法读写NTFS文件系统的分区的,那么Windows XP的安装程序为什么可以读写NTFS分区?其实这就是微型文件系统驱动的功劳了。只有在载入了这个驱动之后,ntldr才能找到您硬盘上被格式化为 NTFS或者FAT/FAT32文件系统的分区。如果这个驱动损坏了,就算您的硬盘上已经有分区,ntldr也认不出来的。

读取了文件系统驱动,并成功找到硬盘上的分区后,引导载入程序的初始化过程就已经完成了,随后我们将会进行到下一步。

操作系统选择

这 一步并非必须的,只有在您计算机中安装了多个Windows操作系统的时候才会出现。不过无论您的计算机中安装了几个Windows,计算机启动的过程 中,这一步都会按照设计运行一遍,只有在确实安装了多个系统的时候,系统才会显示一个列表,让您选择想要引导的系统。但如果您只有一个系统,那么引导程序 在判断完之后会直接进入到下一阶段。

如果您已经安装了多个Windows操作系统(泛指Windows 2000/XP/2003这类较新的系统,不包括Windows 9x系统),那么所有的记录都会被保存在系统盘根目录下一个名为boot.ini的文件中。ntldr程序在完成了初始化工作之后就会从硬盘上读取 boot.ini文件,并根据其中的内容判断计算机上安装了几个Windows,它们分别安装在第几块硬盘的第几个分区上。如果只安装了一个,那么就直接 跳过这一步。但如果安装了多个,那么ntldr就会根据文件中的记录显示一个操作系统选择列表,并默认持续30秒。只要您做出选择,ntldr就会自动开 始装载被选择的系统。如果您没有选择,那么30秒后,ntldr会开始载入默认的操作系统。至此操作系统选择这一步已经成功完成。

 

小知识:系统盘(System Volume)和引导盘(Boot Volume)有什么区别?

这 是两个很容易被人搞混的概念,因为根据微软对这两个名词的定义,很容易令人产生误解。根据微软的定义,系统盘是指保存了用于引导Windows的文件(根 据前面的介绍,我们已经清楚,这些文件是指ntldr、boot.ini等)的硬盘分区/卷;而引导盘是指保存了Windows系统文件的硬盘分区/卷。 如果只有一个操作系统的话,我们通常会将其安装在第一个物理硬盘的第一个主分区(通常被识别为C盘)上,那么系统盘和引导盘属于同一个分区。但是,如果您 将您的Windows安装到了其他分区中,例如D盘中,那么系统盘仍然是您的C盘(因为尽管Windows被安装到了其他盘,但是引导系统所用的文件还是 会保存在C盘的根目录下),但您的引导盘将会变成是D盘。很奇怪的规定,保存了引导系统所需文件的分区被叫做“系统盘”,反而保存了操作系统文件的分区被 叫做“引导盘”,正好颠倒了。不过微软就是这样规定的。

硬件检测

这一过程中主要 需要用到Ntdetect.com和Ntldr。当我们在前面的操作系统选择阶段选择了想要载入的Windows系统之后,Ntdetect.com首先 要将当前计算机中安装的所有硬件信息收集起来,并列成一个表,接着将该表交给Ntldr(这个表的信息稍后会被用来创建注册表中有关硬件的键)。这里需要 被收集信息的硬件类型包括:总线/适配器类型、显卡、通讯端口、串口、浮点运算器(CPU)、可移动存储器、键盘、指示装置(鼠标)。至此,硬件检测操作 已经成功完成。

配置文件选择

这一步也不是必须的。只有在计算机(常用于笔记本电脑)中创建了多个硬件配置文件的时候才需要处理这一步。

小知识:什么是硬件配置文件?为什么要用它?

这 个功能比较适合笔记本电脑用户。如果您有一台笔记本电脑,主要在办公室和家里使用,在办公室的时候您可能会使用网卡将其接入公司的局域网,公司使用了 DHCP服务器为客户端指派IP地址;但是回到家之后,没有了DHCP服务器,启动系统的时候系统将会用很长时间寻找那个不存在的DHCP服务器,这将延 长系统的启动时间。在这种情况下就可以分别在办公室和家里使用不同的硬件配置文件了,我们可以通过硬件配置文件决定在某个配置文件中使用哪些硬件,不使用 哪些硬件。例如前面列举的例子,我们可以为笔记本电脑在家里和办公室分别创建独立的配置文件,而家庭用的配置文件中会将网卡禁用。这样,回家后使用家用的 配置文件,系统启动的时候会直接禁用网卡,也就避免了寻找不存在的DHCP服务器延长系统启动时间。

 

如 果Ntldr检测到系统中创建了多个硬件配置文件,那么它就会在这时候将所有可用的配置文件列表显示出来,供用户选择。这里其实和操作系统的选择类似,不 管系统中有没有创建多个配置文件,Ntldr都会进行这一步操作,不过只有在确实检测到多个硬件配置文件的时候才会显示文件列表。

载入内核阶段

在这一阶段,Ntldr会载入Windows XP的内核文件:Ntoskrnl.exe,但这里仅仅是载入,内核此时还不会被初始化。随后被载入的是硬件抽象层(hal.dll)。

硬 件抽象层其实是内存中运行的一个程序,这个程序在Windows XP内核和物理硬件之间起到了桥梁的作用。正常情况下,操作系统和应用程序无法直接与物理硬件打交道,只有Windows内核和少量内核模式的系统服务可 以直接与硬件交互。而其他大部分系统服务以及应用程序,如果想要和硬件交互,就必须透过硬件抽象层进行。

小知识:为什么要使用硬件抽象层

硬 件抽象层的使用主要有两个原因:1,忽略无效甚至错误的硬件调用。如果没有硬件抽象层,那么硬件上发生的所有调用甚至错误都将会反馈给操作系统,这可能会 导致系统不稳定。而硬件抽象层就像工作在物理硬件和操作系统内核之间的一个过滤器,可以将认为会对操作系统产生危害的调用和错误全部过滤掉,这样直接提高 了系统的稳定性;2,多平台之间的转换翻译。这个原因可以列举一个形象的例子,假设每个物理硬件都使用不同的语言,而每个操作系统组件或者应用程序则使用 了同样的语言,那么不同物理硬件和系统之间的交流将会是混乱而且很没有效率的。如果有了硬件抽象层,等于给软硬件之间安排了一位翻译,这位翻译懂所有硬件 的语言,并会将硬件说的话用系统或者软件能够理解的语言原意转达给操作系统和软件。通过这个机制,操作系统对硬件的支持可以得到极大的提高。

硬 件抽象层被载入后,接下来要被内核载入的是HKEY_LOCAL_MACHINESystem注册表键。Ntldr会根据载入的Select键的内容判断 接下来需要载入哪个Control Set注册表键(图2),而这些键会决定随后系统将载入哪些设备驱动或者启动哪些服务。这些注册表键的内容被载入后,系统将进入初始化内核阶段,这时候 ntldr会将系统的控制权交给操作系统内核。

初始化内核阶段

当进入到这一阶段 的时候,计算机屏幕上就会显示Windows XP的标志了,同时还会显示一条滚动的进度条,这个进度条可能会滚动若干圈(图3)。从这一步开始我们才能从屏幕上队系统的启动有一个直观的印象。在这一 阶段中主要会完成这四项任务:创建Hardware注册表键、对Control Set注册表键进行复制、载入和初始化设备驱动,以及启动服务。

 

创建Hardware注册表键

首先要在注册表中创建Hardware键,Windows内核会使用在前面的硬件检测阶段收集到的硬件信息来创建HKEY_LOCAL_MACHINEHardware键,也就是说,注册表中该键的内容并不是固定的,而是会根据当前系统中的硬件配置情况动态更新。

对Control Set注册表键进行复制

如 果Hardware注册表键创建成功,那么系统内核将会对Control Set键的内容创建一个备份。这个备份将会被用在系统的高级启动菜单中的“最后一次正确配置”选项。例如,如果我们安装了一个新的显卡驱动,重启动系统之 后Hardware注册表键还没有创建成功系统就已经崩溃了,这时候如果选择“最后一次正确配置”选项,系统将会自动使用上一次的Control Set注册表键的备份内容重新生成Hardware键,这样就可以撤销掉之前因为安装了新的显卡驱动对系统设置的更改。

载入和初始化设备驱动

在 这一阶段里,操作系统内核首先会初始化之前在载入内核阶段载入的底层设备驱动,然后内核会在注册表的 HKEY_LOCAL_MACHINESystemCurrentControlSetServices键下查找所有Start键值为“1”的设备驱动 (图4)。这些设备驱动将会在载入之后立刻进行初始化,如果在这一过程中发生了任何错误,系统内核将会自动根据设备驱动的“ErrorControl”键 的数值进行处理。“ErrorControl”键的键值共有四种,分别具有如下含义:

0,忽略,继续引导,不显示错误信息。

1,正常,继续引导,显示错误信息。

2,恢复,停止引导,使用“最后一次正确配置”选项重启动系统。如果依然出错则会忽略该错误。

3,严重,停止引导,使用“最后一次正确配置”选项重启动系统。如果依然出错则会停止引导,并显示一条错误信息。

启动服务

系 统内核成功载入,并且成功初始化所有底层设备驱动后,会话管理器会开始启动高层子系统和服务,然后启动Win32子系统。Win32子系统的作用是控制所 有输入/输出设备以及访问显示设备。当所有这些操作都完成后,Windows的图形界面就可以显示出来了,同时我们也将可以使用键盘以及其他I/O设备。

 

接下来会话管理器会启动Winlogon进程,至此,初始化内核阶段已经成功完成,这时候用户就可以开始登录了。

登录阶段

在 这一阶段,由会话管理器启动的winlogon.exe进程将会启动本地安全性授权(Local Security Authority,lsass.exe)子系统。到这一步之后,屏幕上将会显示Windows XP的欢迎界面(图5)或者登录界面,这时候您已经可以顺利进行登录了。不过与此同时,系统的启动还没有彻底完成,后台可能仍然在加载一些非关键的设备驱 动。

随后系统会再次扫描 HKEY_LOCAL_MACHINESystemCurrentControlSetServices注册表键(还记得第一次扫描这里是在启动进行到那 一步的时候吗?),并寻找所有Start键的数值是“2”或者更大数字的服务。这些服务就是非关键服务,系统直到用户成功登录之后才开始加载这些服务。

小知识:为什么Windows XP的启动速度要比Windows 2000快

目 前所有Windows操作系统中,可能Windows 2000的启动速度是最慢的,这并不是因为计算机硬件的性能不够,而是因为Windows 2000的设计“先天不足”。为了弥补这一不足,微软在开发Windows XP的时候想出了一个新方法,那就是,所有不重要的设备驱动和服务都将在用户登录系统之后才加载和运行。也就是说,在系统启动过程中,加载和运行的程序全 部都是运行系统所必需的,这样才能用最短的时间显示出登录界面,供用户登录。而用户登录后系同才开始加载非关键组件。可以说,Windows XP启动速度的加快实际上是一种“投机取巧”的作法,不过这种作法确实相当有效。然而这种设计也带来了一些问题,例如有些朋友反映,为什么自己的系统已经 成功登录了,可是非要过好几分钟之后桌面上才会显示出任务栏以及桌面图标等内容。其实这就是因为在等待的这几分钟里,系统正在忙于处理那些不重要的服务和 组件。如果需要处理的内容太多,或者计算机的硬件配置不够强大,就有可能产生这种现象。

 

小知识:如何控制非关键服务的启动顺序

您 已经知道了,非关键服务是在用户成功登录之后才加载的,那么我们能否人为控制这些服务加载的顺序?这其实是很简单的。服务的启动顺序也是靠各自Start 注册表键的数值以及每个服务之间的依存关系决定的,假设服务A的运行必须依靠服务B,那么在服务B正常启动之前,服务A都无法成功启动。假设服务C的 Start键数值是3,而服务D的Start键数值是6,那么服务C将会优先于服务D启动(数值越小优先级越高)。

到这里,Windows XP的启动过程就算全部完成了。



<script src="http://www.zhizihua.com/blog/PLUGIN/copytofriends/copy.js" type="text/javascript"></script>




<script src="http://www.zhizihua.com/blog/PLUGIN/copytofriends/copy.js" type="text/javascript"></script>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值