现成
01靜態鏈接 :01將可重定位的文件和命令行變成完全鏈接的、可加載、可運行的目標文件;02可重定位目標文件由各代碼和數據節組成;
完成靜態鏈接,鏈接器要完成以下兩個工作:
1)符號解析,將每一個符號引用正好和一個符號定義關聯起來;
2)重定位:可重定位的目標文件地址都是從零開始的,連接器通過吧每個符號定義與一個內存位置關聯起來,從而重定位這些節,然后修改所有對這些符號的引用,使得他們指向這個內存位置。
02目標文件:分類 可重定位目標文件;可執行目標文件;共享目標文件;
03符號和符號表:
定義:符號表記錄了目標模塊定義的符號和引用的符號信息。三種符號類型:
1.由模塊m定義并能被其他模塊引用的全局符號,非靜態的C函數和全局變量;
2.由其他模塊定義并被模塊m引用的全局變量;
3.由目標模塊定義和引用的局部符號,表現為靜態全局變量和函數;
符號解析:鏈接符號引用與符號定義
a.全局符號的多重定義問題
強符號:已被初始化的全局變量和函數
弱符號:未被初始化的全局變量
b.規則
1:不允許有多個同名的強符號
2:如果有一個強符號和多個弱符號同名,選強符號
3:如果有多個弱符號同名,從其中任選一個
c.符號的地址由鏈接器確定,但符號的大小以及其類型在編譯器就已經確定了,,鏈接器只負責解析和重定位符號確認符號的地址,同名符號有且僅有一個地址
d.靜態鏈接可選方式:
1)一組可重定位目標文件
2)所有相關的目標模塊打包成一個單獨文件---靜態庫(存檔文件)
e.使用靜態庫來解析符號
1)一個特殊現象:
一個靜態庫被解析后需后面的靜態庫使用若,,,
2)過程:符號解析時,鏈接器從左到右按照他們在編譯器驅動程序命令行上出現的順序來燒描可重定位目標文件和存檔文件(如.c--.o)。鏈接器維護三個集合:所有目標模塊集合E;未定義的集合U;定義的集合D;
燒描開始,鏈接器來判斷輸入f是什么,若是目標文件,f添加到E,修改U/D來反應f中的符號定義和引用;若f是存檔文件,鏈接器就嘗試匹配U中未解析的符號和由存檔文件成員定義的符號,若存檔文件成員m中有已定義的符號,m放E,修改U/D;如果鏈接器完成掃描后,U非空,則鏈接器輸出錯誤并終止,否則,它會合并和重定位E中的目標文件;
04重定位和加載?
01) 重定位 (可結合靜態鏈接的兩個步驟作答)
需要的術語:
a.重定位節和符號定義:聚合所有目標文件的相同節,鏈接器開始將運行時內存地址賦給每一個節和每一個符號 ☆☆☆
b.重定位節中的符號引用,鏈接器修改代碼節和數據節中的每個符號引用,使得他們執行正確的運行時地址。
02)過程:
a.一個概念,當匯編器遇到未知的符號引用,就會生成一個重定位條目,代碼的重定位條目存放在 . rel.text中,已初始化數據的重定位條目放在.rel.data中
重定位結構的結構:
typedef struct{long offset; //符號相對節的偏移long type:32;symbol:32;long addend; }Elf64_Rela;b.符號引用的重定位
只講兩種基本的重定位類型:
01相對引用
02絕對引用
03)加載 ☆☆☆
1)背景:elf可執行文件的格式跟可重定位的格式是很相似的
2)可執行目標文件的加載
?加載器將目標文件的代碼和數據復制到內存中,然后跳轉到入口點來運行即_start函數的地址;
?05 動態鏈接庫
?1)出現的原因:為了解決靜態庫維護還是相對麻煩以及很多共用的庫在內存中有很多碎片造成的內存浪費。
2)動態鏈接:共享庫在加載或運行時加載到任意內存位置,并和在內存中的程序鏈接起來,由 動態鏈接器完成。
3)相關細節:
動態鏈接不會復制共享庫的代碼和數據段,僅會復制一些重定位信息和符號表;
信息和符號表
共享庫中會有一個 .interp節,這個節包含動態鏈接器的路徑名,動態鏈接器本身就是一個共享目標如(ld-linux.so)。
當運行一個可執行文件時,加載器會通過.interp節的信息找到動態鏈接器來運行,而動態鏈接器重定位相關的.so來完成鏈接任務:
? 位置無關代碼PIC(position independent code):共享庫若想要被進程共享就要求使用位置無關代碼,位置無關代碼是可以加載到內存的任何位置,而無需鏈接器修改即可以加載和重定位;
06 異常和進程
異常就是控制流突變,用來響應處理器狀態中的某個變化,一部分由硬件實現,一部分
由操作系統來實現,每個異常都會被分配一個異常號,一些由硬件設計者來分配,常
見的有:內存缺頁,算術溢出,內存訪問違規,除零,一些由內核設計者分配,常用的
有系統調用和外部I/o設備的信號,異常有如下分類:中斷,故障,陷阱,終止。
其中中斷是異步發生的,中斷函數處理結束后返回下條指令,陷阱是同步的總是返回
下條指令,故障時同步的,由潛在可恢復的錯誤造成(缺頁),返回當前的指令,終止
同步的,不可恢復,不會返回。
28/539
a.異常處理寫過程調用的一些不同之處
01返回地址不一樣,異常返回fizz需要根據異常類型來確定
02異常處理時,處理器將一些額外的處理狀態壓入棧中
03若是由用戶態進入內核態,則異常處理便用內核棧
04異常處理程序運行在內核態
b.四種不同類型
中斷:不是由一條專門的指令造成的,它是處理器在每次執行一條指令后檢測是否有中斷產生:
陷阱和系統調用:陷阱是有意義的異常,像中斷處理程序一樣,陷阱處理程序將返回下一條指令。陷阱最重要的作用是
在用戶程序和內核之間提供一個像過程一樣的接口,叫做系統調用。系統調用都有一個服務號,指令“syscall n”來請求服務n;
,
中斷與系統調用的區別:系統調用采用陷阱門,中斷進入中斷服務cpu自動關閉中斷IF清0,防止嵌套;陷阱門進入服務程序時
IF不變,是開中斷下進行的,所有系統調用可被中斷;
?系統調用與一般程序區別:調用的棧不同,一個運行在內核模式一個運行在用戶模式;
故障:若故障能夠被恢復,則返回到引起故障的指令,否則終止;
終止:不可恢復的錯誤。
?
進程:
a.定義:一個執行中的程序的實例。
進程與程序的區別:程序是一堆代碼可以作為目標文件存在于磁盤上,或者作為段存在于地址空間中,進程是執行程序的一個具體實例,程序總是運行在某個進程的上下文中。
b.進程提供應用程序的關鍵抽象:
一個獨立的邏輯控制流,它提供一個假象,好像我們的程序獨占地使用處理器。
一個私有的地址空間,它提供一個假象,好像我們的程序獨占地使用內存系統。
c.邏輯控制流
PC值的序列-邏輯流
?d.用戶模式下訪問內核
唯一的模式就是通過異常(中斷、故障、陷阱)
處理器通常是用某個控制寄存器中的一個模式位,該寄存器描述了當前進程享有的特權。當設置了模式位時,進程就運行
在內核模式中(也叫超級用戶模式)一個運行在內核模式的進程可以執行指令集中的任何指令,并可以訪問系統中的
任何內存位置。沒有設置,就是普通用戶模式,不允許執行特權指令,比如停止處理器等;
e.上下文切換過程
每個進程維持一個上下文,上下文是內核重新啟動一個被搶占的進程所需的狀態。
過程:1保存當前進程的上下文,2恢復某個先前被搶占的進程被保存的上下文,3將控制傳遞給這個新恢復的進程。
?
7進程控制和信號
1.進程控制
?a.獲取進程號(進程有唯一的進程號 正數 ID/PID)
getpid;
b.創建進程和終止進程
程序員角度分三種
1運行:進程在cpu運行或等待運行且最終被內核調度。
2停止:進程的執行被掛起,且無觸發條件不會被調度。
3終止:永遠停止了,如收到一個信號終止了進程,或調用exit;
4創建:fork、vfork;
fork:調用一次返回兩次,和父進程用相同的地址空間,與父進程并發執行,與父共享文件,pid不一樣;
c.回收子進程
一個終止的進程若沒有被父進程回收,會變成僵死進程,仍然會占用內存空間,直到被回收
若其父進程先終止,則另init進程收養、回收。init進程pid為1,是所有進程的祖先。
d.進程的休眠
sleep pause;
?
2.信號
a.一次軟件形式的異常,一個信號就是一條消息,他通知進程系統中發生了一個某種類型
的事件。
底層的硬件異常是由內核異常處理程序處理,一般情況對用戶而言是不可見的,但信號提供
了一種機制告訴用戶進程系統發生了什么樣的異常。
b.發生信號的原理:內核改變目的進程的上下文中某個狀態來傳遞一個信號給目的進程;
(收發信號可簡理解為:上下文中存與取)
接收信號后常見的信號處理行為有以下幾種:
1進程終止:SIGKILL
2進程終止并轉儲內存
3進程停止(掛起)知道被SIGXONT信號重啟
4進程忽略該信號
c.進程組的概念,每個進程都屬于一個進程組,且父子進程同屬于一個進程組。
d.阻塞和解除阻塞
隱式阻塞:阻塞任何與當前正在處理的信號類型的待處理的信號。
顯式阻塞:明確的阻塞和解除阻塞選定的信號。
e.非本地跳轉
一種用戶級異常控制流,他可以將控制直接從一個函數轉移到另一個當前正在執行的函數而不用通過函數棧機制
8.進程間的通信
指在不同的進程之間傳播和交換信息
常見的通信手段:
1、管道及有名管道 :管道用于有親緣關系進程間的通信,有名管道克服了管道沒有名字的限制,因此,有名管道
可用于無親緣關系進程間通信。
2、信號:用于通知接受進程有某種事件發生,除了用于進程間通信外,進程還可以發信號給進程本身。
3、消息隊列:是消息的鏈表,存放在內核中,并由消息隊列標志符標示。克服了信號傳輸信息少,管道只能承載無格式字節
及緩存區大小受限等問題;
4信號量:信號量是一個計數器,可以用來控制多個進程對共享資源的訪問;
5共享內存:多個進程可以訪問同一塊內存空間,是最快的ipc形式,一般結合信號量使用;
6套接字:更一般的進程間通信機制,可以完成本機或者跨機器的進程通信;
?
9.進程間信號量的控制
?信號量是一個計數器,是一個具有非負整值的全局變量,能做如下兩個操作:
P(s):如果s為非零,那么P將s減1,并且立即返回。如果s為零,那么就掛起這個線程,直到
s變為非零,而一個V操作會重啟這個線程。重啟后P操作將s減1,并將控制返回給調用者。
V(s):V操作將s加1,如果有任何線程阻塞在P操作等待s變成非零,那么V操作會重啟這些
線程中的一個,然后該線程將s減1,完成它的P操作。
?
10.信號量
信號量是一個特殊的變量,程序對其訪問都是原子操作,且只允許對它進行P和V操作,最簡單
的信號量是只能取0和1的變量,這也是信號量最常用的一種形式,叫做二進制信號量。而可以取多個
常亮的信號量被稱為通用信號量;
?
11 各種并發編程模式
概念:如果邏輯控制流在時間上有重疊,那它們就是并發的;
場景:
1訪問慢速I/O設備
2與人交互
3推遲工作以降低延遲
4服務多個網絡客戶端
5在多核機器上并行計算
三種并發編程模式
1基于進程的并發編程
每個邏輯控制流都是一個進程,由內核來調度和維護
優缺點:
優點:共享文件表,不共享用戶空間,更加的安全。
缺點:開銷大,共享信息困難,要使用顯式的IPC
2基于I/O多路復用的并發編程
基本的思想就是使用select函數,要求掛起進程,只有在一個或多個I/O事件發生后,
才將控制返回給應用程序;
I/O多路復用可以用做并發事件驅動程序的基礎。
優缺點:
優點:程序員能更好的控制程序,共享數據簡單
缺點:編碼復雜,并且程度越小,復雜度越高;
3基于線程并發模型
概念:線程就是運行在進程上下文中的邏輯流,線程也由內核調度,每個線程都有自己的線程上下文,包括線程ID、棧、棧指針、程序計數器、條件嗎和通用目的寄存器。
線程的切換比進程快很多,線程是對等的,任何線程都可以訪問共享虛擬內存的任何位置。
分離線程
線程要么是可結合的,要么是可分離的,區別:
1可結合線程:可以被其他線程回收和殺死,被回收之前,它所占有的資源不會被釋放
2可分離線程:不可以回收不可以殺死,它的內存資源在其終止時自動釋放,默認創建的都是可結合線程;
?
12?共享變量和線程同步
?1)線程內存模型:
每個線程都有自己獨立的線程上下文、包括線程ID、棧棧指針、程序計數器、條形碼和通用目的寄存器。
線程與其他線程共享進程上下文的剩余部分。包括整個用戶虛擬地址空間。
2)將變量映射到內存
全局變量:定義在函數之外的變量,運行時,全局變量只有一個實例,任何線程都可以引用。
本地自動變量:定義在函數內,但沒有用static屬性的變量,運行時每個線程的棧都包含它自己的
所有本地自動變量實例。
本地靜態變量:函數內部用static屬性的變量,運行時只有一個實例。
3)共享變量
我們說一個變量v是共享的,當且僅當它的一個實例被一個以上的線程引用。如果只有一個線程引用就
不是共享的。共享變量是簡單的,但這種共享方式會引來同步錯誤,因為線程之間的運行是競爭關系,
沒辦法確認進程運行的順序,為了解決這種錯誤,可以使用信號量來對共享資源加鎖,使其確定線程對該
變量的互斥訪問。
4)線程同步
確保每個線程在執行它的臨界區中的指令時,擁有對共享變量的互斥訪問。
通過對二元信號量的使用(二元信號量也稱為互斥鎖),對共享資源進行加鎖,使得每次只會有一個
線程能夠訪問,直到訪問完成,其他線程才能訪問,這樣就能對資源的互斥旺旺,達到線程同步。
13 其他并行問題
1)線程安全:如果一個函數被稱為線程安全的,那 當且僅當被多個并發線程反復地調用時,它會一直產生正確的結果。
四種線程不安全函數:
1不保護共享變量的函數
2保持跨越多個調用的狀態的函數。
3返回指向靜態變量的指針函數。
2)可重入性
有一類重要的線程安全函數,叫可重入函數,當它們被多個線程調用時,不會引用任何共享數據,盡管
線程安全和可重入不等價,可重入函數屬于線程安全函數;
顯式可重入函數:不依賴調用者,所有的數據引用都是本地自動棧變量,且參數是傳值傳遞的(不是指針);
隱式可重入函數:形參可以使指針,小心的傳遞指向非共享數據的指針;
3)LINUX提供不安全的可重入版本(安全)函數名多以_r結尾;
4)競爭:當程序的結果依賴一個線程要在另一個線程達到y點前到達它的控制流中的x點時,就會產生競爭;
5)死鎖:一個線程阻塞了,等待一個永遠不會為真的條件。
?
?
--
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
---------------------
?
總結
- 上一篇: 切木头之二分法启示
- 下一篇: Algs4-1.5.1使用quick-f