Linux内核编程(十一)设备模型


  

一、知识点

在这里插入图片描述

1. 设备模型

  Linux 支持世界上几乎所有的,不同功能的硬件。所以Linux驱动一定要跨平台。而且现在支持的硬件数量在一直增加,代码的复杂程度也在上升。为了做好设备驱动的管理,并降低驱动开发难度。兼容设备的热拔插和电源管理。Linux对硬件设备进行了分类和归纳,并抽象出来了一套标准的数据结构和接口。这个就是设备模型。
   Linux引入了设备驱动模型分层的概念, 将我们编写的驱动代码分成了两块:设备与驱动。设备负责提供硬件资源而驱动代码负责去使用这些设备提供的硬件资源。 并由总线将它们联系起来。

在这里插入图片描述
   设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况,提出了以下几个重要概念:
  ①设备(device) :挂载在某个总线的物理设备;
  ②驱动(driver) :与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
  ③总线(bus) :负责管理挂载对应总线的设备以及驱动;
  ④类(class) :对于具有相同功能的设备,归结到一种类别,进行分类管理;

2. sysfs 文件系统

   sysfs文件系统是 Linux2.6版本引入的虚拟文件系统。sysfs把连接在系统上的设备模型组织成为一个分级的层次视图。并且可以向用户空间导出内核据结构以及属性。

   和设备模型有关的文件夹有classdevicesbus这三个文件夹。
在这里插入图片描述

(1)bus文件夹:这个文件夹下的所有目录是 Linux系统支持并且已经注册的总线。从总线这个角度展示现在有哪些总线以及总线下连接了什么设备和驱动。
在这里插入图片描述

(2)devices文件夹:该文件夹下的所有目录是连接到总线的全部设备,从设备级联角度进行展示。
在这里插入图片描述

(3)class文件夹:对设备进行归类。对于具有相同功能的设备,归结到一种类别。类下的所有设备都是/sys/devices下的设备的软连接。
在这里插入图片描述

3. kobject、kset设备模型框架

在这里插入图片描述

   kobject是一个面向对象的管理机制,是构成设备上述设备模型的核心结构,在内核中注册一个kobject,对应就是在sysfs文件系统中创建一个目录和目录里的一个文件夹。即一个kobject对应/sys下一个目录。
  在实际使用 kobject 的时候,一般不会单独使用。通常要嵌入到一个数据结构中。这样就可以把高级对象接入到设备模型里面。举例如下:

struct device {
	struct device		*parent;
	struct device_private	*p;
	struct kobject kobj;  //将kobj 内嵌到device设备下。
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;
	...
}

//平台总线
struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;        //包含device结构体。
	u32		num_resources;
	struct resource	*resource;
	const struct platform_device_id	*id_entry;
	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;
	/* arch specific additions */
	struct pdev_archdata	archdata;
};

二、kobject实验

kobject 结构体如下,头路径:#include <linux/kobject.h>

struct kobject {
    const char        *name;                    // 对象的名称
    struct list_head  entry;                    // 链表节点,用于在链表中插入和删除 kobject
    struct kobject    *parent;                  // 指向父对象的指针
    struct kset       *kset;                    // 指向 kset 结构体的指针,kset 是一组 kobject 的集合
    struct kobj_type  *ktype;                   // 指向 kobj_type 结构体的指针,定义了 kobject 的类型和操作
    struct sysfs_dirent *sd;                    // 指向 sysfs_dirent 结构体的指针,管理 sysfs 中的目录项
    struct kref       kref;                     // 引用计数,用于管理 kobject 的生命周期
    unsigned int state_initialized:1;           // 标志位,表示 kobject 是否已初始化
    unsigned int state_in_sysfs:1;              // 标志位,表示 kobject 是否在 sysfs 中
    unsigned int state_add_uevent_sent:1;       // 标志位,表示是否已发送添加 uevent
    unsigned int state_remove_uevent_sent:1;    // 标志位,表示是否已发送删除 uevent
    unsigned int uevent_suppress:1;             // 标志位,表示是否抑制 uevent
};

1. 创建kobject

方式一:

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
/*
返回值:struct kobject * ,接收kobject。
参数:
	const char *name:创建的kobject的文件夹的名称。
	struct kobject *parent:创建的kobject 文件夹的父目录。
*/

方式二:

// 1. 先使用kzalloc给kobject申请一个地址空间。
struct kobject *mykoject;
struct kobj_type *myktype;
mykoject =kzalloc(sizeof(struct kobject), GFP_KERNEL);

