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

歡迎訪問 生活随笔!

生活随笔

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

linux

Linux内核态抢占机制分析

發布時間:2023/12/15 linux 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux内核态抢占机制分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html

?

?

【摘要】本文首先介紹非搶占式內核(Non-Preemptive Kernel)和可搶占式內核(Preemptive Kernel)的區別。接著分析Linux下有兩種搶占:用戶態搶占(User Preemption)、內核態搶占(Kernel Preemption)。然后分析了在內核態下:如何判斷能否搶占內核(什么是可搶占的條件);何時觸發重新調度(何時設置可搶占條件);搶占發生的時機(何時檢查可搶占的條件);什么時候不能搶占內核。最后分析了2.6kernel中如何支持搶占內核。

?

【關鍵字】內核態搶占 用戶態搶占 中斷 實時性 自旋鎖 linux kernel schedule preemption reentrant

?

?

?

1.非搶占式和可搶占式內核的區別

?

為了簡化問題,我使用嵌入式實時系統uC/OS作為例子。首先要指出的是,uC/OS只有內核態,沒有用戶態,這和Linux不一樣。

?

多任務系統中,內核負責管理各個任務,或者說為每個任務分配CPU時間,并且負責任務之間的通訊。內核提供的基本服務是任務切換。調度 (Scheduler),英文還有一詞叫dispatcher,也是調度的意思。這是內核的主要職責之一,就是要決定該輪到哪個任務運行了。多數實時內核 是基于優先級調度法的。每個任務根據其重要程度的不同被賦予一定的優先級。基于優先級的調度法指,CPU總是讓處在就緒態的優先級最高的任務先運行。然 而,究竟何時讓高優先級任務掌握CPU的使用權,有兩種不同的情況,這要看用的是什么類型的內核,是不可剝奪型的還是可剝奪型內核。

?

?

?

非搶占式內核

?

非搶占式內核是由任務主動放棄CPU的使用權。非搶占式調度法也稱作合作型多任務,各個任務彼此合作共享一個CPU。異步事件還是由中斷服務來處理。中斷 服務可以使一個高優先級的任務由掛起狀態變為就緒狀態。但中斷服務以后控制權還是回到原來被中斷了的那個任務,直到該任務主動放棄CPU的使用權時,那個 高優先級的任務才能獲得CPU的使用權。非搶占式內核如下圖所示。

?

?

?

?

非搶占式內核的優點有:

?

·中斷響應快(與搶占式內核比較);

?

·允許使用不可重入函數;

?

·幾乎不需要使用信號量保護共享數據。運行的任務占有CPU,不必擔心被別的任務搶占。這不是絕對的,在打印機的使用上,仍需要滿足互斥條件。

?

非搶占式內核的缺點有:

?

·任務響應時間慢。高優先級的任務已經進入就緒態,但還不能運行,要等到當前運行著的任務釋放CPU。

?

·非搶占式內核的任務級響應時間是不確定的,不知道什么時候最高優先級的任務才能拿到CPU的控制權,完全取決于應用程序什么時候釋放CPU。

?

?

?

搶占式內核

?

使用搶占式內核可以保證系統響應時間。最高優先級的任務一旦就緒,總能得到CPU的使用權。當一個運行著的任務使一個比它優先級高的任務進入了就緒態,當 前任務的CPU使用權就會被剝奪,或者說被掛起了,那個高優先級的任務立刻得到了CPU的控制權。如果是中斷服務子程序使一個高優先級的任務進入就緒態, 中斷完成時,中斷了的任務被掛起,優先級高的那個任務開始運行。搶占式內核如下圖所示。

?


?

搶占式內核的優點有:

?

·使用搶占式內核,最高優先級的任務什么時候可以執行,可以得到CPU的使用權是可知的。使用搶占式內核使得任務級響應時間得以最優化。

?

搶占式內核的缺點有:

?

·不能直接使用不可重入型函數。調用不可重入函數時,要滿足互斥條件,這點可以使用互斥型信號量來實現。如果調用不可重入型函數時,低優先級的任務CPU的使用權被高優先級任務剝奪,不可重入型函數中的數據有可能被破壞。

?

?

?

2.Linux下的用戶態搶占和內核態搶占

?

?

?

Linux除了內核態外還有用戶態。用戶程序的上下文屬于用戶態,系統調用和中斷處理例程上下文屬于內核態。在2.6 kernel以前,Linux kernel只支持用戶態搶占。

