sysfs--初窥门径

来源:互联网 发布:qq炫舞头像源码 编辑:程序博客网 时间:2024/05/01 07:28

好文:http://www.ibm.com/developerworks/cn/linux/l-cn-sysfs/
先来看下怎么用sysfs,再去分析原理。以2.6.30内核中led的使用为例,因为我常用的3.2.0感觉不如这个版本清楚
涉及到文件 class.c led-class.c leds-s3c24xx.c

先来看用户空间怎么控制Led灯
点灯:echo 1 > /sys/class/leds/led0/brightness
灭灯:echo 0 > /sys/class/leds/led0/brightness
下面我们就来看看sysfs是如何一步步产生了这些操作结点的

/sys目录我们先略过,这肯定是sysfs核心产生的。我们分析sys里边的内容如何产生,
过程中凡是涉及到sysfs核心的东西都先略过,后期再分析。
先来看一下sys目录:# ls /sys -F 加了-F选项会显示出文件类型
block/ class/ devices/ fs/ module/
bus/ dev/ firmware/ kernel/ power/
对于各个目录的作用,在《深入理解linux内核》设备驱动模型一节和上面网址中都有介绍。
现在按照上面的例子,先来看看class目录的产生。
1.在…/driver/base/class.c中。

classes_init(void)    class_kset = kset_create_and_add("class", NULL, NULL);

看一下kset_create_and_add的原型:
static struct kset *kset_create(const char *name, struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj)
第一个是名字,第二个略过不说,第三个是父类容器
从形参看,第三个参数是NULL,所以class是没有父类容器的,所以class目录会出现在/sys的底层目录
好了,/sys/class出现了,我们不再往下深入,只是理清大体流程。

2.那么如何在class目下下产生其它节点内?
熟悉Linux分层设计思想的肯定一下就能想到,既然是在class目录下产生节点,肯定是由class核心来统一管理了。
没错,就是这样,class提供了一个注册函数__class_create供外部使用,这个函数又被device.h重新封装为class_create
class_create这个函数相信只要写过字符设备的人都很熟悉,在调用device_create之前通常会先调用class_create产生类。
好了,接下来我们就看看内核自带的Led驱动是怎么做的。

leds_init(void)    leds_class = class_create(THIS_MODULE, "leds");  //这个调用,就在/sys/class下产生了leds目录    leds_class->suspend = led_suspend;    leds_class->resume = led_resume;    

这里要多提一点,如果我们平时写个led驱动会怎么做呢?通常大部分人会直接在同一个文件中调用
class_create和device_create,或者注册成为misc设备。这样并没有什么不妥,但是内核的做法更
符合软件分层设计思想。把led共性抽出来,注册为一个leds类,然后由led_class.c统一管理。
然后所有的平台相关的设备再通过led_class.c提供的接口把具体的设备注册到leds目录里边。
是不是一层层的非常清楚明了。这就是内核魅力所在。到这里,就产生了/sys/class/leds目录。

led-class.c就是整个led子系统的核心了,下面我们就从led_classdev_register来分析,
详细看下led类是如何利用sysfs实现相应功能的。

led_classdev_register(struct device *parent, struct led_classdev *led_cdev)  //...drivers/leds/led-class.c    led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name);    device_create_file(led_cdev->dev, &dev_attr_brightness);    device_create_file(led_cdev->dev, &dev_attr_max_brightness);    device_create_file(led_cdev->dev, &dev_attr_trigger);

形参有两个,一个是parent,一个是led_classdev结构体指针
parent通常指向platform平台设备的私有数据,这个可以先不用管,后边用处也不大
第二个参数就是Led核心提供的led设备抽象,可以参考下文来了解
http://blog.csdn.net/yuanlulu/article/details/6438841

然后就是我们熟悉的device_create函数了,都知道是在/dev目录下生成相应的设备结点。
而且会让led_cdev->dev和led核心维护的leds_class建立联系,为后边的device_create_file做准备。
接下来就是最重要的三个device_create_file调用,这个函数是sysfs核心提供的,通过调用它,
就可以生成设备的属性,也就是在用户空间操作的属性。
比如上面的device_create_file(led_cdev->dev, &dev_attr_brightness)调用
就会在/sys/class/leds/led0目录下产生brightness属性,而我们用
echo 1 > /sys/class/leds/led0/brightness命令即可实现点灯
cat /sys/class/leds/led0/brightness就可以查看灯的状态