// 2. 使用kobject_init_and_add初始化并添加一个kobject。
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
			 struct kobject *parent, const char *fmt, ...)
/*
		struct kobject *kobj: kobject结构体。
		struct kobj_type *ktyp:类型。
		struct kobject *parent:创建的kobject 文件夹的父目录。
		const char *fmt:名称。
使用示例:ret=kobject_init_and_add(mykoject, &myktype ,NULL, "mykojecte");
*/

2. 释放kobject

  是 Linux 内核中的一个函数,用于减少 kobject 的引用计数,并在引用计数变为零时释放该对象。
注意:在释放kobj时,要先释放子目录的kobj,在释放其父目录的kobj!

void kobject_put(struct kobject *kobj);

★示例一:使用两种方式创建kobject

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc

struct kobject *mykobject01;
struct kobject *mykobject02;
struct kobject *mykobject03;
struct kobj_type mytype;

static int __init mykobj_init(void)
{
    int ret;

//使用方式一创建kobject。
    mykobject01 = kobject_create_and_add("mykobject01", NULL);
    if (!mykobject01)
        return -ENOMEM;

    mykobject02 = kobject_create_and_add("mykobject02", mykobject01);
    if (!mykobject02) {
        kobject_put(mykobject01);
        return -ENOMEM;
    }

//使用方式二创建kobject。
    mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    if (!mykobject03) {
        kobject_put(mykobject02);
        kobject_put(mykobject01);
        return -ENOMEM;
    }
    
    ret = kobject_init_and_add(mykobject03, &mytype, NULL, "mykobject03");
    if (ret) {
        kobject_put(mykobject03);
        kobject_put(mykobject02);
        kobject_put(mykobject01);
        return ret;
    }
    return 0;
}

static void __exit mykobj_exit(void)
{
    kobject_put(mykobject03);
    kobject_put(mykobject02); //先释放子目录
    kobject_put(mykobject01); //再释放父目录
}

module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

在这里插入图片描述

★示例二:两种方式的区别

  问题:由于使用kobject_put函数会使得kobject的引用计数器值-1,则计数器值为0时自动释放其内存,那么是在哪里释放的呢?

答:对于方法一,内核已经自己实现了释放的方法,不需要我们手动去调用实现,我们只需要创建object即可。而对于方法二,则需要我们自己去实现kobj_type 结构体中的释放函数,通过参数kobj_type来传入 。下面我们拿方法二来实验。

kobj_type结构体

struct kobj_type {
    void (*release)(struct kobject *kobj);   // 用于释放 kobject
    const struct sysfs_ops *sysfs_ops;    // 指向 sysfs 操作的指针
    struct attribute **default_attrs;     // 指向默认属性的指针数组
    const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); // 函数指针,用于获取子命名空间类型
    const void *(*namespace)(struct kobject *kobj);  // 用于获取命名空间
    unsigned int (*ephemeral)(struct kobject *kobj);  // 用于判断 kobject 是否为临时的
};

代码

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc

struct kobject *mykobject01;

static void dynamic_kobj_release(struct kobject *kobj)
{
	printk("kobject: (%p): %s\n", kobj, __func__);
	kfree(kobj);
}

static struct kobj_type mytype= {
	.release= dynamic_kobj_release,
};

static int __init mykobj_init(void)
{
    int ret;
    mykobject01= kzalloc(sizeof(struct kobject), GFP_KERNEL);
    ret = kobject_init_and_add(mykobject01, &mytype, NULL, "mykobject01");
    return 0;
}

static void __exit mykobj_exit(void)
{
    kobject_put(mykobject01); 
}

module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

   现象:在模块加载时,创建kobject,此时该kobject的引用计数器值为1。在模块卸载时,调用kobject_put,使得kobject的引用计数器值-1,并且此时引用计数器值变为了0,触发释放函数。
在这里插入图片描述

三、kset实验

   当多个kobject属于同一类的时候,为了方便管理,就引入了Kset。Kset可以认为是一组kobject的集合,是kobject的容器。比如/sys/bus下就属于同一类kobject。kset结构体如下,头文件路径:#include <linux/kobject.h>

struct kset {
	struct list_head list;//连接所包含的kobject对象的链表首部
	spinlock_t list_lock;//维护list链表的自旋锁
	struct kobject kobj; //内嵌的kobject结构体,说明kset本身也是一个目录
	const struct kset_uevent_ops *uevent_ops; //热插拔事件
};

1. 创建kset

struct kset *kset_create_and_add(const char *name,
				 const struct kset_uevent_ops *uevent_ops,
				 struct kobject *parent_kobj)
