首页 > 系统相关 >【Linux中断】中断下半部-软中断softirq的原理与使用

【Linux中断】中断下半部-软中断softirq的原理与使用

时间:2023-06-11 12:22:38浏览次数:64  
标签:__ SOFTIRQ 中断 Linux cpu pending softirq

软中断

软中断是中断下半部的典型处理机制,是随着SMP的出现应运而生的,也是tasklet实现的基础,软中断的出现是为了满足中断上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。

软中断有以下特性

  • 产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)
  • 可以并发运行在多个cpu上。所以软中断必须要设计为可重入的函数(允许多个CPU同时操作)。

软中断数据结构

  • 软中断描述符
sturct softirq_action
{
    void *(action)(struct softirq_action *);
};

描述每一种类型的软中断,void (*action)是软中断触发时的执行函数。

  • 软中断全局数据和支持的枚举类型
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
enum
{
    HI_SOFTIRQ=0,     // 高优先级的tasklet
    TIMER_SOFTIRQ,    // 用于定时器的下半部
    NET_TX_SOFTIRQ,   // 用于网络层发数据包
    NET_RX_SOFTIRQ,   // 用于网络层收数据包
    BLOCK_SOFTIRQ,
    IRQ_POLL_SOFTIRQ,
    TASKLET_SOFTIRQ,   // 低优先集的tasKlet
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
                numbering. Sigh! */
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */


    NR_SOFTIRQS
};

软中断的API接口

  • 注册软中断
void open_softirq(int nr, void (*action)(struct softirq_action *));

注册对应类型的处理函数到全局数组softirq_vec中。 网络法宝的对应类型是NET_TX_SOFTIRQ的处理函数net_tx_action。

  • 触发软中断
void raise_softirq(unsigned int nr);

软中断类型nr作为偏移量置位每个CPU变量irq_stat[cpu_id]的成员变量_softirq_pending,这是同一类型的软中断可以在多个cpu核心上并行运行的根本原因。

  • 软中断执行函数
do_softirq() -> __do_softirq()

在执行软中断处理函数__do_softirq()前需要满足两个条件:

(1)不在中断中(包括硬中断,软中断和NMI)
(2)有软中断处于pending状态

这样设计的原因是为了避免软件中断在中断嵌套中调用,达到在单个CPU上软件中断不能被重入的目的。

ARM架构的CPU不存在中断嵌套中调用软件中断的问题,因为ARM架构的CPU在处理硬件中断的过程中关闭掉中断,在进入软中断处理过程之后才会重新开启硬件中断,如果在软件中断处理过程中有硬件中断嵌套,也不会再次调用软中断,因为硬中断是软中断处理过程中再次进入的。

软中断实现原理

中断处理过程图

img

软中断的调度时机

1.do_irq完成I/O中断时调用irq_exit

2.系统使用I/O APIC,在处理玩本地时钟中断时

3.local_bh_enable,开启本地软中断时

4.SMP系统中,cpu处理完备CALL_FUNCTION_VECTOR处理器间中断所触发的函数时。

5.ksoftirqd/n线程被唤醒时

软中断处理流程

asmlinkage __visible void __softirq_entry __do_softirq(void)
{
    unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
    unsigned long old_flags = current->flags;
    int max_restart = MAX_SOFTIRQ_RESTART;
    struct softirq_action *h;
    bool in_hardirq;
    __u32 pending;
    int softirq_bit;

    /*
     * Mask out PF_MEMALLOC as the current task context is borrowed for the
     * softirq. A softirq handled, such as network RX, might set PF_MEMALLOC
     * again if the socket is related to swapping.
     */
    current->flags &= ~PF_MEMALLOC;
    // 获取当前有哪些等待的软中断
    pending = local_softirq_pending();
    account_irq_enter_time(current);

    // 关闭软中断,实质是设置正在处理软件的中断标记,在同一个CPU上使得不能重入_do_softirq函数
    __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
    in_hardirq = lockdep_softirq_start();

restart:
    /* Reset the pending bitmask before enabling irqs */
    // 重新设置软中断标志为0,这样在重新烤漆中断后硬件中断中又可以设置软件中断位
    set_softirq_pending(0);

    // 开启硬件中断
    local_irq_enable();

    h = softirq_vec;

    // 遍历pending标志的每一位,如果这一位设置就会调用软件中断的处理函数
    while ((softirq_bit = ffs(pending))) {
        unsigned int vec_nr;
        int prev_count;

        h += softirq_bit - 1;

        vec_nr = h - softirq_vec;
        prev_count = preempt_count();

        kstat_incr_softirqs_this_cpu(vec_nr);

        trace_softirq_entry(vec_nr);
        h->action(h);
        trace_softirq_exit(vec_nr);
        if (unlikely(prev_count != preempt_count())) {
            pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
                   vec_nr, softirq_to_name[vec_nr], h->action,
                   prev_count, preempt_count());
            preempt_count_set(prev_count);
        }
        h++;
        pending >>= softirq_bit;
    }

    if (__this_cpu_read(ksoftirqd) == current)
        rcu_softirq_qs();

    // 关闭硬件中断
    local_irq_disable();
    // 查看是否有软件中断处于pending状态
    pending = local_softirq_pending();
          
    if (pending) {
        if (time_before(jiffies, end) && !need_resched() &&
            --max_restart)
            goto restart;
        // 如累积重复进入软件中断处理的次数超过max_restart=10次,就唤醒内核进程来处理软件中断
        wakeup_softirqd();
    }
    
    lockdep_softirq_end(in_hardirq);
    account_irq_exit_time(current);
    // 开启软中断
    __local_bh_enable(SOFTIRQ_OFFSET);
    WARN_ON_ONCE(in_interrupt());
    current_restore_flags(old_flags, PF_MEMALLOC);
}

