当前位置: 首页 > news >正文

嵌入式八股RTOS与Linux--中断篇

前言

  中断可以说是操作系统最重要的部分,其实前面的讲解我们也看到了,操作系统不是每时每刻都在运行的程序,很多时候都触发了中断之后进行处理,操作系统是由中断驱动的;关于中断我前面的文章有提到过:
https://blog.csdn.net/qq_45731845/article/details/146291274

RTOS的中断管理

  首先要把中断优先级设置为4:即没有响应优先级 都是抢占优先级
RTOS的中断管理除了涉及到了中断优先级配置的寄存器,这几个寄存器也同样重要
在这里插入图片描述

  1. 受RTOS的中断和不受RTOS的中断
    我们可以配置哪些中断优先级受RTOS管理 哪些中断优先级不受RTOS管理
        // 优先级数小于5的不归FreeRTOS管理
        #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
    
    • 为什么还要有不受RTOS管理的中断呢?
        除了什么硬件异常啊之类的,我们试想这样一个场景:假设我有一个电机的传感器要求每100us采集一次数据–这项工作去抽象成一个任务去做是不可能的,延时的处理是放在sysTickHandler中的,不可能把这个时钟配置的这么快,而且用任务处理时效性也不是很好(做不到100us精确延时)
      假设我们设置一个定时器中断不受RTOS的中断管理,就可以通过配置定时器和中断处理函数做到这件事了
  2. 相关的函数
  • 什么是临界区与临界资源?
    临界资源:是指一次仅允许一个进程或线程访问的共享资源,如果多个进程/线程同时访问,可能会导致数据不一致或竞态条件
    临界区: 访问临界资源的代码叫做临界区
  • taskENTER_CRITICAL与对应的ISR(在临界区进入中断)
    实际上taskENTER_CRITICAL就是通过写BASEPRI寄存器来暂时屏蔽对受RTOS管理的中断的响应
    void vPortEnterCritical( void )
    {
        portDISABLE_INTERRUPTS();
        uxCriticalNesting++;            // 这个修改全局变量是为了支持嵌套
        if( uxCriticalNesting == 1 )
        {
            configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
        }
    }
    static portFORCE_INLINE void vPortRaiseBASEPRI( void )
    {
        uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;  // 这个宏跟我们设置的configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY宏相关的哟
        __asm
        {
            /* Set BASEPRI to the max syscall priority to effect a critical
             * section. */
            /* *INDENT-OFF* */
            msr basepri, ulNewBASEPRI
            dsb
            isb
            /* *INDENT-ON* */
        }
    }
    
      对应的ISR函数版本,为啥这么做我想是因为在中断里不想去修改全局变量uxCriticalNesting去进行嵌套吧,而是需要我们手动处理状态
    static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
    {
        uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
        __asm
        {
            /* Set BASEPRI to the max syscall priority to effect a critical
            * section. */
            /* *INDENT-OFF* */
            mrs ulReturn, basepri
            msr basepri, ulNewBASEPRI
            dsb
            isb
            /* *INDENT-ON* */
        }
        return ulReturn;
    }
    
  • dsb与isb指令
    dsb和isb和我们前面讲的内存屏障有关,作用就是确保后面所有的函数都是用的bssepri的新值(毕竟我们的CPU和编译器有时候会优化指令执行顺序,从而引起不可知的错误)
    • dsb
      ​确保所有内存访问指令(如 LDR/STR)在屏障前完成,再执行后续指令。
      解决多级流水线或总线乱序执行导致的数据同步问题
    • isb
      清空处理器流水线,确保后续指令从内存重新预取,使用最新的寄存器或内存值。
      解决指令预取导致的上下文不一致问题。
  • xxFromISR探究
    我们随便找一个FromISR就会发现进入中断服务函数做的第一件事往往是调用portASSERT_IF_INTERRUPT_PRIORITY_INVALID();函数
    BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue,
                              BaseType_t * const pxHigherPriorityTaskWoken )
    {
        BaseType_t xReturn;
        UBaseType_t uxSavedInterruptStatus;
        Queue_t * const pxQueue = xQueue;
        configASSERT( pxQueue );
        configASSERT( pxQueue->uxItemSize == 0 );
        configASSERT( !( ( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) && ( pxQueue->u.xSemaphore.xMutexHolder != NULL ) ) );
    
        portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
        /**.....*/
    }
    