/*
const char *name:kest文件夹的名字
const struct kset_uevent_ops *uevent_ops:指向 kset_uevent_ops 结构的指针,该结构包含 kset 的用户空间事件操作。常为NULL。
struct kobject *parent_kobj :父节点目录
*/

2. 注销kset

void kset_unregister(struct kset *k);

★示例

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc

struct kobject *mykobject01;
struct kobject *mykobject02;
struct kset *my_kset;
struct kobj_type mytype;

static int __init mykobj_init(void)
{
    int ret;
    //1. 创建kest
    my_kset=kset_create_and_add("my_kset",NULL,NULL); //在sys下创建my_kset目录.
    //2. 创建kobject1
    mykobject01= kzalloc(sizeof(struct kobject), GFP_KERNEL); 
    mykobject01->kset=my_kset;
    ret = kobject_init_and_add(mykobject01, &mytype, NULL, "mykobject01");
    //3. 创建kobject2
    mykobject02= kzalloc(sizeof(struct kobject), GFP_KERNEL); 
    mykobject02->kset=my_kset;
    ret = kobject_init_and_add(mykobject02, &mytype, NULL, "mykobject02");
  
    return 0;
}

static void __exit mykobj_exit(void)
{
    kobject_put(mykobject01);
    kobject_put(mykobject02);
    kset_unregister(my_kset);
}

module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

在这里插入图片描述

四、引用计数器

1. 概念

  引用计数器是一个计数器,它记录了某个资源当前被多少个用户或对象引用。当资源被引用时,引用计数器增加;当引用被释放时,引用计数器减少。当引用计数器减少到零时,表示资源不再被任何对象引用,此时可以安全地释放资源。

2. 为什么要引入引用计数器?

  答:如果我们写了一个字符驱动。当硬件设备插上时,系统会生成一个设备节点。用户在应用空间操作这个设备节点就可以操作设备。如果此时将硬件断开。驱动是不是就要立刻释放呢?如果立刻释放,应用程序是不是就崩了呢。所以要等应用程序关闭,在去释放驱动。要如何实现呢?在 Linux 系统中是通过引用技术去来实现的。比如用 kref这个变量记录某个驱动或者某块内存的引用次数。初始值为1,每引用一次+1,每取消引用一次-1,当计数值为0的时候。自动调用自定义的释放函数进行释放驱动或者内存。

3. 常用函数

引用计数器使用结构体struct kref来描述,结构体如下。头文件路径:#include <linux/kref.h>

struct kref {
	atomic_t refcount; //用于计数
};

  在使用引用计数器时,结构体kref一般被嵌入进其他结构中来使用。如结构体 kobject。当然也可以在自定义的结构体中使用该结构体。以下是对原子操作的进一步封装的函数。

(1)初始化计数器值为1

void kref_init(struct kref *kref);

(2)计数器值+1

void kref_get(struct kref *kref);

(3)计数器值-1

  当计数器的值为0时,会自动调用自定义的release函数执行释放操作。kobject_put是对该函数的进一步封装。

int kref_put(struct kref *kref, void (*release)(struct kref *kref));
//void (*release)(struct kref *kref):自定义的release函数。

4. 实验

  现象:当我们创建一个kobj时,该kobj里的引用计数器就会自动进行+1操作(初值为0)。当我们卸载一个kobj时,该kobj里的引用计数器就会自动进行-1操作,这是因为kobj的创建和卸载函数是对引用计数器操作函数的进一步封装,实质还是对计数器的操作。由于在创建object时调用的函数已经实现了释放操作,所以我们就不需要再使用kref_put去执行释放。

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/kernel.h>
#include <linux/slab.h>  // For kzalloc
#include <linux/kref.h>
#include <linux/atomic.h>

struct kobject *mykoject01;
struct kobject *mykoject02;
struct kobject *mykoject03;

static void dynamic_kobj_release(struct kobject *kobj)
{
	printk("kobject: (%p): %s\n", kobj, __func__);
	kfree(kobj);
}

static struct kobj_type mytype= {
	.release= dynamic_kobj_release,
};


static int __init mykobj_init(void)
{
    int ret;

    mykoject01 = kobject_create_and_add("mykoject01", NULL);
	printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));

    mykoject02 = kobject_create_and_add("mykoject02", mykoject01);
	printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));

    mykoject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    ret = kobject_init_and_add(mykoject03, &mytype, NULL, "mykoject03");
	printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));

    return 0;
}

