Linux内核分析方法谈_第1页
Linux内核分析方法谈_第2页
Linux内核分析方法谈_第3页
Linux内核分析方法谈_第4页
Linux内核分析方法谈_第5页
已阅读5页,还剩30页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、尽管linux绝对是最流行的开源操作系统,但是相对于其他操作系统的漫 长丿力史來说,linux的丿力史非常短暂。在计算机出现早期,程序员是使用硬件语 言在裸硬件上进行开发的。缺少操作系统就意味着在某个吋间只有一个应用程序 (和一个用户)可以使用这些庞大而又昂贵的设备。早期的操作系统是在20世 纪50年代开发的,用来提供简单的开发体验。包括为ibm 701开发的 general motors operating system (gmos)和 north american aviation 为 ibm 709 开发的 fortran monitor system (fms)。在 20 世纪 60

2、年代,mit (massachusetts institute of technology)和一些公 司为 ge-645 开发了一个名为 multics (multiplexed information and computing service)的实验性的操作系统。这个操作系统的开发者z at&t 后来退出了 multics,并在1970年开发了自己的名为unics的操作系统。与 这个操作系统一同诞生的是c语言,c语言就是为此而开发的,然后它们使用 c语言对操作系统进行了重写,使操作系统开发具有可移植性。二十年后,andrewtanenbaum创建了一个微内核版本的unix®

3、;,名为minix (代表minimal unix),它可以在小型的个人计算机上运行。这个开源操作系 统在20世纪90年代激发了 linus torvalds开发linux的灵感(请参看图 1所示)。linux快速从一个个人项目进化成为一个全球数千人参与的开发项目。对于 linux来说,最为重要的决策之一是采用gpl (gnu general public license) 在gpl保护z下,linux内核可以防止商业使用,并且它还从gnu项口(richard stallman开发,其源代码要比linux内核大得多)的用户空间开发 受益。这允许使用一些非常有用的应用程序,例如gcc (gnu

4、compiler collection)和各种 shell 支持。nux的最大的好处z就是它的源码公开。同时,公开的核心源码也吸引着无数的 电脑爱好者和程序员;他们把解读和分析linux的核心源码作为自己的最大兴趣,把修 改linux源码和改造linux系统作为口己对计算机技术追求的最大目标。linux内核源码是很具吸引力的,特别是当你弄懂了一个分析了好久都没搞懂的问 题;或者是被你修改过了的内核,顺利通过编译,一切运行止常的时候。那种成就感真 是油然而生!而且,对内核的分析,除了出自对技术的狂热追求z外,这种令人生畏的 劳动所带來的回报也是非常令人着迷的,这也正是它拥有众多追随者的主要原因:

5、首先,你可以从中学到很多的计算机的底层知识,如后而将讲到的系统的引导 和硬件提供的中断机制等;其它,象虚拟存储的实现机制,多任务机制,系统 保护机制等等,这些都是非都源码不能体会的。同时,你还将从操作系统的整体结构中,体会整体设计在软件设计中的份量和 作用,以及一些宏观设计的方法和技巧:linux的内核为上层应用提供一个与具 体硬件不相关的平台;同时在内核内部,它又把代码分为与体系结构和硕件相 关的部分,和可移植的部分;再例如,linux虽然不是微内核的,但他把人部分的设备驳动处理成相对独立的内核模块,这样减小了内核运行的开销,增强了 内核代码的模块独立性。而r你还能从对内核源码的分析中,体会

6、到它在解决某个具体细节问题时,方 法的巧妙:如后面将分析到了的linux通过botoom_half机制來加快系统对屮 断的处理。最璽要的是:在源码的分析过程中,你将会被一点一点地、潜移默化地专业化。 一个专业的程序员,总是把代码的清晰性,兼容性,可移植性放在很重要的位 置。他们总是通过定义人量的宏,来增强代码的清晰度和可读性,而乂不增加 编译后的代码长度和代码的运行效率;他们总是在编码的同时,就考虑到了以 后的代码维护和升级。甚至,只要分析百分之一的代码后,你就会深刻地体会 到,什么样的代码才是一个专业的程序员写的,什么样的代码是一个业余爱好 者写的。而这一点是任何没有真正分析过标准代码的人都

