Linux程式分析
多工系統可以分為:非搶佔式和搶佔式。Linux提供搶佔式多工模式。程式在被搶佔之前能夠執行的時間叫程式的時間片,Linux獨一無二的公平排程程式本身並沒有採用時間片來達到公平排程。
Linux之前採用O(1)排程器,它對大伺服器的工作負載很理想,但是對響應時間敏感的程式卻有不足。在2.6.23核心版本中用完全公平排程演算法(CFS)代替了O(1)排程演算法。
程式可以被分為I/O消耗型和處理器消耗型。I/O消耗型指程式的大部分時間用來提交I/O請求或是等待I/O請求。處理器消耗型指程式把事件大多數用在執行程式碼上。排程策略通常在兩個矛盾中尋找平衡:程式響應迅速和最大系統利用率。Linux更傾向於優先排程I/O消耗型程式。
排程程式總是選擇時間片未用盡而且優先順序最高的程式執行。
Linux採用兩種不同的優先順序範圍。第一種是nice值,越大的nice值意味著更低的優先順序。第二種是實時優先順序,其值可以配置,越高的實時優先順序數值意味著程式優先順序越高。任何實時程式的優先順序都高於普通程式。
CFS做法:允許每個程式執行一段時間,迴圈輪轉,選擇執行最少的程式作為下一個執行程式,而不再採用分配給每一個程式時間片的做法,CFS在所有可執行程式總數基礎上計算出一個程式應該執行多久,而不是依靠nice值來計算時間片。CFS引入每個程式獲得的時間片底線(最小粒度)預設情況為1ms,絕對的nice值不再影響排程決策,任何程式所獲得的處理器時間由它自己和其他所有可執行程式nice值的相對差決定。
Linux排程的實現:
時間記賬
排程器實體結構是struct sched_entity,它嵌入在程式描述符struct task_struct中。虛擬時間以ns為單位,存在vruntime變數中,它和定時器節拍不再相關,vruntime變數可以準確測定程式的執行時間,而且可以知道誰應該是下一個被執行的程式。
程式選擇
當CFS需要選擇下一個執行程式時,它會挑一個具有最小vruntime的程式,這就是CFS排程演算法的核心:選擇具有最小的vruntime的任務。這裡採用紅黑樹實現的。
排程器入口
程式排程的主要入口點是schedule函式,該函式會以優先順序為序,從高到低,依次檢查每一個排程器,並且從最高優先順序的排程器中,選擇最高優先順序的程式。
睡眠和喚醒
對於睡眠,核心的操作是:程式把自己標記為休眠狀態,從可執行紅黑樹中移出,放入等待佇列,然後呼叫schedule函式選擇和執行一個其他程式。對於喚醒,正好相反,程式被設定為可執行狀態,然後再從等待佇列中移到可執行紅黑樹中。
休眠有兩種程式狀態:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE。它們唯一的區別在於處在TASK_UNINTERRUPTIBLE的程式會忽略訊號,處於TASK_INTERRUPTIBLE狀態的程式如果接收到一個訊號,會被提前喚醒並響應該訊號。兩種狀態的程式位於同一個等待佇列上,等待某些事情,不能夠執行。
關於休眠還需要注意一點:存在虛假的喚醒,所以有時候需要一個迴圈處理來保證程式被喚醒的條件真正大達成。
等待佇列的使用
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //定義等待佇列頭
wait_event_interruptible(button_waitq, ev_press); //休眠
wake_up_interruptible(&button_waitq); //喚醒
搶佔和上下文切換
上下文切換就是從一個可執行程式切換到另一個可執行程式。Schedule函式主要完成兩個工作:其一,把虛擬記憶體從上一個程式對映切換到新程式中。其二,從上一個程式的處理器狀態切換到新程式的處理器狀態。
核心必須知道在什麼時候呼叫schedule函式,如何僅靠使用者程式程式碼顯式地呼叫schedule,它們可能就會永遠的執行下去,所以核心中提供了一個need_resched標誌來表明是否需要重新執行一次排程,該標誌對於核心來講是一個資訊,它表示有其他程式應當被執行了,要儘快呼叫排程程式。
使用者搶佔
核心即將返回使用者空間的時候,如果need_resched標誌被設定,會導致schedule函式被呼叫,此時就會發生使用者搶佔。使用者搶佔在如下情況時產生:其一,從系統呼叫返回使用者空間時。其二,從中斷處理程式返回使用者空間時。
核心搶佔
在2.6版核心中,核心引入搶佔能力,只要重新排程是安全的,即只要沒有持有鎖,核心就可以在任何時候搶佔正在執行的任務。為了支援核心搶佔,在每個程式的thread_info引入preempt_count計數器,當做一把鎖來使用。核心搶佔在如下情況時產生:其一,中斷處理程式正在執行,且返回核心空間之前。其二,核心程式碼再一次具有可搶佔性的時候。其三,如果核心中的任務顯式地呼叫schedule函式。其四,如果核心中的任務阻塞(這樣會呼叫schedule函式)。
程式是正在執行的程式程式碼的實時結果,是處於執行期的程式以及相關的資源的總稱。執行緒是在程式中活動的物件,核心排程的物件是執行緒,而不是程式。對Linux系統而言,不區分執行緒和程式。程式提供兩種虛擬機器制:虛擬處理器和虛擬記憶體。執行緒之間可以共享記憶體,但每個都擁有各自的虛擬處理器。在Linux中,通過fork建立子程式,子程式通過exit()系統呼叫終結程式並將其佔用的資源釋放掉,此時子程式被設定為僵死狀態,父程式可以通過wait()或waitpid()系統呼叫查詢子程式是否終結。
核心把程式的列表存放在叫任務佇列(task_list)的雙向迴圈列表中,連結串列中的每一個項都是型別為task_struct,稱為程式描述符。Linux通過slab分配器分配task_struct結構,由於現在用的slab分配器動態生成task_struct,所以只需要在棧低或者棧頂建立一個新的結構體struct thread_info。核心通過程式標識值PID來標識每個程式,核心把每個程式的PID存放在它們各自的程式描述符中,所有的程式都是PID為1的init程式的後代,PID最大預設值為32768,這個值越小轉一週就越快,系統管理員可以通過修改/proc/sys/kennel/pid_max來提高上限。
程式五種狀態標誌:TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UN INTERRUPTIBLE、_TASK_TRACED、_TASK_STOPPED。
程式上下文
一般程式在使用者空間執行,當一個程式執行了一個系統呼叫或者觸發了某個異常,它就陷入核心空間,此時,我們稱核心“代表程式執行”並處於程式上下文中。除非在此間隙有更高優先順序的程式需要執行並且由排程器做出了相應的調整,否則在核心退出的時候,程式恢復在使用者空間會繼續執行。系統呼叫和異常處理程式是對核心明確定義的介面,對核心的所有訪問都必須通過這些介面。
程式建立
子程式和父程式區別僅僅在於PID(每個程式唯一)、PPID(父程式的程式號,子程式將其設定為被拷貝程式的PID)和某些資源和統計量(如掛起的訊號)。Linux的fork()使用寫時拷貝頁實現,寫時拷貝頁是一種可以推遲甚至免除拷貝資料的技術,核心此時並不是賦值整個程式地址空間,而是讓父程式和子程式共享同一個拷貝。fork()的實際開銷就是賦值父程式的頁表以及給子程式建立唯一的程式描述符。核心有意會選擇子程式先執行,因為一般子程式都會馬上呼叫exec()函式,這樣避免了寫時拷貝頁的額外開銷,如果父程式先執行的話,有肯能會開始向地址空間寫入。Vfork()除了不拷貝父程式的頁表項外,其他跟fork一樣,由於vfork語意微妙,系統最好不要用這個函式。
執行緒建立
使用者空間建立執行緒可以用clone函式建立,但我們的重點是核心執行緒,核心常常需要在後臺執行一些操作,這種任務可以通過核心執行緒完成。核心執行緒和普通程式的區別在於核心執行緒沒有獨立的地址空間,它們只在核心空間執行,從來不切換到使用者空間去,核心執行緒和普通程式一樣,可以被排程,可以用被搶佔。
核心通過從kthreadd核心程式中衍生出所有新的核心執行緒的,於是,從現有核心執行緒中建立一個新的核心執行緒有兩種方法。其一,利用kthread_creat()函式建立,並用wake_up_process()喚醒。其二,直接執行kthread_run()。核心執行緒啟動之後就一直執行直到呼叫do_exit()退出,或者核心的其他不煩你呼叫kthread_stop()退出。
在呼叫exit()結束子程式後,儘管執行緒已經僵死不能執行了,但系統還保留了它的程式描述符,這樣做可以讓系統有辦法在子程式終結後仍能獲得它的資訊。所以,程式終結時需要將清理工作和刪除程式描述符工作分開執行,我們的exit就完成的清理工作,刪除程式描述符由父程式中的wait函式完成。Wait函式的標準動作是掛起呼叫它的程式,直到其中的一個子程式退出。
孤兒程式
如果父程式在子程式之前退出,必須有一個機制來保證子程式能找到一個新的父親,否則這些孤兒程式就會在退出時永遠處於僵死狀態,白白耗費記憶體。解決這個問題的辦法是給子程式在當前執行緒組內找一個執行緒作為父親,如果不行,就讓init做它們的父程式。
相關文章
- Linux init程式分析Linux
- Linux下的程式分析–PSLinux
- Linux下init程式原始碼分析Linux原始碼
- Linux程式效能分析和火焰圖Linux
- Linux下的守護程式分析Linux
- Linux 下程式許可權分析Linux
- Linux核心排程分析(程式排程)Linux
- Linux 執行緒(程式)數限制分析Linux執行緒
- Linux程式排程核心實現分析Linux
- 利用linux系統命令分析PHP程式LinuxPHP
- Linux核心分析--系統呼叫實現程式碼分析(轉)Linux
- linux程式之記憶體洩漏分析Linux記憶體
- Linux下Python程式Killed,分析其原因LinuxPython
- linux程式管理子系統簡要分析Linux
- 《Linux系統程式設計訓練營》9_Linux 程式層次分析Linux程式設計
- Linux程式排程邏輯與原始碼分析Linux原始碼
- Linux下的應用程式效能分析 總結Linux
- Linux核心建立一個程式的過程分析Linux
- Linux系統程式底層debug除錯及程式原理分析利器Linux除錯
- linux效能分析Linux
- linux 網橋程式碼分析 二 網橋初始化Linux
- Linux作業系統程式管理的分析與應用Linux作業系統
- 深入淺出分析Linux裝置驅動程式中斷Linux
- Linux系統程式設計 - 07. 迴圈建立N個子程式分析Linux程式設計
- Linux作業系統的許可權程式碼分析【轉】Linux作業系統
- Linux C++程式進行效能分析工具gprof使用入門LinuxC++
- 深入淺出分析Linux裝置驅動程式中斷(轉)Linux
- linux原始碼分析Linux原始碼
- Linux核心分析。3Linux
- Linux核心分析。4Linux
- Linux核心分析。5Linux
- LINUX核心分析。6Linux
- LINUX核心分析。7Linux
- LINUX核心分析。8Linux
- linux中vmstat分析Linux
- Linux效能分析工具Linux
- 一次Linux遭入侵,挖礦程式被隱藏案例分析Linux
- GNU/Linux程式崩潰分析框架漏洞導致核心提權風險Linux框架