news 2026/10/13 6:47:46

LXC 编码风格指南:Linux 容器项目 C 代码规范与工程实践深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LXC 编码风格指南:Linux 容器项目 C 代码规范与工程实践深度解析
  • 容器运行时
  • 云原生
  • 运维

【免费下载链接】lxc

LXC - Linux Containers

项目地址:https://gitcode.com/gh_mirrors/lx/lxc
点击查看免费下载

本指南系统梳理 LXC(Linux Containers)项目官方编码规范文档(仓库根目录 CODING_STYLE.md)的全部要点:从"以 Linux 内核编码风格为基础但存在明确差异"的总体原则,到缩进、注释、错误处理、资源清理、goto用法、字符串安全、commit 规范等 26 条具体规则。文章结合仓库源码(src/lxc各模块)与自动化脚本(coccinelle/目录)给出可验证的实现证据,帮助读者在向 LXC 提交补丁、阅读其源码或为同类系统级 C 项目制定规范时,快速把握每一处"硬性要求"与"灵活边界"。

LXC 项目整体遵循 Linux 内核编码风格,但围绕"共享库线程安全""跨平台可移植""错误处理一致性"等容器运行时特有的诉求,定义了一系列与内核风格不同的专属规则。本文以官方规范为骨架,逐条展开并辅以源码佐证。

1. 总体原则:在 Linux 内核风格之上的差异

CODING_STYLE.md 开篇即声明:LXC 遵循 Linux 内核编码风格(见内核树的Documentation/process/coding-style.rst),但有几点差异,正是本文要说明的内容。

官方规范的几条总体性说明值得先理解:

  • 规则针对新代码,但欢迎清理旧代码:历史遗留的不符合规范代码不构成"可以不遵守规范"的理由,维护者欢迎这类清理补丁。
  • 维护者保留裁量权:合并 Pull Request 时,维护者可以忽略本文的某些规则。若强制执行某条规则对某个具体 commit 弊大于利,可以灵活处理——这条看似"奇怪"的规则目的是降低新开发者入门门槛、避免无谓的 bikeshedding(在琐碎风格上反复争论)。此外,维护者可以直接取用不合规范的提交,按其风格改写并追加自己的Signed-off-by,这有助于首次贡献者快速通过合并队列。
  • 尚无自动风格检查:项目当前没有集成自动风格检查工具,但推荐使用clang-format做近似检查。

1.1 用 clang-format 快速自检

规范提供了一个.clang-format配置片段,可直接写入$HOME/.clang-format使用:

cat << EOF > "${HOME}"/.clang-format AlignEscapedNewlines: Left BreakBeforeBraces: Attach AlwaysBreakBeforeMultilineStrings: false BreakBeforeBinaryOperators: None MaxEmptyLinesToKeep: 1 PenaltyBreakBeforeFirstCallParameter: 1000000 BinPackArguments: true BinPackParameters: true AllowAllParametersOfDeclarationOnNextLine: false AlignAfterOpenBracket: true SpacesInSquareBrackets: false SpacesInCStyleCastParentheses: false SpaceInEmptyParentheses: false SpaceBeforeParens: ControlStatements SpaceAfterCStyleCast: false SortIncludes: true PenaltyReturnTypeOnItsOwnLine: 10000 PenaltyExcessCharacter: 10 Language: Cpp ForEachMacros: ['lxc_list_for_each', 'lxc_list_for_each_safe'] AllowShortLoopsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortCaseLabelsOnASingleLine: false AllowShortBlocksOnASingleLine: false BasedOnStyle: LLVM TabWidth: 8 IndentWidth: 8 UseTab: Always BreakBeforeBraces: Linux AllowShortIfStatementsOnASingleLine: false IndentCaseLabels: false EOF

配置要点解读:

  • UseTab: Always+IndentWidth: 8+TabWidth: 8对应规范第 2 条"只使用 Tab"(与内核风格一致,一个缩进层级 = 一个 Tab = 8 列);
  • ForEachMacros声明了lxc_list_for_each/lxc_list_for_each_safe两个 LXC 自定义遍历宏,让 clang-format 能正确格式化它们包围的循环体(这两个宏定义于 src/lxc/list.h);
  • 规范特别提醒:clang-format 并不能处理所有情况,例如大多数struct初始化(尤其是第 22 条要求的手工对齐)就无法自动完成,仍需人工按本文规范处理。

2. 只使用 Tab 缩进

LXC 明确规定:缩进只用 Tab,不用空格。这继承了内核风格(内核中一个缩进层级为 8 列),也与上节 clang-format 配置中的UseTab: Always呼应。在提交代码前可用git diff检查是否混入了空格缩进。

