Buling-

导航

Linux 下开机自启动设置

瑞芯微RV1126B下Buildroot开机自启动

lINUX 下开机自启动的方法有很多,目前我知道的较为常用的主要有三种:rc.localinit.dsystemctl
但是我所使用的是 Buildroot 系统,没有 update-rc.d 工具,无法进行链接,所以主要使用 rc.localinit.d 设置开机自启动

rc.local文件中添加自启动命令

示例为开机启动 rc.local 触发 serial.sh 脚本文件执行串口输出程序。因为不想在系统启动文件里面做过多的修改,避免造成设备启动卡死,所以选择创建 rc.local 文件单独作为统一自定义自启动入口文件。

设置流程

/etc/init.d/rcS系统根本启动脚本

极简系统 Buildroot 默认不执行 rc.local,需要在 rcS 末尾添加触发逻辑

#!/bin/sh

# 原有逻辑:遍历执行/etc/init.d/S??*脚本
for i in /etc/init.d/S??* ;do
     [ ! -f "$i" ] && continue
     case "$i" in
        *.sh)
            (
                trap - INT QUIT TSTP
                set start
                . $i
            )
            ;;
        *)
            $i start
            ;;
    esac
done

# 新增:触发rc.local(核心!)
echo "------ rcS start run rc.local: $(date) ------" >> /tmp/rcs_debug.log 2>&1
if [ -x "/etc/rc.local" ]; then
     /etc/rc.local >> /tmp/rcs_debug.log 2>&1
     echo "------ rc.local run over : $? ------" >> /tmp/rcs_debug.log 2>&1
else
     echo "ERROR: /etc/rc.local couldnot find! " >> /tmp/rcs_debug.log 2>&1
fi

/etc/rc.local 统一自定义自启入口

若目录下没有该文件,可以自行创建

#!/bin/sh -e
# 极简自启入口:仅延迟+调用serial.sh
# 1. 清空旧日志(避免语法错误,直接写路径)
rm -f /tmp/rc_local.log

# 2. 延迟20秒(确保串口/文件系统就绪)
sleep 20

# 3. 记录自启触发日志
echo "------ [ $(date) ] rc.local trigger serial.sh ------" >> /tmp/rc_local.log 2>&1

# 4. 执行serial.sh(后台运行)
if [ -x "/opt/serial/serial.sh" ]; then
    /opt/serial/serial.sh >> /tmp/rc_local.log 2>&1 &
else
    echo "------ [ $(date) ] ERROR: serial.sh no execute permission ------" >> /tmp/rc_local.log 2>&1
    chmod +x /opt/serial/serial.sh && /opt/serial/serial.sh >> /tmp/rc_local.log 2>&1 &
fi

exit 0

/opt/serial/serial.sh 自定义执行脚本

该脚本主要是为了创建可写日志目录 (/tmp/serial) 与执行二进制程序 serial

#!/bin/sh
# 职责:创建可写日志目录 + 执行核心程序
LOG_DIR="/tmp/serial"
EXE_FILE="/opt/serial/serial"

# 1. 创建/tmp/serial目录(规避/opt只读)
mkdir -p "${LOG_DIR}" 2>/dev/null
chmod 777 "${LOG_DIR}"

# 2. 记录脚本执行日志
echo "[ $(date) ] serial.sh: create log dir ${LOG_DIR}" >> "${LOG_DIR}/serial_sh.log" 2>&1

# 3. 执行核心程序(后台运行,避免阻塞系统)
if [ -x "${EXE_FILE}" ]; then
    echo "[ $(date) ] serial.sh: run ${EXE_FILE}" >> "${LOG_DIR}/serial_sh.log" 2>&1
    ${EXE_FILE} >> "${LOG_DIR}/serial_sh.log" 2>&1 &
else
    echo "[ $(date) ] serial.sh: ${EXE_FILE} no execute permission" >> "${LOG_DIR}/serial_sh.log" 2>&1
    chmod +x "${EXE_FILE}" && ${EXE_FILE} >> "${LOG_DIR}/serial_sh.log" 2>&1 &
