Linux kernel之中断

news2024/11/26 15:23:21

Linux内核中断顶半部和中断底半部

设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。下图描述了Linux内核的中断处理机制。

上半部也就是硬中断软中断只是底半部的一种实现机制

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

顶半部用于完成尽量少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态,并在清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,从而可以服务更多的中断请求。

现在,中断处理工作的重心就落在了底半部的头上,需用它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可被新的中断打断

尽管顶半部、底半部的结合能够善系统的响应能力,但是,僵化地认为Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。

所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保其数据结构。

软中断和硬中断的区别

硬中断:

1. 硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)。

2. 处理中断的驱动是需要运行在CPU上的,因此,当中断产生的时候,CPU会中断当前正在运行的任务,来处理中断。在有多核心的系统上,一个中断通常只能中断一颗CPU(也有一种特殊的情况,就是在大型主机上是有硬件通道的,它可以在没有主CPU的支持下,可以同时处理多个中断。)。

3. 硬中断可以直接中断CPU。它会引起内核中相关的代码被触发。对于那些需要花费一些时间去处理的进程,中断代码本身也可以被其他的硬中断中断。

4. 对于时钟中断,内核调度代码会将当前正在运行的进程挂起,从而让其他的进程来运行。它的存在是为了让调度代码(或称为调度器)可以调度多任务。

软中断

1. 软中断的处理非常像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的。

2. 通常,软中断是一些对I/O的请求。这些请求会调用内核中可以调度I/O发生的程序。对于某些设备,I/O请求需要被立即处理,而磁盘I/O请求通常可以排队并且可以稍后处理。根据I/O模型的不同,进程或许会被挂起直到I/O完成,此时内核调度器就会选择另一个进程去运行。I/O可以在进程之间产生并且调度过程通常和磁盘I/O的方式是相同。

3. 软中断仅与内核相联系。而内核主要负责对需要运行的任何其他的进程进行调度。一些内核允许设备驱动的一些部分存在于用户空间,并且当需要的时候内核也会调度这个进程去运行。

4. 软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断。这种中断是一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求。有一个特殊的软中断是Yield调用,它的作用是请求内核调度器去查看是否有一些其他的进程可以运行。

硬中断、软中断和信号的区别

硬中断是外部设备对CPU的中断软中断是中断底半部的一种处理机制,而信号则是由内核(或其他进程)对某个进程的中断。在涉及系统调用的场合,人们也常说通过软中断(例如ARM为swi)陷入内核,此时软中断的概念是指由软件指令引发的中断,和我们这个地方说的softirq是两个完全不同的概念,一个是software,一个是soft。 需要特别说明的是,软中断以及基于软中断的tasklet如果在某段时间内大量出现的话,内核会把后续软中断放入ksoftirqd内核线程中执行。总的来说,中断优先级高于软中断,软中断又高于任何一个线程。软中断适度线程化,可以缓解高负载情况下系统的响应。

进程上下文

  所谓的进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容,当内核需要切换到另一个进程时,它 需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。

  一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。

  用户级上下文: 正文、数据、用户堆栈以及共享存储区;

  寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);

  系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。

  当发生进程调度时,进行进程切换就是上下文切换(context switch)。操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。而系统调用进行的是模式切换(mode switch)。模式切换与进程切换比较起来,容易很多,而且节省时间,因为模式切换最主要的任务只是切换进程寄存器上下文的切换。

进程上下文主要是异常处理程序和内核线程内核之所以进入进程上下文是因为进程自身的一些工作需要在内核中做。例如,系统调用是为当前进程服务的,异常通常是处理进程导致的错误状态等。所以在进程上下文中引用current是有意义的。

中断上下文

  硬件通过触发信号,向CPU发送中断信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核, 内核通过这些参数进行中断处理。

  所以,“中断上下文”就可以理解为硬件传递过来的这些参数和内核需要保存的一些环境,主要是被中断的进程的环境

  内核进入中断上下文是因为中断信号而导致的 中断处理或软中断。而中断信号的发生是随机的,中断处理程序及软中断并不能事先预测发生中断时当前运行的是哪个进程,所以在中断上下文中引用current是可以的,但没有意义。

  事实上,对于A进程希望等待的中断信号,可能在B进程执行期间发生。例如,A进程启动写磁盘操作,A进程睡眠后B进程在运行,当磁盘写完后磁盘中断信号打断的是B进程,在中断处理时会唤醒A进程。

