LKD_chapter6_中断和中断处理程序

Chapter6 中断与中断处理程序


    中断机制:让硬件需要的时候再向内核发出信号(变内核主动为硬件主动)。

    中断本质上是一种特殊的电信号,由硬件设备发向处理器。处理器接收到中断后,会马上向向操作系统反应此信号的到来,然后由OS负责处理这些新来的数据。

    不同的设备对应的中断不同,每个中断都通过一个唯一的数字来标识。这个中断值常被称为中断请求线(IRQ)。

    异常:产生时必须考虑与处理器时钟同步,常常称为同步中断。

    中断处理程序(ISR):中断处理程序通常不是和特定的设备相关联,而是和特定的中断相关联。一个设备可产生多种不同的中断,就会对应多个中断处理程序。一个设备的中断处理程序是设备驱动程序的一部分。

    中断处理程序与其他内核函数的区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于特殊的中断上下文中。对于硬件而言,迅速对齐中断服务非常重要,对于系统的其他部分,中断处理程序在尽可能短的时间内完成同样重要。

    一般把中断处理切成两部分,中断处理程序是上半部(top half)--接收到一个中断,它就立即开始执行,但只做有严格时限的工作,如对接收的中断应答或复位硬件,这些工作在中断被禁止的情况下完成的。能够被允许稍后完成的工作会推迟到下半部(bottom half)去。此后,在合适的时机,下半部会被开中断执行。

注册中断处理程序

    注册中断处理程序,使用下面的接口注册并激活一个中断处理程序。

int request_irq(unsigned int irq, /*要分配的中断号*/

irqreturn_t (*handler)(int, void *, struct pt_regs *), /*处理中断的程序,函数原型特定的*/

unsigned long irqflags,  /*中断掩码值*/

const char * devname,  /*与中断相关的设备ASCII文本表示法*/

void *dev_id)         /*主要用于共享的中断线,必需传递唯一的信息,实践中往往会通过它传递驱动程序的设备结构*/


SA_SHIRQ Interrupt is shared 多个中断处理程序之间共享中断线。在同一个给定线上注册的每个处理程序必须制定这个标志。

SA_INTERRUPT Disable local interrupts while processing 快速中断处理程序,迅速执行并且调用频率很高的中断服务函数。在本地处理器上,快速中断处理程序在禁止所有中断的情况下执行。使得快速中断不受其他终端的干扰。默认情况下(无该标识)除了正在运行的中断处理程序对应的那条中断线被屏蔽外,所有其他中断都是激活的。

SA_SAMPLE_RANDOM The interrupt can be used for entropy 此设备的中断对内核熵池有贡献。内核熵池负责提供从各种随机事件导出真正的随机数。


    返回值-EBUSY:指示给定中断线已经在使用(未指定SA_SHIRQ)。

    request_irq函数可能会睡眠,不要在中断上下文或其他不允许阻塞的代码中调用该函数。该函数睡眠由于:注册过程中内核需要在/proc/irq文件创建一个与中断对应的项,函数proc_mkdir在创建新的procfs项时,会间接调用kmalloc导致函数睡眠。

初始化硬件和注册中断处理程序的顺序必须正确,以防止中断处理程序在设备初始化完成之前就开始执行。

释放中断处理程序

    指定的中断线不是共享的,删除处理函数的同时将禁用这条中断线。如果中断线是共享的,则仅删除dev_id所对应的处理程序,而这条中断线仅在删除最后一个处理程序时才会被禁用。必须在进程上下文中调用free_irq函数。

void free_irq(unsigned int irq, void *dev_id)

编写中断处理程序

static irqreturn_t intr_handler(int irq, void *dev_instance, struct pt_regs *regs);

参数irq:处理程序要响应的中断的中断线号。

参数dev_instance:与中断处理程序注册时传递给request_irq()的参数dev_id一致。该值在共享中断中要确定唯一性,通常会把设备结构传递给dev_id。

参数regs:包含处理中断之前处理器的寄存器和状态。

返回值为 irqreturn_t类型,实质上是int类型。返回为IRQ_NONE和IRQ_HANDLED。可使用宏IRQ_RETVAL(x)来测试返回值,x为非0值,该宏返回IRQ_HANDLED,否则返回IRQ_NONE。

    中断处理程序通常会标记位static,从来不会被别的文件中的代码调用。

    Linux中的中断处理程序是无需重入的。当一个给定的中断处理程序正在执行时,相应的中断线在所有的处理器上都会被屏蔽掉,以防止在同一中断线上接收另一个新的中断。通常情况下,其他所有的中断都是打开的,所以不同中断线上的其他中断都能被处理,但是当前中断线总是被禁止。

    共享中断处理程序需要注意的三点:

·resuest_irq()的参数flags必须设置SA_SHIRQ标志。

·对于每个注册的中断处理程序,dev_id必须是唯一的。

·中断处理程序必须能够区分它的设备是否真的产生了中断。需要硬件支持,同时也需要处理程序中有相关的处理逻辑。


中断上下文

    执行一个中断处理程序或下半部时,内核处于中断上下文中。进程是以进程上下文的形式连接到内核中的,因此进程上下文可以睡眠,也可以调用调度程序。中断山下文与进程没有什么瓜葛,所以中断上下文不可以睡眠,不能在中断上下文中调用引起睡眠的函数。

    中断上下文有严格的时间限制,在中断中尽量不要使用循环去处理繁重的工作,中断打断了其他代码的执行,所以必须尽可能的迅速、简洁。尽量把工作从中断处理程序中分离出来,放在下半部分执行。

    中断处理程序拥有自己的栈,每个处理器一个,大小为一页。需要尽可能的节约内核栈空间。

