《Linux内核设计与实现》读书笔记——中断和中断处理

中断

硬件在需要的时候向内核发出信号的机制,称为中断机制

中断机制用于设备与内核通信。

中断本质是一种特殊的电信号,由硬件设备发向处理器。

处理器接受到中断后,会马上向操作系统反映此信号的到来,然后由操作系统负责处理这些新到来的数据。

中断随时可以产生,因此内核随时可能因为新到来的中断而被打断,所以必须保证中断处理程序能够快速执行。

中断是一种电信号,由硬件设备产生,并直接送入中断控制器的输入引脚。

中断控制器是简单的电子芯片,其作用是将多路中断管线,采用复用技术只通过一个和处理器相连接的管线与处理器通信。

当接收到信号之后,中断控制器会给处理器发送一个电信号。

处理器检测到该信号,就中断自己当前的工作转而处理中断。

不同的设备对应不同的中断,而每个中断都通过一个唯一的数字来标识。

这个数字称为中断请求(IRQ)号

中断请求号在经典PC上是固定的,比如IRQ0是时钟中断,IRQ1是键盘中断;也可以是动态分配的,比如在PCI总线上的设备的中断。

 

异常

异常也称为同步中断。(相对的,前面讲的中断被称为异步中断)。

它由处理器本身产生。

对中断的处理与跟异常的处理基本一致。(前面的系统调用章节就说过,系统调用可以通过中断触发)

 

中断处理程序

内核执行中断处理程序或者中断服务例程来响应特定的中断。

中断处理程序针对中断,而不是针对产生中断的设备。

如果一个设备会产生多种中断,则对应有多个中断处理程序。

设备的中断处理程序由它的设备驱动提供

在Linux中,中断处理程序就是普通的C函数,但是有特定的类型声明。

 

上半部和下半部

中断处理程序需要快速执行,但是中断处理程序又可能需要处理较多的工作量 。

为了解决这个问题,Linux提供了中断处理程序的上下半部:

上半部:接受到中断之后立即执行,但只做严格时限的工作;

下半部:在合适的时机会被开中断执行。

 

注册中断处理程序

中断处理程序是驱动程序的一部分。

驱动程序会调用request_irq()来注册中断处理程序(include\linux\interrupt.h):

static inline int __must_check

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

        const char *name, void *dev)

irq:表示要分配的中断号。对于传统PC设备,它的值是确定的;而对于大部分设备,这个值要么通过探测获取,要么可以通过编程动态确定

handler:中断处理程序:

typedef irqreturn_t (*irq_handler_t)(int, void *);

flags:中断标志,有不同的作用:

/*
 * These flags used only by the kernel as part of the
 * irq handling routines.
 *
 * IRQF_DISABLED - keep irqs disabled when calling the action handler
 * IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
 * IRQF_SHARED - allow sharing the irq among several devices
 * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
 * IRQF_TIMER - Flag to mark this interrupt as timer interrupt
 * IRQF_PERCPU - Interrupt is per cpu
 * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
 * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
 *                registered first in an shared interrupt is considered for
 *                performance reasons)
 * IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
 *                Used by threaded interrupts which need to keep the
 *                irq line disabled until the threaded handler has been run.
 */
#define IRQF_DISABLED       0x00000020
#define IRQF_SAMPLE_RANDOM  0x00000040
#define IRQF_SHARED            0x00000080
#define IRQF_PROBE_SHARED   0x00000100
#define IRQF_TIMER             0x00000200
#define IRQF_PERCPU            0x00000400
#define IRQF_NOBALANCING    0x00000800
#define IRQF_IRQPOLL        0x00001000
#define IRQF_ONESHOT        0x00002000

name:与中断相关的设备的ASCII文本表示。

dev:用于共享中断。

  • 当一个中断处理程序需要释放时,dev将提供唯一的标志信息,以便从共享中断线的诸多中断处理程序中删除指定的那一个。
  • 此外,内核每次调用中断处理程序时,都会把这个指针传递个它,以便标识是哪个设备需要执行中断处理函数。实践中它往往指向驱动程序的设备结构。
  • 如果无需共享中断线,那么它的值设置为空。

request_irq()函数可能会睡眠,因此,不能再中断上下文或其它不予许阻塞的代码中调用该函数。

卸载驱动程序时,需要注销相应的中断处理函数:

extern void free_irq(unsigned int, void *);

中断号如果不共享,则删除中断处理程序之后会被禁用。

 