static void __exit mykobj_exit(void)
{
	printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
    printk("mykoject02 kref is %d\n", atomic_read(&mykoject02->kref.refcount));
    printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));
    
    kobject_put(mykoject01);
	printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
    printk("mykoject02 kref is %d\n", atomic_read(&mykoject02->kref.refcount));
    printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));

    kobject_put(mykoject02);
	printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
    printk("mykoject02 kref is %d\n", atomic_read(&mykoject02->kref.refcount));
    printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));

    kobject_put(mykoject03);
	printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
    printk("mykoject02 kref is %d\n", atomic_read(&mykoject02->kref.refcount));
    printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));
}

module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

现象:当kobj2卸载时,kobj1的计数器也会-1。
在这里插入图片描述

理解
在这里插入图片描述

五、在sys目录下创建属性文件并进行读写操作

1. 什么是属性文件呢?

  属性文件(attribute file)通常是指通过 sysfs 文件系统暴露出来的文件,这些文件允许用户空间与内核对象进行交互。每个属性文件表示内核对象的一个属性,可以通过读取或写入这些文件来查询或设置相应的属性。
  例如下图中的文件,可以读取属性值。这些文件代表内核对象的属性,并且可以通过常规的文件操作命令(如 cat、echo)进行读取和修改。
在这里插入图片描述

2. 如何创建属性文件呢?

  通过kobj创建函数的第二种方式中的kobj_type 参数进行创建。并完善该结构体成员中的sysfs_ops(设置读写)和 attribute (设置权限)成员。

struct kobj_type {
    void (*release)(struct kobject *kobj);   // 用于释放 kobject
    const struct sysfs_ops *sysfs_ops;    // 指向 sysfs 操作的指针
    struct attribute **default_attrs;     // 指向默认属性的指针数组
    const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); // 函数指针,用于获取子命名空间类型
    const void *(*namespace)(struct kobject *kobj);  // 用于获取命名空间
    unsigned int (*ephemeral)(struct kobject *kobj);  // 用于判断 kobject 是否为临时的
};
/*
struct attribute {
	const char		*name;     //属性文件名称
	umode_t			mode;      //权限,注意这个权限不能设置太高,0777会报错的。
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	bool			ignore_lockdep:1;
	struct lock_class_key	*key;
	struct lock_class_key	skey;
#endif
};

struct sysfs_ops {
//读
	ssize_t	(*show)(struct kobject *, struct attribute *,char *);
//写
	ssize_t	(*store)(struct kobject *,struct attribute *,const char *, size_t);
	const void *(*namespace)(struct kobject *, const struct attribute *);
};
*/

3. 实验代码

  内容:在/sys目录下创建kobj_gpio目录,并在kobj_gpio中创建属性文件value和direction,将属性文件的权限改为0666,并可以实现对权限文件的读写操作。注意:文件的权限不能设置太高,如0777,这样编译时可能会报错。

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc

struct my_kobj {
    struct kobject kobj;
    int value;
    int direction;
};

struct my_kobj *kobj_gpio;

static void dynamic_kobj_release(struct kobject *kobj)
{
    struct my_kobj *kobj_gpio = container_of(kobj, struct my_kobj, kobj);
    printk("kobject: (%p): %s\n", kobj, __func__);
    kfree(kobj_gpio);
}

struct attribute value = {
    .name = "value",
    .mode = 0666,
};

struct attribute direction = {
    .name = "direction",
    .mode = 0666,
};

struct attribute *my_attrs[] = {
    &value,
    &direction,
    NULL, // 属性数组必须以 NULL 结尾
};

// 读取,使用 cat
ssize_t my_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
    struct my_kobj *kobj_gpio = container_of(kobj, struct my_kobj, kobj);
    ssize_t value;

    if (strcmp(attr->name, "value") == 0) {
        value = sprintf(buf, "%d\n", kobj_gpio->value);
    } else if (strcmp(attr->name, "direction") == 0) {
        value = sprintf(buf, "%d\n", kobj_gpio->direction);
    } else {
        value = 0;
    }
    return value;
}

// 写入,使用 echo
ssize_t my_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size)
{
    struct my_kobj *kobj_gpio = container_of(kobj, struct my_kobj, kobj);

    if (strcmp(attr->name, "value") == 0) {
        sscanf(buf, "%d", &kobj_gpio->value);
    } else if (strcmp(attr->name, "direction") == 0) {
        sscanf(buf, "%d", &kobj_gpio->direction);
    }
    return size;
}

struct sysfs_ops my_ops = {
    .show = my_show,
    .store = my_store,
};

static struct kobj_type mytype = {
    .release = dynamic_kobj_release,
    .sysfs_ops = &my_ops,
    .default_attrs = my_attrs,
};

