DAY8-异步通知,驱动模型和platform

283 阅读8分钟

4.异步通知

异步通知不阻塞任何操作,当用户程序添加异步通知后立即返回,当IO条件满足时内核通过向用户进程发送信号(SIGIO)的方式通知用户程序完成IO操作。

(1)用户程序的实现

1)捕获信号SIGIO(signal),信号处理函数中完成具体的IO操作

2)指定当前进程为文件属主

fcntl(fd,F_SETOWN,getpid());              

3)为文件描述符添加FASYNC标志

flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|FASYNC);//调用内核中file_operations中的fasync函数

(2)内核驱动中的实现

1)实现file_operations中的fasync函数

这个函数要调用fasync_helper,实现发送信号到用户空间的机制。

2)当IO条件满足时,调用kill_fasync通知用户进程

5.总结

image-20220802000127148

十二.Linux内核中内存的使用

1.Linux中内存的分配

用户空间:malloc calloc realloc/free

内核空间:kmalloc vmalloc __get_free_pages

需要包含的头文件:

#include <linux/slab.h>
#include <linux/vmalloc.h>
kmalloc:申请指定长度的连续内存空间,分配过程中可能导致睡眠
void *kmalloc(size_t size, gfp_t flags);
​
vmalloc:申请指定长度的内存空间,不保证空间连续,用于申请较大的内存
void *vmalloc(unsigned long size);
​
__get_free_pages:申请指定长度的连续内存空间,按页分配,分配的页数用2的指数次方作为参数
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

2.kmalloc和__get_free_pages的分配方式参数

GFP_KERNEL:普通分配方式,可能导致睡眠
GFP_ATOMIC:原子分配方式,不睡眠,不能中断,调度无法移除
            用于紧急情况下分配内存,可用于中断上下文
GFP_HIGHUSER:分配高端内存
​
__GFP_IO:分配时可以进行IO操作
__GFP_FS:分配过程中可以执行文件系统相关操作
__GFP_WAIT:分配过程中允许阻塞
__GFP_HIGH:紧急分配保留的内存页,必须以原子方式完成,意味着不可中断

Linux内核的虚拟内存分布:

image-20220802000136408

image-20220802000141496

分配和释放:

           ```c
           kmalloc         __get_free_pages            vmalloc
           kfree           free_pages                  vfree  
           ```

2.内核接口mmap的实现

需要包含的头文件:

#include <linux/mman.h>

用户空间mmap对应的内核接口时file_operations中的同名函数,格式如下:

int (*mmap) (struct file *filp, struct vm_area_struct *vma);

mmap的实现必须要有以下两个部分:

     ```c
     1.设置虚拟内存保护
         vma->vm_flags |= VM_IO;
         vma->vm_flags |= VM_RESERVED;
         
     2.建立页表
         int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
     		unsigned long pfn, unsigned long size, pgprot_t prot);
         参数:
             vma - 虚拟内存区域结构体
            addr - 虚拟存储器的起始地址,一般使用vma->vm_start
            pfn - 物理存储器的页号,可以使用vma->vmpgoff,驱动中会替换
            size - 映射区域大小
            prot - VMA的保护属性,一般使用vma->vm_page_prot
        成功返回0,失败返回非0错误码             
     ```

十三.Linux内核驱动模型

1.kobject

kobject是Linux设备模型的基本结构,类似于C++的基类,在实际应用中会嵌入到更大的对象中来描述设备模型,比如device bus driver.......

所有的容器都使用了kobject,通过kobject联系到一起,形成一个树状结构,这树状结构对应/sys目录,每个在内核中注册的kobject对象都会在/sys下有一个目录。

2.kobject的组成

       ```c
       struct kobject {
       	const char		*name;//名字
       	struct list_head	entry;//内核链表,连接到kset
       	struct kobject		*parent;//父对象
       	struct kset		*kset;//所属kset
       	struct kobj_type	*ktype;//指向其对象类型的描述指针
       	struct sysfs_dirent	*sd;//sysfs文件系统该对象对应的文件路径节点指针
       	struct kref		kref;//引用计数
       	unsigned int state_initialized:1;
       	unsigned int state_in_sysfs:1;
       	unsigned int state_add_uevent_sent:1;
       	unsigned int state_remove_uevent_sent:1;
       	unsigned int uevent_suppress:1;
       };
       
       struct kobj_type {
       	void (*release)(struct kobject *kobj);//删除kobject时自动调用的函数
       	const struct sysfs_ops *sysfs_ops;//修改对象属性时调用的函数集合
       	struct attribute **default_attrs;//属性数组
       	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
       	const void *(*namespace)(struct kobject *kobj);
       };
       
       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 *);
       };
       
       struct attribute {
       	const char		*name;//kobject目录下属性文件名
       	umode_t			mode;//文件权限
       #ifdef CONFIG_DEBUG_LOCK_ALLOC
       	struct lock_class_key	*key;
       	struct lock_class_key	skey;
       #endif
       };
       ```

3.如何向内核添加kobject

(1)初始化

使用kobj_type初始化kobject

  ```c
  void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
  ```

(2)向内核添加

		const char *fmt, ...);
//添加时也初始化
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
			 struct kobject *parent, const char *fmt, ...);  

(3)从内核删除

void kobject_del(struct kobject *kobj);

注:kobject_add可以将引用计数+1,kobject_del将引用计数-1

入过引用计数为0,调动release函数释放kobject对象

4.kset

kset是kobject的集合,管理多个kobject结构

image-20220802000154249

kset结构:

struct kset {
	struct list_head list;//连接kset中所有kobject的链表
	spinlock_t list_lock;
	struct kobject kobj;//自身kobject结构
	const struct kset_uevent_ops *uevent_ops;//热插拔操作操作函数集合
};