7、无法体会到的。然而,由于内核代码的冗长,和内核体系结构的庞杂,所以分析内核也是一个很艰 难,很需耍毅力的事;在缺乏指导和交流的悄况下,尤其如此。只有方法正确,才能事 半功倍。止是基丁这种考虑,作者希望通过此文能给人家一些借鉴和启迪。由于本人所进行的分析都是基于2.2.5版本的内核;所以,如果没有特別说明,以 下分析都是基于i386单处理器的2.2.5版本的linux内核。所有源文件均是相对于目录 /usr/src/linux 的。方法之一:从何入手要分析linux内核源码,首先必须找到各个模块的位置,也即要弄懂源码的文件组 织形式。虽然对于有经验的高手而言,这个不是很难;但对于很多初级的lin

8、ux爱好者, 和那些对源码分析很有兴趣但接触不多的人來说,这还是很冇必要的。1> linux核心源程序通常都安装在/usr/src/linux卜,而且它有一个非常简单的编号 约定:任何偶数的核心(的二个数为偶数,例如2.0.30)都是一个稳定地发行的核心, 而任何奇数的核心(例如2.1.42)都是一个开发中的核心。2、核心源程序的文件按树形结构进行组织,在源程序树的最上层,即目录 /usr/src/linux卜有这样一些目录和文件: copying: gpl版权中明。对具有gpl版权的源代码改动而形成的程序,或使用 gpl工具产生的程序,具有使用gpl发表的义务,如公开源代码; cred

9、its:光荣榜。对linux做出过很大贡献的一些人的信息; maintainers:维护人员列表,对当前版本的内核各部分都有谁负责; makefile:第一个makefile文件。用来组织内核的各模块,记录了个模块间的相互这 间的联系和依托关系,编译时使用;仔细阅读各子目录卜的makefile文件对弄清各个文 件这间的联系和依托关系很有帮助; readme:核心及其编译配置方法简单介绍; rules.make:各种makefilemake所使用的一些共同规则; reportingbugs:有关报告bug的一些内容; arch/ : arch子目录包括了所有和体系结构相关的核心代码。它的每一个子

10、目录都代 表一种支持的体系结构,例如i386就是关于intel cpu及与z相兼容体系结构的子口录。pc机一般都基于此目录; include/: include t目录包括编译核心所需要的大部分头文件。与平台无关的头文件 在include/linux子目录下,与intel cpu相关的头文件在include/asm-i386子目录下, 而include/scsi目录则是有关scsi设备的头文件口录; ini”:这个目录包含核心的初始化代码(注:不是系统的引导代码),包含两个文件 main.c和version.c,这是研究核心如何工作的好的起点之一。 mm/:这个目录包括所有独立于cpu体系结构

11、的内存管理代码,如页式存储管理内 存的分配和释放等;而和体系结构相关的内存管理代码则位于arch/*/mm/,例如 arch/i386/mm/fault.c; kernel/:主要的核心代码,此目录下的文件实现了大多数linux系统的内核函数,其 中最巫要的文件当属sched.c;同样,和体系结构相关的代码在arch/*/kernel中; drivers/:放置系统所有的设备驱动程序;每种驱动程序又各占用一个子h录:如, /block下为块设备驱动程序,比如ide (ide.c)。如果你希望查看所有可能包含文件系 统的设备是如何初始化的,你可以看drivers/block/genhd.c屮的d

12、evice_setup()o它不 仅初始化硕盘,也初始化网络,因为安装nfs文件系统的时候需要网络 documentation/:文档目录,没有内核代码,只是一套有用的文档,可惜都是english 的,看看应该有用的哦; fs/:所有的文件系统代码和各种类型的文件操作代码,它的每一个了目录支持一个文 件系统,例如fat和ext2; ipc/:这个口录包含核心的进程间通讯的代码; lib/:放置核心的库代码; net/:核心与网络相关的代码; modules/:模块文件日录,是个空日录,用于存放编译时产牛的模块日标文件; scripts/:描述文件,脚本,用于对核心的配置;一般,在每个子目录卞,

13、都有一个makefile和一个readme文件,仔细阅读这两 个文件,对内核源码的理解很有用。对linux内核源码的分析,有儿个很好的入口点:一个就是系统的引导和初始化, 即从机器加电到系统核心的运行;另外一个就是系统调用,系统调用是用户程序或操作 调用核心所提供的功能的接口。対于那些対硬件比较熟悉的爱好者,从系统的引导入手 进行分析,可能來的容易一些;而从系统调用下口,则可能更合适于那些在dos或uinx、 linux下有过c编程经验的高手。这两点,在后面述将介绍到。方法之二:以程序流程为线索,一线串珠从表面上看,linux的源码就象一团扎乱无章的乱麻,其实它是一个组织得冇条冇 理的蛛网。耍

