中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

程序是怎么執(zhí)行的

2018-07-20    來源:編程學習網(wǎng)

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

  Docker是一個建立在操作系統(tǒng)+編譯器基礎之上的系統(tǒng),所以了解操作系統(tǒng),編譯器以及程序運行機制對我們理解Docker來說非常重要。本文是一個自己的體會,有很多不精確的地方,目的是希望大家多關注低層,多修煉內(nèi)功,多讀好書。

  一直想寫篇文章來說明在程序運行過程中操作系統(tǒng)都干了些什么事。下面我試著說明:

  首先,任何程序都是有格式的,所謂無規(guī)矩不成方圓,任何美的,精巧的事物都是精密組織的,程序也一樣。我之前用的最多的是c#與java,有趣的是,當時很多人嘲笑java與c#們一直在用腳本寫程序,大概在他們眼里c與c++才是真正的程序。但是,現(xiàn)實就是現(xiàn)實,其實我們都是在一個叫做虛擬機的程序下寫托管代碼,它掌握著程序的編譯,鏈接,加載,映射與最終執(zhí)行與終止。它就是操作系統(tǒng),準確的講是操作系統(tǒng)+編譯器。他們是真正的元虛擬機。

  然后我來解釋下如何運行一個程序:

  程序是精巧與復雜的,熟悉它以后你也會覺得它是脆弱的,因為只要有一個bit發(fā)生錯誤,整個系統(tǒng)就會崩潰。這個系統(tǒng)就是執(zhí)行文件格式,在linux下叫elf(executable linkable format)而windows下叫pe(portable execute)。我想寫操作系統(tǒng)第一步就是制定這個規(guī)則,不然一切都沒有規(guī)律。所以我想linus牛,但是ken tomason有過之而無不及,畢竟你是在人家基礎之上發(fā)展而來的,計算機世界就是如此沒辦法,誰讓你在人家下面呢?

  我以linux系統(tǒng)為例,簡單講講程序由編譯 鏈接裝載與執(zhí)行。elf文件格式分為很多段—section,總體分為只讀可執(zhí)行的代碼段與可讀可寫的數(shù)據(jù)段。.txt就是典型的代碼段,.data .rodata .symbl .rel .got .plt都是數(shù)據(jù)段。那么,編譯器負責將程序員寫的程序,編譯成elf文件,代碼,注視,代碼行對應機器碼信息,就是調(diào)試信息啦會進去.txt .code .comment .debug段,常量與靜態(tài)變量進入.data .rodata .bss。接下來,編譯器將引用的頭文件中的代碼(特指靜態(tài)編譯)與引用的glibc中的庫函數(shù)打包(鏈接)到整個可執(zhí)行文件中,然后在elf文件中設置文件頭信息,如段表位置,程序入口位置等信息。當然,這里不得不提的是符號表,與重定位表,他們是整個程序最終能跑起來的關鍵。gcc是靠符號,或者說程序是靠符號來鏈接的,不管是函數(shù)還是變量,都是符號而已,所以從側(cè)面講,寫程序跟寫文章沒啥區(qū)別。程序就像個圖書館,每個函數(shù)與變量都是書,鏈接程序好比在圖書館看書,當你看到一個點時,就會叫你去某某位置拿另一本書,翻到特定位置開始繼續(xù)讀,如果沒找到就會爆出鏈接錯誤。而重定位表就是一次性講所有對需要跳轉(zhuǎn)的位置進行更改,以確保程序中不存在沒有拿到手的書。

  好,現(xiàn)在程序已經(jīng)鏈接好了,接下來就是操作系統(tǒng)進行裝載與執(zhí)行了。當然這是靜態(tài)的鏈接,動態(tài)鏈接會稍微復雜,會寫很多,這里不討論。操作系統(tǒng)會打開elf文件的裝載視圖,它能根據(jù)裝載視圖的段表—segment這跟section在中文都是段,沒辦法!這個視圖是將數(shù)據(jù)與代碼分開的,相似section鏈接在一起,所以數(shù)量也比section少很多,目的是在裝載時節(jié)約內(nèi)存。因為,段映射到內(nèi)存是要地址對齊的,如按照地址4096(一般簇大小為4k)整除來對齊,這樣做是有好處的,能減少內(nèi)存碎片,加快磁盤讀寫速度,磁盤最小扇區(qū)512byte,所以整數(shù)倍讀取能少一次尋址,當然效率更高。這在游戲引擎,數(shù)據(jù)庫設計領域比較多見,畢竟io是最大瓶頸,所以再這程序時也要考慮對象占用內(nèi)存大小是否是操作系統(tǒng)最小簇的整數(shù)倍來判斷一個程序是否是高人所做。

  回來,操作系統(tǒng)會最先讀取可執(zhí)行的文件頭,因為里面有運行程序的信息,如段表位置,程序入口,程序類型等。對于操作系統(tǒng)最重要的是段表與程序入口。其中段表就是elf中有多少段,每個段在文件中的偏移,入口則是常說得main函數(shù)的虛擬地址。這里就出現(xiàn)一個問題,程序非得以main函數(shù)開始嗎?其實看出來了,不用!只是gcc認定符號main為c語言的入口,其他程序照抄罷了,當然你可以加入編譯條件更改入口即可。gcc是stallman寫的,他是個黑客,全世界只要運行c的地方,他都能黑,呵呵。

  好了,操作系統(tǒng)在讀取可執(zhí)行程序頭時做了三件事:1.創(chuàng)建虛擬內(nèi)存空間來容納一個進程,2.根據(jù)文件頭內(nèi)容建立程序虛擬內(nèi)存地址與elf文件的映射關系表,vma(virtual memory area)結(jié)構(gòu),3.初始化程序的棧空間與堆空間。下面解釋下這三個過程。

  1,虛擬內(nèi)存。虛擬內(nèi)存是編譯器與操作系統(tǒng)的一個約定。任何程序在編譯無鏈接時得地址都是虛擬地址。為什么要用虛擬地址這個問題說來話長。話說在很久以前,大家都很窮,都沒內(nèi)存,但是要運行的程序很多,系統(tǒng)不可能為每個程序分配單獨的內(nèi)存,同時領導還要求同時所有程序都要運行,咋辦呢?辦法總比問題多,咱可以分時嘛,你上完cpu我再上,但是大家各自在用cpu時,其他只能看著,直到一個人說"下一個",這個人不管在干嘛都得放棄,讓其他人用cpu。這樣對所有人都公平,而且每個人在用cpu是能感覺到cpu只被它獨有,用戶體驗還挺好。所以一次解決可所有問題。而,這個組織人,就是那個喊“下一個”的家伙就是操作系統(tǒng)。那,說這么多,跟虛擬地址有啥關系呢?其實仔細想想如果大家都是用物理地址,而彼此在運行時都獨占系統(tǒng)資源,那前一個程序修改了我的數(shù)據(jù)咋辦,得了,都由操作系統(tǒng)說了算吧,它做內(nèi)存映射的維護,大家都用統(tǒng)一的地址空間,但是運行時映射到不同的物理內(nèi)存互不干擾來。所以你可以看到所有l(wèi)inux程序都從相同的虛擬地址開始執(zhí)行。

  2.建立內(nèi)存到文件得映射。我們知道,程序都不是一次性加載到內(nèi)存的,而是一段段的,這是由著名的copy on write規(guī)則約束而來的。而這一段也是規(guī)定好大小的一般是操作系統(tǒng)簇的大小,也叫一頁。當程序運行過程中發(fā)現(xiàn)某個數(shù)據(jù)在內(nèi)存中沒有則會報一個頁讀取錯誤,并觸發(fā)操作系統(tǒng)的缺頁中斷。這時就要靠操作系統(tǒng)通過讀取elf文件頭建立的從文件系統(tǒng)到虛擬內(nèi)存的映射來獲取了。它等于是程序運行時到程序得一個索引結(jié)構(gòu),存儲了運行時程序虛擬內(nèi)存地址到文件地址的對應表。

  3.好了,第三步最簡單,就是操作系統(tǒng)載人main函數(shù)后面跟的那個char argc與char*argv了。他們是程序啟動參數(shù)。還要載入程序運行的環(huán)境變量,棧空間,堆空間,也就是靜態(tài)數(shù)據(jù)與全局變量部分。然后把程序執(zhí)行寄存器指向程序開始的地方。開始執(zhí)行!看似簡單,但是很復雜的過程開始了!

  好了,這就是簡單的程序如何被操作系統(tǒng)執(zhí)行的簡單描述,當然這只是靜態(tài)鏈接程序的加載,動態(tài)鏈接稍微復雜點。原理差不多,呵呵。

標簽: linux 代碼 腳本 數(shù)據(jù)庫

版權(quán)申明:本站文章部分自網(wǎng)絡,如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:10個所需的IT技能 助你職場成功

下一篇:20個非常有用的Java程序片段