这个函数干了什么呢?–就是查看调用该函数的中断的优先级是不是受FreeRTOS管理

```C
    void vPortValidateInterruptPriority( void )
{
    uint32_t ulCurrentInterrupt;
    uint8_t ucCurrentPriority;

    // 得到当前中断的中断号
    ulCurrentInterrupt = vPortGetIPSR();

    // portFIRST_USER_INTERRUPT_NUMBER是16 因为前15个都是系统异常
    if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
    {
        // 通过寄存器得到中断优先级
        ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];
        configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
        // ucMaxSysCallPriority的值在xPortStartScheduler时被配置了,配置为
        //ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
    }
}
```
  1. 为什么FreeRTOS要区分带中断的API和不带中断的API
  • 在中断里不能延时 阻塞,毕竟你总不能在中断里vTaskDelay()多久
  • vPortValidateInterruptPriority函数
    验证中断优先级:确保中断的优先级符合 FreeRTOS 的要求,特别是 configMAX_SYSCALL_INTERRUPT_PRIORITY 的要求,避免高优先级中断调用不安全的 FreeRTOS API 函数。

Linux的中断管理

  1. 用户态与内核态
    在这里插入图片描述
  • 用户态和内核态分别是什么?为什么要区分?
    • 用户态: 应用程序拥有有限的系统资源访问权限,只能在操作系统划定的特定空间内运行
    • 内核态: 拥有最高权限,可直接操作硬件、管理内存、调度进程等
    • 为什么要区分: 安全性 / 稳定性(一个应用崩溃不影响其他) / 提高性能
  • 用户态切换内核态的方式?
    • 系统调用: fork() / read() / write()
    • 异常:比如缺页异常
    • 外设中断
  • 用户态如何访问内核态资源?
    • 系统调用
    • 库函数 : malloc() / printf()
    • shell : cat / …
  • 切换过程会保存那些信息
    • 保存用户态信息:将用户态的寄存器信息保存到内核栈中。
    • 切换到内核栈:设置堆栈指针寄存器为内核栈的地址。
    • 执行内核态程序:加载中断处理程序的地址到寄存器,开始执行内核态程序。
    • 恢复用户态
  1. 系统调用
  • 系统调用是什么?
    在系统调用时会触发一个特殊的中断,CPU此时就会从用户态切换内核态 不同的系统调用有不同的系统调用号 从而执行不同的处理
    系统调用并不直接返回错误码,而是将错误码放入一个名为errno的全局变量中。通常用一个负的返回值来表明错误。返回一个0值通常表明成功。
    在这里插入图片描述

  • 系统调用的具体步骤

    • 设置参数(用户态)
    • 触发系统调用 int 0x80 此时用户态会切换到内核态
    • 内核检查传入参数的合法性
    • 根据系统调用号找到处理程序并运行
    • 返回参数
  • 减少系统调用的次数

    • 合并系统调用: read/write—>readv / writev
    • 避免拷贝: read/write–>mmap
    • 缓存数据
  1. Linux的上半/下半部中断
  • 为什么要区分上/下半部
      中断作为异步执行,其时间应该越短越好 所以只在中断里做必须执行的部分 而把剩下的部分"推迟"处理就好,从而平衡实时性与系统响应能力,减少中断屏蔽时间

  • 下半部的实现 tasklet / softirq / workqueue
    下半部在执行的时候是开中断的 可以被打断的
    概括性总结
    因为软中断和tasklet还是在中断上下文 所以不能睡眠 但是workqueue就是个线程 是可以阻塞和睡眠的
    在这里插入图片描述

    • 软中断
      软中断是一个数组 在编译期间就被确定了的
      软中断在设计上要求支持重入(多个CPU同时调用都没问题)
      通过do_softirq()来执行软中断
      在这里插入图片描述

      • do_softirq()的时机
        中断退出的时候,会唤醒并调用软中断
        raise_softirq是会处理软中断
        ksoftirqd线程:避免大量的软中断导致用户态啥也不做
    • tasklet函数
      tasklet是通过软中断实现的,不过它有了新的定义:那就是不可能多个CPU同时运行一个tasklet函数怎么做到的 通过一个原子变量进行计数
      在这里插入图片描述

      tasklet本身就是在中断执行完的时候通过tasklet_schedule()添加到本CPU链表的尾部
      在这里插入图片描述

      tasklet_action来执行中断
      在这里插入图片描述

      • tasklet_action的调用时机—同软中断
    • workqueue
      对于tasklet和软中断,都不能睡眠,这在有时候可太不好用了
      工作队列的话是吧工作推后,交给一个内核线程去执行—内核线程就可以正常被CPU调度了 这样的话就可以去执行一些阻塞之类的操作了
      一般每隔CPU都有一个线程专门处理工作队列–循环遍历链表
      在这里插入图片描述

      但因为是依次遍历 所以如果我们任务前面有非常耗时的任务就得等待了–此时我们可以创建一个新的线程专门处理我们的中断下半部任务

  • 中断的处理流程
    cpu接受中断->保存中断上下文跳转到中断处理例程->执行中断上半部(并在最后调用软中断)->执行中断下半部->恢复中断上下文。

    irqreturn_t my_isr(int irq, void *dev_id) {
        /.../
        tasklet_schedule(&my_tasklet); // 触发下半部
        return IRQ_HANDLED;
    }
    
  • 中断处理函数的注册
    中断的申请request_irq的正确位置:应该是在第一次打开 、硬件被告知终端之前

    if (request_irq(irq, my_isr, IRQF_SHARED, "my_device", dev)) {
        printk("注册中断失败\n");
        return -EIO;
    }
    
  • 中断处理函数编写的注意事项

    • 尽量短小
    • 不要休眠与阻塞
    • 中断的返回值: 裸机没有返回值 / Linux内核中必须正确返回irqreturn_t
  1. 内核线程与用户线程
    用户线程是在用户空间的 每个线程是独立的拥有自己的用户空间
    而内核线程是在内核空间 共享内存的
    在这里插入图片描述

