总线、设备与驱动(2)

总线的属性

总线属性代表着该总线特有的信息与配置,如果通过sysfs文件系统为总线生成属性文件,那么用户空间的程序可以通过该文件接口的方式很容易地显示或者更改该总线的属性。根据实际需要,可以为总线创建不止一个属性文件,每个文件代表该总线的一个或一组属性信息。总线属性在内核中的数据结构为:

<include/linux/device.h>
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);
};

成员变量attr表示总线的属性信息,其类型为struct attribute:

<include/linux/sysfs.h>
struct attribute {
    const char      *name;
    mode_t        mode;
};

struct bus_attribute的另外两个成员show与store分别用来显示和更改总线的属性。内核定义有一个宏BUS_ATTR,用来方便为总线定义一个属性对象:

<include/linux/device.h>
#define BUS_ATTR(_name,_mode,_show,_store)  \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
<include/linux/sysfs.h>
#define __ATTR(_name,_mode,_show,_store) { \
    .attr = {.name = __stringify(_name), .mode = _mode }, \
    .show=_show,                   \
    .store=_store,                    \
}

BUS_ATTR宏将定义一个以“bus_attr_”开头的总线属性对象,而生成总线属性文件则需要使用bus_create_file函数:

<drivers/base/bus.c>
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
{
    int error;
    if (bus_get(bus)) {
        error = sysfs_create_file(&bus->p->subsys.kobj, &attr->attr);
        bus_put(bus);
    } else
        error = -EINVAL;
    return error;
}

sysfs_create_file用来在sysfs文件树中创建一个属性文件,这里不会讨论sysfs实现这个函数的细节。我们关注的是,用户层的应用程序如何利用总线属性文件的接口来显示和更改总线属性。这里以bus_register函数中的add_probe_files调用为例,后者会用BUS_ATTR宏定义一个总线属性,然后为之生成一个属性文件(可以在/sys/bus目录中的任一总线目录下发现drivers_autoprobe文件)​。

通过BUS_ATTR宏,add_probe_files为此定义的总线属性为:

<drivers/base/bus.c>
static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,
        show_drivers_autoprobe, store_drivers_autoprobe);

上面的宏将产生一个总线属性对象bus_attr_drivers_autoprobe,该文件的模式为S_IWUSR |S_IRUGO,表明对root用户而言具有读与写的权限。显示该总线属性的函数为show_drivers_autoprobe:

<drivers/base/bus.c>
static ssize_t show_drivers_autoprobe(struct bus_type*bus,char*buf)
{
    return sprintf(buf,"%d\n",bus->p->drivers_autoprobe);
}
static ssize_t store_drivers_autoprobe(struct bus_type*bus,
                      const char*buf,size_t count)
{
    if(buf[0]=='0')
        bus->p->drivers_autoprobe=0;
    else
        bus->p->drivers_autoprobe=1;
    return count;
}

通过上面这个函数实现,可以发现该属性文件向用户空间提供了一个显示和更改bus->p->drivers_autoprobe成员的接口。下面看一个用户空间如何在shell里面显示和更改一个bus的drivers_autoprobe成员的例子,比如对于/sys/bus/pci而言,在Linux shell里面:

/sys/bus/pci$ cat drivers_autoprobe

输出的1表明当前PCI总线的drivers_autoprobe成员值为1。要更改这个值,可以使用如下命令:

root@AMDLinuxFGL:/home/dennis/book # echo 0 > drivers_autoprobe

命令成功执行后,再用“cat drivers_autoprobe”命令,就会发现输出已经是0了。在构造sysfs文件系统的超级块时,内核会调用到sysfs_init_inode函数,这个函数为sysfs文件系统中的inode初始化了相关的操作对象i_op和i_fop,这样对于在sysfs文件系统中生成总线属性文件的bus_create_file而言,它生成的属性文件被用户空间的shell命令cat操作时,将利用到inode上i_fop操作集:

<fs/sysfs/inode.c>
static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode)
{case SYSFS_KOBJ_ATTR:
        inode->i_size=PAGE_SIZE;
        inode->i_fop=&sysfs_file_operations;
        break;}

sysfs_file_operations的定义为:

<fs/sysfs/file.c>
const struct file_operations sysfs_file_operations={
    .read      =sysfs_read_file,
    .write     =sysfs_write_file,
    .llseek    =generic_file_llseek,
    .open      =sysfs_open_file,
    .release   =sysfs_release,
    .poll      =sysfs_poll,//轮询
};

所以shell环境下的cat命令最终会调用到sysfs_read_file函数,在后者调用的fill_read_buffer中,将调用到总线属性对象中的show函数:

<fs/sysfs/file.c>
static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
       {
       …
       count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page);}

这里只是给出了用户空间与内核空间通过总线属性文件交互的通道框架。

设备与驱动的绑定

