Linux操作系统第五章_第1页
Linux操作系统第五章_第2页
Linux操作系统第五章_第3页
Linux操作系统第五章_第4页
Linux操作系统第五章_第5页
已阅读5页,还剩47页未读 继续免费阅读

下载本文档

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

文档简介

第五章中断与异常中断的基本知识中断描述符表的初始化中断处理中断的下半部处理机制中断的应用-时钟中断数据传送控制方式选择和衡量控制方式的原则:数据传送速度足够高系统开销小,所需的处理控制程序少;能充分发挥硬件资源的能力;数据传送控制方式程序直接控制方式中断控制方式DMA方式通道方式ProgrammedDirectControl否外围设备做接收或发送数据准备接收到start命令标志触发器置“done”等待CPU来的下条指令准备完毕?是否CPU发start命令等待执行下条指令开始数据传送设备标志触发器为done”?是(a)(b)优点:控制简单,不需要多少硬件支持缺点:1.CPU和外设只能串行工作;2.CPU在一段时间内只能和一台外设交换数据,不能实现设备间的并行操作;3.无法发现和处理由于设备和其他硬件所产生的错误。Interrupt中断控制方式的处理过程接收到CPU发来的start指令准备数据并将其置入缓冲寄存器缓冲寄存器满吗?控制器发中断信号向设备发start指令将中断允许位置1调度程序调度其他进程其他进程执行收到中断信号了吗?中断处理被中断进程执行否否是是设备CPU优点:并行操作缺点:1.I/O控制器的数据缓冲寄存器满,就会发中断。此寄存器一般较小,则在一次数据传送过程中,中断次数较多,将耗去大量CPU时间;若设备间并行操作,则中断次数增加,造成CPU无法响应中断和数据丢失。2.中断方式是假设外设速度很低。如果外设速度很高,则造成CPU来不及取走数据缓冲寄存器中的数据,造成数据丢失。中断控制的主要优点:CPU只有在I/O需要服务时才响应。外部中断:外部设备所发出的I/O请求。内部中断:也称之为“异常”,是为解决机器运行时所出现的某些随机事件及编程方便而出现的。<>中断掠影中断向量:8位无符号整数中断源的编号0~255外设可屏蔽中断:32~47屏蔽外部I/O请求

中断线、IRQ异常及非屏蔽中断:0~31异常:CPU内部中断非屏蔽中断:计算机内部硬件出错引起的异常软中断:48~255中断描述符表:描述中断的相关信息中断相关的汇编指令:<>5.1中断的基本知识中断向量-每个中断源都被分配一个8位无符号整数作为类型码,即中断向量。(0~255)中断的种类:中断:外部可屏蔽中断外部非屏蔽中断异常:不使用中断控制器,不能被屏蔽故障陷阱<>中断向量-中断源的类型Intelx86通过两片中断控制器8259A来响应15个外中断源,每个8259A可管理8个中断源。外部设备拥有相应权限时

,可以向特定的中断线发送中断请求信号IRQ。外部I/O请求的屏蔽:从CPU的角度,清除eflag的中断标志位(关中断)从中断控制器的角度,将中断屏蔽寄存器的相应位置位<>外设可屏蔽中断

异常就是CPU内部出现的中断,即在CPU执行特定指令时出现的非法情况。非屏蔽中断就是计算机内部硬件出错时引起的异常情况。

Intel把非屏蔽中断作为一种异常来处理。在CPU执行一个异常处理程序时,就不再为其他异常或可屏蔽中断请求服务。

<>异常及非屏蔽中断

Intelx86处理器发布了大约20种异常(具体数字与处理器模式有关)。Linux内核必须为每种异常提供一个专门的异常处理程序。

<>异常及非屏蔽中断中断描述符表(IDT):即中断向量表,每个中断占据一个表项(门描述符,8字节)。<>中断描述符表主要门描述符为:(1)中断门(Interruptgate):类型码为110,请求特权级(DPL)为0。

中断门包含了一个中断或异常处理程序所在段的选择符和段内偏移量。(2)陷阱门(Trapgate):类型码为111,与中断门类似,其唯一的区别是,不关中断。(3)系统门(Systemgate):Linux内核特别设置的,用来让用户态的进程访问Intel的陷阱门,DPL为3。系统调用就是通过系统门进入内核的。中断描述符表寄存器IDTR:存放中断描述符表在内存的起始地址。中断描述符表寄存器IDTR是一个48位的寄存器,其低16位保存中断描述符表的大小,高32位保存中断描述符表的基址。中断描述符表调用过程指令CALL:CALL过程名调用中断过程的指令INT

