使用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
#include
#include

#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

看出其规则来了吧!就是去掉/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

你可能感兴趣的:(使用sysctl)