我们来看看Linux设备驱动模型中的一个重要概念:设备与驱动的绑定(binding)​。这里的绑定,简单地说就是将一个设备与能控制它的驱动程序结合到一起的行为。两个内核对象间的结合自然是靠各自背后的数据结构中的某些成员来完成。总线在设备与驱动绑定的过程中发挥着核心作用:总线相关的代码屏蔽了大量底层琐碎的技术细节,为驱动程序员们提供了一组使用友好的外在接口,从而简化了驱动程序的开发工作。在总线上发生的两类事件将导致设备与驱动绑定行为的发生:一是通过device_register函数向某一bus上注册一设备,这种情况下内核除了将该设备加入到bus上的设备链表的尾端,同时会试图将此设备与总线上的所有驱动对象进行绑定操作(当然,操作归操作,能否成功则是另外一回事)​;二是通过driver_register将某一驱动注册到其所属的bus上,内核此时除了将该驱动对象加入到bus的所有驱动对象构成的链表的尾部,也会试图将该驱动与其上的所有设备进行绑定操作。

当调用device_register向某一bus上注册一设备对象时,device_bind_driver函数会被调用来将该设备与它的驱动程序绑定起来:

<drivers/base/dd.c>
int device_bind_driver(struct device *dev)
{
    int ret;
    ret = driver_sysfs_add(dev);
    if (!ret)
        driver_bound(dev);
    return ret;
}

其中driver_sysfs_add用来在sysfs文件系统中建立绑定的设备与驱动程序之间的链接符号文件。而driver_bound函数中关于绑定的最核心的代码为:

klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);

用来将设备private结构中的knode_driver节点加入到与该设备绑定的驱动private结构中的klist_devices链表中。所以所谓设备与驱动的绑定,从代码的角度看,其实是在两者之间通过某种数据结构的使用建立了一种关联的渠道。

设备

设备在内核中的数据结构为struct device,该类型的实例是对具体设备的一个抽象:

<include/linux/device.h>
struct device {
    struct device        *parent;
    struct device_private   *p;
    struct kobject         kobj;
    const char      *init_name;
    struct device_type *type;
    struct mutex         mutex;
    struct bus_type  *bus;
    struct device_driver *driver;
    void     *platform_data;
    struct dev_pm_info    power;
#ifdef CONFIG_NUMA
    int       numa_node;
#endif
    u64      *dma_mask;
    u64      coherent_dma_mask;
    struct device_dma_parameters *dma_parms;
    struct list_head  dma_pools;
    struct dma_coherent_mem   *dma_mem;
    struct dev_archdata    archdata;
    dev_t         devt;
    spinlock_t      devres_lock;
    struct list_head  devres_head;
    struct klist_node knode_class;
    struct class     *class;
    const struct attribute_group **groups;
    void (*release)(struct device*dev);
};
struct device *parent

当前设备的父设备。

struct device_private *p

指向该设备的驱动相关的数据。

struct kobject kobj

代表struct device的内核对象。(有种类似面向对象的继承关系)

const char *init_name

设备对象的名称。在将该设备对象加入到系统中时,内核会把init_name设置成kobj成员的名称,后者在sysfs中表现为一个目录。

struct bus_type *bus

设备所在的总线对象指针

struct device_driver *driver

用以表示当前设备是否已经与它的driver进行了绑定,如果该值为NULL,说明当前设备还没有找到它的driver。

系统中的每个设备都是一个struct device对象,内核为容纳所有这些设备定义了一个kset——devices_kset,作为系统中所有struct device类型内核对象的容器。同时,内核将系统中的设备分为两大类:block和char。每类对应一个内核对象,分别为sysfs_dev_block_kobj和sysfs_dev_char_kobj,自然地这些内核对象也在sysfs文件树中占有对应的入口点,block和char内核对象的上级内核对象为dev_kobj。设备相关在Linux系统初始化期间由devices_init来完成:

<drivers/base/core.c>
int __init devices_init(void)
{
    …
    devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
    dev_kobj = kobject_create_and_add("dev", NULL);
    sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
    sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);return 0;
}

这个函数的操作反映到/sys文件目录下,就是生成了/sys/devices、/sys/dev、/sys/dev/block和/sys/dev/char。Linux内核中针对设备的主要操作有

device_initialize

用于设备的初始化,该函数的实现为:

<drivers/base/core.c>
void device_initialize(struct device *dev)
{
    dev->kobj.kset = devices_kset;
    kobject_init(&dev->kobj, &device_ktype);
    INIT_LIST_HEAD(&dev->dma_pools);
    mutex_init(&dev->mutex);
    lockdep_set_novalidate_class(&dev->mutex);
    spin_lock_init(&dev->devres_lock);
    INIT_LIST_HEAD(&dev->devres_head);
    device_pm_init(dev);
    set_dev_node(dev, -1);
}

这个函数主要用于初始化dev的一些成员,其中dev->kobj.kset = devices_kset表明了dev所属的kset对象为devices_kset,device_pm_init用来初始化dev与电源管理相关的部分。

device_register用来向系统注册一个设备,在源码中的实现为:

<drivers/base/core.c>
int device_register(struct device *dev)
{
      device_initialize(dev);
      return device_add(dev);
}

所以device_register内部除了调用device_initialize来初始化dev对象外,还会通过device_add的调用将设备对象dev加入到系统中。

你可能感兴趣的:(总线、设备与驱动(2))