博客專欄

        EEPW首頁 > 博客 > 【征程 6】工具鏈 VP 示例為什么能運行

        【征程 6】工具鏈 VP 示例為什么能運行

        發布人:地平線開發者 時間:2025-04-01 來源:工程師 發布文章

          1.引言

          在上一篇文章【征程 6】VP 簡介與單算子實操 中,介紹了 VP 是什么,并以單算子 rotate 為例,介紹了 VP API 使用方法,但對于對 C++不那么熟悉的伙伴,可能會有這樣的疑問:一個 main 函數就讓 VP 示例跑起來了?沒有什么依賴嗎?CMakeLists.txt 沒看到,xxx.h 頭文件也沒有,甚至連怎么編譯的都沒寫,只有 main 文件中的 C++代碼,還是讓人有點迷迷瞪瞪。由于本人就是屬于對 C++不那么熟悉的同學,所以下面會從我的視角來介紹上一篇文章遺留的問題,如果其中有錯誤或表述不當的地方,歡迎評論指正。


            2.代碼解讀

            OE/samples/ucp_tutorial/目錄下的結構如下:

            .
            ├── 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

            有個感覺即可,后面會細致的解讀運行起一個 VP 示例所依賴的文件。


            2.1 main.cpp

            從 main.cpp 看過去,內容以及解讀如下:

            #include <iostream>    // 引入標準輸入輸出庫
            #include "rotate.h"    // 引入自定義頭文件 rotate.h
            
            int32_t main(int32_t argc, char **argv) {  // 主函數,接受命令行參數
              single_rotate();   // 調用 single_rotate() 函數
              return 0;          // 返回 0,表示程序正常結束,符合 C++ 規范
            }

            有兩處拿出來解釋下:#include “rotate.h”:該文件是一個自定義頭文件,用于聲明函數或類,代碼中調用的 single_rotate() 在 rotate.h 中被聲明,并在相應的 rotate.cpp 中被實現。int32_t main(int32_t argc, char **argv):入口函數 main(),接收兩個參數:argc:命令行參數個數(包括程序本身)。argv:存儲命令行參數的字符串數組(char*)。

            2.2 rotate.h

            下面看一下頭文件 rotate.h,代碼作用:聲明 single_rotate() 函數,并防止頭文件被重復包含。

            #ifndef VP_CODE_07_ROTATE_IMAGE_PROCESS_H_
            #define VP_CODE_07_ROTATE_IMAGE_PROCESS_H_
            
            #include "hobot/vp/hb_vp.h"   // 包含的頭文件,不在當前目錄下,為什么能包含?
            #include "log_util.h"         // 包含的頭文件,就在當前目錄下
            
            int32_t single_rotate();
            
            #endif  // VP_CODE_07_ROTATE_IMAGE_PROCESS_H_

            來了解一下這段代碼能防止頭文件被重復包含。

            #ifndef VP_CODE_07_ROTATE_IMAGE_PROCESS_H_
            #define VP_CODE_07_ROTATE_IMAGE_PROCESS_H_
            ...
            #endif  // VP_CODE_07_ROTATE_IMAGE_PROCESS_H_

            這種方式稱為 頭文件保護(Header Guard),用于防止頭文件的 重復包含,避免 編譯錯誤。

            還是有些不太理解?詳細解釋一下:

            1.什么是頭文件保護:在 C/C++ 語言中,頭文件(。h)是用于聲明變量、函數、類等的文件。為了防止頭文件被 重復包含(multiple inclusion),通常使用 頭文件保護(Header Guard),其基本結構是:

              #ifndef HEADER_NAME_H    // 如果 HEADER_NAME_H 未定義
              #define HEADER_NAME_H    // 定義 HEADER_NAME_H
              
              // 頭文件內容
              // 變量、函數、類的聲明等
              
              #endif  // 結束頭文件保護

                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"

                當 main.cpp 被編譯時,它會展開 #include:在 A.h 中直接包含 void foo();,B.h 也包含 A.h,再次引入 void foo();這會導致重復聲明,如果沒有 頭文件保護,編譯器可能會報錯:

                error: redefinition of ‘void foo()’

                當使用了上面#ifndef / #define / #endif,就可以避免這個問題,原理如下:

                第一次 包含 A.h 時:

                第二次 再次包含 A.h:

                編譯器在處理頭文件時會進行優化,所以頭文件保護不會影響性能,為了避免 重復包含頭文件 導致的編譯錯誤,提高代碼可維護性,推薦大家使用頭文件保護。

                最后,#ifndef 保護多個頭文件需要不同的宏名,因為宏名重復,也可能導致錯誤,建議使用 文件名相關的宏 方便記憶排查。

                2.3 log_util.h

                在 rotate.h 中包含了 log_util.h,定義了一些用于日志打印的宏,具體的代碼解讀可見文章:【征程 6】工具鏈 VP 示例中日志打印解讀

                2.4 rotate.cpp

                該文件解讀可見文章:【征程 6】VP 簡介與單算子實操

                2.5 CMakeLists.txt

                想了解 VP 示例中 CMakeLists.txt 的嵌套以及運行邏輯,可見文章:【征程 6】工具鏈 VP 示例中 Cmakelists 解讀

                2.6 build.sh

                該 Bash 腳本 用于 構建 aarch64(ARM64)或 x86(PC 端)架構的項目,并支持 自動檢測 gcc 版本,確保編譯環境正確。

                # 默認編譯 ARM64 版本、 Release 模式
                arch=aarch64
                build_type=release
                
                # 顯示幫助信息
                function show_usage() {
                cat <<EOF
                
                Usage: bash -e $0 <options>    # 第一個參數是目標架構,后面參數可選
                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 環境變量
                    if [ ! $LINARO_GCC_ROOT ];then
                        echo "Please set environment LINARO_GCC_ROOT correctly"
                        # 若未設置,則使用默認路徑
                        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
                    # 設置 gcc/g++ 交叉編譯器
                    export CC="${LINARO_GCC_ROOT}/bin/aarch64-none-linux-gnu-gcc"
                    export CXX="${LINARO_GCC_ROOT}/bin/aarch64-none-linux-gnu-g++"
                    # 執行 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
                    # 調用 check_gcc,確保 gcc 版本合格
                    check_gcc
                    # 不使用交叉編譯器,直接使用本機 gcc/g++
                    export CC=gcc
                    export CXX=g++
                    # cmake 選項 PLATFORM_AARCH64=OFF
                    cmake .. -Dbuild_type=${build_type} -DPLATFORM_AARCH64=OFF
                    make -j8
                    make install
                }
                
                # 支持的架構 aarch64、x86
                ARCH_OPTS=(aarch64 x86)
                # getopt 命令行選項解析工具,用于處理命令行中的選項(如 -a、--arch 等),詳解見下文
                # -o a:h:定義短選項,a:接收一個參數,表示 --arch 選項,h:表示 --help 選項,不接參數
                # -al arch:,help:定義長選項,arch:接收一個參數,--arch 后需跟一個值,help:不需要參數
                # -- "$@":"$@" 是傳遞給腳本的所有命令行參數,-- 用于標識選項結束,防止后續的命令行參數被當作選項解析
                GETOPT_ARGS=`getopt -o a:h -al arch:,help -- "$@"`
                # 通過 eval 命令將 getopt 解析后的選項參數設置為當前腳本的命令行參數。確保可以使用 $1, $2 等變量訪問解析后的命令行選項
                eval set -- "$GETOPT_ARGS"
                
                # 當 $1 不為空時,進入循環
                # $1 是第一個命令行參數,循環會遍歷所有傳入的參數,直到所有參數都被處理完。
                while [ -n "$1" ]
                do
                  case "$1" in
                    -a|--arch)    # 匹配 -a 或 --arch 選項
                      arch=$2    # 將第二個參數(即 --arch 后的值)賦值給變量 arch
                      shift 2    # shift 命令會將位置參數左移 2 位,意味著處理過的選項被移除,接下來可以處理下一個參數
                      # 檢查 arch 是否是有效的選項之一。"${ARCH_OPTS[*]}" 是一個數組,包含所有有效的架構選項。
                      # [[ ! "${ARCH_OPTS[*]}" =~ $arch ]]:使用正則表達式檢查 $arch 是否在 ARCH_OPTS 數組中。
                      # 如果無效,則打印錯誤信息并調用 show_usage 顯示幫助
                      if [[ ! "${ARCH_OPTS[*]}" =~ $arch ]] ; then
                        echo "invalid arch: $arch"
                        show_usage
                      fi
                      ;;
                    # 匹配 -h 或 --help 選項,如果用戶請求幫助,則調用 show_usage 函數顯示幫助信息,之后使用 break 跳出循環。
                    -h|--help) show_usage; break;;
                    # 當遇到 -- 時,停止解析選項,后面的參數被視為位置參數
                    --) break ;;
                    # *用于匹配其他任何不符合上述選項的參數
                    *) 
                      echo $1,$2 
                      show_usage; 
                      break;;
                  esac
                done
                
                # 根據 arch 選擇 build_arm 或 build_x86
                if [[ $arch == "aarch64" ]]; then
                  build_arm
                else
                  build_x86
                fi

                getopt 會返回一個規范化的、已排序的選項和參數字符串,存儲在 GETOPT_ARGS 變量中。例如,輸入:

                ./build.sh -a x86 --help

                則 GETOPT_ARGS 可能會被解析成:

                --arch x86 --help

                希望把 build.sh 腳本運行起來:

                開啟調試模式,打印執行的每一條命令及其參數
                set -x
                如果任何命令執行失敗(返回非零退出狀態),則立即終止腳本執行
                set -e
                運行 build.sh,并傳遞參數 -a x86
                bash build.sh -a x86

                到這兒,項目構建編譯就完成了。


                  3.程序執行

                  項目構建完成后,會在 vp/vp_samples 下準備好程序可執行的相關依賴文件

                  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

                  運行 vp_samples/script_x86/07_single_rotate/run_single_rotate.sh 腳本即可。

                  # bin可執行文件路徑
                  bin=../x86/bin/single_rotate
                  # 二進制文件目錄 ../x86/bin/
                  root=../x86/bin/
                  # 共享庫目錄 ../x86/lib
                  lib=../x86/lib
                  
                  # 指定運行時動態鏈接庫路徑,確保執行 single_rotate 時能找到 ../x86/lib 里的共享庫(.so)
                  # ${LD_LIBRARY_PATH} 可能已經有其他路徑,: 號保證新路徑追加,不會覆蓋原有路徑
                  export LD_LIBRARY_PATH=${lib}:${LD_LIBRARY_PATH}
                  # 將 root 和 bin 目錄添加到 PATH 變量,使 single_rotate 可直接運行,而無需寫完整路徑。
                  export PATH=${root}:${bin}:${PATH}
                  
                  export HB_DSP_ENABLE_CONFIG_VDSP=true    # 開啟 DSP 配置
                  # 設置 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 連接失敗時,不進行重試
                  
                  # ${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 版本
                  # 設定 Xtensa 處理器的配置文件路徑
                  # Xtensa 開發工具的安裝目錄,用戶需自行安裝并正確設置該變量
                  export XTENSA_SYSTEM=${XTENSA_ROOT}/../../../builds/${XTENSA_VERSION}/${XTENSA_CORE}/config
                  export XTENSA_CONFIG=${XTENSA_ROOT}/../../../builds/${XTENSA_VERSION}/${XTENSA_CORE}/config
                  
                  # 執行 single_rotate,并傳遞所有命令行參數 $*
                  # ${bin} 解析為 ../x86/bin/single_rotate,所以實際執行:../x86/bin/single_rotate $*
                  ${bin} $*

                  至此,程序完成運行。


                  *博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。




                  相關推薦

                  技術專區

                  關閉
                  主站蜘蛛池模板: 镇远县| 新宾| 长丰县| 精河县| 玛多县| 永春县| 合阳县| 瑞安市| 汝城县| 盐津县| 达拉特旗| 永春县| 璧山县| 冕宁县| 灵寿县| 宝丰县| 台州市| 淳安县| 乌海市| 崇信县| 武定县| 夏津县| 大连市| 武功县| 洪江市| 侯马市| 塘沽区| 娄底市| 吉水县| 葫芦岛市| 赫章县| 尚义县| 遂平县| 宁陕县| 秦皇岛市| 深水埗区| 林口县| 和龙市| 招远市| 太康县| 乳山市|