Linux内核中断系统

简介: 中断在驱动中是非常常用的,无论是外部的GPIO中断,还是SPI,I2C等发送或接收中断,都是必不可少的。所以今天来看看Linux中的中断处理。

 前言

   中断在驱动中是非常常用的,无论是外部的GPIO中断,还是SPI,I2C等发送或接收中断,都是必不可少的。所以今天来看看Linux中的中断处理。


中断分类

image.gif编辑

上面我们根据中断来源,屏蔽方式和中断入口对中断进行了简单的分类。

 

中断控制器

PIC: 可编程中断控制器

GIC: Generic Interrupt Controller, 通用中断控制器。(常用)

image.gif编辑

GIC是目前最常见的一种中断控制器,它在多核CPU中特别常见。它对中断做了细分:

  • SGI: Software Generated Interrupt, 软件产生的中断,可以用于多核的核间通信。一个CPU可以通过写GIC的寄存器给另外一个CPU产生中断。(中断号0~15)
  • PPI: Private Peripheral Interrupt, 某个CPU私有外设的中断,这类外设的中断只能发给绑定的那个CPU。(中断号16~31)
  • SPI: Shared Peripheral Interrupt, 共享外设的中断,这类中断可以路由到任何一个CPU。(中断号32~1019)

这些更详细的内容可以到内核源码中的Documentation中找到更详细的介绍。


中断处理框架

   为了在中断执行时间尽量短和中断处理需完成的工作尽量大之间找到平衡点,Linux将中断处理程序分解为两个半部: 顶半部和底半部。

   顶半部用于完成尽量少的比较紧急的功能。底半部完成耗时操作。顶半部不会被中断打断,底半部可以。

   如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。

驱动中断编程

//申请中断
/*
参数说明:
    irq:要申请的硬件中断号
    handler:中断处理函数,顶半部
    flags:中断触发方式
    name:中断名称
    dev:传递给中断处理函数的私有数据
*/
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
      const char *name, void *dev)
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
     unsigned long irqflags, const char *devname, void *dev_id)
//释放中断
const void *free_irq(unsigned int irq, void *dev_id)
//使能和屏蔽某个中断
void disable_irq(unsigned int irq) //需要等待目前中断处理完成
void disable_irq_nosync(unsigned int irq) //不需要等待目前中断处理完成
void enable_irq(unsigned int irq)
//使能和屏蔽所有中断
#define local_irq_enable()  do { raw_local_irq_enable(); } while (0)
#define local_irq_disable() do { raw_local_irq_disable(); } while (0)


上面是Linux提供的一些接口函数,比较简单。


底半部机制

Linux实现底半部的机制主要有tasklet, 工作队列, 软中断和线程irq。

1. tasklet

要使用tasklet只要使用下面的宏将tasklet和其处理函数关联在一起即可。

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

image.gif

然后在需要调用tasklet的时候调用tasklet_schedule函数就能使系统在适当的时候进行调度运行。

static inline void tasklet_schedule(struct tasklet_struct *t)

image.gif

tasklet程序模板

//定义tasklet和底半部函数并关联
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
//中断处理底半部
void xxx_do_tasklet(unsigned long)
{
    ......
}
//中断处理顶半部
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
   ......
   tasklet_schedule(&xxx_tasklet);
   ...... 
}
//设备驱动模块加载函数
int __init xxx_init(void)
{
    //申请中断
    result = request_irq(xxx_irq, xxx_interrupt, 
                IRQF_DISABLED, "xxx", NULL);
    ......
    return IRQ_HANDLED;            
}
//设备驱动模块卸载函数
void __exit xxx_exit(void)
{
    ......
    //释放中断
    free_irq(xxx_irq, xxx_interrupt);
}

image.gif

2. 工作队列

工作队列的使用方法和tasklet基本相似。

(1) 定义一个工作队列和底半部处理函数

struct work_struct my_wq; //工作队列
void my_wq_func(struct work_struct *work);//处理函数

image.gif

(2) 初始化工作队列

INIT_WORK(&my_wq, my_wq_func);

image.gif

(3) 调用工作队列执行函数