14、把整个结构分析清楚,除了找岀线头,还得理顺各个部分之间的关系,有 条不紊的一点一点的分析。所谓以程序流程为线索、一线串珠,就是指根据程序的执行流程,把程序执行过程 所涉及到的代码分析清楚。这种方法最典型的应用有两个:一是系统的初始化过程;二 是应用程序的执行流程:从程序的装载,到运行,一直到程序的退出。为了简便起见,遵从循序渐进的原理,现就系统的初始化过程来具体的介绍这种方 法。系统的初始化流程包括:系统引导,实模式下的初始化,保护模式下的初始化共三 个部分。下面将介绍。inux系统的常见引导方式有两种:lilo引导和loadin引导;同时linux内核也自带 了一个bootsectloade

15、r。由于它只能实现linux的引导,不像前两个那样具有很大的灵 活性(lilo可实现多重引导、loadin可在dos卞引导linux),所以在普通应用场合实际上 很少使用bootsect-loaderc当然,bootsect-loader也具有它自己的优点:短小没有多 余的代码、附带在内核源码中、是内核源码的有机组成部分,等等。bootsect-loader在内和源码中对应的程序是/arch/i386/boot/bootsect.s。下面将 主要是针对此文件进行的分析。1. 几个相关文件:<1> /arch/i386/boot/bootsect.s <2> /in cl

16、ude/li nux/con fig.h<3> /in clude/asm/boot.h<4> /in clude/li nu x/autoconf.h2. 引导过程分析:对于intel x86 pc,开启电源后,机器就会开始执行rom bios的一系列系统 测试动作,包括检查ram, keyboard,显示器,软硬磁盘等等。执行完bios 的系统测试z后,紧接着控制权会传移给rom屮的启动程序(rom bootstrap routine);这个程序会将磁盘上的笫0轨笫0扇区(叫boot sector或mbr ,系 统的引导程序就放在此处)读入内存中,并放到自0x070

17、0:0x0000 始的512 个字节处;然后处理机将跳到此处开始执行这一引导程序;也即装入mbr屮的 引导程序后,cs:ip = 0x0700:0x0000。加电后处理机运行在与8086相兼容 的实模式下。如果耍用bootsect-loader进行系统引导,则必须把bootsect.s编译连接斤对应 的二进制代码置于mbr;当rom bios把bootsect.s编译连接后对应的二进 制代码装入内存斤 机器的控制权就完全转交给bootsect;也就是说,bootsect 将是第一个被读入内存中并执行的程序。bootsect接管机器控制权后,将依次进行以下一些动作:1. 首先,bootsect将

18、它泊己”(自位置0x07c0:0x0000 始的512个字节)从被 rom bios载入的地址0x0700:0x0000处搬到0x9000:0000处 这一任务山 bootsect.s的前十条指令完成;第十一条指令“jmpi go,initseg”则把机器跳转 到“新”的 bootsect 的“jmpi go,initseg”后的那条指令“go: mov di,#0x4000-12n; 之后,继续执行bootsect的剩卜的代码;在bootsect.s屮定义了几个常量:bootseg = 0x07c0 bios载入mbr的约定位置的段址;initseg = 0x9000 bootsect.s的

19、前i 条指令将自己搬到此处(段址)setupseg =0x9020 装入 setup.s 的段址sysseg =0x1000系统区段址对于这些常量可参m/include/asm/boot.h中的定义;这些常量在下面的分析屮将 会经常用到;2. 以0x9000:0x4000-12为栈底,建立自己的栈区;其中0x9000:0x4000-12 到0x9000:0x4000的一十二个字节预留作磁盘参数表区;3. 在0x9000:0x4000-12到0x9000:0x4000的一二个预留字节中建立新的磁 盘参数表,之所以叫“新”的磁盘参数表,是相对于bios建立的磁盘参数表而言 的。由于设计者考虑到有些

20、老的bios不能准确地识別磁盘“每个磁道的扇区数”, 从而导致bios建立的磁盘参数表妨碍磁盘的最高性能发挥,所以,设计者就在 bios建立的磁盘参数表的棊础上通过枚举法测试,试图建立准确的“新”的磁盘参 数表(这是在后继步骤中完成的);并把参数表的位置由原来的0x0000:0x0078 搬到0x9000:0x4000-12;且修改老的磁盘参数表区使之指向新的磁盘参数表;4. 接下来就到了 load_setup子过程;它调用0x13中断的第2号服务;把第0 道第2扇区开始的连续的setup_sects (为常量4)个扇区读到紧邻bootsect的内 存区;,即0x9000:0x0200开始的2