static int __init mykobj_init(void)
{
    int ret;
    kobj_gpio = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
    // 在/sys目录下创建kobj_gpio目录。
    ret = kobject_init_and_add(&kobj_gpio->kobj, &mytype, NULL, "kobj_gpio");
    return 0;
}

static void __exit mykobj_exit(void)
{
    kobject_put(&kobj_gpio->kobj);
}

module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

在这里插入图片描述

★优化:读写功能函数

  上面的代码中,如果我们每添加一个属性文件,就需要进行if判断,如果我们添加的属性文件较多时,会大量的修改读写函数的内容。为了让代码更易于扩展,可以为每个属性文件定义各自的读写函数,这样当添加新的属性时,不需要修改核心的读写函数,只需要添加新的属性文件和对应的读写函数。
  为了实现这个功能,我们使用linux内核提供的结构体,如下。这个结构体将属性和读写函数进行了进一步的封装。

struct kobj_attribute {
    struct attribute attr;      // 属性结构体
    ssize_t (*show)(struct kobject *kobj,     // 函数指针,用于显示属性值,读操作
                    struct kobj_attribute *attr,
                    char *buf);
    ssize_t (*store)(struct kobject *kobj,    // 函数指针,用于存储属性值 ,写操作
                     struct kobj_attribute *attr,
                     const char *buf, size_t count);
};

我们可以使用下面的宏定义来完成创建属性文件。

#define __ATTR(_name,_mode,_show,_store) { 				\
	.attr = {.name = __stringify(_name), .mode = _mode },		\
	.show	= _show,						\
	.store	= _store,						\
}
/*
_name:名称。
_mode:权限。
_show:读函数。
_store:写函数。
*/

优化代码

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc

struct my_kobj {
    struct kobject kobj;
    int value;
    int direction;
};

struct my_kobj *kobj_gpio;

static void dynamic_kobj_release(struct kobject *kobj)
{
    struct my_kobj *kobj_gpio = container_of(kobj, struct my_kobj, kobj);
    printk("kobject: (%p): %s\n", kobj, __func__);
    kfree(kobj_gpio);
}

// value 属性的读写函数
static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    struct my_kobj *kobj_gpio = container_of(kobj, struct my_kobj, kobj);
    return sprintf(buf, "%d\n", kobj_gpio->value);
}

static ssize_t value_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
    struct my_kobj *kobj_gpio = container_of(kobj, struct my_kobj, kobj);
    sscanf(buf, "%d", &kobj_gpio->value);
    return count;
}

// direction 属性的读写函数
static ssize_t direction_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    struct my_kobj *kobj_gpio = container_of(kobj, struct my_kobj, kobj);
    return sprintf(buf, "%d\n", kobj_gpio->direction);
}

static ssize_t direction_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
    struct my_kobj *kobj_gpio = container_of(kobj, struct my_kobj, kobj);
    sscanf(buf, "%d", &kobj_gpio->direction);
    return count;
}

// 定义每个属性文件的 kobj_attribute 结构体
static struct kobj_attribute value_attr = __ATTR(value, 0664, value_show, value_store);  //这里权限不能太高,否则会报错
static struct kobj_attribute direction_attr = __ATTR(direction, 0664, direction_show, direction_store);//这里权限不能太高,否则会报错

// 读取,使用 cat
ssize_t my_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
    struct kobj_attribute *kobj_attr = container_of(attr, struct kobj_attribute, attr);
    return kobj_attr->show(kobj, kobj_attr, buf);
}

// 写入,使用 echo
ssize_t my_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size)
{
    struct kobj_attribute *kobj_attr = container_of(attr, struct kobj_attribute, attr);
    return kobj_attr->store(kobj, kobj_attr, buf, size);

}

struct sysfs_ops my_ops = {
    .show = my_show,
    .store = my_store,
};

// 将所有属性文件放在一个属性数组中
static struct attribute *my_attrs[] = {
    &value_attr.attr,
    &direction_attr.attr,
    NULL, // 属性数组必须以 NULL 结尾
};

static struct kobj_type mytype = {
    .release = dynamic_kobj_release,
    .sysfs_ops = &my_ops,
    .default_attrs = my_attrs,
};


static int __init mykobj_init(void)
{
    int ret;
    kobj_gpio = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
    // 在/sys目录下创建kobj_gpio目录。
    ret = kobject_init_and_add(&kobj_gpio->kobj, &mytype, NULL, "kobj_gpio");
    return 0;
}

static void __exit mykobj_exit(void)
{
    kobject_put(&kobj_gpio->kobj);
}

module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

