申嵌Linux驱动开发基础班2-1字符设备驱动开发1
2-1字符设备驱动程序基本结构
字符设备开发的基本步骤
1、确定主设备号和次设备号
2、实现字符驱动程序
实现file_operations结构体
实现初始化函数,注册字符设备
实现销毁函数,释放字符设备
实现字符设备其他基本成员函数
3、创建设备文件节点
相关知识点:
一、设备号的概念和分配、释放:
1、 主设备号是内核识别一个设备的标识。(12bits整数,0到4095,通常使用1到255,由后往前走)
2、 次设备号有内核使用,用于正确确定设备文件所指的设备(20bits,0到1048575,通常使用0到255)
3、 设备编号内部表达
3.1、dev_t类型32bits(用于保存设备编号:主12bits+次20bits)
3.2、从dev_t获得主设备号和次设备号
MAJOR(dev_t) MINOR(dev_t)
3.3、由主、次设备号转化成dev_t类型
MKDEV(int major,int minor)
4、 分配主设备好
4.1、手工分配主设备号:找一个内核没有使用的
#include
int register_chrdev_region(dev_t first, unsigned int count, char* name)
first:要分配的设备编号范围的起始值,次设备号经常为0
count:所请求的连续设备编号的个数(即次设备号有几个)
name:和该编号范围关联的设备名
4.2、动态分配
#include
int register_chr_dev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char* name)
*dev:传入指针,系统自动分配的设备编号,并通过该指针传回。
firstminor:指第一个此设备号(通常为0)
count:所请求的连续设备编号的个数(即次设备号有几个)
name:和该编号范围关联的设备名
5、 释放设备号
void unregister_chr_region(dev_t dev,unsigned int count); //该函数通常在模块清除函数中调用。
二、实现字符设备驱动程序
1、cdev结构体
struct cdev
{
struct kobject kobj; //内勤的kobject对象
struct module *owner; //所属模块
struct file_operations *ops; //文件操作结构(指向file_operations文件操作结构体)
struct list_head list;
dev_t dev; //设备号,前面已分配,现在要初始化
unsigned int count;
}
2、操作cdev的函数
2.1、void cdev_int(struct cdev *cdev,struct file_operations *fops);//用于初始化已分配的cdev结构,并建立cdev和file_operations之间的连接。这里的cdev是已经静态分配的空间,最常使用。
2.2、struct cdev *cdev_alloc(void);//用于动态申请一cdev内存空间。
2.3、int cdev_add(struct cdev *dev,dev_t num,unsigned count)//分别向系统添加一个cdev,完成字符设备的注册,通常在模块加载函数中。
Num:设备号
Count: 有几个设备如果一个填1.
注:一般模块加载,完成设备的注册要调用两个函数首先是cdev_int()然后是cdev_add()
2.4、void cdev_dev(struct cdev *cdev);//分别向系统删除一个cdev,完成字符设备的注销,通常在模块的卸载函数中调用。
3、file_operatins结构体
3.1字符驱动和内核的结构(在include、linux、fs.h中定义)
3.2字符驱动只要实现一个file_operations结构体
3.3当注册到内核中,内核就有了操作此设备的能力。
4、file_operations的主要成员:
struct module *owner: //指向模块自身:THIS——MODULE
open://打开设备
release://关闭设备
read:从设备上读数据
write:向设备上写数据
ioctl:I/O控制函数
llseek:定位当前读写位置指针
mmap:映射设备空间到进程地址空间
4、 file结构体
4.1、file结构:(file_operations结构相关的一个结构体。描述一个正在打开的设备文件)
4.2、成员:
loff_t f_pos:当前读写位置
unsigned int f_flags:标识文件打开时,是否可读可写。
Struct file_operations *f_op:文件相关的操作,指向所实现的struct file_operations
Void *private_data:私有数据指针。驱动程序可以将这个字段用于任何目的或者忽略这个字段
5、 inode结构体
5.1内核用inode结构在内部表示文件
5.2 inode和file的区别
file 表打开的文件描述符
多个表打开的文件描述符的file结构,可以指向单个inode结构。
5.3 inode结构中主要的两个字段
dev_t i_rdev 对表设备文件的inode结构,该字段包含了真正的设备编号
struct cdev *i_cdev
struct cdev 是表示字符设备的内核的内部结构
当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构。
5.4从一个inode中获得主次设备号
Unsigned int iminor (struct inode *inode)
Unsigned int imajor(struct inode *inode)
6、 注册设备,在模块或驱动初始化时调用
Linux-2.6中
Void cdev_init(struct cdev* dev,struct file_operations *fops);
Int cdev_add(struct cdev* dev,dev_t num,unsigned count);
7、 注销设备:在模块的卸载时调用
Linux 2.6
Void cdev_del(struct cdev* dev);
举例:
/ /设备驱动模块加载函数
static int __init xxx_init(void)
{
...
cdev_init(&xxx_dev.cdev, &xxx_fops); / /初始化cdev
xxx_dev.cdev.owner = THIS_MODULE;
if (xxx_major){ / /获取字符设备号
register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
}
else{
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
}
ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1 ) ; / /注册设备
...
}
/*设备驱动模块卸载函数*/
static void __exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no, 1); / /释放占用的设备号
cdev_del(&xxx_dev.cdev); / /注销设备
...
}
申嵌Linux驱动开发基础班2-2字符设备驱动开发2
8、打开int xxx_open(struct inode *inode, struct file *filp)
模块使用计数加1
识别次设备号
硬件操作:
检查设备相关错误(诸如设备未就绪或类似的硬件问题);
如果设备是首次打开,则对其初始化;
如果有中断操作,申请中断处理程序;
9、关闭int xxx_release(struct inode *inode, struct file *filp) ;
模块使用计数减1
释放由open分配的,保存在filp>private_data里的所有内容。
硬件操作:
如果申请了中断,则释放中断处理程序。
在最后一次关闭操作时关闭设备。
10、read/write
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
char __user *buff指向用户空间的缓冲区,这个缓冲区或者保存将写入的数据,或者是一个存放一个新读入数据的空缓冲区。其中__表示危险的操作。
Lofff_t *offp表偏移空间
Size_t count表读写大小
11、用户空间和内核空间之间的数据拷贝过程,
不能简单的用指针操作或者memcpy来进行数据拷贝
用户空间的数据是可以被换出的,会产生一个页面失效异常。
用户空间的地址无法在内核空间中使用。
12、用户空间和内核空间之间进行数据拷贝的函数:
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count) ;(上面的write()函数调用了这个)
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);
(上面的read()函数调用了这个函数)
如果要复制的内存是简单类型,如char、int、long等,put_user()和get_user()
实例:
读设备模板
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count ,loff_t*f_pos){
...
copy_to_user(buf, ..., ... );
...
}
写设备模板
ssize_t xxx_write(struct file *fil p, const char __user *buf , size_t count ,loff_t *f_pos){
...
copy_from_user(..., buf, ... );
...
}
13、ioctl函数
为设备驱动程序执行“命令”提供了一个特有的入口点。
用来设置或者读取设备的属性信息。
int ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
unsigned int cmd事先定义的IO控制命令(参数不一定是这种类型,要进行各种转换)
arg为对应与cmd命令的参数
13.1 cmd 参数的定义
不推荐用0x1,0x2,0x3之类的值
Linux对ioctl()的cmd参数有特殊的定义
构造命令编号的宏:
设备类型(type) |
序列号(number) |
方向(direction) |
数据尺寸(size) |
8bit |
8bit |
2bit |
13/14bit |
_
I O(type,nr)用于构造无参数的命令编号;
_IOR(type,nr,datatype)用于构造从驱动程序中读取数据的命令编号;
_IOW(type,nr,datatype)用于写入数据的命令;
_IOWR(type,nr,datatype)用于双向传输。
type和number位字段通过参数传入,而size位字段通过对datatype参数取sizeof获得。
13.2 ioctl函数模板
int xxx_ioctl( struct inode *inode, struct f ile *filp, unsigned int cmd,unsigned long arg){
...
switch (cmd)
{
case XXX_CMD1:
...
break;
case XXX_CMD2:
...
break;
default: ///*不能支持的命令*/
return - EINVAL;
}
return 0;
}
总结:字符设备驱动结构