INT中断向量(0~255)中断返回指令IRETIRET

加载中断描述符表的指令LIDT

LIDT48位的伪描述符<>相关汇编指令

Linux内核在系统的初始化阶段要初始化可编程控制器8259A;将中断描述符表的起始地址装入IDTR寄存器,并初始化表中的每一项。

当计算机运行在实模式时,中断描述符表被初始化,并由BIOS使用。真正进入了Linux内核,中断描述符表就被移到内存的另一个区域,并为进入保护模式进行预初始化:用汇编指令LIDT对中断向量表寄存器IDTR进行初始化,即把IDTR置为0;把中断描述符表IDT的起始地址装入IDTR;setup_idt()函数填充中断描述表中的256个表项。<>5.2中断描述符表的初始化IDT表项的设置通过_set_gate()函数实现

调用该函数在IDT表中插入一个中断门:voidset_intr_gate(unsignedintn,void*addr){_set_gate(idt_table+n,14,0,addr);}调用该函数在IDT表中插入一个陷阱门:staticvoid__init

set_trap_gate(unsignedintn,void*addr){_set_gate(idt_table+n,15,0,addr);}调用该函数在IDT表中插入一个系统门:staticvoid__initset_system_gate(unsignedintn,void*addr){_set_gate(idt_table+n,15,3,addr);}<>IDT表项的设置trap_init()函数用于设置中断描述符表开头的19个陷阱门和系统门。这些中断向量都是CPU保留用于异常处理的,例:set_trap_gate(0,÷_error);set_trap_gate(1,&debug);set_trap_gate(19,&simd_coprocessor_error);set_system_gate(SYSCALL_VECTOR,&system_call);

<>初始化陷阱门和系统门中断门的设置是由init_IRQ()函数中的一段代码完成的:设置时必须跳过用于系统调用的向量0x80

中断处理程序的入口地址是一个数组interrupt[],数组中的每个元素是指向中断处理函数的指针。

<>中断门的设置for(i=0;i<NR_IRQS;i++){intvector=FIRST_EXTERNAL_VECTOR+i;if(vector!=SYSCALL_VECTOR)set_intr_gate(vector,interrupt[i]);}中断和异常的硬件处理:从硬件的角度看CPU如何处理中断和异常中断请求队列的建立:方便外设共享中断线

中断处理程序的执行

从中断返回:调用恢复中断现场的宏RESTORE_ALL,彻底从中断返回

<>5.3中断处理当CPU执行了当前指令之后,CS和EIP这对寄存器中所包含的内容就是下一条将要执行指令的虚地址。在对下一条指令执行前,CPU先要判断在执行当前指令的过程中是否发生了中断或异常。如果发生了一个中断或异常,那么CPU将做以下事情:<>中断和异常的硬件处理

确定所发生中断或异常的向量i(在0~255之间)通过IDTR寄存器找到IDT表,读取IDT表第i项(或叫第i个门)分“段”级、“门”级两步进行有效性检查检查是否发生了特权级的变化

中断和异常处理中CPU的工作SSESPEFLAGSCSEIPERRORCODEEFLAGSCSEIPERRORCODE堆栈增长方向中断发生前夕的SS:ESP返回地址错误码<>中断处理程序堆栈由于硬件条件的限制,很多硬件设备共享一条中断线。为方便处理,Linux为每条中断线设置了一个中断请求队列。中断服务例程与中断处理程序中断线共享的数据结构注册中断服务例程<>中断请求队列的建立中断服务例程(InterruptServiceRoutine):每个中断请求都有自己单独的中断服务例程。中断处理程序:共享同一条中断线的所有中断请求有一个总的中断处理程序。在Linux中,15条中断线对应15个中断处理程序。<>中断服务例程与中断处理程序

structirqaction{void(*handler)(int,void*,structpt_regs*);unsignedlongflags;unsignedlongmask;constchar*name;void*dev_id;structirqaction*next;};

Handler:指向一个具体I/O设备的中断服务例程Flags:用一组标志描述中断线与I/O设备之间的关系。SA_INTERRUPT:中断处理程序执行时必须禁止中断SA_SHIRQ:允许其它设备共享这条中断线。SA_SAMPLE_RANDOM:内核可以用它做随机数产生器。SA_PROBE:内核正在使用这条中断线进行硬件设备探测。<>中断线共享的数据结构