kset的函数操作接口:

1.注册
    int kset_register(struct kset *k);
2.注销
    void kset_unregister(struct kset *k);
3.创建并添加到内核
    struct kset *kset_create_and_add(const char *name,
				 const struct kset_uevent_ops *uevent_ops,
				 struct kobject *parent_kobj);
    参数:
        name - kset名
        uevent_ops - 热插拔函数集合
        parent_kobj - 父对象指针
   成功返回kset地址,失败返回NULL       

热插拔函数集合:

struct kset_uevent_ops {
	int (* const filter)(struct kset *kset, struct kobject *kobj);//是否传递给用户空间
	const char *(* const name)(struct kset *kset, struct kobject *kobj);//传递给用户空间的名字
	int (* const uevent)(struct kset *kset, struct kobject *kobj,
		      struct kobj_uevent_env *env);//添加用户空间需要的参数和环境变量
};

image-20220802000206807

image-20220802000211614

十四.Linux内核设备总线驱动模型

1.引言

我们之前写的led/btn驱动,如果换了一个硬件平台,硬件连接和CPU可能发生了改变,当前驱动生效,需要进行修改,需要修改的部分是和硬件有关的代码,软件框架几乎不用做任何改变。

为了更加方便移植,能够修改较小的内容达到移植的目的。Linux内核提供了将驱动硬件部分和软件部分分离开来的方法,移植的时候只需要修改其中的硬件部分。

image-20220802000220911

2.内核中实现软硬件分离的机制

设备-总线-驱动模型

image-20220802000227343

以Linux内核中的平台设备总线驱动模型(platform)为例来介绍 设备-总线-驱动模型 的工作机制

3.platform

需要包含的头文件:

#include <linux/platform_device.h>

在内核中,实现了一条虚拟总线,叫做platform_bus_type

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,//软件和硬件匹配的函数
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

这条总线上维护者两条链表,dev(设备)链表,drv(驱动)链表

dev链表中的节点存储硬件信息,节点类型:struct platform_device

struct platform_device {
	const char	* name;//名字,可用于匹配
	int		id;
	struct device	dev;
	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;
};

struct resource {
	resource_size_t start;//硬件起始信息
	resource_size_t end;//硬件结束信息
	const char *name;
	unsigned long flags;//硬件信息类型
	struct resource *parent, *sibling, *child;
};

drv链表中的节点保存软件信息,节点类型:struct platform_driver

struct platform_driver {
	int (*probe)(struct platform_device *);//匹配成功自动调用的函数
	int (*remove)(struct platform_device *);//解除匹配自动调用的函数
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;//包含名字,可以用于匹配
	const struct platform_device_id *id_table;//名字数组,可以用于匹配
};

4.platform的硬件部分和软件部分如何匹配形成一个完整的驱动(何时匹配?如何匹配?)

匹配的动作发生在添加软件节点/硬件节点的时候
匹配通过调用总线提供的match函数

具体的过程:

如果往drv链表中添加一个软件节点(struct platform_dirver),内核就会去遍历dev链表,取出dev链表中的每一个节点和新加入软件节点进行匹配(调用总线的match函数),如果匹配成功,说明软件节点找到了对应的硬件节点,自动调用软件节点中的probe函数,并且将硬件节点的地址传递给该函数,在probe函数中就可以实现完整的驱动。

匹配函数(match)如何工作:

(1)通过设备树传递的硬件信息进行匹配

(2)使用platform_device节点的id_table和platform_dirver中的name匹配

(3)使用platform_device节点的name和platform_dirver中的name匹配

image-20220802000237481

5.编程实现

platform的管理机制内核已经实现,驱动开发者只需要往内核添加节点即可。

(1)添加硬件节点

1)分配初始化resource结构

struct resource xxx_res[] = {
    [0] = {
        .start = 硬件信息的起始值,//IO内存起始地址,IO起始端口号,起始中断号...
        .end = 硬件信息的结束值,
        .flags = 硬件信息的类型,//IO端口号信息,IO内存信息,中断号信息....
    }, 
    ...... 
};

2)分配初始化platform_device结构

struct platform_device xxx_dev = {
    .name = "名称",//用于匹配
    .id = -1,//用于区分同名成员
    .resource = xxx_res,//硬件信息数组首地址
    .num_resources = ARRAY_SIZE(xxx_res),//硬件信息数组长度
    .dev = {
        .release = xxx,//删除硬件节点自动调用的函数
        .platform_data = ***,//传递额外的数据信息
    }
};

3)通过调用platform_device_register向内核添加platform_device

int platform_device_register(struct platform_device *pdev);
//删除
void platform_device_unregister(struct platform_device *pdev);

(2)添加软件节点

1)分配初始化struct platform_driver结构

struct platform_driver xxx_drv = {
    .id_table = 名字数组,//用于匹配,优先级高于name
    .driver = {
        .name = "名称",//用于匹配
    },
    .probe = xxx_probe,//匹配成功自动调用的函数
    .remove = xxx_remove,//解除匹配自动调用的函数    
};

2)通过调用platform_driver_register向内核添加platform_driver

int platform_driver_register(struct platform_driver *drv);
//删除
void platform_driver_unregister(struct platform_driver *drv);

3)内核提供的获取硬件资源信息的函数

struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
参数:
    dev - 硬件节点地址
    type - 获取的资源类型
    num - 获取该类型资源的编号
成功返回资源的地址,失败返回NULL

练习:

完成在匹配函数中获取硬件信息数据并打印出来

作业:

使用platform实现led驱动