sysfs具体做了什么暂时不分析,但是大体能够猜到,调用device_create_file函数就会导致
sysfs核心为这个属性建立相应的目录,并且绑定相应的读写方法。
那么对于led的操作方法是什么呢?在led-class.c中搜dev_attr_brightness你会发现是搜不到的,
这是因为它被定义成了一个宏函数DEVICE_ATTR,这也是sysfs核心提供的。代码中有这么一句:
static DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store);
把这个宏展开就是:

static struct device_attribute dev_attr_brightness = { \    .attr = {.brightness = __stringify(brightness), .mode = 0644 }, \    .show   = led_brightness_show,                  \    .store  = led_brightness_store,                 \}   

dev_attr_brightness就出现了,而show和store方法也被绑定了。
当调用cat …led0/brightness命令时候,就会调用led_brightness_show函数
当调用echo 1 > …led0/brightness命令时候,就会调用led_brightness_store函数

我们看一下led_brightness_store的实现

led_brightness_store(const char *buf, size_t size)    long state = simple_strtoul(buf, &after, 10);  //把buf的字符串转换为long型    if (state == LED_OFF)        led_set_brightness(led_cdev, state);            led_cdev->brightness_set(led_cdev, value);

函数原型略去了两个形参,buf就是echo的内容了,sysfs已经帮我们从用户空间拷贝到了内核空间,
所以可以直接使用,首先把字符串转为数字,然后判断是灭灯还是亮灯,最后调用的是led_cdev->brightness_set
熟悉驱动框架的都知道,这个肯定就是调用到具体的平台相关led驱动中的函数了,即通过led-class.c
提供的led_classdev_register接口注册进来的驱动提供的具体函数了。以2240为例就是

s3c24xx_led_probe    cdev.brightness_set = s3c24xx_led_set;    led_classdev_register(&dev->dev, &led->cdev);

那么我们应该怎么给驱动添加sysfs属性的支持呢?下面就以蜂鸣器操作为例展示一下:
这是linux3.2.0中关于AM335X平台内核提供的蜂鸣器驱动源码–am335x_buzzer.c

/*    am335x_buzzer.c  gpio driver ,misc device     PINS:        GPIO1_28*/#include <linux/init.h>#include <linux/module.h>#include <linux/miscdevice.h>#include <linux/moduleparam.h>#include <linux/platform_device.h>#include <linux/fs.h>#include <linux/gpio.h>#define  DEV_NAME  "buzzer" #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))#define GPIO_BUZZER    GPIO_TO_PIN(1, 28)#define BEEP_ON         0#define BEEP_OFF        1static int gpio_open(struct inode *inode, struct file *file){           return 0;}static int gpio_release(struct inode *inode, struct file *file){    return 0;}static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){    switch(cmd)    {        case BEEP_OFF:            gpio_set_value(GPIO_BUZZER, BEEP_OFF);            break;        case BEEP_ON:            gpio_set_value(GPIO_BUZZER, BEEP_ON);               break;        default:            return -ENOTTY;    }    return 0;}static struct file_operations dev_ope = {    .owner= THIS_MODULE,    .unlocked_ioctl = gpio_ioctl,    .open           = gpio_open,    .release        = gpio_release,};static struct miscdevice gpio_misc = {    .minor = MISC_DYNAMIC_MINOR,    .name = DEV_NAME,    .fops = &dev_ope,};static int __devinit gpio_probe(struct platform_device *pdev){    int ret;    int result;    ret = misc_register(&gpio_misc);    if (ret)    {        printk(KERN_ERR"misc gpio failed\n");        return ret;    }    result = gpio_request(GPIO_BUZZER, "gpio_buzzer");      if (result != 0)    {        printk("buzzer_request pin failed!\n");    }       gpio_direction_output(GPIO_BUZZER, BEEP_OFF);     printk(KERN_INFO"am335x buzzer driver\n");    return 0;}static int __devexit gpio_remove(struct platform_device *pdev){    misc_deregister(&gpio_misc);    gpio_free(GPIO_BUZZER);    printk(KERN_INFO "unregistered gpio_buzzer device\n ");    return 0;}static struct platform_driver gpio_device_driver = {    .probe = gpio_probe,    .remove = gpio_remove,    .driver = {        .name = DEV_NAME,        .owner = THIS_MODULE,    }};static int __init gpio_init(void){    return platform_driver_register(&gpio_device_driver);}static void __exit gpio_exit(void){    platform_driver_unregister(&gpio_device_driver);}module_init(gpio_init);module_exit(gpio_exit);MODULE_DESCRIPTION("GPIO_BUZZER Drivers"); MODULE_LICENSE("GPL");

