通用教程:脚本化定制内核配置 & 用 sed 改造构建脚本

通用教程:脚本化定制内核配置 & 用 sed 改造构建脚本

一、总体思路

vendor SDK 的内核通常由 build.sh 里的 gen_rt_kernel_image() 函数编译。 要"免交互"地定制内核,做两件事:

  1. 生成基线配置:make <board>_defconfig
  2. 直接改 .config,再 make olddefconfig 让依赖收敛

此外还要改造 build.sh 本身:修正已有命令、跳过交互式 menuconfig、 插入自动执行命令。这些都用 sed 完成。

二、sed 定位:地址

/pattern/                    只处理匹配该模式的行
/start/,/end/                处理从 start 到 end 的范围
/funcname()/,/^}/            函数体范围(函数名到第一个顶格 })
/范围/ { /pattern/ 命令 }    在范围内再按 pattern 过滤

本教程统一用函数体范围:

1
sed -i '/gen_rt_kernel_image()/,/^}/ { ... }' build.sh

含义:只动 gen_rt_kernel_image() 函数内部,不影响文件其它地方。

三、sed 常用动作

  1. 替换 s —— 改已有内容

1
sed -i 's/旧/新/' file
  • 分隔符可替换:s|旧|新|、s#旧#新#。替换内容含 /(如路径)时, 换成 | 就不必转义。
  • 捕获组:\(...\) 分组,\1、\2 引用;\0 表示整个匹配。
  • :space: 匹配空白,\+ 表示一个或多个(GNU)。

例:修正 build.sh 里 publickey 的拷贝路径

1
2
3
4
DTS_FILE="build.sh"
sed -i '/gen_rt_kernel_image()/,/^}/ {
    s|^\([[:space:]]*cp\)[[:space:]]\+publickey|\1 kbuild/publickey|
}' "${DTS_FILE}"

把 cp publickey 改成 cp kbuild/publickey。

  1. 行首插注释 —— 注释掉某行

1
2
3
4
DTS_FILE="build.sh"
sed -i '/gen_rt_kernel_image()/,/^}/ {
s/^\([[:space:]]*\)\(make.*menuconfig\)/\1# \2/
}' "${DTS_FILE}"

\1 是原缩进,\2 是整条 make 命令。结果:

make ... menuconfig   ->   # make ... menuconfig

注意:shell 的注释符是 #,不是 // 。用 // 会被当成命令去执行 (//指向根目录),可能报错、甚至在 set -e 下中断构建。 这个写法是幂等的:再跑一次时行首已是 # 而不是 make,不会匹配。

  1. 追加 a\ —— 在匹配行后加一行

1
2
3
sed -i '/范围/ {
    /menuconfig/a\    bash config-packages.sh
}' "${DTS_FILE}"

a\ 后面直接写要追加的内容,缩进要自己敲空格。 缺点:不同 sed 对 a\ 后空白的处理略有差异,缩进不够可控。

  1. 用 s + \n 插入并保留缩进(更稳)

1
2
3
sed -i '/gen_rt_kernel_image()/,/^}/ {
s/^\([[:space:]]*\).*menuconfig/\0\n\1bash config-packages.sh/
}' "${DTS_FILE}"
  • \0 = 整个匹配行
  • \1 = 行首缩进
  • \n = 换行(GNU sed 支持)

结果:在匹配行之后插入一行,缩进自动对齐。推荐用这种,而不是 a\。

  1. 插入文件内容 r —— 把整段脚本嵌进 build.sh

1
2
3
sed -i '/gen_rt_kernel_image()/,/^}/ {
    /ascend310B_defconfig/r '"$BLK"'
}' "${DTS_FILE}"
  • r 文件名:把该文件内容插入到匹配行之后。
  • 文件名要读到行尾,所以 "$BLK" 必须放行末,} 另起一行。
  • 用 '"$BLK"' 拼接(单引号脚本 + 双引号变量),既让 shell 展开路径, 又不用给整段 sed 脚本转义。

四、预览 vs 落盘

