目录
1.Linux 2.6 标准字符设备核心结构
2. 特征
3.静态设备号申请函数:
4.动态设备号申请函数:
5.设备号释放函数
6.核心结构分配函数
7. 核心结构 struct cdev 初始化函数
8.真正的设备注册\注销函数
9.Linux 2.6 字符设备驱动编程步骤
1.模块的加载函数:
2.模块卸载函数:
10.驱动源码:
11.测试源码:
12.驱动测试过程
13.完善驱动代码,增加函数值的判断
/include/linux/cdev.h
必须实现的成员:
ops: 文件操作方法指针
dev: 起始设备号(包含主设备号和次设备号), dev_t 实际上是一个 u32 类型
count: 本设备要占用次设备号的数量(连续的), 从 dev 中的的次设备号开始。
设备号
Linux2.6 版本以上内核, 使用 dev_t 来表示一个设备号, dev_t 实际上是一个 u32 类型。 其中高 12 位是主设备号,低 20 位是次设备号。
主设备号: dev_t 高 12 位, 2^12=4K (10 是给杂项设备使用)
早期范围 0~255, Linux2.6 范围 0 ~ 4095
次设备号: dev_t 低 20 位, 2^20=1M
早期范围 0~255, Linux2.6 范围 0 ~ 1M-1
合成设备号:
MKDEV(ma,mi): ma: 主设备号; mi: 次设备号
分解设备号:
MAJOR(dev) : 从设备号 dev 中分解出主设备号
MINOR(dev) :从设备号 dev 中分解出次设备号
内核中定义:
/include/linux/kdev_t.h
1. 安装后, 不会自动创建/dev/设备文件节点, 需要手动使用 mknod 命令创建。
2. 调用一个 cdev_add 注册后, 指定数量的次号被占用完了。 数量可以自己指定,一个主设备可以使用cdev_add 函数注册多次。
3. 设备号使用前需要先申请: register_chrdev_region--静态分配,或 alloc_chrdev_region--动态分配设备号函数申请。
int register_chrdev_region(
dev_t from, /* 起始设备号(主、次)*/
unsigned count, /* 连续的次设备号数量*/
const char *name) /* 设备名,不需要和/dev/的设备文件名相同*/
头文件: #include <linux/fs.h>
功能: 申请一个设备号范围
参数: from:起始设备号(主、次)
count:连续的次设备号数量
name:设备名,不需要和/dev/的设备文件名相同
返回值: 0:成功;失败:返回负数
int alloc_chrdev_region(
dev_t *dev, /* 存放分配到的第一个设备(包含主次)*/
unsigned baseminor, /* 要分配起始次设备号 */
unsigned count, /* 连续的次设备号数量*/
const char *name) /* 设备名,不需要和/dev/的设备文件名相同*/
头文件: #include <linux/fs.h>
功能: 申请一个设备号范围
参数: dev:存放分配到的第一个设备(包含主次设备号)
baseminor:要分配起始次设备号
count:连续的次设备号数量
name:设备名,不需要和/dev/的设备文件名相同
返回值: 0:成功;失败:返回负数
void unregister_chrdev_region( dev_t from, /* 起始设备号(主、次)*/
unsigned int count) /* 连续的次设备号数量*/
头文件: #include <linux/fs.h>
功能: 释放一个设备号范围
参数: from:起始设备号(主、次) (包含主次设备号)
count:连续的次设备号数量
返回值: 无
核心结构分配函数: struct cdev *cdev_alloc(void)
头文件: #include <linux/cdev.h>
功能: 在堆空间中分配 一个核心结构, 注意,不使用时候要使用 kfree 函数释放;
参数:无
返回值: 返回分配到 struct cdev 结构空间首地址
说明: 用完记得释放,否则会造成内存泄漏。
第一种方式:
struct cdev k; //定义变量,定义后已经有 struct cdev 结构内存空间
第二种方式:
struct cdev *p; //这种写法只是定义了指针,但是没有分配 struct cdev 结构的内存空间。
p = cdev_alloc(); //写在模块加载函数中
初始化函数: void cdev_init(struct cdev *cdev, /* 需要初始化的核心结构指针*/
const struct file_operations *fops)/* 字符设备的文件操作方法*/
实现源码:
头文件: #include <linux/cdev.h>
功能: 初始化核心结构, 具体做的是清零核心结构,初始化核心结构的 list, kobj, ops 成员
参数:
cdev:需要初始化的核心结构指针
fops:文件操作方法结构指针
返回值: 无
说明: 写这种种驱动模型时候,不需要在定义 struct cdev 结核变量初始化,因为调用 cdev_init 函数时候会把它清 0,定义时候的初始无效。
注册函数: int cdev_add(struct cdev *p, /* 已经初始化的核心结构指针 */
dev_t dev, /* 起始设备号(包含主次设备号在内)*/
unsigned count) /* 连续次设备号数量*/
头文件: #include <linux/ cdev.h>
功能: 注册一个 cdev 结构
参数: p:已经初始化的核心结构指针
dev:起始设备号(包含主次设备号在内)
count:连续次设备号数量
返回值: 成功:返回 0 失败:返回负数
注销函数: void cdev_del(struct cdev *p)
头文件: #include <linux/cdev.h>
功能: 注销一个 cdev 结构
参数: p:前面注册的 struct cdev 结构指针
返回值: 无
杂项设备模型:定义杂项设备核心结构—>填充核心结构注册核心结构 这个过程中涉及到成员使用倒推法则实现。
Linux 2.6 模型编写方法相同。
1. 使用 cdev_alloc(void) 分配 cdev 结构空间
2. 申请设备号:动态或静态
静态申请:
int register_chrdev_region(dev_t from, /* 起始设备号(主、次)*/
unsigned count, /* 连续的次设备号数量*/
const char *name) /* 设备名,不需要和/dev/的设备文件名相同*/
动态申请:
int alloc_chrdev_region(
dev_t *dev, /* 存放分配到的第一个设备(包含主次)*/
unsigned baseminor, /* 要分配起始次设备号 */
unsigned count, /* 连续的次设备号数量*/
const char *name) /* 设备名,不需要和/dev/的设备文件名相同*/
当你不确定哪个号能使用时候,就必须使用动态方式申请。
3. 初始化 cdev 结构
void cdev_init(struct cdev *cdev, /* 需要初始化的核心结构指针*/
const struct file_operations *fops)/* 字符设备的文件操作方法*/
4. 注册已经初始化好的 cdev 结构
int cdev_add(struct cdev *p, /* 已经初始化的核心结构指针 */
dev_t dev, /* 起始设备号(包含主次设备号在内)*/
unsigned count) /* 连续次设备号数量*/
做的事情和加载函数相反,顺序也要相反
1. 注销 cdev 结构
void cdev_del(struct cdev *p)
2. 释放设备号
void unregister_chrdev_region( dev_t from, /* 起始设备号(主、次)*/
unsigned int count) /* 连续的次设备号数量*/
3. 释放 cdev 结构空间
kfree(void*p)
驱动核心:
实现 struct file_operations 结构。
[root@ChenZhiFa home]# ls
app linux26_module.ko
安装驱动模块:
[root@ChenZhiFa home]# insmod linux26_module.ko
[15280.615000] cdev_add ok
[15280.615000] major:249 --->分配得到的主设备号是 249
查看/proc/devices 文件中是否有 源码中注册的设备名 “linux26“
[root@ChenZhiFa home]# cat /proc/devices | grep linux26
249 linux26 -->可以知道源码中申请设备号中的 name 参数是用来标识设备给谁使用
查看是否会自动创建设备文件:
[root@ChenZhiFa home]# ls /dev/linux26 -l
ls: /dev/linux26: No such file or directory --->不会自动创建设备文件
手动创建设备文件:主设备号和次设备号都是正确的:
[root@ChenZhiFa home]# mknod /dev/mydev0 c 249 0
运行 app 测试驱动:
[root@ChenZhiFa home]# ./app [15464.165000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15464.165000] line:14, xxx_open is call [15464.165000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15464.165000] line:22, xxx_read is call [15464.165000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15464.165000] line:29, xxx_write is call [15464.180000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15464.180000] line:36, xxx_llseek is call [15464.190000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15464.190000] line:53, xxx_unlocked_ioctl is call [15464.205000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15464.205000] line:44, xxx_release is call fd=3 /dev/mydev0 open success 删除上一步手动创建的设备文件,新建立一个次设备不同的,但是也在注册范围内的设备文件: [root@ChenZhiFa home]# rm /dev/mydev0 [root@ChenZhiFa home]# mknod /dev/mydev0 c 249 1 代码中申请了 2 个次设备号: 0, 1,所以 1 也是合法的次设备号。 使用新的设备文件测试:结果成功的。 [root@ChenZhiFa home]# ./app [15581.625000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15581.625000] line:14, xxx_open is call [15581.625000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15581.625000] line:22, xxx_read is call [15581.625000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15581.625000] line:29, xxx_write is call [15581.635000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15581.635000] line:36, xxx_llseek is call [15581.650000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15581.650000] line:53, xxx_unlocked_ioctl is call [15581.660000] file:/mnt/hgfs/share/20160909device_drvier/04_linux26_device/linux26_module.c [15581.660000] line:44, xxx_release is call fd=3 /dev/mydev0 open success删除上一步手动创建的设备文件,新建立一个次设备不同的,但是不在注册范围内的设备文件:
[root@ChenZhiFa home]# rm /dev/mydev0
[root@ChenZhiFa home]# mknod /dev/mydev0 c 249 2
代码中申请了 2 个次设备号: 0, 1,所以 2 也不是合法的次设备号。
使用新的设备文件测试:结果失败的。
[root@ChenZhiFa home]# ./app
open error 测试结果失败,说明了 Linux2.6 真的可以指定占用多少个次设备号。
驱动直接操作是硬件,如果驱动不可靠,操作系统直接会异常,甚至崩溃。所以,在驱动代码中,凡是执行有可能失败的函数都需要对其返回值进行判断,成功后才可以进入下一步。
修改后代码:
static int __init linux26_device_init(void) { int ret = -1; //1. 使用 cdev_alloc(void) 分配 cdev 结构空间 pcdev = cdev_alloc(); if(pcdev == NULL) { printk(KERN_EMERG" cdev_alloc error\n"); ret = -ENOMEM; /* 分配失败一般是由于内存不足导致的 */ return ret; } //2. 申请设备号:动态或静态 ret = alloc_chrdev_region(&dev_no, 0 , 2, MY_DEVICE_NAME); if(ret < 0 ) { //释放前面成功的资源 kfree(pcdev); /* 3.释放 cdev 结构空间 */ printk(KERN_EMERG"alloc_chrdev_region error\n"); return ret; } //3. 初始化 cdev 结构 cdev_init(pcdev, &mymisc_fops); /* 字符设备的文件操作方法*/ //4. 注册已经初始化好的 cdev 结构 ret = cdev_add(pcdev, dev_no, 2) ; if(ret < 0 ) { //释放前面成功的资源 unregister_chrdev_region(dev_no, 2); /* 3.释放前面申请的调和号*/ kfree(pcdev); /* 3.释放 cdev 结构空间 */ printk(KERN_EMERG"alloc_chrdev_region error\n"); return ret; } major = MAJOR(dev_no); /* 从设备号中提取主设备号 */ printk(KERN_EMERG"cdev_add ok\n"); printk(KERN_EMERG"major:%d \n", major); //输出主设备号 return 0; } 使用 goto 语句后修改的错误检测代码: static int __init linux26_device_init(void) { int ret = -1; //1. 使用 cdev_alloc(void) 分配 cdev 结构空间 pcdev = cdev_alloc(); if(pcdev == NULL) { printk(KERN_EMERG" cdev_alloc error\n"); ret = -ENOMEM; /* 分配失败一般是由于内存不足导致的 */ goto err_cdev_alloc; } //2. 申请设备号:动态或静态 ret = alloc_chrdev_region(&dev_no, 0 , 2, MY_DEVICE_NAME); if(ret < 0 ) { printk(KERN_EMERG"alloc_chrdev_region error\n"); goto err_alloc_chrdev_region; } //3. 初始化 cdev 结构 cdev_init(pcdev, &mymisc_fops); /* 字符设备的文件操作方法*/ //4. 注册已经初始化好的 cdev 结构 ret = cdev_add(pcdev, dev_no, 2) ; if(ret < 0 ) { printk(KERN_EMERG"alloc_chrdev_region error\n"); goto err_cdev_add; } major = MAJOR(dev_no); /* 从设备号中提取主设备号 */ printk(KERN_EMERG"cdev_add ok\n"); printk(KERN_EMERG"major:%d \n", major); /*输出主设备号*/ return 0; //如果没有写这一条,会导致驱动注册成功但是打不开设备。 err_cdev_add: //释放前面成功的资源 unregister_chrdev_region(dev_no, 2); /* 3.释放前面申请的调和号*/ err_alloc_chrdev_region: //释放前面成功的资源 kfree(pcdev); /* 3.释放 cdev 结构空间 */ err_cdev_alloc: return ret; }