Linux内核分析方法谈(3)(转)

Linux内核分析方法谈(3)(转)[@more@]

  方法之三:以数据结构为基点,触类旁通

  结构化程序设计思想认为:程序 = 数据结构 + 算法。数据结构体现了整个系统的构架,所以数据结构通常都是代码分析的很好的着手点,对Linux内核分析尤其如此。比如,把进程控制块结构分析清楚了,就对进程有了基本的把握;再比如,把页目录结构和页表结构弄懂了,两级虚存映射和内存管理也就掌握得差不多了。为了体现循序渐进的思想,在这我就以Linux对中断机制的处理来介绍这种方法。

  首先,必须指出的是:在此处,中断指广义的中断概义,它指所有通过idt进行的控制转移的机制和处理;它覆盖以下几个常用的概义:中断、异常、可屏蔽中断、不可屏蔽中断、硬中断、软中断 … … …

  I、硬件提供的中断机制和约定

  一.中断向量寻址:

  硬件提供可供256个服务程序中断进入的入口,即中断向量;

  中断向量在保护模式下的实现机制是中断描述符表idt,idt的位置由idtr确定,idtr是个48位的寄存器,高32位是idt的基址,低16位为idt的界限(通常为2k=256*8);

  idt中包含256个中断描述符,对应256个中断向量;每个中断描述符8位,其结构如图一:

1143656239_ddvip_8623.gif

  中断进入过程如图二所示。

  当中断是由低特权级转到高特权级(即当前特权级CPL>DPL)时,将进行堆栈的转移;内层堆栈的选择由当前tss的相应字段确定,而且内层堆栈将依次被压入如下数据:外层SS,外层ESP,EFLAGS,外层CS,外层EIP; 中断返回过程为一逆过程;

  二.异常处理机制:

  Intel公司保留0-31号中断向量用来处理异常事件:当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,异常的处理程序由操作系统提供,中断向量和异常事件对应如表一:

1143656239_ddvip_9011.gif

  表一、中断向量和异常事件对应表

中断向量号 异常事件 Linux的处理程序
0 除法错误 Divide_error
1 调试异常 Debug
2 NMI中断 Nmi
3 单字节,int 3 Int3
4 溢出 Overflow
5 边界监测中断 Bounds
6 无效操作码 Invalid_op
7 设备不可用 Device_not_available
8 双重故障 Double_fault
9 协处理器段溢出 Coprocessor_segment_overrun
10 无效TSS Incalid_tss
11 缺段中断 Segment_not_present
12 堆栈异常 Stack_segment
13 一般保护异常 General_protection
14 页异常 Page_fault
15 Spurious_interrupt_bug
16 协处理器出错 Coprocessor_error
17 对齐检查中断 Alignment_check

  三.可编程中断控制器8259A:

  为更好的处理外部设备,x86微机提供了两片可编程中断控制器,用来辅助cpu接受外部的中断信号;对于中断,cpu只提供两个外接引线:NMI和INTR;

  NMI只能通过端口操作来屏蔽,它通常用于:电源掉电和物理存储器奇偶验错;

  INTR可通过直接设置中断屏蔽位来屏蔽,它可用来接受外部中断信号,但只有一个引线,不够用;所以它通过外接两片级链了的8259A,以接受更多的外部中断信号。8259A主要完成这样一些任务:

  中断优先级排队管理,

  接受外部中断请求

  向cpu提供中断类型号

  外部设备产生的中断信号在IRQ(中断请求)管脚上首先由中断控制器处理。中断控制器可以响应多个中断输入,它的输出连接到 CPU 的 INT 管脚,信号可通过INT 管脚,通知处理器产生了中断。如果 CPU 这时可以处理中断,CPU 会通过 INTA(中断确认)管脚上的信号通知中断控制器已接受中断,这时,中断控制器可将一个 8 位数据放置在数据总线上,这一 8 位数据也称为中断向量号,CPU 依据中断向量号和中断描述符表(IDT)中的信息自动调用相应的中断服务程序。图三中,两个中断控制器级联了起来,从属中断控制器的输出连接到了主中断控制器的第 3 个中断信号输入,这样,该系统可处理的外部中断数量最多可达 15 个,图的右边是 i386 PC 中各中断输入管脚的一般分配。可通过对8259A的初始化,使这15个外接引脚对应256个中断向量的任何15个连续的向量;由于intel公司保留0-31号中断向量用来处理异常事件(而默认情况下,IBM bios把硬中断设在0x08-0x0f),所以,硬中断必须设在31以后,linux则在实模式下初始化时把其设在0x20-0x2F,对此下面还将具体说明。

  图三、i386 PC 可编程中断控制器8259A级链示意图