中断函数

1.开关中断

/************关闭指定中断*************************/

void enable_irq(unsigned int irq)

void disable_irq(unsigned int irq) //要等到当前正在执行的中断处理函数执行完才返回

void disable_irq_nosync(unsigned int irq)  //函数调用以后立即返回

/**************硬中断****************************/

local_irq_disable():关闭中断

local_irq_enable():开启中断

local_irq_save(flags):关闭中断并保存中断标志位

local_irq_restore(flags):开启中断并清除中断标志位

/*************底半部开关************************/

local_bh_disable();

local_bh_enable();

/*************判断中断状态*********************/

#define in_interrupt() (irq_count())   // 是否处于中断状态(硬中断或软中断)

#define in_irq() (hardirq_count())     // 是否处于硬中断

#define in_softirq() (softirq_count()) // 是否处于软中断

  disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。

  当任务A调用local_irq_disable企图关闭中断1s,然后再开启中断,但在中断中出现了另一个优先级更高的任务B也调用了local_irq_disable关闭中断,然后100ms后执行local_irq_enable开启中断,导致任务A的中断没有持续1s就被打开了,这显然是有问题的,所以需要用到后两个中断函数,当B任务关闭中断的时候保存中断标志位,开启中断的时候清除中断标志位,还原之前的中断状态,才能使任务A继续处于中断状态中正确执行。

2、申请中断

int request_irq(unsigned int irq, irq_handle_t handle, unsigned int flags,

                const char *name, void* dev_id);

int devm_request_irq(struct device *dev, unsigned int irq, irq_handle_t handle,

                unsigned int flags, const char *name, void* dev_id);

其中flags代表中断标志,可以取值如下:

  上升沿触发:IRQF_TRIGGER_RISING

  下降沿触发:IRQF_TRIGGER_FALLING

  高电平触发:IRQF_TRIGGER_HIGH

  低电平触发:IRQF_TRIGGER_LOW

  共享中断:IRQF_SHARED

dev_id代表要传给中断处理程序的私有数据,一般是这个设备的结构体或者NULL
  devm_request_irqrequest_irq区别在于前者是申请的内核“managed”资源,不需要自己手动释放,会自动回收资源,而后者需要手动调用free_irq来释放中断资源

3、释放中断

void free_irq(unsigned int irq, void *dev_id);

实现中断底半部的三种方法: 软中断   tasklet    工作队列

软中断

软中断( Softirq)也是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候, tasklet是基于软中断实现的,因此也运行于软中断上下文。

在Linux内核中,用 softing_action结构体表征一个软中断,这个结构体包含软中断处理函数指针和传递给该函数的参数。使用 open_softirq()函数可以注册软中断对应的处理函数,而 raise_softirq()函数可以触发一个软中断。

软中断和 tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工作队列则运行于进程上下文。因此,在软中断和 tasklet处理函数中不允许睡眠,而在工作队列处理函数中允许睡眠。

local_bh_disable()和 llocal_bh_enable()是内核中用于禁止和使能软中断及 tasklet底半部机制的函数

软中断模版

asmlinkage void do_softirq(void)

{

    __u32 pending;

    unsigned long flags;

    /* 判断是否在中断处理中,如果正在中断处理,就直接返回 */

    if (in_interrupt())

        return;

    /* 保存当前寄存器的值 */

    local_irq_save(flags);

    /* 取得当前已注册软中断的位图 */

    pending = local_softirq_pending();

    /* 循环处理所有已注册的软中断 */

if (pending)

        __do_softirq();

    /* 恢复寄存器的值到中断处理前 */

    local_irq_restore(flags);

}

tasklet

tasklet的使用较简单,它的执行上下文是软中断,执行时机通常是顶半部返回的时候。我们只需要定义 tasklet及其处理函数,并将两者关联则可,例如

void my_tasklet_func(unsigned long); /*定义一个处理函数*/

DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);

/*定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联*/

代码DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)实现了定义名称为my_tasklet的tasklet,并将其与my_tasklet_func()这个函数绑定,而传入这个函数的参数为data。 在需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度运行

tasklet_schedule(&my_tasklet);

