【深入解析MAX OSX & iOS操作系統(tǒng)】讀書(shū)筆記 —— Mach-O格式、進(jìn)程以及線程內(nèi)幕

概念

進(jìn)程和線程

進(jìn)程可以屬于進(jìn)程組,進(jìn)程組的主要作用是讓用戶可以同時(shí)控制多個(gè)進(jìn)程——通常向一個(gè)進(jìn)程組發(fā)送信號(hào)的方式控制這些進(jìn)程。

線程只不過(guò)是一組寄存器的狀態(tài),一個(gè)進(jìn)程中可以存在多個(gè)線程,一個(gè)進(jìn)程內(nèi)的多有線程都共享虛擬內(nèi)存空間、文件描述符文件句柄。進(jìn)程的抽象以一個(gè)或多個(gè)線程的容器的形式保存下來(lái)。

進(jìn)程生命周期

進(jìn)程生命周期

進(jìn)程會(huì)盡可能保存在SRUN狀態(tài)(即正在運(yùn)行或可運(yùn)行狀態(tài)),除非要等待一些資源(I/O相關(guān)資源、同步對(duì)象互斥體和鎖)。進(jìn)程正在等待時(shí),就沒(méi)有必要占用CPU了,甚至沒(méi)有必要存在于運(yùn)行隊(duì)列中。這個(gè)進(jìn)程置于“睡眠”狀態(tài),即SSLEEP狀態(tài),進(jìn)程會(huì)一直睡直到等待的資源變得可用。當(dāng)然睡眠的進(jìn)程也會(huì)被信號(hào)喚醒。

多線程的一個(gè)主要優(yōu)點(diǎn)是一個(gè)線程的狀態(tài)可以獨(dú)立于其他線程,但給一個(gè)線程子在睡眠時(shí),另一個(gè)線程可以被調(diào)度在CPU上執(zhí)行。

通過(guò)一個(gè)特殊的信號(hào)TSTOP可以使一個(gè)進(jìn)程停止執(zhí)行。相當(dāng)于凍結(jié)進(jìn)程,掛起這個(gè)進(jìn)程的所有線程。只有發(fā)送另一個(gè)CONT信號(hào)才能恢復(fù)進(jìn)程。

當(dāng)一個(gè)進(jìn)程通過(guò)從main()函數(shù)返回或通過(guò)調(diào)用exit(2)完成執(zhí)行時(shí),這個(gè)進(jìn)程被從內(nèi)存中清理干凈,從而完全終止。終止一個(gè)進(jìn)程會(huì)同時(shí)終止其所有線程,但是在進(jìn)程完成終止之前,會(huì)短暫地處于僵尸狀態(tài)

僵尸狀態(tài)

父進(jìn)程通過(guò)wait()等待子進(jìn)程,收集子進(jìn)程返回值。當(dāng)父進(jìn)程拋棄了子進(jìn)程時(shí),子進(jìn)程會(huì)處于僵尸狀態(tài),僵尸狀態(tài)的資源都釋放了,只是占用著PID,當(dāng)父進(jìn)程通過(guò)wait()收集子進(jìn)程返回值時(shí),子進(jìn)程才會(huì)真正死掉。

UNIX信號(hào)

信號(hào)是一種軟件中斷,表示進(jìn)程自己發(fā)生了異常,或者發(fā)生了外部的事件。信號(hào)指發(fā)送給程序的異步通知,其中不包含數(shù)據(jù)。信號(hào)由操作系統(tǒng)發(fā)送給進(jìn)程,用于表示發(fā)生了某種條件,而這種條件通常是因?yàn)槟愁愑布e(cuò)誤或程序異常而產(chǎn)生的。

OSX定義了31個(gè)信號(hào),定義在<sys/signal.h>


來(lái)源:

  • HW:硬件異常或錯(cuò)誤
  • OS:操作系統(tǒng),內(nèi)核代碼中的某個(gè)地方
  • TTY:終端驅(qū)動(dòng)程序
  • User:用戶通過(guò)kill命令發(fā)出(用戶可通過(guò)這個(gè)命令模擬所有其他信號(hào))

