Linux设备驱动开发(字符设备1)


3.设备驱动简介

在MCU开发编程中,硬件的设备驱动是我们自己写的,在代码中由我们自己去调用它,每个人的写法不同,并没有统一的规范。但是在Linux下的驱动编写,于MCU不同,它有严格的编写规范,哪些该做什么内容都是有严格的要求。

  • Linux开发有两个岗位:BSP开发和应用开发

BSP开发:一般来说开发流程,初始化CPU、内存这些是第一步,然后是串口、shell、文件系统也要起来,然后就是根据需求是否要调一些基本的驱动了。负责板级支持包的开发、调试和维护工作。需要掌握计算机原理方面的知识,操作系统的相关知识,C语言功底,掌握一定的硬件和电路原理方面的知识,熟悉常见的接口协议,如I2C, SPI, UART, USB等。


应用开发:只需要了解Linux驱动的调用API(应用程序编程接口),知道怎么使用它。


Linux内核把设备分成三个类:字符设备、块设备、网络设备。内核针对每一类设备都提供了对应驱动模型架构,包括基本的内核设施和文件系统接口
在这里插入图片描述

  • 字符设备:数据的传输是以字节流的形式传输,如键盘、鼠标、触摸屏、摄像头,LCD显示屏等等。
  • 块设备:数据是以块为单位传输的。如硬盘、U盘等存储设备。
  • 网络设备:网络是linux内核的一大功能模块,网络设备在内核总独立成为一类设备。提供专用API(socket编程)。

3.1字符设备驱动

3.1.1Linux系统下的主设备号和次设备号

字符设备是通过文件系统中的设备名字来存取的,一般放于/dev目录下。字符设备驱动的文件由“ls -l”查看第一列的"c"字母。块设备也出现在/dev目录下面,用"b"字母标识。ls -l命令会出现两个用逗号隔开数字,这两个数字就是特殊设备的主次设备号。前面的为主设备号,后面的为次设备号。下图中的autofs文件是字符设备(c),主设备号10,次设备号235。
在这里插入图片描述
主设备号标识连接的驱动,如上图的autofs由驱动10管理。Linux内核允许多个驱动设备共享主设备号,但是看到的大部分设备仍按照一个主设备号一个驱动的原则。
次设备号被决定应用于哪个设备。主设备号和次设备号两部分,其中高 12 位为主设备号,低 20 位为次设备号, Linux系统中主设备号范围为 0~4095。

#include <linux/kdev.h>
#define MINORBITS 5
#define MINORMASK ((2U << MINORBITS) - 3) 
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
  • 宏定义 MINORBITS 表示次设备号位数,一共是 5 位。
    宏定义 MINORMASK 表示次设备号掩码。
    宏定义 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 5 位。
    宏定义 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 5 位值。

3.2分配和释放设备号

3.2.1静态分配设备号

如果没有指定设备号的话就使用如下函数来申请设备号:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:

int register_chrdev_region(dev_t frist, unsigned int count, char *name)
参数:
	first:要分配的起始设备号。first的次编号部分通常是从0开始,但不是强制的。
	count:请求分配的设备号的总数。注意,如果count太大,你要求的范围可能溢出到下一次编号;但是只要你要求的编号范围可用,一切都任然会正确工作。
	name:设备名字;它会出现在/proc/devices和sysfs中。
	register_chrdev_region的返回值:是0。分配成功:返回0;出错:返回一个负的错误码。

当驱动的主、次设备号申请成功后,/proc/devices里将会出现该设备,但是/dev路径下并不会创建该设备文件。

3.2.2动态分配设备号

字符设备动态注册函数

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
参数:
    dev:只是一个输出参数,保存申请到的设备号。
    baseminor:要使用的被请求的第一个次设备号,常是0;
    count:要申请的设备号个数。
    name:设备名字。 分配成功:返回0。出错:返回一个负的错误码

3.2.3为什么建议使用动态分配主设备号?

  • 一部分主设备号已经静态地分配给了大部分常见设备。在内核源码树的Documentaion/devices.txt中可以找到这些设备的清单
  • 如果我们简单静态的选定一个尚未被使用的编号,如果我们的驱动程序只有我们自己使用,那么这种方法行的通,但是如果驱动程序被广泛使用,那么静态选定的主设备号可能会造成冲突和麻烦
  • 因此,对于一个新的驱动程序,我们强烈建议不要随便选择一个当前未使用的设备号作为主设备号,而应该使用动态分配机制获取主设备号,因此建议使用alloc_chrdev_region函数而不是register_chrdev_region函数
    动态分配的缺点是:由于分配的主设备号不能保证始终一致,所以无法预先创建设备节点。对于驱动程序的一般用法,这倒不是问题,因为一旦分配了设备号,就可以从/proc/devices中读取得到(/proc/devices文件示例如下)。因此,为了加载一个使用动态主设备号的设备驱动程序,对insmod的调用可替换为一个简单的脚本,该脚本在调用insmod之后读取/proc/devices以获得新分配的主设备号,然后创建对应的设备文件

3.2.4释放主次设备号

注销字符设备后要释放掉其设备号,不管是通过alloc_chrdev_region函数还是register_chrdev_region 函数申请的设备号,都使用如下释放函数:

void unregister_chrdev_region(dev_t from,unsigned count)

    参数from:要释放的设备号。
    参数count:表示从 from 开始,要释放的设备号数量。

3.3创建/删除设备结点

3.3.1手动创建设备节点