<>structirqaction{void(*handler)(int,void*,structpt_regs*);unsignedlongflags;unsignedlongmask;constchar*name;void*dev_id;structirqaction*next;};

Name:I/O设备名dev_id:指定I/O设备的主设备号和次设备号(参见第9章)。Next:指向irqaction描述符链表的下一个元素

中断线共享的数据结构

初始化IDT表之后,必须通过request_irq()函数将相应的中断服务例程挂入中断请求队列,即对其进行注册。

在关闭设备时,必须通过调用free_irq()函数释放所申请的中断请求号。

<>注册中断服务例程

int

request_irq(unsigned

int

irq,void(*handler)(int,void*,struct

pt_regs*),unsignedlongirqflags,constchar*devname,void*dev_id)假定一个程序要对/dev/fd0/(第一个软盘对应的设备)设备进行访问,通常将IRQ6分配给软盘控制器,给定这个中断号6,软盘驱动程序就可以发出下列请求,以将其中断服务例程挂入中断请求队列:

request_irq(6,floppy_interrupt, SA_INTERRUPT|SA_SAMPLE_RANDOM,"floppy",NULL);

注册中断服务例程

所有的中断处理程序都执行四个基本的操作:在内核栈中保存IRQ的值和寄存器的内容。给与IRQ中断线相连的中断控制器发送一个应答,这将允许在这条中断线上进一步发出中断请求。执行共享这个IRQ的所有设备的中断服务例程(ISR)。跳到ret_from_intr()的地址后终止。<>中断处理程序的执行

CPU从中断控制器的一个端口取得中断向量I

根据I从中断描述符表IDT中找到相应的中断门从中断门获得中断处理程序IRQn_interrupt的入口地址

判断是否要进行堆栈切换调用do_IRQ()对所接收的中断进行应答,并禁止这条中断线调用handle_IRQ_event()来运行对应的中断服务例程-处理最紧急的任务。

<>中断处理程序的执行

IRQn_interrupt:pushl$n-256jmpcommon_interruptcommon_interrupt:SAVE_ALLcalldo_IRQjmpret_from_intrdo{status|=action->flags;action->handler(irq,action->dev_id,regs);action=action->next;}while(action);

当处理所有外设中断请求的函数do_IRQ()执行时,内核栈顶包含的就是do_IRQ()的返回地址,这个地址指向ret_from_intr。从中断返回时,CPU要调用恢复中断现场的宏RESTORE_ALL,彻底从中断返回。

<>从中断返回中断服务例程在中断请求关闭的条件下执行,避免嵌套使中断控制复杂化。

系统不能长时间关中断运行,因此内核应尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。内核把中断处理分为两部分:上半部(tophalf)和下半部(bottomhalf),上半部内核立即执行,而下半部留着稍后处理。

<>5.4中断的下半部处理机制

为什么把中断分为两部分来处理?一个快速的“上半部”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。这一部分做的工作很少。下半部运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。Linux内核下半部的实现机制在内核的演变:bottomhalf(简称bh)在2.4以后的版本:小任务(tasklet)下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。

为什么把中断分为两部分来处理下半部是一个不能与其他下半部并发执行的高优先级小任务。

bh_base是一个指向下半部的指针数组,用于组织所有下半部

bh_base数组共有32项,每一项都是一种下半部。<>下半部

下半部外部设备TIMER_BH定时器TQUEUE_BH

周期性任务队列SERIAL_BH

串行端口IMMEDIATE_BH

立即任务队列Linux常用的下半部一个函数指针数组bh_base[],它把所有的后半部分都组织起来,其大小为32,数组中的每一项就是一个后半部分,即一个bh函数。设置了两个32位无符号整数bh_active和bh_mask,每个无符号整数中的一位对应着bh_base[]中的一个元素。在2.4以前的内核中,每次执行完do_IRQ()中的中断服务例程以后,以及每次系统调用结束之前,就在一个叫do_bottom_half()的函数中执行相应的bh函数。在do_bottom_half()中对bh函数的执行是在关中断的情况下进行的,这是因为,对单CPU来说,bh函数的执行可以不嵌套;而对于多CPU来说,在同一时间内最多只允许一个CPU执行bh函数。那么,在新内核的设计中,是改进bh机制还是抛弃bh机制,建立一种新的机制?2.4选择了一种折中的办法,继续保留bh机制,另外增加一种或几种机制,并把它们纳入一个统一的框架中,这就是2.4内核中的软中断(softirq)机制。软中断机制:软中断却在任何时候都不需要串行化。Tasklet机制:建立在软中断之上,同一个tasklet只能运行在一个CPU上,而不同的tasklet可以同时运行在不同的CPU上。在这种情况下,tasklet就不需要是可重入的,因此,编写tasklet比编写一个软中断要容易。

