linux 中断管理(一)

一、中断作用

Linux 内核需要对连接到计算机上的所有硬件设备进行管理。如果要管理这些设备,首先得和它们互相通信才行。
一般有两种方案可实现这种功能:

  • 轮询(polling) 让内核定期对设备的状态进行查询,然后做出相应的处理;
  • 中断(interrupt) 让硬件在需要的时候向内核发出信号(变内核主动为硬件主动)。

使用轮询的方式会占用CPU比较多的时间,效率极低。例如:要读取一个按键有没有被按下时,一个进程需要不断地查询按键有没有被按下。这样这个任务就占用CPU大量得时间,使得CPU做了大量的无用功。使用中断提供这样的一个机制。当按键没有被按下的时候,挂起当前进程,将控制权转交给其他进程。当按键按下的时候,操作系统把当前进程设为活动的,从而允许该进程继续执行。

二、linux中断管理

linux 内核将所有的中断统一编号,使用一个 irq_desc 结构体数组描述中断。一个数组项对用一个中断(或者是一组中断,它们共用中断号)。
struct irq_desc 结构体记录了,中断的名称、中断状态,底层硬件访问接口(使能中断,屏蔽中断,清除中断),中断处理函数的入口,
通过它可以调用用户注册的中断处理函数。

1、struct irq_desc

struct irq_descinclude\linux\irq.h 文件里面定义

struct irq_desc {
    irq_flow_handler_t  handle_irq;    /* 当前中断的处理函数入口 */
    struct irq_chip     *chip;         /* 底层硬件访问 */
        ...
    struct irqaction    *action;    /* 用户注册的中断处理函数链表 */
    unsigned int        status;     /* IRQ状态 */

    ...

    const char      *name;         /* 中断函数名 */
} ____cacheline_internodealigned_in_smp;

a. handle_irq

handle_irq 是这个或者是这组的中断的处理函数入口。发生中断时,会调用asm_do_IRQ函数。在这个函数里面根据中断号调用相应irq_desc数组项的handle_irq
handle_irq里面会使用chip成员的接口来使能、屏蔽、清除中断。还会一一调用用户注册在action链表里面的处理函数。

b. struct irq_chip

struct irq_chip 在 include\linux\irq.h 文件里面定义

struct irq_chip {
    const char  *name;
    /* 启动中断,如果不设置则缺省为 "enable" */
    unsigned int    (*startup)(unsigned int irq);
    /* 关闭中断,如果不设置则缺省为 "disable" */
    void        (*shutdown)(unsigned int irq);
    /* 使能中断,如果不设置则缺省为"unmask" */
    void        (*enable)(unsigned int irq);
    /* 禁止中断,如果不设置则缺省为"mask" */
    void        (*disable)(unsigned int irq);
    /* 响应中断,一般是清除当前的中断,使得可以接收下一个中断 */
    void        (*ack)(unsigned int irq);
    /* 屏蔽中断源 */
    void        (*mask)(unsigned int irq);
    /* 屏蔽和响应中断 */
    void        (*mask_ack)(unsigned int irq);
    /* 开启中断 */
    void        (*unmask)(unsigned int irq);
  ....
};

c. struct irqaction

struct irqaction 结构体在 include\linux\interrupt.h 文件里面定义。
用户注册的每个中断处理函数都用一个 irqaction 结构体来描述一个中断(例如共享中断)可以有多个处理函数。
它们的irqaction结构以action 为表头链成一个链表

struct irqaction {
    /* 用户注册的中断处理函数 */
    irq_handler_t handler;
    /* 中断的标志,是否是共享中断,中断的触发方式是电平触发,还是边沿触发 */
    unsigned long flags;

    cpumask_t mask;
    /* 用户注册时,给的中断的名字 */
    const char *name;
    /* handler 中断函数的参数,也可以用来区分共享中断 */
    void *dev_id;
    /* 链表的指针 */
    struct irqaction *next;
    /* 中断号 */
    int irq;
    struct proc_dir_entry *dir;
};

2. 小结

对于 struct irq_desc数组 、 struct irq_chip结构体 和 struct irqaction 三者之间的关系,如下图:

三、中断处理初始化

1、中断处理初始化

init\Main.c 文件的 start_kernel 函数里面调用的init_IRQ() 函数就是中断体系结构的初始化

2、init_IRQ 函数

init_IRQ 用来初始化中断处理体系结构, 在arch\arm\kernel\irq.c文件里面

void __init init_IRQ(void)
{
    int irq;
        /* 初始化irq_desc[] 每一项的中断状态 */
    for (irq = 0; irq < NR_IRQS; irq++)
        irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
    ...
        /* 架构相关的中断初始化函数 */
    init_arch_irq();
}

3、 init_arch_irq

init_arch_irq 是一个函数指针,arch\arm\kernel\setup.c 文件 setup_arch() 函数被初始化

void __init setup_arch(char **cmdline_p)
{
    ...
    init_arch_irq = mdesc->init_irq;
    ...
}