?

2.1?用戶態搶占(User Preemption)

?

在kernel返回用戶態(user-space)時,并且need_resched標志為1時,scheduler被調用,這就是用戶態搶占。當 kernel返回用戶態時,系統可以安全的執行當前的任務,或者切換到另外一個任務。當中斷處理例程或者系統調用完成后,kernel返回用戶態 時,need_resched標志的值會被檢查,假如它為1,調度器會選擇一個新的任務并執行。中斷和系統調用的返回路徑(return path)的實現在entry.S中(entry.S不僅包括kernel entry code,也包括kernel exit code)。

?

2.2?內核態搶占(Kernel Preemption)

?

在2.6 kernel以前,kernel code(中斷和系統調用屬于kernel code)會一直運行,直到code被完成或者被阻塞(系統調用可以被阻塞)。在 2.6 kernel里,Linux kernel變成可搶占式。當從中斷處理例程返回到內核態(kernel-space)時,kernel會檢查是否可以搶占和是否需要重新調度。 kernel可以在任何時間點上搶占一個任務(因為中斷可以發生在任何時間點上),只要在這個時間點上kernel的狀態是安全的、可重新調度的。

?

?

?

3.內核態搶占的設計

?

3.1?可搶占的條件

?

要滿足什么條件,kernel才可以搶占一個任務的內核態呢?

?

·沒持有鎖。鎖是用于保護臨界區的,不能被搶占。

?

·Kernel code可重入(reentrant)。因為kernel是SMP-safe的,所以滿足可重入性。

?

如何判斷當前上下文(中斷處理例程、系統調用、內核線程等)是沒持有鎖的?Linux在每個每個任務的thread_info結構中增加了preempt_count變量作為preemption的計數器。這個變量初始為0,當加鎖時計數器增一,當解鎖時計數器減一。

?

3.2?內核態需要搶占的觸發條件

?

內核提供了一個need_resched標志(這個標志在任務結構thread_info中)來表明是否需要重新執行調度。

?

3.3?何時觸發重新調度

?

set_tsk_need_resched():設置指定進程中的need_resched標志

?

clear_tsk need_resched():清除指定進程中的need_resched標志

?

need_resched():檢查need_ resched標志的值;如果被設置就返回真,否則返回假

?

什么時候需要重新調度:

?

·時鐘中斷處理例程檢查當前任務的時間片,當任務的時間片消耗完時,scheduler_tick()函數就會設置need_resched標志;

?

·信號量、等到隊列、completion等機制喚醒時都是基于waitqueue的,而waitqueue的喚醒函數為default_wake_function,其調用try_to_wake_up將被喚醒的任務更改為就緒狀態并設置need_resched標志。

?

·設置用戶進程的nice值時,可能會使高優先級的任務進入就緒狀態;

?

·改變任務的優先級時,可能會使高優先級的任務進入就緒狀態;

?

·新建一個任務時,可能會使高優先級的任務進入就緒狀態;

?

·對CPU(SMP)進行負載均衡時,當前任務可能需要放到另外一個CPU上運行;

?

3.4?搶占發生的時機(何時檢查可搶占條件)

?

·當一個中斷處理例程退出,在返回到內核態時(kernel-space)。這是隱式的調用schedule()函數,當前任務沒有主動放棄CPU使用權,而是被剝奪了CPU使用權。

?

·當kernel code從不可搶占狀態變為可搶占狀態時(preemptible again)。也就是preempt_count從正整數變為0時。這也是隱式的調用schedule()函數。

?

·一個任務在內核態中顯式的調用schedule()函數。任務主動放棄CPU使用權。

?

·一個任務在內核態中被阻塞,導致需要調用schedule()函數。任務主動放棄CPU使用權。

?

3.5?禁用/使能可搶占條件的操作

?

對preempt_count操作的函數有add_preempt_count()、sub_preempt_count()、inc_preempt_count()、dec_preempt_count()。

?

使能可搶占條件的操作是preempt_enable(),它調用dec_preempt_count()函數,然后再調用preempt_check_resched()函數去檢查是否需要重新調度。

?

禁用可搶占條件的操作是preempt_disable(),它調用inc_preempt_count()函數。

?

