[toc]
kernel/sysctl.c & fs/proc/proc_sysctl.c 内核参数调整接口(Kernel Parameter Tuning Interface) 历史与背景 这项技术是为了解决什么特定问题而诞生的? Sysctl(System Control)机制的诞生是为了解决一个核心的系统管理问题:如何为系统管理员和应用程序提供一个统一的、动态的接口来查看和修改正在运行的Linux内核的内部参数 。
在Sysctl出现之前,调整内核行为通常需要:
修改内核源代码并重新编译 :这是最原始、最不灵活的方式。
在内核启动时传递引导参数(Boot Parameters) :这比重新编译要好,但仍然需要在启动时就确定参数,无法在系统运行时动态调整。
随着Linux内核变得越来越复杂,需要暴露给管理员进行调整的“旋钮”(Tunables)也越来越多。这些参数涵盖了从网络协议栈行为、虚拟内存管理策略到文件系统特性等方方面面。Sysctl的出现,就是为了提供一个**在运行时(Runtime)**就能与这些内核变量进行交互的框架,从而实现:
动态调优 :管理员可以根据系统的实时负载和需求,动态地调整内核参数以优化性能或改变行为,而无需重启系统。
系统监控 :提供一个标准化的路径来读取内核的各种状态和统计信息。
统一的接口 :将内核中分散在各个子系统的可调参数,通过一个统一的、层次化的命名空间暴露出来。
它的发展经历了哪些重要的里程碑或版本迭代?
sysctl(2)系统调用的引入 :最初,Sysctl的主要接口是一个专门的sysctl(2)系统调用。这个系统调用使用一个由整数组成的、基于OID(对象标识符)的非直观路径来访问参数。例如,net.ipv4.ip_forward可能对应于{CTL_NET, NET_IPV4, NET_IPV4_IP_FORWARD}。
/proc/sys文件系统的出现 :这是决定性的里程碑。社区认识到sysctl(2)系统调用非常不便于人类使用和脚本编写。为了提供一个更友好、更易于浏览的接口,内核引入了/proc/sys这个“伪文件系统”。procfs将Sysctl的层次化命名空间映射 为文件系统的目录和文件结构。
net.ipv4.ip_forward这个参数现在可以直接通过读写文件/proc/sys/net/ipv4/ip_forward来访问。
这种基于文件I/O的接口非常符合Unix“一切皆文件”的哲学,使得管理员可以用标准的shell工具(cat, echo)来轻松地查看和修改内核参数。
sysctl(2)系统调用的废弃 :由于/proc/sys接口的巨大成功和便利性,sysctl(2)系统调用在Linux 2.6之后被废弃(Deprecated) ,不再推荐在新代码中使用。现在,sysctl机制几乎完全等同于/proc/sys文件系统。
目前该技术的社区活跃度和主流应用情况如何? /proc/sys是现代Linux系统中进行内核参数调优和状态监控的核心标准接口 。
主流应用 :
系统管理员 :日常使用sysctl命令(它实际上是/proc/sys的一个包装器)或直接编辑/etc/sysctl.conf文件来持久化内核参数的修改。
网络性能调优 :调整TCP/IP协议栈的各种缓冲区大小、超时时间等参数(如net.core.somaxconn, net.ipv4.tcp_rmem)是网络优化的常见操作。
虚拟内存管理 :调整“脏页”回写策略(vm.dirty_ratio)、交换分区使用倾向(vm.swappiness)等。
容器和虚拟化 :在创建网络命名空间时,内核会自动为新的命名空间创建一套独立的/proc/sys/net参数,实现了网络栈的隔离配置。
核心原理与设计 它的核心工作原理是什么? Sysctl的核心是一个基于表的注册机制 ,并由procfs提供前端展现。
内核中的注册表 (struct ctl_table) :
内核的各个子系统(如网络、虚拟内存)如果想暴露一个可调参数,就需要定义一个struct ctl_table实例。
这个结构体描述了参数的所有信息:
procname: 参数的名称(即在/proc/sys中显示的文件名)。
data: 一个指向内核中实际变量的指针。
maxlen: 变量的大小。
mode: 文件的权限(可读/可写)。
proc_handler: 一个可选的处理函数指针。这使得读写操作可以不仅仅是简单的变量拷贝,还可以触发更复杂的内核动作。
child: 指向一个子ctl_table数组,用于构建目录层次。
注册到核心 :
在模块初始化时,子系统调用register_sysctl_table()将这个ctl_table注册到Sysctl核心中。Sysctl核心维护着一个由这些表构成的全局树状结构。
/proc/sys的映射 :
proc_sysctl.c实现了/proc/sys这个伪文件系统。
当用户访问/proc/sys下的一个路径时(例如cat /proc/sys/net/ipv4/ip_forward):
procfs的路径查找代码会遍历Sysctl的全局ctl_table树,匹配路径的每个部分(net, ipv4, ip_forward)。
找到匹配的ctl_table条目后,它会为这个条目动态地创建一个inode。
当用户对这个文件进行read或write操作时,procfs会调用该ctl_table条目中指定的proc_handler函数(如果存在),或者使用一个默认的处理函数。
处理函数会根据ctl_table中的data指针,读取或修改内核中对应的变量。
它的主要优势体现在哪些方面?
动态性 :无需重启即可实时查看和修改内核行为。
统一与可发现性 :提供了一个集中的、层次化的、可自描述的(通过ls -R /proc/sys)接口。
易用性 :基于文件的接口非常直观,易于管理员和脚本使用。
灵活性 :通过自定义处理函数,Sysctl不仅能读写变量,还能触发复杂的内核动作。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
非结构化数据 :Sysctl主要设计用来处理简单的、单一的值(整数、字符串)。它不适合用来导出复杂的、结构化的数据或大量的统计信息。这类需求通常由procfs下的其他文件(如/proc/net/dev)或sysfs来满足。
命名空间隔离不完全 :虽然网络参数(/proc/sys/net)是每个网络命名空间独立的,但其他大多数Sysctl参数(如/proc/sys/vm, /proc/sys/kernel)是全局的 ,不能被容器隔离。这是容器技术中一个已知的局限性。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案? Sysctl是调整内核全局行为策略和协议栈参数 的标准解决方案。
开启IP转发 :echo 1 > /proc/sys/net/ipv4/ip_forward。这是将一台Linux机器配置为路由器的基本步骤。
调整虚拟内存行为 :echo 10 > /proc/sys/vm/swappiness。降低内核使用交换分区的倾向,让系统更倾向于回收文件页缓存。
安全加固 :禁用ICMP重定向(net.ipv4.conf.all.accept_redirects = 0)或开启SYN Cookies(net.ipv4.tcp_syncookies = 1)以抵御某些网络攻击。
增加系统限制 :增加最大文件句柄数(fs.file-max)或最大PID数(kernel.pid_max)。
是否有不推荐使用该技术的场景?为什么?
控制设备状态 :不推荐。控制或查看单个设备 的状态(例如,一个LED的亮度,一个网卡的速率)应该使用sysfs (/sys)。Sysctl是用于控制内核子系统的全局行为 ,而sysfs是用于控制具体的设备实例 。
查看进程信息 :不推荐。应使用/proc/[pid]/。
获取大量的、格式化的统计数据 :不推荐。例如,获取网络接口的收发包统计,直接读取/proc/net/dev比通过Sysctl更合适。
对比分析 请将其 与 其他相似技术 进行详细对比。
特性
Sysctl (/proc/sys)
Sysfs (/sys)
Procfs (其他部分, e.g., /proc/[pid])
核心功能
调整内核全局行为 和子系统策略。
表示和控制设备模型 中的具体设备和驱动。
报告进程状态 和导出格式化的、只读的 系统信息。
数据模型
层次化的键值对 。主要用于读写单一值 。
面向对象的图结构 。将kobject的层次结构映射为目录,“一文件一值”。
混合模型 。部分是目录结构(进程),部分是包含复杂文本的大文件。
读写特性
可读可写 。其主要目的就是“可调”。
可读可写 。用于读取设备状态和写入设备配置。
大部分是只读的 。用于报告状态,而非配置。
命名空间隔离
部分隔离 (只有net等少数部分)。
不隔离 。反映的是全局的物理或虚拟设备。
部分隔离 。/proc/[pid]自然是隔离的,但/proc/meminfo等是全局的。
典型用途
调整TCP缓冲区大小 (net.core.wmem_max)。
改变LED亮度 (/sys/class/leds/led0/brightness)。
查看进程的内存映射 (/proc/self/maps)。
总结
内核的“设置”菜单
系统的“设备管理器”
系统的“任务管理器”和“信息中心”
Sysctl基础设施常量与写入模式配置 本代码片段的功能是为Linux内核的sysctl子系统定义一组基础的、全局性的常量和配置变量。它不创建任何用户可见的sysctl文件,而是为其他地方定义的ctl_table条目提供共享的数据源,并定义一个关键的全局策略——如何处理对/proc/sys/文件的写入操作,以增强系统的健壮性和安全性。
实现原理分析 此代码是sysctl框架的底层支持,其实现原理是为更高层的配置表提供数据和行为开关。
共享常量数组 (sysctl_long_vals) :
代码定义并导出了一个包含0, 1, LONG_MAX的全局数组。
EXPORT_SYMBOL_GPL意味着内核中的任何其他部分(遵循GPL许可)都可以引用这个数组。
其目的是提供一个通用的、可复用的数据源。在定义许多ctl_table条目时,需要指定允许的最小值和最大值。通过让.extra1和.extra2字段指向这个共享数组中的元素(例如,通过SYSCTL_ZERO, SYSCTL_ONE等宏),可以避免在内核的各个角落重复定义这些常用常量,从而节省少量内存并提高代码的一致性。
编译时限制常量 (ngroups_max, cap_last_cap) :
ngroups_max和cap_last_cap被定义为static const int。它们的值来自于内核头文件中定义的宏NGROUPS_MAX(进程可属的最大补充组数)和CAP_LAST_CAP(最后一个有效的能力值)。
将这些宏的值赋给静态常量变量的目的是为了获取一个内存地址 。ctl_table的.data字段需要一个指针。虽然NGROUPS_MAX是一个编译时常量,但它没有内存地址。通过这个赋值,sysctl_subsys_table(在之前的示例中)就可以安全地使用 &ngroups_max 来填充.data字段,从而将这个编译时确定的内核限制暴露给用户空间。
写入模式控制 (sysctl_writes_strict) :
这是本片段中最核心的功能。它定义了一个枚举sysctl_writes_mode和相应的全局变量sysctl_writes_strict,用于精确控制通过procfs写入sysctl值的行为语义。
SYSCTL_WRITES_LEGACY : 旧的、不推荐的行为。写入操作会忽略文件的当前偏移量,每次write()系统调用都会完全覆盖整个sysctl值。
SYSCTL_WRITES_WARN : 与旧行为相同,但如果写入时文件偏移量不为0,内核会打印一条警告。这是一个过渡模式,旨在帮助开发者发现并修复依赖旧行为的脚本或程序。
SYSCTL_WRITES_STRICT : 当前的默认模式,也是最安全的模式。它强制要求对数值型sysctl的写入必须从文件偏移量0开始,并且一次write()调用必须包含完整的数值。这可以防止因部分写入或意外的文件指针位置而导致内核参数被错误地设置为截断或无效的值。
这个全局变量sysctl_writes_strict会被sysctl文件处理函数(如proc_dointvec)读取,并根据其值来调整自身的行为。之前示例中定义的/proc/sys/kernel/sysctl_writes_strict文件就是用来在运行时修改这个全局变量值的用户接口。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const unsigned long sysctl_long_vals[] = { 0 , 1 , LONG_MAX };EXPORT_SYMBOL_GPL(sysctl_long_vals); #if defined(CONFIG_SYSCTL) static const int ngroups_max = NGROUPS_MAX;static const int cap_last_cap = CAP_LAST_CAP;#ifdef CONFIG_PROC_SYSCTL enum sysctl_writes_mode { SYSCTL_WRITES_LEGACY = -1 , SYSCTL_WRITES_WARN = 0 , SYSCTL_WRITES_STRICT = 1 , }; static enum sysctl_writes_mode sysctl_writes_strict = SYSCTL_WRITES_STRICT;#endif #endif
内核子系统参数初始化:创建/proc/sys/kernel/基础接口 本代码片段的功能是为Linux内核定义并注册一组基础的、与子系统和进程属性相关的运行时参数。它利用sysctl机制,在/proc/sys/kernel/目录下创建对应的文件,从而允许用户空间查询内核中关于进程组员数量上限(ngroups_max)、能力系统(capabilities)的范围(cap_last_cap)以及特定于体系结构的非对齐内存访问处理策略。
实现原理分析 此功能的实现是Linux sysctl框架的标准应用,旨在将内核内部的配置变量安全地暴露给用户空间。
Sysctl表定义 (sysctl_subsys_table) :
代码的核心是sysctl_subsys_table数组,它是一个ctl_table结构体的集合。该数组中的每一个条目都定义了一个将在/proc/sys/kernel/下创建的文件。
关键字段描述了每个接口的行为:
.procname: procfs中的文件名。
.data: 指向内核中实际存储该参数值的全局变量的指针。
.mode: 文件的访问权限。在此代码段中,ngroups_max和cap_last_cap被设为0444(只读),因为它们是由内核在编译时或启动时确定的常量,不应在运行时被修改。
.proc_handler: 指向处理该文件读写请求的内核函数。这里使用了标准的proc_dointvec(处理整数)和proc_dointvec_minmax(处理带范围检查的整数)。
条件编译 (#ifdef) :
该表的定义中使用了#ifdef预处理指令。例如,与非对齐访问相关的条目(unaligned-trap, ignore-unaligned-usertrap)仅在内核配置了相应的体系结构支持选项(CONFIG_SYSCTL_ARCH_UNALIGN_*)时才会被编译。这确保了sysctl接口只暴露与当前内核配置和目标体系结构相关的参数。
注册过程 (sysctl_init_bases) :
sysctl_init_bases函数在内核初始化期间被调用,它通过register_sysctl_init("kernel", sysctl_subsys_table)将定义的sysctl_subsys_table注册到 “kernel” 命名空间下。sysctl框架会据此在procfs中创建/proc/sys/kernel/目录(如果尚不存在)并填充其中对应的文件。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #ifdef CONFIG_SYSCTL static const struct ctl_table sysctl_subsys_table [] = {#ifdef CONFIG_PROC_SYSCTL { .procname = "sysctl_writes_strict" , .data = &sysctl_writes_strict, .maxlen = sizeof (int ), .mode = 0644 , .proc_handler = proc_dointvec_minmax, .extra1 = SYSCTL_NEG_ONE, .extra2 = SYSCTL_ONE, }, #endif { .procname = "ngroups_max" , .data = (void *)&ngroups_max, .maxlen = sizeof (int ), .mode = 0444 , .proc_handler = proc_dointvec, }, { .procname = "cap_last_cap" , .data = (void *)&cap_last_cap, .maxlen = sizeof (int ), .mode = 0444 , .proc_handler = proc_dointvec, }, #ifdef CONFIG_SYSCTL_ARCH_UNALIGN_ALLOW { .procname = "unaligned-trap" , .data = &unaligned_enabled, .maxlen = sizeof (int ), .mode = 0644 , .proc_handler = proc_dointvec, }, #endif #ifdef CONFIG_SYSCTL_ARCH_UNALIGN_NO_WARN { .procname = "ignore-unaligned-usertrap" , .data = &no_unaligned_warning, .maxlen = sizeof (int ), .mode = 0644 , .proc_handler = proc_dointvec, }, #endif }; int __init sysctl_init_bases (void ) { register_sysctl_init("kernel" , sysctl_subsys_table); return 0 ; } #endif
Sysctl标准整数处理程序:/proc接口的核心转换逻辑 本代码片段是Linux sysctl子系统的核心实现之一,它提供了一整套标准化的处理函数(proc handlers),用于安全、高效地处理用户空间通过/proc/sys/文件对内核整数类型(int, unsigned int, bool, u8)参数的读写操作。这些函数是绝大多数数值型sysctl接口(如proc_dointvec, proc_dointvec_minmax等)的底层基础,负责处理ASCII字符串与内核二进制整数之间的转换、错误检查、范围验证和并发安全。
实现原理分析 该代码的设计体现了高度的模块化和可扩展性,通过函数分层和回调机制来构建功能。
分层设计 :
转换层 (do_proc_d...vec_conv) : 这是最底层,负责在已解析出的unsigned long值和目标内核变量(如int *或unsigned int *)之间进行最终的、类型安全的转换。它处理特定类型的溢出检查(例如,确保一个值在INT_MAX范围内)和符号处理。
解析/格式化层 (__do_proc_d...vec) : 这是核心工作层。它负责处理来自用户空间的原始字符缓冲区。在写入 时,它调用proc_skip_spaces和proc_get_long等辅助函数来解析ASCII字符串,提取出数值。在读取 时,它调用proc_put_long和proc_put_char来将内核中的数值格式化为ASCII字符串。这一层通过一个函数指针参数conv来调用转换层的函数,实现了逻辑的解耦。
公共API层 (proc_d...vec) : 这是最高层,是ctl_table中.proc_handler字段实际使用的函数。这些函数是对解析/格式化层的简单封装,它们为调用提供了便利,并传入默认的转换函数。
通过回调实现扩展性 :
proc_dointvec_minmax和proc_douintvec_minmax是该设计模式的最佳体现。它们不是重新实现一遍解析逻辑,而是继续调用通用的do_proc_dointvec函数。
它们通过传递一个自定义的转换函数 (do_proc_dointvec_minmax_conv)和一个包含最小/最大值指针的参数结构体 (param)来实现范围检查。
这个自定义的转换函数在内部先调用默认的转换函数,然后再执行额外的范围检查。这使得在不改变核心解析逻辑的情况下,轻松地为sysctl参数添加了范围验证功能。
并发安全 (READ_ONCE/WRITE_ONCE) :
所有对内核sysctl变量的直接读写都通过READ_ONCE()和WRITE_ONCE()宏来完成。这些宏确保了内存操作的原子性(对于对齐的自然大小变量),并充当了编译器屏障。这可以防止编译器进行可能破坏并发访问的优化(如将内存访问优化为寄存器访问),从而保证了在多核或中断环境下对这些变量访问的安全性。
代码分析 核心转换函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 static int do_proc_dointvec_conv (bool *negp, unsigned long *lvalp, int *valp, int write, void *data) { if (write) { if (*negp) { if (*lvalp > (unsigned long ) INT_MAX + 1 ) return -EINVAL; WRITE_ONCE(*valp, -*lvalp); } else { if (*lvalp > (unsigned long ) INT_MAX) return -EINVAL; WRITE_ONCE(*valp, *lvalp); } } else { int val = READ_ONCE(*valp); if (val < 0 ) { *negp = true ; *lvalp = -(unsigned long )val; } else { *negp = false ; *lvalp = (unsigned long )val; } } return 0 ; } static int do_proc_douintvec_conv (unsigned long *lvalp, unsigned int *valp, int write, void *data) { if (write) { if (*lvalp > UINT_MAX) return -EINVAL; WRITE_ONCE(*valp, *lvalp); } else { unsigned int val = READ_ONCE(*valp); *lvalp = (unsigned long )val; } return 0 ; }
通用整数向量解析与格式化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static int __do_proc_dointvec(void *tbl_data, const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, unsigned long *lvalp, int *valp, int write, void *data), void *data) { if (!conv) conv = do_proc_dointvec_conv; for (; left && vleft--; i++, first=0 ) { unsigned long lval; bool neg; if (write) { proc_skip_spaces(&p, &left); err = proc_get_long(&p, &left, &lval, &neg, ...); if (conv(&neg, &lval, i, 1 , data)) { err = -EINVAL; break ; } } else { if (conv(&neg, &lval, i, 0 , data)) { err = -EINVAL; break ; } if (!first) proc_put_char(&buffer, &left, '\t' ); proc_put_long(&buffer, &left, lval, neg); } } return err; } static int __do_proc_douintvec(...){ }
公共Sysctl处理函数API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int proc_dointvec (const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_dointvec(table, write, buffer, lenp, ppos, NULL , NULL ); } int proc_douintvec (const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_douintvec(table, write, buffer, lenp, ppos, do_proc_douintvec_conv, NULL ); } int proc_dobool (...) { }
带范围检查的封装函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static int do_proc_dointvec_minmax_conv (bool *negp, unsigned long *lvalp, int *valp, int write, void *data) { struct do_proc_dointvec_minmax_conv_param *param = data; ret = do_proc_dointvec_conv(negp, lvalp, ip, write, data); if (write) { if ((param->min && *param->min > tmp) || (param->max && *param->max < tmp)) return -EINVAL; WRITE_ONCE(*valp, tmp); } return 0 ; } int proc_dointvec_minmax (const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { struct do_proc_dointvec_minmax_conv_param param = { .min = (int *) table->extra1, .max = (int *) table->extra2, }; return do_proc_dointvec(table, write, buffer, lenp, ppos, do_proc_dointvec_minmax_conv, ¶m); }
Sysctl 无符号长处理程序:单位转换和范围检查 本代码片段是Linux sysctl子系统中一个功能强大且设计精巧的处理程序,专门用于读写unsigned long类型的内核参数。其核心功能不仅包括处理ASCII字符串与unsigned long数组之间的转换和范围检查,还内建了一个通用的单位转换机制 。这使得内核可以将内部使用的一种单位(如jiffies)自动、透明地转换成对用户更友好的单位(如毫秒)进行展示和配置。
代码分析 核心工作函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 static int __do_proc_doulongvec_minmax(void *data, const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos, unsigned long convmul, unsigned long convdiv) { for (; left && vleft--; i++, first = 0 ) { unsigned long val; if (write) { err = proc_get_long(&p, &left, &val, &neg, ...); if (err || neg) { err = -EINVAL; break ; } val = convmul * val / convdiv; if ((min && val < *min) || (max && val > *max)) { err = -EINVAL; break ; } WRITE_ONCE(*i, val); } else { val = convdiv * READ_ONCE(*i) / convmul; proc_put_long(&buffer, &left, val, false ); } } return err; }
公共API封装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static int do_proc_doulongvec_minmax (...) { return __do_proc_doulongvec_minmax(table->data, table, write, buffer, lenp, ppos, convmul, convdiv); } int proc_doulongvec_minmax (const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_doulongvec_minmax(table, write, buffer, lenp, ppos, 1l , 1l ); } int proc_doulongvec_ms_jiffies_minmax (const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_doulongvec_minmax(table, write, buffer, lenp, ppos, HZ, 1000l ); }
fs/proc/proc_sysctl.c sysctl_find_alias Sysctl 查找别名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 struct sysctl_alias { const char *kernel_param; const char *sysctl_param; }; static const struct sysctl_alias sysctl_aliases [] = { {"hardlockup_all_cpu_backtrace" , "kernel.hardlockup_all_cpu_backtrace" }, {"hung_task_panic" , "kernel.hung_task_panic" }, {"numa_zonelist_order" , "vm.numa_zonelist_order" }, {"softlockup_all_cpu_backtrace" , "kernel.softlockup_all_cpu_backtrace" }, { } }; static const char *sysctl_find_alias (char *param) { const struct sysctl_alias *alias ; for (alias = &sysctl_aliases[0 ]; alias->kernel_param != NULL ; alias++) { if (strcmp (alias->kernel_param, param) == 0 ) return alias->sysctl_param; } return NULL ; }
sysctl_table_root sysctl 表根目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static const struct ctl_table root_table [] = { { .procname = "" , .mode = S_IFDIR|S_IRUGO|S_IXUGO, }, }; static struct ctl_table_root sysctl_table_root = { .default_set.dir.header = { {{.count = 1 , .nreg = 1 , .ctl_table = root_table }}, .ctl_table_arg = root_table, .root = &sysctl_table_root, .set = &sysctl_table_root.default_set, }, };
sysctl_mount_point 系统挂载点 1 2 3 4 5 6 7 static const struct ctl_table sysctl_mount_point [] = { { } };
1 2 #define sysctl_set_perm_empty_ctl_header(hptr) \ (hptr->type = SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static void init_header (struct ctl_table_header *head, struct ctl_table_root *root, struct ctl_table_set *set , struct ctl_node *node, const struct ctl_table *table, size_t table_size) { head->ctl_table = table; head->ctl_table_size = table_size; head->ctl_table_arg = table; head->used = 0 ; head->count = 1 ; head->nreg = 1 ; head->unregistering = NULL ; head->root = root; head->set = set ; head->parent = NULL ; head->node = node; INIT_HLIST_HEAD(&head->inodes); if (node) { const struct ctl_table *entry ; list_for_each_table_entry(entry, head) { node->header = head; node++; } } if (table == sysctl_mount_point) sysctl_set_perm_empty_ctl_header(head); }
find_entry 查找 sysctl 表中的条目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static const struct ctl_table *find_entry (struct ctl_table_header **phead, struct ctl_dir *dir, const char *name, int namelen) { struct ctl_table_header *head ; const struct ctl_table *entry ; struct rb_node *node = dir->root.rb_node; lockdep_assert_held(&sysctl_lock); while (node) { struct ctl_node *ctl_node ; const char *procname; int cmp; ctl_node = rb_entry(node, struct ctl_node, node); head = ctl_node->header; entry = &head->ctl_table[ctl_node - head->node]; procname = entry->procname; cmp = namecmp(name, namelen, procname, strlen (procname)); if (cmp < 0 ) node = node->rb_left; else if (cmp > 0 ) node = node->rb_right; else { *phead = head; return entry; } } return NULL ; }
find_subdir 查找子目录 1 2 3 4 5 6 7 8 9 10 11 12 13 static struct ctl_dir *find_subdir (struct ctl_dir *dir, const char *name, int namelen) { struct ctl_table_header *head ; const struct ctl_table *entry ; entry = find_entry(&head, dir, name, namelen); if (!entry) return ERR_PTR(-ENOENT); if (!S_ISDIR(entry->mode)) return ERR_PTR(-ENOTDIR); return container_of(head, struct ctl_dir, header); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static void erase_entry (struct ctl_table_header *head, const struct ctl_table *entry) { struct rb_node *node = &head->node[entry - head->ctl_table].node; rb_erase(node, &head->parent->root); } static void erase_header (struct ctl_table_header *head) { const struct ctl_table *entry ; list_for_each_table_entry(entry, head) erase_entry(head, entry); }
start_unregistering 开始取消注册 sysctl 表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static void start_unregistering (struct ctl_table_header *p) { lockdep_assert_held(&sysctl_lock); if (unlikely(p->used)) { struct completion wait ; init_completion(&wait); p->unregistering = &wait; spin_unlock(&sysctl_lock); wait_for_completion(&wait); } else { p->unregistering = ERR_PTR(-EINVAL); spin_unlock(&sysctl_lock); } proc_sys_invalidate_dcache(p); spin_lock(&sysctl_lock); erase_header(p); }
put_links 删除 sysctl 表中的链接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static void put_links (struct ctl_table_header *header) { struct ctl_table_set *root_set = &sysctl_table_root.default_set; struct ctl_table_root *root = header->root; struct ctl_dir *parent = header->parent; struct ctl_dir *core_parent ; const struct ctl_table *entry ; if (header->set == root_set) return ; core_parent = xlate_dir(root_set, parent); if (IS_ERR(core_parent)) return ; list_for_each_table_entry(entry, header) { struct ctl_table_header *link_head ; const struct ctl_table *link ; const char *name = entry->procname; link = find_entry(&link_head, core_parent, name, strlen (name)); if (link && ((S_ISDIR(link->mode) && S_ISDIR(entry->mode)) || (S_ISLNK(link->mode) && (link->data == root)))) { drop_sysctl_table(link_head); } else { pr_err("sysctl link missing during unregister: " ); sysctl_print_dir(parent); pr_cont("%s\n" , name); } } }
drop_sysctl_table 删除 sysctl 表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static void drop_sysctl_table (struct ctl_table_header *header) { struct ctl_dir *parent = header->parent; if (--header->nreg) return ; if (parent) { put_links(header); start_unregistering(header); } if (!--header->count) kfree_rcu(header, rcu); if (parent) drop_sysctl_table(&parent->header); }
new_dir 创建新的 sysctl 目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static struct ctl_dir *new_dir (struct ctl_table_set *set , const char *name, int namelen) { struct ctl_table *table ; struct ctl_dir *new ; struct ctl_node *node ; char *new_name; new = kzalloc(sizeof (*new) + sizeof (struct ctl_node) + sizeof (struct ctl_table) + namelen + 1 , GFP_KERNEL); if (!new) return NULL ; node = (struct ctl_node *)(new + 1 ); table = (struct ctl_table *)(node + 1 ); new_name = (char *)(table + 1 ); memcpy (new_name, name, namelen); table[0 ].procname = new_name; table[0 ].mode = S_IFDIR|S_IRUGO|S_IXUGO; init_header(&new->header, set ->dir.header.root, set , node, table, 1 ); return new; }
get_subdir 查找或创建具有指定名称的子目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 static struct ctl_dir *get_subdir (struct ctl_dir *dir, const char *name, int namelen) { struct ctl_table_set *set = dir->header.set ; struct ctl_dir *subdir , *new = NULL ; int err; spin_lock(&sysctl_lock); subdir = find_subdir(dir, name, namelen); if (!IS_ERR(subdir)) goto found; if (PTR_ERR(subdir) != -ENOENT) goto failed; spin_unlock(&sysctl_lock); new = new_dir(set , name, namelen); spin_lock(&sysctl_lock); subdir = ERR_PTR(-ENOMEM); if (!new) goto failed; subdir = find_subdir(dir, name, namelen); if (!IS_ERR(subdir)) goto found; if (PTR_ERR(subdir) != -ENOENT) goto failed; err = insert_header(dir, &new->header); subdir = ERR_PTR(err); if (err) goto failed; subdir = new; found: subdir->header.nreg++; failed: if (IS_ERR(subdir)) { pr_err("sysctl could not get directory: " ); sysctl_print_dir(dir); pr_cont("%*.*s %ld\n" , namelen, namelen, name, PTR_ERR(subdir)); } drop_sysctl_table(&dir->header); if (new) drop_sysctl_table(&new->header); spin_unlock(&sysctl_lock); return subdir; }
sysctl_mkdir_p 创建或获取 ctl_table 的目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static struct ctl_dir *sysctl_mkdir_p (struct ctl_dir *dir, const char *path) { const char *name, *nextname; for (name = path; name; name = nextname) { int namelen; nextname = strchr (name, '/' ); if (nextname) { namelen = nextname - name; nextname++; } else { namelen = strlen (name); } if (namelen == 0 ) continue ; dir = get_subdir(dir, name, namelen); if (IS_ERR(dir)) break ; } return dir; }
xlate_dir 将 ctl_dir 翻译为 sysctl_table_root 中的目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static struct ctl_dir *xlate_dir (struct ctl_table_set *set , struct ctl_dir *dir) { struct ctl_dir *parent ; const char *procname; if (!dir->header.parent) return &set ->dir; parent = xlate_dir(set , dir->header.parent); if (IS_ERR(parent)) return parent; procname = dir->header.ctl_table[0 ].procname; return find_subdir(parent, procname, strlen (procname)); }
get_links 检查并处理 sysctl 表中的链接(links) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static bool get_links (struct ctl_dir *dir, struct ctl_table_header *header, struct ctl_table_root *link_root) { struct ctl_table_header *tmp_head ; const struct ctl_table *entry , *link ; if (header->ctl_table_size == 0 || sysctl_is_perm_empty_ctl_header(header)) return true ; list_for_each_table_entry(entry, header) { const char *procname = entry->procname; link = find_entry(&tmp_head, dir, procname, strlen (procname)); if (!link) return false ; if (S_ISDIR(link->mode) && S_ISDIR(entry->mode)) continue ; if (S_ISLNK(link->mode) && (link->data == link_root)) continue ; return false ; } list_for_each_table_entry(entry, header) { const char *procname = entry->procname; link = find_entry(&tmp_head, dir, procname, strlen (procname)); tmp_head->nreg++; } return true ; }
new_links 创建新的链接表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 static struct ctl_table_header *new_links (struct ctl_dir *dir, struct ctl_table_header *head) { struct ctl_table *link_table , *link ; struct ctl_table_header *links ; const struct ctl_table *entry ; struct ctl_node *node ; char *link_name; int name_bytes; name_bytes = 0 ; list_for_each_table_entry(entry, head) { name_bytes += strlen (entry->procname) + 1 ; } links = kzalloc(sizeof (struct ctl_table_header) + sizeof (struct ctl_node)*head->ctl_table_size + sizeof (struct ctl_table)*head->ctl_table_size + name_bytes, GFP_KERNEL); if (!links) return NULL ; node = (struct ctl_node *)(links + 1 ); link_table = (struct ctl_table *)(node + head->ctl_table_size); link_name = (char *)(link_table + head->ctl_table_size); link = link_table; list_for_each_table_entry(entry, head) { int len = strlen (entry->procname) + 1 ; memcpy (link_name, entry->procname, len); link->procname = link_name; link->mode = S_IFLNK|S_IRWXUGO; link->data = head->root; link_name += len; link++; } init_header(links, dir->header.root, dir->header.set , node, link_table, head->ctl_table_size); links->nreg = head->ctl_table_size; return links; }
insert_links 用于在 sysctl 表的层次结构中插入链接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 static int insert_links (struct ctl_table_header *head) { struct ctl_table_set *root_set = &sysctl_table_root.default_set; struct ctl_dir *core_parent ; struct ctl_table_header *links ; int err; if (head->set == root_set) return 0 ; core_parent = xlate_dir(root_set, head->parent); if (IS_ERR(core_parent)) return 0 ; if (get_links(core_parent, head, head->root)) return 0 ; core_parent->header.nreg++; spin_unlock(&sysctl_lock); links = new_links(core_parent, head); spin_lock(&sysctl_lock); err = -ENOMEM; if (!links) goto out; err = 0 ; if (get_links(core_parent, head, head->root)) { kfree(links); goto out; } err = insert_header(core_parent, links); if (err) kfree(links); out: drop_sysctl_table(&core_parent->header); return err; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static int insert_header (struct ctl_dir *dir, struct ctl_table_header *header) { const struct ctl_table *entry ; struct ctl_table_header *dir_h = &dir->header; int err; if (sysctl_is_perm_empty_ctl_header(dir_h)) return -EROFS; if (sysctl_is_perm_empty_ctl_header(header)) { if (!RB_EMPTY_ROOT(&dir->root)) return -EINVAL; sysctl_set_perm_empty_ctl_header(dir_h); } dir_h->nreg++; header->parent = dir; err = insert_links(header); if (err) goto fail_links; list_for_each_table_entry(entry, header) { err = insert_entry(header, entry); if (err) goto fail; } return 0 ; fail: erase_header(header); put_links(header); fail_links: if (header->ctl_table == sysctl_mount_point) sysctl_clear_perm_empty_ctl_header(dir_h); header->parent = NULL ; drop_sysctl_table(dir_h); return err; }
sysctl_check_table: 校验Sysctl表定义的正确性 此函数是Linux sysctl子系统的一个内部辅助函数, 它不执行任何注册操作, 而是扮演一个至关重要的**”校验器”或”静态分析器”**的角色。它的核心原理是: 在将一个由驱动程序定义的ctl_table结构体数组正式注册到/proc/sys之前, 对其进行一系列的健全性检查(sanity check), 以捕捉常见的编程错误, 从而防止这些错误在运行时引发内核崩溃或未定义行为。
这是一种典型的防御性编程实践, 它极大地增强了内核的健壮性。当开发者在自己的驱动中定义一个新的内核可调参数时, 这个函数就像一个”语法警察”, 确保其定义符合sysctl框架的基本要求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 static int sysctl_check_table (const char *path, struct ctl_table_header *header) { const struct ctl_table *entry ; int err = 0 ; list_for_each_entry(entry, header) { if (!entry->procname) err |= sysctl_err(path, entry, "procname is null" ); if ((entry->proc_handler == proc_dostring) || (entry->proc_handler == proc_dobool) || (entry->proc_handler == proc_dointvec) || (entry->proc_handler == proc_douintvec) || (entry->proc_handler == proc_douintvec_minmax) || (entry->proc_handler == proc_dointvec_minmax) || (entry->proc_handler == proc_dou8vec_minmax) || (entry->proc_handler == proc_dointvec_jiffies) || (entry->proc_handler == proc_dointvec_userhz_jiffies) || (entry->proc_handler == proc_dointvec_ms_jiffies) || (entry->proc_handler == proc_doulongvec_minmax) || (entry->proc_handler == proc_doulongvec_ms_jiffies_minmax)) { if (!entry->data) err |= sysctl_err(path, entry, "No data" ); if (!entry->maxlen) err |= sysctl_err(path, entry, "No maxlen" ); else err |= sysctl_check_table_array(path, entry); } if (!entry->proc_handler) err |= sysctl_err(path, entry, "No proc_handler" ); if ((entry->mode & (S_IRUGO|S_IWUGO)) != entry->mode) err |= sysctl_err(path, entry, "bogus .mode 0%o" , entry->mode); } return err; }
Sysctl 表注册: 在/proc/sys中创建内核参数文件 此代码片段展示了Linux内核sysctl子系统的核心注册机制。sysctl是内核提供的一种标准接口, 允许用户空间通过/proc/sys/下的虚拟文件系统在运行时读取和修改内核的可调参数 。
这段代码的根本原理是: 它提供了一套API, 能够将驱动程序中定义的一个C语言数据结构(struct ctl_table数组)动态地翻译并注册成/proc/sys/目录下的一个文件和目录层次结构。 这一过程涉及内存分配、目录创建、并发访问控制以及将文件操作(读/写)链接到驱动程序提供的特定处理函数上。
register_sysctl_sz: 便捷的API封装这是一个暴露给内核其他模块使用的标准API函数。它是一个简单的封装, 目的是为了方便驱动开发者使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 struct ctl_table_header *register_sysctl_sz (const char *path, const struct ctl_table *table, size_t table_size) { return __register_sysctl_table(&sysctl_table_root.default_set, path, table, table_size); } EXPORT_SYMBOL(register_sysctl_sz); struct ctl_table_header *register_sysctl_mount_point (const char *path) { return register_sysctl_sz(path, sysctl_mount_point, 0 ); } EXPORT_SYMBOL(register_sysctl_mount_point);
__register_sysctl_table: 核心实现这是实际执行所有注册工作的核心函数。它的执行流程是一个精心设计的、保证了并发安全和错误恢复的序列。
在STM32H750 (ARMv7M, 单核)上的情况: 这段代码是完全独立于硬件体系结构 的内核核心代码, 其逻辑和原理在STM32H750上与在任何其他平台上完全相同。
用途 : 即使在嵌入式系统中, sysctl也极其有用。它提供了一个无需重新编译固件就能在运行时调试和调整 内核行为的强大方法。例如, 一个传感器驱动可以通过/proc/sys/dev/sensor/polling_interval文件暴露其轮询间隔, 允许工程师在现场进行动态调整。
并发控制 : spin_lock(&sysctl_lock)在单核系统上仍然至关重要。如果内核是抢占式的(CONFIG_PREEMPT), 一个正在修改全局sysctl树的任务可能会被另一个也想访问该树的任务抢占。spin_lock通过在临界区内禁用内核抢占, 来防止这种数据竞争, 确保了sysctl树数据结构的完整性。
“解锁-操作-重锁”模式 : sysctl_mkdir_p函数在创建目录时可能需要分配内存, 这是一个可能会导致当前任务睡眠的操作。在持有自旋锁时睡眠是绝对禁止的, 会导致系统死锁 。因此, 代码采用了标准的”解锁->执行可能睡眠的操作->重新锁定”模式, 这是编写健壮内核代码的典范。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 struct ctl_table_header *__register_sysctl_table ( struct ctl_table_set *set , const char *path , const struct ctl_table *table , size_t table_size ) { struct ctl_table_root *root = set ->dir.header.root; struct ctl_table_header *header ; struct ctl_dir *dir ; struct ctl_node *node ; header = kzalloc(sizeof (struct ctl_table_header) + sizeof (struct ctl_node)*table_size, GFP_KERNEL_ACCOUNT); if (!header) return NULL ; node = (struct ctl_node *)(header + 1 ); init_header(header, root, set , node, table, table_size); if (sysctl_check_table(path, header)) goto fail; spin_lock(&sysctl_lock); dir = &set ->dir; dir->header.nreg++; spin_unlock(&sysctl_lock); dir = sysctl_mkdir_p(dir, path); if (IS_ERR(dir)) goto fail; spin_lock(&sysctl_lock); if (insert_header(dir, header)) goto fail_put_dir_locked; drop_sysctl_table(&dir->header); spin_unlock(&sysctl_lock); return header; fail_put_dir_locked: drop_sysctl_table(&dir->header); spin_unlock(&sysctl_lock); fail: kfree(header); return NULL ; }
__register_sysctl_init 将 sysctl 表注册到路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 void __init __register_sysctl_init(const char *path, const struct ctl_table *table, const char *table_name, size_t table_size) { struct ctl_table_header *hdr = register_sysctl_sz(path, table, table_size); if (unlikely(!hdr)) { pr_err("failed when register_sysctl_sz %s to %s\n" , table_name, path); return ; } kmemleak_not_leak(hdr); } #define register_sysctl_init(path, table) \ __register_sysctl_init(path, table, #table, ARRAY_SIZE(table))
proc_sys_init 初始化 proc 文件系统的 sysctl 目录 1 2 3 4 5 6 7 8 9 10 11 int __init proc_sys_init (void ) { struct proc_dir_entry *proc_sys_root ; proc_sys_root = proc_mkdir("sys" , NULL ); proc_sys_root->proc_iops = &proc_sys_dir_operations; proc_sys_root->proc_dir_ops = &proc_sys_dir_file_operations; proc_sys_root->nlink = 0 ; return sysctl_init_bases(); }
setup_sysctl_set: 初始化一个Sysctl”集合” 此函数是Linux内核sysctl子系统中一个非常底层的初始化辅助函数。它的核心作用是为一个sysctl“集合”(ctl_table_set)数据结构进行标准的、模板化的初始化 。
一个sysctl“集合”是sysctl框架为了支持**用户命名空间(User Namespace)**而引入的一个关键抽象。它允许多个独立的、可能包含同名文件的sysctl树并存, 而内核可以根据当前进程的上下文(即它属于哪个用户命名空间)来决定向其展示哪一个”集合”的内容。
此函数的原理可以理解为为一个新的sysctl集合对象赋予”生命”和”身份” :
清零 : memset(set, 0, sizeof(*set)); 确保了结构体的所有成员都被设置为一个已知的、干净的初始状态(零或NULL)。这是一种良好的编程实践, 可以防止未初始化变量导致的随机错误。
设置可见性规则 : set->is_seen = is_seen; 这一步是为该集合赋予”身份”的核心。它将一个由调用者提供的is_seen函数指针保存到集合中。这个函数定义了**”谁能看见我”**的规则。当内核需要决定是否向某个进程展示这个集合的内容时, 就会调用这个函数。
建立根目录 : init_header(&set->dir.header, ...) 这一步为该集合创建了一个逻辑上的”根目录”。它将该集合与一个ctl_table_root关联起来, 这个root定义了整个sysctl树的通用行为(例如, 如何查找、如何检查权限)。同时, 它使用一个全局的root_table作为该集合的初始内容, 确保了即使在没有任何其他条目注册之前, 该集合也是一个结构完整的、有效的实体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 void setup_sysctl_set (struct ctl_table_set *set , struct ctl_table_root *root, int (*is_seen)(struct ctl_table_set *)) { memset (set , 0 , sizeof (*set )); set ->is_seen = is_seen; init_header(&set ->dir.header, root, set , NULL , root_table, 1 ); }
kernel/sysctl.c proc_dostring: 内核 sysctl 字符串处理的标准实现 本代码片段是 Linux 内核 sysctl 框架的核心部分,提供了处理字符串类型 sysctl 条目的标准、通用处理函数 proc_dostring。其核心功能是安全地在内核空间的字符串缓冲区(由 ctl_table 指定)和用户空间的缓冲区之间进行双向数据拷贝,并实现了对文件读写位置(ppos)的精细控制,同时引入了一套用于规范化写操作行为的策略机制。
实现原理分析 该实现将复杂的 VFS 文件操作抽象为一个通用的处理函数,其原理围绕着对读写路径的区分处理、对文件位置指针的策略性应用以及用户交互的健壮性设计。
写操作策略 (proc_first_pos_non_zero_ignore) :
内核引入了一个全局配置 sysctl_writes_strict,用于定义当用户尝试从非零位置(即非文件开头)写入 sysctl 文件时的行为。这旨在纠正一个历史遗留问题:用户可能无意中通过 echo "val" >> /proc/sys/... 这样的追加操作来修改配置,导致非预期的结果。
proc_first_pos_non_zero_ignore 函数是这个策略的执行者。
SYSCTL_WRITES_STRICT (严格模式) : 如果 ppos 非零,函数返回 true。这意味着调用者(通常是处理数字的 sysctl handler)应该拒绝这次写操作。
SYSCTL_WRITES_WARN (警告模式) : 如果 ppos 非零,函数调用 warn_sysctl_write 打印一条一次性的内核警告,然后返回 false,允许写操作继续。
默认模式 : 行为同警告模式,但不会打印警告。
proc_dostring 本身虽然调用了此函数(主要为了触发警告),但其内部实现 _proc_do_string 选择了自己的方式来处理 ppos,对于字符串类型,它倾向于忽略非零的写位置,以保证健壮性。
核心读写逻辑 (_proc_do_string) :
写路径 (write = 1) : a. 位置处理 : 在严格模式下,它会检查 ppos 是否超出现有字符串的末尾,如果是则直接返回,防止追加。否则,它将从 ppos 指定的位置开始覆盖。在非严格模式下,它会强制忽略 ppos,总是从缓冲区的起始位置 (len = 0) 开始写,确保每次写操作都是一次完全的覆盖。 b. 数据拷贝 : 它从用户缓冲区逐字节地拷贝数据到内核缓冲区,直到遇到用户输入的 \0 或 \n,或者达到用户指定的长度或内核缓冲区的最大长度。这种设计使得用户通过 echo 命令写入时,最后的换行符不会被计入配置值。 c. 安全终止 : 拷贝结束后,它总是在内核缓冲区的末尾写入一个 \0,确保内核中的字符串始终是有效的 C 字符串。
读路径 (write = 0) : a. 位置处理 : 它正确地实现了对 ppos 的支持,允许用户从文件的任意位置开始读取。如果 ppos 已经超出了字符串的实际长度,它会返回 0 字节,这符合标准的文件读写行为。 b. 数据拷贝 : 它使用 memcpy 进行高效的块拷贝,将内核缓冲区中从 ppos 开始的数据拷贝到用户缓冲区。 c. 用户体验优化 : 如果拷贝的数据没有填满用户的缓冲区(len < *lenp),它会在数据的末尾追加一个 \n 换行符。这使得用户在使用 cat /proc/sys/... 等命令时,能够得到格式美观、以换行符结尾的输出,体验与读取普通文本文件完全一致。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 static int _proc_do_string(char *data, int maxlen, int write, char *buffer, size_t *lenp, loff_t *ppos) { size_t len; char c, *p; if (!data || !maxlen || !*lenp) { *lenp = 0 ; return 0 ; } if (write) { if (sysctl_writes_strict == SYSCTL_WRITES_STRICT) { len = strlen (data); if (len > maxlen - 1 ) len = maxlen - 1 ; if (*ppos > len) return 0 ; len = *ppos; } else { len = 0 ; } *ppos += *lenp; p = buffer; while ((p - buffer) < *lenp && len < maxlen - 1 ) { c = *(p++); if (c == 0 || c == '\n' ) break ; data[len++] = c; } data[len] = 0 ; } else { len = strlen (data); if (len > maxlen) len = maxlen; if (*ppos > len) { *lenp = 0 ; return 0 ; } data += *ppos; len -= *ppos; if (len > *lenp) len = *lenp; if (len) memcpy (buffer, data, len); if (len < *lenp) { buffer[len] = '\n' ; len++; } *lenp = len; *ppos += len; } return 0 ; } static void warn_sysctl_write (const struct ctl_table *table) { pr_warn_once("%s wrote to %s when file position was not 0!\n" "This will not be supported in the future. To silence this\n" "warning, set kernel.sysctl_writes_strict = -1\n" , current->comm, table->procname); } static bool proc_first_pos_non_zero_ignore (loff_t *ppos, const struct ctl_table *table) { if (!*ppos) return false ; switch (sysctl_writes_strict) { case SYSCTL_WRITES_STRICT: return true ; case SYSCTL_WRITES_WARN: warn_sysctl_write(table); return false ; default : return false ; } } int proc_dostring (const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { if (write) proc_first_pos_non_zero_ignore(ppos, table); return _proc_do_string(table->data, table->maxlen, write, buffer, lenp, ppos); }