它是以platform总线实现的,在probe函数中又调用了misc_register把它注册为misc类设备
所以此时加载驱动会在/sys/class/misc中看到buzzer节点,但是ls一下只有下面几个属性
$ls /sys/class/misc/buzzer
dev power subsystem uevent

这四个属性是所有设备共有的属性,由sysfs核心产生,此时我们想提供一个state属性,
可用通过cat …/buzzer/state查看蜂鸣器状态,通过echo 0 > …/buzzer/state和
echo 0 > …/buzzer/state实现蜂鸣器响和不响操作。
我们需要做的有下面几件事:
1.调用 DEVICE_ATTR 宏声明state属性名称、读写权限及操作函数
2.实现操作函数buzzer_state_show和buzzer_state_set
3.调用device_create_file接口增加属性

所以代码就改为(只列出增加部分和修改部分,另增加了一个example属性):

static ssize_t buzzer_state_show(struct device *dev,         struct device_attribute *attr, char *buf){    return sprintf(buf, "%u\n", gpio_get_value(GPIO_BUZZER));}static ssize_t buzzer_state_set(struct device *dev,        struct device_attribute *attr, const char *buf, size_t size){    char *after;    unsigned long state = simple_strtoul(buf, &after, 1);    switch(state)    {        case BEEP_OFF:            gpio_set_value(GPIO_BUZZER, BEEP_OFF);            break;        case BEEP_ON:            gpio_set_value(GPIO_BUZZER, BEEP_ON);               break;        default:            return -ENOTTY;    }    return size;}static DEVICE_ATTR(state, 0644, buzzer_state_show, buzzer_state_set);static ssize_t buzzer_example_show(struct device *dev,         struct device_attribute *attr, char *buf){    return sprintf(buf, "\n%s\n", "hello I am sysfs example");}static ssize_t buzzer_example_set(struct device *dev,        struct device_attribute *attr, const char *buf, size_t size){    printk("%s\n", buf);    return size;}static DEVICE_ATTR(example, 0644, buzzer_example_show, buzzer_example_set);static int __devinit gpio_probe(struct platform_device *pdev){    int ret;    int result;    ret = misc_register(&gpio_misc);    if (ret)    {        printk(KERN_ERR"misc gpio failed\n");        return ret;    }    result = gpio_request(GPIO_BUZZER, "gpio_buzzer");      if (result != 0)    {        printk("buzzer_request pin failed!\n");    }       gpio_direction_output(GPIO_BUZZER, BEEP_OFF);     ret = device_create_file(gpio_misc.this_device, &dev_attr_state);    if(ret)        goto err_out;    ret = device_create_file(gpio_misc.this_device, &dev_attr_example);    if(ret)        goto err_example;       printk(KERN_INFO"am335x buzzer driver\n");    return 0;err_example:    device_remove_file(gpio_misc.this_device, &dev_attr_state);err_out:    gpio_free(GPIO_BUZZER);    misc_deregister(&gpio_misc);    return ret;}

做了修改后加载驱动,再查看buzzer的属性时候就可以看到增加了state属性
$ ls /sys/class/misc/buzzer
dev example power state subsystem uevent

然后进行测试:
$ cat example
hello I am sysfs example

$ echo 123456 > example
[ 566.408172] 123456 //驱动中把用户输入简单的通过printk打印出来了

$ cat state
1 //即蜂鸣器的管脚是高电平

echo 0 > state 就会导致蜂鸣器鸣叫

至此,就把sysfs应用简单分析了一下,并简单修改了buzzer的驱动验证了一下。
至于sysfs的核心实现,后边有时间再写吧。最后为整个子系统分下层


用户空间:
使用shell命令操作sys下节点属性可以直接控制硬件。
比如echo 1 > /sys/class/leds/led0/brightness


内核空间:


sysfs核心:产生/sys目录


class.c:产生/sys/class目录


led-class.c:产生/sys/class/leds目录及相关属性


leds-s3c24xx.c:提供属性操作具体方法


0 0
原创粉丝点击