linux kernel软中断及其衍生品-定时器 tasklet的实现

软中断概念在嵌入式开发可以有两个不同的解释:

其一,软中断在处理器设计中是处理器异常之一,程序软件使用指定指令(如arm的SWI指令)引发该异常从而陷入内核态执行,最典型的软件应用就是系统调用。

其二,在kernel代码中实现了一套软中断机制,区别于硬件中断的硬件触发软件处理,而是软件触发软件处理。

今天来学习的是kernel下的软中断机制,

学习最常用的硬件中断,我们最关心的是中断触发(硬件)-中断分发-中断处理这个流程如何完成,对于软中断我们也需要搞明白这几点。

首先来看下kernel中跟软中断相关的结构体变量,在kernel/softirq.c中,如下:

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

softirq_vec数组表示kernel下所有注册的软中断。

struct softirq_action
{
    void    (*action)(struct softirq_action *);
};
数组下标表示软中断号,softirq_action则代表软中断处理函数。

softirq_vec类似于kernel下表征硬件中断的irq_desc。

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */

enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

    NR_SOFTIRQS
};

kernel下定义了10种软中断,其中包括应用于定时器 tasklet 网络收发(NAPI) 块设备读写等。

根据注释可以看出,kernel并不希望开发者来添加新的softirq,如果有需求,可以利用由softirq实现的tasklet实现需求。

下面来看softirq的注册 触发 和 分发处理

1 软中断注册

softirq注册使用函数open_softirq,类似于硬件中断的register_irq。定义在kernel/softirq.c中

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    softirq_vec[nr].action = action;
}

很简单,添加softirq_vec对应下标的处理函数即可。

在kernel启动start_kernel中就注册了定时器的softirq,如下:

void __init init_timers(void)
{
    int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
                (void *)(long)smp_processor_id());

    init_timer_stats();

    BUG_ON(err != NOTIFY_OK);
    register_cpu_notifier(&timers_nb);
    open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

定时器处理函数为run_timer_softirq,后面学习定时器实现还会分析。

2 软中断触发

区别于硬件中断由硬件触发,软中断由软件触发,由函数raise_softirq和raise_softirq_irqoff触发,实现如下:

inline void raise_softirq_irqoff(unsigned int nr)
{
    __raise_softirq_irqoff(nr);

    /*
     * If we're in an interrupt or softirq, we're done
     * (this also catches softirq-disabled code). We will
     * actually run the softirq once we return from
     * the irq or softirq.
     *
     * Otherwise we wake up ksoftirqd to make sure we
     * schedule the softirq soon.
     */
    if (!in_interrupt())
        wakeup_softirqd();
}

void raise_softirq(unsigned int nr)
{
    unsigned long flags;

    local_irq_save(flags);
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);
}

void __raise_softirq_irqoff(unsigned int nr)
{
    trace_softirq_raise(nr);
    or_softirq_pending(1UL << nr);
}

2个函数区别在于raise_softirq在触发softirq时会屏蔽irq。

触发softirq都是调用or_softirq_pending,将内核变量__softirq_pending的nr位置位。

所以__softirq_pending变量类似于硬件中断中intc的中断状态寄存器。我们可以称之为软中断状态变量!

3 软中断的分发处理

硬件中断是异步于处理器,异步于软件程序的。硬件中断触发-中断异常入口-中断分发处理,硬件中断分发处理时机取决于硬件中断的时机,软件不需要控制。

而软中断不能异步触发调用,软中断的分发处理函数是do_softirq,分发处理时机主要有3种:

(1)在处理完一个硬件中断以后。

(2)在ksoftirqd内核线程中。

(3)在那些显式检查和执行待处理的软中断的代码中,如网络子系统中(显式调用do_softirq来收发数据包)。

我们主要来看前2种情况

(1) 硬件中断处理函数退出时

中断处理函数退出时会调用irq_exit,在kernel/softirq.c中如下:

static inline void invoke_softirq(void)
{
    if (!force_irqthreads) {
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
        __do_softirq();
#else
        do_softirq();
#endif
    } else {
        __local_bh_disable((unsigned long)__builtin_return_address(0),
                SOFTIRQ_OFFSET);
        wakeup_softirqd();
        __local_bh_enable(SOFTIRQ_OFFSET);
    }
}
/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
    account_system_vtime(current);
    trace_hardirq_exit();
    sub_preempt_count(IRQ_EXIT_OFFSET);
    if (!in_interrupt() && local_softirq_pending())
        invoke_softirq();

#ifdef CONFIG_NO_HZ
    /* Make sure that timer wheel updates are propagated */
    if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
        tick_nohz_irq_exit();
#endif
    rcu_irq_exit();
    sched_preempt_enable_no_resched();
}

检查__softirq_pending是否有置位,有则invoke_softirq调用do_softirq。

(2) ksoftirqd内核线程

该内核线程的实现也在kernel/softirq.c中,这里不贴代码了。