mdesc->init_irq 指向的是 arch\arm\plat-s3c24xx\irq.c 文件的s3c24xx_init_irq()函数。
MACHINE_START(S3C2440, "SMDK2440") 是一个宏,用来定义 struct machine_desc 结构体
结构体在 arch\arm\mach-s3c2440\Mach-smdk2440.c文件里面定义并且初始化 init_irq指向s3c24xx_init_irq()函数

MACHINE_START(S3C2440, "SMDK2440")
    /* Maintainer: Ben Dooks <[email protected]> */
    .phys_io    = S3C2410_PA_UART,
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params    = S3C2410_SDRAM_PA + 0x100,
         /* init_irq成员在这里初始化 */
    .init_irq   = s3c24xx_init_irq,
    .map_io     = smdk2440_map_io,
    .init_machine   = smdk2440_machine_init,
    .timer      = &s3c24xx_timer,
MACHINE_END

4、s3c24xx_init_irq函数

s3c24xx_init_irq()函数在arch\arm\plat-s3c24xx\irq.c定义(部分代码如下面的代码块),他为所有的所有与芯片操作相关的数据结构(irq_desc[irq].chip),,并且初始化了处理函数入口(irq_desc[irq].handle_irq)。
IRQ_EINT0IRQ_EINT3 为例,set_irq_chip函数就是将irq_desc[irqno].chip = &s3c_irq_eint0t4,以后就可以通过irq_desc[irqno].chip结构的函数指针来设置触方式,使能中断、屏蔽中断了
set_irq_handler函数就是设置中断处理函数入口 将irq_desc[irqno].handle_irq = handle_edge_irq 发生中断时,handle_edge_irq会调用用户具体注册的处理函数
irq_desc[irqno].falgs设置为IRQF_VALID

void __init s3c24xx_init_irq(void)
{
    ...
    /* external interrupts */
    for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
        irqdbf("registering irq %d (ext int)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_eint0t4);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }
    for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
        irqdbf("registering irq %d (extended s3c irq)\n", irqno);
        set_irq_chip(irqno, &s3c_irqext_chip);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }
    ...
}

5、set_irq_chip函数

int set_irq_chip(unsigned int irq, struct irq_chip *chip)
{
    struct irq_desc *desc;
    unsigned long flags;
        /* 判断是否超过最大的中断号 */
    if (irq >= NR_IRQS) {
        printk(KERN_ERR "Trying to install chip for IRQ%d\n", irq);
        WARN_ON(1);
        return -EINVAL;
    }

    if (!chip)
        chip = &no_irq_chip;
        /* 通过中断号找到irq_desc数组对应的数组项 */
    desc = irq_desc + irq;
    spin_lock_irqsave(&desc->lock, flags);
        /* 判断 chip 的成员即&s3c_irq_eint0t4的成员是否为空,如果为空就设置为默认的操作函数 */
    irq_chip_set_defaults(chip);
        /* 设置irq_desc[].chip成员 */
    desc->chip = chip;
    spin_unlock_irqrestore(&desc->lock, flags);
    return 0;

5、set_irq_handler函数

set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
    __set_irq_handler(irq, handle, 0, NULL);
}

/***************************************************************************************/

__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
          const char *name)
{
    struct irq_desc *desc;
    unsigned long flags;
    /* 通过中断号找到irq_desc数组对应的数组项 */
    desc = irq_desc + irq;

   ...
    /*  中间还会做一些判断 */
   ...
    /* 设置中断处理函数,名字*/
    desc->handle_irq = handle;
    desc->name = name;
    /* 设置中断的状态,开启中断 */
    if (handle != handle_bad_irq && is_chained) {
    desc->status &= ~IRQ_DISABLED;
    desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
    desc->depth = 0;
    desc->chip->unmask(irq);
    }

}

6、set_irq_flags函数

void set_irq_flags(unsigned int irq, unsigned int iflags)
{
    struct irq_desc *desc;
    unsigned long flags;

    if (irq >= NR_IRQS) {
        printk(KERN_ERR "Trying to set irq flags for IRQ%d\n", irq);
        return;
    }
        /* 找到数组项 */
    desc = irq_desc + irq;
    spin_lock_irqsave(&desc->lock, flags);

       /* 设置中断状态 */
    desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
    if (iflags & IRQF_VALID)
        desc->status &= ~IRQ_NOREQUEST;
    if (iflags & IRQF_PROBE)
        desc->status &= ~IRQ_NOPROBE;
    if (!(iflags & IRQF_NOAUTOEN))
        desc->status &= ~IRQ_NOAUTOEN;
    spin_unlock_irqrestore(&desc->lock, flags);
}

四、总结

中断处理体系结构的初始化的过程其实就是对irq_desc[]数组的每一项初始化进行初始化.
一个中断或者一组中断通过irq_desc[]的一个数组项来管理。
数组项里面的 handle_irq chip action 三个重要的结构体成员。

  • handle_irq是当前的中断处理函数
  • chip底层硬件相关的处理函数(设置中断的触发方式,屏蔽中断,使能中断)
  • action链表头,用户注册的处理函数都链接到这个链表里面,发生中断的时候,就会从之里面调用用户注册进来的中断服务函数

