【征程 6】工具鏈 VP 示例為什么能運(yùn)行
1.引言
在上一篇文章【征程 6】VP 簡介與單算子實(shí)操 中,介紹了 VP 是什么,并以單算子 rotate 為例,介紹了 VP API 使用方法,但對于對 C++不那么熟悉的伙伴,可能會有這樣的疑問:一個 main 函數(shù)就讓 VP 示例跑起來了?沒有什么依賴嗎?CMakeLists.txt 沒看到,xxx.h 頭文件也沒有,甚至連怎么編譯的都沒寫,只有 main 文件中的 C++代碼,還是讓人有點(diǎn)迷迷瞪瞪。由于本人就是屬于對 C++不那么熟悉的同學(xué),所以下面會從我的視角來介紹上一篇文章遺留的問題,如果其中有錯誤或表述不當(dāng)?shù)牡胤剑瑲g迎評論指正。
2.代碼解讀
OE/samples/ucp_tutorial/目錄下的結(jié)構(gòu)如下:
. ├── deps_aarch64 │ ├── appsdk │ ├── eigen │ ├── fmt │ ├── gflags │ ├── glog │ ├── hlog │ ├── nlohmann │ ├── opencv │ ├── openssl │ ├── protobuf │ ├── rapidjson │ ├── ucp │ ├── uWS │ └── zlib ├── deps_x86 │ ├── eigen │ ├── fmt │ ├── gflags │ ├── hlog │ ├── nlohmann │ ├── opencv │ └── ucp └── vp ├── code └── vp_samples
在 vp/code/07_single_rotate 目錄如下:
. ├── CMakeLists.txt ├── log_util.h ├── main.cpp ├── rotate.cpp └── rotate.h
有個感覺即可,后面會細(xì)致的解讀運(yùn)行起一個 VP 示例所依賴的文件。
2.1 main.cpp
從 main.cpp 看過去,內(nèi)容以及解讀如下:
#include <iostream> // 引入標(biāo)準(zhǔn)輸入輸出庫 #include "rotate.h" // 引入自定義頭文件 rotate.h int32_t main(int32_t argc, char **argv) { // 主函數(shù),接受命令行參數(shù) single_rotate(); // 調(diào)用 single_rotate() 函數(shù) return 0; // 返回 0,表示程序正常結(jié)束,符合 C++ 規(guī)范 }
有兩處拿出來解釋下:#include “rotate.h”:該文件是一個自定義頭文件,用于聲明函數(shù)或類,代碼中調(diào)用的 single_rotate() 在 rotate.h 中被聲明,并在相應(yīng)的 rotate.cpp 中被實(shí)現(xiàn)。int32_t main(int32_t argc, char **argv):入口函數(shù) main(),接收兩個參數(shù):argc:命令行參數(shù)個數(shù)(包括程序本身)。argv:存儲命令行參數(shù)的字符串?dāng)?shù)組(char*)。
2.2 rotate.h
下面看一下頭文件 rotate.h,代碼作用:聲明 single_rotate() 函數(shù),并防止頭文件被重復(fù)包含。
#ifndef VP_CODE_07_ROTATE_IMAGE_PROCESS_H_ #define VP_CODE_07_ROTATE_IMAGE_PROCESS_H_ #include "hobot/vp/hb_vp.h" // 包含的頭文件,不在當(dāng)前目錄下,為什么能包含? #include "log_util.h" // 包含的頭文件,就在當(dāng)前目錄下 int32_t single_rotate(); #endif // VP_CODE_07_ROTATE_IMAGE_PROCESS_H_
來了解一下這段代碼能防止頭文件被重復(fù)包含。
#ifndef VP_CODE_07_ROTATE_IMAGE_PROCESS_H_ #define VP_CODE_07_ROTATE_IMAGE_PROCESS_H_ ... #endif // VP_CODE_07_ROTATE_IMAGE_PROCESS_H_
這種方式稱為 頭文件保護(hù)(Header Guard),用于防止頭文件的 重復(fù)包含,避免 編譯錯誤。
還是有些不太理解?詳細(xì)解釋一下:
1.什么是頭文件保護(hù):在 C/C++ 語言中,頭文件(。h)是用于聲明變量、函數(shù)、類等的文件。為了防止頭文件被 重復(fù)包含(multiple inclusion),通常使用 頭文件保護(hù)(Header Guard),其基本結(jié)構(gòu)是:
#ifndef HEADER_NAME_H // 如果 HEADER_NAME_H 未定義 #define HEADER_NAME_H // 定義 HEADER_NAME_H // 頭文件內(nèi)容 // 變量、函數(shù)、類的聲明等 #endif // 結(jié)束頭文件保護(hù)
A.h 頭文件
// A.h #ifndef A_H #define A_H void foo(); #endif // A_H
B.h 頭文件
// B.h #ifndef B_H #define B_H #include "A.h" #endif // B_H
main.cpp 中
// main.cpp #include "A.h" #include "B.h"
當(dāng) main.cpp 被編譯時,它會展開 #include:在 A.h 中直接包含 void foo();,B.h 也包含 A.h,再次引入 void foo();這會導(dǎo)致重復(fù)聲明,如果沒有 頭文件保護(hù),編譯器可能會報錯:
error: redefinition of ‘void foo()’
當(dāng)使用了上面#ifndef / #define / #endif,就可以避免這個問題,原理如下:
第一次 包含 A.h 時:
第二次 再次包含 A.h:
編譯器在處理頭文件時會進(jìn)行優(yōu)化,所以頭文件保護(hù)不會影響性能,為了避免 重復(fù)包含頭文件 導(dǎo)致的編譯錯誤,提高代碼可維護(hù)性,推薦大家使用頭文件保護(hù)。
最后,#ifndef 保護(hù)多個頭文件需要不同的宏名,因為宏名重復(fù),也可能導(dǎo)致錯誤,建議使用 文件名相關(guān)的宏 方便記憶排查。
2.3 log_util.h
在 rotate.h 中包含了 log_util.h,定義了一些用于日志打印的宏,具體的代碼解讀可見文章:【征程 6】工具鏈 VP 示例中日志打印解讀。
2.4 rotate.cpp
該文件解讀可見文章:【征程 6】VP 簡介與單算子實(shí)操。
2.5 CMakeLists.txt
想了解 VP 示例中 CMakeLists.txt 的嵌套以及運(yùn)行邏輯,可見文章:【征程 6】工具鏈 VP 示例中 Cmakelists 解讀。
2.6 build.sh
該 Bash 腳本 用于 構(gòu)建 aarch64(ARM64)或 x86(PC 端)架構(gòu)的項目,并支持 自動檢測 gcc 版本,確保編譯環(huán)境正確。
# 默認(rèn)編譯 ARM64 版本、 Release 模式 arch=aarch64 build_type=release # 顯示幫助信息 function show_usage() { cat <<EOF Usage: bash -e $0 <options> # 第一個參數(shù)是目標(biāo)架構(gòu),后面參數(shù)可選 available options: -a|--arch: set arch ([aarch64|x86]), default is aarch64 -h|--help EOF exit } # 檢查gcc版本 function check_gcc() { export compiler=$(which gcc) ### get version code MAJOR=$(echo __GNUC__ | $compiler -E -xc - | tail -n 1) # 獲取 gcc 主版本號 MINOR=$(echo __GNUC_MINOR__ | $compiler -E -xc - | tail -n 1) # 獲取 次版本號 PATCHLEVEL=$(echo __GNUC_PATCHLEVEL__ | $compiler -E -xc - | tail -n 1) # 獲取 修訂號 gcc_version=${MAJOR}.${MINOR}.${PATCHLEVEL} # 檢查 gcc 是否 >= 5.4.0 if ((${MAJOR} < 5)) || ((${MAJOR} == 5 && ${MINOR} < 4)) || ((${MAJOR} == 5 && ${MINOR} == 4 && ${PATCHLEVEL} < 0)); then echo "Your gcc version is ${gcc_version}" echo "x86 GCC version should be >= 5.4.0, please unpack ddk/package/host/gcc-5.4.0.tar.gz to install then re-execute the install.sh." exit # 版本低于 5.4.0,則終止并提示安裝 else echo "GCC version check success. GCC version is ${gcc_version}." fi } function build_arm() { # 刪除舊的 build_arm 目錄 rm -rf build_arm rm -rf outputs mkdir build_arm cd build_arm # check environment for arm64 # 檢查 LINARO_GCC_ROOT 環(huán)境變量 if [ ! $LINARO_GCC_ROOT ];then echo "Please set environment LINARO_GCC_ROOT correctly" # 若未設(shè)置,則使用默認(rèn)路徑 export LINARO_GCC_ROOT=/arm-gnu-toolchain-12.2.rel1-x86_64-aarch64-none-linux-gnu else export LINARO_GCC_ROOT=${LINARO_GCC_ROOT} fi # 設(shè)置 gcc/g++ 交叉編譯器 export CC="${LINARO_GCC_ROOT}/bin/aarch64-none-linux-gnu-gcc" export CXX="${LINARO_GCC_ROOT}/bin/aarch64-none-linux-gnu-g++" # 執(zhí)行 CMake 和 Make,選項 PLATFORM_AARCH64=ON cmake .. -Dbuild_type=${build_type} -DPLATFORM_AARCH64=ON make -j8 make install } function build_x86() { rm -rf build_x86 rm -rf outputs mkdir build_x86 cd build_x86 # 調(diào)用 check_gcc,確保 gcc 版本合格 check_gcc # 不使用交叉編譯器,直接使用本機(jī) gcc/g++ export CC=gcc export CXX=g++ # cmake 選項 PLATFORM_AARCH64=OFF cmake .. -Dbuild_type=${build_type} -DPLATFORM_AARCH64=OFF make -j8 make install } # 支持的架構(gòu) aarch64、x86 ARCH_OPTS=(aarch64 x86) # getopt 命令行選項解析工具,用于處理命令行中的選項(如 -a、--arch 等),詳解見下文 # -o a:h:定義短選項,a:接收一個參數(shù),表示 --arch 選項,h:表示 --help 選項,不接參數(shù) # -al arch:,help:定義長選項,arch:接收一個參數(shù),--arch 后需跟一個值,help:不需要參數(shù) # -- "$@":"$@" 是傳遞給腳本的所有命令行參數(shù),-- 用于標(biāo)識選項結(jié)束,防止后續(xù)的命令行參數(shù)被當(dāng)作選項解析 GETOPT_ARGS=`getopt -o a:h -al arch:,help -- "$@"` # 通過 eval 命令將 getopt 解析后的選項參數(shù)設(shè)置為當(dāng)前腳本的命令行參數(shù)。確??梢允褂?nbsp;$1, $2 等變量訪問解析后的命令行選項 eval set -- "$GETOPT_ARGS" # 當(dāng) $1 不為空時,進(jìn)入循環(huán) # $1 是第一個命令行參數(shù),循環(huán)會遍歷所有傳入的參數(shù),直到所有參數(shù)都被處理完。 while [ -n "$1" ] do case "$1" in -a|--arch) # 匹配 -a 或 --arch 選項 arch=$2 # 將第二個參數(shù)(即 --arch 后的值)賦值給變量 arch shift 2 # shift 命令會將位置參數(shù)左移 2 位,意味著處理過的選項被移除,接下來可以處理下一個參數(shù) # 檢查 arch 是否是有效的選項之一。"${ARCH_OPTS[*]}" 是一個數(shù)組,包含所有有效的架構(gòu)選項。 # [[ ! "${ARCH_OPTS[*]}" =~ $arch ]]:使用正則表達(dá)式檢查 $arch 是否在 ARCH_OPTS 數(shù)組中。 # 如果無效,則打印錯誤信息并調(diào)用 show_usage 顯示幫助 if [[ ! "${ARCH_OPTS[*]}" =~ $arch ]] ; then echo "invalid arch: $arch" show_usage fi ;; # 匹配 -h 或 --help 選項,如果用戶請求幫助,則調(diào)用 show_usage 函數(shù)顯示幫助信息,之后使用 break 跳出循環(huán)。 -h|--help) show_usage; break;; # 當(dāng)遇到 -- 時,停止解析選項,后面的參數(shù)被視為位置參數(shù) --) break ;; # *用于匹配其他任何不符合上述選項的參數(shù) *) echo $1,$2 show_usage; break;; esac done # 根據(jù) arch 選擇 build_arm 或 build_x86 if [[ $arch == "aarch64" ]]; then build_arm else build_x86 fi
getopt 會返回一個規(guī)范化的、已排序的選項和參數(shù)字符串,存儲在 GETOPT_ARGS 變量中。例如,輸入:
./build.sh -a x86 --help
則 GETOPT_ARGS 可能會被解析成:
--arch x86 --help
希望把 build.sh 腳本運(yùn)行起來:
開啟調(diào)試模式,打印執(zhí)行的每一條命令及其參數(shù) set -x 如果任何命令執(zhí)行失敗(返回非零退出狀態(tài)),則立即終止腳本執(zhí)行 set -e 運(yùn)行 build.sh,并傳遞參數(shù) -a x86 bash build.sh -a x86
到這兒,項目構(gòu)建編譯就完成了。
3.程序執(zhí)行
項目構(gòu)建完成后,會在 vp/vp_samples 下準(zhǔn)備好程序可執(zhí)行的相關(guān)依賴文件
vp_samples . ├── data └── script_x86 ├── 07_single_rotate │ ├── rotate.jpg │ └── run_single_rotate.sh └── x86 ├── bin │ └── single_rotate └── lib ├── libalog.so.1 ├── libarm_model_gdc.so ├── libhb_arm_rpc.so ├── libhbmem.so.1 ├── libhbucp.so ├── libhbvp.so ├── libhlog.so -> libhlog.so.1 ├── libhlog.so.1 -> libhlog.so.1.14.3 ├── libhlog.so.1.14.3 ├── libhlog_wrapper.so ├── libopencv_world.so.3.4 └── libperfetto_sdk.so
運(yùn)行 vp_samples/script_x86/07_single_rotate/run_single_rotate.sh 腳本即可。
# bin可執(zhí)行文件路徑 bin=../x86/bin/single_rotate # 二進(jìn)制文件目錄 ../x86/bin/ root=../x86/bin/ # 共享庫目錄 ../x86/lib lib=../x86/lib # 指定運(yùn)行時動態(tài)鏈接庫路徑,確保執(zhí)行 single_rotate 時能找到 ../x86/lib 里的共享庫(.so) # ${LD_LIBRARY_PATH} 可能已經(jīng)有其他路徑,: 號保證新路徑追加,不會覆蓋原有路徑 export LD_LIBRARY_PATH=${lib}:${LD_LIBRARY_PATH} # 將 root 和 bin 目錄添加到 PATH 變量,使 single_rotate 可直接運(yùn)行,而無需寫完整路徑。 export PATH=${root}:${bin}:${PATH} export HB_DSP_ENABLE_CONFIG_VDSP=true # 開啟 DSP 配置 # 設(shè)置 DSP 日志級別 export HB_DSP_LOG_LEVEL=3 export HB_DSP_VDSP_LOG_LEVEL=3 export HB_UCP_ENABLE_RELAY_MODE=false # 禁用 UCP 透傳模式 export HB_DSP_CMODEL_IMAGE=${root}/image/vdsp0 # 指定 DSP 仿真鏡像路徑 export HB_DSP_CONNECT_RETRY_TIMES=0 # DSP 連接失敗時,不進(jìn)行重試 # ${XTENSA_ROOT} is the root directory where the xtensa compilation environment is installed. # The user needs to install the compilation environment by himself. # For details, please see the dsp development document-Linux development environment installation chapter in the oe document # For example, in oe document, need to set 'export XTENSA_ROOT=/opt/xtensa/XtDevTools/install/tools/RI-2021.7-linux/XtensaTools/' export XTENSA_CORE=Vision_Q8 # 指定使用 Vision_Q8 處理器 export XTENSA_VERSION=RI-2023.11-linux # 指定 Xtensa 版本 # 設(shè)定 Xtensa 處理器的配置文件路徑 # Xtensa 開發(fā)工具的安裝目錄,用戶需自行安裝并正確設(shè)置該變量 export XTENSA_SYSTEM=${XTENSA_ROOT}/../../../builds/${XTENSA_VERSION}/${XTENSA_CORE}/config export XTENSA_CONFIG=${XTENSA_ROOT}/../../../builds/${XTENSA_VERSION}/${XTENSA_CORE}/config # 執(zhí)行 single_rotate,并傳遞所有命令行參數(shù) $* # ${bin} 解析為 ../x86/bin/single_rotate,所以實(shí)際執(zhí)行:../x86/bin/single_rotate $* ${bin} $*
至此,程序完成運(yùn)行。
*博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點(diǎn),如有侵權(quán)請聯(lián)系工作人員刪除。