我们需要创建设备结点文件,需要使用mknod命令。我们也可以在驱动里调用相应的函数,通知应该程序空间自动创建该设备文件。
输入如下命令创建/dev/chrdev1 这个设备节点文件: mknod /dev/chrdev1 c 200 0
“mknod”是创建节点命令,“/dev/chrdev1”是要创建的节点文件,“c”表示这是个字符设备,“200”是设备的主设备号,“0”是设备的次设备号。

3.3.2自动创建设备节点

  • 设备文件创建相关的两个函数:class_create()和device_create()

class_create():在调用device_create()前要先用class_create()创建一个类。类这个概念在Linux中被抽象成一种设备的集合。类在/sys/class目录中。

/dev/tty:终端设备,例如控制台、串口、终端仿真器等。
/dev/input:输入设备,例如鼠标、键盘、触摸屏等。
/dev/sda:磁盘设备,例如硬盘、固态硬盘、U盘等。
/dev/video:视频设备,例如摄像头、视频采集卡等。
/dev/net:网络设备,例如以太网卡、无线网卡等。

class是一个结构体,定义在/include/linux/device.h中,class_create()在内核中是一个宏。

#define class_create(owner, name)		\
({									    \
	static struct lock_class_key __key;	    \
	__class_create(owner, name, &__key);	\
})
    参数owner:struct module结构体类型的指针,一般赋值为THIS_MODULE。
    参数name:char类型的指针,类名。

device_create():创建完类后,还需要在这个类下创建一个设备

struct device *device_create(struct class *class, struct device *parent, 
dev_t devt, void *drvdata, const char *fmt, ...)
参数:
    class:该设备依附的类
    parent:父设备
    devt:设备号(此处的设备号为主次设备号)
    drvdata:私有数据
    fmt:设备名。

device_create()能自动创建设备文件依赖udev这个应用程序。udev是一个工具,能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除。设备文件通常放在/dev下,使用udev后,在/dev目录下就只包含系统中真正存在的设备。

3.3.3删除设备节点

class_destroy()函数:用于从Linux内核系统删除设备节点,定义在/include/linux/device.h中

void class_destroy(struct class *cls)
参数:
	cls:struct class结构体类型的变量,代表通过class_create创建的设备的节点。

3.4字符设备的重要数据结构

3.4.1文件操作结构体file_operations

  • 结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。在系统内部,I/O设备的存取操作通过特定的入口点来进行,而这组特定的入口点恰恰是由设备驱动提供的。通常这组设备驱动程序接口是由结构file_operation结构体向系统说明的,它定义在include/linux/fs.h中

该结构体是底层操作函数的集合

struct file_operations { 
		struct module *owner; 	
		ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); 
		ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
	    int (*open) (struct inode *, struct file *);
	    int (*release) (struct inode *, struct file *);
}

file_operations重要的成员:
owner : 指向拥有该结构体的模块的指针,内核使用该指针维护模块使用计数。
llseek : 用来修改文件的当前读写位置,把新位置作为返回值返回,loff_t是在LINUX中定义的长偏移量 .
read : 用来从设备中读取数据。非负返回值表示成功读取的直接数。
write : 向设备发送数据。
open : 用于打开设备文件。
release:用于释放设备文件,与应用程序的close对应
ioctl : 提供一种执行设备特定命令的方法
unsigned int (*poll) (struct file *, struct poll_table_struct *);系统调用select和poll的后端实现,用这两个系统调用来查询 设备是否可读写,或是否处于某种状态。如果poll为空,则驱动设备会被认为即可读又可写,返回值是一个状态掩码。
int (*mmap) (struct file *, struct vm_area_struct *);将设备内存映射到进程地址空间
  • 驱动内核模块是不需要实现每个函数的。相对应的file_operations的项就为 NULL。
  • 没有显示声明的结构体成员都被gcc初始化为NULL。

3.4.2文件对象file结构体

:文件对象file代表着一个打开的文件。进程通过文件描述符fd与已打开文件的file结构相联系。
:struct file 在<linux/fs.h>中定义。
:struct file的指针常常称为file或者filp(file pointer)。 我们将一直成这个指针为filp以避免核结构自身混淆,file指的是结构体,而filp指的是结构体指针。

struct file {
			union {
					struct list_head		fu_list;
					struct rcu_head
}f_u;

3.4.3索引节点inode结构体

  • 文件打开,在内存建立副本后,由唯一的索引节点inode描述。
  • 与file结构不同: file结构是进程使用的结构,进程每打开一个文件,就建立一个file结构。不同的进程打开同一个文件,建立不同的file结构。
    inode结构是内核使用的结构,文件在内存建立副本,就建立一个inode结构来描述。一个文件在内存里面只有一个inode结构对应。
  • inode结构包含大量描述文件信息的成员变量。但是对于描述设备文件的inode,跟设备驱动有关的成员只有两个。
    dev_t i_rdev; 包含真正的设备号。
    struct file_operations *i_fop;在生成设备文件的时候,这个文件操作成员被赋予一个默认值;
    struct cdev *i_cdev; 这个结构体代表字符设备,这个成员包含一个指针,指向这个结构体,当节点指的是一个字符设备文件时。
  • 从inode中获得主设备号和次设备号的宏:
    unsigned int iminor(struct inode *inode);
    unsigned int imajor(struct inode *inode);

3.4.5三者区别

  • struct inode 结构代表一个实实在在文件,每个文件只对应一个inode;
  • struct file 结构代表一个打开的文件,同一个文件可以对应多个file结构;
  • struct file_operations结构代表底层操作硬件函数的集合;

字符设备驱动流程图

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林同学_ioT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值