共享中断处理程序

需要满足以下几点:

  1. request_irq()的参数flag必须设置IRQF_SHARED标志;
  2. dev参数必须唯一;
  3. 中断处理程序中必须能够区分它的设备是否真的产生了中断,这需要硬件的支持,也需要处理程序中有相关的处理逻辑;

内核接收一个中断后,它将依次调用在该中断线上注册的每一个处理程序。这也是上面第3条的原因。

 

中断处理函数

一个例子:

/*
 * Handler for RTC timer interrupt
 */
static irqreturn_t
timer_interrupt(int irq, void *dev_id)
{
    struct pt_regs *regs = get_irq_regs();
    do_timer(1);
#ifndef CONFIG_SMP
    update_process_times(user_mode(regs));
#endif
    do_profile(regs);
    RTC_RTCC = 0;               /* Clear interrupt */
    return IRQ_HANDLED;
}

中断处理程序通常是static的,因为它不会被其它文件中的代码直接调用。

中断处理程序返回特定的值(include\linux\irqreturn.h):

/**
 * enum irqreturn
 * @IRQ_NONE        interrupt was not from this device
 * @IRQ_HANDLED     interrupt was handled by this device
 * @IRQ_WAKE_THREAD handler requests to wake the handler thread
 */
enum irqreturn {
    IRQ_NONE,
    IRQ_HANDLED,
    IRQ_WAKE_THREAD,
};

Linux中的中断处理程序是无须重入的,因为当中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽。

 

中断上下文

中断上下文不可以睡眠,所以不能在中断处理函数中使用可能睡眠的函数。

中断上下文具有较为严格的时间限制,

中断处理程序拥有自己的栈,每个处理器一个,大小为一页,称为中断栈。

中断处理程序不需要关心栈如何设置,或者内核栈的大小是多少,但是应该尽量节约内核栈空间。

 

内核处理中断程序的入口

中断开始于特定平台预定义的入口点(arch\x86\kernel\entry_32.S):

.section .init.rodata,"a"
ENTRY(interrupt)
.text
    .p2align 5
    .p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
    RING0_INT_FRAME
vector=FIRST_EXTERNAL_VECTOR
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
    .balign 32
  .rept 7
    .if vector < NR_VECTORS
      .if vector <> FIRST_EXTERNAL_VECTOR
    CFI_ADJUST_CFA_OFFSET -4
      .endif
1:  pushl $(~vector+0x80)   /* Note: always in signed byte range */
    CFI_ADJUST_CFA_OFFSET 4
      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
    jmp 2f
      .endif
      .previous
    .long 1b
      .text
vector=vector+1
    .endif
  .endr
2:  jmp common_interrupt
.endr
END(irq_entries_start)
.previous
END(interrupt)
.previous
/*
 * the CPU automatically disables interrupts when executing an IRQ vector,
 * so IRQ-flags tracing has to follow that:
 */
    .p2align CONFIG_X86_L1_CACHE_SHIFT
common_interrupt:
    addl $-0x80,(%esp)  /* Adjust vector into the [-256,-1] range */
    SAVE_ALL
    TRACE_IRQS_OFF
    movl %esp,%eax
    call do_IRQ
    jmp ret_from_intr
ENDPROC(common_interrupt)
    CFI_ENDPROC

这里的do_IRQ对应到了C代码。下面以arch\x86\kernel\irq.c为例:

unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    /* high bit used in ret_from_ code  */
    unsigned vector = ~regs->orig_ax;
    unsigned irq;
    exit_idle();
    irq_enter();
    irq = __get_cpu_var(vector_irq)[vector];
    if (!handle_irq(irq, regs)) {
        ack_APIC_irq();
        if (printk_ratelimit())
            pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
                __func__, smp_processor_id(), vector, irq);
    }
    irq_exit();
    set_irq_regs(old_regs);
    return 1;
}

中断处理程序执行过程中会屏蔽中断(部分或者所有),但是这些中断并不会被放弃,中断处理芯片会存放这些中断,直到CPU空出来之后再送入。

 

/proc/interrupts

procfs是一个虚拟文件系统,它只存在于内核内存,一般安装与/proc目录。

在procfs中读写文件都要调用内核函数,这些函数模拟从真实文件中读或写。

/proc/interrunpts文件是一个例子,该文件存放系统中与中断相关的统计信息:

 

中断控制操作

Linux内核提供了一组接口用于操作机器上的中断状态。

 

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/106144564
今日推荐