【進階】從linux到android,進程的方方面面

最近在閱讀《Linux內核設計與實現》,這裡做一下linux中進程相關的知識點整理,以及android中進程的淺析。
下面1,2小節整理自《Linux內核設計與實現》 第三章《進程管理》和第四章《進程調度》。第3節整理android中進程的知識點。

1 Linux中的進程管理

以下內容整理自:《Linux內核設計與實現》 第三章《進程管理》

1.1進程和線程

進程是資源分配的最小單位。
線程是操作系統調度執行的最小單位。

進程和線程是程序運行時狀態,是動態變化的,進程和線程的管理操作(比如,創建,銷毀等)都是由內核來實現的。Linux中的進程於Windows相比是很輕量級的,而且不嚴格區分進程和線程,線程是一種特殊的進程。

進程提供2種虛擬機制:虛擬處理器和虛擬內存
每個進程有獨立的虛擬處理器和虛擬內存,每個線程有獨立的虛擬處理器,同一個進程內的線程有可能會共享虛擬內存。

內核把進程的列表存放在任務隊列(task list)中(雙向循環鏈表),鏈表的每一項類型為task_struct,我們稱之為進程描述符(process descriptor)。進程的信息主要保存在task_struct中(位於 include/linux/sched.h)

通過task_struct和thread_info存放和表示進程。

進程標識PID(process identification value)和線程標識TID(thread identification value)對於同一個進程或線程來說都是相等的。
Linux中可以用ps命令查看所有進程的信息:
ps -eo pid,tid,ppid,comm

1.2 進程的生命周期

進程的各個狀態之間的轉化構成瞭進程的整個生命周期。

 

進程有五種進程狀態:
除瞭圖片上面的三種還有,_TASK_TRACED_TASK_STOPPED

1.3 進程的創建

Linux中創建進程分2步:fork()和exec()。

1 fork(): 通過拷貝當前進程創建一個子進程 (實際上最終是通過clone( ) )
2 exec(): 讀取可執行文件,將其載入到地址空間中運行 (是一個系統調用族)

創建的流程:

1 調用dup_task_struct()為新進程分配內核棧,task_struct等,其中的內容與父進程相同。
2 check新進程(進程數目是否超出上限等)
3 清理新進程的信息(比如PID置0等),使之與父進程區別開。
4 新進程狀態置為 TASK_UNINTERRUPTIBLE
5 更新task_struct的flags成員。
6 調用alloc_pid()為新進程分配一個有效的PID
7 根據clone()的參數標志,拷貝或共享相應的信息
8 做一些掃尾工作並返回新進程指針
9 創建進程的fork()函數實際上最終是調用clone()函數。

創建線程和進程的步驟一樣,隻是最終傳給clone()函數的參數不同。
比如,通過一個普通的fork來創建進程,相當於:clone(SIGCHLD, 0)
創建一個和父進程共享地址空間,文件系統資源,文件描述符和信號處理程序的進程,即一個線程:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)

Linux通過Clone()系統調用實現fork()

在內核中創建的內核線程與普通的進程之間還有個主要區別在於:內核線程沒有獨立的地址空間,它們隻能在內核空間運行。這與之前提到的Linux內核是個單內核有關。

1.4 進程的終止

發生在進程調用exit()系統調用時
和創建進程一樣,終結一個進程同樣有很多步驟:

子進程上的操作,靠do_exit()完成->定義於(kernel/exit.c)

1 設置task_struct中的標識成員設置為PF_EXITING
2 調用del_timer_sync()刪除內核定時器, 確保沒有定時器在排隊和運行
3 調用exit_mm()釋放進程占用的mm_struct
4 調用sem__exit(),使進程離開等待IPC信號的隊列
5 調用exit_files()和exit_fs(),釋放進程占用的文件描述符和文件系統資源
6 把task_struct的exit_code設置為進程的返回值
7 調用exit_notify()向父進程發送信號,並把自己的狀態設為EXIT_ZOMBIE
8 切換到新進程繼續執行

子進程進入EXIT_ZOMBIE之後,雖然永遠不會被調度,關聯的資源也釋放 掉瞭,但是它本身占用的內存還沒有釋放,比如創建時分配的內核棧,task_struct結構等。這些由父進程來釋放。父進程上的操作(release_task)
父進程受到子進程發送的exit_notify()信號後,將該子進程的進程描述符和所有進程獨享的資源全部刪除。

