大家好,欢迎来到IT知识分享网。
我们都知道linux下用户态与内核态通信的方法有
- procfs(/proc)
- sysctl(/proc/sys)
- sysfs(/sys)
- netlink 套接口
- 内核启动参数
- 模块参数
而这些方法是如何实现,又是怎样的一个原理,接下来就让我们来了解下。
一、什么是systcl
sysctl是一个用于运行时配置内核参数的命令,这些内核参数位于/proc/sys目录下。sysctl配置与显示在/proc/sys目录中的内核参数.可以用sysctl来设置或重新设置联网等功能,如IP转发、IP碎片去除以及源路由检查等。用户只需要编辑/etc/sysctl.conf文件,即可手工或自动执行由sysctl控制的功能。
1、常见用法
首先sysctl命令是系统开源软件包提供的,我们目前主要发行版的的linux系统都默认预装了procps包,提供了sysctl命令供用户配置sysctl实现的内核参数。
tangm@tangm-PC:~$ which sysctl /sbin/sysctl tangm@tangm-PC:~$ apt-file search /sbin/sysctl procps: /sbin/sysctl
这个命令我们常用的方法用
1)列出所有的变量并查看
sysctl -a | less
2)修改某变量的值
sysctl -w 变量名=变量值 #sysctl -w vm.max_map_count=
3)读一个指定的变量,例如 kernel.msgmnb:
[tangm@tangm] $ sysctl kernel.msgmnb kern.maxproc: 65536
要设置一个指定的变量,直接用 variable=value 这样的语法:
[tangm@tangm]$ sudo sysctl kernel.msgmnb=1024 kernel.msgmnb: 1024
可以使用sysctl修改系统变量,也可以通过编辑sysctl.conf文件来修改系统变量。
sysctl 变量的设置通常是字符串、数字或者布尔型。 (布尔型用 1 来表示’yes’,用 0 来表示’no’)。
sysctl -w kernel.sysrq=0 sysctl -w kernel.core_uses_pid=1 sysctl -w net.ipv4.conf.default.accept_redirects=0 sysctl -w net.ipv4.conf.default.accept_source_route=0 sysctl -w net.ipv4.conf.default.rp_filter=1 sysctl -w net.ipv4.tcp_syncookies=1 sysctl -w net.ipv4.tcp_max_syn_backlog=2048 sysctl -w net.ipv4.tcp_fin_timeout=30 sysctl -w net.ipv4.tcp_synack_retries=2 sysctl -w net.ipv4.tcp_keepalive_time=3600 sysctl -w net.ipv4.tcp_window_scaling=1 sysctl -w net.ipv4.tcp_sack=1
除此之外,我们还可以通过文件的读写操作进行设置。
tangm@tangm-PC:~$ cat /proc/sys/kernel/sysrq 438 tangm@tangm-PC:~$ cat /proc/sys/net/ipv4/tcp_sack 1
二、sysctl的实现
1、sysctl实现流程
框架
2、内核中sysctl的代码实现
首先,在内核源码树中,sysctl的主体实现代码存放在fs/proc/目录下,它的目录情况如下:
tangm@tangm-PC:~/work/community/update-kernel/linux-next$ ls fs/proc/ array.c fd.h loadavg.c proc_tty.c uptime.c base.c generic.c Makefile root.c util.c bootconfig.c inode.c meminfo.c self.c version.c cmdline.c internal.h namespaces.c softirqs.c vmcore.c consoles.c interrupts.c nommu.c stat.c cpuinfo.c Kconfig page.c task_mmu.c devices.c kcore.c proc_net.c task_nommu.c fd.c kmsg.c proc_sysctl.c thread_self.c
sysctl实现的总入口在我们的fs/proc/proc_sysctl.c文件中,首先我们来了解一下sysctl实现中的主要数据结构和函数接口
tangm@tangm-PC:~$ cat include/linux/sysctl.h ... /* A sysctl table is an array of struct ctl_table: */ /* ctl_table结构体,是sysctl中最重要最常用的一个结构体 */ struct ctl_table { /* sysctl接口名称,最终我们在系统文件系统中能够看到的名字 */ const char *procname; /* Text ID for /proc/sys, or zero */ void *data; int maxlen; umode_t mode; /* 是否存在子ctl_table,不存在不需要复制,存在需要指向子ctl_table地址 *、 struct ctl_table *child; /* Deprecated */ /* systcl处理接口,处理当接口的值被用户重新设置后的一些操作 */ proc_handler *proc_handler; /* Callback for text formatting */ struct ctl_table_poll *poll; void *extra1; void *extra2; } __randomize_layout; ... /* struct ctl_path describes where in the hierarchy a table is added */ struct ctl_path { const char *procname; }; ... /* sysctl注册接口的实际核心总入口 */ struct ctl_table_header *__register_sysctl_table( struct ctl_table_set *set, const char *path, struct ctl_table *table); struct ctl_table_header *__register_sysctl_paths( struct ctl_table_set *set, const struct ctl_path *path, struct ctl_table *table); /* 提供给用户注册sysctl接口是调用的接口 *、 struct ctl_table_header *register_sysctl(const char *path, struct ctl_table *table); struct ctl_table_header *register_sysctl_table(struct ctl_table * table); struct ctl_table_header *register_sysctl_paths(const struct ctl_path *path, struct ctl_table *table); /* 注销释放接口 */ void unregister_sysctl_table(struct ctl_table_header * table); extern int sysctl_init_bases(void); extern void __register_sysctl_init(const char *path, struct ctl_table *table, const char *table_name); #define register_sysctl_init(path, table) __register_sysctl_init(path, table, #table) extern struct ctl_table_header *register_sysctl_mount_point(const char *path); ... #endif /* _LINUX_SYSCTL_H */
它提供了一个register_sysctl()方法,用于让用户注册自己要注册实现sysctl接口。用户怎么注册我们在后续详细说明。用户调用register_sysctl()就可将自己想要实现的systcl接口注册完成。
/ * register_sysctl - register a sysctl table * @path: The path to the directory the sysctl table is in. * sysctl接口的路径,具体存放在系统的那个位置 * @table: the table structure * 当前要注册的接口列表,即你需要注册那些接口 * * Register a sysctl table. @table should be a filled in ctl_table * array. A completely 0 filled entry terminates the table. * * See __register_sysctl_table for more details. */ struct ctl_table_header *register_sysctl(const char *path, struct ctl_table *table) { return __register_sysctl_table(&sysctl_table_root.default_set, path, table); } EXPORT_SYMBOL(register_sysctl);
注册sysctl的核心处理接口,这里包括计算ctl_table长度并申请内存空间,检查systcl接口目录是否合法和存在,插入sysctl接口到sysctl的总表中。
struct ctl_table_header *__register_sysctl_table( struct ctl_table_set *set, const char *path, struct ctl_table *table) { struct ctl_table_root *root = set->dir.header.root; struct ctl_table_header *header; struct ctl_dir *dir; struct ctl_table *entry; struct ctl_node *node; int nr_entries = 0; //遍历ctl_table表,计算ctl_table长度(当前ctl_table已空结尾,即'{ }'结尾) list_for_each_table_entry(entry, table) nr_entries++; //申请内存空间 header = kzalloc(sizeof(struct ctl_table_header) + sizeof(struct ctl_node)*nr_entries, GFP_KERNEL_ACCOUNT); if (!header) return NULL; node = (struct ctl_node *)(header + 1); init_header(header, root, set, node, table); if (sysctl_check_table(path, table)) goto fail; spin_lock(&sysctl_lock); dir = &set->dir; /* Reference moved down the directory tree get_subdir */ dir->header.nreg++; spin_unlock(&sysctl_lock); //创建proc sysctl所在目录,所在目录为/proc/sys/下的目录,具体路径由用户指定 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); dump_stack(); return NULL; }
1
static int insert_header(struct ctl_dir *dir, struct ctl_table_header *header) { struct ctl_table *entry; int err; /* Is this a permanently empty directory? */ if (is_empty_dir(&dir->header)) return -EROFS; /* Am I creating a permanently empty directory? */ if (header->ctl_table == sysctl_mount_point) { if (!RB_EMPTY_ROOT(&dir->root)) return -EINVAL; set_empty_dir(dir); } dir->header.nreg++; header->parent = dir; err = insert_links(header); if (err) goto fail_links; list_for_each_table_entry(entry, header->ctl_table) { 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) clear_empty_dir(dir); header->parent = NULL; drop_sysctl_table(&dir->header); return err; }
一个ctl_table的初始化赋值,必须包含5个变量初始化,不能为空。这5个变量分别为:1)proc_handler; 2)data; 3)maxlen; 4)mode; 5)procname。
// 从下文代码可以看出,一个ctl_table的初始化赋值,必须包含4个变量初始化,不能为空 // 这四个变量分别为:1)proc_handler; 2)data; 3)maxlen; 4)mode. static int sysctl_check_table(const char *path, struct ctl_table *table) { struct ctl_table *entry; int err = 0; list_for_each_table_entry(entry, table) { if (entry->child) err |= sysctl_err(path, entry, "Not a file"); // ctl_table中的proc_handler变量不能为空,为空会返回"No proc_handler"错误 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)) { // proc_handler不为空的情况下,继续检查data和maxlen是否合法 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"); // 检查sysctl接口文件的权限 if ((entry->mode & (S_IRUGO|S_IWUGO)) != entry->mode) err |= sysctl_err(path, entry, "bogus .mode 0%o", entry->mode); } return err; }
static struct ctl_table_header *new_links(struct ctl_dir *dir, struct ctl_table *table, struct ctl_table_root *link_root) { struct ctl_table *link_table, *entry, *link; struct ctl_table_header *links; struct ctl_node *node; char *link_name; int nr_entries, name_bytes; name_bytes = 0; nr_entries = 0; list_for_each_table_entry(entry, table) { nr_entries++; name_bytes += strlen(entry->procname) + 1; } links = kzalloc(sizeof(struct ctl_table_header) + sizeof(struct ctl_node)*nr_entries + sizeof(struct ctl_table)*(nr_entries + 1) + name_bytes, GFP_KERNEL); if (!links) return NULL; node = (struct ctl_node *)(links + 1); link_table = (struct ctl_table *)(node + nr_entries); link_name = (char *)&link_table[nr_entries + 1]; link = link_table; list_for_each_table_entry(entry, table) { int len = strlen(entry->procname) + 1; memcpy(link_name, entry->procname, len); link->procname = link_name; link->mode = S_IFLNK|S_IRWXUGO; link->data = link_root; link_name += len; link++; } init_header(links, dir->header.root, dir->header.set, node, link_table); links->nreg = nr_entries; return links; }
查找当前的sysctl接口目录是否已经创建,如果未创建则创建
/* Find or the directory for the ctl_table. If one is not found create it. */ // 查找当前的sysctl接口目录是否已经创建,如果未创建则创建 static ctl_dir *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; /* * namelen ensures if name is "foo/bar/yay" only foo is * registered first. We traverse as if using mkdir -p and * return a ctl_dir for the last directory entry. */ dir = get_subdir(dir, name, namelen); if (IS_ERR(dir)) break; } return dir; }
三、实现一个自己的sysctl接口
1、已实现的sysctl接口代码
我们以reboot和poweroff为例,先看看已经实现的sysctl接口都做了些什么事情。以下代码是从kernel/reboot.c文件中截取的sysctl注册的核心数据和代码实现。
... static int C_A_D = 1; ... #define POWEROFF_CMD_PATH_LEN 256 static char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff"; ... //sysctl宏控制,开启宏的情况下才启用ctl_table,否则将进入一个while空循环,什么也不处理 #ifdef CONFIG_SYSCTL static struct ctl_table kern_reboot_table[] = { //在第二章节中的提到,必须注册初始化5个变量值,下方两个接口都是最少赋值情况下注册的 { // proc sysctl名称,对应/proc/sys/xxx/poweroff_cmd .procname = "poweroff_cmd", // data数据内容,可以是具体指令,可以是bool值或其他一些类型的数据值 .data = &poweroff_cmd, // 数据长度 .maxlen = POWEROFF_CMD_PATH_LEN, // 文件权限 .mode = 0644, // 接口的处理类型 .proc_handler = proc_dostring, }, { // proc sysctl名称,对应/proc/sys/xxx/ctrl-alt-del .procname = "ctrl-alt-del", // data数据内容,可以是具体指令,可以是bool值或其他一些类型的数据值 .data = &C_A_D, // 数据长度 .maxlen = sizeof(int), // 文件权限 .mode = 0644, // 接口的处理类型 .proc_handler = proc_dointvec, }, // 已空结束table { } }; // 需要注册的sysctl接口的初始化接口 static void __init kernel_reboot_sysctls_init(void) { // 调用proc_sysctl.c中实现的init接口,需要传入/proc/sys/下的相对路径 // 本次调用传入了"kernel"和kern_reboot_table两个变量 // 实际完成注册启动系统后,我们将看到/proc/sys/kernel/有以下两个文件 // $ cat /proc/sys/kernel/poweroff_cmd // /sbin/poweroff // $ cat /proc/sys/kernel/ctrl-alt-del // 0 register_sysctl_init("kernel", kern_reboot_table); } #else // CONFIG_SYSCTL未开启时,不做任何处理,空循环 #define kernel_reboot_sysctls_init() do { } while (0) #endif /* CONFIG_SYSCTL */ ... static int __init reboot_ksysfs_init(void) { struct kobject *reboot_kobj; int ret; reboot_kobj = kobject_create_and_add("reboot", kernel_kobj); if (!reboot_kobj) return -ENOMEM; ret = sysfs_create_group(reboot_kobj, &reboot_attr_group); if (ret) { kobject_put(reboot_kobj); return ret; } // 在reboot实现初始化等流程完成后,调用sysctl初始化注册函数 kernel_reboot_sysctls_init(); return 0; } // 调用initcall,让系统在启动时去调用接口,启动reboot的初始化过程 // 完成reboot的初始化后会注册systcl,并完成reboot_sysctl的初始化 late_initcall(reboot_ksysfs_init);
CONFIG_SYSCTL宏是作为sysctl实现的内核宏控,如果未开启则未提供sysctl机制,所有注册的sysctl接口都需要考虑未开启宏的处理方法(可以为空循环)。在注册ctl_table时,至少需要初始化5个结构体变量,如下:
{ }
}
所有的sysctl注册初始化,都通过register_sysctl_init(“sysdir1”, xxx_table)实现,在最终完成注册并启动系统后,我们将看到/proc/sys/sysdir1/my_sysctl文件,后续当这个文件发生变更时,将调用你所注册的handler处理函数进行处理。
因此,我们可以通过sysctl来控制/proc/sys/下的文件,进而和内核进行通信,实时的对内核运行过程中的某些配置进行配置和修改。
如果有哪里需要详细介绍或者想要了解讨论的地方,欢迎留言~
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/110518.html