Linux操作系统sysctl机制的思想与实现

续《linux的netlink机制》:其实只要同时提供实现一个内核netlink的内核模块和一个定制用户策略的用户空间netlink程序,你就可以完全控制linux内核了,这里内核的netlink就是机制,而用户的netlink就是策略,正好一套,这样的机制是在太棒了,把自己的机制和策略用netlink随意往linux里面插,linux竟然成了一个舞台了,实际情况就是这样的。

linux中不乏用户空间与内核空间的通信的机制,比如netlink,procfs,sysctl,/dev下的设备驱动,当然还有更xx的系统调用。其实这些都是机制和策略的结合,内核实现的是机制,而用户实现的是策略,在代码表现上,一般都是一套代码,也就是一套程序,用户空间的策略控制程序和内核空间的机制代码逻辑,内核空间傻傻的为用户服务,它只知道来命令就做事而根本不管是什么命令以及这个命令达到了什么效果,这就是linux,机制和策略分离,很好很强大。前面分析了netlink,其实正如《linux的netlink机制》说的,netlink必须提供一套程序才会发挥作用,一个机制一个策略,那么sysctl当然也是这样的,在linux中有sysctl机制,它可以控制和改变内核中的变量,然后各个模块可以根据这些变量的值来采取不同的行为,这些变量就好像开关一样负责内核中一些重要机制的开关和微调。
在内核中sysctl机制的实现十分简单,和linux内核的大多数机制一样,采用先注册后使用的方式,只要你register了一个sysctl的实体,那么在用户空间你就可以控制你注册的实体变量了,然后在内核中你就可以用这些变量,根据这些变量的不同值来采取不同的动作,内核中的sysctl实现根本不涉及任何用户策略方面的东西,仅仅是注册,具体如何使用,那纯粹是用户设计该sysctl实体时要考虑的事情。下面就是一个例子:
static struct ctl_table zhaoya_test[] = {
{
.ctl_name = 1,
.procname = "value",
.data = &value,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = &proc_dointvec, //这个回调函数是为procfs提供了。
},
{
.ctl_name = 0
}
};
static struct ctl_table zhaoya_root = {
.ctl_name = CTL_CPU + 10,
.procname = "zhaoya",
.mode = 0555,
.child = zhaoya_test,
};
static int __init test_init(void)
{
register_sysctl_table(&zhaoya_root, 0);
return 0;
}
就这就完事了,当我用sysctl的-w命令来改写value变量的时候,proc_dointvec就会将我传入的新值赋给value,其实加载模块后你会发现,在/proc/sys下有了zhaoya目录,在这个目录下有了value文件,那么如果你读/proc/sys/zhaoya/value的话就会读到新值。sysctl的内核实现就是sys_sysctl,最终调用do_sysctl:
int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *oldlenp, void __user *newval, size_t newlen)
{
struct list_head *tmp;
if (nlen = CTL_MAXNAME)
return -ENOTDIR;
if (oldval) {
int old_len;
if (!oldlenp || get_user(old_len, oldlenp))
return -EFAULT;
}
tmp = &root_table_header.ctl_entry;
do {
struct ctl_table_header *head =
list_entry(tmp, struct ctl_table_header, ctl_entry);
void *context = NULL;
int error = parse_table(name, nlen, oldval, oldlenp, newval, newlen, head->ctl_table, &context);
if (context)
kfree(context);
if (error != -ENOTDIR)
return error;
tmp = tmp->next;
} while (tmp != &root_table_header.ctl_entry);
return -ENOTDIR;
}
static int parse_table(int __user *name, int nlen, void __user *oldval, size_t __user *oldlenp, void __user *newval, size_t newlen, ctl_table *table, void **context)
{
int n;
repeat:
...
for ( ; table->ctl_name; table++) {
if (n == table->ctl_name || table->ctl_name == CTL_ANY) {
int error;
if (table->child) {
if (ctl_perm(table, 001)) //测试权限,ctl_table中的mode字段就是权限字段,每一位都有解释。
return -EPERM;
...//有table->strategy的情况,我的例子没有,故忽略。
}
error = do_sysctl_strategy(table, name, nlen, oldval, oldlenp, newval, newlen, context);
return error;
}
}
return -ENOTDIR;
}
最后的do_sysctl_strategy实现了变量的替换,该函数中的ctl_perm很有意思,它就是利用ctl_table中的mode字段和sysctl中的操作字段来判断当前操作是否被允许的,其实也没有什么意思。
int do_sysctl_strategy (ctl_table *table, int __user *name, int nlen, void __user *oldval, size_t __user *oldlenp, void __user *newval, size_t newlen, void **context)
{
int op = 0, rc;
size_t len;
if (oldval)
op |= 004;
if (newval)
op |= 002;
if (ctl_perm(table, op))
return -EPERM;
...
if (table->data && table->maxlen) {
if (oldval && oldlenp) {
if (get_user(len, oldlenp))
return -EFAULT;
if (len) {
if (len > table->maxlen)
len = table->maxlen;
if(copy_to_user(oldval, table->data, len))
return -EFAULT;
if(put_user(len, oldlenp))
return -EFAULT;
}
}
if (newval && newlen) {
len = newlen;
if (len > table->maxlen)
len = table->maxlen;
if(copy_from_user(table->data, newval, len))
return -EFAULT;
}
}
return 0;
}
这就是sysctl的几乎全部了,十分简单,为何如此简单,正是因为linux内核没有那么多策略性的东西,它只实现机制。记住,只要是机制和策略分离的东西,你要想提供一个功能你就必须提供一套程序,一个来实现机制,另一个实现策略,正如sysctl和netlink一样。

你可能感兴趣的:(linux)