• kernel笔记——中断


    cpu与磁盘、网卡、键盘等外围设备(相对于cpu和内存而言)交互时,cpu下发I/O请求到这些设备后,相对cpu的处理能力而言,磁盘、网卡等设备需要较长时间完成请求处理。

    那么在请求发出到处理完成这段时间,应如何设定cpu的行为,既能让这期间运行的其他程序得到执行,又能在外设处理完成后,cpu及时获取到处理完成的消息?

    可以按以下方式设定cpu行为:

    1. cpu以轮询(polling)的方式,每隔一段时间询问请求是否处理完成
    2. cpu下发请求后执行其他进程,磁盘等外设完成处理后,主动告知cpu

    对于第1种方式,轮询会带来不必要的cpu消耗。第2种是现行的方式,外设通过电信号告知cpu的机制被称为中断(interrupt)。

    IRQ line与ISR

    不同设备发起的中断以不同的数值标识,这些标识中断的数值被称为中断请求线(interrupt request line, IRQ line),IRQ line与中断的关系相当于系统调用号与系统调用的关系。

    cpu接收到中断后会将中断递交给内核处理,内核中有相应的函数完成中断处理(interrupt handler或interrupt service routine,ISR)。

    中断上下文

    每个中断对应一个中断处理函数,当中断处理函数被调用时,内核处于中断上下文(interrupt context)。不同于进程上下文(process context),中断处理过程中不能发生阻塞、休眠,即不进行进程调度,其基于以下原因:

    1. 中断处理函数需要较快的响应速度,尽快告知外设已接收到中断的消息,以让外设继续工作
    2. 中断处理中没有独立的函数栈,如果被调度,不会保存寄存器的值,因而回不到调度前执行的代码

    中断屏蔽

    从是否可屏蔽的角度,中断可分为:

    • 可屏蔽中断(maskable interrupt): 可通过设定中断屏蔽寄存器EFLAGS中IF标志位关闭的中断
    • 不可屏蔽中断(non-maskable interrupt, NMI): 无法通过设置标志位屏蔽的中断,如电源掉电、时钟中断

    对于可屏蔽中断,关闭中断的方式有以下3种:

    1. 使用cli(clear interrupt)指令,在全局范围关闭所有中断,使用sti(set interrupt)指令恢复
    2. 调用local_irq_disable或local_irq_save关闭当前cpu中断,使用local_irq_enable或local_irq_restore恢复
    3. 调用disable_irq或disable_irq_nosync在全局范围关闭某一特定中断线,使用enable_irq或synchronize_irq恢复

    因中断是异步的,屏蔽中断可用于防止中断嵌套。非正常的中断关闭会带来很多不良结果,例如cpu不响应键盘中断时,用户无法使用键盘操作;又例如cpu不响应时钟中断,则不能进行进程调度,依赖于时钟中断的任务都无法完成,机器基本变成僵尸。

    中断处理流程

    外设发起中断,cpu接收中断并传递给内核处理,下图说明了该过程:

    内核代码中,中断号被传递到do_IRQ处理,do_IRQ调用__do_IRQ函数,__do_IRQ中调用handle_IRQ_event函数,其调用相应的中断处理函数进行中断处理;处理完成,返回到do_IRQ函数,之后调用irq_exit,在irq_exit函数中,调用do_softirq处理软中断,软中断相关内容将在后文讲述。

    proc接口

    proc文件系统向用户开放了各个中断的情况:

    linux # cat /proc/interrupts
             CPU0      CPU1
    0:    178037   155726    IO-APIC-edge    timer
    1:      49578      7372    IO-APIC-edge    i8042
    8:           34         23    IO-APIC-edge    rtc0
    ……
    NMI:         0           0    Non-maskable interrupts
    LOC:   85437    86438    Local timer interrupts
    ……

    以上输出显示了中断号、各个cpu接收中断次数、中断控制器(interrupt controller)和中断名称等信息,从/proc/stat也可查到中断相关信息。

    cpu亲和力

    多核cpu给中断处理方式带来了新的问题:若某个中断到来,应该由哪个核处理?

    我们可以通过proc的接口,设定特定的cpu处理某个中断,这被称为cpu对中断的亲和力(affinity),利用/proc/irq/{interrupt num}/smp_affinity可以查看或设定cpu亲和力:

    linux# cat /proc/irq/[0,1]/smp_affinity
    3
    1

    以上查询结果显示,0号、1号cpu均可处理0号中断,而1号中断更倾向于交给1号cpu处理。若要设定1号cpu处理1号中断,可执行以下命令:

    linux # echo 2 > /proc/irq/1/smp_affinity

     

    中断负载平衡

    irqbalance为用户态下的一个守护进程,其可平均地在多核cpu间分发中断:

    linux # ps -elf | grep irqbalance | grep -v grep
    1 S root 2408 1 0 80 0 - 2242 - Jul12 ? 00:00:01 /usr/sbin/irqbalance

    下半部

    内核接收到cpu传递过来的中断后,为做到快速响应外设,中断的处理被分成两部分:

    • 上半部(top halves):不得不做的工作放在上半部,也即中断处理程序中,例如告知外设接收到中断、将数据从外设中拷贝到内存
    • 下半部(bottom halves):不紧急的工作延后完成,如处理上半部中从外设拷贝来的数据

    例如网卡接收数据过程中,首先网卡发起中断告诉cpu取数据,然后内核从网卡读取数据存入缓存,再之后内核解析数据并将数据送到应用层。如果以上工作都让中断处理程序来处理,过程太长,会导致丢失新来的中断。更优的方式是将以上工作分成两部分,从网卡读取数据到缓存由上半部完成,解析数据等较不紧急的工作由下半部完成。

    下面我们看softirq和tasklet两种下半部实现方式。

    softirq

    softirq在2.3版本内核被引入,相关代码在中定义,内核中直接使用softirq的场景较少。

    可用的softirq有以下几种,在中定义:

    enum
    {
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
    NR_SOFTIRQS
    };

    提交一个softirq需要调用raise_softirq函数,raise_softirq调用raise_softirq_irqoff,该函数将相应软中断标识为pending、完成软中断提交。

    标识为pending的软中断可在以下时机得到处理:

    1. 硬中断处理完成、do_IRQ函数即将返回时处理

    do_IRQ调用irq_exit函数,irq_exit调用invoke_softirq,即do_softirq函数,do_softirq调用__do_softirq。在__do_softirq函数中,最多处理10个软中断,若软中断的个数超过10,则调用wakeup_softirq唤醒ksoftirqd内核线程进行软中断处理

    2. ksoftirqd内核线程被唤醒后处理

    以上说明了ksoftirqd线程被唤醒的一种情况,wakeup_softirqd还会在raise_softirq_irqoff函数中被调用,也即软中断被提交之后,可唤醒ksoftirqd完成软中断处理

    3. 显式地调用do_softirq进行软中断处理

    在内核网络代码中,netif_rx_ni函数会主动调用do_softirq进行软中断处理

    tasklet

    tasklet基于softirq实现,其本质也是softirq,对应softirq枚举类型中的HI_SOFTIRQ和TASKLET_SOFTIRQ,HI_SOFTIRQ优先级较高。

    相比softirq,即使相同类型的softirq也可同时在不同的cpu上处理,而相同类型的tasklet不可同时在不同cpu上处理,不同类型的tasklet可以。

    可通过tasklet_schedule或tasklet_hi_schedule提交tasklet,这两个函数最终调用raise_softirq_irqoff提交软中断。

    因tasklet提交的同样是软中断,所以还是由do_softirq函数完成tasklet的处理。

    Reference: Chapter 7 and chapter 8, Linux kernel development.3rd.Edition

  • 相关阅读:
    面试题总结
    软件测试面试心得
    git上传本地文件到gitee上
    一个测试员失业的自述
    整理了Linux常用命令变量
    java B2B2C多用户商城系统-搜索分词架构分享
    B2B2C商城系统-Table组件封装代码分享
    Javashop 电商系统sso登录实现
    b2b2c商城系统-会员预存款架构及源码分享
    java 商城系统源码分享-snowflake发号器
  • 原文地址:https://www.cnblogs.com/felixzh/p/9047712.html
Copyright © 2020-2023  润新知