默認(rèn)處理

  • C——SA_CORE:除非另外指定,進(jìn)程會(huì)產(chǎn)生核心轉(zhuǎn)儲(chǔ)
  • I_SA_IGNORE:忽略掉信號(hào)
  • K_SA_KILL:進(jìn)程會(huì)終止
  • S_SA_STOP:進(jìn)程暫停運(yùn)行
  • T_SA_TTYSTOP

信號(hào)原本是設(shè)計(jì)為發(fā)送給進(jìn)程的,不過(guò)POSIX允許向單個(gè)線程發(fā)送信號(hào)。

可執(zhí)行文件

可執(zhí)行文件都包含一個(gè)頭簽名,通常是一個(gè)“魔數(shù)”,據(jù)此可以精確判定可執(zhí)行文件的格式,這個(gè)標(biāo)志就告訴操作系統(tǒng)內(nèi)核將這個(gè)文件讀入內(nèi)存。

一些可執(zhí)行文件

續(xù)表

OSX支持三種:解釋器腳本格式、通用二進(jìn)制格式、Mach-O格式。

通用二進(jìn)制格式

通用二進(jìn)制格式只不過(guò)是其支持的各種架構(gòu)的二進(jìn)制文件的打包文件。

Mach-O 二進(jìn)制文件

Mach-O文件頭

同一種二進(jìn)制文件可用于多種目標(biāo)文件類型:

  • 可執(zhí)行文件
  • 庫(kù)文件
  • 核心轉(zhuǎn)儲(chǔ)文件
  • 內(nèi)核擴(kuò)展文件

filetype表示目標(biāo)文件的類型。

加載命令

Mach-O文件頭包含了非常詳細(xì)的指令,指導(dǎo)如何設(shè)置并加載二進(jìn)制數(shù)據(jù)。一些命令是由內(nèi)核加載器直接使用,其他命令由動(dòng)態(tài)鏈接器處理。

內(nèi)核加載器:bsd/kern/mach_oader.c

加載過(guò)程在內(nèi)核的部分負(fù)責(zé)新進(jìn)程的基本設(shè)置:

  • 分配虛擬內(nèi)存
  • 創(chuàng)建主線程
  • 處理代碼簽名、加密

對(duì)于動(dòng)態(tài)鏈接的可執(zhí)行文件(大部分可執(zhí)行文件都是動(dòng)態(tài)鏈接),真正的庫(kù)加載和符號(hào)解析的工作都是通過(guò) LC_LOAD_DYLINKER 命令指定的動(dòng)態(tài)鏈接器在用戶態(tài)完成。控制權(quán)會(huì)轉(zhuǎn)交給鏈接器,鏈接器進(jìn)而接著處理文件頭中的其他加載命令。

加載命令:

  • LC_SEGMENT :進(jìn)程虛擬內(nèi)存設(shè)置
    • 對(duì)于每一個(gè)段,將文件中相應(yīng)的內(nèi)容加載到內(nèi)存中
  • LC_UNIXTHREAD
    • 當(dāng)所有庫(kù)都完成加載之后,dyld的工作也完成了,之后由LC_UNIXTHREAD命令負(fù)責(zé)啟動(dòng)二進(jìn)制程序的主線程
  • LC_THREAD
    • 用戶核心轉(zhuǎn)儲(chǔ)文件
  • LC_MAIN
    • 設(shè)置程序主線程的入口地址和棧大小
  • LC_CODE_SIGNATURE
    • LC_CODE_SIGNATURE包含了Mach-O二進(jìn)制文件的代碼簽名,如果簽名和代碼本身不匹配,內(nèi)核立即給進(jìn)程發(fā)送了一個(gè)SIGKILL信號(hào)

動(dòng)態(tài)庫(kù)

可執(zhí)行文件很小是獨(dú)立的,除了極少數(shù)的一些靜態(tài)鏈接的可執(zhí)行文件。大部分可執(zhí)行文件都是動(dòng)態(tài)鏈接的,都依賴一些預(yù)先存在的庫(kù),既可能是操作系統(tǒng)提供的,也可能是第三方的庫(kù)。

啟動(dòng)時(shí)庫(kù)的加載

