《中断和中断处理》PPT课件.ppt_第1页
《中断和中断处理》PPT课件.ppt_第2页
《中断和中断处理》PPT课件.ppt_第3页
《中断和中断处理》PPT课件.ppt_第4页
《中断和中断处理》PPT课件.ppt_第5页
已阅读5页,还剩111页未读 继续免费阅读

下载本文档

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

文档简介

第 3 章 中断和中断处理,硬件中断机制是一个操作系统内核中非常重要的部分。它的设计直接影响到操作系统整体的性能。它与硬件平台和内核的其它部分,如内存管理、进程调度、设备驱动等都有很密切的关系。因此,它也是操作系统中比较复杂的一个模块。 Linux的硬件中断机制的设计有很多独到之处,本章把kernel 2.4和kernel 2.2.x的相关机制进行详细的对比,使读者能够更好的领会最新的kernel 2.4中的硬件中断机制。,3.1 硬件提供的中断机制和约定,硬中断即和硬件相关的中断也就是通常意义上的“中断处理程序”,它是直接处理由硬件发过来的中断信号的。当某个设备发出中断请求时,CPU停止正在执行的指令,转而跳到包括中断处理代码或者包括指向中断处理代码的转移指令所在的内存区域。这些代码一般在CPU的中断方式下运行。就回去自己驱动的设备上去看看设备的状态寄存器以了解发生了什么事情,并进行相应的操作。当中断处理完毕以后,CPU将恢复到以前的状态,继续执行中断处理前正在执行的指令。 中断的流程如图3.1所示。,3.1 硬件提供的中断机制和约定,图3.1 中断流程,大多数处理器在处理中断过程方式下将不会再有中断发生。但有些CPU的中断有自己的优先权,更高优先权的中断则可以发生。这意味着第一级的中断处理程序必须拥有自己的堆栈,以便在处理更高级别的中断前保存CPU的执行状态。,3.1 硬件提供的中断机制和约定,Linux系统是包含内核、系统工具、完整的开发环境和应用的类Unix操作系统。这个系统是由全世界各地的成千上万的程序员设计和实现的。1984年,Richard Stallman创立了GNU工程,其目标是开发一个完全免费的类Unix系统及其应用程序。1991年,芬兰赫尔辛基大学一位名叫Linus Torvalds的学生开始了开放源代码的Linux雏形的设计。其目的是建立不受任何商品化软件的版权制约的、全世界都能自由使用的Unix兼容产品 由于Linux是一套具有Unix全部功能的免费操作系统,它在众多的软件中占有很大的优势,为广大的计算机爱好者提供了学习、探索以及修改计算机操作系统内核的机会,3.1.1 中断产生的过程,CPU 在一些外部硬件的帮助下处理中断。中断处理硬件和具体的系统相关,但一般来说,这些硬件系统和 i386 处理器的中断系统在功能上是一致的。 图3.2 i386 PC 可编程中断控制器8259A级链示意图,3.1.1 中断产生的过程,对于中断,CPU只提供两条外接引线:NMI和INTR;这里的中断线是实际存在的电路,它们通过硬件接口连接到CPU外的设备控制器上。NMI只能通过端口操作来屏蔽,它通常用于电源掉电和物理存储器奇偶验错;INTR可通过直接设置中断屏蔽位来屏蔽,它可用来接受外部中断信号。INTR只有一条引线,为更好的处理外部设备,x86微机通过外接两片级连了可编程中断控制器8259A,以接受更多的外部中断信号。每个8259A中断控制器可以管理8条中断线,当两个8259级联的时候共可以控制15条中断线。在图3.2表示了两个级联的中断控制器,从属中断控制器的输出连接到了主中断控制器的第 3 个中断信号输入,这样,该系统可处理的外部中断数量最多可达 15 个。图的右边是 i386 PC 中各中断输入管脚的一般分配。可通过对8259A的初始化,使这15个外接引脚对应256个中断向量的任何15个连续的向量。设备通过中断线向中断控制器发送高电平告诉操作系统它产生了一个中断,而操作系统会从中断控制器的状态位知道是哪条中断线上产生了中断。,3.1.1 中断产生的过程,8259A主要完成中断优先级排队管理、接受外部中断请求和向CPU提供中断类型号这样一些任务。 由于Intel公司保留0-31号中断向量用来处理异常事件,所以,硬中断必须设在31以后,Linux则在实模式下初始化时把硬中断设在0x20-0x2F。 外部设备产生的中断实际是电平的变化信号,外部设备产生的中断信号在IRQ(中断请求)管脚上,这一信号首先由中断控制器处理。中断控制器可以响应多个中断输入,它的输出连接到 CPU 的 INT 管脚,CPU 在该管脚上的电平变化可通知处理器产生了中断。如果 CPU 这时可以处理中断,CPU 会通过 INTA(中断确认)管脚上的信号通知中断控制器已接受中断,这时,中断控制器可将一个 8 位数据放置在数据总线上,这一 8 位数据也称为中断向量号,CPU 依据中断向量号和中断描述符表(IDT)中的信息自动调用相应的中断服务程序。,3.1.1 中断产生的过程,中断控制器中的控制寄存器实际映射到了 CPU 的 I/O 地址空间中,通过对寄存器的设置,可设定中断控制器屏蔽某些中断,也可以指定中断控制器的特殊响应方式,因此,中断控制器也称为可编程中断控制器。在 Linux 中,两个中断控制器初始设置为固定优先级的中断响应方式。有关可编程控制器的详细信息可参阅有关的资料。 中断处理程序得知设备发生了一个中断,但并不知道设备发生了什么事情,只有在访问了设备上的一些状态寄存器以后,才能知道具体发生了什么,要怎么去处理。,3.1.2 中断请求,设备只有对某一条确定的中断线拥有了控制权,才可以向这条中断线上发送信号。由于计算机的外部设备越来越多,所以15条中断线已经不够用了。要使用中断线,就得进行中断线的申请,就是IRQ(Interrupt Requirement),也常把申请一条中断线称为申请一个IRQ或者是申请一个中断号。 IRQ是非常宝贵的,所以建议只有当设备需要中断的时候才申请占用一个IRQ,或者是在申请IRQ时采用共享中断的方式,这样可以让更多的设备使用中断。无论对IRQ的使用方式是独占还是共享,申请IRQ的过程都分为3步: (1) 将所有的中断线探测一遍,看看哪些中断还没有被占用。从这些还没有被占用的中断中选一个作为该设备的IRQ。 (2)通过中断申请函数申请选定的IRQ,这是要指定申请的方式是独占还是共享。 (3)根据中断申请函数的返回值决定怎么做:如果成功了则执行中断,如果没成功则或者重新申请或者放弃申请并返回错误。 22,3.1.3 置中断标志位,在处理中断的时候,中断控制器会屏蔽掉原先发送中断的那个设备,直到它发送的上一个中断被处理完了为止。因此如果发送中断的那个设备载中断处理期间又发送了一个中断,那么这个中断就被永远的丢失了。 这种情况之所以发生,是因为中断控制器并不能缓冲中断信息。当前面的一个中断没有处理完之前又有新的中断到达,中断控制器就会丢掉新的中断。这个问题可以通过设置主处理器(CPU)上的“置中断标志位”(sti)来解决,因为主处理器具有缓冲中断的功能。如果使用了“置中断标志位”,在处理完中断以后使用sti函数就可以使先前被屏蔽的中断得到服务。,3.1.3 中断处理程序的不可重入性,有时候需要屏蔽中断,是出于管理上的考虑。因为中断处理程序是不可重入的,所以不能并行执行同一个中断处理程序,因此在中断处理的过程中要屏蔽由同一个IRQ来的新中断。 由于设备驱动程序要和设备的寄存器打交道,设备寄存器就是全局变量。如果一个中断处理程序可以并行,很有可能会发生驱动程序锁死的情况。当驱动程序锁死的时候,操作系统并不一定会崩溃,但是锁死的驱动程序所支持的那个设备就不能再使用了。因此,最简单的办法就是禁止同一设备的中断处理程序并行,即设备的中断处理程序是不可重入的。由于中断处理程序要求不可重入,编写可重入的中断处理程序则几乎是不可能的。所以通常不必编写可重入的中断处理程序。但可编写可重入的设备驱动程序。 一旦中断的竞争条件出现,有可能会发生死锁的情况,严重时可能会将整个系统锁死。所以一定要避免竞争条件的出现。,3.1.4 时钟和定时器中断,操作系统应该能够在将来某个时刻准时调度某个任务。所以需要一种能保证准时调度某个任务运行的机制。希望支持每种操作系统的微处理器必须包含一个可周期性中断它的可编程间隔定时器。该定时器可以在指定的时间周期性地中断处理器。这个周期性中断被称为系统时钟周期,它像音乐中的节拍器一样来协调着系统中所有的活动。 除此之外,操作系统还必须具备一定的接口记录系统的时间,并为程序提供时间服务。一般来说,操作系统和计算机硬件一起维护着系统中的时间。Linux的时钟观念很简单:它表示系统启动后的以时钟周期计数的时间。在 PC 机中,Linux 利用 BIOS CMOS 中记录的时间(称为“硬件时钟”)作为系统启动时的时间基准,而在系统运行时,利用时钟周期测量系统的时间(称为“软件时钟”)。,3.1.4 时钟和定时器中断,Linux 利用全局变量jiffies(瞬时)作为系统时间的测量基准,所有的时间都从 1970.1.1 0:00:00 开始计算,系统启动时,将 CMOS 中记录的时间转化为从 1970.1.1 0:00:00 算起的 jiffies 值。Linux 内核中没有任何时区的概念,Linux 内核中的时间以格林尼治时间记录,将格林尼治时间转换为本地时间的任务则由应用程序负责。 Linux 的 jiffies 值由两部分组成,分别用 32 位无符号整数记录自 1970.1.1 00:00:00 开始的秒数以及秒数千分值。这样,Linux 可正确处理的时间值最大到 1970 年后的138 年,即 2108 年,而时间的计量也可精确到千分之一秒。在到达 2108 年之前,人们会想出更好的办法来计时。 Linux包含两种类型的系统定时器,它们都可以在某个系统时间上被队列例程使用,但是它们的实现稍有区别。图 3.3 说明了这两种定时器机制。,3.1.4 时钟和定时器中断,图 3.3 Linux 中的两种系统定时器,3.1.4 时钟和定时器中断,第一种是老的定时器机制,它包含指向timer_struct结构的32位指针的静态数组以及当前活动定时器的掩码 :time_active。 此定时器表中的位置是静态定义的(类似底层部分的数据结构bh_base)。数组中的元素通常是静态定义的,在系统初始化过程中填充这些元素。其入口在系统初始化时被加入到表中。 第二种是相对较新的定时器机制,它使用以定时器到期时间的升序排列的timer_list链表结构组织。,3.1.4 时钟和定时器中断,这两种方法都使用jiffies作为时间周期的终结。如果某个定时器要在 5 秒之后到期,则必须将5秒时间转换成对应的 jiffies 值,并且将它和以jiffies计数的当前系统时间相加从而得到该定时器到期的系统时间。在每个系统时钟周期里,定时器的底层部分处理过程被标记成活动状态,当调度程序下次运行时能进行定时器队列的处理。定时器底层部分处理过程要处理上述两种类型的系统定时器。对老的系统定时器来说,就是检查timer_active位是否置位。如果活动定时器已经到期(到期时间大于或等于当前系统的 jiffies),则调用对应的定时器例程,并清除 timer_active 中的相应活动位。对于新定时器,则检查链表中的 timer_list 数据结构,每个到期的定时器从链表中移出,而对应的定时器例程被调用。新的定时器机制的优点之一是能传递一个参数给定时器例程。 Linux提供了两种定时器服务。一种早期的由timer_struct等结构描述,由run_old_times函数处理。另一种“新”的服务由timer_list等结构描述,由add_timer、del_timer、cascade_time和run_timer_list等函数处理。,3.1.4 时钟和定时器中断,早期的定时器服务利用如下数据结构: struct timer_struct unsigned long expires; /本定时器被唤醒的时刻 void (*fn)(void); / 定时器唤醒后的处理函数 struct timer_struct timer_table32; /最多可同时启用32个定时器 unsigned long timer_active; / 每位对应一定时器,置1表示启用 新的定时器服务依靠链表结构突破了32个的限制,利用如下的数据结构: struct timer_list struct timer_list *next; struct timer_list *prev; unsigned long expires; unsigned long data; / 用来存放当前进程的PCB块的指针,可作为参数传 void (*function)(unsigned long); 给function ,3.1.4 时钟和定时器中断,系统启动核心时,调用start_kernal()开始各方面的初始化,在这之前,各种中断都被禁止,只有在完成必要的初始化后,直到执行完Kmalloc_init()后,才允许中断(initmain.c)。 在CPU调度时、系统调用返回前和中断处理返回前都会作判断调用do_bottom_half函数。Do_bottom_half函数依次扫描32个队列,找出需要服务的队列,执行服务后把对应该队列的bh_active的相应位置0。由于bh_active标志中TIMER_BH对应的bit为1,因而系统根据服务函数入口地址数组bh_base找到函数timer_bh()的入口地址,并马上执行该函数,在函数timer_bh中,调用函数run_timer_list()和函数run_old_timers()函数,定时执行服务。,3.2 Linux的中断处理,3.2.1 Linux中断处理程序的特色,考虑到中断处理的效率,Linux的中断处理程序分为两个部分:上半部(top half)和下半部(bottom half)。上半部的功能是“登记中断”。当一个中断发生时,就把设备驱动程序中中断例程的下半部挂到该设备的下半部执行队列中去,然后就等待新的中断的到来。这样,上半部执行的速度就会很快,就可以接受所负责设备产生的更多中断。上半部之所以要快,是因为它是完全屏蔽中断的,其它的中断只能等到这个中断处理程序执行完毕以后才能申请,不能得到及时的处理。快速的中断处理程序就可以对设备产生的中断尽可能多地进行服务。,3.2 Linux的中断处理,有些中断事件的处理比较复杂,中断处理程序必须多花一点时间才能够把事情做完。为了化解在短时间内完成复杂处理的矛盾,Linux引入了下半部的概念。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的。上半部只是将下半部放入了它们所负责的设备的中断处理队列中去,然后就什么都不管了。因此,下半部几乎做了中断处理程序所有的工作,包括查看设备上的寄存器以获得产生中断的事件信息,并根据这些信息进行相应的处理。如果下半部不知道怎么去做,它就使用鸵鸟算法来解决问题,即忽略这个事件。 由于下半部是可中断的,所以在它运行期间,如果其它的设备产生了中断,这个下半部可以暂时的中断掉,等到那个设备的上半部运行完了,再回头来运行它。但是要注意,如果一个设备中断处理程序正在运行,无论它是运行上半部还是运行下半部,只要中断处理程序还没有处理完毕,在这期间设备产生的新的中断都将被忽略掉。因为中断处理程序是不可重入的,同一个中断处理程序是不能并行的。,3.2 Linux的中断处理,Linux将中断处理程序划分成两个部分的一个原因,是要把中断的总延迟时间最小化。Linux内核定义了两种类型的中断,快速的和慢速的,这两者之间的一个区别是慢速中断自身还可以被中断,而快速中断则不能。因此,当处理快速中断时,如果有其它中断到达;不管是快速中断还是慢速中断,它们都必须等待。为了尽可能快地处理这些其它的中断,内核就需要尽可能地将处理延迟到下半部分执行。 其次,当内核执行上半部分时,正在服务的这个特殊IRQ将会被可编程中断控制器禁止,于是,连接在同一个IRQ上的其它设备就只有等到该该中断处理被处理完毕后果才能发出IRQ请求。而采用Bottom_half机制后,不需要立即处理的部分就可以放在下半部分处理,从而,加快了处理机对外部设备的中断请求的响应速度。,3.2 Linux的中断处理,还有一个原因是,处理程序的下半部分还可以包含一些并非每次中断都必须处理的操作;对这些操作,内核可以在一系列设备中断之后集中处理一次就可以了。即在这种情况下,每次都执行并非必要的操作完全是一种浪费,而采用Bottom_half机制后,可以稍稍延迟并在后来只执行一次就行了。 在下半部中也可以进行中断屏蔽。如果某一段代码不能被中断的话。可以使用cti、sti或者是save_flag、restore_flag来实现。,3.2.2 中断的相关数据结构,从数据结构入手,应该说是分析操作系统源码最常用的和最主要的方法。因为操作系统的几大功能部件,如进程管理、设备管理、内存管理等,都可以通过对其相应的数据结构的分析来弄懂其实现机制。很好的掌握这种方法,对分析Linux内核大有帮助。 中断向量在保护模式下的实现机制是中断描述符表 (Interrupt Descriptor Table, IDT),中断描述符表的结构如图3.4所示。中断描述符表即中断向量表相当于一个数组,包含256个中断描述符,每个中断描述符8位,对应硬件提供的256个中断服务例程的入口,即256个中断向量。IDT的位置由idtr确定,idtr是个48位的寄存器,高32位是IDT的基址,低16位为IDT的界限(通常为2k=256*8)。,3.2.2 中断的相关数据结构,图 3.4 Linux 的中断处理数据结构,3.2.2 中断的相关数据结构,在 i386 系统中,Linux 启动时要设置系统的中断描述符表IDT。IDT 中包含各个中断(以及异常,诸如浮点运算溢出)的服务程序地址,中断服务程序地址由 Linux 提供。每个设备驱动程序可以在图 3.4 所示的结构(irq_action)中注册自己的中断及中断处理程序地址。Linux 的中断服务程序根据 irq_action 中的注册信息调用相应的设备驱动程序的中断处理程序。和硬件相关的中断处理代码隐藏在中断服务程序中,这样,设备驱动程序的中断处理程序可在不同平台之间方便移植。一般来说,CPU 在处理中断时,首先要在堆栈中保存与 CPU 指令执行相关的寄存器(例如指令计数寄存器),然后调用中断服务程序,中断服务程序结束时再恢复这些寄存器。,3.2.2 中断的相关数据结构,图3.5 与硬中断相关的几个数据结构的关系,3.2.2 中断的相关数据结构,irq_action 实际是一个数组,其中包含指向 irqaction 的指针,每个数组元素分别定义一个 IRQ。Linux 内核提供相应的操作函数,设备驱动程序可调用这些操作函数设置相应的中断处理函数。一般在系统启动时,由各个设备驱动程序通过如下途径获取相关的设备 IRQ 并设置对应的 irq_action 数组元素所指向的 irqaction 结构。 由于0-31号中断向量已被Intel保留,就剩下32-255共224个中断向量可用。在Linux中,这224个中断向量除了0x80 (SYSCALL_VECTOR)用作系统调用总入口之外,其它都用在外部硬件中断源(包括可编程中断控制器8259A的15个irq)上。实际上,当没有定义CONFIG_X86_IO_APIC时,其它223(除0x80外)个中断向量,只利用了从32号开始的15个,其它208个空着未用。这些中断服务程序入口的设置将在下面详细说明。 与硬中断相关数据结构主要有三个, 三者关系如图3.5所示。,3.2.2 中断的相关数据结构,(1) 定义在/arch/i386/Kernel/irq.h中的struct hw_interrupt_type数据结构,它是一个抽象的中断控制器。这包含一系列的指向函数的指针,这些函数处理控制器特有的操作: typename:控制器的名字。 startup:允许从给定的控制器的IRQ所产生的事件。 shutdown:禁止从给定的控制器的IRQ所产生的事件。 handle:根据提供给该函数的IRQ,处理唯一的中断。 enable和disable:这两个函数基本上和startup和shutdown相同; 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); ;,3.2.2 中断的相关数据结构,(2) 定义在/arch/i386/Kernel/irq.h中的另外一个数据结构是irq_desc_t,它具有如下成员: status:一个整数。代表IRQ的状态:IRQ是否被禁止了,有关IRQ的设备当前是否正被自动检测,等等。 handler:指向hw_interrupt_type的指针。 action:指向irqaction结构组成的队列的头。正常情况下每个IRQ只有一个操作,因此链接列表的正常长度是1(或者0)。但是,如果IRQ被两个或者多个设备所共享,那么这个队列中就有多个操作。 depth:irq_desc_t的当前用户的个数。主要是用来保证在中断处理过程中IRQ不会被禁止。,3.2.2 中断的相关数据结构,irq_desc是irq_desc_t 类型的数组。对于每一个IRQ都有一个数组入口,即数组把每一个IRQ映射到和它相关的处理程序和irq_desc_t中的其它信息。 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;,3.2.2 中断的相关数据结构,(3) 定义在include/linux/ interrupt.h中的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不是共享的,因此这个成员就为空。,3.2.2 中断的相关数据结构,struct irqaction void (*handler)(int, void *, struct pt_regs *); unsigned long flags; unsigned long mask; const char *name; void *dev_id; struct irqaction *next; ;,3.2.3 中断向量表IDT的初始化,Linux内核在初始化阶段完成了对页式虚拟管理的初始化后,调用trap_init()和init_IRQ()两个函数进行中断机制的初始化。其中trap_init()主要是对一些系统保留的中断向量的初始化,而init_IRQ()则主要是用于外设的中断。,3.2.3 中断向量表IDT的初始化,void _init trap_init(void) #ifdef CONFIG_EISA if (isa_readl(0x0FFFD9) = E+(I8)+(S16)(A24) EISA_bus = 1; #endif set_trap_gate(0,_error); set_trap_gate(1,3.2.3 中断向量表IDT的初始化,set_system_gate(5, set_trap_gate(6,&invalid_op) set_trap_gate(7,device_not_available); set_trap_gate(8,&double_fault); set_trap_gate(9,&coprocessor_segment_overrun); set_trap_gate(10,&invalid_TSS); set_trap_gate(11,&segment_not_present); set_trap_gate(12,&stack_segment); set_trap_gate(13,&general_protection); set_trap_gate(14,&page_fault); set_trap_gate(15,&spurious_interrupt_bug); set_trap_gate(16,&coprocessor_error); set_trap_gate(17,&alignment_check); set_trap_gate(18,&machine_check); set_trap_gate(19,&simd_coprocessor_error);,3.2.3 中断向量表IDT的初始化,set_system_gate(SYSCALL_VECTOR, /12 即二进制的1100b,类型码为100,即调用门 这些函数都调用同一个子程序_set_gate(),第一个参数用以设置中断描述符表idt_table中的第n项,第二个参数对应于门格式中的D位加类型位段,第三个参数是对应的DPL位段。,3.2.3 中断向量表IDT的初始化,#define _set_gate(gate_addr,type,dpl,addr) do int _d0,_d1; _asm_ _volatile_(“movw %dx,%ax“) “movw %4,%dx “ “mov1 %eax,%0 “ “mov1 %edx,%1“ :“=m“ (*(long * ) (gate_addr), “=m“ (*(1+(long *)(gate_addr),“= while(0),3.2.3 中断向量表IDT的初始化,在第一个“:”到第二个“:”之间为输出部,有4个约束输出,将有4个变量会被改变,分别为%0、%1、%2和%3相结合。其中%0和%1都是内存单元,分别和gate_addr、gate_addr+1结合,%2于局部变量_d0结合,存放在寄存器%eax中;%3于局部变量_d1结合,存放在寄存器%edx中。 第二个“:”之后的部分是输入部,输出部已经定义了%0-%3,输入部中的第一个变量为%4,而紧接着的第二、第三个变量分别等价于输出部的%3和%2。输入部中说明的个输入变量地值,包括%3和%2,都会在引用这些变量之前设置好。在执行指令部的代码之前,会先将%eax设成(_KERNEL_CS16),把%edx设为addr,%4变量设置为(0x8000+(dpl13)+type8),则%4变量对应于门结构中的第32-47位,其P位为1。,3.2.3 中断向量表IDT的初始化,指令部第一条指令“movw %dx,%ax”,将%dx的低16位移入%ax的低16位,这样,在%eax中,其高16位为_KERNEL_CS,而低16位为addr的低16位,形成了门结构中的第0-31位。 第二条指令“movx %4 ,%dx”,将%4放入%dx中,也就是将门结构中的第32-47位放在%dx中,而对于%edx来说,就对应着门结构中的高32位。 第三条指令“mov1 %eax,%0”,将%edx写入变量%0中,即*gate_addr。 第4条指令“mov1 %eax,%1”将%edx写入变量%1中,即*(gate_addr+1)。 将第三、第4条指令合起来看,就是将整个门结构的内容都写道*gate_addr中去了。,3.2.3 中断向量表IDT的初始化,void _inti init_IRQ(void) int i; #ifndef CONFIG_X86_VISWS-APIC init_ISA_irqs(); #else init_VISWS_APIC_irqs(); #endif / Cover the whole vector space,no vector can escape / us. (some of these will be overridden and become / special SMP interrupts) for (i=0;i NR_IRQS;i+) int vector = FIRST_EXTERNAL_VECTOR + i; if (vector != SYSCALL_VECTOR) set_intr_gate(vector,interrupti); ,3.2.3 中断向量表IDT的初始化,#ifdef CONFIG_SMP / IRQ0 must ve given a fixed assignment and initialized, / because its used before the I0-APIC is set up. set_intr-gate(FIRST_DEVICE_VECTOR,interrupt0); / The reschedule interrupt is a CPU-to-CPU reschedule-helper IPI,driven by wakeup. set_intr_gate(RESCHEDULE_VECTOR,reschedule_interrupt); / IPI for generic function call set_intr_gate(CALL_FUCTION_VECTOR,call_funtion_interrupt); #endif,3.2.3 中断向量表IDT的初始化,#ifdef CONFIG_X86_LOCAL_APIC / IPI vectors for APIC spurious and error interrupts set_intr_gate(SPURIOUS_APIC_VECTOR,spurious_interrupt); set_intr_gate(ERROR_APIC_VECTOR,error_interrupt); #endif / Set the clock to HZ Hz, we already have a valid vector now; outb_p(0x34,0x43); / binary, mode 2, LSB/MSB ,ch 0 outb_p(LATCH ,3.2.3 中断向量表IDT的初始化,i386体系支持256个中断向量。扣除了为CPU保留的向量后,很难说剩下的中断向量是否够用。所以,Linux系统中为每个中断向量设置一个队列,根据每个中断源所使用的中断向量,将其中断服务程序挂到相应的队列中。数组irq_desc中的每个元素则是这样一个队列头部以及控制结构。当中断发生时,首先执行中断向量相对应的一段总服务程序,根据具体中断源的设备号在其所属队列中找到特定的服务程序加以执行。 首先对PC的中断控制器8259A的初始化,并初始化了数组iirq_desc。接着从FIRST_EXTERNAL_VECTOR开始,设立NR_IRQS个中断向量的 IDT表项。常数FIRST_EXTERNAL_VECTOR定义为 0x20,而NR_IRQS则为224。其中还跳过了用于系统调用的向量0x80。,3.2.3 中断向量表IDT的初始化,忽略多处理器SMP结构和SG1工作站的特殊处理,剩下的就是对系统时钟的初始化。在PC中,定时器/计数器芯片8254共有三个通道,通道0是一个产生实时时钟信号的系统计时器,而程序中要设置的也就是通道0。用于控制8254的端口共有4个,前三个分别对应于单个通道的端口,最后一个通道对应于8254的控制字寄存器端口。 outb_p(0x34,0x43);/设置通道的工作方式 /选通通道0,先读写高字节,后读写低字节,工作于方式2,二进制数 outb_p(LATCH /写入高字节 /设置通道0的记数值,3.2.3 中断向量表IDT的初始化,到此已经设置好了IDT,也有了一个中断向量:0号中断时钟中断。虽然该中断服务的入口地址已经设置到中断向量表中,但还没有把0号中断具体的中断服务程序挂到0号中断的队列中去。此时,这些中断地队列都是空的,即使开了中断并产生了时钟中断,也只不过是让它在中断处理的总服务程序中空跑一趟。 设置好了中断向量表,中断队列都还是空的。想要中断程序生效,下一步就要初始化中断请求队列,并把具体的中断服务程序挂到中断队列中去。,3.2.4 中断请求队列的初始化,通用中断门是多个中断源共用的,在系统运行的过程中允许动态改变。因此,在IDT的初始化阶段只是为每个中断向量准备一个中断请求队列,即中断请求队列的数组irq_desc。中断请求队列头部的数据结构是在include/linux/irq.h中定义的。,3.2.4 中断请求队列的初始化,struct hw_interrupt_type const char * typename; /赋予控制器的人工可读的名字 unsigned int (*startup)(unsigned int irq); /允许从给定的控制器的IRQ事件发生 void (*shutdown)(unsigned int irq); /禁止从给定的控制器的IRQ事件发生 void (*enable)(unsigned int irq); void (*ack)(unsigned int irq); void (*end)(unsigned int irq); void (*set_affinity)(unsiged int irq,unsigned long mask); ;,3.2.4 中断请求队列的初始化,typedef struct hw_interupt_type hw_irq_controller; typedef struct unsigned int status; / IRQ status hw_irq_controller *handler; struct irqaction *action; /IRQ action list unsigned int depth; / nested irq disables /irq_desc_t 当前用户的个数。用于保证在事件处理过程中IRQ不会被禁止 spinlock_t lock; _cacheline_aligned irq_desc_t; extern irq_desc_t irq_descNR_IRQS;,3.2.4 中断请求队列的初始化,每个队列头中,用指针action来维持一个由中断服务程序描述项构成的单链表,还有一个指针handler指向另一个数据结构hw_interrupt_type。Hw_interrupt_type中的一些函数指针,用于该队列的,而不是用于具体的中断源的服务。 这些函数都是在init_ISA_irqs中设置好的。 void_init init /ISA_irqs中设置好的 int i; init_8259A(0); for (i=0;i irq_desci.handler=“ INTA-cycle old-style 16 (i,3.2.4 中断请求队列的初始化,先对8259A进行初始化,将开头16个中断请求队列的handler指针设置成指向数据结构i8259A_irq_type。 struct irqcation void (*handler)(int,void *,struct pt_regs *); /指向具体中断服务程序 unsigned long flags; unsigned long mask; const char *name; void *dev_id; struct irqaction *next; ;,3.2.4 中断请求队列的初始化,IDT表初始化之初,每个中断服务队列都是空的。真正的中断服务要到具体设备的初始化程序将其中断服务程序通过reques_irq()向系统“登记”,挂进某个中断请求队列。 int request_irq(unsigned int irq, void (*handler)(int,void*, struct pt_regs *), unsigned long irqflags, const char * devname, void *dev_id) 参数表中的irq为中断请求队列的序号,对应中断控制器的一个通道。这个中断请求号和CPU所用的中断向量是不同的,中断请求号0相当于中断向量0x20。Ireflags 是一些标志位,其中的SA_SHIRQ标志与其它中断源共用该中断请求通道。此时,必须提供一个dev_id以供区别。 在request_irq中分配并设置了irqaction结构,便调用setup_irq将其链入响应的中断请求队列。,3.3 Linux2.4 的软中断处理机制,根据与软件相关或和硬件相关,Linux的中断可分为软中断和硬中断两种。软中断是一种“信号机制”,Linux通过信号来产生对进程的各种中断操作,现在知道的信号共有31个。 一般来说,软中断是由内核机制的触发事件引起的(例如进程运行超时),但是不可忽视有大量的软中断也是由于和硬件有关的中断引起的,例如当打印机端口产生一个硬件中断时,会通知和硬件相关的硬中断,硬中断就会产生一个软中断并送到操作系统内核里,内核就会根据这个软中断唤醒在打印机任务队列中的睡眠进程。,3.3 Linux2.4 的软中断处理机制,软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。硬中断是外部设备对CPU的中断,软中断通常是硬中断服务程序对内核的中断,信号则是由内核(或其它进程)对某个进程的中断。软中断的一种典型应用是所谓的“下半部”(bottom half),它的得名来自于将硬件中断处理分离成“上半部”和“下半部”两个阶段的机制:上半部在屏蔽中断的上下文中运行,用于完成关键性的处理动作;而下半部则处理相对不是非常紧急的,比较耗时的动作。因此,下半部由系统自行安排运行时机,不在中断服务上下文中执行。bottom half的应用也是激励内核发展出目前的软中断机制的原因。软中断是Linux系统原“底半处理”的升级,在原有的基础上发展的新的处理方式,以适应多CPU 、多线程的软中断处理。要了解软中断,必须要先了解原来的底半处理的处理机制。,3.3.2 底半处理,在Linux内核中,bottom half通常用“bh“表示,最初用于在特权级较低的上下文中完成中断服务的非关键耗时动作,现在也用于一切可在低优先级的上下文中执行的异步动作。最早的bottom half实现是借用中断向量表的方式,在目前的2.4.x内核中仍然可以看到: static void (*bh_base32)(void);/ kernel/softirq.c 系统定义了一个函数指针数组,共有32个函数指针,采用数组索引来访问,与此相对应的是一套函数: void init_bh(int nr,void (*routine)(void); /为第nr个函数指针赋值为routine void remove_bh(int nr); /动作与init_bh()相反,卸下nr函数指针 void mark_bh(int nr); /标志第nr个bottom half可执行了,3.3.2 底半处理,由于历史的原因,bh_base各个函数指针位置大多有了预定义的意义,在v2.4.x内核里有这样一个枚举: enum TIMER_BH = 0, TQUEUE_BH, DIGI_BH, SERIAL_BH, RISCOM8_BH, SPECIALIX_BH, AURORA_BH, ESP_BH, SCSI_BH, IMMEDIATE_BH, CYCLADES_BH, CM206_BH, JS_BH, MACSERIAL_BH, ISICOM_BH ;,3.3.2 底半处理,并约定某个驱动使用某个bottom half位置,比如串口中断就约定使用SERIAL_BH,现在用得多的主要是TIMER_BH、TQUEUE_BH和IMMEDIATE_BH,但语义已经很不一样了,因为整个bottom half的使用方式已经很不一样了,这三个函数仅仅是在接口上保持了向下兼容,在实现上一直都在随着内核的软中断机制在变。在2.4.x内核里,它用的是tasklet机制。,3.3.2 与底半处理相关的数据结构:,某些特殊时刻并不能在内核中执行操作。例如中断处理过程中。当中断发生时,处理器将停止当前正在执行的指令, 操作系统将中断发送到相应的设备驱动程序上去处理。此时系统中其他程序都不能运行, 因此,在这段时间内,设备驱动程序要以最快的速度完成中断处理,设备驱动的中断处理过程不宜过长。Linux 内核利用底层处理过程帮助实现中断的快速处理。对于在中断处理过程之外进行的其他大部分工作,Linux底层部分处理机制可以让设备驱动和Linux内核其他部分将这些工作进行排序以延迟执行。,3.3.2 与底半处理相关的数据结构:,系统中最多可以有32个不同的底层处理过程;bh_base是指向这些过程入口的指针数组。而bh_active和 bh_mask用来表示那些处理过程已经安装以及那些处于活动状态。如果bh_mask的第N位置位则表示bh_base的 第N个元素包含底层部分处理例程。如果bh_active的第N位置位,则表示第N个底层处理过程例程,可在调度器认为合适的时刻调用。这些索引被定义成静态的;定时器底层部分处理例程具有最高优先级(索引值为0), 控制台底层部分处理例程其次(索引值为1)。典型的底层部分处理例程含有与之相连的

温馨提示

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

最新文档

评论

0/150

提交评论