- 固件
- 操作系统
- 驱动开发
- 嵌入式
【免费下载链接】edk2
EDK II
本篇技术指南聚焦 edk2 仓库中RedfishPkg/Library/JsonLib这一关键基础设施:它如何将业界成熟的开源 JSON 库 Jansson 移植进 UEFI/EDK II 环境,并以JsonLib.h封装出一套面向 Redfish 应用的 EDKII JSON API。读完本文,你将掌握 Jansson 的核心设计理念、JsonLib 在 EDK II 中的构建集成方式(含配置头文件与编译选项)、其 API 映射与编码/解码标志的对应关系,以及仓库内针对上游load.c构建问题的本地修复细节。
一、Jansson:被选中的 JSON 处理引擎
RedfishPkg/Library/JsonLib/Readme.rst开篇即明确了 JsonLib 的第三方依赖:Jansson——一个用于 JSON 数据的编码、解码与操作的 C 语言库。其核心特性与设计原则包括:
- 简单直观的 API 与数据模型:以
json_t为统一句柄,对象、数组、字符串、整数、实数、布尔与 null 均通过同一套引用计数机制管理; - 全面的文档:Jansson 2.13 提供完整 API 参考(对应版本文档位于官方 readthedocs 站点的 2.13 分支);
- 零外部依赖:可独立编译,便于嵌入到 UEFI 固件这类受控环境中;
- 完整 Unicode(UTF-8)支持:词法层直接处理多字节 UTF-8 序列,并提供
\uXXXX转义与代理对(surrogate pair)解码; - 广泛的测试套件:上游持续以自动化测试保障 API 稳定性。
在许可与适用性方面,Readme.rst 明确指出 Jansson 采用MIT 许可证(详见仓库根目录的 License.txt),API 稳定且已用于生产环境,可运行于众多类 Unix 系统与 Windows,既适用于桌面、服务器,也适用于小型嵌入式系统——这与固件场景的需求高度吻合。
事实依据:以上结论均出自 Readme.rst,属项目文档明确声明的信息。
二、JsonLib 在 EDK II / Redfish 项目中的定位
Readme.rst 特别强调:
在 UEFI/EDK II 环境中,Redfish 项目消费(consume)Jansson 来实现 JSON 操作。
这意味着 JsonLib 不是孤立模块,而是整个 Redfish 栈的数据平面基础。Redfish 协议本身以 JSON 作为资源表述格式(包括 REST 请求/响应体、属性清单、事件负载等),因此 JsonLib 提供的能力直接决定了 Redfish 驱动与库对配置数据的解析与序列化效率。
版本信息同样来自 Readme.rst:
- edk2 上的 Jansson 版本为 2.13.1,API 行为以该版本的官方参考手册为准;
- EDK II 的 jansson 封装层:
JsonLib.h中定义了一组映射到 Jansson 函数的 EDKII JSON API,作为 EDK II 与上游库之间的稳定适配边界。
从仓库结构看,JsonLib 的封装遵循 EDK II 标准库封装惯例:公开 API 头文件放在包级 Include 目录(RedfishPkg/Include/Library/JsonLib.h),并在 RedfishPkg.dec 中注册为JsonLib|Include/Library/JsonLib.h的库类映射;同时该 .dec 文件还将Library/JsonLib(私有头文件)与Library/JsonLib/jansson/src(供引用jansson.h)登记为 include 路径(RedfishPkg.dec)。
三、库封装与构建集成
JsonLib 作为 EDK II 库模块,其构建描述位于 JsonLib.inf。关键元数据如下:
| 字段 | 值 | 说明 |
|---|---|---|
BASE_NAME | JsonLib | 库名 |
MODULE_TYPE | DXE_DRIVER | 模块类型 |
LIBRARY_CLASS | JsonLib|DXE_DRIVER UEFI_APPLICATION UEFI_DRIVER | 允许被三类模块链接 |
CONSTRUCTOR | JsonLibConstructor | 构造函数,负责库初始化(见下文) |
源码组成(JsonLib.inf)分三组:
- 第三方 jansson 源码:
jansson/src/下的dump.c、error.c、hashtable.c、hashtable_seed.c、memory.c、pack_unpack.c、strbuffer.c、strconv.c、utf.c、value.c、version.c——覆盖了序列化(dump)、哈希表(hashtable)、内存分配钩子(memory)、打包/解包(pack_unpack)、UTF-8 处理(utf)与核心值模型(value)等全部模块。jansson 目录本身以 git 子模块形式挂载(submodule 指向上游提交e9ebfa7e)。 - edk2 封装层:
JsonLib.c、jansson_config.h、jansson_private_config.h。 - 本地修复覆盖文件:
load.c(解决上游构建问题,详见第六节)。
依赖的库类(JsonLib.inf):BaseLib、BaseMemoryLib、Ucs2Utf8Lib、RedfishCrtLib、DebugLib、MemoryAllocationLib、PrintLib、UefiRuntimeServicesTableLib、UefiLib。其中RedfishCrtLib为 Redfish 包内的 C 运行时替代库(RedfishPkg.dec 注释说明 CRT 库供 edk2 JsonLib 使用),用于在固件环境补齐fopen、strtol、snprintf等标准 C 函数;JsonLib 与 RedfishCrtLib 均被纳入 RedfishPkg.dsc 参与包级构建。
3.1 编译选项与警告抑制
由于 Jansson 是面向宿主系统的 C 代码,直接编入 EDK II 需要专门处理工具链差异。JsonLib.inf 的[BuildOptions]给出了 MSFT 与 GCC 两套策略(JsonLib.inf):
- MSFT(VS):
/wd4204 /wd4244 /wd4090 /wd4334 /wd4706抑制 const 限定符不一致、类型转换截断、32 位移位隐式转 64 位、非标准非 const 聚合初始化、条件表达式内赋值等警告,避免在/WX下构建失败;同时-DHAVE_CONFIG_H=1让 jansson 源码包含jansson_private_config.h,并通过/U_WIN32 /UWIN64 /U_MSC_VER屏蔽 Windows 预定义宏,防止 jansson 走 MSVC 专用分支。 - GCC:
-Wno-unused-function -Wno-unused-but-set-variable容忍未使用函数与变量警告;同样定义HAVE_CONFIG_H=1并取消WIN32/_WIN32/WIN64/_MSC_VER;X64 额外追加-DNO_MSABI_VA_FUNCS(关闭 MS ABI 可变参数函数约定,适配 GCC 的 SysV ABI 实现)。
3.2 配置头文件:UEFI 环境的裁剪开关
Jansson 原生通过 autotools/CMake 生成jansson_config.h,而 JsonLib 用两份手工维护的头文件完成等价配置:
jansson_config.h(文件本体)声明了 UEFI 下的关键能力取舍:
| 宏 | 值 | 含义 |
|---|---|---|
JSON_INLINE | (定义) | 使用内联而非static inline,适配 EDK II 编译环境 |
JSON_INTEGER_IS_LONG_LONG | 1 | 整数类型采用 64 位 long long,对应封装层EDKII_JSON_INT_T为INT64 |
JSON_HAVE_LOCALECONV | 0 | 不支持 locale 相关转换(避免依赖宿主 locale) |
JSON_HAVE_ATOMIC_BUILTINS | 0 | 禁用 GCC 原子内建函数 |
JSON_HAVE_SYNC_BUILTINS | 0 | 禁用 GCC 同步内建函数(UEFI 单任务环境无需原子同步) |
JSON_PARSER_MAX_DEPTH | 2048 | 解析器最大嵌套深度限制 |
jansson_private_config.h(文件本体)补充:
| 宏 | 值 | 含义 |
|---|---|---|
HAVE_SYS_TIME_H | 1 | 声明存在<sys/time.h> |
HAVE_SYS_TYPES_H | 1 | 声明存在<sys/types.h> |
INITIAL_HASHTABLE_ORDER | 3 | 对象哈希表初始容量阶(2^3 = 8 个桶),控制小对象的内存开销 |
HAVE_UNISTD_H未在此定义,这一细节正是第六节所述构建问题的修复开关所在。
四、EDKII JSON API 封装(JsonLib.h)
公开接口定义在 RedfishPkg/Include/Library/JsonLib.h,共 945 行,是 EDK II 侧唯一需要包含的头文件。其核心设计是将 Jansson 的json_t*句柄抽象为不透明指针:
typedef VOID *EDKII_JSON_VALUE; typedef VOID *EDKII_JSON_ARRAY; typedef VOID *EDKII_JSON_OBJECT;同时以typedef INT64 EDKII_JSON_INT_T;映射 Jansson 的json_int_t(头文件注释明确对应JSON_INTEGER_IS_LONG_LONG置 1 的配置,JsonLib.h)。
4.1 类型与错误模型
EDKII_JSON_TYPE枚举(JsonLib.h):Object、Array、String、Integer、Real、True、False、Null八种类型,与json_type一一对应;EDKII_JSON_ERROR结构(JsonLib.h):映射json_error_t,字段包括Line、Column、Position、Source(80 字节,错误来源标识,如<string>/<buffer>)与Text(160 字节,错误消息文本),可用于定位解析失败的具体行列位置。
4.2 解码标志(Decoding Flags)
对应 Jansson 2.13 的json_loads/loadb系列标志(JsonLib.h):
| EDKII 宏 | 值 | 对应行为 |
|---|---|---|
EDKII_JSON_REJECT_DUPLICATES | 0x1 | 拒绝重复对象键(默认允许后者覆盖前者) |
EDKII_JSON_DISABLE_EOF_CHECK | 0x2 | 禁用 EOF 检查,允许解析多个 JSON 值 |
EDKII_JSON_DECODE_ANY | 0x4 | 允许根节点为任意 JSON 类型(否则必须是对象或数组) |
EDKII_JSON_DECODE_INT_AS_REAL | 0x8 | 将整数按实数解码 |
EDKII_JSON_ALLOW_NUL | 0x10 | 允许字符串中出现\u0000 |
4.3 编码标志(Encoding Flags)
对应json_dumps/dumpf系列(JsonLib.h):
| EDKII 宏 | 值 | 对应行为 |
|---|---|---|
EDKII_JSON_MAX_INDENT/EDKII_JSON_INDENT(n) | 0x1F /(n)&0x1F | 缩进控制(0 为压缩,1~31 为空格缩进量) |
EDKII_JSON_COMPACT | 0x20 | 紧凑输出(去掉多余空白) |
EDKII_JSON_ENSURE_ASCII | 0x40 | 非 ASCII 字符转义为\uXXXX输出 |
EDKII_JSON_SORT_KEYS | 0x80 | 按键排序输出 |
EDKII_JSON_PRESERVE_ORDER | 0x100 | 保持插入顺序(需要哈希表支持有序遍历) |
EDKII_JSON_ENCODE_ANY | 0x200 | 允许编码任意根类型 |
EDKII_JSON_ESCAPE_SLASH | 0x400 | 转义/为\/ |
EDKII_JSON_REAL_PRECISION(n) | ((n)&0x1F)<<11 | 实数打印精度控制 |
EDKII_JSON_EMBED | 0x10000 | 嵌入模式(JsonLib 扩展) |
4.4 便利宏
头文件还提供两个遍历宏(JsonLib.h):
EDKII_JSON_ARRAY_FOREACH(Array, Index, Value):按索引遍历数组元素;EDKII_JSON_OBJECT_FOREACH_SAFE(Object, N, Key, Value):通过迭代器安全遍历对象键值对,N保存迭代游标以支持遍历中安全操作。
4.5 函数族概览
JsonLib.c(文件本体,32 KB)实现全部封装函数,覆盖:值创建与释放(JsonValueInitArray/Object/String/Integer/Real/True/False/Null、JsonValueFree)、类型判断与取值(JsonValueGetType、JsonValueGetString/Integer/Real/Boolean)、对象操作(JsonObjectSetValue/GetValue/Remove/ContainsKey、迭代器系列)、数组操作(JsonArrayAppend/Insert/Set/Get/Remove/Count)、序列化与反序列化(JsonDumpString、JsonLoadString等),以及深拷贝/比较工具。所有创建型 API 遵循引用计数策略:新值引用计数为 1,由调用方通过JsonValueFree()释放,与 Jansson 的json_incref/decref语义保持一致(参见 JsonLib.h 中JsonValueInitArray的接口注释)。
五、构造函数与内存管理
JsonLib 定义了JsonLibConstructor构造函数(JsonLib.inf),在模块加载时完成库级初始化。结合 Jansson 的memory.c设计可知,Jansson 支持通过json_set_alloc_funcs()注入自定义内存分配器——这正是 UEFI 环境的关键适配点:JsonLib 在构造函数中为 jansson 挂接 EDK II 的AllocatePool/FreePool体系,确保第三方库的内存分配纳入固件的内存管理域,避免与 UEFI 内存模型冲突(这一结论由 JsonLib 依赖MemoryAllocationLib以及 jansson 可配置分配器机制共同推断)。
六、已知问题与本地修复:load.c 的 stdin 条件编译
Readme.rst 记录了 JsonLib 在集成过程中遇到并解决的唯一已知问题:
构建失败出现在
jansson/src/load.c。修复方式是在load.c中增加代码,根据HAVE_UNISTD_H宏条件性地使用 stdin。该修复 PR 已提交至 Jansson 开源社区(akheron/jansson#558)。
在仓库中,这一修复以独立覆盖文件RedfishPkg/Library/JsonLib/load.c 的形式存在(1424 行),并在 JsonLib.inf 中显式标注为"修复构建问题的源码覆盖",编译时替换上游同名文件参与构建。
从源码看,修复的实际落点非常清晰:
- 头文件条件包含:load.c 用
#ifdef HAVE_UNISTD_H包裹#include <unistd.h>,避免在无 POSIX 环境的固件工具链中因缺失头文件而编译失败; json_loadf的 stdin 判定:load.c 中,只有定义了HAVE_UNISTD_H时才将input == stdin映射为错误来源<stdin>,否则统一归为<stream>——UEFI 环境没有标准stdin概念,此判定可防止对全局stdin符号的隐式依赖;json_loadfd的 STDIN_FILENO 判定:load.c 同样以HAVE_UNISTD_H保护对STDIN_FILENO宏的引用,并在 fd_get_func 中把read()调用整体置于该宏保护之下。
由于 edk2 的 jansson_private_config.h 刻意不定义HAVE_UNISTD_H(只定义HAVE_SYS_TIME_H/HAVE_SYS_TYPES_H),上述 stdin/read 相关代码路径在固件构建中会被整体剔除,从而实现"既不破坏编译、又保留宿主平台完整功能"的双重目标。
七、解析器实现纵深:从 JSON 文本到 json_t
深入本地 load.c 可以还原 Jansson 解析器的完整工作流,这对理解 JsonLib 行为与错误报告至关重要:
- 词法分析(lexer):
stream_t结构(load.c)按字节流驱动,stream_get(load.c)负责多字节 UTF-8 序列的切分与校验(utf8_check_first/utf8_check_full),任何非法字节都会以json_error_invalid_utf8终止解析;lex_scan_string(load.c)处理转义序列、\uXXXX及 UTF-16 代理对合成; - 语法分析(parser):
parse_json(load.c)要求根节点为[或{(除非置位JSON_DECODE_ANY),随后递归调用parse_object/parse_array/parse_value构建对象树;parse_value(load.c)以lex->depth与JSON_PARSER_MAX_DEPTH(edk2 配置为 2048)配合检测栈溢出,超过即报json_error_stack_overflow; - 错误上下文:错误对象记录行/列/位置,并附带出错位置附近的文本片段(
error_set,load.c),非标准错误码(如文件提前结束)会细化映射为json_error_premature_end_of_input; - 多种输入源:本地 load.c 实现了六种入口——
json_loads(以\0结尾的 C 字符串,load.c)、json_loadb(定长缓冲区,load.c)、json_loadf(FILE*)、json_loadfd(文件描述符)、json_load_file(路径打开文件)与json_load_callback(回调式增量读取,内置 1024 字节缓冲,load.c)。其中json_loadb与json_loads最贴合固件场景——Redfish 消息通常以内存缓冲区形式到达,无需文件系统支持。
这也解释了为何 JsonLib 能在无文件系统的 UEFI 阶段正常工作:JsonLib 封装层主要面向字符串/缓冲区输入,文件与 fd 入口保留给宿主侧工具链或具备文件系统的运行阶段使用。
八、如何在 EDK II 项目中启用 JsonLib
若要在自定义平台中使用 JsonLib,通常需要以下三步(依据仓库现有配置整理):
- 包依赖:确保平台 .dsc 的
[Packages]包含MdePkg/MdePkg.dec、MdeModulePkg/MdeModulePkg.dec与RedfishPkg/RedfishPkg.dec(参见 JsonLib.inf); - 库引用:在模块 .inf 的
[LibraryClasses]声明JsonLib,或在平台 .dsc 的[LibraryClasses.common]中指定JsonLib|RedfishPkg/Library/JsonLib/JsonLib.inf(JsonLib 支持被 DXE_DRIVER、UEFI_APPLICATION、UEFI_DRIVER 三类模块链接); - 包含头文件:源码中
#include <Library/JsonLib.h>即可使用EDKII_JSON_VALUE等全部 API,无需直接引用 jansson 头文件。
包级构建时,RedfishPkg.dsc 已默认编译 JsonLib 与 RedfishCrtLib,Redfish 系驱动(如 RedfishDiscoverDxe、RedfishRestExDxe)均在此基础上获得统一的 JSON 处理能力。
九、小结
JsonLib 是 edk2 中 Redfish 功能栈与第三方 Jansson 之间的桥梁:上游提供成熟稳定的 JSON 引擎(MIT 许可、零依赖、UTF-8 完备、测试充分),JsonLib 通过 JsonLib.inf 的精细化构建配置、jansson_config.h 与 jansson_private_config.h 的能力裁剪、JsonLib.h 的类型/标志映射,以及 load.c 的本地修复,将这一通用库无缝嵌入 UEFI 固件环境。理解这一层的设计,无论是排查 Redfish 数据解析问题,还是为其他固件场景引入 JSON 能力,都能直接复用本文所述的移植方法论。
- 固件
- 操作系统
- 驱动开发
- 嵌入式
【免费下载链接】edk2
EDK II
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考