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对应的函数。



相关文章
|
2月前
|
Ubuntu Linux Anolis
Linux系统禁用swap
本文介绍了在新版本Linux系统(如Ubuntu 20.04+、CentOS Stream、openEuler等)中禁用swap的两种方法。传统通过注释/etc/fstab中swap行的方式已失效,现需使用systemd管理swap.target服务或在/etc/fstab中添加noauto参数实现禁用。方法1通过屏蔽swap.target适用于新版系统,方法2通过修改fstab挂载选项更通用,兼容所有系统。
213 3
Linux系统禁用swap
|
2月前
|
Linux
Linux系统修改网卡名为eth0、eth1
在Linux系统中,可通过修改GRUB配置和创建Udev规则或使用systemd链接文件,将网卡名改为`eth0`、`eth1`等传统命名方式,适用于多种发行版并支持多网卡配置。
297 3
|
3月前
|
安全 网络协议 Linux
深入理解Linux内核模块:加载机制、参数传递与实战开发
本文深入解析了Linux内核模块的加载机制、参数传递方式及实战开发技巧。内容涵盖模块基础概念、加载与卸载流程、生命周期管理、参数配置方法,并通过“Hello World”模块和字符设备驱动实例,带领读者逐步掌握模块开发技能。同时,介绍了调试手段、常见问题排查、开发规范及高级特性,如内核线程、模块间通信与性能优化策略。适合希望深入理解Linux内核机制、提升系统编程能力的技术人员阅读与实践。
410 1
|
Ubuntu Linux 网络安全
Linux系统初始化脚本
一款支持Rocky、CentOS、Ubuntu、Debian、openEuler等主流Linux发行版的系统初始化Shell脚本,涵盖网络配置、主机名设置、镜像源更换、安全加固等多项功能,适配单/双网卡环境,支持UEFI引导,提供多版本下载与持续更新。
279 0
Linux系统初始化脚本
|
3月前
|
运维 Linux 开发者
Linux系统中使用Python的ping3库进行网络连通性测试
以上步骤展示了如何利用 Python 的 `ping3` 库来检测网络连通性,并且提供了基本错误处理方法以确保程序能够优雅地处理各种意外情形。通过简洁明快、易读易懂、实操性强等特点使得该方法非常适合开发者或系统管理员快速集成至自动化工具链之内进行日常运维任务之需求满足。
226 18
|
2月前
|
安全 Linux Shell
Linux系统提权方式全面总结:从基础到高级攻防技术
本文全面总结Linux系统提权技术,涵盖权限体系、配置错误、漏洞利用、密码攻击等方法,帮助安全研究人员掌握攻防技术,提升系统防护能力。
270 1
|
2月前
|
监控 安全 Linux
Linux系统提权之计划任务(Cron Jobs)提权
在Linux系统中,计划任务(Cron Jobs)常用于定时执行脚本或命令。若配置不当,攻击者可利用其提权至root权限。常见漏洞包括可写的Cron脚本、目录、通配符注入及PATH变量劫持。攻击者通过修改脚本、创建恶意任务或注入命令实现提权。系统管理员应遵循最小权限原则、使用绝对路径、避免通配符、设置安全PATH并定期审计,以防范此类攻击。
978 1
|
3月前
|
缓存 监控 Linux
Linux系统清理缓存(buff/cache)的有效方法。
总结而言,在大多数情形下你不必担心Linux中buffer与cache占用过多内存在影响到其他程序运行;因为当程序请求更多内存在没有足够可用资源时,Linux会自行调整其占有量。只有当你明确知道当前环境与需求并希望立即回收这部分资源给即将运行重负载任务之前才考虑上述方法去主动干预。
1524 10
|
3月前
|
安全 Linux 数据安全/隐私保护
为Linux系统的普通账户授予sudo访问权限的过程
完成上述步骤后,你提升的用户就能够使用 `sudo`命令来执行管理员级别的操作,而无需切换到root用户。这是一种更加安全和便捷的权限管理方式,因为它能够留下完整的权限使用记录,并以最小权限的方式工作。需要注意的是,随意授予sudo权限可能会使系统暴露在风险之中,尤其是在用户不了解其所执行命令可能带来的后果的情况下。所以在配置sudo权限时,必须谨慎行事。
556 0