根据ksoftirqd线程实现可以看出,当内核中出现大量软中断的时候,内核进程ksoftirqd就会调用do_softirq处理它们。

软中断分发处理时机清楚了,下面来看do_softirq如何处理,在kernel/softirq.c中:

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

local_softirq_pending获取__softirq_pending软中断状态变量,如果有softirq的pending则调用__do_softirq,如下:

#define MAX_SOFTIRQ_RESTART 10
asmlinkage void __do_softirq(void)
{
    struct softirq_action *h;
    __u32 pending;
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;

    pending = local_softirq_pending();
    account_system_vtime(current);

    __local_bh_disable((unsigned long)__builtin_return_address(0),
                SOFTIRQ_OFFSET);
    lockdep_softirq_enter();

    cpu = smp_processor_id();
restart:
    /* Reset the pending bitmask before enabling irqs */
    set_softirq_pending(0);

    local_irq_enable();

    h = softirq_vec;
    do {
        if (pending & 1) {
            unsigned int vec_nr = h - softirq_vec;
            int 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())) {
                printk(KERN_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() = prev_count;
            }

            rcu_bh_qs(cpu);
        }
        h++;
        pending >>= 1;
    } while (pending);

    local_irq_disable();

    pending = local_softirq_pending();
    if (pending && --max_restart)
        goto restart;

    if (pending)
        wakeup_softirqd();

    lockdep_softirq_exit();

    account_system_vtime(current);
    __local_bh_enable(SOFTIRQ_OFFSET);
}

__do_softirq处理softirq之前获取并清空__softirq_pending,然后根据pending遍历softirq_vec,调用对应pending的action处理函数。

遍历完成后,会再次获取__softirq_pending,如果在遍历softirq_vec期间又产生了softirq,__do_softirq会跳转到restart再次遍历处理softirq。

最多反复处理MAX_SOFTIRQ_RESTART次。

如果还有softirq的pending,则唤醒ksoftirqd内核线程来处理剩余的softirq。

软中断的注册 触发 处理就是这样,软中断是kernel实现的纯软件机制,依托于中断退出和内核线程时机来做分发处理。

基于软中断,kernel实现了定时器 tasklet,下面来重点来看下内核定时器的实现原理。

一 内核定时器

内核定时器是通过内核时钟系统和软中断结合实现的。

在driver中对于定时器的使用,一般是这样:

    static struct timer_list test_timer;
    init_timer(&test_timer);
    test_timer.function = (void *)test_handle_function;
    test_timer.data = (u32) test;
    test_timer.expires = CHECK_TIME + jiffies;
    add_timer(&test_timer);

该段代码初始化timer_list各个成员,timer_list代表一个定时器对象,最后将该timer_list添加到内核定时器链表tvec_bases中。

下面来看下内核如何实现的定时器。

1 注册定时器软中断

前面软中断中讲过,start_kernel中init_timers注册了定时器软中断处理函数run_timer_softirq,软中断号是TIMER_SOFTIRQ。

2 触发定时器软中断

定时器软中断的触发是在kernel的时钟中断中,根据前一篇博文《linux kernel时钟系统的前世今生》链接地址:

对于tickless系统,中断处理函数是tick_nohz_handler,在kernel/time/tick-sched.c中,该函数除了更新xtime以及设置下次触发时间外,还会调用update_process_times。

该函数调用了run_local_timers,如下

void run_local_timers(void)
{
    hrtimer_run_queues();
    raise_softirq(TIMER_SOFTIRQ);
}

其中调用raise_softirq触发了定时器软中断。

3 处理定时器软中断

根据前面处理软中断的讲解,硬件中断退出或ksoftirqd内核线程中do_softirq遍历softirq_vec。

因触发了定时器软中断,所以遍历softirq_vec会调用softirq_action[TIMER_SOFTIRQ].action,就是run_timer_softirq。

run_timer_softirq遍历tvec_bases中所有定时器timer_list,对比timer_list.expires与当前的jiffies,如果一致,说明定时到期,调用timer_list.function。

内核定时器的实现类似于硬件中断的二级中断,根据intc的中断状态寄存器分发中断后,我们还会根据模块内的中断状态寄存器来确定模块内具体引起中断的子模块。

内核定时器是在时钟中断中触发定时器软中断,在所有硬件中断退出时分发处理定时器软中断,判断时间到期,则执行定时器处理函数,最后清除掉定时器软中断。

二 tasklet

1 注册tasklet软中断

在start_kernel中softirq_init中注册了tasklet软中断处理函数:

open_softirq(TASKLET_SOFTIRQ, tasklet_action);

2 注册tasklet

可以使用<linux/interrupt.h>中定义的两个宏中的一个DECLARE_TASKLET或 DECLARE_TASKLET_DISABLED来静态创建tasklet,前者把创建的tasklet的引用计数器设置为0,该tasklet处于激活状态。另一个把引入计数器设为1,所以该tasklet处于禁止状态。

还可以使用tasklet_init()动态创建一个tasklet。