原文地址:https://www.cnblogs.com/gulan-zmc/p/11823660.html

时间: 2024-10-08 16:38:11

linux 中断管理(一)的相关文章

Linux中断管理 (3)workqueue工作队列

目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管理 (3)workqueue工作队列> 关键词: 工作队列的原理是把work(需要推迟执行的函数)交由一个内核线程来执行,它总是在进程上下文中执行. 工作队列的优点是利用进程上下文来执行中断下半部操作,因此工作队列允许重新调度和睡眠,是异步执行的进程上下文,它还能解决软中断和tasklet执行时间过长导

linux 中断管理(四)

前言: 前面介绍了linux异常处理体系结构.linux中断管理一.linux中断管理二.linux中断管理三.接下来写一篇使用中断的例子.在2440这块开发板里面有四个按键,将四个按键注册为中断方式,然后在应用层上面将四个按键的值读取出来,并且打印出来. 一.中断注册和释放函数 1.中断注册函数 int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char devname,

Linux进程管理 (篇外)内核线程简要介绍

关键词:kthread.irq.ksoftirqd.kworker.workqueues 在使用ps查看线程的时候,会有不少[...]名称的线程,这些有别于其它线程,都是内核线程. 其中多数内核线程从名称看,就知道其主要功能. 比如给中断线程化使用的irq内核线程,软中断使用的内核线程ksoftirqd,以及work使用的kworker内核线程. 本文首先概览一下Linux都有哪些内核线程,然后分析创建内核线程的API. 在介绍内核线程和普通线程都有哪些区别? 最后介绍主要内核线程(irq/ks

Linux进程管理简谈

Linux系统进程管理简谈 进程是什么? 简单来说进程是一个正在执行的程序的一个副本,存在生命周期,有段指令和代码在不断运行. linux内核存储信息的固定格式:task struct 进程的相关信息存储在链表中 多个任务的task struct组件的链表:task list 进程的创建:父进程创建子进程(内核创建init进程,剩余一切进程有init及其子进程进程创建) 父进程创建子进程时向内核调用fork()来创建子进程并且通过调用clone()复制父进程的信息给子进程 Linux进程的优先级

linux系统进程管理——常用命令总结

进程管理:1.程序和进程的关系:    程序是为了完成某种任务而设计的软件,比如LibreOffice是程序    进程就是运行中的程序:运行中的程序是程序的一个副本,存在着生命周期:    一个运行着的程序,可能有多个进程.尤其是作为服务进程,在为了响应多个访问时会创建多个进程来对其进行服务:    Linux内核存储进程信息的固定格式:task struct:    多个任务的task struct组件的链表:task list: 2.进程分为交互进程(用户.前台进程).批处理进程和守护进程

Linux中断(interrupt)子系统之一:中断系统基本原理

这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层.内核版本基于3.3.虽然内核的版本不断地提升,不过自从上一次变更到当前的通用中断子系统后,大的框架性的东西并没有太大的改变. /*****************************************************************************************************/ 声明:本博内容

linux进程管理(5)---进程消亡

一.目的 本文将讲述进程是如何消亡的.一个进程既有父进程又有子进程,因此进程消亡时,既要通知父进程,也要安排好子进程. 当前进程消亡时主要做了三件大事:释放当前进程占用的资源:为当前进程的子进程重新寻找"养父":通知当前进程的父进程,释放当前进程剩下的资源. 当前进程释放掉大多数进程资源后,只保留内核栈.structtask_struct数据结构:剩下的资源由父进程负责释放. linux调用sys_exit().sys_wait4()实现进程的消亡,代码可以在kernel/exit.c

Linux性能及调优指南(翻译)之Linux进程管理

译文如下:1.1 Linux进程管理 进程管理是操作系统的最重要的功能之一.有效率的进程管理能保证一个程序平稳而高效地运行. Linux的进程管理与UNIX的进程管理相似.它包括进程调度.中断处理.信号.进程优先级.上下文切换.进程状态.进度内存等. 在本节中,我们将描述Linux进程管理的基本原理的实现.它将更好地帮助你理解Linux内核如何处理进程及其对系统性能的影响. 1.1.1 什么是进程?一个进程是一个运行在处理器的程序的一个实例.该进程使用Linux内核能够处理的任何资源来完成它的任

ARM linux电源管理——Cortex A系列CPU(32位)睡眠和唤醒的底层汇编实现

ARM linux电源管理——Cortex A系列CPU(32位)睡眠和唤醒的底层汇编实现 承接 http://www.wowotech.net/pm_subsystem/suspend_and_resume.html Linux电源管理(6)_Generic PM之Suspend功能一文中的下图. 本文主要分析平台相关的CPU睡眠和唤醒,即下电和上电流程,以及ARM底层汇编代码实现. 内核版本:3.1.0               CPU:ARM Cortex-A7 1 平台相关函数执行流程