Bh机制在2.4中依然存在,但不是作为一个单独的机制存在,而是建立在tasklet之上。因此,在2.4版中,设备驱动程序的开发者必须更新他们原来的驱动程序,用tasklet代替bh。下半部

小任务是指待处理的下半部,其数据结构为tasklet_struct,每个结构代表一个独立的小任务。小任务既可以静态地创建,也可以动态地创建。

<>小任务机制

structtasklet_struct{Structtasklet_struct*next;unsignedlongstate;atomic_tcount; void(*func)(unsignedlong);unsignedlongdata; };

如果准备静态地创建一个小任务(也就是对它直接引用),使用下面两个宏中的一个:DECLARE_TASKLET(name,func,data)DECLARE_TASKLET_DISABLED(name,func,data)这两个宏都能根据给定的名字静态地创建一个tasklet_struct结构。第一个宏把创建的小任务的引用计数器设置为0,因此,该小任务处于激活状态。另一个把引用计数器设置为1,所以该小任务处于禁止状态。例如:DECLARE_TASKLET(my_tasklet,my_tasklet_handler,dev);这行代码其实等价于structtasklet_structmy_tasklet={NULL,0,ATOMIC_INIT(0),tasklet_handler,dev};声明和使用小任务voidtasklet_handler(unsignedlongdata)小任务不能睡眠,不能在小任务中使用信号量或者其它产生阻塞的函数。但它运行时可以响应中断。通过调用tasklet_schedule()函数并传递给它相应的tasklet_struct指针,该小任务就会被调度以便适当的时候执行:tasklet_schedule(&my_tasklet)在小任务被调度以后,只要有机会它就会尽可能早的运行。调用tasklet_disable()函数来禁止某个指定的小任务。tasklet_disable(&my_tasklet);调用tasklet_enable()函数可以激活一个小任务。tasklet_enable(&my_tasklet); 可以调用tasklet_kill()函数从挂起的队列中去掉一个小任务。<>编写并调度自己的小任务任务队列就是指以双向队列形式连接起来的任务链表,每一个链表元素都描述了一个可执行的内核任务。tq_struct来描述任务队列中的每一个链表成员。三个特殊的任务队列:tq_immediate任务队列,由IMMEDIATE_BH下半部运行,该队列中包括要执行的内核函数和标准的下半部。

tq_timer任务队列,由TQUEUE_BH下半部运行,每次时钟中断都激活这个下半部。tq_disk任务队列,用于块设备任务。<>任务队列大部分PC机中有两个时钟源,分别是实时时钟(RTC)和操作系统(OS)时钟。实时时钟也叫硬件时钟,它靠电池供电,即使系统断电,也可以维持日期和时间。RTC和OS时钟之间的关系通常也被称作操作系统的时钟运作机制。不同的操作系统,其时钟运作机制也不同。

<>5.5中断的应用-时钟中断时钟运作机制OS时钟是由可编程定时/计数器产生的输出脉冲触发中断而产生的。操作系统的“时间基准”由设计者决定,Linux的时间基准是1970年1月1日凌晨0点。OS时钟记录的时间就是系统时间。系统时间以“时钟节拍”为单位。Linux中用全局变量jiffies表示系统自启动以来的时钟节拍数目。每次时钟中断jiffies就加1.

<>Linux时间系统每一次时钟中断的产生都触发下列几个主要的操作:自系统启动以来所花费的时间加1更新时间和日期确定当前进程在CPU上已运行了多长时间,如果已经超过了分配给它的时间,则抢占它更新资源使用统计数检查定时器时间间隔是否已到,如果是,则调用适当的函数

<>时钟中断处理程序时钟中断处理程序本身来完成通过TIMER_BH和TQUEUE_BH下半部调用的函数来完成timer_bh()函数与TIMER_BH下半部相关联,它在每个时钟节拍都被激活。

TIMER_BH下半部以关中断调用update_times()函数,该函数会以关中断来更新xtime。更新了系统时钟xtime之后,update_times()再次打开中断。时钟中断的下半部处理<>定时器是管理内核所花时间的基础,也被称为动态定时器或内核定时器。

定时器的使用:执行一些初始化工作,设置一个到期时间,指定

温馨提示

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

评论

0/150

提交评论