制作 arm-linux 的 sd 启动卡(AM335x 启动卡制作)

核心步骤:

  1. 制作用于拷贝的 SD 启动卡镜像文件:boot 文件(uboot,kernel等),roofs.tar.gz 文件系统
  2. 使用ubuntu 将待制作的启动卡 SD 卡进行格式化,格式化为需要的大小及内存格式分区
  3. 将步骤1 的镜像拷贝至步骤2 的对应分区,解压缩

踩坑经验:

  1. 步骤1 中的镜像制作时,须保持文件的所有者为 root;打包 rootfs.tar.gz 时,须注意将文件的属性一并进行打包
  2. 步骤3 ,文件拷贝至目标 SD 启动卡完成后,须注意解压缩后,某些文件的属性须设置为可执行,如init 文件,及其调用的 so 文件的属性等

可用的制卡脚本

点击查看代码

#!/bin/bash

# ==============================================================================
# 批量 SD 卡启动盘制作脚本 (基于 sfdisk)
# 适用场景: ARM 开发板 (AM335x, i.MX6, RK3399 等) 批量系统烧录
# 依赖工具: sfdisk, mkfs.vfat, mkfs.ext4, tar, md5sum, lsblk
# ==============================================================================

# ================= 配置区域 (请根据实际需求修改) =================
BOOT_FILES_DIR="./boot_files"       # 存放 MLO, u-boot.img, zImage, dtb 等文件的目录
ROOTFS_ARCHIVE="./rootfs.tar.gz"    # 根文件系统压缩包路径
LOG_FILE="batch_burn_log.csv"       # 日志文件路径
SYSTEM_DISK="sda"               # 【重要】当前主机的系统盘标识,防止误格式化
MIN_SIZE_GB=1                       # 最小 SD 卡容量限制 (GB)
MAX_SIZE_GB=64                      # 最大 SD 卡容量限制 (GB)
PARALLEL_JOBS=4                     # 最大并行烧录数量
# ================================================================

# 全局变量
START_TIME=$(date +%s)
TARGET_DEVICES=()
FAILED_DEVICES=()

# 初始化日志
echo "Timestamp,Device,Status,Message" > "$LOG_FILE"

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log_msg() {
    local device=$1
    local status=$2
    local message=$3
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "${timestamp},${device},${status},${message}" >> "$LOG_FILE"
    
    if [ "$status" == "FAIL" ]; then
        echo -e "${RED}[FAIL]${NC} ${device}: ${message}"
    elif [ "$status" == "SUCCESS" ]; then
        echo -e "${GREEN}[OK]${NC}   ${device}: ${message}"
    else
        echo -e "${YELLOW}[INFO]${NC} ${device}: ${message}"
    fi
}