在这里插入图片描述

★思考:可以使用创建kobj的方式一来创建属性文件嘛?

答:其实是可以的,使用方式一来创建kobj,内核已经帮我们实现了attr的属性内容,所以不需要我们自己手动去编写,但是我们也可以使用其他API函数来完成对属性文件的创建。

static inline int sysfs_create_file(struct kobject *kobj,
				          const struct attribute *attr)
//struct kobject *kobj:在哪个kobj下创建属性文件
//const struct attribute *attr :要创建的属性

完整代码

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc

struct kobject *kobj_gpio;

// value 属性的读写函数
static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
	ssize_t count;
    count=sprintf(buf,"hello\n");
    return count;
}

static ssize_t value_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
    printk("buf is %s\n",buf);
    return count;
}

// direction 属性的读写函数
static ssize_t direction_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
	ssize_t count;
    count=sprintf(buf,"world\n");
    return count;
}

static ssize_t direction_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
    printk("buf is %s\n",buf);
    return count;
}

// 定义每个属性文件的 kobj_attribute 结构体
static struct kobj_attribute value_attr = __ATTR(value, 0664, value_show, value_store);  //这里权限不能太高,否则会报错
static struct kobj_attribute direction_attr = __ATTR(direction, 0664, direction_show, direction_store);//这里权限不能太高,否则会报错


static int __init mykobj_init(void)
{
    int ret;
    kobj_gpio= kobject_create_and_add("kobj_gpio", NULL);
    ret=sysfs_create_file(kobj_gpio, &value_attr.attr);
    ret=sysfs_create_file(kobj_gpio, &direction_attr.attr);
    return 0;
}

static void __exit mykobj_exit(void)
{
    kobject_put(kobj_gpio);
}

module_init(mykobj_init);
module_exit(mykobj_exit);
MODULE_LICENSE("GPL");

在这里插入图片描述

在这里插入图片描述

★优化二:创建属性文件组

  上个思考标题的代码中,我们使用sysfs_create_file函数来创建属性文件,那么我们如果创建100个属性文件,那岂不是要调用100次?为了优化这种方式,我们引入了创建属性文件组这个函数,函数原型如下:

int sysfs_create_group(struct kobject *kobj,
				     const struct attribute_group *grp);
/*
struct kobject *kobj :在哪个kobj下创建属性文件
const struct attribute_group *grp :要创建的属性文件组
*/

完整代码
功能与上一个代码完全一致,只是优化了创建方式。
  需要注意的是:attribute_group 结构体的name成员,如果我们不实现这个成员时,创建的属性文件会直接创建在kobj目录下。如果我们使用了name成员,那么会在kobj目录下创建名为name的目录,然后将属性文件创建在name目录下。

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc

struct kobject *kobj_gpio;

// value 属性的读写函数
static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    ssize_t count;
    count = sprintf(buf, "hello\n");
    return count;
}

static ssize_t value_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
    printk("buf is %s\n", buf);
    return count;
}

// direction 属性的读写函数
static ssize_t direction_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    ssize_t count;
    count = sprintf(buf, "world\n");
    return count;
}

static ssize_t direction_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
    printk("buf is %s\n", buf);
    return count;
}

// 定义每个属性文件的 kobj_attribute 结构体
static struct kobj_attribute value_attr = __ATTR(value, 0664, value_show, value_store);
static struct kobj_attribute direction_attr = __ATTR(direction, 0664, direction_show, direction_store);

// 将所有属性文件放在一个属性数组中,并以 NULL 结尾
static struct attribute *my_attrs[] = {
    &value_attr.attr,
    &direction_attr.attr,
    NULL,
};

const struct attribute_group my_grp = {
    .name = "attr_file",
    .attrs = my_attrs,
};

static int __init mykobj_init(void)
{
    int ret;

    // 创建 kobject 并检查返回值
    kobj_gpio = kobject_create_and_add("kobj_gpio", kernel_kobj);

    // 创建 sysfs 属性组并检查返回值
    ret = sysfs_create_group(kobj_gpio, &my_grp);
    return 0;
}

static void __exit mykobj_exit(void)
{
    // 移除 sysfs 属性组并释放 kobject
    sysfs_remove_group(kobj_gpio, &my_grp);
    kobject_put(kobj_gpio);
}

module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

六、注册一个自己的总线

  总线bus_type 结构体, 头文件路径:#include <linux/device.h>。此处我们只需要先完成前三个成员即可。

struct bus_type {
    const char      *name;          // 总线的名称
    int (*match)(struct device *dev, struct device_driver *drv); // 匹配设备和驱动程序的回调函数
    int (*probe)(struct device *dev);  // 设备探测回调函数
    
