使用sysctl
前面说到的内核模块参数是在加载模块时使用,而这次我们要说一种在模块加载后修改数据的方法,其是基于后期要说到的procfs而设计的,它是应用程序设置和获取运行时内核配置参数的一种方法,一般相应的参数对应/proc/sys目录,除了使用系统内置的sysctl工具操作,也可直接对/proc/sys目录下的结点直接操作。
对于sysctl会使用到如下函数来注册和释放:
1.注册sysctl
struct ctl_table_header *register_sysctl_table(struct ctl_table *table) ;
2.释放sysctl
void unregister_sysctl_table(struct ctl_table_header * header) ;
这两个函数在fs/proc/proc_sysctl.c中实现,在include/linux/sysctl.h中声明,从两个函数可以看到两者是通过ctl_table_header结构体连通的,该结构体不详说,接下来看看ctl_table这个结构体,在include/linux/sysctl.h有如下内容:
struct ctl_table
{
const char *procname; /* Text ID for /proc/sys, or zero */
void *data;
int maxlen;
umode_t mode;
struct ctl_table *child; /* Deprecated */
proc_handler *proc_handler; /* Callback for text formatting */
struct ctl_table_poll *poll;
void *extra1;
void *extra2;
};
其中,procname对应/proc/sys目录下显示的名称,data对应该节点在内核中的变量,maxlen为条目的最大长度(即当对应变量为字符串时,该字条串的长度,超过部分截掉),mode为在/proc/sys对应节点的访问权限(与《模块参数》里说到的八进制数一致,当该ctl_table的child不空时,该值为0555),child不为空则表示/proc/sys/$procname为目录,否则为文件节点(树状叶子),而proc_handler为该文件节点对应的处理回调函数指针,extra1和extra2是proc_handler的参数(在下面含有minmax的函数名对应的函数里会使用到),相应的函数可支持为proc_dostring()、 proc_dointvec()、proc_dointvec_jiffies()、proc_dointvec_userhz_jiffies()、proc_dointvec_minmax()、proc_doulongvec_ms_jiffies_minmax()、proc_doulongvec_minmax() ,这些在kernel/sysctl.c里定义,当然也可重构相应的处理函数,在等会的例子里可看到,poll暂时不使用,后期详细说明sysctl时再讨论,注意最后一个ctl_table数组成员要为空。
下面还是实例感受吧:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sysctl.h>
#define MAX_SLAM_STRING_SIZE 256
static int slam_int;
static char slam_string[MAX_SLAM_STRING_SIZE];
static int slam_int_callback(ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int rc;
int *data = table->data;
printk("Original data = %d\n", *data);
rc = proc_dointvec(table, write, buffer, lenp, ppos);
if (write)
printk("This is write operation,now data = %d\n", *data);
return rc;
}
static struct ctl_table slam_child[] = {
{
.procname = "slam_int",
.data = &slam_int,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = slam_int_callback,
},
{
.procname = "slam_string",
.data = slam_string,
.maxlen = MAX_SLAM_STRING_SIZE,
.mode = 0666,
.proc_handler = &proc_dostring,
},
{},
};
static struct ctl_table sysctl_ctl_table[] = {
{
.procname = "slam",
.mode = 0555,
.child = slam_child,
},
{},
};
static struct ctl_table_header * sysctl_header;
static __init int sysctl_example_init(void)
{
sysctl_header = register_sysctl_table(sysctl_ctl_table);
if (sysctl_header == NULL)
{
printk("register_sysctl_table failed!\n");
return -1;
}
printk("sysctl_example_init success!\n");
return 0;
}
static __exit void sysctl_example_exit(void)
{
unregister_sysctl_table(sysctl_header);
printk("%s success!\n",__func__);
}
module_init(sysctl_example_init);
module_exit(sysctl_example_exit);
相应的Makefile内容如下:
obj-m += sysctl_example.o
CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6
all:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
最终的源码文件目录树如下:
/home/xinu/xinu/linux_kernel_driver_l1/sysctl_example/
├── Makefile
└── sysctl_example.c
关于源码里sysctl_example_init函数注册sysctl时用到的sysctl_ctl_table,如果其不是一个数组,而是一个struct ctl_table结构体变量,则注册参数要改为&sysctl_ctl_table,那么相应的变量里就不存在table数组最后一个量为空的时候,那么等会作为child的table里的结点都会作为相应/proc/sys/slam目录下的子结点外,还会作为/proc/sys目录的子结点,不过源码里已改为正常的情况了,你也可以尝试下。 在加载驱动后,由于在代码里相应的变量均未赋值,故而会得到如下值:
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ cat /proc/sys/slam/slam_int
0
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ cat /proc/sys/slam/slam_string
即slam_int初始化为0,而slam_string初始化为空。那么接下来设置并查看设置后其值:
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ echo 1 > /proc/sys/slam/slam_int xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ cat /proc/sys/slam/slam_int
1
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ echo “hello” > /proc/sys/slam/slam_string xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ cat /proc/sys/slam/slam_string
hello
相应的dmesg如下:
[22406.958394] sysctl_example_init success!
[22418.675928] Original data = 0
[22418.675961] Original data = 0
[22576.226091] Original data = 0
[22576.226115] This is write operation,now data = 1
[22580.876721] Original data = 1
[22580.876751] Original data = 1
看来每次读都会关联到所有子结点?留个问题后期细说。从上面的操作并未发挥sysctl的魅力,与平常的procfs操作没区别,接下来我们使用sysctl的规则来读写这些变量:
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ sysctl slam.slam_int
slam.slam_int = 1
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ sysctl slam.slam_string
slam.slam_string = hello
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ sysctl -w slam.slam_int=4
slam.slam_int = 4
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ sysctl slam.slam_int
slam.slam_int = 4
xinu@slam:~/xinu/linux_kernel_driver_l1/sysctl_example$ cat /proc/sys/slam/slam_int
4
看出其规则来了吧!就是去掉/proc/sys,然后每一级当一个量,“/”更换为“.”,其中读与写区别就在-w参数了,但这样操作仅仅是临时的,那我们可以修改/etc/sysctl.conf文件,将要设定为每次启动系统后的新值添加到该文件中,有如下例:
slam.slam_int=4
但这句话生效的前提是这个KO在先加载或者其编译到内核里了,这个就不再说了,自己好好研究下吧!
好了,就先说到这了,有空使用sysctl -a看看系统里可支持的内核运行时变量有哪些吧!
参考网址:
http://www.embeddedlinux.org.cn/html/yingjianqudong/201304/17-2547.html
http://www.ibm.com/developerworks/cn/linux/l-kerns-usrs/index.html
http://m.blog.csdn.net/blog/iamonlyme/11180131
http://www.ug.it.usyd.edu.au/~vnik5287/sysctl.pdf