字符设备驱动
〇、基本知识
设备驱动分类
(按共性分类方便管理)
1.字符设备驱动
字符设备指那些必须按字节流传输,以串行顺序依次进行访问的设备。它们是我们日常最常见的驱动了,像鼠标、键盘、打印机、触摸屏,还有点灯以及I2C、SPI、音视频都属于字符设备驱动。
字符设备不经过系统快速缓冲。
2.块设备驱动
就是存储器设备的驱动,比如 EMMC、NAND、SD 卡和 U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,按块随机访问,可以用任意顺序进行访问,以块为单位进行操作,因此叫做块设备。数据的读写只能以块(通常是512B)的倍数 进行。与字符设备不同,块设备并不支持基于字符的寻址。
块设备经过设备缓冲
3.网络设备驱动
就是网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴。按TCP/IP协议栈传输。
网络设备面向数据包的接受和发送而设计,它并不对应文件系统的节点
注意:
块设备和网络设备驱动要比字符设备驱动复杂,就是因为其复杂所以半导体厂商一般都编写好了,大多数情况下都是直接可以使用的。
一个设备可以属于多种设备驱动类型,比如USB WIFI,其使用 USB 接口,属于字符设备,但是其又能上网,所以也属于网络设备驱动。
设备驱动框架
为了安全
一切皆文件
为了标准化操作函数,方便对接工作
open read write close
字符设备框架
字符设备驱动编写三部曲
- 注册设备号
- 初始化字符设备
- 实现需要的文件操作
一、注册设备号
为了让内核知道这个设备是合法的,将构造的设备号注册到内核中,表明该设备号已经被占用,如果有其他驱动随后要注册该设备号,将会失败。
- 主次设备号
- MKDEV
- register_chrdev_region
驱动部分
00_头文件
#include <linux/fs.h> //for MKDEV register_chrdev_region
01_主次设备号
#define LED_MA 500 //主设备号 用于区分不同种类的设备
//某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。
#define LED_MI 0 //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1 //有多少个设备
02_注册字符设备号
dev_t devno = MKDEV(LED_MA, LED_MI);
int ret;
ret = register_chrdev_region(devno, LED_NUM, "yhai_led"); /*注册字符设备号(静态分配),为了让内核认可
为一个字符驱动获取一个或多个设备编号
dev_id: 分配的起始设备编号(常常是0)
DEVICE_NUM: 请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
DEVICE_NAME: 是应当连接到这个编号范围的设备的名字
alloc_chrdev_region 可进行动态分配
*/
if (ret < 0) {
printk("register_chrdev_region\n");
return ret;
}
03_取消注册
dev_t devno = MKDEV(LED_MA, LED_MI);
unregister_chrdev_region(devno, LED_NUM); //取消注册
总程序
//led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h> //for MKDEV register_chrdev_region
#define LED_MA 500 //主设备号 用于区分不同种类的设备
//某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。
#define LED_MI 0 //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1 //有多少个设备
static int led_init(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);
int ret;
ret = register_chrdev_region(devno, LED_NUM, "yhai_led"); /*注册字符设备号(静态分配)
为一个字符驱动获取一个或多个设备编号
dev_id: 分配的起始设备编号(常常是0)
DEVICE_NUM: 请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
DEVICE_NAME: 是应当连接到这个编号范围的设备的名字
alloc_chrdev_region 可进行动态分配
*/
if (ret < 0) {
printk("register_chrdev_region\n");
return ret;
}
printk("led init\n");
return 0; //返回值 0:成功 负值:失败
}
static void led_exit(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);
unregister_chrdev_region(devno, LED_NUM); //取消注册
printk("led exit\n");
}
module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明
验证测试
# insmod led.ko /*加载模块
# rmmod led //卸载模块
二、初始化字符设备
连接设备号对应的操作
- file_operations
- cdev_init 连接设备号对应的操作
- cdev_add 添加到散列表,里面放着一堆字符设备。应用层open时根据设备号在散列表中找到设备,open返回的fd找到对应file结构,然后调用相应操作
驱动部分
00_头文件
#include <linux/cdev.h> //字符设备头文件
01_字符设备初始化
struct file_operations led_fops 这部分全是函数指针
struct cdev cdev; //定义字符设备
static int led_open(struct inode *inode, struct file *file)
{
printk("driver led open\n");
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
printk("driver led close\n");
return 0;
}
struct file_operations led_fops = {
//文件操作(一切皆文件)
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
};
cdev_init(&cdev, & led_fops);//字符设备初始化
ret = cdev_add(&cdev, devno, LED_NUM); //添加字符设备到系统中
if (ret < 0) {
printk("cdev_add\n");
return ret;
}
02_字符设备删除
这个删完,再取消注册,相当于把空间中的内容都清掉,再把空间释放
cdev_del(&cdev)
应用部分
交叉编译aarch64-linux-gnu-gcc app.c
//app.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/led", O_RDWR);
if (fd < 0) {
perror("open");
exit(1);
}
printf("open led ok\n"); //注意要加\n 否则打印信息可能没有
return 0;
}
总程序
//led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h> //for MKDEV register_chrdev_region
#include <linux/cdev.h> //字符设备头文件
#de