使用 Docker 構建 PetaLinux 開(kāi)發(fā)環(huán)境
0. 背景
本文引用地址:http://dyxdggzs.com/article/201807/383661.htm0.1 PetaLinux 是什么
PetaLinux 是 Xilinx 推出的用于在其自家 SoC 上構建嵌入式 Linux 的一套工具集,集成了編譯、調試、仿真等眾多工具。
0.2 原有的搭建 PetaLinux 開(kāi)發(fā)環(huán)境的方式有什么問(wèn)題
實(shí)驗室的項目基于 Xilinx 的 Zynq 系列 SoC 開(kāi)發(fā),需要使用 PetaLinux 構建嵌入式 Linux 系統,第一步就是搭建開(kāi)發(fā)環(huán)境。團隊成員平時(shí)的主力系統是 Windows,使用虛擬機的方式搭建 Linux 開(kāi)發(fā)環(huán)境。搭建開(kāi)發(fā)環(huán)境出現困難,或為節省時(shí)間的目的時(shí)會(huì )從其他成員那里復制搭建好環(huán)境的虛擬機。這些方式已產(chǎn)生或可能產(chǎn)生的問(wèn)題可以總結如下:
1. 安裝開(kāi)發(fā)環(huán)境的依賴(lài)稍顯復雜,且根據操作系統不同而異,新手不易搞定。安裝過(guò)程沒(méi)有被很好的文檔化,團隊成員重復勞動(dòng)明顯。
2. 統一操作系統版本困難。開(kāi)發(fā)人員(現有成員、未來(lái)加入成員、項目其他團隊的成員)可能使用著(zhù)不同的 Linux 發(fā)行版和版本,因為他們可能有不同的喜好,版本上喜歡嘗鮮或守舊,在既有系統上已經(jīng)有順手的開(kāi)發(fā)工具和設置等等。而當大家希望統一開(kāi)發(fā)環(huán)境或自行搭建失敗時(shí),往往選擇復制整個(gè)虛擬機鏡像。
3. 使用復制整個(gè)虛擬機的方式傳遞開(kāi)發(fā)環(huán)境不夠靈活。表現為:
使用了一段時(shí)間后的虛擬機大小可能動(dòng)輒四五十個(gè) GB,拷貝時(shí)間長(cháng),難以通過(guò)網(wǎng)絡(luò )共享。
為保留可回退的環(huán)境,可能為虛擬機添加快照,但這會(huì )進(jìn)一步顯著(zhù)增加虛擬機的體積。
傳遞虛擬機不僅傳遞了需要的開(kāi)發(fā)環(huán)境,還傳遞了大量無(wú)用的軟件和個(gè)人設置,難以兼顧不同開(kāi)發(fā)者之間的習慣差異。
對于不同項目、用途可能存在多個(gè)虛擬機,資源冗余很大。
4. 使用虛擬機性能表現差。主機配置不高時(shí),使用虛擬機(可能會(huì )再虛擬機內運行 IDE 等其他開(kāi)發(fā)工具)會(huì )經(jīng)??D,降低了開(kāi)發(fā)效率。
5. 使用為某一開(kāi)發(fā)環(huán)境而復制來(lái)的虛擬機,遷就其環(huán)境不愿再安裝合適的開(kāi)發(fā)工具(如 IDE 等)。
0.3 基于 Docker 的解決方案
Docker 作為在很多場(chǎng)景下虛擬機的替代方案備受矚目,其資源消耗小、為單一應用配置環(huán)境、易于通過(guò)網(wǎng)絡(luò )共享等特點(diǎn)很好的解決了上面提到的諸多問(wèn)題。通過(guò) Docker 來(lái)構建 PetaLinux 開(kāi)發(fā)環(huán)境,我們可以獲得以下優(yōu)勢:
經(jīng)過(guò)壓縮的鏡像體積只有 1GB 左右,方便網(wǎng)絡(luò )傳輸。
很容易再團隊中統一開(kāi)發(fā)環(huán)境,包括操作系統和各種庫。Docker 鏡像的只讀特性保證了有一個(gè)可回退的一致環(huán)境。
開(kāi)發(fā)環(huán)境中的依賴(lài)被 Dockerfile 清晰、明確的記錄下來(lái),具有很好的文檔效應,方便團隊長(cháng)期共享和維護。
Docker 可以運行在之前的虛擬機的操作系統上,也可以運行在一個(gè)“精簡(jiǎn)”的操作系統上(如 Docker for Windows 的方式),還可以放到服務(wù)器上,運行多個(gè)容器供團隊成員使用。這使得使用 Linux 環(huán)境的方式更加多樣、靈活,且能減少虛擬機性能原因帶來(lái)的影響
現在理想很豐滿(mǎn),但現實(shí)中還需要經(jīng)過(guò)一番探究和試驗,下面就讓我們開(kāi)始。
1. 構建 Docker 鏡像
Docker 的優(yōu)勢就在于我們可以使用“代碼”來(lái)表示需要的環(huán)境,它既能描述環(huán)境,也能直接指導生成環(huán)境,這份“代碼”就是 Dockerfile。下面詳細的記述了這份 Dockerfile 的每一部分,過(guò)程中遇到的問(wèn)題、解決辦法、注意事項等。關(guān)于 Dockerfile,可以參考官方的 Dockerfile reference1,以及 Best practices for writing Dockerfiles2。
1.1 設置構建參數
Dockerfile 中允許使用 ARG 指令設置構建時(shí)參數,這些參數在 Dockerfile 中具有默認值,在構建時(shí)可以通過(guò) --build-arg 參數指定新的值來(lái)覆蓋默認值。這些參數可以在 Dockerfile 中被引用(引用方式與在 shell 中引用變量一樣),但不會(huì )出現在最終的鏡像里。注意一條 ARG 指令只能指定一個(gè)參數,這一點(diǎn)和 ENV 指令是不同的。這里我設置了兩個(gè)參數如下:
ARG install_dir=/opt
ARG installer_url=172.17.0.1:8000
其中 install_dir 用來(lái)指定 PetaLinux 的安裝路徑,installer_url 用來(lái)指定 PetaLinux 安裝包的網(wǎng)絡(luò )地址。如果安裝包在互聯(lián)網(wǎng)上,則這里是一個(gè)訪(fǎng)問(wèn)鏈接,如果安裝包在本地,則這里被指定為 Docker 的默認網(wǎng)橋,通過(guò)它聯(lián)通本地網(wǎng)絡(luò )服務(wù)器和構建時(shí)的臨時(shí)容器。關(guān)于這一部分,我會(huì )在后面詳述。
1.2 設置環(huán)境變量
通常情況下,PetaLinux 使用一個(gè)設置腳本來(lái)添加自身的各項工具到環(huán)境變量中,在使用相關(guān)工具前需要通過(guò) source /settings.sh 來(lái)執行腳本。但現在我要制作一個(gè)專(zhuān)屬于 PetaLinux 的環(huán)境,完全可以把環(huán)境變量設置好來(lái)免去這個(gè)步驟。在 Dockerfile 中使用 ENV 指令來(lái)設置環(huán)境變量:
ENV PETALINUX_VER=2014.4
PETALINUX=${install_dir}/petalinux-v2014.4-final
ENV PATH=${PETALINUX}/tools/linux-i386/arm-xilinx-gnueabi/bin:
${PETALINUX}/tools/linux-i386/arm-xilinx-linux-gnueabi/bin:
${PETALINUX}/tools/linux-i386/microblaze-xilinx-elf/bin:
${PETALINUX}/tools/linux-i386/microblazeel-xilinx-linux-gnu/bin:
${PETALINUX}/tools/linux-i386/petalinux/bin:
${PETALINUX}/tools/common/petalinux/bin:
${PATH}
這里有兩點(diǎn)需要注意。一是 ENV 雖然支持并推薦在一條指令下設置多個(gè)環(huán)境變量,但如果這些環(huán)境變量之間存在相互引用的情況,就在分開(kāi)寫(xiě)了。比如這里設置 PATH 變量時(shí)引用了 PETALINUX 變量,它們就不能在同一個(gè) ENV 指令下進(jìn)行設置了。二是 PATH 這個(gè)變量中每個(gè)路徑之間不可以有空格,否則是搜索不到可執行文件的,所以這里也只能不顧縮進(jìn)來(lái)保證沒(méi)有空格了。網(wǎng)絡(luò )上似乎沒(méi)有什么解決這個(gè)問(wèn)題的討論,而且在 ENV 指令下我們無(wú)法使用任何其他的工具去處理這個(gè)字符串。
我們其實(shí)也可以把 PetaLinux 提供的 settings.sh 腳本添加到 .bashrc 文件中,使得其每次被自動(dòng)執行。實(shí)際上這個(gè)腳本中除了配置環(huán)境變量,最后還運行了 PetaLinux 自帶的一個(gè)環(huán)境檢查工具,用于檢查網(wǎng)絡(luò )、磁盤(pán)剩余空間等信息,使用前述設置環(huán)境變量的方式就忽略這個(gè)檢查工具了。
1.3 安裝依賴(lài)
在 PetaLinux 的參考指南3中給出了它所依賴(lài)的工具和庫,然而并不全面和準確。一是因為有些包已經(jīng)被替代,現在無(wú)法獲得4;二是對于 32 位庫支持5只是一筆帶過(guò),并未具體列出;三是有的包可能因非?;A而未列出,但是在 Docker 的基礎鏡像中卻沒(méi)有包含,如 bc。以下是我測試成功的、在當前基礎鏡像下需要的所有依賴(lài):
RUN dpkg --add-architecture i386
apt-get update apt-get install -y --no-install-recommends
# Required tools and libraries of Petalinux.
# See in: ug1144-petalinux-tools-reference-guide, v2014.4.
tofrodos
iproute
gawk
gcc-4.7
git-core
make
net-tools
rsync
wget
tftpd-hpa
zlib1g-dev
flex
bison
bc
lib32z1
lib32gcc1
libncurses5-dev
libncursesw5-dev
libncursesw5:i386
libncurses5:i386
libbz2-1.0:i386
libc6:i386
libstdc++6:i386
libselinux1
libselinux1:i386
# Using expect to install Petalinux automatically.
expect
rm -rf /var/lib/apt/lists/* /tmp/*
ln -fs gcc-4.7 /usr/bin/gcc
ln -fs gcc-ar-4.7 /usr/bin/gcc-ar
ln -fs gcc-nm-4.7 /usr/bin/gcc-nm
ln -fs gcc-ranlib-4.7 /usr/bin/gcc-ranlib
這里我們使用 --no-install-recommends 參數來(lái)避免安裝不必要的包,并在安裝結束后清理 /var/lib/apt/lists/ 和 /tmp/ 目錄,以盡可能的使鏡像精簡(jiǎn)。
由于在 Ubuntu 16.04 上安裝 GCC 會(huì )默認安裝 gcc 5 的版本,而 2014.4 版本的 PetaLinux 應該沒(méi)有適配 gcc 5,會(huì )出現很多警告。這里采取的辦法是安裝 gcc-4.7,并修改符號鏈接,使 /usr/bin/gcc 指向這一版本的 GCC。事實(shí)上我并不確切知道應該安裝哪個(gè)版本,在 PetaLinux 2016.4 中指定了使用 gcc 4.8。這里影響應該不大,因為真正用于構建項目的交叉編譯器是 PetaLinux 自帶的。
在網(wǎng)上搜索時(shí)發(fā)現,一般資料都沒(méi)有介紹如何直接更改一個(gè)軟連接的指向。不知道的情況下,就只能先刪除再重建這個(gè)鏈接了。最終還是在 Stack Overflow 上找到了答案,其實(shí)我們可以使用 -f 選項在一條命令中更改軟連接的指向。這個(gè)技巧在后面還會(huì )用到。
1.4 使用 expect 腳本自動(dòng)安裝 PetaLinux
PetaLinux 的安裝包在安裝過(guò)程中會(huì )顯示許可證協(xié)議,并要求用戶(hù)輸入確認信息。這樣的交互方式給我們的自動(dòng)化處理造成了一點(diǎn)小困難,然而程序員前輩們肯定是不允許這種不能自動(dòng)化的情況持續的,expect 這個(gè)工具就是專(zhuān)門(mén)用來(lái)自動(dòng)處理這種需要交互輸入的情況的。expect 常常用來(lái)處理 SSH 登陸等需要交互輸入密鑰的情況,它會(huì )監視一個(gè)程序的輸出,并在捕獲到了特定的輸出后給出一個(gè)預設的輸入。這里我們使用一個(gè) auto-install.sh 腳本來(lái)自動(dòng)安裝 PetaLinux,腳本的內容如下:
#!/usr/bin/env expect
set timeout -1
set install_dir [lindex $argv 0]
spawn ./petalinux-v2014.4-final-installer.run $install_dir
expect Press Enter to display the license agreements
send r
expect *>*
send yr
expect *>*
send yr
expect eof
第一行聲明使用 expect 這個(gè)工具來(lái)解釋此腳本,/usr/bin/env 會(huì )遍歷 PATH 變量來(lái)尋找后面的可執行文件,這樣避免了依賴(lài)于 expect 的安裝路徑。
第二行設置等待超時(shí),因為 PetaLinux 的安裝過(guò)程比較慢,這里將其設為 -1,即一直等待。
第三行設置一個(gè)變量來(lái)接收此腳本的參數,我們借此來(lái)指定希望將 PetaLinux 安裝到哪個(gè)目錄下。注意 expect 腳本設置參數的方式和 bash 腳本不同,參數 0 代表我們調用腳本時(shí)給出的第一個(gè)參數,而在 bash 腳本中,參數 0 代表腳本本身的名字。
第五行用 spawn 命令去執行安裝程序。PetaLinux 的安裝程序的第一個(gè)參數也是安裝路徑。
接下來(lái)我們用 expext 命令來(lái)捕獲程序的輸出,用 send 命令發(fā)送預設的輸入。安裝程序提示你確認協(xié)議的語(yǔ)句是這樣的:Do you accept this license? [y/N] >,直接用 expext 匹配這一句會(huì )有問(wèn)題,因為至少 [] 在 expect 的語(yǔ)法中是有特定含義的,需要轉義。已無(wú)心情研究 expect 那奇怪的語(yǔ)法,所幸它有很棒的模糊匹配功能,我們只需要匹配最后一個(gè) > 字符就可以了。
1.5 減小鏡像體積
PetaLinux 的安裝包在 Xilinx 官方網(wǎng)站上可以下載,但需要先注冊,沒(méi)有固定的下載鏈接。所以要么需要在構建前把它下載到本地,要么在互聯(lián)網(wǎng)上尋找一個(gè)合適的托管地點(diǎn),可以提供穩定的下載鏈接。
PetaLinux 的安裝包比較大(2014.4 版有 1.2GB,而 2016.4 已經(jīng)到了喪心病狂的 8.3 GB),在安裝完成后,安裝包再留在鏡像中已經(jīng)沒(méi)有什么意義了,還會(huì )顯著(zhù)的增加鏡像的體積。這里要理解 Docker 的鏡像是由一個(gè)個(gè)的層(layer)組成的,Dockerfile 中的每一條指令都對應于一層,每一層都是在前一層的基礎上進(jìn)行的增量的改變。這意味著(zhù),一旦我們在某一層中引入了一個(gè)文件,即使在下一層中將其刪除,對體積的減小也無(wú)濟于事,我們只是無(wú)法在最終的容器中“看見(jiàn)”它們而已。如果我們使用 COPY 指令將 PetaLinux 的安裝包添加進(jìn)去,則 COPY 指令會(huì )生成一個(gè)層,我們無(wú)法再把它產(chǎn)生的體積抹除掉。Stack Overflow 上有一個(gè)關(guān)于這個(gè)問(wèn)題的討論6,主要提到了三種方式:一是在本地構建一個(gè)網(wǎng)絡(luò )服務(wù)器,通過(guò)網(wǎng)絡(luò )的方式傳到 Docker 容器的內部,我采用了這種方式,后面詳述;二是不能使用 Dockerfile 的方式構建容器,而是在容器中完成安裝和清理工作后手動(dòng)提交更改到鏡像;三是使用第三方工具對生成的鏡像進(jìn)行再壓縮。
這里使用網(wǎng)絡(luò )是更好的方式,一方面如果我們在互聯(lián)網(wǎng)或者私有服務(wù)器上存放了安裝包,通過(guò)更改 installer_url 變量就可以使用新的地址獲取文件;另一方面,在本地可以使用 Python 輕松的創(chuàng )建一個(gè) HTTP 服務(wù)器。在 Dockerfile 中,我們使用 wget 下載安裝包、配置其權限、運行自動(dòng)安裝腳本,最后刪除安裝包。這些步驟必須在一個(gè) RUN 指令下完成,這樣安裝包才不會(huì )留在最終的鏡像里。
WORKDIR $install_dir
COPY ./auto-install.sh .
RUN wget -q $installer_url/petalinux-v2014.4-final-installer.run
chmod a+x petalinux-v2014.4-final-installer.run
./auto-install.sh $install_dir
rm -rf petalinux-v2014.4-final-installer.run
在外部,我使用了一個(gè)腳本來(lái)封裝啟動(dòng) HTTP 服務(wù)器、構建 Docker 鏡像、停止服務(wù)器的步驟:
#!/usr/bin/env bash
installer_dir=$1
docker_context=`pwd`
echo Start to build petalinux tools docker image ...
echo -----------------------------------------------
cd $installer_dir
python3 -m http.server
server_pid=$!
cd $docker_context
installer_ip=`ifconfig docker0 | grep 'inets' | awk '{print $2}'`
docker build -t petalinux-docker:2014.4
--build-arg installer_url=${installer_ip}:8000
.
kill $server_pid
echo ---------------
echo Finish. ^_^
echo ---------------
這個(gè)腳本的第一個(gè)參數是安裝包在本地的路徑。首先讓服務(wù)器在后臺建立,并記錄下其對應的 pid,在完成鏡像的構建后再將其殺死。Python 創(chuàng )建的服務(wù)器會(huì )默認監聽(tīng) 8000 端口。在容器內部(Docker 構建的過(guò)程即相當于在臨時(shí)的容器中執行 Dockerfile 的過(guò)程)可以通過(guò) Docker 的默認網(wǎng)橋(docker0)的 IP 地址來(lái)訪(fǎng)問(wèn)本地主機。網(wǎng)橋對應的 IP 地址并不是唯一的,Docker 是根據主機中網(wǎng)卡的配置不同,選擇一個(gè)沒(méi)有被占用的私有網(wǎng)段(如果 3 類(lèi)私有 IP 網(wǎng)段都被占用了,Docker 啟動(dòng)時(shí)會(huì )報錯),也可以自行更改,所以這里我們從 ifconfig 的輸出中提取 docker0 對應得 IP 地址。docker build 命令中 -t 參數為鏡像指定標簽,--build-arg 參數用來(lái)覆蓋我們在 Dockerfile 內部設置的參數,最后一個(gè)參數 . 指的是構建環(huán)境(build context)為當前路徑。注意,如果你將安裝包放在了這個(gè)構建環(huán)境的同一個(gè)目錄下,一定要通過(guò) .dockerignore 文件來(lái)忽略這個(gè)安裝包文件,因為否則它會(huì )被發(fā)送到 Docker daemon 上,增加構建時(shí)間且毫無(wú)用處,除非你要使用 COPY 指令的方式導入安裝包。
1.6 其他
PetaLinux 會(huì )檢查 shell 環(huán)境,并推薦使用 Bash。在 Ubuntu:16.04 的鏡像中 /bin/sh 這個(gè)軟連接指向的是 /bin/dash,這里我們將其更改為 /bin/bash。
RUN ln -fs /bin/bash /bin/sh # bash is PetaLinux recommended shell
使用 WORKDIR 指令新建了一個(gè) /workspace 的路徑用于連接數據卷。最后一個(gè) WORKDIR 指定的路徑就會(huì )是進(jìn)入容器后的所在路徑,這一點(diǎn)似乎官方文檔沒(méi)有明說(shuō)。
WORKDIR /workspac
1.7 鏡像的構建
如果安裝包放在本地,則如 1.5 節所述,使用 build-image.sh 腳本構建鏡像。如果安裝包在互聯(lián)網(wǎng)或本地服務(wù)器上,則直接使用 docker build 命令,并使用 installer_url 參數指定訪(fǎng)問(wèn)地址。
2. 測試
你可以自行按照上面的方法自行構建鏡像,也可以從 Docker Hub 上下載我上傳好的鏡像:
docker pull xaljer/petalinux:2014.4
運行容器:
docker run -ti -v /path/to/projects:/workspace xaljer/petalinux:2014.4
在容器中創(chuàng )建工程并編譯:
petalinux-create -t project -s
-n
cd
petalinux-build # 構建整個(gè)工程,會(huì )比較慢
3. 現有問(wèn)題和下一步工作
3.1 現有問(wèn)題
PetaLinux 2014.4 支持的原本是 Ubuntu 14.04,但使用此版本的鏡像時(shí)發(fā)現,其軟件源似乎有些問(wèn)題,經(jīng)常安裝失敗,故沒(méi)有使用。
PetaLinux 會(huì )提示找不到 tftp,這是因為沒(méi)有對其進(jìn)行進(jìn)一步的配置。如果不使用 tftp 可以忽略這個(gè)問(wèn)題。
在 Docker for Windows 下構建時(shí),可能會(huì )出現錯誤7,將存儲驅動(dòng)更改為 aufs 后可修復。然而 Windows 下構建的鏡像仍有其他問(wèn)題,無(wú)法使用,作者尚未對其作更多的測試和探究。
3.2 下一步工作
添加 Vivado SDK 的一些工具。
PetaLinux 工具的名字都有點(diǎn)長(cháng),可以考慮在鏡像里對常用的操作添加別名。但在容器外部作可能會(huì )更方便一些,因為我們不必交互式的進(jìn)入容器,而是使用 docker exec 來(lái)執行命令,此時(shí)可以在容器外面為整個(gè)命令添加別名。
有了標準化環(huán)境,不僅可以在自己的電腦上運行,還希望放在私有服務(wù)器上,讓大家通過(guò)網(wǎng)絡(luò )訪(fǎng)問(wèn)。要達到這樣的目的,一要能通過(guò) SSH 訪(fǎng)問(wèn)容器,二要能在服務(wù)器的數據卷和本地計算機之間同步數據(源碼及編譯結果)。對于 SSH,初步設想可以通過(guò)外部的一些 Docker 工具來(lái)完成,而不是在容器內部建立 SSH 服務(wù)器,因為有多個(gè)容器時(shí),要對應多個(gè)不同端口等問(wèn)題。對于數據同步,可以在本地的 Windows 系統上通過(guò) Linux 子系統(WSL)建立 NFS 服務(wù)器,在容器內部掛載 NFS,或者通過(guò) Docker 的插件實(shí)現直接將遠端的 NFS 作為數據卷掛載。
4. 總結
如果并不需要 Docker 的一些優(yōu)勢,我們也可以考慮將 PetaLinux 裝進(jìn) Windows 的 Linux 子系統(WSL),這樣可以有更好的性能和更無(wú)縫的操作。
使用虛擬機在 Windows 下搭建嵌入式開(kāi)發(fā)環(huán)境是以往非常常用的方式,但也是一種比較笨重的方式。隨著(zhù)一些新的技術(shù)、平臺的出現,如 Docker 和 WSL,我們可以嘗試利用它們搭建開(kāi)發(fā)環(huán)境,提升開(kāi)發(fā)的效率。
評論