你应该知道Linux内核softirq
說起這個softirq ,很多人還是一頭霧水,覺得這個是什么東西,跟tasklets 和 workqueue有什么不同。
每次談到這個,很多人,包括我,都是有點緊張,特別是面試的時候,因為你一旦說錯了什么,那么你這次面試估計就歇菜了。
談到這個,我們不得不說中斷,中斷處理,我相信很多人都是知道的,中斷分為上半部和下半部,原來的Linux 內(nèi)核是沒有下半部的,中斷來了,就在中斷里處理事件,說白了,就是執(zhí)行一些函數(shù)操作,但是這個會導致一個問題,就是系統(tǒng)調(diào)度慢了,對于用戶來說,你用手機的時候,感覺到十分卡頓,真想摔了這個手機,所以,后來就出現(xiàn)了中斷下半部。
中斷下半部的機制有很多種。例如:softirq、tasklet、workqueue或是直接創(chuàng)建一個kernel thread來執(zhí)行bottom half(這在舊的kernel驅(qū)動中常見,現(xiàn)在,一個理智的driver廠商是不會這么做的)
tasklet 是基于softirq實現(xiàn)的,所以我們討論tasklet的時候,其實也是在說softirq,他們都是運行在中斷上下文的。
workqueue和softirq不同的是,它運行是進程上下文。
為什么需要這么多機制來實現(xiàn)中斷下半部呢?
你可以理解,我如果要去紐約,我可以坐飛機,可以坐輪船,也可以開小汽車,甚至,我還可以騎自行車,中斷下半部也是一樣,tasklet的出現(xiàn),是因為把softirq封裝起來,更加方便別人使用。workqueue的話,緊急程度就沒有softirq那么緊急,可以說優(yōu)先級沒有那么高,如果是非常緊急的事情,比如網(wǎng)絡事件,我們還是優(yōu)先使用softirq來實現(xiàn)。
Linux 內(nèi)核里面可以有多少種softirq呢?
softirq是一種非常緊急的事件,所以說,不是你想用就用的,內(nèi)核里面定義了一個枚舉變量來說明softirq支持的類型。
/* PLEASE, avoid to allocate new softirqs, if you need not _really_ highfrequency 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, /* Unused, but kept as tools rely on the
numbering. Sigh! */
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
看到上面的那段英文了沒,它說,「大部分情況下,tasklets已經(jīng)滿足你的要求,就不要只想著用softirq」
軟中斷是什么時候執(zhí)行的呢?
軟中斷和workqueue存在非常大的區(qū)別,我們在上面說過,軟中斷是在中斷上下文的,但是中斷上半部已經(jīng)脫離了中斷了,它如何跟中斷上下文存在千絲萬縷的聯(lián)系呢?說到這里,我們不拿出代碼來說,那就是耍流氓了。
/** Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
WARN_ON_ONCE(!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! */
}
我們看看irq_exit()這個函數(shù),這個函數(shù)是在什么時候調(diào)用?就是中斷上半部執(zhí)行結(jié)束后,需要跳出中斷上半部的時候,我們需要執(zhí)行irq_exit(),然后在里面有一個判斷。
if (!in_interrupt() && local_softirq_pending())invoke_softirq();
tick_irq_exit();
rcu_irq_exit();
trace_hardirq_exit(); /* must be last! */
}
這幾行代碼就是判斷當前是否需要執(zhí)行軟中斷,說白了,就是CPU需要執(zhí)行一段優(yōu)先級非常高的代碼,這段代碼需要在中斷結(jié)束,關閉中斷后馬上執(zhí)行。
#define local_softirq_pending() this_cpu_read(irq_stat.__softirq_pending)那我們在中斷上半部執(zhí)行結(jié)束后,如何設置需要執(zhí)行softirq呢?使用這個函數(shù)
#define set_softirq_pending(x) __this_cpu_write(irq_stat.__softirq_pending, (x))softirq相關的代碼都在 softirq.c 這個文件里面,如果想有更深入了解的同學,可以一睹源碼風采,不吹,C語言的源碼真是一個寶藏,細細評味,可以挖掘出非常多的好東西。
我們分析下 invoke_softirq
這個函數(shù)是執(zhí)行softirq的函數(shù),里面也有一些判斷的東西,我看了下源碼,理解了下,順便解讀下自己的看法,如果有疑問或者問題的,請讀者們指出來,只有不斷的探討,大家才可能收獲更多的東西。
static inline void invoke_softirq(void){
if (!force_irqthreads) {
#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
/*
* We can safely execute softirq on the current stack if
* it is the irq stack, because it should be near empty
* at this stage.
*/
__do_softirq();
#else
/*
* Otherwise, irq_exit() is called on the task stack that can
* be potentially deep already. So call softirq in its own stack
* to prevent from any overrun.
*/
do_softirq_own_stack();
#endif
} else {
wakeup_softirqd();
}
}
不會看注釋的碼農(nóng)不是好碼農(nóng),不寫注釋的碼農(nóng)就不是碼農(nóng),如果你想寫代碼,就一定要會寫注釋,同時,你也要會看別人的注釋,好吧,這類源碼都是老外寫的,所以我們一定要習慣看英文注釋,慢慢看,不要著急,學會上下文推敲,這樣才像一個大神嘛。
我不是大神,所以,我就瞎說一下上面使用一個 force_irqthreads 來區(qū)分一個東西,就是軟中斷上下文,軟中斷上下文是不能睡眠的,你知道的,你要是在中斷里面睡眠,那系統(tǒng)調(diào)度就起不來了,起不來的原因那可真是五花八門,因為你不知道進入中斷的時候做了什么事情,在中斷里面的時候,我們只有更高優(yōu)先級的中斷才能打斷當前中斷,現(xiàn)在新版本的Linux內(nèi)核取消了中斷嵌套,那你要是在中斷里面睡覺,就沒有人叫你起床了,那就只能出現(xiàn)panic,掛機了。用我的話來說,那就是睡死了。
如果你的softirq里面執(zhí)行很多東西,在軟中斷上文沒有執(zhí)行完,那你就需要用到軟中斷線程把剩下的事情做完,然后就出現(xiàn)了wakeup_softirqd(),這個就是處理軟中斷下半部的。
看看__do_softirq()里面做的事情吧
asmlinkage __visible void __softirq_entry __do_softirq(void){
unsigned long end = jiffies + MAX_SOFTIRQ_TIME; //最大處理時間:2毫秒
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART; //最大回圈次數(shù):10次
struct softirq_action *h;
bool in_hardirq;
__u32 pending;
int softirq_bit;
/*
* Mask out PF_MEMALLOC s 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 swap
*/
current->flags &= ~PF_MEMALLOC;
pending = local_softirq_pending(); //獲取本地CPU上等待處理的軟中斷掩碼
account_irq_enter_time(current);
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
in_hardirq = lockdep_softirq_start();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0); //清除本地CPU上等待處理的軟中斷掩碼
local_irq_enable(); // 開中斷狀態(tài)下處理軟中斷
h = softirq_vec; // h指向軟中斷處理函式陣列首元素
while ((softirq_bit = ffs(pending))) { //依次處理軟中斷,軟中斷編號越小,越優(yōu)先處理,優(yōu)先順序越高
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;
}
rcu_bh_qs();
local_irq_disable(); //關中斷,判斷在處理上次軟中斷期間,硬中斷處理函式是否又排程了軟中斷
pending = local_softirq_pending();
if (pending) { //軟中斷再次被排程
if (time_before(jiffies, end) && !need_resched() &&
--max_restart) //沒有達到超時時間,也不需要被排程,并且排程次數(shù)也沒有超過10次
goto restart; //重新執(zhí)行軟中斷
wakeup_softirqd(); //否則喚醒軟中斷核心執(zhí)行緒處理剩下的軟中斷,當前CPU退出軟中斷上下文
}
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);
}
這段代碼主要是展示了軟中斷上下文的處理策略,一次處理所有等待的軟中斷,處理輪訓結(jié)束或者時間超過2ms,就跳出軟中斷上下文,跑到軟中斷線程里面去執(zhí)行,避免系統(tǒng)響應過慢。
如何加入新的軟中斷?
說了那么多,這個應該是重點了,一直強調(diào),不要加軟中斷,使用tasklet就夠了,但是無法避免就是有人要用啊。
內(nèi)核使用下面函數(shù)來新增一個軟中斷
void open_softirq(int nr, void (*action)(struct softirq_action *)){
softirq_vec[nr].action = action;
}
當然了,你可以新增一個枚舉變量
內(nèi)核里面是這樣使用下面這個函數(shù)調(diào)用
open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);好了,就說這么多~
右下角,你懂的??????*??*??????-??-??????*??*?-??-?
—————END—————
掃碼或長按關注
回復「?加群?」進入技術(shù)群聊
總結(jié)
以上是生活随笔為你收集整理的你应该知道Linux内核softirq的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你知道char *s和char s[]的
- 下一篇: springboot activiti