在內核中有很多函數調用了preempt_enable()和preempt_disable()。比如spin_lock()函數調用了preempt_disable()函數,spin_unlock()函數調用了preempt_enable()函數。

?

3.6?什么時候不允許搶占

?

preempt_count()函數用于獲取preempt_count的值,preemptible()用于判斷內核是否可搶占。

?

有幾種情況Linux內核不應該被搶占,除此之外,Linux內核在任意一點都可被搶占。這幾種情況是:

?

·內核正進行中斷處理。在Linux內核中進程不能搶占中斷(中斷只能被其他中斷中止、搶占,進程不能中止、搶占中斷),在中斷例程中不允許進行進程調度。進程調度函數schedule()會對此作出判斷,如果是在中斷中調用,會打印出錯信息。

?

·內核正在進行中斷上下文的Bottom Half(中斷的下半部)處理。硬件中斷返回前會執行軟中斷,此時仍然處于中斷上下文中。

?

·內核的代碼段正持有spinlock自旋鎖、writelock/readlock讀寫鎖等鎖,處干這些鎖的保護狀態中。內核中的這些鎖是為了在SMP 系統中短時間內保證不同CPU上運行的進程并發執行的正確性。當持有這些鎖時,內核不應該被搶占,否則由于搶占將導致其他CPU長期不能獲得鎖而死等。

?

·內核正在執行調度程序Scheduler。搶占的原因就是為了進行新的調度,沒有理由將調度程序搶占掉再運行調度程序。

?

·內核正在對每個CPU“私有”的數據結構操作(Per-CPU date structures)。在SMP中,對于per-CPU數據結構未用spinlocks保護,因為這些數據結構隱含地被保護了(不同的CPU有不一樣的 per-CPU數據,其他CPU上運行的進程不會用到另一個CPU的per-CPU數據)。但是如果允許搶占,但一個進程被搶占后重新調度,有可能調度到 其他的CPU上去,這時定義的Per-CPU變量就會有問題,這時應禁搶占。

?

?

?

4.Linux內核態搶占的實現

?

4.1?數據結構

?

在thread_info.h中

?

?

?

1. struct?thread_info?{

?

2. ????struct?task_struct??*task;

?

3. ????struct?exec_domain??*exec_domain;

?

4. ????__u32???????????flags;

?

5. ????__u32???????????status;

?

6. ????__u32???????????cpu;

?

7. ????int?????????preempt_count;

?

9. ????mm_segment_t????????addr_limit;

?

10.? ????struct?restart_block????restart_block;

?

11.? ????void?__user?????*sysenter_return;

?

12.? #ifdef?CONFIG_X86_32

?

13.? ????unsigned?long???????????previous_esp;

?

16.? ????__u8????????????supervisor_stack[0];

?

17.? #endif

?

18.? };

?

4.2?代碼流程

?

禁用/使能可搶占條件的函數

?

?

?

1. #if?defined(CONFIG_DEBUG_PREEMPT)?||?defined(CONFIG_PREEMPT_TRACER)

?

2.

?

3. extern?void?add_preempt_count(int?val);

?

4.

?

5. extern?void?sub_preempt_count(int?val);

?

6.

?

7. #else

?

8.

?

9. #?define?add_preempt_count(val)?do?{?preempt_count()?+=?(val);?}?while?(0)

?

10.

?

11.? #?define?sub_preempt_count(val)?do?{?preempt_count()?-=?(val);?}?while?(0)

?

12.

?

13.? #endif

?

14.

?

15.? #define?inc_preempt_count()?add_preempt_count(1)

?

16.

?

17.? #define?dec_preempt_count()?sub_preempt_count(1)

?

18.

?

19.? #define?preempt_count()?(current_thread_info()->preempt_count)

?

20.

?

21.? #define?preempt_disable()?\

?

22.

?

23.? do?{?\

?

24.

?

25.? ????inc_preempt_count();?\

?

26.

?

27.? ????barrier();?\

?

28.

?

29.? }?while?(0)

?

30.

?

31.? #define?preempt_enable_no_resched()?\

?

32.

?

33.? do?{?\

?

34.

?

35.? ????barrier();?\

?

36.

?

37.? ????dec_preempt_count();?\

?

38.

?

39.? }?while?(0)

?

40.

?

41.? #define?preempt_check_resched()?\

?

42.

?

