Sysfs文件系统

来源:互联网 发布:互联网金融 创业 知乎 编辑:程序博客网 时间:2024/05/20 13:14
sysfs文件系统存在于内存中,是一个虚拟文件系统,其提供了kobject对象层次的视图,可以让用户以一个简单文件系统的方式来观察系统中的各种设备的拓扑结构,使用属性对象,kobject可以导出文件的方式,将内核变量提供给用户读取或者写入。在2.6内核的系统中,都拥有sysfs文件系统。sysfs将kobject对象与目录项紧密联系,通过kobject结构体中的dentry字段实现的。

sysfs根目录喜爱包括7个目录:block,bus,class,devices,firmware,module,power.

下面就开始源代码之旅,其实,有时候看源代码确实让人挺犯恶心的。

sysfs仅仅是一个在内存中的虚拟文件系统,并没有提供实际数据的文件。而文件集合是通过ktype字段提供的,而在kobj_type中包含一个default_attrs,这是一个attribute结构体数组,这些属性负责将内核数据映射成sysfs中的文件。这个结构体中,三个字段都很重要,因为它们决定了在sysfs中出现的会是什么。其中的name就是属性名称,出现在sysfs中的就是它,而mode则是权限。

在看init_sysfs()前,先看下sysfs_dirent结构体。

struct sysfs_dirent
{
    atomic_t        s_count;
    atomic_t        s_active;
    struct sysfs_dirent    *s_parent;
    struct sysfs_dirent    *s_sibling;
    const char        *s_name;
    union {
        struct sysfs_elem_dir        s_dir;
        struct sysfs_elem_symlink    s_symlink;
        struct sysfs_elem_attr        s_attr;
        struct sysfs_elem_bin_attr    s_bin_attr;
    };
    unsigned int        s_flags;
    ino_t            s_ino;
    umode_t            s_mode;
    struct iattr        *s_iattr;
};

struct sysfs_dirent sysfs_root = {
    .s_name        = "",
    .s_count    = ATOMIC_INIT(1),
    .s_flags    = SYSFS_DIR,
    .s_mode        = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO,
    .s_ino        = 1,
};

看上面这两个片段,就可以清晰的知道sysfs_dirent的作用。其中,atomic_t的作用在kobject结构体中已经说明,是引用计数。这个结构体的作用就是sysfs和kobject的一个路径。里面的这些指针,使得形成一个树状。而s_name就是名字,然后s_flags就是系统路径,s_mode就是权限。

而在初始化sysfs中,开始就有这样一句:
kmem_cache_create("sysfs_dir_cache",sizeof(struct sysfs_dirent),0,0,NULL);然后是sysfs_inode_init()(bdi_init(&sysfs_backing_dev_info))
然后register_filesystem(&sysfs_fs_type);注册文件系统,文件系统是一个很大的结构。这里可以先忽略,只明白意思就可以了。
static struct file_system_type sysfs_fs_type = {
    .name        = "sysfs",
    .get_sb        = sysfs_get_sb,
    .kill_sb    = kill_anon_super,
};

然后:kern_mount(&sysfs_fs_type);此时基本准备好了sysfs。

然后还记得在kobject源代码分析中,kobject_add吗?这是将kobject导入到sysfs中。里面调用了create_dir()。首先要定义mode的属性。然后新建立一个路径:sysfs_new_dirent(name, mode, SYSFS_DIR);将需要添加的参数加入。sd->s_dir.kobj = kobj这步需要注意。这是指向的kobject

