当咱们对字符设备进行编程的时候,须要作一些常有的准备工做,获取设备号,对设备文件操做函数的注册,文件信息的初始化,文件的内核表现形式,向内核的注册等等.node
对字符设备的访问是经过文件系统内的设备名称进行的,一般在/dev目录下.
使用ls -l 每行的第一个字符用来识别该文件类型,c就是字符设备驱动文件.b就是块设备驱动文件.
内核经过主次设备号来进行管理设备.
主设备号表示对应的驱动程序(虽然linux容许多个驱动程序共享主设备号,可是绝大部分的设备仍是一个主设备号对应一个驱动程序),次设备号表示具体的设备编号.
使用ls -l的命令,显示在用户组后面的两列数据就是主次设备号.linux
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,失败返回失败码.若是是负数,则该请求的编号区域不可用.app
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:须要释放的设备号的个数.
一般在清除函数中调用.async
已使用主设备号能够在/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);//从系统中移除该字符设备