go編譯器指令集優化
❶ 現在go可以靜態編譯一個程序么
第一步:all.bash
% cd $GOROOT/src
% ./all.bash
第一步有些突兀,因為 all.bash 僅僅調用了其它兩個 shell 腳本;make.bash 和 run.bash。如果你在使用 Windows 或 Plan 9,過程是一樣的,只是腳本擴展名變成了.bat 或.rc。對於本文中的其它腳本,請根據你的系統適當改動。
第二步:make.bash
. ./make.bash --no-banner
main.bash 來源於 all.bash,因此調用退出將正確終止便宜進程。main.bash 有三個主要工作,第一個是驗證編譯 Go 的環境是否完整。完整性檢查在過去幾年中建立,它通常嘗試避免使用已知的破損工具或必然失敗的環境進行編譯。
第三步. cmd/dist
gcc -O2 -Wall -Werror -ggdb -o cmd/dist/dist -Icmd/dist cmd/dist/*.c
一旦可用性檢查完畢,make.bash 將編譯產生 cmd/dist,cmd/dist取代了之前存在於Go 1 之前的Makefile 編譯系統。cmd/dist用來管理少量的pkg/runtime的代碼生成。cmd/dist 是C語言編寫的程序,能夠充分利用系統C編譯器和頭文件來處理大部分主機系統平台的檢測。cmd/dist通常用來檢測主機的操作系統和體系結構,即環境變數$GOHOSTOS和$GOHOSTARCH .如果是交叉編譯的話,變數 $GOOS和$GOARCH可能會由於你的設置而不同。事實上,Go 通常用作跨平台編譯器,只不過多數情況下,主機和目標系統一致而已。接下來,make.bash 調用cmd/dist 的引導參數的支持庫、 lib9、 libbio 和 libmach,使用編譯器套件,然後用自己的編譯器進行編譯。這些工具也是用 C 語言寫的中,但是由系統 C 編譯器編譯產生。
echo "# Building compilers and Go bootstrap tool for host, $GOHOSTOS/$GOHOSTARCH."
buildall="-a"
if [ "$1" = "--no-clean" ]; then
buildall=""
fi
./cmd/dist/dist bootstrap $buildall -v # builds go_bootstrap
使用的編譯器套件 cmd/dist 編譯產生一個版本的gotool,go_bootstrap。但go_bootstrap並不是完整得gotool,比方說 pkg/net 就是孤立的,避免了依賴於 cgo。要編譯的文件的列表以及它們的依賴項,是由cmd/dist編譯的 ,所以十分謹慎地避免引入新的生成依賴項 到 cmd/go。
第四步:go_bootstrap
現在, go_bootstrap 編譯完成了,make.bash 的最後一部就是使用 go_bootstrap 完成 Go 標准庫的編譯,包括整套 gotool 的替換版。
echo "# Building packages and commands for $GOOS/$GOARCH."
"$GOTOOLDIR"/go_bootstrap install -gcflags "$GO_GCFLAGS" \
-ldflags "$GO_LDFLAGS" -v std
第五步:run.bash
現在,make.bash 完成了,運行回到了 all.bash,它將引用 run.bash。run.bash 的工作是編譯和測試標准庫,運行時以及語言測試套件。
bash run.bash --no-rebuild
使用 --no-rebuild 標識是因為 make.bash 和 run.bash 可能都調用了 go install -a std,這樣可以避免重復,--no-rebuild 跳過了第二個 go install。
# allow all.bash to avoid double-build of everything
rebuild=true
if [ "$1" = "--no-rebuild" ]; then
shift
else
echo '# Building packages and commands.'
time go install -a -v std
echo
fi
第六步:go test -a std
echo '# Testing packages.'
time go test std -short -timeout=$(expr 120 \* $timeout_scale)s
echo
下一步 run.bash z則是對標准庫中的所有包進行單元測試,這是使用 testing 包編寫的。由於 $GOPATH 和 $GOROOT 中的代碼存在於同一個命名空間中,我們不能使用 go test,這可能會測試 $GOPATH 中的所有包,所以將創建別名std來標識標准庫中的包。由於有些測試需要很長時間,或耗用大量內存,測試將會通過 -short 標識將其過濾。
第七步 runtime 和 cgo 測試
run.bash的下一節將運行大量對cgo支持的平台測試,運行一些季春測試,編譯 Go 附帶的一些雜項程序。隨著時間的推移,這份雜項程序列表已經變長了,當它們發現自己並不包含在編譯過程中時,沉默將不可避免的被打破。
第八步: go run test
(xcd ../test
unset GOMAXPROCS
time go run run.go
) || exit $?
run.bash的倒數第二步調用了$GOROOT目錄下test文件夾中的編譯器和運行時測試。這其中有描述編譯器和運行時本身的低層級測試。而子目錄 test/bugs 及 test/fixedbugs 中的測試對已知問題和已解決問題進行特別的測試。所有測試的測試驅動器是 $GOROOT/test/run.go,該程序很小,它調用test文件夾中的每個.go 文件。有些 .go 文件在首行上描述了預期的運行結果,例如,程序失敗或是放出特定的輸出隊列。
第九步go tool api
echo '# Checking API compatibility.'
go tool api -c $GOROOT/api/go1.txt,$GOROOT/api/go1.1.txt \
-next $GOROOT/api/next.txt -except $GOROOT/api/except.txt
run.bash的最後一部將調用API工具,API工具的作用是執行 Go 1 約定;導出的符號,常數,函數,變數,類型和方法組成2012年確認的 Go 1 API。Go 1 寫在 api/go1.txt 文件,而 Go 1.1 則寫在 api/go1.1.txt文件中。另一個額外的文件,api/next.txt 描述了G 1.1自後添加到標准庫和運行時中的符號。當 Go 1.2 發布時,這個文件將會成為 Go 1.2 的約定,另一個新的 next.txt 文件也將被創建。這里還有一個小文件,except.txt,它包括 Go 1 約定中被批準的擴展。對文件的增添總是小心翼翼的。
❷ 為什麼要使用 Go 語言,Go 語言的優勢在哪裡
已經有好多程序員都把Go語言描述為是一種所見即所得(WYSIWYG)的編程語言。這是說,代碼要做的事和它在字面上表達的意思是完全一致的。 在這些新語言中,包含D,Go,Rust和Vala語言,Go曾一度出現在TIOBE的排行榜上面。與其他新語言相比,Go的魅力明顯要大很多。Go的成熟特徵會得到許多開發者的欣賞,而不僅僅是因為其誇大其詞的曝光度。下面我們來一起探討一下谷歌開發的Go語言以及談談Go為什麼會吸引眾多開發者: 快速簡單的編譯 Go編譯速度很快,如此快速的編譯使它很容易作為腳本語言使用。關於編譯速度快主要有以下幾個原因:首先,Go不使用頭文件;其次如果一個模塊是依賴A的,這反過來又取決於B,在A裡面的需求改變只需重新編譯原始模塊和與A相依賴的地方;最後,對象模塊裡麵包含了足夠的依賴關系信息,所以編譯器不需要重新創建文件。你只需要簡單地編譯主模塊,項目中需要的其他部分就會自動編譯,很酷,是不是? 通過返回數值列表來處理錯誤信息 目前,在本地語言裡面處理錯誤的方式主要有兩種:直接返回代碼或者拋異常。這兩種都不是最理想的處理方式。其中返回代碼是非常令人沮喪的,因為返回的錯誤代碼經常與從函數中返回的數據相沖突。Go允許函數返回多個值來解決這個問題。這個從函數裡面返回的值,可以用來檢查定義的類型是否正確並且可以隨時隨地對函數的返回值進行檢查。如果你對錯誤值不關心,你可以不必檢查。在這兩種情況下,常規的返回值都是可用的。 簡化的成分(優先於繼承) 通過使用介面,類型是有資格成為對象中一員的,就像Java指定行為一樣。例如在標准庫裡面的IO包,定義一個Writer來指定一個方法,一個Writer函數,其中輸入參數是位元組數組並且返回整數類型值或者錯誤類型。任何類型實現一個帶有相同簽名的Writer方法是對IO的完全實現,Writer介面。這種是解耦代碼而不是優雅。它還簡化了模擬對象來進行單元測試。例如你想在資料庫對象中測試一個方法,在標准語言中,你通常需要創建一個資料庫對象,並且需要進行大量的初始化和協議來模擬對象。在Go裡面,如果該方法需要實現一個介面,你可以創建任何對該介面有用的對象,所以,你創建了MockDatabase,這是很小的對象,只實現了幾個需要運行和模擬的介面——沒有構造函數,沒有附件功能,只是一些方法。 簡化的並發性 相對於其他語言,並發性在Go裡面顯得更加容易。把『go』關鍵字放在任意函數前面然後那個函數就會在其go-routine自動運行(一個很輕的線程)。go-routines是通過通道進行交流並且基本上封鎖了所有的隊列消息。普通工具對相互排斥是有用,但是Go通過使用通道來踢掉並發性任務和坐標更加容易。 優秀的錯誤消息 所有與Go相似的語言,自身作出的診斷都是無法與Go相媲美的。例如,一個死鎖程序,在Go運行時會通知你目前哪個線程導致了這種死鎖。編譯的錯誤信息是非常詳細全面和有用的。 其他 這里還有許多其他吸引人的地方,下面就一概而過的介紹一下,比如高階函數、垃圾回收、哈希映射和可擴展的數組內置語言(部分語言語法,而不是作為一個庫)等等。 當然,Go並不是完美無瑕。在工具方面還有些不成熟的地方和用戶社區較小等,但是隨著谷歌語言的不斷發展,肯定會有整治措施出來。盡管許多語言,尤其是D、Rust和Vala旨在簡化C++並且對其進行簡化,但它們給人的感覺仍是「C++看上去要更好」。
【Go語言的優勢】
可直接編譯成機器碼,不依賴其他庫,glibc的版本有一定要求,部署就是扔一個文件上去就完成了。
靜態類型語言,但是有動態語言的感覺,靜態類型的語言就是可以在編譯的時候檢查出來隱藏的大多數問題,動態語言的感覺就是有很多的包可以使用,寫起來的效率很高。
語言層面支持並發,這個就是Go最大的特色,天生的支持並發,我曾經說過一句話,天生的基因和整容是有區別的,大家一樣美麗,但是你喜歡整容的還是天生基因的美麗呢?Go就是基因裡面支持的並發,可以充分的利用多核,很容易的使用並發。
內置runtime,支持垃圾回收,這屬於動態語言的特性之一吧,雖然目前來說GC不算完美,但是足以應付我們所能遇到的大多數情況,特別是Go1.1之後的GC。
簡單易學,Go語言的作者都有C的基因,那麼Go自然而然就有了C的基因,那麼Go關鍵字是25個,但是表達能力很強大,幾乎支持大多數你在其他語言見過的特性:繼承、重載、對象等。
豐富的標准庫,Go目前已經內置了大量的庫,特別是網路庫非常強大,我最愛的也是這部分。
內置強大的工具,Go語言裡面內置了很多工具鏈,最好的應該是gofmt工具,自動化格式化代碼,能夠讓團隊review變得如此的簡單,代碼格式一模一樣,想不一樣都很困難。
跨平台編譯,如果你寫的Go代碼不包含cgo,那麼就可以做到window系統編譯linux的應用,如何做到的呢?Go引用了plan9的代碼,這就是不依賴系統的信息。
內嵌C支持,前面說了作者是C的作者,所以Go裡面也可以直接包含c代碼,利用現有的豐富的C庫。
❸ Go語言編譯器TinyGo,基於LLVM,在微控制器和小系統上編譯和運行
TinyGo是一個為微控制器、WebAssembly(Wasm)和命令行工具等小型場景設計的Go語言編譯器。TinyGo重用了Go語言工具和LLVM使用的庫,以編譯用Go語言編寫的程序。目前,該項目在GitHub上已經積累了10.1k的Star。
如下為一個示常式序,當運行在任何支持的帶板載LED的主板上時,則會點亮內置LED。
上述程序可以在單片機、Adafruit ItsyBitsy M0微控制器或任何支持的帶內置LED的板上進行編譯和不需要修改的運行,只要設置正確的TinyGo編譯器目標即可。例如,設置如下目標可以編譯和點亮 單片機。
項目概述
TinyGo項目旨在將Go語言引入到具有單進程或核心的微控制器和小系統。TinyGo類似於emgo,但主要的區別在於作者想要保留Go內存模型。另一個區別在於TinyGo在內部使用LLVM,因而可以獲得更小更高效的代碼以及更高的靈活性。
創建TinyGo項目的初衷是,如果Python可以在微控制器上運行,Go語言當然也應該能夠在更低級微設備上運行。
支持設備
你可以為微控制器、WebAssembly和Linux編譯TinyGo程序。目前,TinyGo支持以下85種微處理器板。
更多技術細節請參閱原項目。
❹ 「干貨」嵌入式Linux系統移植的四大步驟(上)
在學習系統移植的相關知識,在學習和調試過程中,發現了很多問題,也解決了很多問題,但總是對於我們的開發結果有一種莫名其妙的感覺,糾其原因,主要對於我們的開發環境沒有一個深刻的認識,有時候幾個簡單的命令就可以完成非常復雜的功能,可是我們有沒有想過,為什麼會有這樣的效果?
如果沒有去追問,只是機械地完成,並且看到實驗效果,這樣做其實並沒有真正的掌握系統移植的本質。
在做每一個步驟的時候, 首先問問自己,為什麼要這樣做,然後再問問自己正在做什麼? 搞明白這幾個問題,我覺得就差不多了,以後不管更換什麼平台,什麼晶元,什麼開發環境,你都不會迷糊,很快就會上手。對於嵌入式的學習方法,我個人方法就是:從宏觀上把握(解決為什麼的問題),微觀上研究(解決正在做什麼的問題),下面以自己學習的arm-cortex_a8開發板為目標,介紹下自己的學習方法和經驗。
嵌入式Linux系統移植主要由四大部分組成:
一、搭建交叉開發環境
二、bootloader的選擇和移植
三、kernel的配置、編譯、和移植
四、根文件系統的製作
第一部分:搭建交叉開發環境
先介紹第一分部的內容:搭建交叉開發環境,首先必須得思考兩個問題,什麼是交叉環境? 為什麼需要搭建交叉環境?
先回答第一個問題,在嵌入式開發中,交叉開發是很重要的一個概念,開發的第一個環節就是搭建環境,第一步不能完成,後面的步驟從無談起,這里所說的交叉開發環境主要指的是:在開發主機上(通常是我的pc機)開發出能夠在目標機(通常是我們的開發板)上運行的程序。嵌入式比較特殊的是不能在目標機上開發程序(狹義上來說),因為對於一個原始的開發板,在沒有任何程序的情況下它根本都跑不起來,為了讓它能夠跑起來,我們還必須要藉助pc機進行燒錄程序等相關工作,開發板才能跑起來,這里的pc機就是我們說的開發主機,想想如果沒有開發主機,我們的目標機基本上就是無法開發,這也就是電子行業的一句名言:搞電子,說白了,就是玩電腦!
然後回答第二個問題,為什麼需要交叉開發環境?主要原因有以下幾點:
原因 1: 嵌入式系統的硬體資源有很多限制,比如cpu主頻相對較低,內存容量較小等,想想讓幾百MHZ主頻的MCU去編譯一個Linux kernel會讓我們等的不耐煩,相對來說,pc機的速度更快,硬體資源更加豐富,因此利用pc機進行開發會提高開發效率。
原因2: 嵌入式系統MCU體系結構和指令集不同,因此需要安裝交叉編譯工具進行編譯,這樣編譯的目標程序才能夠在相應的平台上比如:ARM、MIPS、 POWEPC上正常運行。
交叉開發環境的硬體組成主要由以下幾大部分 :
1.開發主機
2.目標機(開發板)
3.二者的鏈接介質,常用的主要有3種方式:(1)串口線 (2)USB線 (3)網線
對應的硬體介質,還必須要有相應的軟體「介質」支持:
1.對於串口,通常用的有串口調試助手,putty工具等,工具很多,功能都差不多,會用一兩款就可以;
2.對於USB線,當然必須要有USB的驅動才可以,一般晶元公司會提供,比如對於三星的晶元,USB下載主要由DNW軟體來完成;
3.對於網線,則必須要有網路協議支持才可以, 常用的服務主要兩個
第一:tftp服務:
主要用於實現文件的下載,比如開發調試的過程中,主要用tftp把要測試的bootloader、kernel和文件系統直接下載到內存中運行,而不需要預先燒錄到Flash晶元中,一方面,在測試的過程中,往往需要頻繁的下載,如果每次把這些要測試的文件都燒錄到Flash中然後再運行也可以,但是缺點是:過程比較麻煩,而且Flash的擦寫次數是有限的;另外一方面:測試的目的就是把這些目標文件載入到內存中直接運行就可以了,而tftp就剛好能夠實現這樣的功能,因此,更沒有必要把這些文件都燒錄到Flash中去。
第二: nfs服務:
主要用於實現網路文件的掛載,實際上是實現網路文件的共享,在開發的過程中,通常在系統移植的最後一步會製作文件系統,那麼這是可以把製作好的文件系統放置在我們開發主機PC的相應位置,開發板通過nfs服務進行掛載,從而測試我們製作的文件系統是否正確,在整個過程中並不需要把文件系統燒錄到Flash中去,而且掛載是自動進行掛載的,bootload啟動後,kernel運行起來後會根據我們設置的啟動參數進行自動掛載,因此,對於開發測試來講,這種方式非常的方便,能夠提高開發效率。
另外,還有一個名字叫 samba 的服務也比較重要,主要用於文件的共享,這里說的共享和nfs的文件共享不是同一個概念,nfs的共享是實現網路文件的共享,而samba實現的是開發主機上 Windows主機和Linux虛擬機之間的文件共享,是一種跨平台的文件共享 ,方便的實現文件的傳輸。
以上這幾種開發的工具在嵌入式開發中是必備的工具,對於嵌入式開發的效率提高做出了偉大的貢獻,因此,要對這幾個工具熟練使用,這樣你的開發效率會提高很多。等測試完成以後,就會把相應的目標文件燒錄到Flash中去,也就是等發布產品的時候才做的事情,因此對於開發人員來說,所有的工作永遠是測試。
通過前面的工作,我們已經准備好了交叉開發環境的硬體部分和一部分軟體,最後還缺少交叉編譯器,讀者可能會有疑問,為什麼要用交叉編譯器?前面已經講過,交叉開發環境必然會用到交叉編譯工具,通俗地講就是在一種平台上編譯出能運行在體系結構不同的另一種平台上的程序,開發主機PC平台(X86 CPU)上編譯出能運行在以ARM為內核的CPU平台上的程序,編譯得到的程序在X86 CPU平台上是不能運行的,必須放到ARM CPU平台上才能運行,雖然兩個平台用的都是Linux系統。相對於交叉編譯,平常做的編譯叫本地編譯,也就是在當前平台編譯,編譯得到的程序也是在本地執行。用來編譯這種跨平台程序的編譯器就叫交叉編譯器,相對來說,用來做本地編譯的工具就叫本地編譯器。所以要生成在目標機上運行的程序,必須要用交叉編譯工具鏈來完成。
這里又有一個問題,不就是一個交叉編譯工具嗎?為什麼又叫交叉工具鏈呢?原因很簡單,程序不能光編譯一下就可以運行,還得進行匯編和鏈接等過程,同時還需要進行調試,對於一個很大工程,還需要進行工程管理等等,所以,這里 說的交叉編譯工具是一個由 編譯器、連接器和解釋器 組成的綜合開發環境,交叉編譯工具鏈主要由binutils(主要包括匯編程序as和鏈接程序ld)、gcc(為GNU系統提供C編譯器)和glibc(一些基本的C函數和其他函數的定義) 3個部分組成。有時為了減小libc庫的大小,也可以用別的 c 庫來代替 glibc,例如 uClibc、dietlibc 和 newlib。
那麼,如何得到一個交叉工具鏈呢?是從網上下載一個「程序」然後安裝就可以使用了嗎?回答這個問題之前先思考這樣一個問題,我們的交叉工具鏈顧名思義就是在PC機上編譯出能夠在我們目標開發平台比如ARM上運行的程序,這里就又有一個問題了,我們的ARM處理器型號非常多,難道有專門針對我們某一款的交叉工具鏈嗎?若果有的話,可以想一想,這么多處理器平台,每個平台專門定製一個交叉工具鏈放在網路上,然後供大家去下載,想想可能需要找很久才能找到適合你的編譯器,顯然這種做法不太合理,且浪費資源!因此,要得到一個交叉工具鏈,就像我們移植一個Linux內核一樣,我們只關心我們需要的東西,編譯我們需要的東西在我們的平台上運行,不需要的東西我們不選擇不編譯,所以,交叉工具鏈的製作方法和系統移植有著很多相似的地方,也就是說,交叉開發工具是一個支持很多平台的工具集的集合(類似於Linux源碼),然後我們只需從這些工具集中找出跟我們平台相關的工具就行了,那麼如何才能找到跟我們的平台相關的工具,這就是涉及到一個如何製作交叉工具鏈的問題了。
通常構建交叉工具鏈有如下三種方法:
方法一 : 分步編譯和安裝交叉編譯工具鏈所需要的庫和源代碼,最終生成交叉編譯工具鏈。該方法相對比較困難,適合想深入學習構建交叉工具鏈的讀者。如果只是想使用交叉工具鏈,建議使用下列的方法二構建交叉工具鏈。
方法二: 通過Crosstool-ng腳本工具來實現一次編譯,生成交叉編譯工具鏈,該方法相對於方法一要簡單許多,並且出錯的機會也非常少,建議大多數情況下使用該方法構建交叉編譯工具鏈。
方法三 : 直接通過網上下載已經製作好的交叉編譯工具鏈。該方法的優點不用多說,當然是簡單省事,但與此同時該方法有一定的弊端就是局限性太大,因為畢竟是別人構建好的,也就是固定的,沒有靈活性,所以構建所用的庫以及編譯器的版本也許並不適合你要編譯的程序,同時也許會在使用時出現許多莫名其妙的錯誤,建議讀者慎用此方法。
crosstool-ng是一個腳本工具,可以製作出適合不同平台的交叉編譯工具鏈,在進行製作之前要安裝一下軟體:
$ sudo apt-get install g++ libncurses5-dev bison flex texinfo automake libtool patch gcj cvs cvsd gawk
crosstool腳本工具可以在http://ymorin.is-a-geek.org/projects/crosstool下載到本地,然後解壓,接下來就是進行安裝配置了,這個配置優點類似內核的配置。主要的過程有以下幾點:
1. 設定源碼包路徑和交叉編譯器的安裝路徑
2. 修改交叉編譯器針對的構架
3. 增加編譯時的並行進程數,以增加運行效率,加快編譯,因為這個編譯會比較慢。
4. 關閉JAVA編譯器 ,減少編譯時間
5. 編譯
6. 添加環境變數
7. 刷新環境變數。
8. 測試交叉工具鏈
到此,嵌入式Linux系統移植四大部分的第一部分工作全部完成,接下來可以進行後續的開發了。
第二部分:bootloader的選擇和移植
01 Boot Loader 概念
就是在操作系統內核運行之前運行的一段小程序。通過這段小程序,我們可以初始化硬體設備、建立內存空間的映射圖,從而將系統的軟硬體環境帶到一個合適的狀態,以便為最終調用操作系統內核准備好正確的環境,他就是所謂的引導載入程序(Boot Loader)。
02 為什麼系統移植之前要先移植BootLoader?
BootLoader的任務是引導操作系統,所謂引導操作系統,就是啟動內核,讓內核運行就是把內核載入到內存RAM中去運行,那先問兩個問題:第一個問題,是誰把內核搬到內存中去運行?第二個問題:我們說的內存是SDRAM,大家都知道,這種內存和SRAM不同,最大的不同就是SRAM只要系統上電就可以運行,而SDRAM需要軟體進行初始化才能運行,那麼在把內核搬運到內存運行之前必須要先初始化內存吧,那麼內存是由誰來初始化的呢?其實這兩件事情都是由bootloader來乾的,目的是為內核的運行准備好軟硬體環境,沒有bootloadr我們的系統當然不能跑起來。
03 bootloader的分類
首先更正一個錯誤的說法,很多人說bootloader就是U-boot,這種說法是錯誤的,確切來說是u-boot是bootloader的一種。也就是說bootloader具有很多種類,
由上圖可以看出,不同的bootloader具有不同的使用范圍,其中最令人矚目的就是有一個叫U-Boot的bootloader,是一個通用的引導程序,而且同時支持X86、ARM和PowerPC等多種處理器架構。U-Boot,全稱 Universal Boot Loader,是遵循GPL條款的開放源碼項目,是由德國DENX小組開發的用於多種嵌入式CPU的bootloader程序,對於Linux的開發,德國的u-boot做出了巨大的貢獻,而且是開源的。
u-boot具有以下特點:
① 開放源碼;
② 支持多種嵌入式操作系統內核,如Linux、NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS;
③ 支持多個處理器系列,如PowerPC、ARM、x86、MIPS、XScale;
④ 較高的可靠性和穩定性;
⑤ 高度靈活的功能設置,適合U-Boot調試、操作系統不同引導要求、產品發布等;
⑥ 豐富的設備驅動源碼,如串口、乙太網、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、鍵盤等;
⑦ 較為豐富的開發調試文檔與強大的網路技術支持;
其實,把u-boot可以理解為是一個小型的操作系統。
04 u-boot的目錄結構
* board 目標板相關文件,主要包含SDRAM、FLASH驅動;
* common 獨立於處理器體系結構的通用代碼,如內存大小探測與故障檢測;
* cpu 與處理器相關的文件。如mpc8xx子目錄下含串口、網口、LCD驅動及中斷初始化等文件;
* driver 通用設備驅動,如CFI FLASH驅動(目前對INTEL FLASH支持較好)
* doc U-Boot的說明文檔;
* examples可在U-Boot下運行的示常式序;如hello_world.c,timer.c;
* include U-Boot頭文件;尤其configs子目錄下與目標板相關的配置頭文件是移植過程中經常要修改的文件;
* lib_xxx 處理器體系相關的文件,如lib_ppc, lib_arm目錄分別包含與PowerPC、ARM體系結構相關的文件;
* net 與網路功能相關的文件目錄,如bootp,nfs,tftp;
* post 上電自檢文件目錄。尚有待於進一步完善;
* rtc RTC驅動程序;
* tools 用於創建U-Boot S-RECORD和BIN鏡像文件的工具;
05 u-boot的工作模式
U-Boot的工作模式有 啟動載入模式和下載模式 。啟動載入模式是Bootloader的正常工作模式,嵌入式產品發布時,Bootloader必須工作在這種模式下,Bootloader將嵌入式操作系統從FLASH中載入到SDRAM中運行,整個過程是自動的。 下載模式 就是Bootloader通過某些通信手段將內核映像或根文件系統映像等從PC機中下載到目標板的SDRAM中運行,用戶可以利用Bootloader提供的一些令介面來完成自己想要的操作,這種模式主要用於測試和開發。
06 u-boot的啟動過程
大多數BootLoader都分為stage1和stage2兩大部分,U-boot也不例外。依賴於cpu體系結構的代碼(如設備初始化代碼等)通常都放在stage1且可以用匯編語言來實現,而stage2則通常用C語言來實現,這樣可以實現復雜的功能,而且有更好的可讀性和移植性。
1、 stage1(start.s代碼結構)
U-boot的stage1代碼通常放在start.s文件中,它用匯編語言寫成,其主要代碼部分如下:
(1) 定義入口。由於一個可執行的image必須有一個入口點,並且只能有一個全局入口,通常這個入口放在rom(Flash)的0x0地址,因此,必須通知編譯器以使其知道這個入口,該工作可通過修改連接器腳本來完成。
(2)設置異常向量(exception vector)。
(3)設置CPU的速度、時鍾頻率及中斷控制寄存器。
(4)初始化內存控制器 。
(5)將rom中的程序復制到ram中。
(6)初始化堆棧 。
(7)轉到ram中執行,該工作可使用指令ldrpc來完成。
2、 stage2(C語言代碼部分)
lib_arm/board.c中的start armboot是C語言開始的函數,也是整個啟動代碼中C語言的主函數,同時還是整個u-boot(armboot)的主函數,該函數主要完成如下操作:
(1)調用一系列的初始化函數。
(2)初始化flash設備。
(3)初始化系統內存分配函數。
(4)如果目標系統擁有nand設備,則初始化nand設備。
(5)如果目標系統有顯示設備,則初始化該類設備。
(6)初始化相關網路設備,填寫ip,c地址等。
(7)進入命令循環(即整個boot的工作循環),接受用戶從串口輸入的命令,然後進行相應的工作。
07 基於cortex-a8的s5pc100bootloader啟動過程分析
s5pc100支持兩種啟動方式,分別為USB啟動方式和NandFlash啟動方式:
1. S5PC100 USB啟動過程
[1] A8 reset, 執行iROM中的程序
[2] iROM中的程序根據S5PC100的配置管腳(SW1開關4,撥到4對面),判斷從哪裡啟動(USB)
[3] iROM中的程序會初始化USB,然後等待PC機下載程序
[4] 利用DNW程序,從PC機下載SDRAM的初始化程序到iRAM中運行,初始化SDRAM
[5] SDRAM初始化完畢,iROM中的程序繼續接管A8, 然後等待PC下載程序(BootLoader)
[6] PC利用DNW下載BootLoader到SDRAM
[7] 在SDRAM中運行BootLoader
2. S5PC100 Nandflash啟動過程
[1] A8 reset, 執行IROM中的程序
[2] iROM中的程序根據S5PC100的配置管腳(SW1開關4,撥到靠4那邊),判斷從哪裡啟動(Nandflash)
[3] iROM中的程序驅動Nandflash
[4] iROM中的程序會拷貝Nandflash前16k到iRAM
[5] 前16k的程序(BootLoader前半部分)初始化SDRAM,然後拷貝完整的BootLoader到SDRAM並運行
[6] BootLoader拷貝內核到SDRAM,並運行它
[7] 內核運行起來後,掛載rootfs,並且運行系統初始化腳本
08 u-boot移植(基於cortex_a8的s5pc100為例)
1.建立自己的平台
(1).下載源碼包2010.03版本,比較穩定
(2).解壓後添加我們自己的平台信息,以smdkc100為參考版,移植自己s5pc100的開發板
(3).修改相應目錄的文件名,和相應目錄的Makefile,指定交叉工具鏈。
(4).編譯
(5).針對我們的平台進行相應的移植,主要包括修改SDRAM的運行地址,從0x20000000
(6).「開關」相應的宏定義
(7).添加Nand和網卡的驅動代碼
(8).優化go命令
(9).重新編譯 make distclean(徹底刪除中間文件和配置文件) make s5pc100_config(配置我們的開發板) make(編譯出我們的u-boot.bin鏡像文件)
(10).設置環境變數,即啟動參數,把編譯好的u-boot下載到內存中運行,過程如下:
1. 配置開發板網路
ip地址配置:
$setenv ipaddr 192.168.0.6 配置ip地址到內存的環境變數
$saveenv 保存環境變數的值到nandflash的參數區
網路測試:
在開發開發板上ping虛擬機:
$ ping 192.168.0.157(虛擬機的ip地址)
如果網路測試失敗,從下面幾個方面檢查網路:
1. 網線連接好
2. 開發板和虛擬機的ip地址是否配置在同一個網段
3. 虛擬機網路一定要採用橋接(VM--Setting-->option)
4. 連接開發板時,虛擬機需要設置成 靜態ip地址
2. 在開發板上,配置tftp伺服器(虛擬機)的ip地址
$setenv serverip 192.168.0.157(虛擬機的ip地址)
$saveenv
3. 拷貝u-boot.bin到/tftpboot(虛擬機上的目錄)
4. 通過tftp下載u-boot.bin到開發板內存
$ tftp 20008000(內存地址即可) u-boot.bin(要下載的文件名)
如果上面的命令無法正常下載:
1. serverip配置是否正確
2. tftp服務啟動失敗,重啟tftp服務
#sudo service tftpd-hpa restart
5. 燒寫u-boot.bin到nandflash的0地址
$nand erase 0(起始地址) 40000(大小) 擦出nandflash 0 - 256k的區域
$nand write 20008000((緩存u-boot.bin的內存地址) 0(nandflash上u-boot的位置) 40000(燒寫大小)
6. 切換開發板的啟動方式到nandflash
1. 關閉開發板
2. 把SW1的開關4撥到4的那邊
3. 啟動開發板,它就從nandflash啟動
❺ 為什麼要使用 Go 語言Go 語言的優勢在哪裡
為什麼要使用Go語言:
從工程的角度上來看,對於大多數後台應用場景,選擇Golang是極為明智的選擇。 這樣可以很輕松的兼顧運行性能、開發效率及維護難度這三大讓諸多程序猿欲仙欲死的奇點。
Go有什麼優勢:
可直接編譯成機器碼,不依賴其他庫,glibc的版本有一定要求,部署就是扔一個文件上去就完成了。
靜態類型語言,但是有動態語言的感覺,靜態類型的語言就是可以在編譯的時候檢查出來隱藏的大多數問題,動態語言的感覺就是有很多的包可以使用,寫起來的效率很高。
語言層面支持並發,這個就是Go最大的特色,天生的支持並發,我曾經說過一句話,天生的基因和整容是有區別的,大家一樣美麗,但是你喜歡整容的還是天生基因的美麗呢?Go就是基因裡面支持的並發,可以充分的利用多核,很容易的使用並發。
內置runtime,支持垃圾回收,這屬於動態語言的特性之一吧,雖然目前來說GC不算完美,但是足以應付我們所能遇到的大多數情況,特別是Go1.1之後的GC。
簡單易學,Go語言的作者都有C的基因,那麼Go自然而然就有了C的基因,那麼Go關鍵字是25個,但是表達能力很強大,幾乎支持大多數你在其他語言見過的特性:繼承、重載、對象等。
豐富的標准庫,Go目前已經內置了大量的庫,特別是網路庫非常強大,我最愛的也是這部分。
內置強大的工具,Go語言裡面內置了很多工具鏈,最好的應該是gofmt工具,自動化格式化代碼,能夠讓團隊review變得如此的簡單,代碼格式一模一樣,想不一樣都很困難。
跨平台編譯,如果你寫的Go代碼不包含cgo,那麼就可以做到window系統編譯linux的應用,如何做到的呢?Go引用了plan9的代碼,這就是不依賴系統的信息。
內嵌C支持,前面說了作者是C的作者,所以Go裡面也可以直接包含c代碼,利用現有的豐富的C庫。
Go適合用來做什麼:
伺服器編程,以前你如果使用C或者C++做的那些事情,用Go來做很合適,例如處理日誌、數據打包、虛擬機處理、文件系統等。
分布式系統,資料庫代理器等。
網路編程,這一塊目前應用最廣,包括Web應用、API應用、下載應用。
內存資料庫,前一段時間google開發的groupcache,couchbase的部分組建。
雲平台,目前國外很多雲平台在採用Go開發,CloudFoundy的部分組建,前VMare的技術總監自己出來搞的apcera雲平台。
❻ go語言適合做什麼
Go語言主要用作伺服器端開發。
其定位是用來開發「大型軟體」的,適合於需要很多程序員一起開發,並且開發周期較長的大型軟體和支持雲計算的網路服務。
Go語言融合了傳統編譯型語言的高效性和腳本語言的易用性和富於表達性,不僅提高了項目的開發速度,而且後期維護起來也非常輕松。
go語言之所以能成為我國最火的語言,是因為編寫服務端高並發程序的優勢。我大中華區但凡pv,日活高點的網站,應用,誰沒點這個需求。
這個領域中最優的幾個:golang,erlang,rust。日常生活中螞漏叢人類社交是當今社會上的必然性,人們也伴隨著科技時代的發展,智能電子產品的使用中也必然少不了語言輸入,文字的編輯,語言轉換的便利都均可來源於go語音輸入法。搜輪
❼ Go 語言內存管理(三):逃逸分析
Go 語言較之 C 語言一個很大的優勢就是自帶 GC 功能,可 GC 並不是沒有代價的。寫 C 語言的時候,在一個函數內聲明的變數,在函數退出後會自動釋放掉,因為這些變數分配在棧上。如果你期望變數的數據可以在函數退出後仍然能被訪問,就需要調用 malloc 方法在堆上申請內存,如果程序不再需要這塊內存了,再調用 free 方法釋放掉。Go 語言不需要你主動調用 malloc 來分配堆空間,編譯器會自動分析,找出需要 malloc 的變數,使用堆內存。編譯器的這個分析過程就叫做逃逸分析。
所以你在一個函數中通過 dict := make(map[string]int) 創建一個 map 變數,其背後的數據是放在棧空間上還是堆空間上,是不一定的。這要看編譯器分析的結果。
可逃逸分析並不是百分百准確的,它有缺陷。有的時候你會發現有些變數其實在棧空間上分配完全沒問題的,但編譯後程序還是把這些數據放在了堆上。如果你了解 Go 語言編譯器逃逸分析的機制,在寫代碼的時候就可以有意識地繞開這些缺陷,使你的程序更高效。
Go 語言雖然在內存管理方面降低了編程門檻,即使你不了解堆棧也能正常開發,但如果你要在性能上較真的話,還是要掌握這些基礎知識。
這里不對堆內存和棧內存的區別做太多闡述。簡單來說就是, 棧分配廉價,堆分配昂貴。 棧空間會隨著一個函數的結束自動釋放,堆空間需要時間 GC 模塊不斷地跟蹤掃描回收。如果對這兩個概念有些迷糊,建議閱讀下面 2 個文章:
這里舉一個小例子,來對比下堆棧的差別:
stack 函數中的變數 i 在函數退出會自動釋放;而 heap 函數返回的是對變數 i 的引用,也就是說 heap() 退出後,表示變數 i 還要能被訪問,它會自動被分配到堆空間上。
他們編譯出來的代碼如下:
邏輯的復雜度不言而喻,從上面的匯編中可看到, heap() 函數調用了 runtime.newobject() 方法,它會調用 mallocgc 方法從 mcache 上申請內存,申請的內部邏輯前面文章已經講述過。堆內存分配不僅分配上邏輯比棧空間分配復雜,它最致命的是會帶來很大的管理成本,Go 語言要消耗很多的計算資源對其進行標記回收(也就是 GC 成本)。
Go 編輯器會自動幫我們找出需要進行動態分配的變數,它是在編譯時追蹤一個變數的生命周期,如果能確認一個數據只在函數空間內訪問,不會被外部使用,則使用棧空間,否則就要使用堆空間。
我們在 go build 編譯代碼時,可使用 -gcflags '-m' 參數來查看逃逸分析日誌。
以上面的兩個函數為例,編譯的日誌輸出是:
日誌中的 &i escapes to heap 表示該變數數據逃逸到了堆上。
需要使用堆空間,所以逃逸,這沒什麼可爭議的。但編譯器有時會將 不需要 使用堆空間的變數,也逃逸掉。這里是容易出現性能問題的大坑。網上有很多相關文章,列舉了一些導致逃逸情況,其實總結起來就一句話:
多級間接賦值容易導致逃逸 。
這里的多級間接指的是,對某個引用類對象中的引用類成員進行賦值。Go 語言中的引用類數據類型有 func , interface , slice , map , chan , *Type(指針) 。
記住公式 Data.Field = Value ,如果 Data , Field 都是引用類的數據類型,則會導致 Value 逃逸。這里的等號 = 不單單只賦值,也表示參數傳遞。
根據公式,我們假設一個變數 data 是以下幾種類型,相應的可以得出結論:
下面給出一些實際的例子:
如果變數值是一個函數,函數的參數又是引用類型,則傳遞給它的參數都會逃逸。
上例中 te 的類型是 func(*int) ,屬於引用類型,參數 *int 也是引用類型,則調用 te(&j) 形成了為 te 的參數(成員) *int 賦值的現象,即 te.i = &j 會導致逃逸。代碼中其他幾種調用都沒有形成 多級間接賦值 情況。
同理,如果函數的參數類型是 slice , map 或 interface{} 都會導致參數逃逸。
匿名函數的調用也是一樣的,它本質上也是一個函數變數。有興趣的可以自己測試一下。
只要使用了 Interface 類型(不是 interafce{} ),那麼賦值給它的變數一定會逃逸。因為 interfaceVariable.Method() 先是間接的定位到它的實際值,再調用實際值的同名方法,執行時實際值作為參數傳遞給方法。相當於 interfaceVariable.Method.this = realValue
向 channel 中發送數據,本質上就是為 channel 內部的成員賦值,就像給一個 slice 中的某一項賦值一樣。所以 chan *Type , chan map[Type]Type , chan []Type , chan interface{} 類型都會導致發送到 channel 中的數據逃逸。
這本來也是情理之中的,發送給 channel 的數據是要與其他函數分享的,為了保證發送過去的指針依然可用,只能使用堆分配。
可變參數如 func(arg ...string) 實際與 func(arg []string) 是一樣的,會增加一層訪問路徑。這也是 fmt.Sprintf 總是會使參數逃逸的原因。
例子非常多,這里不能一一列舉,我們只需要記住分析方法就好,即,2 級或更多級的訪問賦值會 容易 導致數據逃逸。這里加上 容易 二字是因為隨著語言的發展,相信這些問題會被慢慢解決,但現階段,這個可以作為我們分析逃逸現象的依據。
下面代碼中包含 2 種很常規的寫法,但他們卻有著很大的性能差距,建議自己想下為什麼。
Benchmark 和 pprof 給出的結果:
熟悉堆棧概念可以讓我們更容易看透 Go 程序的性能問題,並進行優化。
多級間接賦值會導致 Go 編譯器出現不必要的逃逸,在一些情況下可能我們只需要修改一下數據結構就會使性能有大幅提升。這也是很多人不推薦在 Go 中使用指針的原因,因為它會增加一級訪問路徑,而 map , slice , interface{} 等類型是不可避免要用到的,為了減少不必要的逃逸,只能拿指針開刀了。
大多數情況下,性能優化都會為程序帶來一定的復雜度。建議實際項目中還是怎麼方便怎麼寫,功能完成後通過性能分析找到瓶頸所在,再對局部進行優化。
❽ bpftrace動態追蹤golang應用-函數內聯問題
在上一篇文章的golang代碼中,函數add的上一行,增加了一條注釋語句: //go:noinline 。在bpftrace追蹤時,是否可以去掉?有喚喊什麼作用?
為了說明該問題,設計一個例子。
golang代碼中,有兩個求和函數。其中,add1加上 //go:noinline ,另一個add2不加。代碼如雹沖下:
bpftrace程序分別對函數add1和add2的輸入參數、返回值進行追蹤,代碼如下:
執行程序後,可以看到bpftrace程序能夠正常追蹤到函數add1,但是無法追蹤到函數add2。
通過上文中的示例代碼,可以看到,沒有加 //go:noinline 的函數無法被bpftrace程序追蹤到。通過查和肆野閱golang相關文檔,可以知道, //go:noinline 表示該函數在編譯時,不會被內聯。
使用 objump -S 生成golang程序的匯編代碼如下:
通過匯編代碼,我們可以看到,主函數中,地址 0x498e52 處 callq 498e00 調用了add1函數,地址 0x498ebb 處 movq $0x4,(%rsp) 直接計算求值。
因此,golang編譯器在編譯代碼時,會對代碼進行分析,並按照內聯規則,將某些函數生成內聯代碼。一旦函數被內聯,bpftrace將無法追蹤到對應函數。也就是,上文中函數 add2 無法被追蹤到。
針對golang程序中編譯器內聯的問題,可以通過禁止內聯的方式來解決。禁止內聯的方式有:
在實踐中,可以通過 go build -gcflags="-m -m" 來查看,哪些函數會在編譯時執行內聯,如:
從輸出中,可以看到:
關於golang編譯器進行內聯的場景,可以參考golang源碼:https://github.com/golang/go/blob/master/src/cmd/compile/internal/inline/inl.go。
由於golang編譯器內聯優化,bpftrace可能無法正常追蹤golang程序。在編寫bpftrace腳本時,可以先使用 nm 命令查看一下可執行程序,是否存在需要追蹤的函數的符號信息。如果沒有則bpftrace將不能對其進行追蹤。
前面的示例中,都是對 int 類型的參數進行追蹤,那對於 string 類型的參數,是否也可以用同樣的方式進行追蹤?將在下一篇中進行討論。