日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

你应该知道Linux内核softirq

發布時間:2023/12/20 linux 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你应该知道Linux内核softirq 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

說起這個softirq ,很多人還是一頭霧水,覺得這個是什么東西,跟tasklets 和 workqueue有什么不同。

每次談到這個,很多人,包括我,都是有點緊張,特別是面試的時候,因為你一旦說錯了什么,那么你這次面試估計就歇菜了。

談到這個,我們不得不說中斷,中斷處理,我相信很多人都是知道的,中斷分為上半部和下半部,原來的Linux 內核是沒有下半部的,中斷來了,就在中斷里處理事件,說白了,就是執行一些函數操作,但是這個會導致一個問題,就是系統調度慢了,對于用戶來說,你用手機的時候,感覺到十分卡頓,真想摔了這個手機,所以,后來就出現了中斷下半部。

中斷下半部的機制有很多種。例如:softirq、tasklet、workqueue或是直接創建一個kernel thread來執行bottom half(這在舊的kernel驅動中常見,現在,一個理智的driver廠商是不會這么做的

tasklet 是基于softirq實現的,所以我們討論tasklet的時候,其實也是在說softirq,他們都是運行在中斷上下文的。

workqueue和softirq不同的是,它運行是進程上下文。

為什么需要這么多機制來實現中斷下半部呢?

你可以理解,我如果要去紐約,我可以坐飛機,可以坐輪船,也可以開小汽車,甚至,我還可以騎自行車,中斷下半部也是一樣,tasklet的出現,是因為把softirq封裝起來,更加方便別人使用。workqueue的話,緊急程度就沒有softirq那么緊急,可以說優先級沒有那么高,如果是非常緊急的事情,比如網絡事件,我們還是優先使用softirq來實現。

Linux 內核里面可以有多少種softirq呢?

softirq是一種非常緊急的事件,所以說,不是你想用就用的,內核里面定義了一個枚舉變量來說明softirq支持的類型。

/* 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, /* Unused, but kept as tools rely on the
numbering. Sigh! */
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS
};

看到上面的那段英文了沒,它說,「大部分情況下,tasklets已經滿足你的要求,就不要只想著用softirq

軟中斷是什么時候執行的呢?

軟中斷和workqueue存在非常大的區別,我們在上面說過,軟中斷是在中斷上下文的,但是中斷上半部已經脫離了中斷了,它如何跟中斷上下文存在千絲萬縷的聯系呢?說到這里,我們不拿出代碼來說,那就是耍流氓了。

/*
* 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()這個函數,這個函數是在什么時候調用?就是中斷上半部執行結束后,需要跳出中斷上半部的時候,我們需要執行irq_exit(),然后在里面有一個判斷。

if (!in_interrupt() && local_softirq_pending())
invoke_softirq();

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

這幾行代碼就是判斷當前是否需要執行軟中斷,說白了,就是CPU需要執行一段優先級非常高的代碼,這段代碼需要在中斷結束,關閉中斷后馬上執行。

#define local_softirq_pending() this_cpu_read(irq_stat.__softirq_pending)

那我們在中斷上半部執行結束后,如何設置需要執行softirq呢?使用這個函數

#define set_softirq_pending(x) __this_cpu_write(irq_stat.__softirq_pending, (x))

softirq相關的代碼都在 softirq.c 這個文件里面,如果想有更深入了解的同學,可以一睹源碼風采,不吹,C語言的源碼真是一個寶藏,細細評味,可以挖掘出非常多的好東西。

我們分析下 invoke_softirq

這個函數是執行softirq的函數,里面也有一些判斷的東西,我看了下源碼,理解了下,順便解讀下自己的看法,如果有疑問或者問題的,請讀者們指出來,只有不斷的探討,大家才可能收獲更多的東西。

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

不會看注釋的碼農不是好碼農,不寫注釋的碼農就不是碼農,如果你想寫代碼,就一定要會寫注釋,同時,你也要會看別人的注釋,好吧,這類源碼都是老外寫的,所以我們一定要習慣看英文注釋,慢慢看,不要著急,學會上下文推敲,這樣才像一個大神嘛。

我不是大神,所以,我就瞎說一下上面使用一個 force_irqthreads 來區分一個東西,就是軟中斷上下文,軟中斷上下文是不能睡眠的,你知道的,你要是在中斷里面睡眠,那系統調度就起不來了,起不來的原因那可真是五花八門,因為你不知道進入中斷的時候做了什么事情,在中斷里面的時候,我們只有更高優先級的中斷才能打斷當前中斷,現在新版本的Linux內核取消了中斷嵌套,那你要是在中斷里面睡覺,就沒有人叫你起床了,那就只能出現panic,掛機了。用我的話來說,那就是睡死了。

如果你的softirq里面執行很多東西,在軟中斷上文沒有執行完,那你就需要用到軟中斷線程把剩下的事情做完,然后就出現了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; //最大回圈次數: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(); // 開中斷狀態下處理軟中斷

h = softirq_vec; // h指向軟中斷處理函式陣列首元素

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

rcu_bh_qs();
local_irq_disable(); //關中斷,判斷在處理上次軟中斷期間,硬中斷處理函式是否又排程了軟中斷

pending = local_softirq_pending();
if (pending) { //軟中斷再次被排程
if (time_before(jiffies, end) && !need_resched() &&
--max_restart) //沒有達到超時時間,也不需要被排程,并且排程次數也沒有超過10次
goto restart; //重新執行軟中斷

wakeup_softirqd(); //否則喚醒軟中斷核心執行緒處理剩下的軟中斷,當前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);
}

這段代碼主要是展示了軟中斷上下文的處理策略,一次處理所有等待的軟中斷,處理輪訓結束或者時間超過2ms,就跳出軟中斷上下文,跑到軟中斷線程里面去執行,避免系統響應過慢。

如何加入新的軟中斷?

說了那么多,這個應該是重點了,一直強調,不要加軟中斷,使用tasklet就夠了,但是無法避免就是有人要用啊。

內核使用下面函數來新增一個軟中斷

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

當然了,你可以新增一個枚舉變量

內核里面是這樣使用下面這個函數調用

open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);

好了,就說這么多~

右下角,你懂的??????*??*??????-??-??????*??*?-??-?

—————END—————

掃碼或長按關注

回復「?加群?」進入技術群聊

總結

以上是生活随笔為你收集整理的你应该知道Linux内核softirq的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。