4.Linux 2.6 标准字符设备驱动模型

 

目录

1.Linux 2.6 标准字符设备核心结构

2. 特征

3.静态设备号申请函数:

4.动态设备号申请函数:

5.设备号释放函数

6.核心结构分配函数

7. 核心结构 struct cdev 初始化函数

8.真正的设备注册\注销函数

9.Linux 2.6 字符设备驱动编程步骤

1.模块的加载函数:

2.模块卸载函数:

10.驱动源码:

11.测试源码:

12.驱动测试过程

13.完善驱动代码,增加函数值的判断


 

 

1.Linux 2.6 标准字符设备核心结构

/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

2. 特征

1. 安装后, 不会自动创建/dev/设备文件节点, 需要手动使用 mknod 命令创建

2. 调用一个 cdev_add 注册后, 指定数量的次号被占用完了。 数量可以自己指定,一个主设备可以使用cdev_add 函数注册多次。

3. 设备号使用前需要先申请: register_chrdev_region--静态分配,或 alloc_chrdev_region--动态分配设备号函数申请。

3.静态设备号申请函数:

int register_chrdev_region(

dev_t from, /* 起始设备号(主、次)*/

unsigned count, /* 连续的次设备号数量*/

const char *name) /* 设备名,不需要和/dev/的设备文件名相同*/

头文件: #include <linux/fs.h>

功能: 申请一个设备号范围

参数: from:起始设备号(主、次)

count:连续的次设备号数量

name:设备名,不需要和/dev/的设备文件名相同

返回值: 0:成功;失败:返回负数

4.动态设备号申请函数:

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:成功;失败:返回负数

5.设备号释放函数

void unregister_chrdev_region( dev_t from, /* 起始设备号(主、次)*/

unsigned int count) /* 连续的次设备号数量*/

头文件: #include <linux/fs.h>

功能: 释放一个设备号范围

参数: from:起始设备号(主、次) (包含主次设备号)

count:连续的次设备号数量

返回值: 无

6.核心结构分配函数

核心结构分配函数: 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(); //写在模块加载函数中

7. 核心结构 struct cdev 初始化函数

初始化函数: 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,定义时候的初始无效。

8.真正的设备注册\注销函数

注册函数: 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 结构指针

返回值: 无

9.Linux 2.6 字符设备驱动编程步骤

杂项设备模型:定义杂项设备核心结构—>填充核心结构注册核心结构 这个过程中涉及到成员使用倒推法则实现。

Linux 2.6 模型编写方法相同。

1.模块的加载函数:

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) /* 连续次设备号数量*/

2.模块卸载函数:

做的事情和加载函数相反,顺序也要相反

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 结构。

10.驱动源码:

#include <linux/module.h>

#include <linux/init.h>

//包含必须的头文件

#include <linux/fs.h>

#include <linux/cdev.h>

#include <linux/slab.h>

//以下是文件操作方法的具体实现代码

static int xxx_open(struct inode *pinode, struct file *pfile )

{

printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

return 0;

}

static ssize_t xxx_read(struct file *pfile,char __user *buf, size_t count, loff_t *poff)

{

printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

return count;

}

static ssize_t xxx_write(struct file *pfile,const char __user *buf, size_t count, loff_t *poff)

{

printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

return count;

}

static loff_t xxx_llseek(struct file *pfile, loff_t off, int whence)

{

printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

return off;

}

static int xxx_release (struct inode *pinode, struct file *pfile)

{

printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

return 0;

}

static long xxx_unlocked_ioctl (struct file *pfile,unsigned int cmd, unsigned long args)

{

printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

return 0;

}

//文件操作方法集合指针

static const struct file_operations mymisc_fops = {

.open = xxx_open,

.read = xxx_read,

.write = xxx_write,

.llseek = xxx_llseek,

.release = xxx_release,

.unlocked_ioctl = xxx_unlocked_ioctl,

};

//定义设备名

#define MY_DEVICE_NAME "linux26"

static unsigned int major = 0; //设置设备主设备号

static struct cdev *pcdev; //定义一个 cdev 指针

static dev_t dev_no; //第一个设备号(包含了主和次)

//这个代码只是简单的实现 了 Linux2.6 模型的初始化代码,并没有对函数执行结果进行判断

//后面会对这个代码进行改进

static int __init linux26_device_init(void)

{

// int ret;

//1. 使用 cdev_alloc(void) 分配 cdev 结构空间

pcdev = cdev_alloc();

//2. 申请设备号:动态或静态

alloc_chrdev_region(

&dev_no, /* 存放分配到的第一个设备(包含主次)*/

0 , /* 要分配起始次设备号 */

2, /* 连续的次设备号数量*/

MY_DEVICE_NAME); /* 设备名,不需要和/dev/的设备文件名相同*/

//3. 初始化 cdev 结构

cdev_init(pcdev, /* 需要初始化的核心结构指针*/

&mymisc_fops); /* 字符设备的文件操作方法*/

//4. 注册已经初始化好的 cdev 结构

cdev_add(pcdev, /* 已经初始化的核心结构指针 */

dev_no, /* 起始设备号(包含主次设备号在内)*/

2) ; /* 连续次设备号数量*/

major = MAJOR(dev_no); /* 从设备号中提取主设备号 */

printk(KERN_EMERG"cdev_add ok\n");

printk(KERN_EMERG"major:%d \n", major); //输出主设备号

return 0;

}

static void __exit linux26_device_exit(void)

{

//1. 注销 cdev 结构

cdev_del(pcdev);

//2. 释放设备号

unregister_chrdev_region(dev_no, /* 起始设备号(主、次)*/

2); /* 连续的次设备号数量*/

//3. 释放 cdev 结构空间

kfree(pcdev);

printk(KERN_EMERG"cdev_del ok\n");

}

module_init(linux26_device_init);

module_exit(linux26_device_exit);

MODULE_LICENSE("GPL");

 

11.测试源码:

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h> //lseek

#include <sys/ioctl.h> //ioctl

//定义设备名

#define MY_DEVICE_NAME "/dev/mydev0"

int fd; //存放文件描述符号

char save_buf[1024] = {0}; //存放数据使用

int main(void)

{

int ret;

fd = open(MY_DEVICE_NAME, O_RDWR); //以读写方式进行打开

if(fd < 0) {

printf("open error\r\n");

return -1;

}

printf("fd=%d\r\n", fd); //成功时候输出文件描述符

printf(MY_DEVICE_NAME" open success\r\n"); //成功时候输出文件描述符

//读操作

ret = read(fd, save_buf, 1024);

if(ret < 0) {

printf("open error\r\n");

return -1;

}

//写操作

write(fd, "console out test\r\n", sizeof("console out test\r\n"));

//移动文件指针操作

lseek(fd, 0, SEEK_SET);

//i/o 控制操作

ioctl(fd, 0, 0);

//关闭文件

close(fd);

return 0;

}

12.驱动测试过程

[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 真的可以指定占用多少个次设备号

13.完善驱动代码,增加函数值的判断

驱动直接操作是硬件,如果驱动不可靠,操作系统直接会异常,甚至崩溃。所以,在驱动代码中,凡是执行有可能失败的函数都需要对其返回值进行判断,成功后才可以进入下一步。

修改后代码:

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;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值