sysfs--初窥门径

好文: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        1

static 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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浓咖啡jy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值