fi

/opt/serial/serial.c 运行程序

程序需要使用交叉编译工具链生成二进制可执行程序

#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

// 配置项(集中管理)
#define UART_DEV "/dev/ttyS5"
#define BAUDRATE B115200
#define LOG_DIR "/tmp/serial"
#define LOG_FILE "/tmp/serial/serial.log"

// 全局串口文件描述符(避免多次open/close丢失配置)
int g_uart_fd = -1;

// 串口初始化:8N1+无流控+原始模式(解决乱码/错位)
int uart_init() {
    // 1. 打开串口(阻塞模式,避免数据丢失)
    g_uart_fd = open(UART_DEV, O_RDWR | O_NOCTTY);
    if (g_uart_fd < 0) {
        printf("UART open failed: %s\n", UART_DEV);
        return -1;
    }

    // 2. 清空串口缓冲区
    tcflush(g_uart_fd, TCIOFLUSH);

    // 3. 完整配置termios
    struct termios opt;
    tcgetattr(g_uart_fd, &opt);
    cfsetispeed(&opt, BAUDRATE);
    cfsetospeed(&opt, BAUDRATE);
    opt.c_cflag &= ~PARENB;         // 无校验位
    opt.c_cflag &= ~CSTOPB;         // 1位停止位
    opt.c_cflag &= ~CSIZE;          // 清空数据位
    opt.c_cflag |= CS8;             // 8位数据位
    opt.c_cflag &= ~CRTSCTS;        // 关闭硬件流控
    opt.c_cflag |= CLOCAL | CREAD;  // 本地连接+启用接收
    opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始模式
    opt.c_oflag &= ~OPOST;          // 禁用输出处理
    opt.c_iflag &= ~(IXON | IXOFF | IXANY); // 关闭软件流控
    opt.c_cc[VTIME] = 0;            // 无超时
    opt.c_cc[VMIN] = 1;             // 至少1个字符返回
    tcsetattr(g_uart_fd, TCSANOW, &opt);

    printf("UART init success: %s (115200 8N1)\n", UART_DEV);
    return 0;
}

// 日志函数:写入/tmp+串口输出(纯ASCII,避免编码乱码)
void log_msg(const char *msg) {
    // 格式化时间(固定长度,无冗余)
    time_t now = time(NULL);
    struct tm *tm_now = localtime(&now);
    char time_buf[20] = {0};
    strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_now);
    
    // 拼接日志(\r\n适配串口监视器)
    char log_buf[128] = {0};
    snprintf(log_buf, sizeof(log_buf), "[%s] %s\r\n", time_buf, msg);

    // 写入/tmp日志文件(规避/opt只读)
    FILE *fp = fopen(LOG_FILE, "a");
    if (fp) {
        fputs(log_buf, fp);
        fflush(fp);
        fclose(fp);
    }

    // 串口精准写入
    if (g_uart_fd >= 0) {
        write(g_uart_fd, log_buf, strlen(log_buf));
        fsync(g_uart_fd);
    }
}

int main() {
    // 创建/tmp/serial目录(兜底)
    mkdir(LOG_DIR, 0777);
    remove(LOG_FILE); // 清空旧日志

    // 初始化串口
    if (uart_init() < 0) return -1;

    // 业务逻辑(纯英文,避免编码乱码)
    log_msg("UART init success: /dev/ttyS5 (115200 8N1)");
    log_msg("serial program started, running business logic");
    log_msg("Test output 1: 111");
    log_msg("Test output 2: 11");
    log_msg("Test output 3: 11111111");
    log_msg("Test output 4: 111");
    log_msg("serial program execution completed");

    // 关闭串口
    close(g_uart_fd);
    return 0;
}

Ubuntu下 rc-local 服务设置自启动

  • Ubuntu 18.04 及以上版本默认无 /etc/rc.local 文件,需手动创建
  • rc-local 服务默认以 root 身份运行,需确保脚本路径对 root 可读可执行
  • 推荐优先使用 systemd 自定义服务替代 rc-local (传统方式)