21、048个字节;而这四个扇区的内容即是 /arch/i386/boot/setup.s编译连接后对应的二进制代码;也就是说,如果耍用 bootsect-loader进行系统引导,不仅必须把bootsect.s编译连接后对应的二进 制代码置于mbr,而且还得把setup.s编译连接后对应的二进制代码置于紧跟 mbr后的连续的四个扇区屮;当然,由于setup.s对应的可执行码是山bootsect 装载的,所以,在我们的这个项口中可以通过修改bootsect來根据需要随意地 放置setup.s对应的可执行码;5. ioad_setup子过程的唯一出口是probejoop子过程;该过程通过枚举法测 试磁

22、盘“每个磁道的扇区数”;6. 接下来儿个子过程比较清晰易懂:打印我们熟悉的“loading”;读入系统到 0x1000:0x0000;关掉软驳马达;根据的5步测岀的“每个磁道的扇区数”确定磁 盘类型;最后跳转到0x9000:0x0200,即setup.s对应的m执行码的入口,将机 器控制权转交setup.s;整个bootsect代码运行完毕;3. 引导过程执行完后的内存印象图:出于简便考虑,在此分析屮,我忽略了对大内核的处理的分析,因为对大内核的处 理,只是此引导过程屮的一个很小的部分,并不影响对整体的把握。完成了系统的引导 后,系统将进入到初始化处理阶段。系统的初始化分为实模式和保护模式两部

23、分。 ii、实模式下的初始化实模式下的初始化,主要是指从内核引导成功后,到进入保护模式之前系统所做的 一些处理。在内核源码中对应的程序是/arch/i386/boovsetup.s;以下部分主要是针对 此文件进行的分析。这部分的分析主要是耍奔懂它的处理流程和initseg(9000:0000) 段参数表的建立,此参数表包含了很多硕件参数,这些都是以后进行保护模式卜初始化, 以及核心建立的基础。1.几个其它相关文件:<1> /arch/i386/boot/bootsect.s<2> /in clude/linux/config.h<3> /include/as

24、m/boot.h<4> /in elude/ asm/segme nt.h <5> /include/li nu x/version.h<6> /include/li nu x/compile.h2.实模式下的初始化过程分析:开始复位硬盘系统确禍整系统内核段地址到正确位苴"彩尹和沐3汕僮于setup. s 的束氐卫确,则setup区全出 装在 9qjq; q2jq 和 5000:9004. 何;歪则,余m和系统内絃一進 装在 1000: 0000 之qaa55 -和 5a5a澤来的糸覘内建段范址 a 1000芒執的段址还 必须加二setjp余部占

25、用詢空间.将 hdo 和 hdal 的参瓠 从 0000:4*0x41 的32个字节搬到0x0x9000:0x0080linuz实模式下的初始俪程图二将 hdal 的参数(a 0x000:0x0090 起 的10个宁节)涪零检杳m3 a bjs,将其参数貫于0x9000: oxooao检杳ps/2鼠标器,存在则置0x9000: 0x0iff为oxaa,否则苴其为0检查apmbios,将其夢皱直于0x000:0x00645'j ohpooo: oko082 m三十二个字节关申断:并禁止nmi谨求以sysseg与in:tseg间的4kb作为缓冲取, 每设4k,将系统搬到0z0100:000

26、0处检查自己杲否位于setup seg j(t,不存.则探至此处1 idt为空,设呻段址咖心,共256个入口,并设了一个系统数据和一个代解段in;el把d-51号申斷向 量用来处理异常李件,而 t8m则把硕申晰设住 dx)8-d:<df防以必须审 新编程,把硬中騎设到 dx2d-d:<2f使a20线有瓶对中断控制器8259a-l,9259a-2重新编程t孕保护模对initseg(9000:0000)段参数表:(参见 include/linux/tty.h)参数名偏移量(段址均为 0x9000)长度byte参考文件param_cursor_pos0x00002arch/i386/bo

27、ot/video.sextended mem size0x00022arch/i386/boot/setup.sparam_video_page0x00042arch/i386/boot/video.sparam_video_mode0x00061arch/i386/boot/video.sparam_video_cols0x00071arch/i386/boot/video.s没用0x00082include/linux/tty.hparam_video_ega_bx0x000a2arch/i386/boot/video.s没用0x000c2include/linux/tty.hparam_

