Linux设备模型

一、LDM数据结构

linux 设备模型LDM的上层依赖于总线(在内核中表示为struct bus_type结构)、设备驱动程序(由struct device_driver表示)和设备(表示为struct device的实例)。

1. 总线

总线是设备和处理器之间的通道链路。管理总线并将其协议输出到设备的硬件实体称为总线控制器。自身作为设备的总线控制器必须像任何设备那样注册。它是总线上设备的父设备,即位于总线上的设备的父域都指向总线设备。

总线数据结构struct bus_type

struct bus_type {
    const char *name;
    const char *dev_name;
    const char *dev_root;
    struct device_attribute *dev_attrs; /*在新内核版本中已被dev_groups代替*/
    // 表示总线的一组默认属性,在/sys/bus/<bus-name>/中显示
    const struct attribute_group **bus_groups;
    // 表示总线上设备的默认属性,在/sys/bus/<bus-name>/devices/<device-name>/中
    const struct attribute_group **dev_groups;
    // 表示总线上设备的默认属性,在/sys/bus/<bus-name>/drivers/<driver-name>/中
    const struct attribute_group **drv_groups;

    // 每当有新设备或者新驱动程序加入是调用match
    int (*match)(struct device *dev, struct device_driver *drv);
    // 该函数负责分配特点总线的device结构,并调用对应驱动程序的probe函数
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    int (*shutdown)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    const struct dev_pm_ops *pm;
    struct subsys_private *p;
    struct lock_class_key lock_key;
};

每个总线都有自定义的总线变量:

struct bus_type packt_bus_type {
    .name = "packt";
    .match = packt_bus_match,
    .probe = packt_bus_probe,
    .remove = packt_bus_remove,
    .shutdown = packt_bus_shutdown,
};

除定义bus_type外,总线控制器驱动程序还必须定义总线特定的驱动程序结构和设备结构,它们分别通过扩展struct device_driver和struct device得来。

// 1. 用于识别总线设备的id
struct packt_device_id {
    char name[30];
    kernel_ulong_t driver_data;
};

// 2. 总线设备结构
struct packt_device {
    struct module *owner;
    unsigned char name[30];
    struct device dev;
    /*[...]其他私有的成员变量*/
};
#define to_packt_device(d) container_of(d, struct packt_device, dev)

// 3. 总线驱动结构
struct packt_driver {
    struct module *owner;
    unsigned char name[30];
    struct device_driver driver;
    const struct packt_device_id *ids; // 用于使用device id的方式匹配设备
    int (*probe)(struct packt_device *dev);
    int (*remove)(struct packt_device *dev);
    int (*shutdown)(struct packt_device *dev);
    /*[...]其他私有的成员变量*/
};
#define to_packt_driver(d) container_of(d, struct packt_driver, driver)

每个总线结构bus_type{}对象内部包含两个重要列表:设备列表和驱动程序列表;总线驱动程序需要提供以下辅助函数xxx_driver_register()、xxx_driver_unregister()、xxx_device_register()和xxx_device_unregister()。

int packt_driver_register(struct packt_driver *drv) {
    drv->driver.bus = &packt_bus_type;
    return driver_register(&drv->driver);
}
void packt_driver_unregister(struct packt_driver *drv) {
   driver_unregister(&drv->driver);
}
int packt_device_register(struct packt_device *dev) {
    return device_register(&dev->dev);
}
void packt_device_unregister(struct packt_device *dev) {
    dev->dev.parent = &packt_bus; // 总线驱动器设备
    dev->dev.bus = &packt_bus_type;
    return device_unregister(&dev->dev);
}

接着总线驱动程序在packt_bus_type.probe()函数中为连接到总线的每个设备分配特定的设备结构,并初始化设备device的bus和parent字段。

总线控制器设备的注册:

struct device packt_bus = {
    .release = packt_bus_release,
    .parent = NULL, /*根设备,不需要父设备*/
};

static int __init packt_init(void) {
    int status = bus_register(&packt_bus_type);
    if (status < 0) goto err0;
    status = class_register(&packt_master_class);
    if (status < 0) goto err1;
    device_resiter(&packt_bus);
    err1:
        bus_unregister(&packt_bus_type);
    err0:
        return status;
}

2. 设备驱动程序和设备结构

驱动程序device_driver{}结构

struct device_driver {
    const char *name;
    struct bus_type *bus;
    struct module *owner;
    const struct of_device_id *of_match_table; // 用于匹配DT中的设备
    const struct acpi_device_id *acpi_match_table; // 用于匹配acpi模式的设备
    [...]
};

设备结构device{}

struct device {
    struct device *parent;
    struct bus_type *bus;
    struct kobject kobj;
    const struct kobj_type *type;
    const struct attribute_group **groups; //设备属性文件
    struct class *class;
    struct device_driver *driver;
    struct device_node *of_node;  // 设备对应的DT中的节点,可以用了获取资源和配置数据
    void *platform_data;
    void *driver_data;
    void (*release)(struct device *dev);
};