struct sysfs_elem_dir {
    struct kobject        *kobj;
    /* children list starts here and goes through sd->s_sibling */
    struct sysfs_dirent    *children;
};
而在函数开头声明了这样一个结构体:struct sysfs_addrm_cxt acxt;
struct sysfs_addrm_cxt {
    struct sysfs_dirent    *parent_sd;
    struct inode        *parent_inode;
    struct sysfs_dirent    *removed;
    int            cnt;
};
sysfs_addrm_start(&acxt, parent_sd);将父节点拷入这样一个结构体中,看函数的名字就知道:入口地址的开始。
rc=sysfs_add_one(&acxt, sd);增加一个我们定义的节点sd.
sysfs_addrm_finish(&acxt);增加完毕。
return rc;
至此,联系上次分析的kobject,则可以知道,当添加一个kobject的时候,系统通过指针,使得相关方,彼此指向,形成联系。

而内核在默认集合之上,继续添加新属性时也提供了相应的机制,下面就来看看对应的源代码函数:
sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
这就是内核函数中关于添加新属性的函数,看看里面的参数kobject、attribute.意思很直接,可是里面却不是像函数这样看来如此直白。但是程序思路却还算清晰。

函数的调用关系是如此的:sysfs_create_file()-->sysfs_add_file()-->sysfs_add_file_mode()-->sysfs_new_dirent()

在增加文件模块函数中,正如上面所讲解的函数,对照着看看,注意传递参数的不同。

int sysfs_add_file_mode(struct sysfs_dirent *dir_sd,const struct attribute *attr, int type, mode_t amode)
{
    umode_t mode = (amode & S_IALLUGO) | S_IFREG;
    struct sysfs_addrm_cxt acxt;
    struct sysfs_dirent *sd;
    int rc;
    sd = sysfs_new_dirent(attr->name, mode, type);
    if (!sd)
        return -ENOMEM;
    sd->s_attr.attr = (void *)attr;
    sysfs_addrm_start(&acxt, dir_sd);
    rc = sysfs_add_one(&acxt, sd);
    sysfs_addrm_finish(&acxt);
    if (rc)
        sysfs_put(sd);
    return rc;
}

struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type)
{
    char *dup_name = NULL;
    struct sysfs_dirent *sd;
    if (type & SYSFS_COPY_NAME) {name = dup_name = kstrdup(name, GFP_KERNEL);}
    sd = kmem_cache_zalloc(sysfs_dir_cachep, GFP_KERNEL);
    if (sysfs_alloc_ino(&sd->s_ino))goto err_out2;
    atomic_set(&sd->s_count, 1);
    atomic_set(&sd->s_active, 0);
    sd->s_name = name;
    sd->s_mode = mode;
    sd->s_flags = type;
    return sd;
    ......
}
其实过程无非就是建立空间,增加引用技术,建立彼此之间的连接,初始化一些需要的东西,然后找到需要挂接的地址(parent)然后增加一个节点。
而创建符号连接则需要使用int sysfs_create_link(struct kobject * kobj, struct kobject * target, const char * name)函数。
符号名是由name指定的,连接则是由kobj对应的目录映射到target指定的目录。下面来看源代码(其中省略一些语句):

int sysfs_create_link(struct kobject * kobj, struct kobject * target, const char * name)
{
    struct sysfs_dirent *parent_sd = NULL;
    struct sysfs_dirent *target_sd = NULL;
    struct sysfs_dirent *sd = NULL;
    struct sysfs_addrm_cxt acxt;
//如果是头一个,则直接夫节点指向root.否则指向传入的kobj.
    if (!kobj)
        parent_sd = &sysfs_root;
    else
        parent_sd = kobj->sd;
    spin_lock(&sysfs_assoc_lock);
    if (target->sd)
        target_sd = sysfs_get(target->sd);
    spin_unlock(&sysfs_assoc_lock);
    sd = sysfs_new_dirent(name, S_IFLNK|S_IRWXUGO, SYSFS_KOBJ_LINK);//新建路径
    if (!sd)
        goto out_put;

    sd->s_symlink.target_sd = target_sd;/目标指定
    target_sd = NULL;    /* reference is now owned by the symlink */

    sysfs_addrm_start(&acxt, parent_sd);//在入口地址连接
    error = sysfs_add_one(&acxt, sd);//增加一个节点
    sysfs_addrm_finish(&acxt);//节点增加结束
    ......
}

