日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

程序员的自我修养 - 读书笔记文字版

發(fā)布時間:2024/3/13 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 程序员的自我修养 - 读书笔记文字版 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第1部分 簡介

第1章 溫故而知新

  • 程序在運行的時候先通過分段(segmentation)的方式將虛擬地址空間與真實的物理內存地址空間進行一一的映射,但是這種方式每次換入換出的是整個程序,導致IO變大,更具局部性原理,可以采用分頁(Paging)來解決. 分頁就是將地址空間人為的等分成固定大小的頁,每頁的大小(4KB或者4MB)由操作系統(tǒng)確定。 幾乎所有操作系統(tǒng)都采用4KB的分頁,那么對于一個32位的程序來說,最多只能有4GB/4KB = 1048576頁, 物理空間也是同樣的分法。但是當真實物理空間不夠虛擬空間的頁數的時候,真正有效的空間以真實內存空間為準。頁的映射是由MMU部件來完成的。MMU一般集成在CPU內部。

  • 外設使用總線地址,CPU使用物理地址。x86平臺上,物理地址和總線地址相同。

  • 線程可以分為IO密集型線程和CPU密集型線程,當CPU密集型線程的優(yōu)先級較高的時候,可能會導致低優(yōu)先級的線程被餓死,而IO密集型線程獲得較高優(yōu)先級的時候,由于大部分時間是處于等待狀態(tài),所以不叫不容易造成其他線程的餓死。 如果一個線程長時間得不到執(zhí)行,調度系統(tǒng)會逐步提升它的優(yōu)先級讓它執(zhí)行。

  • 線程還可以分為搶占線程和不搶占線程,搶占線程是那些時間片用完之后會被剝奪執(zhí)行權的線程,不可搶占線程是那些除非執(zhí)行完畢否則不能剝奪執(zhí)行權的線程。時至今日,非搶占線程已經十分罕見。

  • windows對于進程和線程的實現如同教科書一般的標準,但是在linux中其實并沒有嚴格的線程和進程的概念,Linux中所有的執(zhí)行實體都被成為任務Task,每一個任務概念上類似于一個單線程的進程,但是它的不同任務之間可以選擇共享內存空間,因此在實際意義上共享了同一內存空間的多個任務構成了一個進程。

  • 為了保證多線程環(huán)境下操作的原子性,有以下幾種辦法:
    6.1 鎖
    6.2 二元信號量和多元信號量
    6.3 互斥量,和上面兩個的區(qū)別是哪個線程建立的互斥量就必須由哪個線程去釋放
    6.4 臨界區(qū),和上面幾種的區(qū)別是,哪個進程創(chuàng)建臨界區(qū)的鎖,就由哪個進程獲取,對其他進程不可見。
    6.5 讀寫鎖,有兩種獲取方式獨占式和共享式,區(qū)別如下

  • 讀寫鎖狀態(tài)以共享方式獲取以獨占方式獲取自由成功成功共享成功等待獨占等待等待
    6.6 條件變量 ,相當于一個柵欄
  • 可重入線程是并發(fā)安全的強力保障,一個可重入的函數可以在多線程環(huán)境下放心使用。

  • 三種線程模型
    8.1 一對一模型
    8.2 多對一模型
    8.3 多對多模型


  • 第2部分 - 靜態(tài)鏈接

    第2章 編譯和鏈接

  • 程序編譯運行的流程是 預編譯-編譯-匯編-鏈接, 命令分別是:
    gcc -E xx.c -o xx.i
    gcc -S xx.i -o xx.s 或者(cc1 xx.i)
    gcc -c xx.s -o xx.o 或者(as xx.s -s xx.o)
    以及 ld xxx.o xxxx.o xxxx.o等

  • 目標文件即.obj文件或者.o文件,本質上是函數的集合,用于重定位, 他們的內部函數和變量的存儲方式和真正的可執(zhí)行文件一樣只是在結構上稍有不同。

  • 第3章 目標文件里有什么

  • PC流行的PE文件格式和ELF文件格式都是COEF格式的變種。

  • bss段只是為全局變量和局部靜態(tài)變量預留位置,在elf文件中不占空間。

  • x86的cpu中字節(jié)序采用小端模式存儲(所以elf文件中變量的存儲采用小端), arm架構的cpu中采用大端,網絡字節(jié)序(TCP/IP)采用大端傳輸。

  • elf文件主要包含,代碼段(.text)和數據段(.data, .rodata, .bss), 未初始化的全局變量和和未初始化的局部靜態(tài)變量被存放在.bss段(有時候未初始化的全局變量也會存放在符號表中)。 字符串一般在.rodata段,也有的在.data段。值得一提的是賦值為0的局部靜態(tài)變量也會被認為是未賦值從而放置在bss 段而不是data段。 attribute 命令可以在代碼中指定變量或者函數存放在elf文件的那一段

  • reaelf -h xx.o可以輸出elf文件的文件頭信息。 ELF文件最開始的16個字節(jié)代表了ELF文件的平臺屬性,其中前四個字節(jié)是ELF文件的魔數,不同平臺的魔術不同(ELF的魔數是0x7f, 0x45, 0x4c, 0x46 即 DEL控制符,‘E’,‘L’,‘F’ PE/COEF的魔數是0x01, 0x07, 即’M’,‘Z’), 操作系統(tǒng)在加載可執(zhí)行文件的時候會確認魔數是否正確,如果不正確會拒絕加載。
    第5個字節(jié)是用來標識文件類型,0x01是32位的,0x02是64位的。第6個字節(jié)表示字節(jié)序是大段或者小端,第七個字節(jié)表示ELF的主版本,一般是1。后面9個字節(jié)沒有指定,表示可擴展。

  • ELF文件中段表的位置由e_shoff成員決定,即(Start of section headers的值決定段表的起始位置)。 readelf -S xx.o 可以顯示真正的段表結構。

  • elf文件中段表是其中的一個段,段表里面存儲了其他各個段的起始地址和大小還有其他一些信息。

  • 符號表里面存儲了全局變量,全局函數和行號和用于調式和核心轉儲的局部符號,行號等信息,鏈接器在鏈接的時候只關注全局函數和變量。 可重定位文件中包含的局部信息對其他重定位文件來說都是不可見的,只有全局函數和變可見。可以用nm來查看elf文件的符號結果。 符號表也是elf文件中的一個段,段名一般叫做.symtab

  • readelf -s xx.o可以打印輸出elf包含的符號表的信息。 分別有符號的類型,值(函數或者變量的地址),大小,綁定信息(局部,全局,弱引用), Ndx表示符號所在的段的下標,該下標可以通過readelf -a xx.o 看到。 值得注意的是符號表中第一個符號,即下標為0的符號永遠是一個未定義的符號。
    對于STT_SECTION類型的符號,它們的符號名沒有顯示,其Ndx所對應的段名也就是這里的符號名, 因為他們是段名符號。可以通過 objdump -t 看到這種段名符號

  • 特殊符號是由ld鏈接器定義的,程序中只需要申明就可以使用,程序在最終鏈接的時候會自動轉化為正確的值,例如__executeable_start, __etext或_etext或etext, _edata或者edata, _end或者end等等。

  • 為了防止函數和全局變量在各文件之間的命名沖突。但是隨著操作系統(tǒng)和編譯器的分化,GCC已經不用在符號前面加_但是windows平臺下的編譯器還保持著前面加_這樣的傳統(tǒng)。此外GCC在windows平臺下下編譯器例如cywin和mingw還保持著這樣的傳統(tǒng)。GCC本身可以通過編譯器選項-fleading-underscore或者-fno-leading-underscore來打開或關閉是否在C語言符號前加下劃線。

  • 函數簽名是C++引入的區(qū)別不同類,命名空間等不同作用域名中相同名稱的成員的機制 binutils提供的 c++filt命令可以解析一個函數修飾后名稱對應的真正的函數簽名。一般規(guī)則是對于在命名空間或者類的中的變量和函數其前面一般是_ZN開頭,以E(+)i/f/d結尾。最后值得說明的是不同平臺的編譯器的對同一函數的函數簽名可能是不同的。 VC++的函數簽名方法沒用向外公開,但是其UndecorateSymbolName()的api可以將修飾后的名稱轉換成函數簽名。
    由于不同編譯器采用不同的名字修飾方法,所以導致了不同編譯器產生的目標文件無法正常的相互鏈接,這也是導致不同編譯器之間不能互操作的主要原因之一。
    目標文件即OBJ文件是跨平臺的

  • extern "C"{}語句會導致受作用的變量和函數名在修飾之后采用的是C語言的格式而不是C++. 對于同一個變量或者函數,C++和C語言的修飾不一樣,為了讓C++能正確引用并使用C語言的符號,通常在聲明這個函數的時候會先判斷當前的編譯單元是C還是C++即使用下面的語句

  • #ifdef __cplusplus extern "C"{ #endifvoid *memset {void *, int , size_t};#ifdef __cplusplus } #endif

    這種技巧幾乎出現在任何系統(tǒng)頭文件中(源文件已經判斷了C或者cpp所以不需要這樣寫)

  • 強符號和弱符號,一般初始化了的全局變量和函數未強符號而未初始化的全局變量為弱符號,強若符號有以下規(guī)則
  • 14.1 不允許強符號重復定義 14.2 如果一個符號在某個目標文件中是強符號,其他目標文件中是弱符號,則選擇強符號 14.3 如果一個符號在多個目標文件中都是若符號,則選擇占用空間最大的那個。

    弱符號和鏈接器的COMMON塊概念的聯系很緊密

    15 強引用和弱引用: 如果引用的一個庫中的符號沒用被定義,則鏈接時候會報為定義錯誤,這是強引用, 這種情況不報錯的就屬于弱引用。 弱引用和弱符號對庫十分有用,因為這樣用戶可以自定義庫中函數,也可以在去掉了某些模塊之后程序依然可以正常鏈接,著使得程序更容易裁剪和組合。

    16 使用gcc/g++ -g參數可以在目標文件中保存調試信息,ELF文件的標準調試信息格式是DWARF, 目前是 DWARF 3, 微軟的調試信息標準格式叫CodeView. 調試信息通常數倍于ELF文件本身的內容, 發(fā)布時候必須去掉。 在Linux下,可以使用strip去掉ELF文件中的調試信息


    第4章 靜態(tài)鏈接

  • 空間地址分配有按序疊加和相似段合并兩種方法,一般都使用相似段合并的方法。最后的可執(zhí)行文件當中包含了可重定位的.o文件里面的所有指令。

  • Linux下,ELF可執(zhí)行文件默認地址從0x08048000開始分配。生成的可執(zhí)行elf文件中的.text段是各個.o文件的.text段大小之和

  • elf文件中需要重定位的段都有一個相對于重定位段(表), 利用objdump -r xx.o 可以查看目標文件的重定位表。

  • 對于弱符號,即未初始化的全局變量,由于在編譯成目標文件之后,編譯器不能確定其大小,所以將其放在COMMON塊中,但是當鏈接器分析完各個目標文件之后就可以確定其大小,從而將其放在BSS段,所以總體來看,未初始化的全局變量還是放在BSS段的。可以在GCC編譯的時候使用fno-common指定未賦值的全局變量不在COMMON塊中, 也可可以在代碼中寫__attribbute__ ((nocommon))將其當成強符號處理。

  • 重復代碼消除,例如C++的模板技術使得模板可以在多個源文件中別實例化但是編譯器并不能知道它在多處被同一種數據類型實例化,所以現在主流編譯器例如GNU 的做法是在每一個目標文件中對于一個模板的同一種實例化使用一種相同的名稱,這樣在鏈接階段,鏈接器會檢查這些重復的段并只保留一份。GCC把這種段叫"Link Once"命名為".gnu.linkonce.name" . VC++叫做COMDAT,
    這種做法的一個潛在的問題是,當編譯器對不同的編譯單元使用不同的編譯優(yōu)化選項的時候,可能會使得相同名稱的段有不同的內容,編譯器的做法是隨意選擇一個作為鏈接的輸入且提供警告信息。

  • 函數級別鏈接: 通常的鏈接過程都是文件或者編譯單元級別的鏈接,但是當只需要使用某個目標為見中的一個函數或變量的時候,就需要全部包含該文件,導致體積很大,編譯器為此專門提供了函數級別的鏈接,與重復代碼消除和相似,編譯器將所有函數都想模板函數一樣單獨保存到一個段中,需要的時候再將其包含到輸出文件,其他的則直接拋棄,這雖然較小的最終文件的體積但是由于段的數目增減,減慢了編譯和鏈接的過程。GCC使用-fdata-sections和-ffunction-sections可以將變量或者函數分別保存到獨立的段中。

  • 全局構造與析構: 全局對象的構造在main函數之前執(zhí)行,全局對象的析構在main函數之后哦執(zhí)行,Linux下的入口函數是_start,用于在main執(zhí)行前進行初始化。為此,ELF文件提供了兩個特殊的段.init和.fini。其中.init中保存的指令是main執(zhí)行之前Glibc的初始化部分, .fini中是main函數正常退出之后Glibc會安排執(zhí)行的代碼。

  • 為了使得不同平臺的目標文件兼容,即可以相互鏈接,這些文件必須有一直的ABI(Application Binary Interface),即二進制兼容,ABI內容包括符號修飾標準,變量內存布局,函數調用方式等等。廠商不希望用戶看見自己的源代碼所以會提供二進制版本,所以二進制兼容在大型項目中變得很重要。目前編譯器的兩大陣營 VISUAL C++和GNU 的GCC各執(zhí)己見互不兼容。ABI兼容問題還有待解決

  • 一個靜態(tài)庫文件(.a)是由許多.o文件合并而來的,linux下使用ar -t xx.a可以查看.a文件中包含的.o文件。在windows平臺下可以使用lib /LIST xx.lib查看

  • 可以使用objdump -t xx.a | grep xxx查找特定的目標文件。使用gcc -static --verbose -fno-builtin hello.c可以將編譯鏈接過程的中間步驟打印出來, 即使我們寫的代碼非常簡單,這也是一個非常長的依賴關系。這個過程會鏈接部分會顯示collect2這是ld的一個包裝,會調用ld

  • BFD(Binary File Descriptor library)是基于所有硬件平臺(不同的處理器和目標文件格式)的一個抽象層,基于BFD可以不用關心具體的硬件格式,而進行統(tǒng)一操作,因為BFD中已經包含了這些CPU和可執(zhí)行文件的格式信息,ubuntu下BFD軟件包的名字叫binutils-dev.

  • 第5章

  • windows上的目標文件為COEF格式,而可執(zhí)行文件是PE格式,PE又是COEF格式衍生出來的, 所以將這類文件統(tǒng)稱為PE/COEF格式

  • 64位的Windows中對PE文件格式做了一點小小的修改,叫做PE32+格式,只是將32位的字段換成了64位而已。

  • VC++有一些對C/C++的專用拓展,使用cl編譯的時候,可以使用 /Za來禁用這些拓展,也可以在程序中使用宏__STDC__來查看VC++是否禁用了這些語法拓展

  • 和GNU對象,Windows上的cl就是gcc, link就是ld, dumpbin就是objdump,

  • PE中有兩個ELF文件中不存在的段,分別是.drectve和 .debug$S. .drectve段是編譯器傳遞給鏈接器的指令,其中的flags表示了他的特點。 .debug$S表示的是符號相關的調試信息。其中有原始文件信息和編譯器信息

  • 可以在cl命令中通過/ZI來關掉默認C庫的鏈接指令

  • PE文件為了兼容DOS的MZ文件結構在PE文件中加入了DOS的相關設置,所以將windows下的可執(zhí)行文件在DOS上運行的時候會輸出"This program cannot be run in DOS"

  • 第3部分 裝在與動態(tài)鏈接

    第 6章

  • 程序和進程的區(qū)別,程序就是菜譜,是一個靜態(tài)概念, 進程就是菜,是一個動態(tài)概念。

  • 程序的尋址空間由CPU的位數決定,所以32位下的程序尋址空間是 2 32 b i t 2^{32} bit 232bit即4GB, 64位下是17179869184 GB, 32位下C語言指針的長度是4字節(jié),64位系統(tǒng)下長度是8字節(jié).

  • 對于一個32位的程序,尋址空間雖然是4GB,但是程序并不能全部使用,例如在Linux下,1GB是留給操作系統(tǒng)的,剩下的3GB給進程,且這3GB內存程序也不能完全使用,還有一部分給其他用途; 在Windows上,默認情況下2GB留給系統(tǒng),2GB留給進程, 但是可以通過修改winows根目錄下Boot.ini文件調整內存分配和linux下一樣。
    1995年Pentium Pro CPU使用PAE(Physical Address Extension),將地址線擴充到了36位,所以理論上計算機可以尋址的空間變成了64G, 但是進程的尋址空間仍然是4G(32位系統(tǒng)下指針是4個字節(jié))

    為了使應用程序能使用超過32位的內存,Windows上可以使用AWE(Address Window Extension)的方式, 在Linux上可以使用mmap(),但是這只是一種補救32地址線的方法, 在原來16位的DOS上也曾有過這樣的做法。

  • 為了提高內存的使用效率,采用動態(tài)裝入的辦法,動態(tài)裝入由兩種方式,第一種是覆蓋裝入,即每次將要使用的模塊裝入內存,不使用的調出,這樣,調用的程序可以共享同一塊內存區(qū)域,使用之前先要將程序的所有調用關系組織成一個樹狀結構。但是這種方式需要保證兩點, 1) 調用路徑上的模塊都應該存在,2)不能存在跨樹調用。 第二種是葉映射的方式,即將內存和文件存在的磁盤空間都劃分成一個一個的頁(通常是4KB),在程序使用的時候將相應的頁調入,決定頁面替換的算法由先進先出(FIFO)和最近最長使用算法等。
    由于每次將同一頁程序裝入內存中時的地址可能并不一樣,所以加入按實際物理地址進行操作,那每次都需要重新讀取地址,所以MMU就用來在實際物理地址和虛擬地址之間轉換,
  • 進程的建立過程:
    4.1 創(chuàng)建虛擬地址空間,也就是分配一個頁目錄。
    4.2 讀取可執(zhí)行文件頭,建立虛擬地址空間和可執(zhí)行文件的對應關系(可執(zhí)行文件被裝載時其實就是被映射的虛擬空間,所以也被稱為映像文件)
    4.3 將CPU指令寄存器設置成可執(zhí)行文件的入口(也就是可執(zhí)行文件代碼段的其實地址)
  • ELF文件的鏈接視圖和執(zhí)行視圖,鏈接視圖是按照Section分配,執(zhí)行試圖又是Segment,Segment是將相同屬性(只讀,可讀寫,可讀可執(zhí)行)的Section作為一個Segment. 一般在鏈接的時候說"段"指的就是Section,在裝載的時候說"段"指的是Segment
    readelf -S xx可以輸出可執(zhí)行文件中的section,而readelf -l xx可以輸出可執(zhí)行文件中的Segment(即程序頭表),即怎樣被裝入進程空間, Segment中只有類型是LOAD的部分會被映射,這部分在裝載之后又會被映射到兩段VMA,分別是可讀可寫的部分和可讀可執(zhí)行的部分,
  • ELF可執(zhí)行文件和動態(tài)鏈接文件都有一個結構叫程序頭表,保存著程序被裝載時候的Segment信息,而靜態(tài)目標文件沒有這個程序頭表,因為目標文件不需要被裝載。
  • 操作系統(tǒng)會通過給進程空間劃分出一個個的VMA來管理進程的虛擬空間,基本原則是將相同權限屬性,有相同映像文件的映射成一個VMA,一般包含四個區(qū)域:代碼VMA,數據VMA,堆VMA,棧VMA,棧通常也叫堆棧。
  • 程序員可以操作的進程空間,理論上linux下是3GB,windows下是2GB,但是操作系統(tǒng)為了防止惡意攻擊,使用了一種叫做隨機地址空間分布的技術,使得真正的堆區(qū)可用空間比理論上要少。
  • 在Linux系統(tǒng)中,為了使得物理內存的使用效率更高,通常不是一個虛擬頁面對應一個物理頁面也是會有一定的重合,重合部分的頁面映射兩次,所以裝載視圖中每一Segment 的其實地址就不再必須是4096的倍數了。
  • 一個進程在剛開始運行的時候,操作系統(tǒng)會預先把系統(tǒng)的環(huán)境變量和命令行參數傳遞到進程的堆棧(棧)中,在main函數開始執(zhí)行的時候,main函數的兩個參數args和argv[]兩個參數就是從這里傳遞進來的,分別表示命令參數的數量和指向命令行傳入參數的指針數組。
  • ELF文件的裝載過程: fork() -> execve() -> sys_execve()【系統(tǒng)調用,用于參數檢查和復制】 -> do_execve()【讀取文件頭部的128字節(jié),決定執(zhí)行程序,如果第一行是#!則會解析這之后的字符串,以確定解釋器的路徑,例如#!/usr/bin/python】->load_elf_binary()->do_execve() -> sys_execve()【從內核態(tài)返回用戶態(tài)】
  • PE文件的裝載過程:讀取文件的第一頁(段表,DOS文件頭,PE文件頭)-> 檢查目標地址是否可用不可用則換一個裝載地址(不可用一般只出現在dll的裝載中)-> 按照段表一一裝載各個段, 裝載所以DLL -> 解析符號 -> 建立主線程并啟動進程
  • 第7章 動態(tài)鏈接

  • 靜態(tài)鏈接的缺點: 1). 內存和磁盤空間的浪費,(會在內存和磁盤中存在多份同一程序的拷貝),2). 程序開發(fā)和發(fā)布時不得不重新鏈接一遍所有文件,每次都需要用戶下載新的連接之后的可執(zhí)行文件
  • 動態(tài)鏈接的優(yōu)點:程序的可拓展性和兼容性,缺點:“DLL Hell"即由于dll文件接口的改變使得程序無法運行而這種錯誤事先難以得知, 還有由于在運行時鏈接所以會使得程序運行的速度相對變慢。
  • 編譯共享so文件的命令gcc -fPIC -shared -o Lib.so Lib.c, 共享目標文件so的裝載地址是從0開始的,所以會在運行時確定地址。
  • 靜態(tài)共享庫和靜態(tài)鏈接庫以及動態(tài)鏈接庫都不同,本身屬于動態(tài)庫,沒有被包含到一個可執(zhí)行文件當中,但是它在運行時的地址是事先已經分配好的。
  • 靜態(tài)鏈接文件是鏈接時重定位,動態(tài)鏈接文件是裝載時重定位,又叫基址重置;但是裝載時重定位的一個大問題是無法實現多個進程的公用,解決辦法是地址無關代碼(PIC),先將將so文件分為四部分:1) 模塊內部的函數調用 2) 模塊內部的數據訪問, 3) 模塊外部的函數調用 4) 模塊外部的數據訪問。 編譯器實際上沒法知道一個函數或者變量是來自外部還是外部,所以編譯器拓展 _declspec(dllimport)用于指定來自外部或者內部。
  • 如何判斷一個動態(tài)共享目標文件(DSO)是否是PIC的代碼: readelf -d xx. so | grep TEXTREL如果沒有任何輸出則是PIC的,因為TEXTREL表示代碼段重定位表地址,PIC不存在這個地址。
  • 地址無關技術也可以用于可執(zhí)行文件,即地址無關可執(zhí)行文件(PIE)相應的GCC編譯參數為-fPIE或-fpie.
  • 為了防止DSO文件在運行時鏈接過程耗費太多的時間,采用延遲綁定(Lazy Binding)機制。即在第一次用到的時候進行地址綁定。
  • 可執(zhí)行文件動態(tài)鏈接的過程時,操作系統(tǒng)啟動動態(tài)鏈接器,即加載ld.so文件并將控制權交給它,它將可執(zhí)行文件需要的共享文件動態(tài)加載完之后,控制權再交給可執(zhí)行文件。ELF文件的。interp段里面保存著一個字符串,這個字符串是動態(tài)鏈接器的路徑,再linux下,幾乎所有ELF文件的動態(tài)鏈接器的路徑都是lib/ld.linux.so.2, 其他*nix系統(tǒng)可能會有差異, 這個路徑是一個軟鏈接,真正的文件是Glibc庫的一部分,升級Glibc庫也會升級動態(tài)鏈接器,但是軟連接總是指向動態(tài)鏈接器文件,不需要手動修改。
  • 動態(tài)鏈接文件中最重要的段時.dynamic段,類似于靜態(tài)鏈接文件的ELF文件頭,里面保存了依賴于那些共享對象,動態(tài)鏈接符號表的位置等等,ldd命令可以查看一個共享庫依賴于哪些共享庫。所依賴的庫中l(wèi)inux.gate.so.1時一個在文件系統(tǒng)中不存在的文件,其加載地址在進程的內核區(qū),
  • 動態(tài)鏈接庫中往往包含了兩個表.dynsym 和.symtab。其中 .symtab保存了所有的符號,包括.dynsym中的符號,和靜態(tài)鏈接的.symtab表類似,動態(tài)鏈接庫也需要很多輔助的表,例如靜態(tài)的符號字符串表.strtab 對應動態(tài)符號字符串表.dynstr 為了加快符號查找過程,還需要,符號哈希表 .hash可以用命令readelf -sD xx.so查看動態(tài)符號表和它的哈希表。
  • 普通共享對象的鏈接和裝載由動態(tài)鏈接器完成,但是動態(tài)鏈接器本身也是一個共享對象,所以要求動態(tài)鏈接器共享對象本身不依賴任何其他的共享對象,且其需要的全局變量和靜態(tài)變量的重定位工作有它自己完成,對于第二個要求,需要一段精妙的代碼完成,這被稱為自舉。
  • 第七章7.6節(jié)以后略去不寫

    第八章

  • 在開發(fā)共享庫的時候,使用C的接口會讓事情簡單很多,因為C++非常復雜,支持模板等很多復雜操作,很容易破壞共享庫的兼容性(ABI)
  • 為了解決兼容性,共享庫的命名采用libxxx.so.x.y.z其中x表示主版本號,y表示次版本號,z表示發(fā)布版本號,x表四重大升級,y表示增量升級,z表示對一些錯誤的修正和改進,相同的x和y,不同的z的共享庫完全兼容,
  • solaris和linux等系統(tǒng)中,會采用SO-NAME的方式解決存在多個相同功能的共享庫的問題,SO-NAME時共享庫的文件名.主版本號命名,例如,假如系統(tǒng)中存在libxxx.so.2.6.1和libxx.so.2.5.3則,在同目錄下會存在一個名為libxx.so.2的軟連接,并且指向最新版本的共享庫libxx.so.2.6.1。在編譯的時候,SO-NAME也會寫入到共享庫的.dynamic段中,防止寫入完整名稱之后遇到共享庫升級而找不到的情況。
  • 在linux中安裝或更新一個共享庫的時候需要運行l(wèi)dconfig命令,它會自動將共享庫目錄中的軟連接指向最新的共享庫,對于新建的共享庫則會新建軟鏈接。
  • 總結

    以上是生活随笔為你收集整理的程序员的自我修养 - 读书笔记文字版的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。