1. 程式人生 > 其它 >linux驅動移植-軟中斷

linux驅動移植-軟中斷

一、軟中斷

在之前關於中斷介紹的文章中,我們一直說到的都是硬體中斷,也就是通過硬體觸發的中斷。其實在linux中,除了硬體中斷外,還有一種中斷,叫做軟中斷。

1.1 什麼是軟體斷

軟中斷,英文叫做softirq,它是核心提供的一種延遲執行機制,它完全由軟體觸發,雖說是延遲機制,實際上,在大多數的情況下,它與普通程序相比,能得到更快的響應時間。

1.2 軟中斷編號

向硬體中斷IRQ 編號一樣,對於軟中斷,linux核心也是用一個softirq編號唯一標識一個softirq,在Linux中,softirq的限制是不能超過32個,核心目前只實現了10種類型的軟體中斷,定義在include/linux/interrupt.h檔案中,它們是:

/* 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,
        IRQ_POLL_SOFTIRQ,
        TASKLET_SOFTIRQ,
        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 };

其中:

  • HI_SOFTIRQ用於高優先順序的tasklet,TASKLET_SOFTIRQ用於普通的tasklet;
  • TIMER_SOFTIRQ是for software timer的(所謂software timer就是說該timer是基於系統tick的)。
  • NET_TX_SOFTIRQ和NET_RX_SOFTIRQ是用於網絡卡資料收發的;
  • BLOCK_SOFTIRQ和BLOCK_IOPOLL_SOFTIRQ是用於block device的
  • SCHED_SOFTIRQ用於排程器,實現SMP系統上週期性的負載均衡;
  • 在啟用高解析度定時器時,還需要一個HRTIMER_SOFTIRQ;
  • RCU_SOFTIRQ是處理RCU的;

1.3 softirq描述符

softirq是靜態定義的,也就是說系統中有一個定義softirq描述符的陣列,該陣列定義在kernel/softirq.c檔案中,softirq編號就是這個陣列的index:

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

這裡的NR_SOFTIRQS恰好是softirq編號的最大值。

核心用softirq_action結構管理軟體中斷的註冊和啟用等操作,它的定義如下:

struct softirq_action
{
    void    (*action)(struct softirq_action *);
};

非常簡單,只有一個用於回撥的函式指標。

1.4 irq_cpustat_t

多個軟中斷可以同時在多個cpu執行,就算是同一種軟中斷,也有可能同時在多個cpu上執行。核心為每個cpu都管理著一個待決軟中斷變數(pending),它就是irq_cpustat_t,定義在arch/arm/include/asm/hardirq.h:::

typedef struct {
        unsigned int __softirq_pending;
#ifdef CONFIG_SMP
        unsigned int ipi_irqs[NR_IPI];
#endif
} ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

__softirq_pending欄位中的每一個bit,對應著某一個軟中斷,某個bit被置位,說明有相應的軟中斷等待處理。

1.5 ksoftirqd

在cpu的熱插拔階段,核心為每個cpu建立了一個用於執行軟體中斷的守護程序ksoftirqd,同時在kernel/softirq.c檔案中定義了一個per_cpu變數用於儲存每個守護程序的task_struct結構指標:

DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
大多數情況下,軟中斷都會在irq_exit階段被執行,在irq_exit階段沒有處理完的軟中斷才有可能會在守護程序中執行。

二、軟體中斷的安裝

2.1 註冊softirq

通過呼叫open_softirq介面函式可以註冊softirq的action回撥函式,該函式定義在kernel/softirq.c:

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

2.2 HI_SOFTIRQ和TASKLET_SOFTIRQ註冊

在softirq初始化函式中,完成了HI_SOFTIRQ和TASKLET_SOFTIRQ的執行函式的註冊:

void __init softirq_init(void)
{
        int cpu;

        for_each_possible_cpu(cpu) {
                per_cpu(tasklet_vec, cpu).tail =
                        &per_cpu(tasklet_vec, cpu).head;
                per_cpu(tasklet_hi_vec, cpu).tail =
                        &per_cpu(tasklet_hi_vec, cpu).head;
        }

        open_softirq(TASKLET_SOFTIRQ, tasklet_action);
        open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

2.3 TIMER_SOFTIRQ註冊

其他的一些softirq,則是在各自模組裡初始化的,比如TIMER_SOFTIRQ的執行函式是在init_timers裡實現註冊的:

void __init init_timers(void)
{
        init_timer_cpus();
        open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

三、軟中斷的觸發

軟中斷的觸發時機:

  • irq_exit:在硬中斷退出時,會檢查local_softirq_pending和preemt_count,如果都符合條件,則執行軟中斷;
  • local_bh_enable:使用此函式開啟軟中斷時,會檢查local_softirq_pending,如果都符合條件,則執行軟中斷。呼叫鏈為local_bh_enable()->__local_bh_enable()->do_softirq();
  • raise_softirq;

3.1 irq_exit

我們在分析發生硬體中斷,執行中斷處理程式的原始碼時,我們介紹了__handle_domain_irq函式,其中irq_enter和irq_exit函式分別用於標記generic handler的進入和退出。

/**
 * __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
 * @domain:     The domain where to perform the lookup
 * @hwirq:      The HW irq number to convert to a logical one
 * @lookup:     Whether to perform the domain lookup or not
 * @regs:       Register file coming from the low-level handling code
 *
 * Returns:     0 on success, or -EINVAL if conversion has failed
 */
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
                        bool lookup, struct pt_regs *regs)
{
        struct pt_regs *old_regs = set_irq_regs(regs);
        unsigned int irq = hwirq;
        int ret = 0;

        irq_enter();                  //  更新一些系統統計資訊,禁止核心搶佔

#ifdef CONFIG_IRQ_DOMAIN
        if (lookup)
                irq = irq_find_mapping(domain, hwirq);  // 根據中斷域和硬體中斷號獲取IRQ編號
#endif

        /*
         * Some hardware gives randomly wrong interrupts.  Rather
         * than crashing, do something sensible.
         */
        if (unlikely(!irq || irq >= nr_irqs)) {    // 中斷號超出範圍
                ack_bad_irq(irq);
                ret = -EINVAL;
        } else {
                generic_handle_irq(irq);       // 重點時這裡
        }

        irq_exit();               // 退出的時候會檢查是否由軟中斷,如果有。則呼叫軟中斷
        set_irq_regs(old_regs);
        return ret;
}

