• 第八章——Linux设备模型(1)


     因为面试被问到了设备模型,所以先复习一下这里。前文实现的比如字符设备驱动存在一些弊端:

    1. 设备和驱动没有分离,设备的信息是硬编码在驱动中,这给驱动造成了极大的限制,通用性变得很差。
    2. 没有类似Windows的设备管理器,不可以方便地查看设备和驱动信息。
    3. 不能自动创建设备节点。
    4. 驱动不能自动加载。
    5. U盘、SD不能自动挂载。
    6. 没有电源管理。

    8.1、解决第二点

     在Linux地sys目录下详细罗列了设备、驱动和硬件相关的信息。

    cd angelica:/ # cd sys
    angelica:/sys # ls -l
    total 0
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 android_brightness
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 android_camera
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 android_lcd
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 android_whitepoint
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 block ----块设备
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 bootinfo
    drwxr-xr-x  20 root root 0 2010-01-01 00:00 bus ----所有总线
    drwxr-xr-x  93 root root 0 2010-01-01 00:00 class ----输入设备、tty……
    drwxr-xr-x   4 root root 0 2010-01-01 00:00 dev
    drwxr-xr-x  10 root root 0 2010-01-01 00:00 devices ----系统中所有设备
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 display_cabc
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 display_hbm
    drwxr-xr-x   3 root root 0 2010-01-01 00:00 firmware
    drwxr-xr-x   9 root root 0 2010-01-01 00:00 fs
    drwxr-xr-x  12 root root 0 2010-01-01 00:00 kernel
    drwxr-xr-x 148 root root 0 2010-01-01 00:00 module
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 mtk_rgu
    drwxr-xr-x   4 root root 0 2010-01-01 00:00 power
    drwxr-xr-x   2 root root 0 2010-01-01 00:00 thermal
    

     生成这些信息的重要内核数据结构——struct kobject。我们不需要理解kobject的详细信息,只需要知道它和sys目录下的目录和文件的关系。
     当向一个内核成功添加kobj对象后,底层的代码会自动在sys目录下生成一个子目录。另外kobj可以附加一些属性,并绑定操作这些属性的方法。其附加的属性会被底层代码自动实现为对象对应目录下的文件。写一个象征性的代码:

    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/slab.h>
    #include <linux/kobject.h>
    
    static struct kset *kset;
    static struct kobject *kobj1;
    static struct kobject *kobj2;
    static unsigned int val = 0;
    
    static ssize_t val_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
    	return snprintf(buf, PAGE_SIZE, "%d
    ", val);
    }
    
    static ssize_t val_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) {
    	char *endp;
    	printk("size=%d
    ", count);
    	val = simple_strtoul(buf, &endp, 10);
    	return count;
    }
    
    static struct kobj_attribute kobj1_val_attr = _ATTR(val, 0666, val_show, val_store); 
    //属性的名字是var,所绑定的读写方法是val_show, val_store,对应的文件权限是0666
    
    static struct attribute *kobj1_attrs[] = {
    	&kobj1_val_attr.attr, //这组属性中只有一个属性 kobj1_val_attr
    	NULL,
    }
    
    static struct attribute_group kobj1_attr_group = {
    	.attrs = kobj1_attrs;
    }
    
    static void __init model_init(void) {
    	int ret;
    	kset = kset_create_and_add("kset", NULL, NULL); //向内核创建并添加kset
    	kobj1 = kobject_create_and_add("kobj1", &kset->kobj);
    	kobj2 = kobject_create_and_add("kobj2", &kset->kobj);
    	
    	ret = sysfs_create_group(kobj1, &kobj1_attr_group); //为kobj1添加一组属性
    	ret = sysfs_create_link(kobj2, kobj1, "kobj1"); //在kobj2下创建了kobj1的软链接,名字是kobj1
    	return 0;
    }
    
    static void __exit model_exit(void) {
    	sysfs_remove_link(kobj2, "kobj1");
    	sysfs_remove_group(kobj1, &kobj1_attr_group);
    	kobject_del(kobj1);
    	kobject_del(kobj2);
        kset_unregister(kset);
    }
    
    module_init(model_init);
    module_exit(model_exit);
    MODULE_LICENSE("GPL");
    

    8.2、解决第一点:总线、设备和驱动

     为了刻画总线、设备和驱动,Linux为这三种对象各自定义了对应的类:struct bus_typestruct devicestruct device_driver,这三者都内嵌了struct kobject或struct kset,于是会生成对应的总线、设备、驱动的目录。可以这样认为,总线、设备、驱动都继承自同一个基类struct kobject。将这三者分开来刻画,解决了第一个问题。设备专门用来描述设备所占有的资源信息,当设备资源改变,驱动代码可不做任何修改。
     写一个象征性的代码:

    /*vbus.c*/
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/device.h>
    
    static int vbus_match(struct device *dev, struct device_driver *drv) { //用于匹配驱动和设备的函数
    	return 1;
    }
    
    static struct bus_type vbus = {
    	.name = "vbus",
    	.match = vbus_match,
    };
    
    EXPORT_SYMBOL(vbus);
    
    static void __init vbus_init(void) {
    	return bus_register(&vbus);
    }
    
    static void __exit vbus_exit(void) {
    	bus_unregister(&vbus);
    }
    
    module_init(vbus_init);
    module_exit(vbus_exit);
    MODULE_LICENSE("GPL");
    
    /*vdrv.c*/
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/device.h>
    
    extern struct bus_type vbus;
    
    static struct bus_type vbus = {
    	.name = "vdrv",
    	.bus = &vbus,
    };
    
    static int __init vdrv_init(void) {
    	return driver_register(&vdrv);
    }
    
    static void __exit vdrv_exit(void) {
    	driver_unregister(&vdrv);
    }
    
    module_init(vdrv_init);
    module_exit(vdrv_exit);
    MODULE_LICENSE("GPL");
    
    /*vdev.c*/
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/device.h>
    
    extern struct bus_type vbus;
    
    static void vdev_release(struct device *dev){}
    
    static struct device vdev = {
    	.init_name = "vdrv",
    	.bus = &vbus,
    	.release = vdev_release,
    };
    
    static int __init vdev_init(void) {
    	return driver_register(&vdev);
    }
    
    static void __exit vdev_exit(void) {
    	driver_unregister(&vdev);
    }
    
    module_init(vdev_init);
    module_exit(vdev_exit);
    MODULE_LICENSE("GPL");
    

    8.3、平台设备及其驱动

     要满足Linux,必须满足总线、设备、驱动。但有的设备并没有对应的物理总线。为此,内核专门开发了platform总线来挂在没有物理总线的设备或不支持热插拔的设备。

    8.3.1、平台设备

     平台设备及其资源通常存在于BSP文件中,比如:arch/arm/目录,用struct platform_device结构表示:

    struct platform_device {
    	const char *name; //设备的名字
    	int id; //设备的ID号
    	bool id_auto;
    	struct device dev; //内嵌的struct device
    	u32 num_resources; //平台设备使用资源个数
    	struct resource *resource; //平台设备的资源列表,指向资源数组中的首地址
    	
    	const struct platform_device_id *id_entry; //用于驱动匹配ID,平台总线match函数优先匹配此ID,不行在尝试用name
    	
    	/*MFD cell pointer*/
    	struct mfd_cell *mfd_cell;
    	
    	/*arch specific additions*/
    	struct pdev_archdata archdata;
    }
    

     上面最关键的就是设备使用的资源信息描述resource,这是设备和驱动分离的关键:

    struct resource {
    	resource_size_t start; //资源的开始:IO(起始内存地址)、中断(中断号)、DMA(DMA通道号)
    	resource_size_t end;
    	const char *name;
    	unsigned long flags; //在include/linux/ioport.h中
    	struct resource *parent, *sibling, *child; //树型结构
    }
    

     向平台注册和注销平台设备的主要函数如下:

    int platform_add_devices(struct platform_device **devs, int num);
    int platform_device_register(struct platform_device *pdev);
    void platform_device_unegister(struct platform_device *pdev);
    

     当平台发现有匹配的驱动时候,会调用驱动平台内的一个函数,并传递匹配平台设备结构地址,平台驱动就可以获得设备资源信息。

    8.3.2、平台驱动

     平台驱动使用struct platform_driver结构表示:

    struct platform_driver {
    	int (*probe)(struct platform_device *) //总线发现匹配的平台设备时调用
    	int (*remove)(struct platform_device *)
    	
    	/*电源管理函数*/
    	void (*shutdown)  ……
    	int (* suspend)  ……
    	int (* resume)  ……
    	
    	struct device_driver driver;
    	const struct platform_device_id *id_table; //平台驱动可以驱动的设备ID列表,可用于和平台匹配
    	bool prevent_deferred_probe;
    }
    

     向平台总线注册和注销平台驱动的主要函数如下:

    platform_driver_register(drv)
    void platform_driver_unregister(struct platform_driver *);
    
  • 相关阅读:
    KVM/QEMU简介
    编辑器制作的一些资源
    HRBEU ACM 图论 1006
    zoj 2001
    HRBEU equal
    zoj Integer Inquiry
    HRBEU 字符串 1003
    poj 2736
    SDUT_DP 1003
    zoj Martian Addition
  • 原文地址:https://www.cnblogs.com/hansenn/p/12763271.html
Copyright © 2020-2023  润新知