3. 注释只允许/* */风格

  • 任何新增注释必须使用/* */,禁止//风格(C99 单行注释)。
  • 单行注释:/*与正文同行,例如:
/* Define pivot_root() if missing from the C library */
  • 多行注释:/*单独一行开始,*/单独一行结束,中间每行以*开头,例如:
/* * At this point the old-root is mounted on top of our new-root * To unmounted it we must not be chdir()ed into it, so escape back * to old-root. */

这一风格与内核一致,源码中随处可见,例如 src/lxc/compiler.h 中大量块注释即为此格式。

4. 尽量在 80 列处换行

  • 该限制不是硬性规定;如果溢出能提升可读性,允许偶尔超限。
  • 原则是"尽量遵守、用常识判断何时破例"。
  • 注意:commit message 正文不受 80 列限制(见第 20 条),目的是便于grep检索。

5. 错误消息规范

  • 错误消息必须以大写字母开头,不得以标点符号结尾。
  • 消息应具有描述性但不过度冗长,最好参考现有错误消息的写法。
  • 规范给出的正例:
SYSERROR("Failed to create directory \"%s\"", path); WARN("\"/dev\" directory does not exist. Proceeding without autodev being set up");

源码佐证:SYSERROR、WARN、ERROR等日志宏定义于 src/lxc/log.h(WARN在 L367、ERROR在 L372、SYSERROR在 L449-L457)。SYSERROR会在 glibc/musl 环境下自动附加"%m"占位符,把当前errno对应的字符串描述拼进日志——这也是规范强调"错误消息要大写开头"能落到实处的关键:配合%m,消息既自带系统错误描述,又保持统一的书写风格。

6. 失败时设置errno

  • 对"非二元式失败"(即存在多种失败形态)的函数,应返回-1并设置一个有意义的errno值。
  • LXC 提供便捷宏minus_one_set_errno:
static int set_config_net_l2proxy(const char *key, const char *value, struct lxc_conf *lxc_conf, void *data) { struct lxc_netdev *netdev = data; unsigned int val = 0; int ret; if (lxc_config_value_empty(value)) return clr_config_net_l2proxy(key, lxc_conf, data); if (!netdev) return minus_one_set_errno(EINVAL); ret = lxc_safe_uint(value, &val); if (ret < 0) return minus_one_set_errno(-ret); switch (val) { case 0: netdev->l2proxy = false; return 0; case 1: netdev->l2proxy = true; return 0; } return minus_one_set_errno(EINVAL); }

该例同时示范了第 6、9、11、15 条规则:把错误值包装为-1+errno;变量在函数顶部声明并分类排序;对strcmp显式与0比较;网络二层代理开关用bool表达。注意set_config_net_l2proxy这类set_config_*函数正是 src/lxc/confile.c 配置解析体系中"网络配置回调"的代表实现。

7. 未导出函数必须声明为static

仅在本文件使用、不对外导出的函数,一律加static。这是控制符号可见度、避免污染全局符号表的基础要求,也便于编译器内联优化。

8. 导出函数必须在头文件中用extern声明

  • 在头文件(*.h)中声明函数时应使用extern关键字;
  • 在源文件(*.c)中声明函数时不应使用extern。

源码佐证:src/lxc/string_utils.h 中即采用__hidden extern bool lxc_string_in_array(const char *needle, const char **haystack);的写法,声明位于头文件、带extern,而其定义在 src/lxc/string_utils.c 中不带extern。

9. 变量声明规则

变量应在函数顶部或新作用域开头声明,严禁在作用域中间声明。声明顺序按以下优先级排列:

  1. 自动释放变量——特指通过gcc/clang的cleanup属性(见第 26 条)清理的变量;
  2. 已初始化变量;
  3. 未初始化变量。

通用细则:

  • 基础类型放在复杂类型之前;
  • libc 定义的标准类型放在 LXC 自定义类型之前;
  • 同类型的多个声明尽量合并到同一行。

规范给出的正例(该函数真实存在于 src/lxc/conf.c):

int lxc_clear_procs(struct lxc_conf *c, const char *key) { struct lxc_list *it, *next; bool all = false; const char *k = NULL; if (strcmp(key, "lxc.proc") == 0) all = true; else if (strncmp(key, "lxc.proc.", sizeof("lxc.proc.") - 1) == 0) k = key + sizeof("lxc.proc.") - 1; else return -1; lxc_list_for_each_safe(it, &c->procs, next) { struct lxc_proc *proc = it->elem; if (!all && strcmp(proc->filename, k) != 0) continue; lxc_list_del(it); free(proc->filename); free(proc->value); free(proc); free(it); } return 0; }