上面两个就是基本的三个函数,其中已经把sysfs和kobject的关系说的挺明白,如何相互关联,和sysfs虚拟文件系统如何形成。其实读源代码的目的就是弄清楚设备结构的意思,其实,就整个文件系统来说,sysfs其中还要牵扯到很多结构体,和文件的读写。这些源代码以后再读,都读完后,就会明白linux的文件系统的整个结构,头脑中就会逐渐的清晰。在linux中不是进程就是文件嘛。fs/sysfs目录下,还有很多函数,可以慢慢的读。

/usr/src/linux-2.6.26.5/drivers/base/bus.c
/usr/src/linux-2.6.26.5/include/linux/device.h

在前面的PCI中,已经有过对于PCI总线的论述,这里可以引申一下子,总线是处理器与一个或者多个设备之间的通道,设备模型中,所有的设备都通过总线相连接。下面就来看看总线的相关函数,以及相关结构体。总线由结构体bus_type代替,在linux中,就我理解,其实就是很多数据结构和相关连的处理函数构成的一个大的结合体。以此来实现人们所需要的功能,其实说白了,软件就是为了这个世界服务的,它也是这个世界的一个组成部分,由人来编写,所以它的思路方式也和人们创造的很多东西一样,正如桌子也是由几个不同的构建组合而成,也有它在不同地点不同的用法,当然,这个例子比较简单,但是意思就是如此,所以不要拘泥于windows还是linux或者unix等等,其实它们的目的都一样。不要被这些系统玩了进去,而固定在一个领域中成为程序的奴隶。一切都是为了解决问题。

struct bus_type
{
    const char        *name;
//总线的名字。每个总线是它自己的子系统; 它们位于总线子系统下面. 一个总线包含2个ksets,是已知的总线的驱动和所有插入总线的设备.
    struct bus_attribute    *bus_attrs;//方法,其中包含struct attribute结构体(前面谈过)
    struct device_attribute    *dev_attrs;//设备方法和设备的struct attribute
    struct driver_attribute    *drv_attrs;//驱动的方法和驱动的struct attribute
    int (*match)(struct device *dev, struct device_driver *drv);
//无论何时一个新设备或者驱动被添加给这个总线.应当返回一个非零值如果给定的设备可被给定的驱动处理.这个函数必须在总线级别处理,核心内核不能知道如何匹配每个可能总线类型的设备和驱动.以下都是它的方法。可以自己来编写定义的,当然除了struct bus_type_private结构体。
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);
    int (*suspend)(struct device *dev, pm_message_t state);
    int (*suspend_late)(struct device *dev, pm_message_t state);
    int (*resume_early)(struct device *dev);
    int (*resume)(struct device *dev);
    struct bus_type_private *p;
};

struct bus_type_private {
    struct kset subsys;
    struct kset *drivers_kset;
    struct kset *devices_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;
};

struct bus_attribute {
    struct attribute    attr;
    ssize_t (*show)(struct bus_type *bus, char *buf);
    ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};

总线注册函数和删除函数:int bus_register(struct bus_type *bus)、void bus_unregister(struct bus_type *bus)

函数调用中的关键函数:
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
kset_create_and_add("devices", NULL,&priv->subsys.kobj);
device_create_file(dev,&bus->dev_attrs[i]);

既然光于总线的结构体是bus_type则在注册一个总线的时候,自然要先声明和初始化它,在ldd3中,作者给出了一个小例子:
struct bus_type ldd_bus_type = { .name = "ldd", .match = ldd_match, .hotplug = ldd_hotplug, };然后调用bus_register()函数,下面就来看看源码

priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);-->(priv->bus = bus;bus->p = priv;)可见两个结构体相互关联起来!而下面的语句在就可以见证kobject和总线之间的关系的一二了:kobject_set_name(&priv->subsys.kobj, "%s", bus->name);总线的名字被传送到了kobject结构体的名字,而kobject又和sysfs相关联,则可以知道,添加一个总线是如何在sysfs中显性显示的了,如果注册成功,会在sysfs/bus目录下看到。然后初始化以些priv结构体的数据:priv->subsys.kobj.kset = bus_kset;priv->subsys.kobj.ktype = &bus_ktype;priv->drivers_autoprobe = 1;这几句的总用在下面的语句中得到体现:kset_register(&priv->subsys);一个容器形成了。这是对象集合
下面就开始建立总线文件:bus_create_file(bus, &bus_attr_uevent);在这个函数中,其实是调用了sysfs_create_file()函数,则可以知道sysfs对应于它的设备的关系了。而接下来,因为kset和kobject和sysfs都是相关联的,kobject是对象,kset是对象的结合体,故在增加一个对象的同时,也该增加和建立一个相关联此对象的对象的集合:kset_create_and_add("devices", NULL,&priv->subsys.kobj);然后:add_probe_files(bus);然后:bus_add_attrs(bus);关键一句:device_create_file(dev, &bus->dev_attrs[i]);建立设备文件,设备是由总线相连接的,自然在这个设备上的文件都应该连接到这个总线上。

而在删除函数中,无意则更加清晰,过程就体现了其的相关联性:bus_remove_attrs(bus);-->remove_probe_files(bus);-->kset_unregister(bus->p->drivers_kset);-->kset_unregister(bus->p->devices_kset);-->bus_remove_file(bus, &bus_attr_uevent);-->kset_unregister(&bus->p->subsys);-->kfree(bus->p);这就是整个一个设备模型和文件系统的过程呀。

设备和驱动的迭代:
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *))
函数列举总线上的每个设备或者驱动, 传递关联的设备结构或者驱动结构给 fn, 连同作为 data 来传递的值. 如果 start 是 NULL, 列举从总线的第一个设备或者第一驱动开始; 否则列举从 start 之后的第一个设备或者第一个驱动开始. 如果 fn 返回一个非零值, 列举停止并且那个值从 bus_for_each_dev或者bus_for_each_drv 返回.
源代码中这两句则是关键:
klist_iter_init_node(&bus->p->klist_drivers, &i,start ? &start->p->knode_bus : NULL);
while ((drv = next_driver(&i)) && !error)error = fn(drv, data);

设备是计算机的基础,操作系统就是一个大的接口,最后溯源都会到对于设备的操作,而对于设备的理解的程度则也是和对计算机系统理解程度成正比的。在最底层,linux系统中的每一个设备都用device结构的一个实例来表示。在看关于设备方面的源代码之前先来认识设备的结构体。设备被抽象为struct device结构体。而其中每个项无疑都很重要,而最基本的要关注的有以下几个:
parent,kobj,bus_id,bus_type,device_driver,driver_data,void(*release)(struct device *dev).
结构体全部部分在/usr/src/linux-2.6.26.5/include/linux/device.h

struct device {
.    .....
    struct device        *parent;
//设备的 "parent" 设备在大部分情况, 一个父设备是某种总线或者主控制器. 如果 parent是NULL, 设备是一个顶层设备.
    struct kobject kobj;
//代表这个设备并且连接它到层次中的 kobject. 注意, 作为一个通用的规则, device->kobj->parent 等同于 device->parent->kobj.
    char    bus_id[BUS_ID_SIZE];    /* position on parent bus */
//唯一确定这个总线上的设备的字符串. PCI 设备, 例如, 使用标准的 PCI ID 格式, 包含域, 总线, 设备, 和功能号.
    ......
    struct bus_type    *bus;        //type of bus device is on 确定设备位于哪种总线.
    struct device_driver *driver;    // which driver has allocated this device 管理这个设备的驱动;
    void        *driver_data;    // 一个可能被设备驱动使用的私有数据成员.
    ......
    void    (*release)(struct device *dev);
//当对这个设备的最后引用被去除时调用的方法;它从被嵌入的kobject的release方法被调用.注册到核心的所有的设备结构必须有一个release方法.
};

