Embeded 5 内核⽂件系统介绍devfs sysfs

https://x509p6c8to.feishu.cn/docs/doccn4zBUYymXWFttgAUuqMNFFf

所有的一切,都是为了更好的管理,使用设备。
名词解析:
Linux中设备驱动常见分类字符设备驱动、块设备驱动和网络设备驱动。
字符设备驱动是占用篇幅最大的一类驱动,因为字符设备最多,从最简单的点灯到I2C、SPI、音频等都属于字符设备驱动的类型。杂项设备是一种特殊的类似字符设备的设备。
块设备和网络设备驱动要比字符设备驱动复杂,就是因为其复杂所以半导体厂商一般都给我们编写好了,大多数情况下都是直接可以使用的。
所谓的块设备驱动就是存储器设备的驱动,比如EMMC、NAND、SD卡和U盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做块设备。
网络设备驱动就更好理解了,就是网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴。
一个设备可以属于多种设备驱动类型,比如USB WIFI,其使用USB接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。

  • 字符设备驱动居于文件,以字节单位接受输入、返回输出 file_operations
  • 块设备驱动以基于文件,块单位接受输入、返回输出 block_device_operations
  • 网络设备驱动居于网络套接字 net_device

字符驱动开发分为两步
一、向下把外设驱动起来
根据datasheet编写
二、向上提供外设访问接口
接口访问方式:
-devfs
-procfs
-sysfs
接口内部实现:
-并发&竞争
-阻塞&非阻塞访问
-异步通知

devfs
为什么有devfs文件系统?
linux下有专门的文件系统用来对设备进行管理,devfs和sysfs就是其中两种。在2.6内核以前一直使用的是devfs,devfs挂载于/dev目录下,提供了一种类似于文件的方法来管理位于/dev目录下的所有设备,我们知道/dev目录下的每一个文件都对应的是一个设备。应用通过对这些文件读写、控制以访问实际设备。
devfs的缺点:
第一,不确定的设备映射,有时一个设备映射的设备文件可能不同,例如我的U盘,可能对应sda也可能对应sdb。
第二,没有足够的主/辅设备号,当设备过多的时候,这就是一个问题。
第三,机制和策略都放到内核空间,指定了第一个为/dev/module1 /dev/module2,打开时加载驱动

udev
为什么有udev工具?
正因为上述这些问题的存在,Linux2.6后引入udev,udev是一种工具,它能够根据系统中的硬件设备的状况动态更新设备文件,包括设备文件的创建,删除等。
用户空间的工具udev使用sysfs提供的信息来实现所有devfs的功能的,但不同的是udev运行在用户空间中,而devfs却运行在内核空间,而且udev不存在devfs那些先天的缺陷。很显然,sysfs将是未来发展的方向。
设备文件通常放在/dev目录下,使用udev后,在/dev下面只包含系统中真实存在的设备。它与硬件平台无关的,位于用户空间,需要内核sysfs和tmpfs的支持,sysfs为udev提供设备入口和uevent通道,tmpfs为udev设备文件提供存放空间。
在 android中,取代udev的是vold,我们这里不去过多的讨论为什么android不继续使用udev,但要知道vold的机制和udev是一样 的,理解了udev,也就理解了vold。android一出生就没有遵守传统linux的许多标准,当然也不能指望udev能很好的服务于 android。android社区的选择是别起炉灶,为android定做一套udev,这就是vold了。

sysfs
为什么有sysfs文件系统?
在linux2.6内核以后,引入了一个新的文件系统sysfs,它挂载于/sys目录下,跟devfs一样它也是一个虚拟文件系统,它把实际连接到系统上的设备和总线组织成一个分级的文件,它们可以由用户空间存取,向用户空间导出内核数据结构以及它们的属性。
sysfs分离device_driver、device,两者通过bus_type进行匹配,一旦匹配成功,xxx_driver的probe函数则被执行(xxx为总线名,如platform pci i2c spi usb等)
它是通过kobject子系统来建立这个信息的,当一个kobject被创建的时候,对应的文件和目录也就被创建了,既然每个设备在sysfs中都有唯一对应的目录,那么也就可以被用户空间读写了。
总线、设备、驱动的各个attribute则落实为sysfs的一个目录,attribute会伴随则show和store函数,可被用于用户空间读取对应的sysfs文件。

