Linux设备驱动开发(5.4.58)-3-NEWCHR
本文目标
本文在上一节的基础上,采用可以指定主设备号,次设备号,手动申请dev_t和自动申请dev_t的API,封装一个全局的对象结构体并用private_data在方法间传递,减少全局变量使用,寄存器操作改用readl和writel,引入cdev、class、device对象。
驱动里面的全局结构体是对象私有数据,fops是对象内部的操作函数,可以通过系统调用对外public。
cdev、class、device应该是驱动对象的各种父类,用来抽象各种子系统的数据和行为,以便实现统一框架。
以上是我个人理解,没有严格意义上的验证。
API介绍
I/O内存访问函数:
采用ioremap将物理地址映射到虚拟地址之后,我们可以直接通过指针访问这些地址,但是Linux内核不建议这么做,而是提供了一系列函数。
1、读操作函数
u8 readb(const volatile void __iomem *addr);
u16 readw(const volatile void __iomem *addr);
u32 readl(const volatile void __iomem *addr);
从返回值类型可以看出其功能。
2、写操作函数
void writeb(u8 value,volatile void __iomem *addr);
void writew(u16 value,volatile void __iomem *addr);
void writel(u32 value,volatile void __iomem *addr);
3、register_chrdev的不足:
ret = register_chrdev(major,module_name,&ldd2_fops);
需要提前知道系统里面不冲突的major,使用过程中发现代码里写的100这个号不能用,难道系统集成商需要把程序员叫过来重新改?
另外,这个API会把一个主设备号下面的2^20-1个次设备号全部用掉。
所以,自然而然有人提出了以下api:
a、指定主设备号,和次设备号个数,一次n个,不浪费。
int register_chrdev_region(dev_t from,unsigned count,const char *name);
b、不指定设备号,只需要指定次设备号的基号,自动分配n个,完美。
int alloc_chrdev_region(dev_t *devid,unsigned baseminior,unsigned count,const char *name);
这个dev_t由内部生成,拿到之后,用下面的宏得到设备号:
major = MAJOR(devid);
minor = MINOR(devid);
释放:
void unregister_chrdev_region(dev_t from,unsigned count);
cdev对象绑定成员函数和设备id
struct cdev{struct kobject kobj; //基础数据结构struct module *owner; //module驱动this指针const struct file_operations *ops; //对象方法struct list_head list;dev_t dev; //对象idunsigned int count;
};
在构造的时候,需要一个全局cdev,以及一个全局的file_operations,并通过下面函数构造:
void cdev_init(struct cdev *cdev,const struct file_operations *fops);
然后将构造好的对象,和申请到的devid绑定:
int cdev_add(struct cdev *cdev,dev_t dev,unsigned count);
删除:
void cdev_del(struct cdev *cdev);
如何让驱动自动创建/dev/xxx
老的设备驱动,需要在加载模块之前,在脚本里,在脚本里加上mknod …,这个也是不合理,因为使用者还需要去查看驱动代码,得知其设计的设备节点号是多少,而且后面就不能改了,万一冲突了咋办?
基于这个需求,内核牛人想出了在内核里搞一个框架,功能如下:
1、加载驱动时,自动创建设备文件,卸载时将它删除。
2、设备驱动可以指定设备名,所有者和权限位,应用同样可以修改。
3、不再需要指定主设备号,给register_chrdev传递0获得可用的主设备号,后续调用devfs_register指定次设备号即可。
这玩意叫做devfs,主要目的是在模块加载和卸载的时候,调用如下接口进行文件系统操作。
ret=register_chrdev(XXX_MAJOR,DEVICE_NAME,&xxx_fops);devfs_handle = devfs_register(NULL,DEVICE_NAME,DEVFS_FL_DEFAULT,XXX_MAJOR,0,S_IFCHR|S_IRUSR,&xxx_fops,NULL);devfs_unregister(devfs_handle);
但这个devfs已成过往,因为内核维护者认为这个devfs有缺点:
1、devfs的工作可以在用户态完成。
2、devfs有无法修复的bug。
3、主要原因是有人认为这个设计是不合理的,policy不应该位于内核态,内核提供的是机制,机制和策略应该是分离的。内核提供的机制是某样事情的固定步骤、方法、而策略是每个步骤采取不同的方式,即用户决定的。机制是稳定的,而策略是灵活的。
比如:内核提供API给人们调节线程优先级,但内核不管具体哪个线程是什么优先级,既然如此,devfs为何要在驱动层直接帮用户创建了设备文件,设置了名字,还设置好了权限呢?
所以,核心原因是devfs管了不该管的事情:)
因此,devfs被位于用户态的udev取代了。
udev设计的出发点:
内核驱动可以实现一个功能,但不能限制如何使用这个功能,关于使用的各种灵活度,应该放在用户空间。
比如,对于devfs,使用mydrv的第一个设备被命名为dev1,第二个为dev2,…devn,但是在用户空间其实可以自由命名。
udev具体实现:
利用hotplug event来工作,热插拔时,设备的详细信息会由内核通过netlink套接字发送出来,发出的事情叫uevent。另外,/sys/module/psmouse/uevent是用来处理冷插拔的(此处以psmouse为例),往里面写入一个add,内核就会重新发出uevent事件,解决冷插拔的问题。用户态只需要有一个进程监听这个套接字即可。
devfs和udev的驱动加载时机:
devfs在设备节点被打开的时候,会加载驱动,无论节点对应的设备是否存在。
而udev认为,设备发出热插拔事件的时候,才需要创建节点,并加载驱动,这个比较好理解,确实更合理,当然用户态有一个进程来监听热插拔事件的UDP socket(PF_NETLINK)。
我们可以根据udev捕获到的消息,创建一个规则,以便每次插入u盘的时候,为u盘创建一个/dev/huqiaoxin_udisk的软链接。
规则文件的大概意思是,新硬件属于什么class,比如net,需要执行什么动作,比如add,mac地址跟xxx匹配,deviid属性跟yyy匹配,type属性为zzz,此时,对这个硬件在udev层面执行的动作是创建/dev/ooo。
udev的衍生
mdev是嵌入式版本,集成到busybox里了。
vold是android版本的。
sysfs和proc
两者都是虚拟文件系统,是为了提供一个给应用层获取硬件和进程信息的直观窗口。
sysfs提供了设备、总线、驱动模型的信息,proc提供了进程和状态信息。
个人理解为,proc以进程为单位来展示系统,sysfs以硬件为单位来展示系统,角度不同。
sysfs下面的block、bus、dev、devices、class、fs、kernel、power、firmware含义
block过滤所有的devices里面的块设备。
devices包含所有设备。
dev按照节点设备号排序和组织,指向具体的devices。
bus按总线分类,比如spi,i2c等,下面有设备和驱动,这里体现总线设备模型
class包含设备类型,也可以叫子系统吧,比如gpio、input、i2c-master等
大部分都是软链接,指向devices目录下的条目。
画一个图可能更清晰:
对总线设备驱动的个人理解
个人理解,总线是抽象出来的,用于操作各个ic平台硬件控制器的通用接口,打个比方,可以把大量ic的i2c控制接口统一了,否则的话,每个厂家的操作方式都不一样。因此,总线是基于内部控制器层面的方法集合,是面向内部控制器的,因为驱动要通信,少不了调用控制器接口。
而设备,是挂在特定总线上具体的物理硬件,也可以理解为DTS里面描述的信息,所以他是数据。
驱动,是针对特定硬件的操作方法,所以,他是控制器外挂载的设备对应的操作方法集合,我们要干的就是这个事情,维护他的数据和方法。
这里的类,则是根据相似行为,抽象出的共同基类,用于简化驱动的实现,同时也是为了统一框架,避免应用层调用驱动的差异化,这样框框定好之后,应用层可以统一实现。硬件变了,驱动变了,应用一行代码不用改,照样可以用。否则的话,同一个功能,一万个人有一万种接口定义方法。
总而言之,一个设备接好之后,他对应的控制器就定了,那么这个方法集合就定了,有现成的一堆给你。同时,他是什么东东,也是定了的,无非是从class下面选一个,或多个,那么又有了一对操作集合。这样看的话,我们写驱动就是给设备定性,获得一堆父类的现有操作接口,而个性化的东西在驱动里面自己整就好了。其中最重要的是,这个关联关系确定之后,底层会自动的做很多事情,比如probe、match之类的,这就是抽象和框架的好处。
欢迎留言讨论。
Linux设备驱动开发(5.4.58)-3-NEWCHR相关推荐
- Linux 设备驱动开发 —— 设备树在platform设备驱动中的使用
关与设备树的概念,我们在Exynos4412 内核移植(六)-- 设备树解析 里面已经学习过,下面看一下设备树在设备驱动开发中起到的作用 Device Tree是一种描述硬件的数据结构,设备树源(De ...
- 《Linux设备驱动开发详解 A》一一2.3 接口与总线
本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...
- Linux设备驱动开发概述
作者:宋宝华 email:author@linuxdriver.cn 在过去这些年,Linux已经成功应用于服务器和桌面系统,而近年来,随着嵌入式系统应用的持续升温,Linux也开始广泛应用于嵌入式领 ...
- linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar
压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...
- Linux 设备驱动开发思想 —— 驱动分层与驱动分离
前面我们学习I2C.USB.SD驱动时,有没有发现一个共性,就是在驱动开发时,每个驱动都分层三部分,由上到下分别是: 1.XXX 设备驱动 2.XXX 核心层 3.XXX 主机控制器驱动 而需要我们编 ...
- linux设备驱动开发专业论坛www.linuxdriver.cn诚征斑竹
www.linuxdriver.cn诚征斑竹,一起繁荣Linux设备驱动开发专业论坛的人气,共同致力于提高中国工程师的Linux设备驱动开发水平,普及Linux设备驱动开发知识.[url]www.li ...
- 《Linux设备驱动开发详解》学习笔记一
Linux设备驱动开发详解学习笔记<一> 书名:<Linux设备驱动开发详解>第二版 主机环境:Linux version 2.6.25-14.fc9.i686@Fedora ...
- 6月14日Linux设备驱动开发免费讲座PPT
这次讲座主要针对的是有一定开发经验的人士,谈了一些开发体会. 6月14日Linux设备驱动开发免费讲座PPT [url]http://www.linuxdriver.cn/20086/20086159 ...
- 《Linux 设备驱动开发详解(第2版)》——1.4 Linux设备驱动
本节书摘来自异步社区<Linux 设备驱动开发详解(第2版)>一书中的第1章,第1.1节,作者:宋宝华著,更多章节内容可以访问云栖社区"异步社区"公众号查看 1.4 L ...
最新文章
- Jquery each和map 的区别
- 原因代码10044-Erdos number Time limit exceeded
- Matlab atan2
- 单例模式引发的内存泄漏:_资源泄漏:救援的命令模式
- 函数参数传递、数组指针、二级指针、左值、引用
- c语言指针慕课,C语言-指针
- Linux 下 Open××× 安装和 Windows Open××× GUI 安装笔记
- 及时复盘的好处_还不会复盘?这篇有最全的复盘介绍
- ofo这事吧,其实也挺好的
- Leetcode 颜色分类
- autojs读取文字_Auto.js 获取识别图片文本
- 计算机内存延迟,内存延迟有多重要?游戏性能测试说明真相:酷睿i9-9900K依然无敌...
- WebServer项目的亮点和难点
- 城市地铁站点接驳公交多目标优化方法
- 小习题:猴子吃桃问题
- 解决“E: Package ‘libqtgui4‘ has no installation candidate”无法安装qt4
- 状态同步和帧同步的优缺点
- python 爬虫斗图吧 多页图片
- java题算工资_Java模版方法的小练习——工资系统
- ubuntu windows远程桌面 xfce4