【Hi3519DV500】开机自启动 目录自启动脚本分析appB依赖于appA脚本自启动脚本分析在单板上验证可以看到在开机自启动项中有以下文件其中rcS的内容如下即执行/etc/init.d/下的S开头的脚本从S后的数字从小到大依次优先执行我们也在/etc/init.d/新建一个S开头的脚本.如S99uart1txrxcfg并写入以下内容同时修改S99uart1txrxcfg为可执行权限chmod 777 S99uart1txrxcfgS90autorun如下#!/bin/sh NORMAL\033[0;39m RED\033[1;31m GREEN\033[1;32m BLUE\033[1;34m umask 027 # 是否扩容 / TOTAL_SIZE$(df -m / | awk NR2 {print $2}) echo $TOTAL_SIZE if [ $TOTAL_SIZE -lt 512 ]; then echo resize2fs resize2fs /dev/mmcblk0p6 else echo no need resize2fs fi cd /komod ./load3519dv500 -i /root/tools/pin_mux.sh /root/tools/time.sh # 切换 usb2 otg 为host echo host /proc/10320000.usb30drd/mode LOG_FILE/root/log/$(date %Y%m%d_%H%M%S).txt cd /root/App ./appA $LOG_FILE 21 cd /root/tools/pqtools/Hi3519DV500_PQ_V2.0.2.0 ./PQTools.sh -c步骤操作说明1umask 027设置新建文件的默认权限属主可读写执行同组可读可执行其他人无权限2检查根分区大小使用df -m /获取根分区总容量MB若小于 512 MB 则执行resize2fs /dev/mmcblk0p6扩容否则跳过。3加载芯片驱动cd /komod ./load3519dv500 -i– 加载 Hi3519DV500 的硬件驱动或初始化固件-i通常表示初始化。4执行引脚复用和时间脚本/root/tools/pin_mux.sh– 配置 GPIO/引脚功能/root/tools/time.sh– 用于同步系统时间。5切换 USB2 OTG 模式echo host /proc/10320000.usb30drd/mode– 将 USB2.0 OTG 端口强制设为 HOST 模式而非设备模式。6启动主应用程序进入/root/NV10以后台方式运行./appA并将标准输出和错误重定向到/root/log/年月日_时分秒.txt。7启动图像质量PQ控制服务进入 PQ 工具目录执行./PQTools.sh -c– 即仅启动ittb_control控制服务不启动流服务。小结该脚本负责系统初始化扩容、驱动加载、引脚配置、USB模式、启动核心业务程序appA以及图像质量调试控制后台。/root/tools/pin_mux.sh# 串口 UART3 bspmm 0x0102600E0 0x1202 #GPIO8_5 UART3_TXD bspmm 0x0102600E4 0x1202 #GPIO8_6 UART3_RXD # IIC6 bspmm 0x00EFF002C 0x1302 #GPIO1_2 I2C6_SCL bspmm 0x00EFF0030 0x1302 #GPIO1_3 I2C6_SDA # IIC3 bspmm 0x0179F0028 0x1102 #GPIO11_1 I2C3_SDA bspmm 0x0179F002C 0x1102 #GPIO11_2 I2C3_SCL # GPIO7_2 bspmm 0x0102600B4 0x1300/root/tools/time.sh#!/bin/sh # 同步硬件时钟给系统时钟 hwclock -s # 获取当前系统时间的完整时间戳 current_time$(date %Y-%m-%d %H:%M:%S) # 判断时间文件是否存在 if [ -f /root/tools/time.txt ]; then # 从文件读取保存的时间 text_time$(cat /root/tools/time.txt) # 检查时间格式是否有效基本格式检查 if ! echo $text_time | grep -q ^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\} [0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}$; then echo 错误/root/tools/time.txt 中的时间格式无效 echo 时间格式应为: YYYY-MM-DD HH:MM:SS echo 当前内容: $text_time echo $current_time /root/tools/time.txt exit 0 fi echo 当前时间: $current_time echo 文本时间: $text_time # 比较两个时间使用字典序比较因为格式固定 if [ $current_time \ $text_time ] || [ $current_time $text_time ]; then # 当前时间更大或相等使用当前时间 echo 使用当前时间: $current_time # 将当前时间写入文件清空写的方式 echo $current_time /root/tools/time.txt echo 已将当前时间写入文件 else # 保存的时间更大使用保存的时间 echo 使用文本时间同步系统时间: $text_time date -s $text_time hwclock -w fi exit 0 else # 如果文件不存在创建文件并写入当前时间 echo 文件不存在使用当前时间并创建文件: $current_time echo $current_time /root/tools/time.txt exit 0 fi/root/tools/time.txt2026-06-25 06:04:19这样就可以在app中对/root/tools/time.txt修改和同步时间了如下#define SYSTEM_TIME_PATH /root/tools/time.txt bool SystemTimeSettingWidget::CreateDirIfNotExists(const QString dirPath) { if (dirPath.isEmpty()) { qWarning() dirPath is empty; return false; } QDir dir(dirPath); if (dir.exists()) { return true; } return dir.mkpath(.); // mkpath会创建所有必要的父目录 } bool SystemTimeSettingWidget::WriteTextData(const QString filePath, const QString text, const QString encoding) { QFileInfo fileInfo(filePath); if (!CreateDirIfNotExists(fileInfo.absolutePath())) { return false; } QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { qDebug() open file failed: filePath; return false; } QTextStream stream(file); if (encoding.toUpper() UTF-8) { stream.setCodec(UTF-8); } else if (encoding.toUpper() GBK) { stream.setCodec(GBK); } else if (encoding.toUpper() UTF-16) { stream.setCodec(UTF-16); } else if (encoding.toUpper() LATIN1) { stream.setCodec(ISO 8859-1); } stream text; stream.flush(); file.close(); return true; } void SystemTimeSettingWidget::UpdateSystemTime(void) { QDate date(m_dataTimeInfo.year, m_dataTimeInfo.month, m_dataTimeInfo.day); QTime time(m_dataTimeInfo.hour, m_dataTimeInfo.minute, m_dataTimeInfo.second); QDateTime localDateTime(date, time, Qt::LocalTime); // QDateTime utcDateTime localDateTime.toUTC(); if (!date.isValid() || !time.isValid() || !localDateTime.isValid()) { return; } QString timeStr localDateTime.toString(yyyy-MM-dd hh:mm:ss); QString command QString(date -s \%1\).arg(timeStr); QStringList arguments; arguments -c command; QProcess *process new QProcess(this); connect(process, QOverloadint, QProcess::ExitStatus::of(QProcess::finished), [process, timeStr, this](int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus); if (exitCode 0) { qDebug() update system time success; } else { qDebug() update system time failed; } QTimer::singleShot(100, [timeStr, this]() { QProcess::execute(hwclock, QStringList() --systohc); // 同步到硬件时钟 WriteTextData(SYSTEM_TIME_PATH, timeStr, UTF-8); }); process-deleteLater(); }); process-start(sh, arguments); }PQTools.sh是海思平台用于图像质量调试与参数控制的辅助工具集脚本主要管理两个核心进程ittb_stream– 负责视频流采集、ISP 处理、编码输出等流媒体任务依赖传感器型号。ittb_control– 负责提供控制接口如参数调节、命令接收、状态查询等通常用于与上位机或调试软件通信。脚本通过命令行参数提供以下功能模式参数功能描述-c仅启动ittb_control控制服务。该模式不依赖传感器参数用于启动参数调节后台。在S90autorun中正是使用此模式。-s sensortype仅启动ittb_stream需指定传感器类型如 IMX334、OV5640 等用于单次采集/编码流。-a sensortype全功能启动先启动ittb_stream等待其内部RecvCfgProc或IspRun线程就绪再启动ittb_control。适用于完整 PQ 调试环境。-sc类似于-a但启动ittb_control时传递参数1具体含义可能为开启某种调试模式。-as停止所有相关进程先killall ittb_control再killall ittb_stream并会等待最多 5 秒若进程未退出则强制kill -9。-h显示帮助信息。内部细节脚本会动态设置LD_LIBRARY_PATH以包含libs目录下的库文件。启动ittb_stream时会等待其特定线程{RecvCfgProc}或{IspRun}出现或进程退出以确保流服务已就绪再启动控制服务-a和-sc模式。停止时使用循环检测进程 PID超时后强制终止确保进程被彻底清理。PQTools.sh在整体系统中的角色在S90autorun中调用PQTools.sh -c意味着系统开机后仅启动图像质量的控制服务而不启动流服务。这可能是因为流媒体采集由appA负责见步骤 6二者分工明确控制服务作为独立后台常驻用于接收外部 PQ 参数调节指令比如通过串口或网络实现动态调优而不干扰主业务流。简而言之PQTools.sh是海思平台 PQ 调试工具链的启动/停止管理器用于灵活控制图像质量相关的流处理和控制组件方便开发人员或产线进行画质调试和参数固化。总结S90autorun是系统级启动脚本完成硬件初始化、业务程序启动以及 PQ 控制服务的后台运行。PQTools.sh是 PQ 调试工具包的前端管理脚本提供启动/停止ittb_stream和ittb_control的多种模式是图像质量调优的重要入口。如果需要完整启用 PQ 调试含流可手动执行./PQTools.sh -a sensortype替代-c但需注意与现有appA的兼容性。appB依赖于appA脚本appB依赖于appA运行到某一步才能启动否则appB早运行会崩溃。上述的脚本虽然能够起到效果但是一直sleep循坏检测会导致吃CPU资源同时也不够及时。因此可以使用利用inotify或pidfilewait事件驱动应用自身可在退出时通过at_exit脚本重新启动或父进程使用wait阻塞等待子进程退出无需轮询。# 父进程启动子进程并等待其退出后自动重启 while true; do $APP_PATH wait $! done这种方式仅在子进程退出时触发重启CPU 负载几乎为零。新建脚本S99xxxxxx.sh每次启动 B 之前都显式等待 A 的就绪标志文件并检查 A 进程是否存活。B 退出后自动重启且每次启动前重新检查 A 状态。#!/bin/sh A_PATH./appA B_PATH./appB READY_FILE/tmp/appA_ready # ---------- 函数启动 A 并确保就绪 ---------- ensure_A_running_and_ready() { # 如果 A 尚未运行则启动它 if ! kill -0 $A_PID 2/dev/null; then rm -f $READY_FILE $A_PATH A_PID$! echo 启动 AppA (PID: $A_PID) fi # 等待 READY_FILE 出现最多等待30秒可根据需要调整 local timeout30 local waited0 while ! [ -f $READY_FILE ]; do sleep 0.2 waited$((waited 1)) if [ $waited -ge $((timeout * 5)) ]; then echo 等待 AppA 就绪超时重新启动 A... # 强制杀死旧的 A如果还活着 kill -9 $A_PID 2/dev/null # 递归调用自身 ensure_A_running_and_ready return fi # 如果 A 进程意外退出重新启动 if ! kill -0 $A_PID 2/dev/null; then echo AppA 在就绪前崩溃重新启动... rm -f $READY_FILE $A_PATH A_PID$! waited0 # 重置等待计数 fi done echo AppA 已就绪 (PID: $A_PID) } # ---------- 主循环 ---------- # 首次启动 A 并等待就绪 ensure_A_running_and_ready while true; do # 每次启动 B 之前再次确保 A 就绪以防 A 在 B 运行期间重启过 ensure_A_running_and_ready # 启动 B $B_PATH B_PID$! echo 启动 AppB (PID: $B_PID)等待退出... wait $B_PID echo AppB 已退出 (退出码: $?) done在 appA 源代码中请务必在所有关键初始化代码完成之后再执行创建文件的操作。int main() { // 1. 解析配置文件 // 2. 连接硬件/网络 // 3. 加载资源 // ... 其他初始化操作 ... // 关键位置 // 走到这里说明所有初始化都已成功AppA 已准备好接收 AppB 的连接 FILE *fp fopen(/tmp/appA_ready, w); if (fp) { fclose(fp); } // // 4. 进入主循环事件循环、消息处理等 while (running) { // 处理业务逻辑 } return 0; }对于无法修改 AppA 源码可以采用一个“包装脚本Wrapper”来模拟创建文件#!/bin/sh # 包装脚本启动 AppA等待3秒假设初始化需要3秒然后创建 ready 文件 ./appA sleep 3 touch /tmp/appA_ready wait但这种方式非常不推荐因为“固定等待3秒”属于硬编码如果系统负载变高AppA 初始化变慢AppB 照样会崩溃。所以强烈建议直接修改 AppA 源码这是最健壮、最优雅的做法。使用inotifywait方式检测到appA创建的/tmp/xxx标志文件后启动appB。使用inotifywait来自inotify-tools是最标准的做法它基于 Linux 内核事件通知进程会阻塞在read系统调用上CPU 占用率为 0。#!/bin/bash # 配置区 TARGET_FILE/tmp/xxx # 你要等待的文件 APP_CMD/root/appPath/app # 要启动的 APP LOG_DIR/root/log # mkdir -p $LOG_DIR LOG_FILE$LOG_DIR/wait_$(date %Y%m%d_%H%M%S).log # 1. 如果文件已经存在立即启动避免死等 if [ -f $TARGET_FILE ]; then echo $(date): 文件已存在直接启动 $APP_CMD | tee -a $LOG_FILE nohup $APP_CMD $LOG_FILE 21 exit 0 fi echo $(date): 等待目标文件 $TARGET_FILE 被创建... | tee -a $LOG_FILE # 2. 解析目录和文件名 DIR$(dirname $TARGET_FILE) BASENAME$(basename $TARGET_FILE) # 3. 创建命名管道FIFO用于安全接收事件避免管道子 shell 陷阱 FIFO/tmp/inotify_$$.fifo mkfifo $FIFO # 4. 后台启动 inotifywait-m 持续监听-q 静默只输出文件名 inotifywait -m -q -e create -e moved_to --format %f $DIR $FIFO INOTIFY_PID$! # 5. 从 FIFO 读取事件阻塞等待不消耗 CPU while read FILENAME $FIFO; do if [ $FILENAME $BASENAME ]; then echo $(date): 检测到文件 $BASENAME正在启动应用... | tee -a $LOG_FILE # 启动目标 APP后台运行脱离终端 nohup $APP_CMD $LOG_FILE 21 # 6. 清理资源并退出 kill $INOTIFY_PID 2/dev/null rm -f $FIFO exit 0 fi # 注意如果创建了其他无关文件循环会继续等待不会退出 done嵌入式系统可能默认不带inotify-tools需要先编译或安装。先执行which inotifywait确认是否已经安编译安装此tools。如果确实没有且无法安装可以退而求其次使用 CPU 轮询# 替代方案如果无 inotify-tools使用信号驱动依然比固定 sleep 省资源 while [ ! -f $TARGET_FILE ]; do # 但可以设置较长的 sleep如 1s实测对嵌入式 CPU 影响极小约 0.1% sleep 1 done nohup $APP_CMD $LOG_FILE 21 也可以使用如下的方式【不推荐】如果sleep/usleep延时过小则很吃CPU资源如果过大则无法快速反应。#!/bin/sh export HOME/root # 硬件初始化 echo Initializing hardware settings... # 硬件时钟同步到系统时钟 hwclock -s # 配置uart1串口寄存器引脚复用为tx/rx模式 bspmm 0x0102600DC 0x1201 bspmm 0x0102600D8 0x1301 # 环境变量设置 echo Setting environment variables... export LD_LIBRARY_PATH/usr/lib/qtlib:$LD_LIBRARY_PATH export QT_QPA_PLATFORM_PLUGIN_PATH/home/app/platforms export QT_QPA_PLATFORMlinuxfb:fb/dev/fb0:rotation0 # 指定字库也可以在QT代码中实现字库加载直接生成app则无需字库和指定字库 # export QT_QPA_FONTDIR/usr/lib/fonts # 配置区域 APP_PATH/home/app/appB APP_NAMEappB LOCK_FILE/tmp/qt5_demo_monitor.lock CHECK_INTERVAL5 touch $LOCK_FILE # # 定义监控函数 start_qt5_demo_when_appA_ready() { echo [$(date)] Waiting for appA to run... while ! pgrep -f appA /dev/null; do sleep 1 done echo [$(date)] appA is running, starting appB... while true; do # --- 核心监控逻辑 --- if (flock -n 9) then # 获取锁成功 PIDS$(pgrep -f $APP_NAME) if [ -n $PIDS ]; then # 发现进程正在运行 echo [$(date)] Status: $APP_NAME is running (PIDs: $PIDS) # --- 新增逻辑检查是否有多余的进程 --- # 使用 wc -l 统计进程数量 COUNT$(echo $PIDS | wc -l) if [ $COUNT -gt 1 ]; then echo [$(date)] CRITICAL: Found $COUNT instances running! Cleaning up... # 如果数量大于1强制 kill 所有实例 # 注意这里 kill 的是查到的 PID不包括 monitor 自身monitor 是 shell 脚本不是 appB kill $PIDS 2/dev/null || true # 等待进程彻底退出防止立即重启又失败 sleep 1 # 再次确认并强制终止可能残留的进程 kill -9 $(pgrep -f $APP_NAME) 2/dev/null || true sleep 1 fi else # 没有发现进程准备启动 echo [$(date)] WARNING: $APP_NAME not found. Launching... # 在启动前再次双重检查并清理防御性编程 # 有时候进程刚挂掉但残留的 PID 文件或状态还在 EXTRA_PIDS$(pgrep -f $APP_NAME) if [ -n $EXTRA_PIDS ]; then echo [$(date)] INFO: Found stale processes ($EXTRA_PIDS), killing... kill $EXTRA_PIDS sleep 1 kill -9 $EXTRA_PIDS 2/dev/null || true fi # 切换目录并启动 cd /home/app || exit 1 echo [$(date)] EXEC: Starting $APP_PATH $APP_PATH fi else # 获取锁失败说明另一个监控实例正在操作 # echo [$(date)] INFO: Another monitor instance is active, skipping check. : fi sleep $CHECK_INTERVAL done 9$LOCK_FILE } # 启动执行 echo Starting application monitor... start_qt5_demo_when_appA_ready 因是在rcS中echo所以上文中的开头上文的功能1等待appA进程起来后再拉起appB进程2每隔5s检测appB是否运行没运行则拉起如果检测到同时运行多个appB则kill掉所有appB后再重新拉起。如果需要配置到文件系统中即将S99uart1txrxcfg文件添加到/hi3519dv500/smp/a55_linux/source/bsp/rootfs_scripts/rootfs/etc/init.d下重新编译烧写固件即可。举例2编译app监听脚本如monitor_myapp.sh# !/bin/sh PROGRAM_PATH/root/path/my_app PID_FILE/root/my_app.pid LOG_FILE/root/my_app.log start() { echo Starting myprogram... # 检查程序是否已运行 if [ -f $PID_FILE ]; then pid$(cat $PID_FILE) if ps -p $pid /dev/null; then echo Program is already running (PID: $pid) return 0 else echo Cleaning up stale PID file... rm -f $PID_FILE fi fi # 启动程序并进入循环监控 while true; do # 启动程序替换为你的实际启动命令 $PROGRAM_PATH $LOG_FILE 21 pid$! echo $pid $PID_FILE echo $(date): Program started with PID $pid $LOG_FILE wait $pid # 获取程序退出码 exit_code$? rm -f $PID_FILE # 记录日志 echo $(date): myprogram exited with code $exit_code, restarting... $LOG_FILE # 短暂延迟避免无限快速重启 sleep 2 # 如果是正常退出如手动停止则跳出循环可选逻辑 if [ $exit_code -eq 0 ]; then echo $(date): myprogram stopped normally. $LOG_FILE break fi done } start