device注册和销毁函数:void device_unregister(struct device *dev)、int device_register(struct device *dev)
相关主要函数:
void device_del(struct device *dev)
int device_add(struct device *dev)
int device_create_file(struct device *dev, struct device_attribute *attr)
device_add_class_symlinks(dev);

在device_add函数中,首先获得设备的父设备,通过这个来设置设备结点,然后加入kobject结构中,这时dev->kobj.parent就可以知道kobject的方便之处。kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev->bus_id);此函数前面分析过。然后sysfs_create_file(&dev->kobj, &attr->attr);此函数调用在device_create_file函数中,此时设备也自然与sysfs相连。而在与class相关性的函数中,是通过新增一个sysfs的link来完成的。此后还有很多的和class与sysfs之间的相连接。同时还要将此设备加入设备链表。而class与sysfs之间则是通过link来实现的,class与device之间的联系是通过结构体中相关结构体的指针互相指向而构成的一个整体。这样看来,就可以明确,kobject,kset,sysfs,class,device,driver之间的关系了,他们彼此之间的关系构成了一个彼此之间关联的网型结构。而呈现在用户面前的就是一个层次清晰,说明间接的挂在在内存的虚拟文件系统。如果注册成功,则可以在sys/devices目录中出现,也会在添加到的总线中显示。sysfs虚拟文件系统主要目的之一就是显性显示设备的状况。

设备模型的作用之一就是跟踪系统知道的设备。进行跟踪的主要原因是让驱动程序核心协调驱动程序与新设备之间的关系,一旦驱动程序是系统中的已知对象,就可能完成大量工作。驱动程序由下面的结构体定义,里面的字段前面几篇文章都有涉及。
struct device_driver {
    const char        *name;name //是驱动的名子( 它在 sysfs 中出现 )
    struct bus_type        *bus;//bus 是这个驱动使用的总线类型
    struct module        *owner;
    const char         *mod_name;    /* used for built-in modules */
    int (*probe) (struct device *dev);
    int (*remove) (struct device *dev);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    struct attribute_group **groups;
    struct driver_private *p;//里面包含了kobject
};

struct driver_private {
    struct kobject kobj;
    struct klist klist_devices;
    struct klist_node knode_bus;
    struct module_kobject *mkobj;
    struct device_driver *driver;
};

注册和注销驱动程序函数:int driver_register(struct device_driver *drv)、void driver_unregister(struct device_driver *drv)
相关函数:
struct device_driver *driver_find(const char *name, struct bus_type *bus)
kset_find_obj(bus->p->drivers_kset, name);
int bus_add_driver(struct device_driver *drv)
int driver_create_file(struct device_driver *drv,struct driver_attribute *attr)

寻找驱动程序driver_find(drv->name,drv->bus)通过总线和name则可以进行寻找.在设备集合kset中找到对应的kset_find_obj(bus->p->drivers_kset, name);然后是:bus_add_driver(drv);这个函数很关键:首先bus_get(drv->bus),然后则是在driver的私有属性中设置:priv->driver = drv;drv->p = priv;priv->kobj.kset = bus->p->drivers_kset;让他们形成相关联的形式,这也是必须的,然后就是kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name)-->module_add_driver(drv->owner, drv)-->driver_create_file(drv, &driver_attr_uevent);这个函数中,主体就是这一句:sysfs_create_file(&drv->p->kobj, &attr->attr);与sysfs形成关联关系。-->add_bind_files(drv);然后接下来:sysfs_create_group(&drv->p->kobj, groups[i]);就完成了注册。
原创粉丝点击