10. 非布尔函数:先赋值返回值,再检查

  • 检查非布尔返回值的函数是否成功时,必须先把返回值赋给变量再判断。
  • 例外:str{n}cmp()系列(单独由第 11 条规范处理)。
  • 布尔函数可以直接在if中检查。
/* assign value to "ret" first */ ret = mount(sourcepath, cgpath, "cgroup", remount_flags, NULL); /* check whether function was successful */ if (ret < 0) { SYSERROR("Failed to remount \"%s\" ro", cgpath); free(sourcepath); return -1; }
extern bool lxc_string_in_array(const char *needle, const char **haystack); /* check right away */ if (lxc_string_in_array("ns", (const char **)h->subsystems)) continue;

这一规则避免把"返回错误码"与"返回布尔"两类函数混用,降低误判概率。

11. 形似布尔、实为整数的函数必须显式与值比较

本条主要针对str{n}cmp()这类"概念上像布尔、实际返回整数"的函数。虽然直接用是允许的,但必须显式与某个值比较,禁止写if (!str{n}cmp())或if (str{n}cmp())。

规范给出的正例(LXC hook 配置解析):

static int set_config_hooks(const char *key, const char *value, struct lxc_conf *lxc_conf, void *data) { char *copy; if (lxc_config_value_empty(value)) return lxc_clear_hooks(lxc_conf, key); if (strcmp(key + 4, "hook") == 0) { ERROR("lxc.hook must not have a value"); return -1; } copy = strdup(value); if (!copy) return -1; if (strcmp(key + 9, "pre-start") == 0) return add_hook(lxc_conf, LXCHOOK_PRESTART, copy); else if (strcmp(key + 9, "start-host") == 0) return add_hook(lxc_conf, LXCHOOK_START_HOST, copy); else if (strcmp(key + 9, "pre-mount") == 0) return add_hook(lxc_conf, LXCHOOK_PREMOUNT, copy); else if (strcmp(key + 9, "autodev") == 0) return add_hook(lxc_conf, LXCHOOK_AUTODEV, copy); else if (strcmp(key + 9, "mount") == 0) return add_hook(lxc_conf, LXCHOOK_MOUNT, copy); else if (strcmp(key + 9, "start") == 0) return add_hook(lxc_conf, LXCHOOK_START, copy); else if (strcmp(key + 9, "stop") == 0) return add_hook(lxc_conf, LXCHOOK_STOP, copy); else if (strcmp(key + 9, "post-stop") == 0) return add_hook(lxc_conf, LXCHOOK_POSTSTOP, copy); else if (strcmp(key + 9, "clone") == 0) return add_hook(lxc_conf, LXCHOOK_CLONE, copy); else if (strcmp(key + 9, "destroy") == 0) return add_hook(lxc_conf, LXCHOOK_DESTROY, copy); free(copy); return -1; }

12. 禁止使用 C99 变长数组(VLA)

VLA 在 C 标准中已被标记为"可选特性",未来标准不保证继续支持,因此 LXC 全面禁止。涉及动态大小缓冲时,应使用malloc/realloc或配合 cleanup 宏(第 26 条)管理。

13. 退出时使用标准 libc 宏

libc提供EXIT_FAILURE与EXIT_SUCCESS,在fork()出的子进程、或从main()退出时应尽量使用它们。

源码佐证:src/lxc/attach.c 的 fork 子进程中大量使用_exit(EXIT_FAILURE)(如 L951、L969、L973 等)。同时仓库提供 coccinelle 脚本 coccinelle/exit.cocci 自动把exit(0)/_exit(0)改写为exit(EXIT_SUCCESS)/_exit(EXIT_SUCCESS)、把exit(1)/_exit(1)改写为exit(EXIT_FAILURE)/_exit(EXIT_FAILURE)。

14.goto的使用规范

goto是 C 语言的重要构造,非常适合做清理(cleanup)或简化函数逻辑,但必须遵守:

  • 使用描述性标签:若标签仅用于错误路径,用on_error而非笼统的out;
  • 只向下跳转,除非是为了处理EAGAIN错误而避免do-while构造(此时可向上跳转重试)。

规范给出的正例(ID 映射配置解析):

static int set_config_idmaps(const char *key, const char *value, struct lxc_conf *lxc_conf, void *data) { unsigned long hostid, nsid, range; char type; int ret; struct lxc_list *idmaplist = NULL; struct id_map *idmap = NULL; if (lxc_config_value_empty(value)) return lxc_clear_idmaps(lxc_conf); idmaplist = malloc(sizeof(*idmaplist)); if (!idmaplist) goto on_error; idmap = malloc(sizeof(*idmap)); if (!idmap) goto on_error; memset(idmap, 0, sizeof(*idmap)); ret = parse_idmaps(value, &type, &nsid, &hostid, &range); if (ret < 0) { ERROR("Failed to parse id mappings"); goto on_error; } INFO("Read uid map: type %c nsid %lu hostid %lu range %lu", type, nsid, hostid, range); if (type == 'u') idmap->idtype = ID_TYPE_UID; else if (type == 'g') idmap->idtype = ID_TYPE_GID; else goto on_error; idmap->hostid = hostid; idmap->nsid = nsid; idmap->range = range; idmaplist->elem = idmap; lxc_list_add_tail(&lxc_conf->id_map, idmaplist); if (!lxc_conf->root_nsuid_map && idmap->idtype == ID_TYPE_UID) if (idmap->nsid == 0) lxc_conf->root_nsuid_map = idmap; if (!lxc_conf->root_nsgid_map && idmap->idtype == ID_TYPE_GID) if (idmap->nsid == 0) lxc_conf->root_nsgid_map = idmap; idmap = NULL; return 0; on_error: free(idmaplist); free(idmap); return -1; }

注意idmaplist/idmap初值置为NULL,使on_error标签处的free()天然安全;idmap = NULL在成功路径上的写法,是为了避免成功后将已挂入链表的对象被on_error误释放。

15. 能用布尔就用布尔,不用整数

当某个概念可以二元化表达时,用bool而非int。例如netdev->l2proxy = false/true(见第 6 条示例)、bool all = false(见第 9 条示例)。

16. 清理函数必须处理空指针与"已清理对象"

  • 若你为复杂类型自定义清理函数,必须保证传入该类型的空值(null)时是 NOOP,即安全跳过。正例:
void lxc_free_array(void **array, lxc_free_fn element_free_fn) { void **p; for (p = array; p && *p; p++) element_free_fn(*p); free((void*)array); }
  • 清理函数还要能接受"已经被清理过的对象"。一种干净的做法:把已清理变量置为特殊值,下次调用时据此跳过。正例——文件描述符清理后置为-EBADF,下次调用检查是否为正数:
static void lxc_put_attach_clone_payload(struct attach_clone_payload *p) { if (p->ipc_socket >= 0) { shutdown(p->ipc_socket, SHUT_RDWR); close(p->ipc_socket); p->ipc_socket = -EBADF; } if (p->pty_fd >= 0) { close(p->pty_fd); p->pty_fd = -EBADF; } if (p->init_ctx) { lxc_proc_put_context_info(p->init_ctx); p->init_ctx = NULL; } }

这一"-EBADF哨兵"模式在 LXC 中广泛使用,例如 src/lxc/memory_utils.h 的close_prot_errno_disarm宏在关闭 fd 后同样置为-EBADF。

17. 有意忽略返回值时,显式(void)强转

  • 当你不关心某函数返回值时,请用(void)显式强转,向读者和静态分析器表明"这是有意忽略"。
  • 标准库中默认允许忽略返回值的函数(如close()、fclose())除外,不必强转。

规范给出的正例(cgroup 文件权限调整,chowmod失败仅是"不便而非致命"):

for (i = 0; hierarchies[i]; i++) { char *fullpath; char *path = hierarchies[i]->fullcgpath; ret = chowmod(path, destuid, nsgid, 0755); if (ret < 0) return -1; if (hierarchies[i]->version == cgroup_super_magic) { fullpath = must_make_path(path, "tasks", null); (void)chowmod(fullpath, destuid, nsgid, 0664); free(fullpath); } fullpath = must_make_path(path, "cgroup.procs", null); (void)chowmod(fullpath, destuid, 0, 0664); free(fullpath); if (hierarchies[i]->version != cgroup2_super_magic) continue; fullpath = must_make_path(path, "cgroup.subtree_control", null); (void)chowmod(fullpath, destuid, nsgid, 0664); free(fullpath); fullpath = must_make_path(path, "cgroup.threads", null); (void)chowmod(fullpath, destuid, nsgid, 0664); free(fullpath); }

源码中(void)strlcat(...)、(void)strlcpy(...)的写法(例如 src/lxc/cgroups/cgfsng.c 中多处)正是本条的普遍实践。

18. 用for (;;)而非while (1)或while (true)

LXC 统一使用for (;;)表示无限循环。仓库的 coccinelle/while-true.cocci 脚本即用 spatch 自动完成while (true)/while (1)→for (;;)的改写,配套执行脚本 coccinelle/run-coccinelle.sh 可对全部*.c/*.h文件批量应用。

19. DCO 声明语句集合

LXC 支持以下 DCO(Developer Certificate of Origin)声明,提交补丁时按实际情况选用:

  • Signed-off-by: Random J Developer <random@developer.org>:你编写了这段代码或有权利将其贡献给 LXC。
  • Acked-by: Random J Developer <random@developer.org>:你读过代码并认为其正确。通常由维护者或资深贡献者使用,可为他人代码的正确性背书。
  • Reviewed-by: Random J Developer <random@developer.org>:你审查过代码并为其正确性背书(即愿意为它可能引入的 bug 负责修复)。通常也是维护者或资深贡献者使用。
  • Co-developed-by: Random J Developer <random@developer.org>:代码无法合理归功于单个开发者,即你们合作完成。
  • Tested-by: Random J Developer <random@developer.org>:你验证过代码修复了某个 bug 或行为符合预期。
  • Reported-by: Random J Developer <random@developer.org>:你发现并报告了 bug。
  • Suggested-by: Random J Developer <random@developer.org>:代码由你编写但创意来自他人。这条常被忽视,但代表良好的礼仪与编码道德:如果别人帮你解决了问题或贡献了巧妙想法,不要默默据为己有——诚实加上Suggested-by。

20. Commit Message 大纲

  • 必须遵守 80 列限制,尤其是标题行(commit message 正文不受第 4 条限制,便于grep)。
  • 只使用英文 commit message,并保证拼写与语法正确;若你是非母语者且觉得困难,可以直接说明,维护者通常会乐意帮你拉取分支并改进消息。
  • 提交消息请始终以受影响文件(去掉扩展名)或模块名作为前缀。

规范给出的好例子:

commit b87243830e3b5e95fa31a17cf1bfebe55353bf13 Author: Felix Abecassis <fabecassis@nvidia.com> Date: Fri Feb 2 06:19:13 2018 -0800 hooks: change the semantic of NVIDIA_VISIBLE_DEVICES="" With LXC, you can override the value of an environment variable to null, but you can't unset an existing variable. The NVIDIA hook was previously activated when NVIDIA_VISIBLE_DEVICES was set to null. As a result, it was not possible to disable the hook by overriding the environment variable in the configuration. The hook can now be disabled by setting NVIDIA_VISIBLE_DEVICES to null or to the new special value "void". Signed-off-by: Felix Abecassis <fabecassis@nvidia.com> commit d6337a5f9dc7311af168aa3d586fdf239f5a10d3 Author: Christian Brauner <christian.brauner@ubuntu.com> Date: Wed Jan 31 16:25:11 2018 +0100 cgroups: get controllers on the unified hierarchy Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>

21.fork()子进程用_exit()终止

fork()出的子进程应使用_exit()而非exit()终止。原因:exit()不是线程安全的,而 LXC 是共享库,必须保证线程安全(exit()会刷新 stdio 缓冲区、运行 atexit 钩子,可能触碰共享状态;_exit()直接退出,不做这些操作)。

源码佐证:src/lxc/attach.c 的lxc_attach相关 fork 子进程路径中大量使用_exit(EXIT_FAILURE)/_exit(...)(见 L951、L1390、L1395 等),src/lxc/execute.c 中执行子进程同样遵循此约定。

22.struct数组初始化时保持水平对齐

  • struct数组初始化时,各成员需按列对齐,例如:
struct foo_struct { int n; int m; int p; }; struct foo_struct new_instance[] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, };
  • 在最后一列最大成员的左花括号后、右花括号前保留一个空格;
  • 在当前列最大成员与下一列成员之间保留一个空格。

规范给出的完整正例(信号名表,带条件编译分支也保持对齐):

struct signame { int num; const char *name; }; static const struct signame signames[] = { { SIGHUP, "HUP" }, { SIGINT, "INT" }, { SIGQUIT, "QUIT" }, { SIGILL, "ILL" }, { SIGABRT, "ABRT" }, { SIGFPE, "FPE" }, { SIGKILL, "KILL" }, { SIGSEGV, "SEGV" }, { SIGPIPE, "PIPE" }, { SIGALRM, "ALRM" }, { SIGTERM, "TERM" }, { SIGUSR1, "USR1" }, { SIGUSR2, "USR2" }, { SIGCHLD, "CHLD" }, { SIGCONT, "CONT" }, { SIGSTOP, "STOP" }, { SIGTSTP, "TSTP" }, { SIGTTIN, "TTIN" }, { SIGTTOU, "TTOU" }, #ifdef SIGTRAP { SIGTRAP, "TRAP" }, #endif #ifdef SIGIOT { SIGIOT, "IOT" }, #endif #ifdef SIGEMT { SIGEMT, "EMT" }, #endif #ifdef SIGBUS { SIGBUS, "BUS" }, #endif #ifdef SIGSTKFLT { SIGSTKFLT, "STKFLT" }, #endif #ifdef SIGCLD { SIGCLD, "CLD" }, #endif #ifdef SIGURG { SIGURG, "URG" }, #endif #ifdef SIGXCPU { SIGXCPU, "XCPU" }, #endif #ifdef SIGXFSZ { SIGXFSZ, "XFSZ" }, #endif #ifdef SIGVTALRM { SIGVTALRM, "VTALRM" }, #endif #ifdef SIGPROF { SIGPROF, "PROF" }, #endif #ifdef SIGWINCH { SIGWINCH, "WINCH" }, #endif #ifdef SIGIO { SIGIO, "IO" }, #endif #ifdef SIGPOLL { SIGPOLL, "POLL" }, #endif #ifdef SIGINFO { SIGINFO, "INFO" }, #endif #ifdef SIGLOST { SIGLOST, "LOST" }, #endif #ifdef SIGPWR { SIGPWR, "PWR" }, #endif #ifdef SIGUNUSED { SIGUNUSED, "UNUSED" }, #endif #ifdef SIGSYS { SIGSYS, "SYS" }, #endif };

23. 用strlcpy()替代strncpy()

  • 复制字符串一律使用strlcpy():它的优点是总是会在目标字符串末尾追加\0(strncpy()在源串长度达到size时不保证 NUL 结尾,还可能用\0填充剩余空间)。
  • 除非有正当理由接受截断,否则必须检查是否发生截断、将其视为错误并妥善处理。

源码佐证:LXC 自带 src/include/strlcpy.c 实现(从 musl 移植),逻辑为:返回源串长度ret;若size非零则复制size-1个字节并强制dest[len] = '\0'。实际使用见 src/lxc/cgroups/cgfsng.c(如len = strlcpy(buf, path, sizeof(buf));)等 40 余处。

24. 用strlcat()替代strncat()

  • 字符串拼接一律使用strlcat():它同样保证追加\0,避免strncat()不保证 NUL 结尾的缺陷。
  • 除非有正当理由接受截断,否则必须检查是否截断、视为错误并处理。

源码佐证:src/include/strlcat.c 为 musl 移植实现,通过strnlen计算已用长度后委托strlcpy完成追加。实际使用见 src/lxc/cgroups/cgfsng.c(L1167-L1170 等)、src/lxc/conf.c 等处。

25.switch语句中的__fallthrough__

当 LXC 检测到编译器足够新时,会要求编译器检查switch语句中未文档化的 fallthrough。凡是case有意落入下一分支的,都必须显式放置__fallthrough__。

int lxc_attach_run_command(void *payload) { int ret = -1; lxc_attach_command_t *cmd = payload; ret = execvp(cmd->program, cmd->argv); if (ret < 0) { switch (errno) { case ENOEXEC: ret = 126; break; case ENOTDIR: __fallthrough; case ENOENT: ret = 127; break; } } SYSERROR("Failed to exec \"%s\"", cmd->program); return ret; }

源码佐证:__fallthrough宏定义于 src/lxc/compiler.h:

#if HAVE_COMPILER_ATTR_FALLTHROUGH || __GNUC__ >= 7 #define __fallthrough __attribute__((__fallthrough__)) #else #define __fallthrough /* fall through */ #endif

即:编译器支持__fallthrough__属性(GCC ≥ 7)时扩展为属性声明,否则退化为空注释。该宏在 src/lxc/attach.c、src/lxc/commands.c、src/lxc/conf.c、src/lxc/criu.c、src/lxc/cgroups/cgfsng.c 等 8 个文件中有实际使用。

26. 禁止使用fgets()

LXC 不允许使用fgets(),应改用getline()或其他方式。原因:fgets()需要固定缓冲区,容易出现截断与边界处理问题;getline()自动分配缓冲区,更适合 LXC 中大量"逐行读取挂载信息、配置项"的场景。规范第 26 条的示例函数turn_into_dependent_mounts()正是用getline(&line, &len, f)逐行读取/proc/self/mountinfo。

27. 禁止在栈上分配内存

明确禁止alloca()在代码库中的任何使用。栈上大块分配既不可移植(alloca本身非标准),又容易引发栈溢出,应改用堆分配 + 第 28 条的 cleanup 宏管理生命周期。

28. 使用 gcc/clang 的 cleanup 宏管理资源

LXC 已从手工清理资源全面转向 gcc/clang 支持的 cleanup 宏:

__attribute__((__cleanup__(<my-cleanup-function-wrapper>)))

只要存在合适的宏,就不允许再手工清理。目前支持以下宏:

/* close file descriptor */ __do_close_prot_errno /* free allocated memory */ __do_free __attribute__((__cleanup__(__auto_free__))) /* close FILEs */ __do_fclose __attribute__((__cleanup__(__auto_fclose__))) /* close DIRs */ __do_closedir __attribute__((__cleanup__(__auto_closedir__)))

(注:规范文档中列出的宏名与源码中的最终定义略有差异——实际定义见 src/lxc/memory_utils.h:__do_close、__do_fclose、__do_closedir、__do_free,以及 src/lxc/utils.h 的__do_lxc_pclose等扩展宏。核心机制一致:变量出作用域时编译器自动调用包装函数完成清理。)

规范给出的完整示例(将旧根挂载树转为 dependent mounts,涉及 fd、FILE 的双重生命周期管理):

void turn_into_dependent_mounts(void) { __do_free char *line = NULL; __do_fclose FILE *f = NULL; __do_close int memfd = -EBADF, mntinfo_fd = -EBADF; int ret; ssize_t copied; size_t len = 0; mntinfo_fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC); if (mntinfo_fd < 0) { SYSERROR("Failed to open \"/proc/self/mountinfo\""); return; } memfd = memfd_create(".lxc_mountinfo", MFD_CLOEXEC); if (memfd < 0) { char template[] = P_tmpdir "/.lxc_mountinfo_XXXXXX"; if (errno != ENOSYS) { SYSERROR("Failed to create temporary in-memory file"); return; } memfd = lxc_make_tmpfile(template, true); if (memfd < 0) { WARN("Failed to create temporary file"); return; } } again: copied = lxc_sendfile_nointr(memfd, mntinfo_fd, NULL, LXC_SENDFILE_MAX); if (copied < 0) { if (errno == EINTR) goto again; SYSERROR("Failed to copy \"/proc/self/mountinfo\""); return; } ret = lseek(memfd, 0, SEEK_SET); if (ret < 0) { SYSERROR("Failed to reset file descriptor offset"); return; } f = fdopen(memfd, "re"); if (!f) { SYSERROR("Failed to open copy of \"/proc/self/mountinfo\" to mark all shared. Continuing"); return; } /* * After a successful fdopen() memfd will be closed when calling * fclose(f). Calling close(memfd) afterwards is undefined. */ move_fd(memfd); while (getline(&line, &len, f) != -1) { char *opts, *target; target = get_field(line, 4); if (!target) continue; opts = get_field(target, 2); if (!opts) continue; null_endofword(opts); if (!strstr(opts, "shared")) continue; null_endofword(target); ret = mount(NULL, target, NULL, MS_SLAVE, NULL); if (ret < 0) { SYSERROR("Failed to recursively turn old root mount tree into dependent mount. Continuing..."); continue; } TRACE("Recursively turned old root mount tree into dependent mount"); } TRACE("Turned all mount table entries into dependent mount"); }

这段代码堪称"风格总复习":cleanup 宏(__do_free/__do_fclose/__do_close)声明在函数最顶部、EINTR时用goto again向上重试(第 14 条允许的例外)、用getline()而非fgets()、(void)之外对move_fd(memfd)的显式所有权转移注释(防止fdopen后重复close的未定义行为)。

cleanup 宏的底层实现(src/lxc/memory_utils.h)核心是:

#define define_cleanup_function(type, cleaner) \ static inline void cleaner##_function(type *ptr) \ { \ if (*ptr) \ cleaner(*ptr); \ } #define call_cleaner(cleaner) \ __attribute__((__cleanup__(cleaner##_function))) __attribute__((unused)) #define close_prot_errno_disarm(fd) \ if (fd >= 0) { \ int _e_ = errno; \ close(fd); \ errno = _e_; \ fd = -EBADF; \ } #define __do_close call_cleaner(close_prot_errno_disarm) #define __do_fclose call_cleaner(fclose) #define __do_closedir call_cleaner(closedir) #define __do_free call_cleaner(free_disarm)

可见__do_close使用close_prot_errno_disarm:先保存errno再close(),随后恢复errno并置 fd 为-EBADF(呼应第 16 条的哨兵约定);__do_free使用free_disarm,释放后置NULL避免 double-free。这套宏在 src/lxc/conf.c、src/lxc/start.c、src/lxc/lxccontainer.c、src/lxc/cgroups/cgfsng.c、src/lxc/storage/ 各后端等 40 余个文件中被广泛使用。

附:规范编号说明

原文档第 24/25 条编号存在重复(两个"24"、"25"),本文按原文顺序依次呈现(fgets禁令、栈上分配禁令、cleanup 宏),实际规则数量为 26 条左右,建议以内容为准,不必纠结编号。

总结

LXC 的编码风格本质是"内核风格 + 共享库工程约束"的融合:Tab 缩进、/* */注释、80 列、错误消息大写、goto清理等与内核一致;而"线程安全下的_exit()与EXIT_*宏"、"strlcpy/strlcat强制 NUL 结尾"、"__fallthrough__显式标注"、"cleanup 宏全面接管资源管理"、"禁止 VLA /alloca/fgets"则是面向现代编译器能力和库级鲁棒性的强化要求。配合coccinelle/目录的自动化改写脚本(while-true.cocci、exit.cocci)与 run-coccinelle.sh 批量执行入口,新贡献者可以先跑脚本再按本文逐条自检,快速产出符合项目规范的补丁。

  • 容器运行时
  • 云原生
  • 运维

【免费下载链接】lxc

LXC - Linux Containers

项目地址:https://gitcode.com/gh_mirrors/lx/lxc
点击查看免费下载
上一篇:StarRocks 元函数 inspect_mv_relationships:透视外部表与物化视图的映射关系
下一篇:Gatsby Content Sync 内容同步机制全解析:从 CMS 预览到 Node Manifest 页面映射

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 6:45:46

5G+智慧文旅落地实操:专网切片、边缘GPU与客流融合避坑指南

简介&#xff1a;本资源是一份聚焦5G技术与文旅产业深度融合的行业应用型课件&#xff0c;面向通信、文旅、政企解决方案等领域的从业者、高校师生及数字化转型研究者&#xff0c;旨在系统解析5G如何破解出行难、停车难、入园难、赏景难、如厕难等文旅服务痛点&#xff0c;并支…

作者头像 李华
网站建设 2026/10/13 6:45:45

27. 数据产品- BI进阶 - AI 应用实战终篇-从 RAG 架构到企业级平台落地

文章目录前言开篇总结&#xff08;总述&#xff09;第一节 多模型兼容配置&#xff1a;云AI API KEY配置调用 本地化部署模型调用双模式第二节 批量导入管理&#xff1a;文件上传、解析与切片全流程第三节 元数据管理&#xff1a;结构化 非结构化双体系管控第四节 企业智能问…

作者头像 李华
网站建设 2026/10/13 6:44:20

编程智能体三件套:管钱、管事、管时间的插件实践

1. 为什么想起来给编程智能体配三件套先说背景。我大概是今年年初开始重度使用 DeepSeek 的编程智能体做日常开发的&#xff0c;干了几周之后&#xff0c;整体感觉是&#xff1a;能力够用&#xff0c;但有两个很要命的问题。第一是成本感缺失。智能体跑起来之后&#xff0c;我经…

作者头像 李华
网站建设 2026/10/13 6:43:57

时序网络瓦解Ⅱ

一、研究背景 【注意时序网络需要结合最大连通分量、时间可达性、时序巨分量等来反应网络】 总结: 时序网络的定义:节点与连边会随时间变化而变化 时序网络注意事项:需要考虑什么时间与什么节点连接,连接多久。 时许网络瓦解注意事项:需要考虑在什么时间与什么节点断开可以…

作者头像 李华
网站建设 2026/10/13 6:43:38

Spring Cloud Gateway限流实战 Redis令牌桶算法+熔断降级

你的微服务网关是不是也遇到过这种场面&#xff1a;大促一开闸&#xff0c;几十万的请求像洪水一样涌进来&#xff0c;下游订单服务瞬间被打瘫&#xff0c;队列积压&#xff0c;用户刷屏投诉"系统繁忙"&#xff1f;限流不是"要不要做"的事&#xff0c;而是…

作者头像 李华
网站建设 2026/10/13 6:42:57

Suno Studio Projects API 工程化实战:从单次生成到项目制管理

1. 从“能生成”到“能管理”&#xff1a;AI 音乐创作流程的工程化拐点AI 音乐生成工具刚火起来那阵子&#xff0c;大多数人的用法都很原始&#xff1a;打开网页&#xff0c;敲一段提示词&#xff0c;等几十秒&#xff0c;下载一首歌&#xff0c;然后关掉页面。下一次想改个风格…

作者头像 李华