那irq_enter和irq_exit究竟做了什麼事情呢?我們先來看看irq_enter,其定義在kernel/softirq.c:

/*
 * Enter an interrupt context.
 */
void irq_enter(void)
{
        rcu_irq_enter();
        if (is_idle_task(current) && !in_interrupt()) {
                /*
                 * Prevent raise_softirq from needlessly waking up ksoftirqd
                 * here, as softirq will be serviced on return from interrupt.
                 */
                local_bh_disable();
                tick_irq_enter();
                _local_bh_enable();
        }

        __irq_enter();
}

 然後我們再來看看irq_exit:

/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
        local_irq_disable();
#else
        lockdep_assert_irqs_disabled();
#endif
        account_irq_exit_time(current);
        preempt_count_sub(HARDIRQ_OFFSET);
        if (!in_interrupt() && local_softirq_pending())
                invoke_softirq();

        tick_irq_exit();
        rcu_irq_exit();
        trace_hardirq_exit(); /* must be last! */
}

3.2 local_bh_enable

3.3 raise_softirq

主動喚起一個軟中斷,只要呼叫raise_softirq即可,它的實現很簡單,實現程式碼位於kernel/softirq.c:
void raise_softirq(unsigned int nr)
{
        unsigned long flags;

        local_irq_save(flags);
        raise_softirq_irqoff(nr);
        local_irq_restore(flags);
}
先是關閉本地cpu中斷,然後呼叫:raise_softirq_irqoff:
/*
 * This function must run with irqs disabled!
 */
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();
}

先是通過__raise_softirq_irqoff設定cpu的軟中斷pending標誌位(irq_stat[NR_CPUS] ),然後通過in_interrupt判斷現在是否在中斷上下文中,或者軟中斷是否被禁止,如果都不成立,則喚醒軟中斷的守護程序,在守護程序中執行軟中斷的回撥函式。否則什麼也不做,軟中斷將會在中斷的退出階段被執行。

四、軟中斷的執行

參考文章

[1]Linux中斷(interrupt)子系統之五:軟體中斷(softIRQ)

[2]Linux的中斷處理機制 [四] - softirq(1)

[3]Linux的中斷處理機制 [五] - softirq(2)

[4]Linux中的中斷處理機制 [六] - 從tasklet到中斷執行緒化

[5]5.5 Linux softirq

[6]linux kernel的中斷子系統之(八):softirq

[7]Softirq, Tasklets and Workqueues