:字符设备驱动
Posted Ven_J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:字符设备驱动相关的知识,希望对你有一定的参考价值。
编写驱动程序的第一步就是:定义驱动程序为用户程序提供的功能(机制)1、主设备号和次设备号 对字符设备的访问是通过文件系统内的设备名称进行的。那些名称被称为特殊文件、设备文件、或者简单称之为文件系统树的节点,他们通常位于/dev目录下。 一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。 在内核中,dev_t类型用来保存设备号——包括主设备号和次设备号。
dev_t是一个32位的数,前12位用来表示主设备号,后20位用来表示次设备号。如果要获得dev_t的主设备号和次设备号,应该使用下面的宏:
MAJOR宏获得主设备号,MINOR宏获得次设备号。 相反,如果需要将主设备号和次设备号转换成dev_t类型则使用下面的宏:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int)((dev) >> MINORBITS)
#define MINOR(dev) ((unsigned int)((dev) & MINORMASK)
2、分配和释放设备编号 在建立一个字符设备之前,我们的驱动程序 首先要获得一个或多个设备号。完成该工作的函数是:
#define MKDEV(ma, mi) (((ma) << MINORBITS) | (mi))
其中,first是要分配的设备编号范围的起始值。first的次设备号经常被置为0。 count是所请求的连续设备编号的个数。如果count非常大,则所请求的范围可能与下一个主设 备号重叠,但是只要我们所请求的编号范围时可用的,那就没有问题。 name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。 register_chrdev_region的返回值在成功的时候返回0。在错误的情况下,返回一个负的错误码,并且不能使用所请求的编号区域。
#include <linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name)
动态分配所需的主设备号:
dev是仅用于输出的参数,在成功完成调用后将保存已分配范围的第一个编号。firstminor是要使用的被请求的第一个次设备号,它通常是0。count和name跟上面的是一样的意思。
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)
释放设备号
first表示要释放的设备号,count表示从first开始要释放的设备号个数。 通常在模块的清除函数中调用unregister_chrdev_region函数。
void unregister_chrdev_region(dev_t first, unsigned int count)
分配主设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是在编译时指定主设备号的余地。
3、重要的数据结构 大部分的驱动程序操作涉及到三个重要的内核数据结构,分别是file_operations、file、inode
if (scull_major)
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
else
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
scull_major = MAJOR(dev);
if (result < 0)
printk(KERN_WARNING "scull: cannot get major %d\\n", scull_major);
return result;
文件操作 file_opreations结构就是用来将驱动程序操作连接到我们保留的设备编号上的。
inode问题之No space left on device!
struct file_operations
struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES
loff_t (*llseek) (struct file *, loff_t, int);//用来修改文件当前的读写位置
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//从设备中同步读取数据
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//向设备发送数据
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的读取操作
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的写入操作
int (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替
int (*mmap) (struct file *, struct vm_area_struct *); //用于请求将设备内存映射到进程地址空间
int (*open) (struct inode *, struct file *); //打开
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *); //关闭
int (*fsync) (struct file *, struct dentry *, int datasync); //刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据
int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
;