我们前面讲过每个线程都是有自己的task_struct的 不管你是哪个属于哪个进程
在这里插入图片描述

ianr

相关文章:

  • vue如何实现前端控制动态路由
  • 基于pycatia的CATIA零部件激活状态管理技术解析
  • Centos7,tar包方式部署rabbitmq-3.7.6
  • C++ 初阶总复习 (16~30)
  • 液压式精密矫平机——以稳定压力,成就工业级平整
  • CVPR-2025 | 南洋理工基于图表示的具身导航统一框架!UniGoal:通用零样本目标导航方法
  • WordPress essential-addons-for-elementor xss漏洞
  • 全排列 II:去重的技巧与实现
  • 深入理解:阻塞IO、非阻塞IO、水平触发与边缘触发
  • 使用FastExcel时的单个和批量插入的问题
  • constant(safe-area-inset-bottom)和env(safe-area-inset-bottom)在uniapp中的使用方法解析
  • 网络安全(一):常见的网络威胁及防范
  • 【动态规划篇】- 路径问题
  • Java算法模板
  • Linux Mem -- 通过reserved-memory缩减内存
  • Python基于EdgeTTS库文本转语音
  • 大数据学习(92)-spark详解
  • sqli-labs靶场 less 3
  • CF每日5题Day5(1400)
  • 使用firewall-cmd配置SIP端口转发,实现双网卡互通,内外网方式
  • 黄宾虹诞辰160周年|一次宾翁精品的大集结
  • 万科:一季度营收近380亿元,销售回款率超100%
  • 五一“拼假”催热超长假期,热门酒店民宿一房难求
  • 文天祥与“不直人间一唾轻”的元将唆都
  • 买新房可申领学位,广州南沙出台购房入学政策
  • 流浪猫给车主造成困扰,长春一小区拟投药应对?律师:此举欠妥