28、video_linesoxoooe1arch/i386/boot/video.sparam_have_vgaoxooof1arch/i386/boot/video.sparam_font_points0x00102arch/i386/boot/video.sparam_lfb_width0x00122arch/i386/boot/video.sparam_lfb_height0x00142arch/i386/boot/video.sparam_lfb_depth0x00162arch/i386/boot/video.sparam_lfb_base0x00184arch/i386/boot/vi

29、deo.sparam_lfb_size0x001c4arch/i386/boot/video.s暂未用0x00204include/linux/tty.hparam_lfb_linelength0x00242arch/i386/boot/video.sparam_lfb_colors0x00266arch/i386/boot/video.s暂未用0x002c2arch/i386/boot/video.sparam_vesapm_seg0x002e2arch/i386/boot/video.sparam_vesapm_off0x00302arch/i386/boot/video.sparam_l

30、fb_pages0x00322arch/i386/boot/video.s保留0x0034-0x003finclude/linux/tty.hapm bios version0x00402arch/i386/boot/setu p.sbios code segment0x00422arch/i386/boot/setup.sbios entry offset0x00444arch/i386/boot/setup.sbios 16 bit code seg0x00482arch/i386/boot/setup.sbios data segment0x004a2arch/i386/boot/set

31、up.s支持32位标志0x004c2arch/i386/boot/setu p.sbios code seg length0x004e4arch/i386/boot/setup.sbios data seg length0x00522arch/i386/boot/setup.shdo参数0x008016arch/i386/boot/setu p. shdo参数0x009016arch/i386/boot/setup.sps/2 device 标志0x0 iff1arch/i386/boot/setup.s* 注: include/linux/tty.h : cl_magic and cl_of

32、fset here1. include/linux/tty.h :unsigned char rsvd_size; /* 0x2c */ unsigned char rsvd_pos; /* 0x2d */ 0表示没有apm bios 0x0002置位表示支持32位模式 0表示没有,oxoaa表示有鼠标器川、保护模式下的初始化保护模式下的初始化,是指处理机进入保护模式后到运行系统笫一个内核程序过程 中系统所做的一些处理。保护模式卜的初始化在内核源码中对应的程序是 /arch/i386/boovcompressed/head.s 和 /arch/i386/kernel/head.s ;以下部分主

33、要 是针对这两个文件进行的分析。1. 几个相关文件:<1 .> /arch/i386/boot/compressed/head.s<2 > /arch/i386/kernel/head.s<3.> /arch/i386/boot/compressed/misc.c<4.> /arch/i386/boot/setup.s<5.> /inelude/ asm/segment.h<6.> /arch/i386/kernel/traps.c<7> /include/i386/desc.h<8.> /incl

34、ude/asm-i386/processor.h2. 保护模式下的初始化过程分析:一、/arch/i386/kernel/head.s 流程:逹意,从此均 为俣护虚拟 方式寻址/arch/i386/kernel/head-sgs开始 + 用 kernel ds 初始化 ds, es, fs, gs此时页目录只定义了 2个页表项: 第0项和第768项,此两项都对 应同一个页表0x00102 »同时, head.s 也0x00102000 处定义了 一个页表民0,饯页表对应物理地 址的0-4m/即在0-4m/物理 地址与逻辑地址是一致的;当然 通过页目录的第7住项,也可访 问此段空间即3

35、g3gb-mm段 也段应此段bssji setup. s 的代码段? k同时参见:/ ar ch/ i386/kernel/1 raps. c和include/i386/ desc. hinitseg段的参数参见 “实模式下的初始化j emp后 2k将作为命令缓冲区 empty_zero_page 位 于 0x105000 | 大小 4ki务 cl_base_addr;cl_offset 开始的 2k 个字节拷贝到empty zero page的后2k此时皿已经初始化了,只需装载到i dtr即可正常情况下,不会返 回 > 若返回,则不正 常,所以死循环的£h/i巒何则l/it仝

36、號程图二有关cpu的信息被放在一个 命名b oot,_cpu_data的结构 cpuinfo_x86 中丿参见 in.cludle/asm-i386/processor.h|检查cpu类型,保存cpu信息;并根据不同类型的cpu来重新初始化cro检查协处理器是否有救,无效则em 5位、否则复 位,同时对保存cpu信息的相应单元迸行标示,在进入保护模式前,已初贻化 了一个只含两个有效采统段 的gdt ;此处重新建立gdt, 新的gdt位于0x106000,共 12+2*1ir_tasks 个 项毎顶 8byte?设置了两个系纭段和 两个用户段,都是从0并始的 4g大小还设了 4个apm段二、/a

