作者:獨孤九賤 平台:2.6.31.13 + x86 32位 供僅讨論學習之處,不對錯誤之處負責,轉載請注明出處。 1、軟中斷 軟中斷的原理就略過了,講核心的書上都有,此處省略1500字。。。。。。 1.1 注冊 還是以我最熟悉的兩個老朋友做為開篇: open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); open_softirq向核心注冊一個軟中斷,其實質是設定軟中斷向量表相應槽位,注冊其處理函數: - void open_softirq(int nr, void (*action)(struct softirq_action *))
- {
- softirq_vec[nr].action = action;
- }
複制代碼 softirq_vec是整個軟中斷的向量表: - struct softirq_action
- {
- void (*action)(struct softirq_action *);
- };
- static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
複制代碼 NR_SOFTIRQS是最大軟中斷向量數,核心支援的所有軟中斷如下: - enum
- {
- HI_SOFTIRQ=0,
- TIMER_SOFTIRQ,
- NET_TX_SOFTIRQ,
- NET_RX_SOFTIRQ,
- BLOCK_SOFTIRQ,
- TASKLET_SOFTIRQ,
- SCHED_SOFTIRQ,
- HRTIMER_SOFTIRQ,
- RCU_SOFTIRQ,
- NR_SOFTIRQS
- };
複制代碼 好像後為為RPS新增了一個,不過這我的核心版本偏低。 1.2 激活 當需要調用軟中斷時,需要調用raise_softirq函數激活軟中斷,這裡使用術語“激活”而非“調用”, 是因為在很多情況下不能直接調用軟中斷。是以隻能快速地将其标志為“可執行”,等待未來某一時刻調用。 為什麼“在很多情況下不能直接調用軟中斷”?試想一下下半部引入的理念,就是為了讓上半部更快地執行。 如果在中斷程式代碼中直接調用軟中斷函數,那麼就失去了上半部與下半部的差別,也就是失去了其存在的意義。 核心使用一個名為__softirq_pending的位圖來描述軟中斷,每一個位對應一個軟中斷,位圖包含在結構irq_stat中: - typedef struct {
- unsigned int __softirq_pending;
- ……
- } ____cacheline_aligned irq_cpustat_t;
- DECLARE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);
複制代碼 宏or_softirq_pending用于設定相應的位(位或操作): - #define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x))
複制代碼 local_softirq_pending用于取得整個位圖(而非某一位): - #define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)
複制代碼 宏__raise_softirq_irqoff是or_softirq_pending的包裹: - #define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)
複制代碼 raise_softirq_irqoff通過調用__raise_softirq_irqoff實作激活軟中斷,它的參數nr即位軟中斷對應的位圖槽位: - inline void raise_softirq_irqoff(unsigned int nr)
- {
- //置位圖,即标記為可執行狀态
- __raise_softirq_irqoff(nr);
- //設定了位圖後,可以判斷是否已經沒有在中斷上下文中了,如果沒有,則是一個立即調用軟中斷的好時機。
- //in_interrupt另一個作用是判斷軟中斷是否被禁用。
- //wakeup_softirqd喚醒軟中斷的守護程序ksoftirq。
- if (!in_interrupt())
- wakeup_softirqd();
- }
複制代碼 現在可以來看"激活"軟中斷的所有含義了,raise_softirq函數完成這一操作: - void raise_softirq(unsigned int nr)
- {
- unsigned long flags;
- //所有操作,應該關閉中斷,避免嵌套調用
- local_irq_save(flags);
- raise_softirq_irqoff(nr);
- local_irq_restore(flags);
- }
複制代碼 可見,激活的操作,主要是兩點: <1>、最重要的,就是置相應的位圖,等待将來被處理; <2>、如果此時已經沒有在中斷上下文中,則立即調用(其實是核心線程的喚醒操作),現在就是将來; 2、排程時機 是的,除了raise_softirq在,可能會(嗯,重要的是“可能”)通過wakeup_softirqd喚醒ksoftirqd外,還得明白軟中斷的其它調用時機。 A、當do_IRQ完成了I/O中斷時調用irq_exit: - #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
- # define invoke_softirq() __do_softirq()
- #else
- # define invoke_softirq() do_softirq()
- #endif
- 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(); //調用軟中斷
複制代碼 B、如果系統使用I/O APIC,在處理完本地時鐘中斷時: - void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)
- {
- ……
- irq_exit();
- ……
- }
複制代碼 C、local_bh_enable local_bh_enable就是打開下半部,當然重中之中就是軟中斷了: - void local_bh_enable(void)
- {
- _local_bh_enable_ip((unsigned long)__builtin_return_address(0));
- }
- static inline void _local_bh_enable_ip(unsigned long ip)
- {
- ……
- if (unlikely(!in_interrupt() && local_softirq_pending()))
- do_softirq();
- ……
- }
複制代碼 D、在SMP中,當CPU處理完被CALL_FUNCTION_VECTOR處理器間中斷所觸發的函數時: 唔,對多核中CPU的之間的通信不熟,不太清楚這個機制…… 3、do_softirq 不論是哪種調用方式,最終都會觸發到軟中斷的核心處理函數do_softirq,它處理目前CPU上的所有軟中斷。 核心将軟中斷設計盡量與平台無關,但是在某些情況下,它們還是會有差異,先來看一個x86 32位的do_softirq版本: - asmlinkage void do_softirq(void)
- {
- unsigned long flags;
- struct thread_info *curctx;
- union irq_ctx *irqctx;
- u32 *isp;
- //軟中斷不能在中斷上下文内嵌套調用。中斷處理程式或下半部采用的是"激活"方式。
- if (in_interrupt())
- return;
- //禁止中斷,儲存中斷标志
- local_irq_save(flags);
- //核心使用一個CPU位圖,确實幾個軟中斷可以同時在不同的CPU上運作,包括相同的軟中斷。例如,
- //NET_RX_SOFTIRQ可以同時跑在多個處理器上。
- //local_softirq_pending用于确定目前CPU的所有位圖是否被設定。即是否有軟中斷等待處理。
- //回想一下經常發生的網卡接收資料處理:當網卡中斷落在哪一個CPU上時,與之相應的軟中斷函數就會在其上執行。
- //從這裡來看,實質就是哪個網卡中斷落在相應的CPU上,CPU置其軟中斷位圖,這裡做相應的檢測(這裡local_softirq_pending隻
- //是一個總的判斷,後面還有按位的判斷),檢測到有相應的位,執行之
- if (local_softirq_pending()) {
- //取得線程描述符
- curctx = current_thread_info();
- //構造中斷上下文結構,softirq_ctx是每個CPU的軟中斷上下文
- //static DEFINE_PER_CPU(union irq_ctx *, softirq_ctx);
- //這裡先取得目前CPU的軟中斷上下文,然後為其賦初始值——儲存目前程序和棧指針
- irqctx = __get_cpu_var(softirq_ctx);
- irqctx->tinfo.task = curctx->task;
- irqctx->tinfo.previous_esp = current_stack_pointer;
- //構造中斷棧幀
- isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
- //call_on_stack切換核心棧,并在中斷上下文上執行函數__do_softirq
- call_on_stack(__do_softirq, isp);
- WARN_ON_ONCE(softirq_count());
- }
- //恢複之
- local_irq_restore(flags);
- }
複制代碼 當配置了CONFIG_4KSTACKS,每個程序的thread_union隻有4K,而非8K。發生中斷時,核心棧将不使用程序的核心棧,而使用每個 cpu的中斷請求棧。 核心棧将使用每個 cpu的中斷請求棧,而非程序的核心棧來執行軟中斷函數: - static void call_on_stack(void *func, void *stack)
- {
- asm volatile("xchgl %%ebx,%%esp \n" //交換棧指針,中斷棧幀的指針stack做為傳入參數(%ebx),交換後esp是irq_ctx的棧頂,ebx是程序核心棧的棧
- "call *%%edi \n" //調用軟中斷函數
- "movl %%ebx,%%esp \n" //恢複之,直接使用movl,而非xchgl是因為函數執行完畢,中斷的棧幀指針已經沒有用處了
- : "=b" (stack)
- : "0" (stack),
- "D"(func)
- : "memory", "cc", "edx", "ecx", "eax");
- }
複制代碼 PS:所有的這些執行,應該都是在定義4K棧的基礎上的: - #ifdef CONFIG_4KSTACKS
- union irq_ctx {
- struct thread_info tinfo;
- u32 stack[THREAD_SIZE/sizeof(u32)];
- } __attribute__((aligned(PAGE_SIZE)));
- static DEFINE_PER_CPU(union irq_ctx *, hardirq_ctx);
- static DEFINE_PER_CPU(union irq_ctx *, softirq_ctx);
- ……
- static void call_on_stack(void *func, void *stack)
- ……
複制代碼 是的,這個版本相對複雜,但是如果看了複雜的,再來看簡單的,就容易多了,當平台沒有定義do_softirq函數時(__ARCH_HAS_DO_SOFTIRQ), 核心提供了一個通用的: - #ifndef __ARCH_HAS_DO_SOFTIRQ
- 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);
- }
- #endif
複制代碼 無需更多的解釋,它非常的簡潔。 不論是哪個版本,都将調用__do_softirq函數: - 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);
- //關閉本地CPU下半部。為了保證同一個CPU上的軟中斷以串行方式執行。
- __local_bh_disable((unsigned long)__builtin_return_address(0));
- lockdep_softirq_enter();
- //擷取本地CPU
- cpu = smp_processor_id();
- restart:
- //清除位圖
- set_softirq_pending(0);
- //鎖中斷,隻是為了保持位圖的互斥,位圖處理完畢。後面的代碼可以直接使用儲存的pending,
- //而中斷處理程式在激活的時候,也可以放心地使用irq_stat.__softirq_pending。
- //是以,可以開中斷了
- local_irq_enable();
- //取得軟中斷向量
- h = softirq_vec;
- //循環處理所有的軟中斷
- do {
- //逐漸取位圖的每一位,判斷該位上是否有軟中斷被設定。若有,處理之
- if (pending & 1) {
- //儲存搶占計數器
- int prev_count = preempt_count();
- kstat_incr_softirqs_this_cpu(h - softirq_vec);
- trace_softirq_entry(h, softirq_vec);
- //調用軟中斷
- h->action(h);
- trace_softirq_exit(h, softirq_vec);
- //判斷軟中斷是否被搶占,如果是,則輸出一段錯誤資訊
- if (unlikely(prev_count != preempt_count())) {
- printk(KERN_ERR "huh, entered softirq %td %s %p"
- "with preempt_count %08x,"
- " exited with %08x?\n", h - softirq_vec,
- softirq_to_name[h - softirq_vec],
- h->action, prev_count, preempt_count());
- preempt_count() = prev_count;
- }
- //??qsctr,這個是啥東東
- rcu_bh_qsctr_inc(cpu);
- }
- //指向下一個軟中斷槽位
- h++;
- //移位,取下一個軟中斷位
- pending >>= 1;
- } while (pending);
- //當軟中斷處理完畢後,因為前面已經開了中斷了,是以有可能新的軟中斷已經又被設定,
- //軟中斷排程程式會嘗試重新軟中斷,其最大重新開機次數由max_restart決定。
- //是以,這裡必須再次關閉中斷,再來一次……
- local_irq_disable();
- //取位圖
- pending = local_softirq_pending();
- //有軟中斷被設定,且沒有超過最大重新開機次數,再來一次先
- if (pending && --max_restart)
- goto restart;
- //超過最大重新開機次數,還有軟中斷待處理,調用wakeup_softirqd。其任處是喚醒軟中斷守護程序ksoftirqd。
- if (pending)
- wakeup_softirqd();
- lockdep_softirq_exit();
- account_system_vtime(current);
- //恢複下半部
- _local_bh_enable();
- }
複制代碼 中斷跟蹤 如果中斷跟蹤CONFIG_TRACE_IRQFLAGS被定義,lockdep_softirq_enter/lockdep_softirq_exit用于遞增/遞減目前程序的軟中斷上下文計數器softirq_context: - # define lockdep_softirq_enter() do { current->softirq_context++; } while (0)
- # define lockdep_softirq_exit() do { current->softirq_context--; } while (0)
複制代碼 trace_softirq_entry與trace_softirq_exit配合使用,可以用于判斷軟中斷的延遲。 |