當掌握越來越多的基礎知識之后,你所看到的代碼視角和你之前看代碼的視角會發(fā)生一個翻天覆地的變化,就像你寫代碼看到的是一行一行代碼的邏輯,而高級程序員看到的是一行一行指令或者你寫函數(shù)調用是一個正常的函數(shù)調用,其他人看到的是調用鏈背后被調用的情況,所以學東西盡量學習一些基礎,這樣能夠帶給我們很不一樣的編程體驗,也能夠讓你了解整個程序的本質。當遇到瓶頸之后,更應該多學一些基礎知識來豐富自己的眼界。
首先看下編譯的過程,
源代碼會經(jīng)過編譯器,首先編譯成匯編文件,匯編文件經(jīng)過匯編器變成目標文件。在目標文件當中,函數(shù)調用地址是沒有被真正的鏈接起來的,鏈接的過程是需要經(jīng)過鏈接器,把目標文件當中相關的地址信息給鏈接起來,最后形成可執(zhí)行的文件。
c編譯舉例
這是一個簡單的add函數(shù),在main方法里面調用這個add函數(shù),然后進行打印。
生成目標文件
gcc -c main.c
用gcc -c的命令可以生成一個目標文件,
看下生成的目標文件里的地址信息
objdump -d main.o
objdump反編譯看下目標文件存了哪些信息,
這是一個.test段,程序最終在內存上面或磁盤上面存儲的時候,它不是無規(guī)律的存儲,最后被翻譯成機器碼之后,也是一段一段存儲的,每一段所存的內容是不一樣的,像.test段存儲的就是正常的代碼段也是函數(shù)段,而聲明的全局變量會存在.data段或.bss段。
這里只需要理解,我們寫的代碼被翻譯成機器碼大概的分段邏輯就行了。
左邊是這條指令的地址0 4 5 8 .... ,就是我們寫的程序加載到內存當中的時候是被加載成一條一條指令,然后每一個指令都會對應一個特定的地址,cpu在取的時候,就會取這個地址上面的信息,就可以知道這條指令地址所對應指令的具體內容。目標文件的這個地址是相對地址,相對于當前段的地址,當前段是.test段,所以是從0開始 按順序排下來。
callq在匯編里面是調用函數(shù)的指令,這里寫的是33 ,但其實在真正目標文件被鏈接成可執(zhí)行文件之后,33會變成add函數(shù)的絕對地址。
被鏈接成可執(zhí)行文件之后,看下整個代碼地址的變化,用gcc命令編譯了一個可執(zhí)行文件,反匯編看下,將.test段的地址列出來了,它已經(jīng)不是相對于.test段的相對地址了,而是一個絕對地址。
然后看下調用callq add函數(shù)的時候 ,1149所對應的首地址是add函數(shù)的第一行。<>括號在真正的機器代碼中是不存在的,反匯編為了增加可讀性才顯示的。
在看了程序是怎么被編譯成可執(zhí)行文件之后,我們又知道了可執(zhí)行文件里面,每一條指令所對應的地址代表什么意思之后,來看下是如何被加載?
這里要明白一點,程序是在內存里面被執(zhí)行的,被加載到內存之后,cpu才能從內存里面讀取并執(zhí)行,所以有一個從磁盤加載到內存的過程,這個過程由加載器去完成的。
提到內存的話,就要提到cpu的實模式和保護模式。
在很早之前,cpu在實模式時期,我們的程序所使用的地址都是物理地址,就是真正的在內存芯片上所能看到的物理地址,使用物理地址之后,就會導致我們寫的程序被編譯成可執(zhí)行文件之后,可執(zhí)行文件是由鏈接器編譯成鏈接腳本生成的,然后在鏈接腳本里面可以指定程序的首地址,如果要指定首地址(有一個默認的首地址),在實模式下,指定了當前編譯程序的首地址之后,那它被加載到物理地址之后,這個首地址就只能是真正的被加載到物理地址的那個地方,如果它的首地址比如是0x10,那它被加載到的物理地址的首地址如果不是0x10 就會導致后面那些指令的順序出現(xiàn)問題,因為指令是順序排布的,就會導致后面的那些指令地址和可執(zhí)行文件里面描述的這些指令地址是不吻合的。
這樣會導致callq函數(shù)會調用到錯誤的地址,所以在cpu的實模式下,調用程序,程序在執(zhí)行的時候,它的首地址要固定住,這樣就會導致一個問題就是得考慮調得那個地址是不是可用的,調用期間內存是不是可用的,所以會演變成后面的cpu保護模式。
cpu保護模式能夠讓程序使用的是一個虛擬地址,現(xiàn)在的64位系統(tǒng)都是使用的頁式管理,基于這個分析一下。要明白虛擬地址,首先要明白地址空間的概念,地址空間可以理解為進程能用的一個地址范圍,比如進程能用的內存是512G,然后由于程序經(jīng)過編譯之后是分段的,就認為這512G里面,0-10G是屬于.test段,10-20G是屬于.data段,20-200G屬于堆空間,其他范圍分:棧空間是哪個范圍,內核空間又是哪個范圍,只是將這段區(qū)間劃分為了具體的內容所在的這段范圍,但是不會實際的在內存上去分配這些內存,只是將范圍劃分出來,而實際保存的也是這些范圍,當需要用到這些范圍地址的時候,cpu才會去通過MMU列表里面去尋找這個虛擬地址所對應的物理地址,如果沒有這個映射關系,才會去真正的分配物理內存創(chuàng)建映射關系,如果可執(zhí)行文件一開始沒有加載到內存,那么后續(xù)地址缺失是如何找到磁盤上面的文件位置的?所以需要看下可執(zhí)行文件里面到底有哪些信息?
這里列出了可執(zhí)行文件里面段的頭部信息,在段的頭部信息里面包含了虛擬地址、文件的偏移量,文件的偏移量可以理解為磁盤信息,可以通過偏移量去定位到在磁盤上的哪個位置,所以操作系統(tǒng)是可以這樣做的:在可執(zhí)行文件里面能夠讀到段地址還有文件偏移地址,所以在進程被加載執(zhí)行的時候,剛開始被加載的時候,是可以為這個進程創(chuàng)建頁表項,頁表項是能夠覆蓋每個段的地址還有文件偏移的地址,但是這個時候,只是標記這個頁表項所映射的這個映射關系,只是標記,并沒有真正的分配實際的物理內存,這樣等到頁缺失的時候 ,夠找到這個頁表項并并且能夠從這個頁表項的標記去發(fā)現(xiàn)沒有分配物理內存,這個時候再從磁盤上去讀,再建立映射關系,這樣就能夠達到在真正使用的時候再去分配物理內存的目的了。
審核編輯:劉清
-
cpu
+關注
關注
68文章
10813瀏覽量
210882 -
GCC
+關注
關注
0文章
105瀏覽量
24807 -
編譯器
+關注
關注
1文章
1617瀏覽量
49019 -
匯編器
+關注
關注
0文章
31瀏覽量
11229
原文標題:程序從編譯到被執(zhí)行的流程
文章出處:【微信號:IC學習,微信公眾號:IC學習】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論