一、rc.local 配置(传统方式)

1. 创建/编辑 rc.local 文件

# 若不存在则创建文件
sudo touch /etc/rc.local
sudo chmod +x /etc/rc.local
sudo vim /etc/rc.local

#!/bin/sh -e
# -e:任意命令失败则脚本退出;若需忽略失败,可移除-e
# 延迟3秒:等待网络/系统资源加载完成(可根据实际调整)
sleep 3
# 执行自定义脚本(绝对路径,避免相对路径问题)
/home/usr/Desktop/test/hello/hello.sh
# 确保脚本最后返回0(rc-local要求正常退出码)
exit 0

# 验证 rc.local 权限
ls -l /etc/rc.local 
# 正确权限应为:-rwxr-xr-x 1 root root ...
# 若无执行权限则添加
sudo chmod +x /etc/rc.local

二、配置 rc-local.service 服务

1. 规范覆盖配置(避免修改系统默认文件)

# 创建自定义配置目录(若不存在)
sudo mkdir -p /etc/systemd/system/rc-local.service.d
# 创建覆盖配置文件
sudo vim /etc/systemd/system/rc-local.service.d/override.conf

写入以下内容:

# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=/etc/rc.local Compatibility
Documentation=man:systemd-rc-local-generator(8)
ConditionFileIsExecutable=/etc/rc.local
After=network.target network-online.target  # 追加network-online,确保网络完全就绪

[Service]
Type=forking          # 适合后台执行的脚本(fork子进程)
ExecStart=/etc/rc.local start
TimeoutSec=0          # 无超时限制
RemainAfterExit=yes   # 脚本执行完后服务仍标记为active
GuessMainPID=no       # 不猜测主进程PID

[Install]
WantedBy=multi-user.target  # 多用户模式下自启
Alias=rc-local.service

2. 重载配置并启用服务

# 核心步骤:重载systemd配置(修改服务文件后必须执行)
sudo systemctl daemon-reload
# 启用开机自启
sudo systemctl enable rc-local.service
# 启动服务
sudo systemctl start rc-local.service
# 检查服务状态
sudo systemctl status rc-local.service
  • 正常状态:Loaded: enabled + Active: active (exited)
  • 若报错:执行journalctl -u rc-local.service查看详细日志。

三、hello.sh 脚本

#!/bin/bash
# 注意:替换以下路径中的"usr"为实际用户名
# 集中定义变量,便于维护
GCC_TOOL="/home/usr/Desktop/rv1126bp/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc"
C_FILE="/home/usr/Desktop/test/hello/hello.c"       # 修正变量名
EXE_FILE="/home/usr/Desktop/test/hello/hello"
LOG_FILE="/home/usr/Desktop/test/hello/hello.log"
# 日志文件权限(避免root创建后普通用户无法读取)
LOG_PERM="644"

# 清空旧日志(避免日志过大,可替换为日志轮转)
rm -f "${LOG_FILE}"

# 第一步:检查编译器是否存在
if [ ! -x "${GCC_TOOL}" ]; then
    echo "$(date +'%Y-%m-%d %H:%M:%S') - 错误:编译器不存在或无执行权限:${GCC_TOOL}" >> "${LOG_FILE}" 2>&1
    exit 1
fi

# 第二步:检查C文件是否存在
if [ ! -f "${C_FILE}" ]; then
    echo "$(date +'%Y-%m-%d %H:%M:%S') - 错误:C文件不存在:${C_FILE}" >> "${LOG_FILE}" 2>&1
    exit 1
fi

