在linux 内核中做开关变量的三种方法—— 利用proc 、sys文件系统,字符设备等与内核进行交互

在linux内核中经常会遇到这样的问题:需要在内核或者驱动中做一个开关变量,通过在用户态控制开关变量的值,从而让内核识别并处理不同的工作。常见的情况是,需

要做一个日志开关,用户可以控制内核是否打印出需要的日志。本文将介绍三种不同的方法,在内核中做开关变量。

 

在 阐述之前,我们假定用户通过cat和 echo 命令进行开关变量的读写。比如用户态开关文件位于 /home/value 。通过 cat /home/value 读取开关变量的值,通过

echo 1 > /home/value 类似的命令写入开关变量的值。之所以要用这两个命令,当然是为了方便,我们不希望再写一个程序,通过open-write-read这样的套路去读

写开关 文件。但是echo 这个程序和 write是有区别的,估计它的内部会连环调用write ,所以在内核中对应的write调用函数返回值需要特殊对待,具体在下面再说。

1 利用字符设备

在原有代码的基础上,添加一个字符设备,通过字符设备的read/write调用来设置开关变量的值。相对而言,该方法是比较麻烦的,但是的确可行。代码贴出如下:

 

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

 

#include <linux/types.h>

#include <linux/fs.h>

#include <linux/slab.h>

#include <linux/cdev.h>

 

#include <linux/kdev_t.h>

#include <asm/uaccess.h>

 

MODULE_LICENSE("GPL");

 

 

static char console_char = '0';

 

struct cdev * console_dev = NULL;

 

 

int console_open(struct inode *inode, struct file *filp)

{

        filp->private_data= console_dev;

    printk(KERN_ALERT"module is opened \n");

        return 0;

}

 

 

int console_close(struct inode *inode, struct file *filp)

{

   printk(KERN_ALERT "module is close \n");

        return 0;

}

 

/*驱动对应的读方法*/

static ssize_t console_read(struct file *filp, char__user *buf, size_t size, loff_t *ppos)

{

       

        put_user(console_char,buf);

        return 0;

}

/*驱动对应的写方法*/

static ssize_t console_write(struct file *filp, constchar __user *buf, size_t size, loff_t *ppos)

{

   get_user(console_char, buf);

   printk(KERN_ALERT "console_char is %c\n", console_char);

   switch(console_char)

    {

/*通过不同的用户输入字做不同的处理*/

    case 'd':

       printk(KERN_ALERT "dev major num is %d, minor num is %d\n",MAJOR(console_dev->dev), MINOR(console_dev->dev));

        break;

        default:

        break;

    }

/*由于是利用echo,而且只写一个变量,所以直接返回size,而不是实际的写入数*/

 return size;

}

 

 

 

struct file_operations console_fops = {

.owner = THIS_MODULE,

.open = console_open,

.release = console_close,

.write = console_write,

.read = console_read,

};

 

/*创建一个字符设备console*/

static struct cdev * alloc_cdev(void)

{

        int devno =0;

        struct cdev* dev = cdev_alloc();

        if(NULL !=dev)

        {

               dev->ops= &console_fops;

               dev->owner= THIS_MODULE;

               alloc_chrdev_region(&dev->dev,0, 1, "console");

               devno= MKDEV(MAJOR(dev->dev),(MINOR(dev->dev)));

              

               if(0> cdev_add(dev, devno, 1))

               {

                       printk(KERN_ALERT"cdev_add failed\n");

               }

 

               printk(KERN_ALERT"dev major num is %d, minor num is %d\n", MAJOR(dev->dev),MINOR(dev->dev));

        }

        return dev;

}

 

static void free_cdev(void)

{

        if(NULL !=console_dev)

        {

               cdev_del(console_dev);

               unregister_chrdev_region(MKDEV(MAJOR(console_dev->dev),0),1);

        }

}

 

static int console_init(void)

{

   printk(KERN_ALERT "console, init\n");

        console_dev= alloc_cdev();

 

    return 0;

}

 

static void console_exit(void)

{

        free_cdev();

   printk(KERN_ALERT "goodbye ,console quit\n");

}

 

 

module_init(console_init);

module_exit(console_exit);


 

完成后,编译出来,通过mkmond /dev/console  c  id1 id 2 来创建一个用户态节点文件,其中id1 id2 是console字符设备的主次设备号。需要特别说明的是,

内核的write调用方法,只写了一个字符,但是却直接返回 请求的size数量,这是因为我们调用echo  来写入一个字符,无论用户输入多少字符,我们都只写入一个字

符,而且骗echo我们已经全部写入了请求的写入数。比如我们执行echo 1234 > /dev/console ,实际上此时只写入1,其他的234 都作废。如果我们返回实际的写入数

,那么echo 会连续一直调用write函数,这并不是我们想要的。

运行字符设备的效果如下:

 

 

通过例子看出,用户态通过echo 写入的值已经在内核中生效,如果便相当于在内核中做了一个开关变量,而且可以通过用户态进行控制。

2 利用 proc 文件系统。

相对字符设备而言,proc 文件系统是比较简单的。但是一般proc文件系统都是只读,这里我们需要创建一个可读可写的proc文件,以此来控制内核中的开关变量。代码比

较简单,如下:

 

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/proc_fs.h>