    const char      *dev_name;      // 默认设备名称
    struct device   *dev_root;      // 根设备
    struct bus_attribute *bus_attrs;   // 总线级别的属性
    struct device_attribute *dev_attrs; // 设备级别的属性
    struct driver_attribute *drv_attrs; // 驱动程序级别的属性
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env); // 处理用户空间事件的回调函数
    int (*remove)(struct device *dev); // 设备移除回调函数
    void (*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 iommu_ops *iommu_ops;    // IOMMU 操作集
    struct subsys_private *p;       // 子系统私有数据
    struct lock_class_key lock_key; // 锁类键
};

1. 常用API

(1) 注册一个总线

成功返回0。

int bus_register(struct bus_type *bus);

(2)注销一个总线

void bus_unregister(struct bus_type *bus);

(3)在自定义总线目录下,创建属性文件

int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
//struct bus_type *bus :总线目录
//struct bus_attribute *attr :属性文件。

其中bus_attribute结构体如下:

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);
};

2. 代码:注册总线

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc
#include <linux/device.h>

//用于在设备和驱动程序匹配时进行检查.
//这个函数会比较设备的名称和驱动程序的名称,如果相等则返回 1,表示匹配成功;否则返回 0。
int my_match(struct device *dev, struct device_driver *drv)
{
	return ( strcmp(dev_name(dev), drv->name)==0 );
}

//如果驱动程序的 probe 函数存在,则调用它并传递设备指针。
int my_probe(struct device *dev)
{
    struct device_driver *drv = dev->driver;
    if (drv->probe)
        return drv->probe(dev);
    return 0;  // 如果没有 probe 函数,返回 0
}

struct bus_type mybus={
    .name="my_bus",
   	.match=my_match ,
   	.probe =my_probe,
};

static int __init bus_init(void)
{
    int ret;
	ret=bus_register(&mybus);
	if(ret!=0){
      pr_err("bus_register error\n");
	  return -1;
	}
    return 0;
}

static void __exit bus_exit(void)
{
	bus_unregister(&mybus);
}

module_init(bus_init);
module_exit(bus_exit);
MODULE_LICENSE("GPL");

  现象: 向内核注册总线后,会自动在自定义总线目录下生成devices、drivers目录以及其他属性文件。
在这里插入图片描述

3. 代码:在自定义总线目录下创建属性文件。

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc
#include <linux/device.h>

//用于在设备和驱动程序匹配时进行检查.
//这个函数会比较设备的名称和驱动程序的名称,如果相等则返回 1,表示匹配成功;否则返回 0。
int my_match(struct device *dev, struct device_driver *drv)
{
	return ( strcmp(dev_name(dev), drv->name)==0 );
}

//如果驱动程序的 probe 函数存在,则调用它并传递设备指针。
int my_probe(struct device *dev)
{
    struct device_driver *drv = dev->driver;
    if (drv->probe)
        return drv->probe(dev);
    return 0;  // 如果没有 probe 函数,返回 0
}

struct bus_type mybus={
    .name="my_bus",
   	.match=my_match ,
   	.probe =my_probe,
};

//读
ssize_t bus_show(struct bus_type *bus, char *buf)
{
	return sprintf(buf,"%s\n","this is my_bus");
}

//写
ssize_t bus_store(struct bus_type *bus, const char *buf, size_t count)
{
  printk("buf is %s\n", buf);
  return count;
}

struct bus_attribute bus_attr={
      .attr ={
			.name="value",
			.mode=0664,
		},
	  .show =bus_show,
	  .store=bus_store,
};

static int __init bus_init(void)
{
    int ret;
	ret=bus_register(&mybus);
	if(ret!=0){
      pr_err("bus_register error\n");
	  return -1;
	}
	bus_create_file(&mybus,&bus_attr);
    return 0;
}

static void __exit bus_exit(void)
{
	bus_remove_file(&mybus,&bus_attr);
	bus_unregister(&mybus);
}

module_init(bus_init);
module_exit(bus_exit);
MODULE_LICENSE("GPL");

在这里插入图片描述

七、在自己总线下注册一个设备

设备结构体struct device 如下所示,由于成员较多。这里我把本节要使用的放到最前面。

struct device {
	const char		*init_name;  // 设备初始化时的名称
	struct bus_type	*bus;	/* 设备所在的总线类型 */
	void	(*release)(struct device *dev); //设备释放函数
	dev_t			devt;	/* 设备号 */
	