43.? do?{?\

?

44.

?

45.? <pre?name="code"?class="cpp">????if?(unlikely(test_thread_flag(TIF_NEED_RESCHED)))?\

?

46.

?

47.? <pre?name="code"?class="cpp"><pre?name="code"?class="cpp">????preempt_schedule();?\

?

48.

?

49.? }?while?(0)

?

50.

?

51.? #define?preempt_enable()?\

?

52.

?

53.? do?{?\

?

54.

?

55.? ????preempt_enable_no_resched();?\

?

56.

?

57.? ????barrier();?\

?

58.

?

59.? ????preempt_check_resched();?\

?

60.

?

61.? }?while?(0)

?

檢查可搶占條件

?

?

?

1. #?define?preemptible()?(preempt_count()?==?0?&&?!irqs_disabled())

?

2.

?

自旋鎖的加鎖與解鎖

?

?

?

1. void?__lockfunc?_spin_lock(spinlock_t?*lock)

?

2. {

?

3. ????preempt_disable();

?

4. ????spin_acquire(&lock->dep_map,?0,?0,?_RET_IP_);

?

5. ????LOCK_CONTENDED(lock,?_raw_spin_trylock,?_raw_spin_lock);

?

6. }

?

7.

?

8. void?__lockfunc?_spin_unlock(spinlock_t?*lock)

?

9. {

?

10.? ????spin_release(&lock->dep_map,?1,?_RET_IP_);

?

11.? ????_raw_spin_unlock(lock);

?

12.? ????preempt_enable();

?

13.? }

?

設置need_resched標志的函數

?

?

?

1. static?inline?void?set_tsk_need_resched(struct?task_struct?*tsk)

?

2. {

?

3. ????set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);

?

4. }

?

5.

?

6. static?inline?void?clear_tsk_need_resched(struct?task_struct?*tsk)

?

7. {

?

8. ????clear_tsk_thread_flag(tsk,TIF_NEED_RESCHED);

?

9. }

?

10.

?

11.? static?inline?int?test_tsk_need_resched(struct?task_struct?*tsk)

?

12.? {

?

13.? ????return?unlikely(test_tsk_thread_flag(tsk,TIF_NEED_RESCHED));

?

14.? }

?



時鐘中斷時調用的task_tick()函數,當時間片消耗完之后,設置need_resched標志

?

?

?

1. static?void?task_tick_rt(struct?rq?*rq,?struct?task_struct?*p,?int?queued)

?

2. {

?

3. ????update_curr_rt(rq);

?

4.

?

5. ????watchdog(rq,?p);

?

6.

?

7.

?

11.? ????if?(p->policy?!=?SCHED_RR)

?

12.? ????????return;

?

13.

?

14.? ????if?(--p->rt.time_slice)

?

15.? ????????return;

?

16.

?

17.? ????p->rt.time_slice?=?DEF_TIMESLICE;

?

18.

?

19.

?

23.? ????if?(p->rt.run_list.prev?!=?p->rt.run_list.next)?{

?

24.? ????????requeue_task_rt(rq,?p,?0);

?

25.? ????????set_tsk_need_resched(p);

?

26.? ????}

?

27.? }

?

?

?

設置任務的need_resched標志,并觸發任務所在CPU的調度器。

?

?

?

1. static?void?resched_task(struct?task_struct?*p)

?

2. {

?

3. ????int?cpu;

?

4.

?

5. ????assert_spin_locked(&task_rq(p)->lock);

?

6.

?

7. ????if?(unlikely(test_tsk_thread_flag(p,?TIF_NEED_RESCHED)))

?

8. ????????return;

?

9.

?

10.? ????set_tsk_thread_flag(p,?TIF_NEED_RESCHED);

?

11.

?

12.? ????cpu?=?task_cpu(p);

?

13.? ????if?(cpu?==?smp_processor_id())

?

14.? ????????return;

?

15.

?

16.

?

17.? ????smp_mb();

?

18.? ????if?(!tsk_is_polling(p))

?

19.? ????????smp_send_reschedule(cpu);

?

20.? }

?

5.參考資料

?

http://blog.csdn.net/sailor_8318/archive/2008/09/03/2870184.aspx

?

《uC/OS-II源碼公開的嵌入式實時多任務操作系統內核》

?

Linux 2.6.29內核源碼

?

總結

以上是生活随笔為你收集整理的Linux内核态抢占机制分析的全部內容,希望文章能夠幫你解決所遇到的問題。

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