例如,在引入sysfs之后的内核,通过cdev_init和cdev_add添加字符设备,通过class_create和device_create函数往sys文件系统中添加设备,udev检测到/sys目录的变动会根据变化在/dev目录下创建对应的设备节点。

procfs
操作系统运行时进程和内核信息存放在此。

platform设备驱动
为什么有platform设备驱动框架?
从Linux 2.6起,为了方便开发人员分离总线、设备、驱动这三个实体,引入了一套新的驱动管理和注册机制:platform_bus、platform_device和platform_driver。Linux中大部分的设备驱动,都可以使用这套机制,设备用platform_device表示,驱动用platform_driver进行注册。
平台设备模型与传统的device和driver模型相比,一个十分明显的优势在于平台设备模型将设备本身的资源注册进内核,由内核统一管理。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。

所谓的 platform 驱动并不是独立于字符设备驱动、块设备驱动和网络设备驱动之外的其他种类的驱动。platform 只是为了驱动的分离与分层而提出来的一种框架,其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动.
设备驱动模型侧重于内核对总线、设备和驱动的管理,并向应用层暴露这些管理的信息。
而字符设备驱动、块设备驱动、网络设备驱动则侧重于设备驱动的功能实现。
platform,I2C,SPI等,都是BUS的一种,位于sys/bus/下,I2C、SPI、USB总线控制键本身也连接在platform总线上,这部分是集成在芯片内部。
platform_device(后期可用DTS代替)
platform_driver

设备号申请、注册、创建都需要用原来驱动函数,只不过提供了一种框架,例如通过platform_device 可以适配多个设备。

DTS
为什么有DTS?
DTS(Device Tree Source)设备树源码。
在过去的Linux内核中,arch/arm/plat-xxxx和arch/arm/mach-xxx充斥着大量垃圾代码,这些代码只是在板卡厂商描述某块板卡的硬件细节,但是对内核来说是无意义的,在Linux2.6后,引入了DTS设备树,设备树提供了一种脚本方式,可以板卡厂家把硬件资源相关的代码转换为这种更清晰的脚本管理。

用户空间访问驱动程序的方式
在linux系统中,用户空间访问驱动程序一般有三种方式
1、通过dev设备文件访问
2、通过procfs接口访问
3、通过sysfs访问

通过设备文件“read/write/ioctl”的访问方式有几个明显的缺点。

  • read/write接口功能单一
  • ioctl虽然可以根据cmd参数实现多重功能,但它们都无法直接在shell/mash脚本中被调用,必须通过C语言方式访问
  • ioctl二进制数据接口存在大小端问题,不同平台CPU(32/64)不方便移植除了“设备文件”方式。
    驱动程序还可以实现procfs虚拟文件系统接口,提供给用户访问。procfs访问驱动程序,同样使用的是"read/open/ioctl"接口,因此也存在“设备文件”方式中的类似问题。
    另外也可以通过sysfs虚拟文件系统接口进行访问。

device_attribute
所谓的attibute,主要用于在sys子系统中,就是内核空间和用户空间进行信息交互的一种方法。例如某个driver定义了一个变量,却希望用户空间程序可以修改该变量,以控制driver的运行行为,那么就可以将该变量以sysfs attribute的形式开放出来。
//device_attribute 结构体中重要俩个函数 这里只用了.show .attr包括模式和名字
static struct device_attribute xxxinfo = {
.attr = {
.name = “xxxinfo”,
.mode = 0444,
},
.show = xxxinfo_show,
};
char *drv_info = “I am xxx.ko”;
在drvinfo_show中采用sprintf()直接将信息输出。用户层利用cat就可获取数据信息
static ssize_t xxxinfo_show(struct device *dev,struct device_attribute * attr,char * buf)
{
return sprintf(buf,“driver info is %s\r\n”, drv_info );
}
//在sys子系统下Class目录下创建一个XXX目录,一般在驱动中的probe函数中进行
myclass = class_create(THIS_MODULE, “XXX”);
if(IS_ERR(myclass))
{
printk(“XXX class_create error\n”);
return 0;
}
//在XXX目录下创建一个yyy目录
mydevice=device_create(myclass, NULL, chrdev_no, NULL, “yyy”);

    //在yyy目录下创建xxxinfo节点,通过读取节点即可获取信息版本
    if(device_create_file(mydevice, &drvinfo))
            printk(KERN_ALERT "Unable to create sysfs entry: '%s'\n",drvinfo.attr.name);
    else
            printk(KERN_ALERT "create sysfs file success\r\n");
    //最后在disconnect函数中使用device_remove_file删除
    device_remove_file(mydevice, &xxxinfo);