從上面的步驟可以看出,必須要確保每個子進程都有父進程,如果父進程在子進程結束之前就已經結束瞭會怎麼樣呢?
子進程在調用exit_notify()時已經考慮到瞭這點。如果子進程的父進程已經退出瞭,那麼子進程在退出時,exit_notify()函數會先調用forget_original_parent(),然後再調用find_new_reaper()來尋找新的父進程。find_new_reaper()函數先在當前線程組中找一個線程作為父親,如果找不到,就讓init做父進程。(init進程是在linux啟動時就一直存在的)


2 Linux中的進程調度

以下內容整理自:《Linux內核設計與實現》 第四章《進程調度》

2.1 什麼是調度?

現在的操作系統都是多任務的,為瞭能讓更多的任務能同時在系統上更好的運行,需要一個管理程序來管理計算機上同時運行的各個任務(也就是進程)。這個管理程序就是調度程序,它的功能說起來很簡單:

1決定哪些進程運行,哪些進程等待
2 決定每個進程運行多長時間

此外,為瞭獲得更好的用戶體驗,運行中的進程還可以立即被其他更緊急的進程打斷。
總之,調度是一個平衡的過程。一方面,它要保證各個運行的進程能夠最大限度的使用CPU(即盡量少的切換進程,進程切換過多,CPU的時間會浪費在切換上);另一方面,保證各個進程能公平的使用CPU(即防止一個進程長時間獨占CPU的情況)。

2.2 調度實現的原理

前面說過,調度功能就是決定哪個進程運行以及進程運行多長時間
決定哪個進程運行以及運行多長時間都和進程的優先級有關。為瞭確定一個進程到底能持續運行多長時間,調度中還引入瞭時間片的概念。
關於進程的優先級

1 進程的優先級有2種度量方法,一種是nice值,一種是實時優先級。
2 nice值的范圍是-20~+19,值越大優先級越低,也就是說nice值為-20的進程優先級最大。
3 實時優先級的范圍是0~99,與nice值的定義相反,實時優先級是值越大優先級越高。
4 實時進程都是一些對響應時間要求比較高的進程,因此系統中有實時優先級高的進程處於運行隊列的話,它們會搶占一般的進程的運行時間。
5 實時優先級高於nice值。
6 一個進程不可能有2個優先級。

關於時間片

有瞭優先級,可以決定誰先運行瞭。但是對於調度程序來說,並不是運行一次就結束瞭,還必須知道間隔多久進行下次調度。
於是就有瞭時間片的概念。時間片是一個數值,表示一個進程被搶占前能持續運行的時間。
也可以認為是進程在下次調度發生前運行的時間(除非進程主動放棄CPU,或者有實時進程來搶占CPU)。
時間片的大小設置並不簡單,設大瞭,系統響應變慢(調度周期長);設小瞭,進程頻繁切換帶來的處理器消耗。默認的時間片一般是10ms

調度實現原理
下面舉個直觀的例子來說明:

假設系統中隻有3個進程ProcessA(NI=+10),ProcessB(NI=0),ProcessC(NI=-10),NI表示進程的nice值,時間片=10ms
1) 調度前,把進程優先級按一定的權重映射成時間片(這裡假設優先級高一級相當於多5msCPU時間)。
假設ProcessA分配瞭一個時間片10ms,那麼ProcessB的優先級比ProcessA高10(nice值越小優先級越高),ProcessB應該分配10*5+10=60ms,以此類推,ProcessC分配20*5+10=110ms
2) 開始調度時,優先調度分配CPU時間多的進程。由於ProcessA(10ms),ProcessB(60ms),ProcessC(110ms)。顯然先調度ProcessC
3) 10ms(一個時間片)後,再次調度時,ProcessA(10ms),ProcessB(60ms),ProcessC(100ms)。ProcessC剛運行瞭10ms,所以變成100ms。此時仍然先調度ProcessC
4) 再調度4次後(4個時間片),ProcessA(10ms),ProcessB(60ms),ProcessC(60ms)。此時ProcessB和ProcessC的CPU時間一樣,這時得看ProcessB和ProcessC誰在CPU運行隊列的前面,假設ProcessB在前面,則調度ProcessB
5) 10ms(一個時間片)後,ProcessA(10ms),ProcessB(50ms),ProcessC(60ms)。再次調度ProcessC
6) ProcessB和ProcessC交替運行,直至ProcessA(10ms),ProcessB(10ms),ProcessC(10ms)。
這時得看ProcessA,ProcessB,ProcessC誰在CPU運行隊列的前面就先調度誰。這裡假設調度ProcessA
7) 10ms(一個時間片)後,ProcessA(時間片用完後退出),ProcessB(10ms),ProcessC(10ms)。
8) 再過2個時間片,ProcessB和ProcessC也運行完退出。