# 第三步:判断是否需要编译
if [ ! -f "${EXE_FILE}" ] || [ "${C_FILE}" -nt "${EXE_FILE}" ]; then
    echo "$(date +'%Y-%m-%d %H:%M:%S') - 检测到C文件修改/可执行文件不存在,开始编译..." >> "${LOG_FILE}" 2>&1
    # 编译:添加-static避免动态库依赖(可选,根据目标机调整)
    "${GCC_TOOL}" "${C_FILE}" -o "${EXE_FILE}" -static -O2 >> "${LOG_FILE}" 2>&1
    # 检查编译结果
    if [ $? -ne 0 ]; then
        echo "$(date +'%Y-%m-%d %H:%M:%S') - 编译失败!错误码:$?" >> "${LOG_FILE}" 2>&1
        exit 1
    fi
    # 添加执行权限
    chmod +x "${EXE_FILE}" >> "${LOG_FILE}" 2>&1
    echo "$(date +'%Y-%m-%d %H:%M:%S') - 编译成功!" >> "${LOG_FILE}" 2>&1
else
    echo "$(date +'%Y-%m-%d %H:%M:%S') - C文件未修改,直接执行程序..." >> "${LOG_FILE}" 2>&1
fi

# 第四步:检查可执行文件是否可执行
if [ ! -x "${EXE_FILE}" ]; then
    echo "$(date +'%Y-%m-%d %H:%M:%S') - 错误:可执行文件无执行权限:${EXE_FILE}" >> "${LOG_FILE}" 2>&1
    chmod +x "${EXE_FILE}" >> "${LOG_FILE}" 2>&1
    # 再次检查
    if [ $? -ne 0 ]; then
        exit 1
    fi
fi

# 第五步:执行程序
echo "$(date +'%Y-%m-%d %H:%M:%S') - 开始执行程序:${EXE_FILE}" >> "${LOG_FILE}" 2>&1
"${EXE_FILE}" >> "${LOG_FILE}" 2>&1
EXEC_EXIT_CODE=$?

# 第六步:记录执行结果
echo "$(date +'%Y-%m-%d %H:%M:%S') - 程序执行完成!退出码:${EXEC_EXIT_CODE}" >> "${LOG_FILE}" 2>&1
# 调整日志权限(方便普通用户查看)
chmod ${LOG_PERM} "${LOG_FILE}" >> "${LOG_FILE}" 2>&1

echo -e "\n" >> "${LOG_FILE}" 2>&1
exit 0

四、hello.c 程序

#include <stdio.h>
#include <time.h>  // 新增:记录执行时间

int main(){
    // 刷新缓冲区,确保"hello_world"能立即写入日志
    setbuf(stdout, NULL);
    
    // 新增:打印执行时间,便于排查
    time_t now = time(NULL);
    printf("程序执行时间:%s", ctime(&now));
    
    printf("hello_world\n");  // 新增换行,避免输出截断
    for(int i = 0; i <20; ++i){
        printf("循环计数:%d\n", i);  // 补充注释,增强可读性
    }
    return 0;
}

五、验证自启动是否生效

1. 手动测试(重启前)

# 手动执行rc.local,验证脚本是否正常运行
sudo /etc/rc.local
# 查看日志
cat /home/usr/Desktop/test/hello/hello.log

2. 重启系统验证

# 重启
sudo reboot
# 重启后检查服务状态
sudo systemctl status rc-local.service
# 检查日志是否有新内容
cat /home/usr/Desktop/test/hello/hello.log

六、systemd自定义服务(推荐替代rc-local)

rc-local是传统方式,Ubuntu推荐用systemd自定义服务,更可控:

# 创建自定义服务文件
sudo vim /etc/systemd/system/hello.service

写入以下内容:

[Unit]
Description=Hello World 自启动服务
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot          # 一次性执行
User=root             # 执行用户(可改为普通用户,需调整路径权限)
ExecStart=/home/usr/Desktop/test/hello/hello.sh
TimeoutSec=30
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

启用服务:

sudo systemctl daemon-reload
sudo systemctl enable hello.service
sudo systemctl start hello.service
sudo systemctl status hello.service

posted on 2026-01-01 12:40  奈何清风  阅读(4)  评论(0)    收藏  举报