啟動配置

作者:

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。