使用tasklet作为底半部处理中断的设备驱动程序模板下所示(仅包含与中断相关的部 分)。

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,

 0, "xxx", NULL);

 ...

 return IRQ_HANDLED;

}

/* 设备驱动模块卸载函数 */

 void __exit xxx_exit(void)

{

 ...

 /* 释放中断 */

 free_irq(xxx_irq, xxx_interrupt);

 ...

}

对应于xxx_irq的中断处理程序被设置为xxx_interrupt()函数,在这个函数中,tasklet_schedule(&xxx_tasklet)调度被定义的tasklet函数xxx_do_tasklet()在适当的时候执行。

工作队列

工作队列的使用方法和tasklet非常相似,但是工作队列的执行上下文是内核线程,因此工作队列可以调度和睡眠。下面的代码用于定义一个工作队列和一个底半部执行函数

struct work_struct my_wq; /* 定义一个工作队列 */

void my_wq_func(struct work_struct *work); /* 定义一个处理函数 */

通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定:

INIT_WORK(&my_wq, my_wq_func);

/* 初始化工作队列并将其与处理函数绑定 */

与tasklet_schedule()对应的用于调度工作队列执行的函数为schedule_work(),如:

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

工作队列函数模版

/* 定义工作队列和关联函数 */

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);

 ...

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

markdown工具Atom预览与插件安装

​atom是以命令行作为插件选项的入口 打开命令输入框 Windows: ctrl shift p Mac: command shift p 输入命令安装 输入 markdown preview toggle ,可以偷懒只输入mdpt(模糊匹配) 按enter键即可看到预览,如图,左边编辑,右…

手把手教你搭建农产品商城小程序:详细步骤解析

随着移动互联网的普及,越来越多的人开始关注如何在手机上进行购物,尤其是对于农产品这类日常生活所需品。本文将手把手教你搭建一个农产品商城小程序,让你轻松实现在手机上购买农产品的愿望。 一、登录乔拓云网后台 首先,我们需要…

【C++STL基础入门】list的运算符重载和关于list的算法

文章目录 前言一、list运算符1.1 逻辑运算符1.2 赋值运算符 二、list相关算法2.1 查找函数总结 前言 C标准模板库(STL)是一组强大而灵活的工具,用于处理数据结构和算法。其中,std::list是STL中的一个重要容器,它实现了…

Hyper-V 虚拟机CentOS配置网络(三)

总目录 https://preparedata.blog.csdn.net/article/details/132877836 文章目录 总目录一、创建交换机二、共享网络给交换机三、虚拟机配置交换机四、配置CentOS网络 一、创建交换机 二、共享网络给交换机 打开物理宿主机的控制面板的网络链接中心,此时已经多了一…

嵌入式Linux驱动开发(I2C专题)(七)

使用GPIO操作I2C设备_IMX6ULL 参考资料: Linux文档 Linux-5.4\Documentation\devicetree\bindings\i2c\i2c-gpio.yamlLinux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txt Linux驱动源码 Linux-5.4\drivers\i2c\busses\i2c-gpio.cLinux-4.9.88\driv…

成集云 | 金蝶K3与旺店通ERP集成(旺店通主管库存)| 解决方案

源系统成集云目标系统 方案介绍 金蝶K3是一款ERP软件,它集成了供应链管理、财务管理、人力资源管理、客户关系管理、办公自动化、商业分析、移动商务、集成接口及行业插件等业务管理组件。以成本管理为目标,计划与流程控制为主线,…

掌动智能:高效实用的Web自动化测试工具

在当今的软件开发领域,快速且高质量的Web应用程序开发和测试至关重要。为了满足这一需求,掌动智能推出了一款高效实用的Web自动化测试工具,帮助测试人员提高测试效率、提升产品质量,并加速交付速度。 掌动智能:引领自动…

代码随想录算法训练营第55天 | ● 392.判断子序列 ● 115.不同的子序列

文章目录 前言一、392.判断子序列二、115.不同的子序列总结 前言 动态规划; 一、392.判断子序列 这道题应该算是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。 所以掌握本题的…

73、SpringBoot 直接整合 JDBC

★ Spring Boot可直接整合JDBC来操作数据库 ——很少这么干,这么干就意味着使用最原始的方式来操作数据库。对于对于极小的项目,直接整合JDBC无需添加JPA、Hibernate等ORM框架。★ JdbcTemplate 为项目添加spring-boot-starter-jdbc.jar依赖&#xff0…

