启动配置¶
- 作者:
Masami Hiramatsu <mhiramat@kernel.org>
概述¶
启动配置扩展了当前的内核命令行,以支持在启动内核时以高效方式处理额外的键值对数据。这允许管理员传递一个结构化键的配置文件。
配置文件语法¶
启动配置语法是简单的结构化键值对。每个键由点连接的单词组成,键和值通过 = 连接。值字符串必须由下面描述的定界符终止。
每个键名单词必须仅包含字母、数字、连字符 (-) 或下划线 (_)。每个值仅包含可打印字符或空格,但不包括定界符,如分号 (;)、换行符 (\n)、逗号 (,)、井号 (#) 和闭括号 (})。
如果 = 后面只有空格直到出现上述定界符之一,则该键被赋予空值。
对于数组,数组值由逗号 (\n) 的折行以提高可读性。因此,数组的第一个条目必须与键在同一行。
KEY[.WORD[...]] = VALUE[, VALUE2[...]][;]
与内核命令行语法不同,逗号和 = 周围的空格(包括制表符)会被忽略。
如果你想在值中使用这些定界符,可以使用双引号 ("VALUE") 或单引号 ('VALUE') 来引用。注意,你不能转义这些引号。
可以存在没有值或具有空值的键。这些键用于检查该键是否存在(类似于布尔值)。
键值语法¶
启动配置语法允许用户使用括号合并部分相同的单词键。例如
foo.bar.baz = value1
foo.bar.qux.quux = value2
这些也可以写成
foo.bar {
baz = value1
qux.quux = value2
}
或者更简短,写成如下形式
foo.bar { baz = value1; qux.quux = value2 }
在两种风格中,相同的键名单词在引导时解析时会自动合并。因此你可以附加类似的树或键值对。
同名键的值¶
禁止两个或多个值或数组共享同一个键。例如,
foo = bar, baz
foo = qux # !ERROR! we can not re-define same key
如果你想更新该值,必须显式使用覆盖运算符 :=。例如
foo = bar, baz
foo := qux
此时,qux 被赋值给 foo 键。这对于在不解析默认引导配置的情况下,通过添加(部分)自定义 bootconfigs 来覆盖默认值非常有用。
如果你想将该值作为数组成员追加到现有键,可以使用 += 运算符。例如
foo = bar, baz
foo += qux
在这种情况下,键 foo 拥有 bar、baz 和 qux。
此外,子键和值可以在父键下共存。例如,允许以下配置。
foo = value1
foo.bar = value2
foo := value3 # This will update foo's value.
注意,由于没有语法可以将原始值直接放在结构化键下,因此你必须在括号外定义它。例如
foo {
bar = value1
bar {
baz = value2
qux = value3
}
}
此外,键下值节点的顺序是固定的。如果同时存在值和子键,值始终是键的第一个子节点。因此,如果用户先指定子键,例如
foo.bar = value1
foo = value2
在程序中(以及 /proc/bootconfig 中),它将如下所示
foo = value2
foo.bar = value1
/proc/bootconfig¶
/proc/bootconfig 是启动配置的用户空间接口。与 /proc/cmdline 不同,此文件显示键值风格的列表。每个键值对以如下风格显示在每一行中
KEY[.WORDS...] = "[VALUE]"[,"VALUE2"...]
使用启动配置引导内核¶
使用 bootconfig 引导内核有两种选择:将 bootconfig 附加到 initrd 镜像,或者将其嵌入到内核本身中。
将启动配置附加到 Initrd¶
由于启动配置文件默认随 initrd 加载,它将被添加到 initrd (initramfs) 镜像文件的末尾,包含填充、大小、校验和以及 12 字节的魔法字,如下所示。
[initrd][bootconfig][padding][size(le32)][checksum(le32)][#BOOTCONFIG\n]
大小 (size) 和校验和 (checksum) 字段是无符号 32 位小端序值。
当启动配置被添加到 initrd 镜像时,总文件大小会按 4 字节对齐。为了填补空隙,将添加空字符 (\0)。因此,size 是启动配置文件长度 + 填充字节。
Linux 内核在内存中解码 initrd 镜像的最后一部分,以获取启动配置数据。由于这种“背负” (piggyback) 方式,只要引导加载程序传递正确的 initrd 文件大小,就不需要更改或更新引导加载程序和内核镜像本身。如果万一引导加载程序传递了更长的大小,内核将无法找到 bootconfig 数据。
为了执行此操作,Linux 内核在 tools/bootconfig 下提供了 bootconfig 命令,允许管理员向 initrd 镜像应用或从中删除配置文件。你可以使用以下命令构建它
# make -C tools/bootconfig
要将你的启动配置文件添加到 initrd 镜像,请如下运行 bootconfig(如果已存在旧数据,将自动删除)
# tools/bootconfig/bootconfig -a your-config /boot/initrd.img-X.Y.Z
要从镜像中删除配置,你可以如下使用 -d 选项
# tools/bootconfig/bootconfig -d /boot/initrd.img-X.Y.Z
然后在正常的内核命令行中添加 “bootconfig”,告诉内核在 initrd 文件末尾寻找 bootconfig。或者,在构建内核时选择 CONFIG_BOOT_CONFIG_FORCE Kconfig 选项。
将启动配置嵌入内核¶
如果你无法使用 initrd,也可以通过 Kconfig 选项将 bootconfig 文件嵌入内核。在这种情况下,你需要使用以下配置重新编译内核
CONFIG_BOOT_CONFIG_EMBED=y
CONFIG_BOOT_CONFIG_EMBED_FILE="/PATH/TO/BOOTCONFIG/FILE"
CONFIG_BOOT_CONFIG_EMBED_FILE 需要 bootconfig 文件相对于源码树或对象树的绝对路径或相对路径。内核会将其作为默认启动配置嵌入。
就像将 bootconfig 附加到 initrd 一样,你需要在内核命令行中使用 bootconfig 选项来启用嵌入式 bootconfig,或者通过启用 CONFIG_BOOT_CONFIG_FORCE Kconfig 选项来构建内核。
注意,即使设置了此选项,你仍然可以通过附加到 initrd 的另一个 bootconfig 来覆盖嵌入的 bootconfig。
通过启动配置传递内核参数¶
除了内核命令行外,启动配置还可以用于传递内核参数。所有在 kernel 键下的键值对都将直接传递给内核命令行。此外,init 下的键值对将通过命令行传递给 init 进程。这些参数按照以下顺序与用户给出的内核命令行字符串拼接,以便命令行参数可以覆盖 bootconfig 参数(这取决于子系统如何处理参数,但通常后出现的参数会覆盖先出现的参数。)
[bootconfig params][cmdline params] -- [bootconfig init params][cmdline init params]
这是一个用于内核/init 参数的 bootconfig 文件示例。
kernel {
root = 01234567-89ab-cdef-0123-456789abcd
}
init {
splash
}
这将被复制到内核命令行字符串中,如下所示
root="01234567-89ab-cdef-0123-456789abcd" -- splash
如果用户给出了其他一些命令行,例如,
ro bootconfig -- quiet
最终的内核命令行将如下所示
root="01234567-89ab-cdef-0123-456789abcd" ro bootconfig -- splash quiet
配置文件限制¶
目前最大配置大小为 32KB,总键名单词(非键值条目)必须在 1024 个节点以内。注意:这不是条目数而是节点数,一个条目必须消耗超过 2 个节点(一个键名单词和一个值)。所以理论上,它最多可包含 512 个键值对。如果键平均包含 3 个单词,则可以包含 256 个键值对。在大多数情况下,配置项的数量会在 100 个条目以下且小于 8KB,因此应该足够。如果节点数超过 1024,即使文件大小小于 32KB,解析器也会返回错误。(请注意,最大大小不包括填充的空字符。)无论如何,由于 bootconfig 命令在将启动配置附加到 initrd 镜像时会进行验证,用户可以在启动前察觉到。
Bootconfig API¶
用户可以查询或循环遍历键值对,也可以找到根(前缀)键节点并查找该节点下的键值对。
如果你有一个键字符串,可以使用 xbc_find_value() 直接查询其值。如果你想知道启动配置中存在哪些键,可以使用 xbc_for_each_key_value() 迭代键值对。注意,你需要使用 xbc_array_for_each_value() 来访问每个数组的值,例如:
vnode = NULL;
xbc_find_value("key.word", &vnode);
if (vnode && xbc_node_is_array(vnode))
xbc_array_for_each_value(vnode, value) {
printk("%s ", value);
}
如果你想专注于具有某个前缀字符串的键,可以使用 xbc_find_node() 通过前缀字符串查找节点,并使用 xbc_node_for_each_key_value() 迭代该前缀节点下的键。
但最典型的用法是在前缀下获取指定名称的值或数组,如下所示
root = xbc_find_node("key.prefix");
value = xbc_node_find_value(root, "option", &vnode);
...
xbc_node_for_each_array_value(root, "array-option", value, anode) {
...
}
这会访问 “key.prefix.option” 的值和 “key.prefix.array-option” 的数组。
不需要锁定,因为在初始化之后,配置变为只读。如果需要修改,必须复制所有数据和键。
函数和结构¶
-
uint32_t xbc_calc_checksum(const void *data, uint32_t size)¶
计算 bootconfig 的校验和
参数
const void *dataBootconfig 数据。
uint32_t sizebootconfig 数据的大小。
描述
计算 bootconfig 数据的校验和值。该校验和将与 BOOTCONFIG_MAGIC 和大小一起用于将 bootconfig 嵌入 initrd 镜像。
-
bool xbc_node_is_value(struct xbc_node *node)¶
测试节点是否为值节点
参数
struct xbc_node *node一个 XBC 节点。
描述
测试 node 是否为值节点,如果是则返回 true,否则返回 false。
-
bool xbc_node_is_key(struct xbc_node *node)¶
测试节点是否为键节点
参数
struct xbc_node *node一个 XBC 节点。
描述
测试 node 是否为键节点,如果是则返回 true,否则返回 false。
-
bool xbc_node_is_array(struct xbc_node *node)¶
测试节点是否为数组成员值节点
参数
struct xbc_node *node一个 XBC 节点。
描述
测试 node 是否为数组成员值节点。
-
bool xbc_node_is_leaf(struct xbc_node *node)¶
测试节点是否为叶子键节点
参数
struct xbc_node *node一个 XBC 节点。
描述
测试 node 是否为叶子键节点,即该节点是键节点且拥有值节点或没有子节点。如果是叶子节点则返回 true,否则返回 false。注意叶子节点除了值节点外还可以有子键节点。
-
const char *xbc_find_value(const char *key, struct xbc_node **vnode)¶
查找匹配键的值
参数
const char *key搜索键
struct xbc_node **vnode用于存放 XBC 值节点指针的容器。
描述
从整个 XBC 树中搜索其键匹配 key 的值,如果找到则返回该值。找到的值节点存储在 *vnode 中。注意,对于仅包含键(无值)的条目,这可能会返回 0 长度字符串并将 NULL 存储在 *vnode 中。
-
struct xbc_node *xbc_find_node(const char *key)¶
查找匹配键的节点
参数
const char *key搜索键
描述
从整个 XBC 树中搜索其键匹配 key 的(键)节点,如果找到则返回该节点。如果未找到,则返回 NULL。
-
struct xbc_node *xbc_node_get_subkey(struct xbc_node *node)¶
如果存在,返回第一个子键节点
参数
struct xbc_node *node父节点
描述
返回 node 的第一个子键节点。如果 node 没有子节点或只有值节点,则返回 NULL。
-
xbc_array_for_each_value¶
xbc_array_for_each_value (anode, value)
迭代数组上的值节点
参数
anode一个 XBC 数组成员值节点
value一个值
描述
迭代从 anode 开始的数组值节点和值。这预期与 xbc_find_value() 和 xbc_node_find_value() 结合使用,以便用户可以处理每个数组条目节点。
-
xbc_node_for_each_child¶
xbc_node_for_each_child (parent, child)
迭代子节点
参数
parent一个 XBC 节点。
child迭代的 XBC 节点。
描述
迭代 parent 的子节点。每个子节点都存储到 child 中。child 可能是值节点和子键节点的混合。
-
xbc_node_for_each_subkey¶
xbc_node_for_each_subkey (parent, child)
迭代子键节点
参数
parent一个 XBC 节点。
child迭代的 XBC 节点。
描述
迭代 parent 的子键节点。每个子节点都存储到 child 中。此时 child 仅为子键节点。
-
xbc_node_for_each_array_value¶
xbc_node_for_each_array_value (node, key, anode, value)
迭代给定键的数组条目
参数
node一个 XBC 节点。
key在 node 下搜索的键字符串
anode数组条目的已迭代 XBC 节点。
value数组条目的已迭代值。
描述
迭代 node 下给定 key 的数组条目。每个数组条目节点存储在 anode 和 value 中。如果 node 没有 key 节点,则不执行任何操作。注意,即使找到的键节点只有一个值(不是数组),这也会执行一次代码块。但是,如果找到的键节点没有值(仅键节点),这也不执行任何操作。因此,请勿将其用于测试键值对是否存在。
-
xbc_node_for_each_key_value¶
xbc_node_for_each_key_value (node, knode, value)
迭代节点下的键值对
参数
node一个 XBC 节点。
knode已迭代的键节点
value已迭代的值字符串
描述
迭代 node 下的键值对。每个键节点和值字符串分别存储在 knode 和 value 中。
-
xbc_for_each_key_value¶
xbc_for_each_key_value (knode, value)
迭代整个树的键值对
参数
knode已迭代的键节点
value已迭代的值字符串
描述
迭代整个 XBC 树中的键值对。每个键节点和值字符串分别存储在 knode 和 value 中。
-
int xbc_node_compose_key(struct xbc_node *node, char *buf, size_t size)¶
合成 XBC 节点的完整键字符串
参数
struct xbc_node *node一个 XBC 节点。
char *buf用于存储键的缓冲区。
size_t sizebuf 的大小。
描述
将 node 的完整长度键合成为 buf。返回存储在 buf 中的键的总长度。如果 node 为 NULL 则返回 -EINVAL,如果键深度超过最大深度则返回 -ERANGE。
-
int xbc_get_info(int *node_size, size_t *data_size)¶
获取已加载启动配置的信息
参数
int *node_size存储节点数量的指针。
size_t *data_size存储启动配置数据大小的指针。
描述
如果在 node_size 非空,则将已用节点数存入其中;如果在 data_size 非空,则将启动配置数据的大小存入其中。如果启动配置已初始化,则返回 0,否则返回 -ENODEV。
-
struct xbc_node *xbc_root_node(void)¶
获取扩展启动配置的根节点
参数
void无参数
描述
返回扩展启动配置根节点的地址。如果未初始化扩展启动配置,则返回 NULL。
-
uint16_t xbc_node_index(struct xbc_node *node)¶
获取 XBC 节点的索引
参数
struct xbc_node *node要获取索引的目标节点。
描述
返回 node 在 XBC 节点列表中的索引号。
-
struct xbc_node *xbc_node_get_parent(struct xbc_node *node)¶
获取父 XBC 节点
参数
struct xbc_node *node一个 XBC 节点。
描述
返回 node 的父节点。如果该节点是树的顶级节点,则返回 NULL。
-
struct xbc_node *xbc_node_get_child(struct xbc_node *node)¶
获取子 XBC 节点
参数
struct xbc_node *node一个 XBC 节点。
描述
返回 node 的第一个子节点。如果该节点没有子节点,则返回 NULL。
-
struct xbc_node *xbc_node_get_next(struct xbc_node *node)¶
获取下一个兄弟 XBC 节点
参数
struct xbc_node *node一个 XBC 节点。
描述
返回 node 的下一个兄弟节点。如果该节点没有下一个兄弟节点,则返回 NULL。注意,即使返回 NULL,也不意味着 node 没有兄弟节点。(你还需要检查父节点的子节点是否为 node。)
-
const char *xbc_node_get_data(struct xbc_node *node)¶
获取 XBC 节点的数据
参数
struct xbc_node *node一个 XBC 节点。
描述
返回 node 的数据(始终是以 null 结尾的字符串)。如果节点数据无效,则发出警告并返回 NULL。
-
struct xbc_node *xbc_node_find_subkey(struct xbc_node *parent, const char *key)¶
查找与给定键匹配的子键节点
参数
struct xbc_node *parent一个 XBC 节点。
const char *key键字符串。
描述
在 parent 下搜索匹配 key 的键节点。key 可以包含多个用 ‘.’ 连接的单词。如果 parent 为 NULL,则在整个树中搜索节点。如果没有匹配的节点,则返回 NULL。
-
const char *xbc_node_find_value(struct xbc_node *parent, const char *key, struct xbc_node **vnode)¶
查找与给定键匹配的值节点
参数
struct xbc_node *parent一个 XBC 节点。
const char *key键字符串。
struct xbc_node **vnode用于存放找到的 XBC 节点的容器指针。
描述
在 parent 下搜索其(父)键节点匹配 key 的值节点,将其存储在 *vnode 中,并返回值字符串。key 可以包含多个用 ‘.’ 连接的单词。如果 parent 为 NULL,则在整个树中搜索节点。如果找到匹配的键,则返回值字符串;如果没有匹配的节点,则返回 NULL。注意,如果该键没有值,这将返回 0 长度字符串并将 NULL 存储在 *vnode 中。此外,如果值为数组,它将返回第一个条目的值。
-
int xbc_node_compose_key_after(struct xbc_node *root, struct xbc_node *node, char *buf, size_t size)¶
合成 XBC 节点的部分键字符串
参数
struct xbc_node *root根 XBC 节点
struct xbc_node *node目标 XBC 节点。
char *buf用于存储键的缓冲区。
size_t sizebuf 的大小。
描述
将 node 的部分键合成为 buf,该键从 root 之后开始(不包括 root)。如果 root 为 NULL,这将返回 node 的完整键名。返回存储在 buf 中的键的总长度。如果 node 为 NULL、root 不是 node 的祖先或 root 就是 node,则返回 -EINVAL;如果键深度超过最大深度,则返回 -ERANGE。这预期与 xbc_find_node() 结合使用,以列出给定键下的所有(子)键。
-
struct xbc_node *xbc_node_find_next_leaf(struct xbc_node *root, struct xbc_node *node)¶
查找给定节点下的下一个叶子节点
参数
struct xbc_node *root一个 XBC 根节点
struct xbc_node *node开始查找的 XBC 节点。
描述
在 root 节点(包括 root 节点本身)下搜索 node 的下一个叶子节点(即终点键节点)。返回下一个节点,如果未找到下一个叶子节点,则返回 NULL。
-
const char *xbc_node_find_next_key_value(struct xbc_node *root, struct xbc_node **leaf)¶
查找下一个键值对节点
参数
struct xbc_node *root一个 XBC 根节点
struct xbc_node **leaf开始搜索的 XBC 节点的容器指针。
描述
在 root 节点下搜索 *leaf 的下一个叶子节点(即终点键节点)。如果找到下一个叶子节点,则返回值并更新 *leaf,如果未找到下一个叶子节点,则返回 NULL。注意,如果该键没有值,这将返回 0 长度字符串,如果值为数组,则返回第一个条目的值。
-
int xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)¶
将 root 下的 bootconfig 键渲染为命令行字符串
参数
char *buf目标缓冲区(当 size 为 0 用于查询长度时可能为 NULL)
size_t sizebuf 的字节大小
struct xbc_node *root其键=值对需要渲染的子树根
描述
遍历 root 下的所有键/值对,并将它们作为空格分隔的命令行字符串输出到 buf。包含空格的值用双引号括起来。返回如果 buf 足够大(匹配 snprintf 语义)时将写入的字节数,或失败时返回负 errno。
-
void _xbc_exit(bool early)¶
清除所有已解析的 bootconfig
-
int xbc_init(const char *data, size_t size, const char **emsg, int *epos)¶
解析给定的 XBC 文件并构建 XBC 内部树
参数
const char *data启动配置文本的原始数据
size_t sizedata 的大小
const char **emsg用于存储错误消息的 const char * 类型的指针
int *epos用于存储错误位置的 int 类型的指针
描述
这会解析 data 中的引导配置文本。size 必须小于 XBC_DATA_MAX。如果成功,则返回存储的节点数(>0),如果有任何错误,则返回 -errno。在出错的情况下,emsg 将更新为错误消息,epos 将更新为错误位置(即 buf 的字节偏移量)。如果错误不是解析器错误,epos 将为 -1。
注释¶
配置语法接受 shell 脚本风格的注释。以井号 (“#”) 开始直到换行符 (“\n”) 的注释将被忽略。
# comment line foo = value # value is set to foo. bar = 1, # 1st element 2, # 2nd element 3 # 3rd element其解析如下
注意,你不能在值和定界符(
;)之间放置注释或换行符。这意味着以下配置存在语法错误key = 1 # comment ,2