3 触发和处理tasklet软中断

触发tasklet软中断的操作通过tasklet_schedule()函数间接调用raise_softirq()函数来实现。

此后则等待任何的硬件中断处理函数退出后调用do_softirq来执行TASKLET_SOFTIRQ软中断的处理函数tasklet_action。

tasket_action中遍历tasklet_vec链表,对于enable的tasklet执行其处理函数。

内核定时器 tasklet实现都类似于硬件中断的二级中断。

由于定时器 tasklet都会在硬件中断退出的时机去检查执行处理,所以会应用于中断底半部的处理。

中断处理要求尽可能的简洁迅速,因此对于时间要求不是很紧迫,允许稍后执行的部分操作可以放在tasklet和定时器处理函数中进行执行。

时间: 2024-11-13 10:31:06

linux kernel软中断及其衍生品-定时器 tasklet的实现的相关文章

linux kernel的中断子系统之(九):tasklet

一.前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),属于不那么紧急需要处理的事情被推迟执行,我们称之deferable task,或者叫做bottom half,.具体如何推迟执行分成下面几种情况: 1.推迟到top half执行完毕 2.推迟到某个指定的时间片(例如40ms)之后执行 3.推迟到某个内核线程被调度的时候执行 对于第一种情况,内核中的机制包括softirq机制和tasklet机制.第二种情况是属于softirq机制的一种应用场

【linux kernel】 中断处理-中断下半部

欢迎转载,转载时需保留作者信息,谢谢. 邮箱:[email protected] 博客园地址:http://www.cnblogs.com/embedded-tzp Csdn博客地址:http://blog.csdn.net/xiayulewa     1.   概述 Linux内核中断机制:为了在中断执行时间尽可能短和中断处理需要完成大量工作之间找到一个平衡点,Linux将中断处理程序分解为两个半部,顶半部和底半部.       顶半部完成尽可能少的比较紧急的任务,它往往只是简单地读取寄存器中

synchronization in Linux kernel

kernel  preemption: the main  characteristic  of  a  preemptive kernel  is  that  a  process  running  in  the  kernel  mode  can be replaced by  another  process  while  in  the  middle  of  a  kernel  function . The main motivation for making a ker

Linux kernel中断子系统之(五):驱动申请中断API

一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的驱动的时候,如何向Linux Kernel中的中断子系统注册中断处理函数?为了理解注册中断的接口,必须了解一些中断线程化(threaded interrupt handler)的基础知识,这些在第二章描述.第三章主要描述了驱动申请 interrupt line接口API request_threaded_irq的规格.第四章是进入request_threaded_irq的实现细节,分析整个代码的执行过程. 二.和中断相关的lin

linux kernel的中断子系统之(八):softirq

一.前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情.在执行bottom half的时候,是开中断的.有多种bottom half的机制,例如:softirq.tasklet.workqueue或是直接创建一个kernel thread来执行bottom half(这在旧的kernel驱动中常见,现在,一个理智的driver厂商是

Linux kernel的中断子系统之(六):ARM中断处理过程

一.前言 本文主要以ARM体系结构下的中断处理为例,讲述整个中断处理过程中的硬件行为和软件动作.具体整个处理过程分成三个步骤来描述: 1.第二章描述了中断处理的准备过程 2.第三章描述了当发生中的时候,ARM硬件的行为 3.第四章描述了ARM的中断进入过程 4.第五章描述了ARM的中断退出过程 本文涉及的代码来自3.14内核.另外,本文注意描述ARM指令集的内容,有些source code为了简短一些,删除了THUMB相关的代码,除此之外,有些debug相关的内容也会删除. 二.中断处理的准备过

linux kernel 时钟系统的前世今生

趁工作不忙想把最近工作中研究到的kernel的时钟系统 软中断 定时器 tasklet 工作队列实现机制总结下,首先说明,这些原理实现对编写driver不会有多大帮助,但是明白理解这些kernel机制的实现原理,对于我们从系统角度去思考解决问题,会有很大帮助.上篇博文<一个奇葩bug的解决>就印证了这一点,链接如下:http://blog.csdn.net/skyflying2012/article/details/44623515. 那么为什么要把这些内容放在一起总结,因为他们之间是相关联的

(转)linux中断 软中断

在分析linux内核的中断,软中断时,先应该明确这样一个派生关系: irq ==> softirq ==> tasklet ==> bottom half ==> task queue------------------------|==> timer 中断是最初的原动力.分时系统依赖于时钟中断来定时重新调度可以运行的程序.外设通过中断来通知cpu处理相关的任务.中断处理程序是内核中一段特殊的,独立的,可运行实体.这个实体,某种程度上,是和进程或线程类似的. 由于中断需要快速

Linux snacks from &lt;linux kernel development&gt;

introduction to the Linux kernel 1.operating system 1) considered the parts of the system 2) responsible for basic use and administration. 3) includes the kernel and device drivers, boot loader, command shell or other user interface, and basic file a