中断处理机制的实现


    中断处理系统实现依赖于处理器、所使用的中断控制器的类型,体系结构的设计以及机器本身。

    中断的初始化在init_IRQ()函数中,过程如下:

for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {

int vector = FIRST_EXTERNAL_VECTOR + i;

if (i >= NR_IRQS)

break;

if (vector != SYSCALL_VECTOR) 

set_intr_gate(vector, interrupt[i]);

}

    其中数组 interrupt[]的初始化在entry.S文件中完成,用于初始化中断门,所有进入或退出中断的操作基本相同。

.data

ENTRY(interrupt)

.text

vector=0

ENTRY(irq_entries_start)

.rept NR_IRQS

ALIGN

1: pushl $vector-256  存储向量号

jmp common_interrupt

.data

.long 1b

.text

vector=vector+1

.endr

ALIGN

common_interrupt:

SAVE_ALL    保存所有寄存器的值

movl %esp,%eax   将寄存器eax指向栈的底部。

call do_IRQ  调用通用的C处理函数

jmp ret_from_intr   中断返回

在内核中,中断开始于内核预定义的入口点处。对于每条中断线,处理器都会跳转到对应的唯一的位置上。这样内核就知道所接收的中断的IRQ号。

ALIGN

ret_from_exception:

preempt_stop /*关闭本地中断*/

ret_from_intr:

GET_THREAD_INFO(%ebp)

movl EFLAGS(%esp), %eax # mix EFLAGS and CS

movb CS(%esp), %al

testl $(VM_MASK | 3), %eax

jz resume_kernel # returning to kernel or vm86-space

ENTRY(resume_userspace) /*中断前为用户态,返回用户空间*/

  cli # make sure we don't miss an interrupt

# setting need_resched or sigpending

# between sampling and the iret

movl TI_flags(%ebp), %ecx

andl $_TIF_WORK_MASK, %ecx # is there any work to be done on

# int/exception return?

jne work_pending 

jmp restore_all

#ifdef CONFIG_PREEMPT

ENTRY(resume_kernel) /*中断前为内核态,返回到内核空间*/

cli

cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ?  /*抢占内核不安全,preempt_count为0执行调度程序*/

jnz restore_all

need_resched:

movl TI_flags(%ebp), %ecx # need_resched set ?

testb $_TIF_NEED_RESCHED, %cl

jz restore_all

testl $IF_MASK,EFLAGS(%esp)     # interrupts off (exception path) ?

jz restore_all

call preempt_schedule_irq

jmp need_resched

#endif

ALIGN

work_pending:

testb $_TIF_NEED_RESCHED, %cl  /*重新调度是否正在挂起*/

jz work_notifysig

work_resched:

call schedule

cli # make sure we don't miss an interrupt

# setting need_resched or sigpending

# between sampling and the iret

movl TI_flags(%ebp), %ecx

andl $_TIF_WORK_MASK, %ecx # is there any work to be done other

# than syscall tracing?

jz restore_all

testb $_TIF_NEED_RESCHED, %cl

jnz work_resched

work_notifysig: # deal with pending signals and

# notify-resume requests

testl $VM_MASK, EFLAGS(%esp)

movl %esp, %eax

jne work_notifysig_v86 # returning to kernel-space or

# vm86-space

xorl %edx, %edx

call do_notify_resume

jmp restore_all

ALIGN

work_notifysig_v86:

pushl %ecx # save ti_flags for do_notify_resume

call save_v86_state # %eax contains pt_regs pointer

popl %ecx

movl %eax, %esp

xorl %edx, %edx

call do_notify_resume

jmp restore_all

    /proc/interrupts 中procfs是一个虚拟文件系统,只存在于内核内存中。安装在/proc目录下,在procfs中读写文件都要调用内核函数。Procfs代码位于fs/proc中。提供/proc/interrupts的函数与体系结构相关的,为show_interrupts()。

中断控制


    Linux内核提供一组接口用于操作机器上的中断状态,能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力。控制中断系统的根本原因是需要提高同步。锁保护机制防止来自其他处理器的并发访问,而禁止中断提供保护机制,则是防止来自其他中断处理程序的并发访问。

    无条件的禁止或者激活中断的传递

local_irq_disable() 

local_irq_enable()

    在禁止中断前保存中断系统的状态会更加安全,在准备激活中断时,只需把中断状态恢复到它们原来的状态。  local_irq_save(x)和 local_irq_restore(x) 的调用必须在同一个函数中进行。

local_irq_save(x)

local_irq_restore(x) 

    以前内核中提供禁止系统中所有处理器上的中断时cti(),激活函数是sti(),现在所有的中断同步必须结合使用本地中断控制和自旋锁。

    禁止指定中断线

    禁止给定中断向系统中所有处理器的传递。

void disable_irq(unsigned int irq)  /*确保所有已经开始执行的处理程序已全部退出*/

void disable_irq_nosync(unsigned int irq) /*不等待当前中断处理程序执行完毕*/

void enable_irq(unsigned int irq)

void synchronize_irq(unsigned int irq)  /*等待一个特定中断处理程序的退出*/

    以上函数可以嵌套调用,只是disable和enable的次数相等时候才能真正激活中断线。

    禁止多个中断处理程序共享的中断线是不合适的,禁止中断线也就禁止了这条线上所有设备的中断传递。

    irqs_disabled() 本地处理器上的中断系统被禁止。它返回非0,否则返回0。

    in_interrupt() 内核处于中断上下文中,返回非0。说明内核此刻正在执行中断处理程序,或者执行下半部处理程序。通常代码需要执行睡眠这种只能在进程上下文进行的操作时,需要使用该接口。

    in_irq() 只有内核确定正在执行中断处理程序时才返回非0。

    中断是一种由设备使用的硬件资源异步地向处理器发信号。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值