這個例子很簡單,主要是為瞭說明調度的原理,實際的調度算法雖然不會這麼簡單,但是基本的實現原理也是類似的:

1)確定每個進程能占用多少CPU時間(這裡確定CPU時間的算法有很多,根據不同的需求會不一樣)
2)占用CPU時間多的先運行
3)運行完後,扣除運行進程的CPU時間,再回到 1)

2.3 Linux上調度實現的方法

Linux上的調度算法是不斷發展的,在2.6.23內核以後,采用瞭“完全公平調度算法”,簡稱CFS。
CFS算法在分配每個進程的CPU時間時,不是分配給它們一個絕對的CPU時間,而是根據進程的優先級分配給它們一個占用CPU時間的百分比。

2.4 調度相關的系統調用

調度相關的系統調用主要有2類:

1) 與調度策略和進程優先級相關 (就是上面的提到的各種參數,優先級,時間片等等) – 下表中的前8個

2) 與處理器相關 – 下表中的最後3個

系統調用 描述
nice() 設置進程的nice值
sched_setscheduler() 設置進程的調度策略,即設置進程采取何種調度算法
sched_getscheduler() 獲取進程的調度算法
sched_setparam() 設置進程的實時優先級
sched_getparam() 獲取進程的實時優先級
sched_get_priority_max() 獲取實時優先級的最大值,由於用戶權限的問題
sched_get_priority_min() 獲取實時優先級的最小值,理由與上面類似
sched_rr_get_interval() 獲取進程的時間片
sched_setaffinity() 設置進程的處理親和力,其實就是保存在task_struct中的cpu_allowed這個掩碼標志。該掩碼的每一位對應一個系統中可用的處理器,默認所有位都被設置,即該進程可以再系統中所有處理器上執行。用戶可以通過此函數設置不同的掩碼,使得進程隻能在系統中某一個或某幾個處理器上運行。
sched_getaffinity() 獲取進程的處理親和力
sched_yield() 暫時讓出處理器

3 android中的進程基礎

3.1 進程

默認情況下,Android為每個應用程序創建一個單獨的進程,所有組件運行在該進程中,這個默認進程的名字通常與該應用程序的包名相同。比如

那麼該程序默認的進程名為com.lt.mytest設置該屬性可以使得本應用程序與其它應用程序共享相同的進程。

註意: 標簽不支持android:process屬性

但是,如果我們想要控制讓某個特定的組件屬於某個進程,我們可以在manifest文件中進行配置。在每種組件元素(activity、service、receiver、provider)的manifest條目中,都支持一個android:process的屬性,通過這個屬性,我們可以指定某個組件運行的進程。我們可以通過設置這個屬性,讓每個組件運行在它自己的進程中,也可以隻讓某些組件共享一個進程。我們要可以通過設置android:process屬性,讓不同應用程序中的組件運行在相同的進程中,這些應用程序共享相同的Linux用戶ID,擁有相同的證書。

元素也有一個android:process屬性,可以設置一個應用於全部組件的默認值。 當可用內存數量低,而一些與用戶即時交互的進程又需要內存時,Android隨時可能會終止某個進程。運行在被終止的進程中的組件會因此被銷毀,但是,當再次需要這些組件工作時,就會再啟動一個進程。

在決定要終止哪個進程時,Android系統會權衡它們對於用戶的重要性。例如,相較於運行可見activities的進程,終止一個運行不可見activities的進程會更加合理。是否終止一個進程,依賴於運行在這個進程中的組件的狀態。

3.2進程生命周期

Android系統會盡可能讓一個應用程序進程運行更長的時間,但是它也需要移除舊的進程,為那些新創建的進程或者相比起來更加重要的進程釋放內存空間。要決定哪個進程保留,哪個進程終止,系統會將每個進程放置到“importance hierarchy”中,“importance hierarchy”是基於運行在進程中的組件以及這些組件的狀態的。擁有最低重要性的進程會首先被幹掉,然後就是那些次低重要性的進程,依次類推。在“importance hierarchy”中,共有五個等級。下面的列表中,按照重要性列出瞭五種不同類型的進程:

1、 前臺進程(Foreground process)
2、 可見進程(Visible process)
3、 服務進程(Service process)
4、 後臺進程(Background process)
5、 空進程(Empty process)

