字符设备驱动编程(一)

当我们对字符设备进行编程的时候,需要做一些常有的准备工作,获取设备号,对设备文件操作函数的注册,文件信息的初始化,文件的内核表现形式,向内核的注册等等.

对字符设备的访问是通过文件系统内的设备名称进行的,通常在/dev目录下.
使用ls -l 每行的第一个字符用来识别该文件类型,c就是字符设备驱动文件.b就是块设备驱动文件.
内核通过主次设备号来进行管理设备.
主设备号表示对应的驱动程序(虽然linux允许多个驱动程序共享主设备号,但是绝大部分的设备还是一个主设备号对应一个驱动程序),次设备号表示具体的设备编号.
使用ls -l的命令,显示在用户组后面的两列数据就是主次设备号.

dev_t:<linux/types.h>一个32位的数,12位表示主设备号,20位表示次设备号.
为了避免造成冲突,我们不能自己直接定义主次设备号,应该使用函数来获得主次设备号.

<linux/kdev_t.h>
MAJOR(dev_t dev);//获得主设备号
MINOR(dev_t dev);//获得次设备号
MKDEV(int major,int minor);//将主次设备号转化为dev_t类型

<linux/fs.h>
int register_chrdev_region(dev_t from,unsigned int count,char * name);//获得设备号
参数:from:已经知道了主设备号的设备号
count:请求连续设备编号的个数,如果很大会和下一个主设备号重叠,但是只要是分配通过的,都是可以正常使用的.
name:该类型设备的名称.
返回值:成功返回0,失败返回失败码.如果是负数,则该请求的编号区域不可用.

int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);//获取主设备号
参数:dev:用于输出,在成功调用后保存以分配的第一个编号
firstminor:要使用使用的第一个次设备号,通常为0
count:请求连续设备编号的个数,如果很大会和下一个主设备号重叠,但是只要是分配通过的,都是可以正常使用的.
name:该类型设备的名称.
返回值:成功返回0,失败返回失败码.

void unregister_chrdev_region(dev_t first,unsigned int count);//释放设备编号
参数:first:释放的设备编号的一个编号
count:需要释放的设备号的个数.
通常在清除函数中调用.

已使用主设备号可以在/proc/devices文件中查看
/sys/dev目录下有详细的设备号的使用情况

字符设备使用文件操作方式进行操作
<linux/fs.h>
file_operations:结构体,内核开放给驱动的一个接口,通过这个结构体可以将对设备文件的读写等操作和驱动的读写操作连接起来.

struct file_operations {
struct module *owner;//拥有该模块的指针
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);//读取目录
/*poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
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 *, int datasync);//fsync 系统调用的后端, 用户调用来刷新任何挂着的数据
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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
};

实现接口的对接:
struct file_operations op = {
.owner = THIS_MODULE,
.read = test_read,
.write = test_write,
};

file结构体:打开的文件的一些信息

inode结构体:表示文件
dev_t i_rdev:对表示设备文件的inode结构,该字段包含了真正的设备编号
struct cdev *i_cdev:表示字符设备的内核的内部构建,当indoe指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针

unsigned int iminor(struct inode *inode);//从indoe中获得次设备号
unsigned int imajor(struct inode *inode);//从indoe中获得主设备号

<linux/cedv.h>
cedv结构体:表示字符设备

struct cdev *my_cdev = cdev_alloc();//申请空间
cdev->owner = THIS_MODULE;//所有者
//注册接口函数
struct file_operations *fops;
cdev_init(my_cdev,fops);

int cdev_add(struct cdev *p, dev_t dev, unsigned count);
//告诉内核该结构的信息,添加完了以后,这个设备就可以使用了,所以要所有东西初始化完成以后才能进行操作
参数:保存有信息的cdev结构体指针
dev设备号
count:设备数量,通常为1
返回值:0成功,错误返回错误码

void cdev_del(struct cdev *p);//从系统中移除该字符设备

原文地址:https://www.cnblogs.com/CHYI1/p/5592061.html