僅有很少量的進(jìn)程只需要內(nèi)核加載器就可以完成加載,OSX上幾乎所有進(jìn)程都是動(dòng)態(tài)鏈接的,所以Mach-O鏡像中有很多“空洞”,即對(duì)外部的庫(kù)符號(hào)引用,這些空洞需要在啟動(dòng)時(shí)填補(bǔ)。這項(xiàng)工作就需要由動(dòng)態(tài)鏈接器完成,也稱為符號(hào)綁定

動(dòng)態(tài)鏈接器是內(nèi)核執(zhí)行 LC_DYLINKER 加載命令時(shí)啟動(dòng)的,通常情況使用/usr/lib/dyld作為動(dòng)態(tài)鏈接器。

dyld是一個(gè)用戶態(tài)進(jìn)程,不屬于內(nèi)核的一部分,作為單獨(dú)的開(kāi)源項(xiàng)目由蘋(píng)果進(jìn)行維護(hù)。從內(nèi)核角度看,dyld是一個(gè)可插入的組件,可以替換為第三方的鏈接器。

共享庫(kù)緩存

共享庫(kù)緩存是dyld支持的另外一個(gè)機(jī)制,指的是一些庫(kù)經(jīng)過(guò)預(yù)先連鏈接,然后保存在磁盤(pán)上的一個(gè)文件中。在iOS中,大部分常用的庫(kù)都被緩存了。

庫(kù)的運(yùn)行時(shí)加載

通常情況,開(kāi)發(fā)者通過(guò) #include包含一些頭文件,頭文件中聲明了他們想要的庫(kù)和符號(hào),有時(shí)候還會(huì)通過(guò)鏈接器的-l參數(shù)指定額外的庫(kù)。通過(guò)這種方式構(gòu)建的可執(zhí)行文件只有在解決了所有依賴條件之后才能加載執(zhí)行。

另一種方法,通過(guò)<dlfcn.h>頭文件中提供的函數(shù)在運(yùn)行時(shí)加載庫(kù),得到更高程度的靈活性,需要在編譯時(shí)確定或指導(dǎo)庫(kù)的名稱,開(kāi)發(fā)者可以準(zhǔn)備多個(gè)庫(kù),在運(yùn)行時(shí)根據(jù)功能或需求加載最合適的庫(kù)。如果庫(kù)加載失敗,程序還可以通過(guò)返回的錯(cuò)誤代碼進(jìn)行錯(cuò)誤處理。

弱符號(hào):將符號(hào)定義為“weak”。搶符號(hào)必須在執(zhí)行之前解析,如果解析失敗,程序運(yùn)行失敗。弱符號(hào)發(fā)生解析錯(cuò)誤不會(huì)引起程序鏈接錯(cuò)誤。動(dòng)態(tài)鏈接器將這個(gè)符號(hào)設(shè)置為NULL,允許程序恢復(fù)這一錯(cuò)誤或采用其他處理措施。

dyld的特性

  • 兩級(jí)名稱空間
  • 函數(shù)攔截
    • 允許一個(gè)庫(kù)將其函數(shù)實(shí)現(xiàn)替換為另一個(gè)函數(shù)的實(shí)現(xiàn)

進(jìn)程地址空間

用戶態(tài)的一個(gè)優(yōu)點(diǎn)在于虛擬內(nèi)存的隔離,進(jìn)程獨(dú)享一個(gè)私有的地址空間。

進(jìn)程內(nèi)存分配(用戶態(tài))

基于棧的內(nèi)存分配通常由編譯器處理。動(dòng)態(tài)內(nèi)存分配一般在堆上進(jìn)行。這些僅限于用戶態(tài),內(nèi)核層面,既沒(méi)有用戶堆也沒(méi)有棧存在。

alloca()

盡管傳統(tǒng)上棧是保存自動(dòng)變量的,但某些情況,也可以使用棧動(dòng)態(tài)分配內(nèi)存,方法就是alloca,和malloc區(qū)別在于函數(shù)返回的指針是棧上的地址,而不是堆上的地址。

堆分配

堆是由C語(yǔ)言運(yùn)行時(shí)庫(kù)維護(hù)的用戶態(tài)數(shù)據(jù)結(jié)構(gòu),通過(guò)堆的使用,程序可以不用直接在頁(yè)面的層次處理內(nèi)存分配。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容