R语言绘图-3-Circular-barplot图

0. 参考: https://r-graph-gallery.com/web-circular-barplot-with-R-and-ggplot2.html 1. 说明: 利用 ggplot 绘制 环状的条形图 (circular barplot),并且每个条带按照数值大小进行排列。 2 绘图代码: 注意:绘图代码中的字体…

redis 哨兵(sentinel)机制

1. 前言 sentinel(哨兵)是Redis 的高可用性解决方案之一。通过哨兵可以创建一个当主服务器出现故障时自动将从服务器升级为主服务器的分布式系统,解决了主从复制出现故障时需要人为干预的问题。 redis 的主从复制的作用有数据预热、负载均衡…

UML基础与应用之对象图

什么是对象图? 对象图表示一组对象及它们之间的关系,是某一时刻系统详细信息的快照,描述系统交互的静态图形,它由协作的对象组成,但不包含在对象之间传递的任何消息。因为对象是类的实例化,所以说某一时刻…

步步为营,如何将GOlang引用库的安全漏洞修干净

文章目录 引场景构建第一步、直接引用的第三方库升级修复策略1.确认是否为直接引用的第三方库2.找到需要升级的版本是否为release版本 第二步、间接引用的第三方库升级修复策略那么问题来了,我们这么间接引用库的对应的直接引用库是哪个呢? (…

uni-app实现web-view图片长按下载

<template><view><web-view :webview-styles"webviewStyles" :src"webUrl"></web-view></view> </template> uniapp的web-view中图片无法长按保存&#xff0c;IOS下是正常的&#xff0c;但是Android下长按无反应 解…

在测试过程中引入可观测性平台提升业务质量

作者 观测云 产品技术专家 成都办公室 - 刘跃兰 前言 随着微服务技术的发展&#xff0c;微服务概念已深入人心&#xff0c;越来越多的企业开始使用微服务架构来开发业务应用。业务应用系统的整体架构变得更加复杂&#xff0c;并存在各种各样的不确定性因素&#xff0c;从而对…

nginx+keepalived集群搭建

1. nginx部署 单机部署可参考&#xff1a;https://blog.csdn.net/ym5209999/article/details/119897237 2. keepalived安装 [rootnginx1 ~]# yum -y install keepalived3. keepalived配置 3.1 安装完成后&#xff0c;默认配置文件位于&#xff1a;/etc/keepalived&#xff…

奶奶都看的懂的《栈》(C语言实现,超详细解析 !!!)

目录 一、前言 二、栈 &#x1f34e;栈的概念 &#x1f350;栈的结构​编辑 &#x1f349;栈的实现 &#x1f34a;栈 各个接口的实现 ⭕ 定义一个 栈 结构体 ⭕栈 的初始化 ⭕ 栈 的尾插 ⭕ 栈 的尾删 ⭕ 栈 内数据个数 ⭕ 获取 栈 顶元素 ⭕ 判断 栈 是否为空 ⭕…

Linux-网卡和网络配置

链接一篇大佬的博客&#xff1a;Linux之手把手教会修改网卡名称 文章目录 修改网卡名称步骤1&#xff1a;修改“/etc/default/grub”步骤2&#xff1a;修改“/etc/sysconfig/network-scripts”下的文件步骤3&#xff1a;修改“ifcfg-eth0”配置步骤4&#xff1a;判断操作系统的…

AIGC入门 - LLM 信息概览

本文将介绍以下 LLM OPTLLaMaAlpacaVicunaMosschatGLMBaichuanOpenbuddy 一、OPT 1、背景 OPT全称Open Pre-trained Transformer Language Models&#xff0c;即“开放的预训练Transformer语言模型”&#xff0c;是 Meta AI 团队在2022年5月发布了开源大模型OPT-175B&#…

Ninja: Towards Transparent Tracing and Debugging on ARM【TEE的应用】

目录 摘要引言贡献 背景TrustZone和受信任的固件PMU和ETM 相关工作x86上的透明恶意软件分析ARM上的动态分析工具基于仿真的系统硬件虚拟化裸机系统 Trustzone相关的系统 系统架构具体实现和评估可以看论文&#xff0c;这里不赘述了讨论总结 作者&#xff1a;Zhenyu Ning and Fe…