# 1. 自动识别候选 SD 卡
scan_devices() {
    echo ">>> 正在扫描可用存储设备..."
    local candidates=()
    
    # 获取所有磁盘设备及其大小
    while read -r dev_name dev_size_bytes; do
        # 跳过系统盘
        if [[ "$dev_name" == "$SYSTEM_DISK" ]]; then
            continue
        fi
        
        # 计算大小 (GB)
        local size_gb=$((dev_size_bytes / 1024 / 1024 / 1024))
        
        # 检查容量范围
        if [ "$size_gb" -ge "$MIN_SIZE_GB" ] && [ "$size_gb" -le "$MAX_SIZE_GB" ]; then
            local full_path="/dev/${dev_name}"
            # 检查是否被挂载,如果挂载则尝试卸载
            if mount | grep -q "$full_path"; then
                log_msg "$full_path" "WARN" "设备已挂载,将尝试卸载"
                sudo umount -l "${full_path}"* 2>/dev/null
            fi
            candidates+=("$full_path")
        fi
    done < <(lsblk -d -n -o NAME,SIZE --bytes --noheadings | awk '{print $1, $2}')

    if [ ${#candidates[@]} -eq 0 ]; then
        echo -e "${RED}错误: 未找到符合条件的 SD 卡 (容量 ${MIN_SIZE_GB}-${MAX_SIZE_GB}GB, 非系统盘)${NC}"
        exit 1
    fi

    TARGET_DEVICES=("${candidates[@]}")
    echo ">>> 发现 ${#TARGET_DEVICES[@]} 张候选 SD 卡: ${TARGET_DEVICES[*]}"
}

# 2. 单卡烧录核心逻辑
burn_single_card() {
    local DEV=$1
    local boot_part="${DEV}1"
    local root_part="${DEV}2"
    
    log_msg "$DEV" "INFO" "开始烧录流程..."

    # --- Step 1: 清理与准备 ---
    # 强制卸载所有分区
    for part in $(lsblk -n -o MOUNTPOINT "$DEV" 2>/dev/null | grep -v '^$'); do
        sudo umount -l "$part" 2>/dev/null
    done
    # 清除旧分区表签名 (可选,防止残留引导信息)
    sudo wipefs -a "$DEV" 2>/dev/null || true
    
    # --- Step 2: 使用 sfdisk 分区 ---
    # AM335x 典型分区布局: 
    # Partition 1: Start 2048, Size 200MB (FAT32, Bootable)
    # Partition 2: Start 206848, Rest of disk (Linux)
    log_msg "$DEV" "INFO" "执行分区操作..."
    
    if ! sudo sfdisk "$DEV" <<EOF >/dev/null 2>&1
label: dos
label-id: 0x00000000
device: $DEV
unit: sectors
${DEV}1 : start=2048, size=409600, type=0c, bootable
${DEV}2 : start=411648, type=83
EOF
    then
        log_msg "$DEV" "FAIL" "sfdisk 分区失败"
        return 1
    fi
    
    # 通知内核重新读取分区表
    sudo partprobe "$DEV" 2>/dev/null || true
    sleep 1 # 等待节点生成

    # --- Step 3: 格式化 ---
    log_msg "$DEV" "INFO" "格式化分区..."
    if ! sudo mkfs.vfat -F 32 -n "boot" "${boot_part}" >/dev/null 2>&1; then
        log_msg "$DEV" "FAIL" "Boot 分区格式化失败"
        return 1
    fi
    
    if ! sudo mkfs.ext3 -L "rootfs" -m 0 "${root_part}" >/dev/null 2>&1; then
        log_msg "$DEV" "FAIL" "Rootfs 分区格式化失败"
        return 1
    fi

    # --- Step 4: 挂载与写入数据 ---
    local MOUNT_BOOT=$(mktemp -d)
    local MOUNT_ROOT=$(mktemp -d)

    # 显式指定挂载参数,禁用noexec,确保根分区支持可执行文件
    sudo mount -o rw,exec "${root_part}" "$MOUNT_ROOT"
    sudo mount "${boot_part}" "$MOUNT_BOOT"

    # 写入 Boot 文件
    if [ -d "$BOOT_FILES_DIR" ]; then
        sudo cp -r "${BOOT_FILES_DIR}"/* "$MOUNT_BOOT/"
        # 确保MLO在FAT32根目录且无隐藏属性
        sudo chmod 644 "$MOUNT_BOOT"/MLO 2>/dev/null || true
    else
        log_msg "$DEV" "WARN" "未找到 boot_files 目录,跳过 Boot 文件拷贝"
    fi

    # 写入 Rootfs:保留完整权限、所有者、特殊属性
    if [ -f "$ROOTFS_ARCHIVE" ]; then
        log_msg "$DEV" "INFO" "解压根文件系统 (耗时较长)..."
        # 关键修改:添加--same-owner参数,完整保留原始文件所有者信息
        if ! sudo tar xzpf "$ROOTFS_ARCHIVE" --same-owner -C "$MOUNT_ROOT" 2>/dev/null; then
            log_msg "$DEV" "FAIL" "Rootfs 解压失败"
            sudo umount "$MOUNT_BOOT" "$MOUNT_ROOT"
            rm -rf "$MOUNT_BOOT" "$MOUNT_ROOT"
            return 1
        fi
        
        # 新增:强制修复所有初始化脚本的可执行权限,彻底解决rcS权限拒绝
        log_msg "$DEV" "INFO" "修复根文件系统初始化脚本权限..."
        sudo chmod +x "${MOUNT_ROOT}/etc/init.d/rcS"
        sudo chmod 755 "${MOUNT_ROOT}/etc/init.d/"*
        # 确保passwd/shadow等关键系统文件权限符合安全规范
        sudo chmod 644 "${MOUNT_ROOT}/etc/passwd"
        sudo chmod 600 "${MOUNT_ROOT}/etc/shadow"
    else
        log_msg "$DEV" "WARN" "未找到 rootfs.tar.gz,跳过根文件系统写入"
    fi

    # 卸载前同步数据,避免权限属性未写入磁盘
    sudo sync
    sudo umount "$MOUNT_BOOT" "$MOUNT_ROOT"
    rm -rf "$MOUNT_BOOT" "$MOUNT_ROOT"

    # --- Step 5: 烧录后权限预校验 ---
    log_msg "$DEV" "INFO" "执行烧录后权限预校验..."
    # 临时挂载根分区,验证rcS权限是否正常
    local TMP_CHECK=$(mktemp -d)
    sudo mount "${root_part}" "$TMP_CHECK"
    # 校验rcS是否具备可执行权限
    if [ ! -x "${TMP_CHECK}/etc/init.d/rcS" ]; then
        log_msg "$DEV" "FAIL" "rcS权限校验失败,可执行位丢失"
        sudo umount "$TMP_CHECK"
        rm -rf "$TMP_CHECK"
        FAILED_DEVICES+=("$DEV")
        return 1
    fi
    sudo umount "$TMP_CHECK"
    rm -rf "$TMP_CHECK"
    log_msg "$DEV" "SUCCESS" "SD卡烧录完成,rcS权限校验通过"
    
    log_msg "$DEV" "SUCCESS" "烧录完成"
    return 0
}

# 3. 并行执行控制器
run_parallel_burn() {
    local pids=()
    local index=0
    
    echo ">>> 开始并行烧录 (最大并发: $PARALLEL_JOBS)"
    
    for dev in "${TARGET_DEVICES[@]}"; do
        # 等待空闲槽位
        while [ $(jobs -r | wc -l) -ge $PARALLEL_JOBS ]; do
            sleep 1
        done
        
        # 后台执行烧录
        burn_single_card "$dev" &
        pids+=($!)
    done

    # 等待所有后台任务完成
    for pid in "${pids[@]}"; do
        wait $pid
        if [ $? -ne 0 ]; then
            # 错误已在 burn_single_card 中记录
            :
        fi
    done
    
    echo ">>> 所有任务已结束"
}

# 4. 最终校验与报告
final_report() {
    local end_time=$(date +%s)
    local duration=$((end_time - START_TIME))
    
    echo ""
    echo "=========================================="
    echo "  烧录任务总结"
    echo "=========================================="
    echo "总耗时: ${duration} 秒"
    echo "成功/总数: $(grep -c "SUCCESS" "$LOG_FILE") / ${#TARGET_DEVICES[@]}"
    echo "失败列表:"
    grep "FAIL" "$LOG_FILE" | awk -F',' '{print "  - " $2 ": " $4}'
    echo "详细日志已保存至: $LOG_FILE"
    echo "=========================================="
}

# ================= 主执行流程 =================
if [ "$(id -u)" -ne 0 ]; then
    echo "错误: 请使用 sudo 运行此脚本"
    exit 1
fi

# 检查必要文件
if [ ! -d "$BOOT_FILES_DIR" ] && [ ! -f "$ROOTFS_ARCHIVE" ]; then
    echo "错误: 请确保 boot_files 目录或 rootfs.tar.gz 存在"
    exit 
fi

scan_devices
run_parallel_burn
final_report

posted @ 2026-08-11 14:52  北洋水师  阅读(16)  评论(0)    收藏  举报
window.addEventListener('load', function() { if (window.hljs && window.hljs.lineNumbersBlock) { document.querySelectorAll('pre code').forEach((block) => { hljs.lineNumbersBlock(block); }); } });