schedule_work(&my_wq); //调度工作队列执行

image.gif

工作队列程序模板

//定义工作队列和处理函数
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);
//中断处理底半部
void xxx_do_work(struct work_struct *work)
{
    .......
}
//中断处理顶半部
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
    .......
    schedule_work(&xxx_wq);
    .......
    return IRQ_HANDLED;
}
//设备驱动模块加载函数
int xxx_init(void)
{
    ......
    //申请中断
    result = request_irq(xxx_irq, xxx_interrupt,
            0, "XXX", NULL);
    //初始化工作队列
    INIT_WORK(&xxx_wq, xxx_do_work); 
    ......     
}
//设备驱动模块卸载函数
void xxx_exit(void)
{
    .......
    //释放中断
    free_irq(xxx_irq, xxx_interrupt);
    .......
}

image.gif

3. 软中断

这个我们一般不直接使用,tasklet就是基于软中断实现的。

4. 线程irq

在内核中除了使用request_irq()、devm_request_irq()申请中断外,还可以使用 request_threaded_irq() 和 devm_request_threaded_irq()来申请。

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
       irq_handler_t thread_fn, unsigned long irqflags,
       const char *devname, void *dev_id)
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
            irq_handler_t handler, irq_handler_t thread_fn,
            unsigned long irqflags, const char *devname,
            void *dev_id)

image.gif

上面两个函数多了个thread_fn, 用这两个API申请中断的时候,内核会为相应的中断号分配一个对应的内核线程。

参数handler对应的函数执行于中断上下文,thread_fn参数对应的函数执行于内核线程。如果handler结束时返回值为IRQ_WAKE_THREAD, 内核会调度对应线程执行thread_fn对应的函数。



相关文章
|
6天前
|
Ubuntu Linux 网络安全
从头安装Arch Linux系统
本文记录了作者安装Arch Linux系统的过程,包括安装成果展示和遇到的疑难点及其解决方法,如硬盘不足、下载失败、设置时区、安装微码和配置无密码登录等。
从头安装Arch Linux系统
|
3天前
|
Linux Shell
Linux系统
是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心,与之前的DOS命令类似。linux命令在系统中有两种类型:内置Shell命令和Linux命令。
|
1天前
|
Linux Shell
Linux系统
是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心,与之前的DOS命令类似。linux命令在系统中有两种类型:内置Shell命令和Linux命令。
|
2天前
|
Ubuntu Linux
Linux系统基本操作
Linux系统基本操作
12 7
|
3天前
|
监控 安全 Linux
使用NRPE和Nagios监控Linux系统资源的方法
通过遵循以上步骤,可以有效地使用NRPE和Nagios监控Linux系统资源,确保系统运行稳定,并及时响应任何潜在的问题。这种方法提供了高度的可定制性和灵活性,适用于从小型环境到大型分布式系统的各种监控需求。
12 2
|
3天前
|
Linux Shell
Linux系统
是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心,与之前的DOS命令类似。linux命令在系统中有两种类型:内置Shell命令和Linux命令。
|
3天前
|
Ubuntu Linux Shell
Linux系统密码忘记
【10月更文挑战第2天】在Linux系统中,若忘记密码,可以通过单用户模式或使用Live CD/USB来重置。对于Ubuntu系统,可通过GRUB引导菜单进入单用户模式,利用命令行重置密码;或使用Live CD/USB启动并挂载硬盘分区后修改密码文件。CentOS系统同样支持单用户模式重置密码,也可借助安装介质进入救援模式,挂载文件系统后进行密码重置。这些方法均能在忘记密码的情况下帮助恢复系统访问。
|
5天前
|
Linux Shell
Linux系统
是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心,与之前的DOS命令类似。linux命令在系统中有两种类型:内置Shell命令和Linux命令。
|
5天前
|
Linux Shell
Linux系统
是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心,与之前的DOS命令类似。linux命令在系统中有两种类型:内置Shell命令和Linux命令。
|
6天前
|
Linux Shell
Linux系统
是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心,与之前的DOS命令类似。linux命令在系统中有两种类型:内置Shell命令和Linux命令。
下一篇
无影云桌面