启动配置

作者:

Masami Hiramatsu <mhiramat@kernel.org>

概述

启动配置扩展了当前的内核命令行,以支持在启动内核时以有效的方式传递额外的键值数据。这允许管理员传递结构化的键配置 文件。

配置文件语法

启动配置语法是一种简单的结构化键值对。每个键由点连接的单词组成,键和值通过 = 连接。值必须以分号 (;) 或换行符 (\n) 结尾。对于数组值,数组条目用逗号 (,) 分隔。

KEY[.WORD[...]] = VALUE[, VALUE2[...]][;]

与内核命令行语法不同,逗号和 = 周围允许空格。

每个关键字必须仅包含字母、数字、破折号 (-) 或下划线 (_)。每个值只能包含可打印字符或空格,但分隔符除外,例如分号 (;)、换行符 (\n)、逗号 (,)、井号 (#) 和右括号 (})。

如果要在值中使用这些分隔符,可以使用双引号 ("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 键。这对于通过添加(部分)自定义启动配置来覆盖默认值而不解析默认启动配置很有用。

如果要将该值作为数组成员附加到现有键,可以使用 += 运算符。例如

foo = bar, baz
foo += qux

在这种情况下,键 foo 具有 barbazqux

此外,子键和一个值可以共存于父键下。例如,允许以下配置。

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

注释

配置语法接受 shell 脚本样式的注释。以井号 (“#”) 开头直到换行符 (“\n”) 的注释将被忽略。

# comment line
foo = value # value is set to foo.
bar = 1, # 1st element
      2, # 2nd element
      3  # 3rd element

这将解析为如下

foo = value
bar = 1, 2, 3

请注意,您不能在值和分隔符(,;)之间放置注释。这意味着以下配置存在语法错误

key = 1 # comment
      ,2

/proc/bootconfig

/proc/bootconfig 是启动配置的用户空间接口。与 /proc/cmdline 不同,此文件显示键值样式列表。每个键值对都以以下样式显示在每一行中

KEY[.WORDS...] = "[VALUE]"[,"VALUE2"...]

使用启动配置启动内核

有两种使用启动配置启动内核的选项:将启动配置附加到 initrd 镜像或将其嵌入到内核本身中。

将启动配置附加到 Initrd

由于默认情况下启动配置文件与 initrd 一起加载,因此它将添加到 initrd (initramfs) 镜像文件的末尾,并带有填充、大小、校验和和 12 字节的魔术字,如下所示。

[initrd][bootconfig][padding][size(le32)][checksum(le32)][#BOOTCONFIG\n]

大小和校验和字段是无符号 32 位小端值。

当启动配置添加到 initrd 镜像时,总文件大小将对齐到 4 字节。为了填充间隙,将添加空字符 (\0)。因此,size 是启动配置文件 + 填充字节的长度。

Linux 内核解码内存中 initrd 镜像的最后一部分以获取启动配置数据。由于这种“piggyback”方法,只要引导加载程序传递正确的 initrd 文件大小,就无需更改或更新引导加载程序和内核镜像本身。如果万一引导加载程序传递了更大的大小,内核将无法找到启动配置数据。

为了执行此操作,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 文件的末尾查找启动配置。或者,构建内核时选择 CONFIG_BOOT_CONFIG_FORCE Kconfig 选项。

将启动配置嵌入到内核中

如果不能使用 initrd,也可以通过 Kconfig 选项将启动配置文件嵌入到内核中。在这种情况下,您需要使用以下配置重新编译内核

CONFIG_BOOT_CONFIG_EMBED=y
CONFIG_BOOT_CONFIG_EMBED_FILE="/PATH/TO/BOOTCONFIG/FILE"

CONFIG_BOOT_CONFIG_EMBED_FILE 需要从源代码树或对象树到启动配置文件的绝对路径或相对路径。内核将它嵌入为默认启动配置。

就像将启动配置附加到 initrd 一样,您需要在内核命令行上使用 bootconfig 选项来启用嵌入的启动配置,或者,构建内核时选择 CONFIG_BOOT_CONFIG_FORCE Kconfig 选项。

请注意,即使您设置了此选项,您也可以通过附加到 initrd 的另一个启动配置来覆盖嵌入的启动配置。

通过启动配置传递内核参数

除了内核命令行之外,启动配置还可用于传递内核参数。 kernel 键下的所有键值对将直接传递到内核 cmdline。此外,init 下的键值对将通过 cmdline 传递给 init 进程。这些参数与用户给定的内核 cmdline 字符串连接在一起,顺序如下,以便命令行参数可以覆盖启动配置参数(这取决于子系统如何处理参数,但通常,较早的参数将被后面的参数覆盖。)

[bootconfig params][cmdline params] -- [bootconfig init params][cmdline init params]

以下是内核/init 参数的启动配置文件示例。

kernel {
  root = 01234567-89ab-cdef-0123-456789abcd
}
init {
 splash
}

这将复制到内核 cmdline 字符串中,如下所示

root="01234567-89ab-cdef-0123-456789abcd" -- splash

如果用户给出一些其他命令行,例如,

ro bootconfig -- quiet

最终的内核 cmdline 将如下所示

root="01234567-89ab-cdef-0123-456789abcd" ro bootconfig -- splash quiet

配置文件限制

目前,最大配置大小为 32KB,总关键字(不是键值条目)必须小于 1024 个节点。注意:这不是条目的数量而是节点,一个条目必须消耗超过 2 个节点(一个关键字和一个值)。因此理论上,它最多可以有 512 个键值对。如果键平均包含 3 个单词,则它可以包含 256 个键值对。在大多数情况下,配置项的数量将少于 100 个条目并且小于 8KB,因此这将足够了。如果节点数超过 1024,即使文件大小小于 32KB,解析器也会返回错误。(请注意,此最大大小不包括填充空字符。)无论如何,由于 bootconfig 命令在将启动配置附加到 initrd 镜像时会验证它,因此用户可以在启动前注意到它。

Bootconfig APIs

用户可以查询或循环遍历键值对,也可以查找根(前缀)键节点并查找该节点下的键值对。

如果您有一个键字符串,您可以使用 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(void *data, uint32_t size)

计算启动配置的校验和

参数

void *data

启动配置数据。

uint32_t size

启动配置数据的大小。

描述

计算启动配置数据的校验和值。该校验和将与 BOOTCONFIG_MAGIC 和大小一起用于将启动配置嵌入到 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 长度的字符串并在 *vnode 中存储 NULL。

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 的子节点。每个子节点都存储到 childchild 可以是值节点和子键节点的混合。

xbc_node_for_each_subkey

xbc_node_for_each_subkey (parent, child)

迭代子子键节点

参数

parent

一个 XBC 节点。

child

迭代的 XBC 节点。

描述

迭代 parent 的子键节点。每个子节点都存储到 childchild 仅是子键节点。

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 的数组条目。 每个数组条目节点都存储到 anodevalue。 如果 node 没有 key 节点,则不执行任何操作。 请注意,即使找到的键节点只有一个值(不是数组),也会执行一次块。 但是,如果找到的键节点没有值(仅键节点),则不执行任何操作。 因此,请勿使用它来测试键值对是否存在。

xbc_node_for_each_key_value

xbc_node_for_each_key_value (node, knode, value)

迭代节点下的键值对

参数

node

一个 XBC 节点。

knode

迭代的键节点

value

迭代的值字符串

描述

迭代 node 下的键值对。 每个键节点和值字符串分别存储在 knodevalue 中。

xbc_for_each_key_value

xbc_for_each_key_value (knode, value)

迭代键值对

参数

knode

迭代的键节点

value

迭代的值字符串

描述

迭代整个 XBC 树中的键值对。每个键节点和值字符串分别存储在 knodevalue 中。

int xbc_node_compose_key(struct xbc_node *node, char *buf, size_t size)

组合 XBC 节点的完整键字符串

参数

struct xbc_node *node

一个 XBC 节点。

char *buf

用于存储键的缓冲区。

size_t size

buf 的大小。

描述

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 不为 NULL,则将其中的已使用节点数量存储到 node_size 中;如果 data_size 不为 NULL,则将引导配置数据的大小存储到 data_size 中。如果引导配置已初始化,则返回 0;否则返回 -ENODEV。

struct xbc_node *xbc_root_node(void)

获取扩展引导配置的根节点

参数

void

无参数

描述

返回扩展引导配置的根节点地址。如果扩展引导配置未初始化,则返回 NULL。

int 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 的字符串,并在 *vnode 中存储 NULL。此外,如果该值是一个数组,则它将返回第一个条目的值。

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 size

buf 的大小。

描述

node 的部分键组合到 buf 中,该部分键从 root 之后开始(不包括 root)。如果 root 为 NULL,则此函数返回 node 的完整键字。返回存储在 buf 中的键的总长度。如果 node 为 NULL,或者 root 不是 node 的祖先,或者 rootnode,则返回 -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 节点下搜索 node 的下一个叶节点(即终端键节点)(包括 root 节点本身)。如果找到下一个节点,则返回该节点;如果未找到下一个叶节点,则返回 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 的字符串,或第一个条目的值。

void _xbc_exit(bool early)

清除所有已解析的引导配置

参数

bool early

如果是在 budy 系统初始化之前调用此函数,则设置为 true。

描述

此函数清除内存中已解析的引导配置的所有数据结构。如果需要使用新的引导配置重用 xbc_init(),则可以使用此函数。

int xbc_init(const char *data, size_t size, const char **emsg, int *epos)

解析给定的 XBC 文件并构建 XBC 内部树

参数

const char *data

引导配置文本原始数据

size_t size

data 的大小

const char **emsg

用于存储错误消息的 const char * 指针

int *epos

用于存储错误位置的 int 指针

描述

此函数解析 data 中的引导配置文本。size 必须小于 XBC_DATA_MAX。如果成功,则返回存储的节点数(>0);如果出现任何错误,则返回 -errno。在出错的情况下,emsg 将更新为错误消息,epos 将更新为错误位置,该错误位置是 buf 的字节偏移量。如果该错误不是解析器错误,则 epos 将为 -1。