	struct device		*parent;
	struct device_private	*p;
	struct kobject kobj;
	const struct device_type *type;
 	......
};

1. 注册一个设备

如果返回值是 0,表示设备注册成功。

int device_register(struct device *dev);

2. 注销一个设备

void device_unregister(struct device *dev);

★示例代码

bus.c

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc
#include <linux/device.h>

//用于在设备和驱动程序匹配时进行检查.
//这个函数会比较设备的名称和驱动程序的名称,如果相等则返回 1,表示匹配成功;否则返回 0。
int my_match(struct device *dev, struct device_driver *drv)
{
	return ( strcmp(dev_name(dev), drv->name)==0 );
}

//如果驱动程序的 probe 函数存在,则调用它并传递设备指针。
int my_probe(struct device *dev)
{
    struct device_driver *drv = dev->driver;
    if (drv->probe)
        return drv->probe(dev);
    return 0;  // 如果没有 probe 函数,返回 0
}

struct bus_type my_bus={
    .name="mybus",
   	.match=my_match ,
   	.probe =my_probe,
};

EXPORT_SYMBOL_GPL(my_bus);  //导出my_bus这个符号,供其他文件使用。

static int __init bus_init(void)
{
    int ret;
	ret=bus_register(&my_bus);
	if(ret!=0){
      pr_err("bus_register error\n");
	  return -1;
	}
    return 0;
}

static void __exit bus_exit(void)
{
	bus_unregister(&my_bus);
}

module_init(bus_init);
module_exit(bus_exit);
MODULE_LICENSE("GPL");

device.c

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc
#include <linux/device.h>

extern struct bus_type my_bus; //声明外部总线

void device_release(struct device *dev)
{
	 printk("this is device_release");
}

struct device my_device={
    .init_name="my_device",
	.bus=&my_bus,   //在哪个总线下注册设备。
	.release=device_release,
	.devt=((255<<20|0)), //设备号

};

static int __init device_init(void)
{
    int ret;
    ret=device_register(&my_device);
    return ret;
}

static void __exit device_exit(void)
{
   device_unregister(&my_device);
}

module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");

我们先安装bus模块,然后再安装device模块。
在这里插入图片描述

八、在自己总线下注册一个驱动

驱动struct device_driver结构体如下所示。

struct device_driver {
	const char *name;            /* 驱动程序的名称 */
	struct bus_type *bus;        /* 驱动程序支持的总线类型 */
	struct module *owner;        /* 拥有此驱动程序的模块 */
	const char *mod_name;        /* 内置模块使用的模块名称 */
	bool suppress_bind_attrs;    /* 如果为 true,则通过 sysfs 禁用绑定/解绑操作 */
	const struct of_device_id *of_match_table;   /* 用于设备匹配的 Open Firmware 设备 ID 表 */
	const struct acpi_device_id *acpi_match_table; /* 用于设备匹配的 ACPI 设备 ID 表 */
	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);      /* 当设备恢复时调用的恢复函数 */
	const struct attribute_group **groups;   /* 用于 sysfs 的属性组数组 */
	const struct dev_pm_ops *pm;             /* 包含电源管理操作的结构体 */
	struct driver_private *p;                /* 驱动程序的私有数据 */
};

1. 注册一个驱动

int driver_register(struct device_driver *drv);

2. 注销一个驱动

void driver_unregister(struct device_driver *drv);

★示例代码

driver.c

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc
#include <linux/device.h>

extern struct bus_type my_bus;

int my_driver_probe(struct device *dev)
{
	printk("this is my_driver_probe\n");
	return 0;
}

int my_driver_remove(struct device *dev)
{
	printk("this is my_driver_remove\n");
	return 0;
}

struct device_driver my_dircer={
   .name="my_device",  //要和设备名称一致,用于匹配
   .bus=&my_bus,
   .probe =my_driver_probe,
   .remove =my_driver_remove,
};

static int __init device_init(void)
{
    int ret;
    ret=driver_register(&my_dircer);
    return ret;
}

static void __exit device_exit(void)
{
   driver_unregister(&my_dircer);
}

module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");

  设备代码和总线代码使用之前的即可,先将总线和设备模块加载到内核后,安装驱动模块。驱动和设备名称进行匹配,成功后触发驱动probe函数。当设备和驱动其中一个卸载时,触发驱动的remove函数。
在这里插入图片描述
在这里插入图片描述

九、总线、设备、驱动三者关系

  在 Linux 内核中,总线(bus)、设备(device)和驱动(driver)三者之间有着紧密的关系,共同构成了设备模型的基础。关系图如下所示:
在这里插入图片描述

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值