sed '...' file        只输出到 stdout,不改文件(预览用)
sed -i '...' file     原地修改
sed -i.bak '...' file 修改并生成 .bak 备份

预览定位某几行:

1
sed '...' build.sh | sed -n '271,273p'

注意:sed -e '…' 若不加 -i,跑完什么都不会变,这是最容易踩的坑。

五、defconfig 与 olddefconfig

1
2
3
4
5
6
7
# 1. 生成基线 .config(只需一次;重复跑会覆盖已有配置)
make ARCH=<arch> CROSS_COMPILE=<toolchain> <board>_defconfig

# 2. 直接编辑 .config(见第六节脚本)

# 3. 让依赖关系收敛
make ARCH=<arch> CROSS_COMPILE=<toolchain> olddefconfig

要点:

  • defconfig 只跑一次。之后改 .config 即可;重新 defconfig 会重置, 需要重跑配置脚本。
  • olddefconfig 会按依赖自动开启/丢弃配置项;依赖不满足的项会被静默 删掉(比如 USB_STORAGE=y 但 SCSI=n)。
  • Kconfig 中一个符号的规范形态只有三种:=y、=m、# … is not set。 没有 CONFIG_X=n 这种写法。

六、非交互式配置 .config 的脚本模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash
set -e

CONFIG_FILE=".config"

configs=(
    "CONFIG_USB_SUPPORT=y"
    # "CONFIG_USB=n"     # 想关掉就写 =n
)

for config in "${configs[@]}"; do
    key="${config%%=*}"
    val="${config#*=}"

    # n -> 写成 Kconfig 的规范注释形态;y -> 写成 KEY=y
    if [ "$val" = "n" ]; then
        want="# ${key} is not set"
    else
        want="${key}=${val}"
    fi

    if grep -q "^${key}=" "$CONFIG_FILE"; then
        # 当前是 =y(或其它值):直接改写
        sed -i "s|^${key}=.*|${want}|" "$CONFIG_FILE"
    elif grep -q "^# ${key} is not set$" "$CONFIG_FILE"; then
        # 当前被注释:按目标改写
        sed -i "s|^# ${key} is not set$|${want}|" "$CONFIG_FILE"
    else
        # 不存在:追加
        echo "${want}" >> "$CONFIG_FILE"
    fi
done

make ARCH=arm64 CROSS_COMPILE=aarch64-target-linux-gnu- olddefconfig

三分支覆盖 .config 里两种现状(KEY=… / # KEY is not set), 幂等、可重复执行。

七、把脚本注入 build.sh(mktemp + heredoc + r)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
BLK=$(mktemp)
cat > "$BLK" <<'EOFF'
cat > config-packages.sh <<'EOF'
#!/bin/bash
set -e
CONFIG_FILE=".config"
configs=(
    "CONFIG_USB_SUPPORT=y"
)
for config in "${configs[@]}"; do
    ...
done
make ARCH=arm64 CROSS_COMPILE=aarch64-target-linux-gnu- olddefconfig
EOF
EOFF

DTS_FILE="build.sh"
sed -i '/gen_rt_kernel_image()/,/^}/ {
    /ascend310B_defconfig/r '"$BLK"'
}' "${DTS_FILE}"
rm "$BLK"

要点:

  1. mktemp 造临时文件,避免污染目录;用完 rm。
  2. 两层 heredoc 都要加引号('EOFF'、'EOF'),否则里层的 ${config}、 ${key} 会在注入时就被提前展开,脚本就废了。
  3. r 插入的内容是顶格的;shell 函数体不依赖缩进,功能没问题, 只是不美观。
  4. 这段只让 build.sh 生成 config-packages.sh,还要另加一行去执行它 (见第三节第 4 点)。

八、幂等守卫

补丁脚本可能被重复执行,注入类操作要加守卫,避免重复插入:

1
2
3
4
5
DTS_FILE="build.sh"
if ! grep -q "config-packages.sh" "${DTS_FILE}"; then
    # 注入生成器 + 注入执行命令,都放进来
    ...
