上一篇嵌入式未來一段時間還是Linux天下,一位嵌入式er初探Linux kernel經驗,我們講到了Linux內核開發和應用程序開發,今天我們來講講Linux重點部分Linux的進程管理。
OS是干啥的?處理提供對硬件層的抽象以外,還擔負著很多的硬件管理功能,而這些功能,用一句話來說,就是來處理各個部件的時空復用問題(時間和空間的重用問題,如cpu是分時重用的(當然還有多核cpu的特例,而內存是即分時又分空的……)。
往古至今,大牛們對OS的定義不下其數,而本人認為最有說服力的OS的定義就是:“所有軟件(特指應用程序)的交集”,料想計算機誕生初期都是專用機,在一款機器上只能跑訂制的專用程序,而這個程序要自己做好所有硬件的協調且還要完成他應有的本分工作,慢慢的計算機向通用型發展,為了提高系統利用率和避免盲目且重復的底層實現,OS隨著需求一步步形成且不斷完善……
不知你發現沒有,好多東西都是在歷史舞臺上重復出現的,仿佛對應著“20年后又是一條好漢”這句話,看現在的google的Chrome OS,其實最初的原型說白了就是把chrome瀏覽器必要的底層重新從OS中剝離,使其具有獨立運行的能力,這不是有點最早的專用機的味道,計算機這東西很是神奇,一個好的點子就可能改變整個市場,甚至一個世紀……
扯遠了,重新回來,回到復用性說起吧,因為cpu要跑的程序很多,但是cup個數有限,這就牽扯到cpu的重用,也就是被多個進程重用(進程與程序的區別就不多說了,自己熟悉OS知識去吧,簡單的提一下程序運行的本質就是從內核申請個進程,再把程序包中的代碼拷貝到對應進程的代碼域并設置好相關變量數據令進程跑起來,所以程序只是靜態的代碼,而進程是一個不斷從程序加載代碼的執行過程),這是由于進程間要復用cpu,所以就要求有人來負責他們有組織有紀律的復用,并協調進程間的輕重緩急、切換規則、切換后的一些處理……
另外還有怎樣生產進程、怎樣切換、怎樣銷毀……這些由誰完成?當然是OS,對于linux,當然是kernel了。畢竟OS就是用來跑程序的,而進程就是程序的靈魂,可見進程管理的重要性,咱就從進程說起先。
雖然吧,進程是處于執行期的程序,但是要明白,進程并不僅僅局限于一段可執行的代碼,你想,要想跑進程,你得知道是誰的進程,要區別于其他進程;還要保證當前進程不能隨意訪問其他進程的地址空間,要是連這都不限制,那寫個黑客程序多隨意啊;還牽扯到多線程問題;另外,由于進程間是復用cpu的,就是一會兒這個執行,一會兒換另一個,那你還要保證它可以接著上次的執行啊,要不不就亂了套了……如此說來,進程需要的東西大概有:
打開的文件
掛起的信號 (linux一個事件的處發是基于信號機制的,就像windows的事件機制)
內核內部數據 (這就是傳說中恢復現場用的,要還原到進程切換前的狀態需要保存現場)
處理器狀態 (沒理解錯的話,這也是現場保持的一部分,因為有些程序的執行是)
地址空間
一個或多個執行線程(Linux下的線程實現非常有趣,也非常簡單,本質上也就是幾個共享進程,沒有設置專門的線程數據結構)
以上是Linux下進程的主要組成部分,當然了,進程管理么,有了進程還要有管理,管理相關著進程的策略和生命周期等一些東西,我們會慢慢講來。
話說很久很久以前,進程自創建時刻起就開始存活了,活在Linux世界的進程爹fork()系統調用一下,就會生個小進程,比和女兒國的水來的還快。進程這東西沒耳朵沒眼睛的,他爹咋知道啥時候生好了。既然fork()是生婆,那這是生婆最懂了。fork()系統調用會返回兩次:一次回到父進程,一次回到子進程。
新的進程是為了立即執行新的不同的程序,而接著調用exec*()這族函數就可以創建新的地址空間,并把新的程序載入。(fork()實際上是由clone()系統調用實現的。)
最終,程序會通過exit()系統調用退出執行。這個函數會終結進程并將其占用的資源釋放。父進程會通過wait4()系統調用來查詢子進程是否終結,這就使得進程擁有了等待特定進程執行的能力。進程退出后被設置為僵死狀態,直到父進程調用wait()或waitpid()為止。
知道了進程不僅僅是由一段執行代碼組成的,咱們就說說linux下的進程的大概過程。其實一個進程就相當于一個軟件的動態執行(嚴格的說是某個軟件子系統的動態執行,當然我們可以把該子系統想象成一個子軟件,這樣會便于理解)。Linux中創建一個進程要用到fork()系統調用,一個子進程的生成是通過拷貝父進程來實現的。fork以后,會返回兩次:一次回到父進程,一次回到子進程。為何要返回兩次,兩次又是如何區別的呢??剛開始我也在像這個問題,因為子進程拷貝了父進程的代碼,返回時處于fork()返回點的上下文是一樣的,但是返回的值不一樣,借此來區分是父進程還是子進程……
這不,新的子進程創建好了,然后干什么?肯定是做不是當前進程的工作,要不創建他干什么?所以,這時候就接著調用exec*()這一族函數,該族函數可以創建新的地址空間,并加載到當前進程執行。
最后,程序通過exit() syscall(系統調用,以后都用這個代指了) 退出執行。這個函數會終結進程并將其占用的資源釋放掉。父進程通過wait4() syscall來查詢子進程是否終結,這使得進程擁有了等待特定進程執行完的能力(這不就是傳說中的同步么?有木有?有木有?哈哈)。進程退出后被設置為僵死狀態,知道父進程調用wait()或waitpid()為止(其實他們貌似都是基于wait4()實現的)。
上面就是進程創建到回收的簡單過程。子進程由父進程創建,父進程回收,有點恢復現場的感覺,不過在比較安全的系統里面,哪里都可以看的“恢復現場”等類似概念的身影,就像借錢一樣,“好借好還再借不難”,做程序也是這個道理,哈哈,慢慢體會,編程里面蘊含很多的哲學道理的。
1、進程描述符及任務結構
在軟件設計中,第一就是抽象名詞,一切名詞都會在計算機中找到它的數據抽象,就是偉大的數據結構童鞋。他可能被抽象成一個變量,一個數組,一個struct,一個對象……可能是任何一種類型,只要滿足你的需求,他就是最perfect的抽象。
進程在kernel中是被放在任務隊列(task list)中的,task list 是個雙向循環鏈表。鏈表中的每一項都是task_struct類型,即進程描述符。定義在中,包含著一個具體進程的所有信息。
1.1 分配進程描述符
進程有的表達了,但是不能胡亂表達啊,就像追女朋友一樣,不能見人就表白,那不成耍流氓了,名額有限,見好就收啊。OS能多道并跑的進程也就那幾個,若肆意創建進程,不跑癱了機子,那就變成病毒程序了,狂吃cpu。Linux使用slab分配器分配task_struct 結構。由slab動態生成task_struct,只需在棧底創建一個新的thread_info ,再用這個結構的數據可以容易的計算出偏移量。
其中包含了task_struct 的指針和進程的相關信息。
1.2 進程描述符的存放
Kernel通過PID唯一標識一個進程,PID是pid_t類型,其實也是int型的,pid最大值是32768 (short int 的Max 值)。可以改其值,在/proc/sys/kernel/pid_max 中,因為大公司的web服務器集群工作時32768個進程多開貌似不夠啊。
內核在處理進程時一般是直接通過task_struct進程的,都是通過current宏直接找到或計算當前task_struct的。有的平臺寄存器豐富,不用專門計算其值,一直把當前運行進程的值保存在專用的寄存器中就OK。如powerPC用r2寄存器,而x86寄存器少要專門計算。
1.3 進程狀態
進程一直處于下面五種狀態之一:
TASK_RUNNING//運行狀態
TASK_INTERRUPTIBLE//可中斷狀態
TASK_UNINTRRUPTIBLE//不可中斷狀態
TASK_ZOMBIE//僵死
TASK_STOPPED//停止
下圖是大概的轉換過程。不很詳細,大家可以search一下……
-
cpu
+關注
關注
68文章
10702瀏覽量
209358 -
Linux
+關注
關注
87文章
11123瀏覽量
207908 -
進程
+關注
關注
0文章
197瀏覽量
13928 -
Kernel
+關注
關注
0文章
48瀏覽量
11094
原文標題:揭開OS的面紗,一位嵌入式er 初探Linux kernel之重點Linux的進程管理
文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論