attribute对应sysfs中的文件,sysfs中的目录来自bus_type、device_drive、device
内核中定义了一些快捷方式便于Attribute的创建工作

相关API
传入主次设备号,创建设备号
typedef u_long dev_t;
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
dev_t devid = MKDEV

查看系统设备号
cat proc/devices

静态申请设备编号
/指定设备编号来静态注册一个字符设备/
int register_chrdev_region(dev_t from, unsigned count, const char *name);  
from: 注册的指定起始设备编号,比如:MKDEV(100, 0),表示起始主设备号100, 起始次设备号为0
count:需要连续注册的次设备编号个数,比如: 起始次设备号为0,count=100,表示0~99的次设备号都要绑定在同一个file_operations操作方法结构体上
*name:字符设备名称(/proc/devices);
当返回值小于0,表示注册失败

动态设备号申请
/动态分配一个字符设备,注册成功并将分配到的主次设备号放入dev里*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
*dev: 存放起始设备编号的指针,当注册成功, *dev就会等于分配到的起始设备编号,可以通过MAJOR()和MINNOR()函数来提取主次设备号
baseminor:次设备号基地址,也就是起始次设备号
count:需要连续注册的次设备编号个数,比如: 起始次设备号(baseminor)为0,baseminor=2,表示0~1的此设备号都要绑定在同一个file_operations操作方法结构体上
*name:字符设备名称
当返回值小于0,表示注册失败

注销字符设备
void unregister_chrdev_region(dev_t from, unsigned count);
from: 注销的指定起始设备编号,比如:MKDEV(100, 0),表示起始主设备号100, 起始次设备号为0
count:需要连续注销的次设备编号个数,比如: 起始次设备号为0,baseminor=100,表示注销掉0~99的次设备号
字符设备驱动结构体
#include <linux/cdev.h>
struct cdev {
struct kobject kobj; // 每个 cdev 都是一个 kobject
struct module *owner; // 指向实现驱动的模块
const struct file_operations *ops; // 操纵这个字符设备文件的方法
struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; // 起始设备编号
unsigned int count; // 设备范围号大小
};

初始化字符设备驱动结构体
并将file_operations结构体放入cdev-> ops 里*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
其中cdev结构体的成员,如下所示:
struct cdev {
struct kobject kobj; // 内嵌的kobject对象
struct module *owner; //所属模块
const struct file_operations *ops; //操作方法结构体
struct list_head list;       //与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev;               //起始设备编号,可以通过MAJOR(),MINOR()来提取主次设备号
unsigned int count;   //连续注册的次设备号个数
};

绑定设备号与字符设备驱动
/将cdev结构体添加到系统中,并将dev(注册好的设备编号)放入cdev-> dev里, count(次设备编号个数)放入cdev->count里/
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

将系统中的cdev结构体删除掉
void cdev_del(struct cdev *p);

创建一个设备类
#include <linux/device.h>
cls=class_create(THIS_MODULE, “hello”);
这个函数是实现创建类的操作。它的本质是在/sys/class/目录创建一个 hello文件夹,文件夹里面的内容仍然为空。

创建对应设备并将其注册到sysfs
device_create(cls,0, MKDEV(major,0), 0, “hello0”); //对应hello_fops1操作结构体
这个函数用来给应用层mdev在/dev下创建设备节点。它的本质是在/sys/module_test文件夹下,创建各种文件,文件的内容是为mdev创建设备节点所服务。
加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,
去/sysfs下寻找对应的类从而创建设备节点。
udev daemon就会自动在/dev下创建my_device设备文件。
device_destroy(cls, MKDEV(major,4));
class_destroy(cls);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值