#include <asm/uaccess.h>

 

MODULE_LICENSE("GPL");

 

char flag = '0';

 

 

static int read_proc_fun(char *buf, char **start, off_toffset, int count, int *eof, void *data)

{

    int n =sprintf(buf, "%c", flag);

   printk(KERN_ALERT "read flag is %c\n", flag);

    *eof = 1;

    return n;

}

static int write_proc_fun(struct file *filp, const char*buffer,unsigned long count,void *data)

{

    char c = '0';

    if(get_user(c,(char *)buffer))

    {

        return-EFAULT;

    }

    else

    {

        flag = c;

    }

    /*根据用户对flag的不同,进而设置不同的开关变量*/

   printk(KERN_ALERT "set flag is %c\n", flag);

    return count;/*为了使用echo ,此处不可返回1 个,否则会再次发生写动作。实际上我们只写了一个字符,并没有写多个*/

}

 

static void create_proc(void)

{

    structproc_dir_entry *entry = NULL;

    entry =create_proc_entry("dev_proc", 0644, NULL);

    if(entry)

    {

        entry->read_proc= read_proc_fun;

       entry->write_proc = write_proc_fun;

    }

}

 

static void del_proc(void)

{

   remove_proc_entry("dev_proc", NULL);

}

static int proc_init(void)

{

   printk(KERN_ALERT "proc, hello\n");

    create_proc();

    return 0;

}

 

static void proc_exit(void)

{

   printk(KERN_ALERT "goodbye ,proc \n");

    del_proc();

}

 

 

module_init(proc_init);

module_exit(proc_exit);

 


 

利用proc 文件系统相对是比较简单的,不知道大家注意没有,proc文件的读写看起来并不统一,因为读方法read_proc中的buf直接就是用户态的地址,可以用sprintf

进行写入;而写方法write_proc 却是内核态的地址,需要用get_user或者 copy_from_user之类的方法,这始终是不太妥。 由于历史原因,proc文件系统不推荐使

用。所以接下来我们使用第三种方法。

 

3 利用 sysfs 文件系统。

 

当然这里写的比较简单和直接,没有对 kobject 等设备模型做处理,有兴趣的可以自己看 linux 内核相关的书籍。代码如下:

 

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/kobject.h>

#include <linux/sysfs.h>

 

 

MODULE_LICENSE("GPL");

 

 

 

 

struct kobject * my_kobject = NULL;

struct attribute my_attr;

struct sysfs_ops my_sysfs_ops;

 

char flag = '0';

 

 

 

ssize_t sf_show(struct kobject *kobj, struct attribute*attr, char *buffer)

{

    int count =sprintf(buffer,"%c",flag);

    return count;

   

}

ssize_t sf_store(struct kobject *kobj, struct attribute*attr, const char *buffer, size_t size)

{

     flag =  buffer[0];

    printk(KERN_ALERT "flag is %c\n", flag);

     return size;

}

static void sysfs_release(struct kobject *kobj)

{

   printk(KERN_ALERT "sysfs_release\n");

}

 

static struct kobj_type dynamic_kobj_ktype = {

        .release       = sysfs_release,

        .sysfs_ops     = &my_sysfs_ops,

   //.default_attrs = (struct attribute **)attr_list, /*这个地方暂时不用写上,内核中说的不是太清楚*/

};

 

 

static void sysfs_create(void)

{

   

    my_kobject =kobject_create_and_add("test_sysfs", NULL);

    if(my_kobject)

    {

       printk(KERN_ALERT"my_kobject %p\n", my_kobject);

       my_attr.name= "my_sys_fs";

       my_attr.mode= S_IRUGO | S_IWUGO;

      my_attr.owner = NULL;

      

      my_sysfs_ops.show = sf_show;

      my_sysfs_ops.store = sf_store;

      

   

       my_kobject->ktype= &dynamic_kobj_ktype;

      sysfs_create_file(my_kobject, &my_attr);

      

    }

 

}

 

static int __init sysfile_init(void)

{

   printk(KERN_ALERT "sysfile, hello\n");

    sysfs_create();

    return 0;

}

 

static void __exit sysfile_exit(void)

{

   printk(KERN_ALERT "goodbye ,sysfile \n");

   

    if(NULL !=my_kobject)

    {

       sysfs_remove_file(my_kobject, &my_attr);

       kobject_del(my_kobject);

       kobject_put(my_kobject);

        my_kobject= NULL;

    }

}

 

module_init(sysfile_init);

module_exit(sysfile_exit);


利用sysfs文件系统有两点值得注意,第一: 相对 字符设备和proc文件系统的读写方法而言,sysfs 文件系统的读写空间地址都直接是用户态地址,不需要通过
copy_to_user和 copy_from_user这类的转换。第二 :上述结构dynamic_kobj_ktype中的default_attrs字符并没有使用,linux内核设计和实现的作者并没有给出详
细的原理和用例,该字段的用法显得比较含糊。通过查看linux的源码发现,源码中的该字段也并没有使用,所以我也暂时将该字段空着。对一个简单的字符开关而言,
这样处理是没有多大问题的。但是如果对于比较复杂的块设备,该字段的原理还需要进一步详细分析。做一件事情,如果不知道它的原理,始终还是不够心安。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值