元素 Android:process屬性定義瞭運行Activity所在進程的名稱。通常,一個應用程序的所有組件運行在應用程序創建的默認的進程。它具有與應用程序包相同的名稱。元素的 android:process屬性可以為所有組件設置不同的默認進程名稱。但是,每個組件都可以覆蓋默認設置,讓應用程序跨多個進程。

如果分配給此屬性的名稱以一個冒號(‘:’)開頭,發將創建一個新的屬於應用程序的私有的進程,在這一進程中運行。如果進程的名稱由小寫字母開始,活動將在該名稱的全局進程中運行,隻要它有這樣做的權限。這樣做將使在不同的應用程序中的組件共享一個進程,減少資源的使用。

與其它應用程序共享的一個Linux User Id的名字。

默認情況下,Android為每個應用程序分配一個唯一的User Id。然而,如果有多個應用程序都將該屬性設置為一個相同的值,那麼它們將共享相同的Id。如果這些應用程序再被設置成運行在一個相同的進程,它們便可以彼此訪問對方的數據。

3.3 android中的多進程使用需要註意的問題

如果你的app有需要的話,將你的app中使用app會在降低內存消耗。但是大多數的app都是不需要多進程的,因為如果方法不當的話,反而會增加內存消耗。(一個可以使用多進程的app情況是,比如你需要在後臺和前臺都需要做大量的工作並且需要分別管理)
比如說音樂播放器,我們需要在後臺利用service進行長時間的音樂播放。假如我們將整個app都放在一個進程中的話,那麼即使我們在操作其他app,後臺音樂播放的時候,關於activity UI界面的許多內存分配以及控制音樂播放的service都會被保存。這種情況,我們就可以使用兩個進程:一個專門針對UI界面,一個專門針對後臺音樂service的播放。

所以針對service,我們就可以指定android:process為一個字符串(可以為任意名字)

當然這裡使用冒號(‘:’) 開頭,這在上面的文章中也提到瞭,這保證瞭當前進程是app私有的。

註意點:
1 如果你需要將你的app劃分為多進程,那麼隻能讓一個進程負責UI處理,其他進程應當避免UI處理,否則你的內存會急速上升,一旦UI繪制之後,想降低內存消耗也會是一個難題。
2 當在android中使用多進程的時候,應當保持代碼的精簡。應為對於共同的實現操作現在會在不同的進程裡造成多餘的系統開銷。假如你使用enums,那麼內存會需要在不同的進程裡創建和初始化這些變量。關於adapters的任何抽象以及臨時變量都會造成重復的開銷。
3 關於多進程的另外一個關註點就是其中存在的一些共同的依賴關系,比如說你的app有一個content provider 運行在默認的進程中(包含UI的進程),那麼後臺進程使用content provider ,那麼content provider也會需要你的內存中有UI進程。這時候,如果你的目標是後臺進程獨立於繁重的前臺進程,那麼它肯定也就不能使用UI進程中content provider 或者service那些瞭。

3.4 關於我們從最近任務列表中清除app的問題

通過3.3, 我們也會聯想到平常使用音樂軟件(比如音樂),當我們選擇退出應用的時候,音樂都會在後臺播放,當時當我們從任務列表中清除音樂軟件的時候,音樂就會停止瞭,那麼當我們從任務列表中清除app,到底發生瞭什麼?直接看看stackexchange這個回答吧 what-actually-happens-when-you-swipe-an-app-out-of-the-recent-apps-list

簡單來說,這和多次按返回鍵退出應用一樣,系統會殺掉後臺進程,但優勢也不是這樣。
從最近任務中移除一個條目會移除這個app存在的後臺進程。但是它並不會直接結束service,當他們在任務列表中被清除的時候,其實他們自己有相應的api處理service是否應當被結束。也就是說,你使用的e-mail接收的app即使你在任務列表中把它清除瞭,它的service也會接收e-mail信息。
當然如果你想要完全停止一個app,你可以通過設置->應用管理 ->進入應用信息頁面,點擊強制退出。強制退出會讓該app的所有進程被殺掉,所有的service停止,所有的通知被移除,所有的提醒被關閉等。該app除瞭被再次調用的情況下,不會再被啟動。
也就是說,是由app來決定在任務列表清楚的時候,後臺進程是否被殺掉。

這也就解釋瞭為什麼我們在最近任務列表中清除瞭支付寶,但是支付寶卻還在我們的後臺運行進程裡面瞭。如果我們直接在應用信息界面強行停止瞭,這時候,支付寶就完全退出瞭。

3.5 android進程保活

關於 Android 進程保活,你所需要知道的一切
Android App 不死之路
Android 後臺任務型App多進程架構演化

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *