- 容器运行时
- 云原生
- 运维
【免费下载链接】lxc
LXC - Linux Containers
本指南系统梳理 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. 变量声明规则
变量应在函数顶部或新作用域开头声明,严禁在作用域中间声明。声明顺序按以下优先级排列:
- 自动释放变量——特指通过
gcc/clang的cleanup属性(见第 26 条)清理的变量; - 已初始化变量;
- 未初始化变量。
通用细则:
- 基础类型放在复杂类型之前;
- 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
相关推荐
Ryujinx项目C代码风格规范深度解析
Ryujinx项目C 代码风格规范深度解析 前言 在大型开源项目开发中,统一的代码风格对于维护代码质量和团队协作至关重要。Ryujinx作为一款使用C 开发的任
游戏开发图形学HarfBuzz代码风格指南:C编码规范与最佳实践
HarfBuzz代码风格指南:C编码规范与最佳实践 想要编写高质量的HarfBuzz代码吗?这份完整的 HarfBuzz代码风格指南 将为您揭示这个强大文本整形
图形学ThreadPool项目代码规范:C++编码风格指南
ThreadPool项目代码规范:C++编码风格指南 1. 前言 你还在为C++线程池代码风格混乱而头疼吗?本文将为你提供一套完整的ThreadPool项目代码
并发编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考