fi
  • 首次:grep 找不到 -> 进入 if -> 两条注入都做。
  • 再次:grep 命中(注入内容里就含 config-packages.sh)-> 整块跳过。

注意:守卫要包住"所有会重复的注入",只包一半仍会重复。

九、环境适配:ARCH / CROSS_COMPILE

defconfig 和 olddefconfig 都要按目标环境指定架构和工具链:

目标          ARCH       CROSS_COMPILE
ARM64         arm64      aarch64-linux-gnu- 或 aarch64-target-linux-gnu-
ARM32         arm        arm-linux-gnueabihf-
x86_64(本机)  x86_64     (留空)
RISC-V 64     riscv      riscv64-linux-gnu-
MIPS          mips       mips-linux-gnu-

变量化写法(换环境只改两行):

1
2
3
4
5
ARCH=arm64
CROSS_COMPILE=aarch64-target-linux-gnu-

make ARCH="${ARCH}" CROSS_COMPILE="${CROSS_COMPILE}" ascend310B_defconfig
make ARCH="${ARCH}" CROSS_COMPILE="${CROSS_COMPILE}" olddefconfig

注意:olddefconfig 本身不编译,但某些平台解析依赖时仍需要 ARCH; CROSS_COMPILE 一起带上最保险,也方便和 build.sh 保持一致。

十、完整流程串联

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 1) 修正 build.sh 里 publickey 拷贝路径
sed -i '/gen_rt_kernel_image()/,/^}/ {
    s|^\([[:space:]]*cp\)[[:space:]]\+publickey|\1 kbuild/publickey|
}' build.sh

# 2) 注释掉 build.sh 里的交互式 menuconfig
sed -i '/gen_rt_kernel_image()/,/^}/ {
s/^\([[:space:]]*\)\(make.*menuconfig\)/\1# \2/
}' build.sh

# 3) 生成 config-packages.sh 并注入 build.sh,同时在 build.sh 中
#    插入执行命令(整体加幂等守卫)
DTS_FILE="build.sh"
if ! grep -q "config-packages.sh" "${DTS_FILE}"; then
    BLK=$(mktemp)
    cat > "$BLK" <<'EOFF'
cat > config-packages.sh <<'EOF'
...见第六节...
EOF
EOFF
    sed -i '/gen_rt_kernel_image()/,/^}/ {
    /ascend310B_defconfig/r '"$BLK"'
}' "${DTS_FILE}"
    rm "$BLK"

    sed -i '/gen_rt_kernel_image()/,/^}/ {
s/^\([[:space:]]*\).*menuconfig/\0\n\1bash config-packages.sh/
}' "${DTS_FILE}"
fi

# 4) 正常构建
./build.sh

最终 build.sh 的 gen_rt_kernel_image() 内大致为:

make ... ascend310B_defconfig
cat > config-packages.sh <<'EOF'      <- r 注入
...
EOF
# make ... menuconfig                 <- 已注释
bash config-packages.sh                <- 插入的执行命令
make ...                               <- 真正编译

十一、常见坑

  1. sed -e 不加 -i 等于没改,只预览;真正生效要 -i。
  2. shell 注释符是 #,不是 //=;用 =// 会被当命令执行。
  3. r 的文件名必须在行末,后面不能跟 }。
  4. heredoc 不加引号会导致变量提前展开,务必用 'EOF'。
  5. 单引号里的 ${VAR} 不展开;要展开就用 '"$VAR"' 拼接或改双引号。
  6. olddefconfig 会丢弃依赖不满足的项,别只 grep 目标项, 把依赖项一起 grep。
  7. 重复 defconfig 会覆盖已加的功能,需要重跑配置脚本。
  8. =m 只对 tristate 符号有效,bool 写 =m 会被强制改回 y/n。
  9. 注入类操作要加幂等守卫,且守卫要覆盖所有会重复的注入。
  10. 变量做参数时加引号:rm "$BLK"、grep ... "${DTS_FILE}"。