二、LDM的基础kobject、kobj_type、kset和属性

1 kobject类型

kobject是LDM的核心,提供设备的引用计数和层次关系,每个kobject都对应一个/sys/下的目录。

struct kobject {
    const char *name; // 名称,可用kobject_set_name(char *fmt ...)设置
    struct list_head entry; // 
    struct kobject *parent;
    struct kset *kset;
    struct kobj_type *ktype;
    struct sysfs_dirent *sd; // 在sysfs中的目录 
    struct kref kref;  // 引用计数
    /*省略其他不关注的成员*/
};

// 相关函数
struct kobject *kobject_create(void);
void kobject_init(struct kobject *kobj, struct kobj_type *type);
int kobject_add(struct kobject *kobj, struct kobject *parent, char *fmt, ...);

// 简化方式
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);

//使用示例,在/sys目录下创建class和devices目录
kobject_create_and_add("class", NULL);
kobject_create_and_add("devices", NULL);

2 kobj_type类型

结构struct kobj_type描述kobject的行为,它是kobject的成员变量。

struct kobj_type {
    void (*release)(struct kobject *);
    const struct sysfs_ops ops;
    struct attribute **default_attrs;
};

struct sysfs_ops {
    ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buf);
    ssize_t (*store)(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size);
};
/*属性缓冲区的长度适中为PAGE_SIZE长度*/

3. kset类型

内核集合对象主要将相关的内核对象组合在一起,kset是kobject的集合。

struct kset {
    struct list_head list; // kset中所包含的kobject的链表头
    spinlock_t list_lock;
    struct kobject kobj; // kset本身作为kobject
    /*省略不关注的字段*/
}

// 创建kset
struct kset *kset_create_and_add(const char *name, const struct kset_uevent_ops *u, 
struct kobject *parent_kobj);
void kset_unregister(struct kset *kset);

// 把kobject加入kset
struct kobject foo_kobj;

struct kset *example_kset = kset_create_and_add("example", NULL, NULL);

foo_kobj.kset = example_kset;
ret = kobject_init_and_add(&foo_kobj, &foo_type, NULL, "foo_name");

4. 属性kobject attribute

属性是kobject导出到用户空间的sysfs文件。嵌入kobject结构体的结构体都有 kobject本身的默认属性和自定义属性。

// 默认属性结构体
struct attribute {
    char *name;
    umode_t mode;
    struct module *owner;
}

/* 加入自定义属性文件的方式,sys_creat_file提供了简便的加入自定义attr的方式,
    不需要自己改造kobj_type.sysfs_ops的show和store函数,自动实现了对结构体成员变量value的读取和写入
*/
struct d_attr {
    struct attribute attr;
    int value;
}

static struct d_attr foo = { .attr.name = "foo", .attr.mode = 0644, .value=0,
};
static struct d_attr bar = { .attr.name = "bar", .attr.mode = 0644, .value=0,
};
sys_create_file(&mykobj, &foo.attr);
sys_create_file(&mykobj, &bar.attr);
/*删除属性文件*/
sys_remove_file(&mykobj, &foo.attr);
sys_remove_file(&mykobj, &bar.attr);

/*进一步简化,提出了属性组的方式,一次可以创建多个属性文件。*/
struct attribute_group {
    struct attribute **attrs;
};

/*属性组创建示例*/
struct attribute *attrs[] = {
    &foo.attr, &bar.attr, NULL
};
static struct attribute_group my_attr_group = { .attrs = attrs, };
sys_create_group(&mykobj, &my_attr_group);

三、bus/device/device_driver/class和属性文件

1. device属性文件

struct device_attribute {
    struct attribute attr;
    ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                    const char *buf, size_t size);
};

DEVICE_ATTR(_name, _mode, _show, _store); // 使用宏静态声明一个属性,带有dev_attr_前缀

/*使用示例*/
static ssize_t foo_show(struct device *dev, struct device_attribute *attr, char *buf) {
    return sprintf(buf, "%d\n", foo_value);
};

static ssize_t bar_show(struct device *dev, struct device_attribute *attr, char *buf) {
    return sprintf(buf, "%d\n", bar_value);
};
DEVICE_ATTR(foo, 0644, foo_show, NULL);
DEVICE_ATTR(bar, 0644, bar_show, NULL);
device_create_file(&mykobj, dev_attr_foo);
device_create_file(&mykobj, dev_attr_bar);

后面bus_attribute/BUS_ATTR, driver_attribute/DRIVER_ATTR, class_attribute/CLASS_ATTR的使用方式与device_attribute/DEVICE_ATTR的使用方式完全雷同。

ldm内核还提供了在/sysfs中创建链接的接口:

int sysfs_create_link(struct kobject *kojb, struct kobject *target, char *name);
int sysfs_remove_link(struct kobject *kojb char *name);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值