37、rch/i386/boot/compressed/head.s 流程:|/arch/i386/boot/cc>iip:r巳 ss巳d/h巳adl s 流程图 |1. 从流程图中可以看到,保护模式下的初始化主耍干了这样儿件事:1. 解压内核到0x100000处、2. 建立页h录和pgo页表并启动分页功能(即虚存管理功能)、3. 保存实模式下测到的硬件信息到empty_zero_page初始化命令 缓存区、4. 检测cpu类型、检查协处理器、5. 重新建立gdt全局描述符表、和屮断描述附表idt;2. 从页h录和pgo页表可以看出,0&#0; 4m物理内存被用作系统区,它被映射 到系

38、统段线性空间的0&#0; 4m和3g&#0; 3g+4m;即系统可以通过访问这两 个段來访问实际的0&#0; 4m物理内存,也就是系统所在的区域;3. 木来在实模式下初始化时已经建立了全局描述符表gdt,而此处重新建立全局描 述符表gdt则主要是出于两个原因:一个就是若内核是大内核bzimag,则以前 建立的gdt,可能已经在解压时被覆盖掉了所以,在这个源码文件中均只采用相 对转移指令jxxnf或jxxnb;二是以前建立的gdt是建立在实地址方式下的,而 现在则是在启用保护虚拟地址方式之后建立的,也即现在的gdt是建立在逻辑地 址(即线性地址)上的;4. 每次建立新的g

39、dt后和启用保护虚拟地址方式后都必须巫新装载系统栈和巫新 初始化各段寄存器:cs,ds,es,fs,gs;5. 从实模式下的初始化和保护模式下的初始化过程可以看ill, linux系统由实模式 进入到保护模式的过程大致如下:实模式到386保护模式毓程图|第一歩第二步第三步6. 由于分页机制只能在保护模式下启动,不能在实模式下启动,所以第一步是必要的; 又因为在386保护模式下gdt和idt是建立在逻辑地址(线性地址)上的,所以第三步也 是必要的;7. 经过实模式和保护模式下的初始后,主要系统数据分布如下:初始后屯要系统数据分布表位置系统数据大小0x101000页目录 swapper_pg_di

40、r4k0x102000页表pgo4k0x103000empty_bad_page4k0x104000e m pty_bad_pa g e_ta b i e4k0x105000empty_zero_page4k0x105000系统硬件参数2k0x105800命令缓冲区2k0x106000全局描述附表gdtjable4192b从上面对linux系统的初始化过程的分析可以看出,以程序执行流程为线索、一线 出珠,就是按照程序的执行先后顺序,弄懂程序执行的各个阶段所进行的处理,及其各 阶段之间的相互联系。而流程图应该是这种分析方法最合适的表达工具。事实上,以程序执行流程为线索,是分析任何源代码都首选的方

41、法。山于操作系统 的特殊性,光用这种方法是远远不够的。当然用这种方法來分析系统的初始化过程或用 户进程的执行流程应该说是很有效的。方法之三:以数据结构为基点,触类旁通结构化程序设计思想认为:程序=数据结构+算法。数据结构体现了整个系统 的构架,所以数据结构通常都是代码分析的很好的着手点,对linux内核分析尤其如此。 比如,把进程控制块结构分析清楚了,就对进程有了基本的把握;再比如,把页目录结 构和页表结构弄懂了,两级虚存映射和内存管理也就掌握得差不多了。为了体现循序渐 进的思想,在这我就以linux对屮断机制的处理來介绍这种方法。首先,必须指出的是:在此处,中断指广义的中断概义,它指所有通过

42、idt进行的 控制转移的机制和处理;它覆盖以f儿个常用的概义:中断、异常、可屏蔽中断、不可 屏蔽中断、换中断、软中断 i、硬件提供的中断机制和约定'寻址:彼件提供可供256个服务程序中断进入的入口,即中断向量;中断向量在保护模式下的实现机制是中断描述符表idt, idt的位置由idtr确定,idtr 是个48位的寄存器,高32位是idt的基址,低16位为idt的界限(通常为2k=256*8);idt中包含256个中断描述符,对应256个中断向量;每个中断描述符8位,其结 构如图一:0-1offset (15-0)2-3selector15 >14 l3fl28,t4-5p |dp

