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.总结
十二.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内核的虚拟内存分布:
分配和释放:
```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结构
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);//添加用户空间需要的参数和环境变量
};
十四.Linux内核设备总线驱动模型
1.引言
我们之前写的led/btn驱动,如果换了一个硬件平台,硬件连接和CPU可能发生了改变,当前驱动生效,需要进行修改,需要修改的部分是和硬件有关的代码,软件框架几乎不用做任何改变。
为了更加方便移植,能够修改较小的内容达到移植的目的。Linux内核提供了将驱动硬件部分和软件部分分离开来的方法,移植的时候只需要修改其中的硬件部分。
2.内核中实现软硬件分离的机制
设备-总线-驱动模型
以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匹配
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驱动