1143656239_ddvip_8260.gif

  II、Linux的中断处理

  硬件中断机制提供了256个入口,即idt中包含的256个中断描述符(对应256个中断向量)。

  而0-31号中断向量被intel公司保留用来处理异常事件,不能另作它用。对这0-31号中断向量,操作系统只需提供异常的处理程序,当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,运行相应的处理程序;而事实上,对于这32个处理异常的中断向量,此版本(2.2.5)的Linux只提供了0-17号中断向量的处理程序,其对应处理程序参见表一、中断向量和异常事件对应表;也就是说,17-31号中断向量是空着未用的。

  既然0-31号中断向量已被保留,那么,就是剩下32-255共224个中断向量可用。这224个中断向量又是怎么分配的呢?在此版本(2.2.5)的Linux中,除了0x80 (SYSCALL_VECTOR)用作系统调用总入口之外,其他都用在外部硬件中断源上,其中包括可编程中断控制器8259A的15个irq;事实上,当没有定义CONFIG_X86_IO_APIC时,其他223(除0x80外)个中断向量,只利用了从32号开始的15个,其它208个空着未用。

  这些中断服务程序入口的设置将在下面有详细说明。

  一.相关数据结构

  中断描述符表idt: 也就是中断向量表,相当如一个数组,保存着各中断服务例程的入口。(详细描述参见图一、中断描述符格式)

  与硬中断相关数据结构:

  与硬中断相关数据结构主要有三个:

  一:定义在/arch/i386/kernel/irq.h中的

  struct hw_interrupt_type {

const char * typename;

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 - IRQ_INPROGRESS, IRQ_DISABLED */

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;

  三:定义在include/linux/ interrupt.h中的

  struct irqaction {

void (*handler)(int, void *, struct pt_regs *);

unsigned long flags;

unsigned long mask;

const char *name;

void *dev_id;

struct irqaction *next;

};

  三者关系如下:

1143656239_ddvip_5354.gif

  图四、与硬中断相关的几个数据结构各关系

  各结构成员详述如下:

  struct irqaction结构,它包含了内核接收到特定IRQ之后应该采取的操作,其成员如下:

  handler:是一指向某个函数的指针。该函数就是所在结构对相应中断的处理函数。

  flags:取值只有SA_INTERRUPT(中断可嵌套),SA_SAMPLE_RANDOM(这个中断是源于物理随机性的),和SA_SHIRQ(这个IRQ和其它struct irqaction共享)。

  mask:在x86或者体系结构无关的代码中不会使用(除非将其设置为0);只有在SPARC64的移植版本中要跟踪有关软盘的信息时才会使用它。

  name:产生中断的硬件设备的名字。因为不止一个硬件可以共享一个IRQ。

  dev_id:标识硬件类型的一个唯一的ID。Linux支持的所有硬件设备的每一种类型,都有一个由制造厂商定义的在此成员中记录的设备ID。

  next:如果IRQ是共享的,那么这就是指向队列中下一个struct irqaction结构的指针。通常情况下,IRQ不是共享的,因此这个成员就为空。

  struct hw_interrupt_type结构,它是一个抽象的中断控制器。这包含一系列的指向函数的指针,这些函数处理控制器特有的操作:

  typename:控制器的名字。

  startup:允许从给定的控制器的IRQ所产生的事件。

  shutdown:禁止从给定的控制器的IRQ所产生的事件。

  handle:根据提供给该函数的IRQ,处理唯一的中断。

  enable和disable:这两个函数基本上和startup和shutdown相同;

  另外一个数据结构是irq_desc_t,它具有如下成员:

  status:一个整数。代表IRQ的状态:IRQ是否被禁止了,有关IRQ的设备当前是否正被自动检测,等等。

  handler:指向hw_interrupt_type的指针。

  action:指向irqaction结构组成的队列的头。正常情况下每个IRQ只有一个操作,因此链接列表的正常长度是1(或者0)。但是,如果IRQ被两个或者多个设备所共享,那么这个队列中就有多个操作。

  depth:irq_desc_t的当前用户的个数。主要是用来保证在中断处理过程中IRQ不会被禁止。

  irq_desc是irq_desc_t 类型的数组。对于每一个IRQ都有一个数组入口,即数组把每一个IRQ映射到和它相关的处理程序和irq_desc_t中的其它信息。

  与Bottom_half相关的数据结构:

  图五、底半处理数据结构示意图

1143656239_ddvip_9108.gif

  bh_mask_count:计数器。对每个enable/disable请求嵌套对进行计数。这些请求通过调用enable_bh和disable_bh实现。每个禁止请求都增加计数器;每个使能请求都减小计数器。当计数器达到0时,所有未完成的禁止语句都已经被使能语句所匹配了,因此下半部分最终被重新使能。(定义在kernel/softirq.c中)

  bh_mask和bh_active:它们共同决定下半部分是否运行。它们两个都有32位,而每一个下半部分都占用一位。当一个上半部分(或者一些其它代码)决定其下半部分需要运行时,就通过设置bh_active中的一位来标记下半部分。不管是否做这样的标记,下半部分都可以通过清空bh_mask中的相关位来使之失效。因此,对bh_mask和bh_active进行位AND运算就能够表明应该运行哪一个下半部分。特别是如果位与运算的结果是0,就没有下半部分需要运行。

  bh_base:是一组简单的指向下半部分处理函数的指针。

  bh_base代表的指针数组中可包含 32 个不同的底半处理程序。bh_mask 和 bh_active 的数据位分别代表对应的底半处理过程是否安装和激活。如果 bh_mask 的第 N 位为 1,则说明 bh_base 数组的第 N 个元素包含某个底半处理过程的地址;如果 bh_active 的第 N 位为 1,则说明必须由调度程序在适当的时候调用第 N 个底半处理过程。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10752043/viewspace-940295/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/10752043/viewspace-940295/

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值