43、l | oxoe| 0x006-7offset (3116)oj注.e. 删磁 ?=o磁 使用则«贤异常dpu 00-11.对应ih个特6嗽(00量高)中断进入过程如图二所示。当中断是由低特权级转到高特权级(即当前特权级cpl>dpl)时,将进行堆栈的转 移;内层堆栈的选择曲当前tss的相应字段确定,而r内层堆栈将依次被压入如下数据: 外层ss,外层esp,eflags,外层cs,外层eip;中断返回过程为一逆过程;图二7中靳进入京尋图idt selector特性字 bffsg - 基址特性字w- pdt 或 ldt |服务程序入口二异常处理机制:intel公司保留031号中断

44、向量用來处理异常事件:当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,异常的处理程序由操作系统提供,中断向量和异常事件对应如表一:表一、中断向量和异常事件对应表中断向量号异常事件linux的处理程序0除法错误divide_error1调试异常debug2nmi中断nmi3单字节,int3int34溢出overflow5边界监测中断bounds6无效操作码lnvalid_op7设备不可用device_ no t_available8双垂故障double_fault9协处理器段溢出coprocessor_segment_overrun10无效tssin calid_tss11缺段

45、中断nsegme nt_not_prese nt12堆栈异常stack_segme nt13一般保护异常general_protection14页异常page_fault15spurious_interrupt_bug16协处理器出错coprocessor_error仃对齐检查中断alignmen t_check三可编程中断控制器8259a :为更好的处理外部设备,x86微机提供了两片可编程屮断控制器,用來辅助cpu接 受外部的中断信号;对丁中断,cpu只提供两个外接引线:nmi和intr;nmi只能通过端口操作来屏蔽,它通常用于:电源掉电和物理存储器奇偶验错;intr可通过直接设置屮断屏蔽位

46、來屏蔽,它可用來接受外部中断信号,但只有一 个引线,不够用;所以它通过外接两片级链了的8259a,以接受更多的外部中断信号。 8259a主要完成这样一些任务:a. 中断优先级排队管理,b. 接受外部中断请求c. 向cpu提供小断类型号外部设备产生的屮断信号在irq (中断请求)管脚上首先由中断控制器处理。屮断 控制器可以响应多个屮断输入,它的输岀连接到cpu的int管脚,信号可通过int 管脚,通知处理器产生了中断。如果cpu这时可以处理中断,cpu会通过inta (中 断确认)管脚上的信号通知屮断控制器已接受屮断,这时,屮断控制器可将一个8位 数据放置在数据总线上,这一 8位数据也称为中断向

47、量号,cpu依据中断向量号和中 断描述符表(idt)中的信息自动调用相应的中断服务程序。图三屮,两个屮断控制器 级联了起來,从属中断控制器的输出连接到了主中断控制器的第3个中断信号输入, 这样,该系统可处理的外部中断数量最多可达15个,图的右边是i386 pc中各中断 输入管脚的一般分配。可通过对8259a的初始化,使这15个外接引脚对应256个屮断 向量的任何15个连续的向量;由于intel公司保留031号中断向量用来处理异常事件(而 默认情况下,ibm bios把硕中断设在0x08-0x0f),所以,硬屮断必须设在31以后,linux 则在实模式下初始化时把其设在0x200x2f,対此下而

48、还将具体说明。图三、i386 pc可编程中断控制器8259a级链示意图irq0 (时钟)irq 1(键盘)irq 3(tty2)irq 4( ttyl)irq5(xtwinchester)irq 6 (软驼)irq 7 (打印机)irq 8 (实时时钟)irq 9 (重定向的irq2)irq 10irq 11irq 12irq 13( fpu 异常)irq 14( at winchester)irq 15硕件中断机制提供了 256个入口,即idt中包含的256个中断描述符(对应256个 中断向量)。ifu 0-31号中断向量被intel公司保留用来处理异常事件,不能另作它用。对这031 号中断

49、向量,操作系统只需提供界常的处理程序,当产生一个异常时,处理机就会口动 把控制转移到相应的处理程序的入口,运行相应的处理程序;而事实上,对于这32个 处理异常的中断向量,此版本(225)的linux只提供了 017号中断向量的处理程序, 其对应处理程序参见表一、中断向量和异常事件对应表;也就是说,17-31号中断向量 是空着未用的。既然031号中断向量已被保留,那么,就是« k 32-255共224个屮断向量可用。 这224个中断向量又是怎么分配的呢?在此版本(2.2.5)的linux中,除了 0x80 (syscall_vector)用作系统调用总入口之外,其他都用在外部硬件中断源