软中断内核线程

触发软件中断的位置是在中断上下文,而软中断的内核线程就已经进入到进程的上下文。软中断的上下文是系统为每个CPU建立的ksoftirqd进程。软中断的内核进程中主要有两个大循环,外层的循环处理有软件中断就处理,没有软件中断就休眠。内层的循环处理软件中断,每循环一次都试探一次是否过长时间占据了CPU,需要调度就是放CPU给其他进程。

set_current_state(TASK_INTERRUPTIBLE);
    //外层大循环。
    while (!kthread_should_stop()) {
        preempt_disable();//禁止内核抢占,自己掌握cpu
        if (!local_softirq_pending()) {
            preempt_enable_no_resched();
            //如果没有软中断在pending中就让出cpu
            schedule();
            //调度之后重新掌握cpu
            preempt_disable();
        }

        __set_current_state(TASK_RUNNING);

        while (local_softirq_pending()) {
            /* Preempt disable stops cpu going offline.
               If already offline, we'll be on wrong CPU:
               don't process */
            if (cpu_is_offline((long)__bind_cpu))
                goto wait_to_die;
            //有软中断则开始软中断调度
            do_softirq();
            //查看是否需要调度,避免一直占用cpu
            preempt_enable_no_resched();
            cond_resched();
            preempt_disable();
            rcu_sched_qs((long)__bind_cpu);
        }
        preempt_enable();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;

wait_to_die:
    preempt_enable();
    /* Wait for kthread_stop */
    set_current_state(TASK_INTERRUPTIBLE);
    while (!kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;










参考文章:

https://zhuanlan.zhihu.com/p/265705850

标签:__,SOFTIRQ,中断,Linux,cpu,pending,softirq
From: https://www.cnblogs.com/Wangzx000/p/17472758.html

相关文章

  • linux防火墙相关命令
    查看防火墙状态:systemctlstatusfirewalld启动防火墙:systemctlstartfirewalld关闭防火墙:systemctlstopfirewalld禁用防火墙:systemctldisablefirewalld重启防火墙(修改配置后要重启防火墙):firewall-cmd--reload开放指定端口:firewall-cmd--permanent--a......
  • 在Windows上无docker直接将基于Solon的jar包通过IDEA部署到Linux的docker上
    为何会选择学习solon?springboot对于我开发小企业应用太重,启动太慢,下班太晚!为何都用windows,还想着不安装dockerdesktop洁癖,运行路径能短就短。步骤(以solon官网的helloword为例)1、下载helloworld代码传送阵:点击我2、通过IDEA打开代码,并运行它(我是下载基于maven版本的)。3......
  • Linux系统下配置Nginx服务器
    Nginx是一个高性能的开源HTTP和反向代理服务器,也可以作为电子邮件(SMTP/POP3/IMAP)代理服务器、负载均衡器和HTTP缓存服务器,使用在安装Nginx之前,需要安装一些其他软件依赖,如gcc、pcre、zlib和openssl。1、yum installgcc-ygcc是GNUCompilerCollection的简称,包含编译器和其他编......
  • Linux下六个有关file使用的实例
    FILE命令用来识别文件类型,也可用来辨别一些文件的编码格式。它是通过查看文件的头部信息来获取文件类型,而不是像Windows通过扩展名来确定文件类型的。本文介绍Linux下的六个有关file的实例。简介file的官方解释为:file - determine file type也就是说可以识别文件......
  • linux如何同时执行两个命令,如何同时运行两个或者多个终端命令
    [-d~/aa]||mkdir~/aa[-f~/sample.txt]&&echo"Fielexits"||touch~/sample.txt查看前一条命令的返回值echo$?echo$?查看命令执行成功与否的原理“进程生命周期”当一个进程执行完毕时,该进程会调用一个名为_exit的例程来通知内核它已经做好......
  • go程序在linux服务器上运行
    一、go语言环境安装参考菜鸟教程二、编译测试代码源码:packagemainimport"fmt"funcmain(){ fmt.Println("gorunning...")}编译在powershell终端输入命令编译后是运行不了的,在源码文件目录cmd,然后输入编译命令gobuild编译前先设置几个环境变量GOARCH:目......
  • Redis安装与启动(Linux)
    安装Redis依赖yuminstall-ygcctcl上传安装包并解压redis.io下载安装包,上传到/usr/local/src目录解压tar-zxvfredis-6.2.6.tat.gz解压成功 进入安装目录,运行编译命令cdredis-6.2-6/make&&makeinstall默认的安装路径在/usr/local/bin下默认启动(不友好......
  • linux 内存管理之内核分页机制(PAGING_INIT)初始化
     TEXT_OFFSET=0x00008000KERNEL_OFFSET=0x80000000PG_DIR_SIZE=0x4000kernel代码起始链接地址如下:PHYS_OFFSET=0x80000000r10指向proc_info_list结构体类型数据__create_page_tables:pgtblr4,r8@pagetableaddres......
  • linux系统离线安装oracle 11g
    1.修改hostname(机器名根据自己的要求改)root用户修改hostname命令:hostnamectlset-hostnamenode2 重启下服务器:reboot 2.安装依赖 root用户在一台有网的电脑上先下载好所需的依赖yuminstall--downloadonly--downloaddir=/tmp/compat-libstdc++-33elfutils-lib......
  • Linux常用命令
    以下是常用的Linux命令:1.ls:列出目录中的文件和子目录。2.cd:切换当前目录。3.pwd:显示当前工作目录的完整路径。4.mkdir:创建一个新的目录。5.rm:删除文件或目录。6.cp:复制文件或目录。7.mv:移动文件或目录,也可以用于重命名文件或目录。8.cat:显示文件内容。9.less:......