Linux 網路協議棧之核心鎖(二) 核心搶占

2021-08-15 10:09:03 字數 4071 閱讀 8022

一、核心搶占

早期的linux核心是不可搶占的。它的排程方法是:乙個程序可以通過schedule()函式自願地啟動一次排程。非自願的強制性排程只能發生在每次從系統呼叫返回的前夕以及每次從中斷或異常處理返回到使用者空間的前夕。但是,如果在系統空間發生中斷或異常是不會引起排程的。這種方式使核心實現得以簡化。但常存在下面兩個問題:

1、如果這樣的中斷發生在核心中,本次中斷返回是不會引起排程的,而要到最初使cpu從使用者空間進入核心空間的那次系統呼叫或中斷(異常)返回時才會發生排程

2、另外乙個問題是優先順序反轉。在linux中,在核心態執行的任何操作都要優先於使用者態程序,這就有可能導致優先順序反轉問題的出現。例如,乙個低優先順序的使用者程序由於執行軟/硬中斷等原因而導致乙個高優先順序的任務得不到及時響應。

當前的linux核心加入了核心搶占(preempt)機制。核心搶占指使用者程式在執行系統呼叫期間可以被搶占,該程序暫時掛起,使新喚醒的高優先順序程序能夠執行。這種搶占並非可以在核心中任意位置都能安全進行,比如在臨界區中的**就不能發生搶占。臨界區是指同一時間內不可以有超過乙個程序在其中執行的指令序列。在linux核心中這些部分需要用自旋鎖保護

核心搶占要求核心中所有可能為乙個以上程序共享的變數和資料結構就都要通過互斥機制加以保護,或者說都要放在臨界區中。在搶占式核心中,認為如果核心不是在乙個中斷處理程式中,並且不在被 spinlock等互斥機制保護的臨界**中,就認為可以"安全"地進行程序切換

linux核心將臨界**都加了互斥機制進行保護,同時,還在執行時間過長的**路徑上插入排程檢查點,打斷過長的執行路徑,這樣,任務可快速切換程序狀態,也為核心搶占做好了準備。

linux核心搶占只有在核心正在執行例外處理程式(通常指系統呼叫)並且允許核心搶占時,才能進行搶占核心。禁止核心搶占的情況列出如下:

(1)核心執行中斷處理例程時不允許核心搶占,中斷返回時再執行核心搶占。

(2)當核心執行軟中斷或tasklet時,禁止核心搶占,軟中斷返回時再執行核心搶占。

(3)在臨界區禁止核心搶占,臨界區保護函式通過搶占計數巨集控制搶占,計數大於0,表示禁止核心搶占。

搶占式核心實現的原理是在釋放自旋鎖時或從中斷返回時,如果當前執行程序的 need_resched 被標記,則進行搶占式排程

struct thread_info

核心排程器的入口為preempt_schedule(),他將當前程序標記為task_preempted狀態再呼叫schedule(),在task_preempted狀態,schedule()不會將程序從執行佇列中刪除。

二、核心搶占api函式

在中斷或臨界區**中,執行緒需要關閉核心搶占,因此,互斥機制(如:自旋鎖(spinlock)、rcu等)、中斷**、鍊錶資料遍歷等需要關閉核心搶占,臨界**執行完時,需要開啟核心搶占。關閉/開啟核心搶占需要使用核心搶占api函式preempt_disable和 preempt_enable。

核心搶占api函式說明如下(在include/linux/preempt.h中):

#if defined(config_debug_preempt) || defined(config_preempt_tracer)

extern void preempt_count_add(int val);

extern void preempt_count_sub(int val);

#define preempt_count_dec_and_test() ()

#else

#define preempt_count_add(val) __preempt_count_add(val)

#define preempt_count_sub(val) __preempt_count_sub(val)

#define preempt_count_dec_and_test() __preempt_count_dec_and_test()

#endif

#define __preempt_count_inc() __preempt_count_add(1)

#define __preempt_count_dec() __preempt_count_sub(1)

#define preempt_count_inc() preempt_count_add(1)

#define preempt_count_dec() preempt_count_sub(1)

#ifdef config_preempt_count

核心搶占api函式的實現巨集定義列出如下(在include/linux/preempt.h中):

#define preempt_enable() \

do while (0)

#define preempt_check_resched() \

do while (0)

#else

#define preempt_enable() \

do while (0)

#define preempt_check_resched() do while (0)

#endif

#define preempt_disable_notrace() \

do while (0)

#define preempt_enable_no_resched_notrace() \

do while (0)

核心搶占排程

linux核心在硬中斷或軟中斷返回時會檢查執行搶占排程。分別說明如下:

1)硬中斷返回執行搶占排程

linux核心在硬中斷或出錯退出時執行函式retint_kernel,執行搶占函式,函式retint_kernel列出如下(在arch/x86/entry_64.s中):

#ifdef config_preempt entry(retint_kernel) cmpl $0,threadinfo_preempt_count(%rcx) jnz retint_restore_args bt $tif_need_resched,threadinfo_flags(%rcx) jnc retint_restore_args bt $9,eflags-argoffset(%rsp) jnc retint_restore_args call preempt_schedule_irq jmp exit_intr #endif

函式preempt_schedule_irq是出中斷上下文時核心搶占排程的入口點,該函式被呼叫和返回時中斷應關閉,保護此函式從中斷遞迴呼叫。該函式列出如下(在kernel/sched、core.c中):

asmlinkage void __sched preempt_schedule_irq(void)

while (need_resched());

exception_exit(prev_state);

}

排程函式schedule會檢測程序的 preempt_counter 是否很大,避免普通排程時又執行核心搶占排程。

2)軟中斷返回執行搶占排程

在開啟頁出錯函式pagefault_enable和軟中斷底半部開啟函式local_bh_enable中,會呼叫函式 preempt_check_resched檢查是否需要執行核心搶占。如果不是並能排程,程序才可執行核心搶占排程。函式 preempt_check_resched列出如下:

#define preempt_check_resched() \

do while (0)

函式preempt_schedule源**與函式preempt_schedule_irq基本上一樣,對程序進行排程,這裡不再分析

linux核心協議棧 鄰居協議概述

1 什麼是鄰居協議?ndp neighbor discovery protocol,鄰居發現協議 是ipv6的乙個關鍵協議,它組合了ipv4中的arp icmp路由器發現和icmp重定向等協議,並對它們作了改進。作為ipv6的基礎性協議,ndp還提供了字首發現 鄰居不可達檢測 重複位址監測 位址自動...

linux核心協議棧 TCP資料傳送之傳送視窗

目錄 1 傳送視窗概述 2 snd una 和 snd wnd 的更新 2.1 傳送視窗初始化 2.1.1 客戶端初始化 2.1.2 伺服器端初始化 2.2 本地接收視窗 rcv wnd 通告 2.2.1 客戶端傳送 2.2.2 伺服器傳送 2.3 傳輸過程中更新傳送視窗 2.3.1 傳送視窗更新條...

Linux核心之RCU鎖

rcu全程read only update rcu機制 rcu記錄了所有指向共享資料的指標的使用者,當要修改共享資料的時候,首先建立乙個副本,在副本中修改。所有讀執行緒都離開讀臨界區之後,指標指向新的修改後副本的指標,並且刪除舊資料。include include include include i...