50、上,其中 包括可编程中断控制器8259a的15个irq:事实上,当没有定义config_x86_io_apic 时,其他223(除0x80外)个中断向量,只利用了从32号开始的15个,其它208个空 着未用。这些中断服务程序入口的设置将在下而有详细说明。相关数据结构a. 中断描述符表idt:也就是中断向量表,相当如一个数组,保存着各中断服务 例程的入口。(详细描述参见图一、中断描述符格式)b. 与硬中断相关数据结构:与硬中断相关数据结构主要有三个:一:定义在/arch/i386/kernel/irq.h 中的struct hw_interrupt_type const char * type

51、name;void (*startup)(unsigned int irq);void (*shutdown)(unsigned int irq);void (*handle)(unsigned int irq, struct pt_regs * regs);void (*enable)(unsigned int irq);void (*disable)(unsigned int irq);二:定义在/arch/i386/kernel/irq.h 中的typedef struct unsigned int status; /* irq status - irqjnprogress, irq_d

52、isabled */ struct hw_interrupt_type *handler; /* handle/enable/disable functions */ struct irqaction *action; /* irq action list */unsigned int depth; /* disable depth for nested irq disables */ irq_desc_t;三:定义在泊clude/linux/ interrupt.h 屮的struct irqaction void (*handler)(int, void *, struct pt_regs

53、*);unsigned long flags;unsigned long mask;const char *name;void *dev_id;struct irqaction *next;;三者关系如下:图四、与駛中断相关的几个数据结构各关系各结构成员详述如下:a. struct irqaction结构,它包含了内核接收到特定 irq之后应该采取的操作,其成员如下: handler:是一指向某个隊 1数的指针。该函数就是所在结构对相应中断的处理函 数。 flags:取值只有 sa_interrupt (中断可嵌套),sa_sample_random(这个中断是源于物理随机性的),和sa_sh

54、irq (这个irq和英它struct irqaction 共享)。 mask:在x86或者体系结构无关的代码屮不会使用(除非将其设置为0);只 有在sparc64的移植版本中耍跟踪有关软盘的信息时才会使用它。 name:产生屮断的硬件设备的名字。因为不止一个硬件可以共享一个irq。 devjd:标识硕件类型的一个唯一的id。linux支持的所有硬件设备的每一种 类型,都有一个由制造厂商定义的在此成员中记录的设备ido next:如果irq是共享的,那么这就是指向队列屮下一个struct irqaction结 构的指针。通常情况下,irq不是共享的,因此这个成员就为空。a. struct hw

55、_interrupt_type 结构,它是一个抽象 的中断控渝器。这包含一系列的指向函数的指针, 这些函数处理控制器特有的操作: typename:控制器的名字。 startup:允许从给定的控制器的irq所产生的事件。 shutdown:禁止从给定的控制器的irq所产牛的事件。 handle:根据提供给该函数的irq,处理唯一的中断。 enable和disable:这两个函数基本上和startup和shutdown相同;a.另外一个数据结构是irq_desc_t,它具有如下成 员: status: 一个整数。代表irq的状态:irq是否被禁止了,有关irq的设备当 前是否正被自动检测,等等。

56、 handler:扌n|nj hw_interrupt_type 的指针。 action:指向irqaction结构组成的队列的头。止常悄况下每个irq只有一个操 作,因此链接列表的止常长度是1 (或者0)。但是,如果irq被两个或者多个 设备所共享,那么这个队列中就有多个操作。 depth: irq_desc_t的当前用户的个数。主要是用來保证在中断处理过程中irq 不会被禁止。 irq_desc是irq_desc_t类型的数组。对于每一个irq都有一个数组入口,即 数组把每一个irq映射到和它相关的处理程序和irq_desc_t中的其它信息。a.与bottom_half相关的数据结构:图五、底半处理数据结构示意图底半处理程序 bh_mask_count:计数器。对每个enable/disable请求嵌套对进彳亍计数。这 些请求通过调用enable_bh和disable_bh实现。每个禁止请求都增加计数器;每个使能请求都减小计竅器。当计数器払到0时,所有未完成的禁止语句都已 经被使能语句所匹配了,因此下半部分最终被重新使能。(定义在kernel/softirq.c 中) bh_

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论