linux字符设备驱动
2017-01-23 16:50
411 查看
linux系统设备的3种类型:字符设备驱动、块设备驱动和网络设备驱动
字符设备:只能一个一个字节读写数据的设备,不能随机读取设备内存中的某一数据
块设备:可以从设备的任意位置读取一定长度数据的设备
在 /dev 目录中 执行 ls-l 时
第一个字母为c表示该设备为字符设备,为b表示块设备
typedef u_long dev_t
u_long 在32位机中是4个字节,64位机中是8字节,32位中,高12位为主设备号,低20位为次设备号。
建议使用方法,动态分配设备号函数为 alloc_chrdev_region()
静态申请设备号
int register_chrdev_region(dev_t form,unsigned count,const char *name);
form :要分配的设备号范围的起始值,一般只提供form的主设备号,次设备号通常设置为0
count:需要申请的连续设备号的个数
name:和该范围编号关联的设备名称,不能超过64字节
成功返回0
动态获取设备号
int alloc_chr_dev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
dev : 输出参数,函数成功后保存分配到的设备号或者连续设备号的第一个
baseminor : 要申请的第一个次设备号 通常设为0
count : 要申请的连续设备号个数 与register_chrdev_region()函数参数相同
name : 设备名字 与register_chrdev_region()函数参数相同
释放设备号
不使用设备的时候应该释放设备号
void unregister_chardev_region(dev_t form ,unsigned count);
form:要释放的设备号
count:从form开始要释放的设备号个数
can@ubuntu:~/c/hehe$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyprintk
6 lp
7 vcs
10 misc
13 input
14 sound/midi
14 sound/dmmidi
21 sg
29 fb
Block devices:
1 ramdisk
259 blkext
7 loop
8 sd
9 md
11 sr
65 sd
66 sd
67 sd
68 sd
69 sd
linux内核中使用cdev结构体描述字符设备,其包含了大量字符设备所共有的特性。cdev结构体定义:
list 结构是一个双向链表,用于将其他结构体连接成一个双向链表。该结构在linux内核中广泛使用
cdev结构体的list成员连接到了inode结构体i_devices成员。其中i_devices也是一个list_head结构。cdev结构与inode结点组成了一个双向链表。
每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应字符设备。例如应用程序打开设备文件A,那么系统会产生一个inode结点,通过inode结点的i_cdev字段找到cdev结构体,通过cdev的ops指针,就能找到设备的操作函数。
某个版本linux内核中的file_operation结构体定义
重要成员:
owner:不是一个函数,是一个指向这个结构模块的指针,用来维持引用计数,当模块还在使用时,不能使用rmmod卸载模块,几乎所有时刻被简单初始化为 THIS_MODULE 一个在linux/module.h中的宏定义
llseek():改变文件中的当前读写位置,将新的位置返回 loff_t类型为 long long类型
read() : 从设备获取数据,成功返回读取字节数,失败返回负的错误编码
write() : 用来邪道设备中,成功返回写入字节数,失败返回负错误码
ioctl() : 执行设备特定命令的方法
open() : 打开一个设备,如果被复制为NULL,那么设备永远打开成功,并不会对设备产生影响
release(): 释放被open()函数中申请的资源,将在文件引用计数为0时被系统调用,对应应用程序的close()方法,但仅当对设备文件的所有打开都被释放后才会被调用
字符设备:只能一个一个字节读写数据的设备,不能随机读取设备内存中的某一数据
块设备:可以从设备的任意位置读取一定长度数据的设备
在 /dev 目录中 执行 ls-l 时
第一个字母为c表示该设备为字符设备,为b表示块设备
主设备号和次设备号
一个字符设备或者块设备都有一个主设备号和一个此设备号,统称为设备号。主设备号用来表示一个特定的驱动程序,此设备号用来表示使用该驱动程序的各设备。1.主设备号和次设备号的表示
linux内核中 dev_t类型用来表示设备号,在linux 2.6.34.14中typedef u_long dev_t
u_long 在32位机中是4个字节,64位机中是8字节,32位中,高12位为主设备号,低20位为次设备号。
2.主设备号和次设备号的获取
为了保障可移植性,应该使用宏MAJOR和MINOR获取主设备号和次设备号,MKDEV宏生成设备号,宏的定义如下:#define MINORBITS 20 //次设备号的位数 #define MINORMASK ((1U << MINORBITS)-1) //次设备号掩码 有MINORBITS个1 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //dev右移20位得到主设备号 #define MINOR(dev) ((unsigned int) ((dev) &MINORMASK)) //与次设备号掩码相与得到次设备号 #define MKDEV(ma,mi) (((ma)<<MINORBITS|(mi))
3.分配设备号
驱动开发者静态指定一个设备号,内核开发者已经为常用设备分配了设备号,在内核源码/documentation/devices.txt 中可找到,此方法很可能造成设备号冲突建议使用方法,动态分配设备号函数为 alloc_chrdev_region()
静态申请设备号
int register_chrdev_region(dev_t form,unsigned count,const char *name);
form :要分配的设备号范围的起始值,一般只提供form的主设备号,次设备号通常设置为0
count:需要申请的连续设备号的个数
name:和该范围编号关联的设备名称,不能超过64字节
成功返回0
动态获取设备号
int alloc_chr_dev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
dev : 输出参数,函数成功后保存分配到的设备号或者连续设备号的第一个
baseminor : 要申请的第一个次设备号 通常设为0
count : 要申请的连续设备号个数 与register_chrdev_region()函数参数相同
name : 设备名字 与register_chrdev_region()函数参数相同
释放设备号
不使用设备的时候应该释放设备号
void unregister_chardev_region(dev_t form ,unsigned count);
form:要释放的设备号
count:从form开始要释放的设备号个数
4.查看设备号
当静态分配设备号时,需要查看文件系统中已经存在的设备号,从而决定使用哪个设备号,方法为读取/proc/devices文件,如下所示can@ubuntu:~/c/hehe$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyprintk
6 lp
7 vcs
10 misc
13 input
14 sound/midi
14 sound/dmmidi
21 sg
29 fb
Block devices:
1 ramdisk
259 blkext
7 loop
8 sd
9 md
11 sr
65 sd
66 sd
67 sd
68 sd
69 sd
cdev结构
当申请字符设备的设备号之后,需要将字符设备注册到系统中,才能使用字符设备。linux内核中使用cdev结构体描述字符设备,其包含了大量字符设备所共有的特性。cdev结构体定义:
struct cdev{ struct kobject kobj; //内嵌的kobject结构,用于内核设备驱动模型的管理 struct module *owner; //指向包含该结构的模块的指针,用于引用计数 const struct file_operations *ops; //指向字符设备操作函数的指针 struct list_head list; //该结构将使用该驱动的字符设备连接成一个链表 dev_t dev; //该字符设备的起始设备号,一个设备可能有多个设备号 unsigned int count; //使用该字符设备驱动的设备数量 }
list 结构是一个双向链表,用于将其他结构体连接成一个双向链表。该结构在linux内核中广泛使用
struct list_head { struct list_head *next,*prev; }
cdev结构体的list成员连接到了inode结构体i_devices成员。其中i_devices也是一个list_head结构。cdev结构与inode结点组成了一个双向链表。
每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应字符设备。例如应用程序打开设备文件A,那么系统会产生一个inode结点,通过inode结点的i_cdev字段找到cdev结构体,通过cdev的ops指针,就能找到设备的操作函数。
file_operations结构体
在 Linux/fs.h 中定义,对设备进行操作的抽象结构体,常用的函数有open(),read(),write(),close(),ioctl()等某个版本linux内核中的file_operation结构体定义
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(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t,loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); 4000 int (*ioctl) (struct inode *, struct file *, unsigned int,unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); 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); int (*lock) (struct file *, int, struct file_lock *); ssize_t(*readv) (struct file *, const struct iovec *, unsigned long,loff_t *); ssize_t(*writev) (struct file *, const struct iovec *, unsigned long,loff_t *); ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t,void __user *); 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); };
重要成员:
owner:不是一个函数,是一个指向这个结构模块的指针,用来维持引用计数,当模块还在使用时,不能使用rmmod卸载模块,几乎所有时刻被简单初始化为 THIS_MODULE 一个在linux/module.h中的宏定义
llseek():改变文件中的当前读写位置,将新的位置返回 loff_t类型为 long long类型
read() : 从设备获取数据,成功返回读取字节数,失败返回负的错误编码
write() : 用来邪道设备中,成功返回写入字节数,失败返回负错误码
ioctl() : 执行设备特定命令的方法
open() : 打开一个设备,如果被复制为NULL,那么设备永远打开成功,并不会对设备产生影响
release(): 释放被open()函数中申请的资源,将在文件引用计数为0时被系统调用,对应应用程序的close()方法,但仅当对设备文件的所有打开都被释放后才会被调用
字符设备驱动的组成
常见的设备结构体、加载函数和卸载函数如下:struct xxx_dev{ //自定义设备结构体 struct cdev cdev; //cdev结构体 ... }; static int __init xxx_init(void) { `... //申请设备号,当XXX_major不为0时,表示静态指定,为0时表示动态申请 if(xxx_major) result = register_chrdev_region(xxx_devno,1,"DEV_NAME"); //静态申请设备号 else { result = alloc_chrdev_region(&xxx_devno,0,1,"DEV_NAME"); xxx_major MAJOR(xxx_devno);//获取申请的主设备号 } //初始化cdev结构,传递file_operation结构指针 cdev_init(&xxx_dev.cdev,&xxx_fops); dev->cdev.owner = THIS_MODULE; //指定所属模块 err = cdev_add(&xxx_dev.cdev,xxx_devno,1); //注册设备 } static void __exit xxx_exit(void) { cdev_del(&xxx_dev.cdev); //注销cdev unregister_chrdev_region(xxx_devno,1); //释放设备号 }
file_operations 结构体和其成员函数
大多数字符设备驱动都会实现read(),write()和ioctl()函数,常见写法如下代码所示//文件操作结构体 static const struct file_operations xxx_fops= { .owner = THIS_MODULE, //模块引用,任何时候都赋值 THIS_MODULE .read = xxx_read, //指定设备的读函数 .write = xxx_write, //指定设备的写函数 .ioctl = xxx_ioctl, //指定设备的控制函数 } //读函数 static ssize_t xxx_read(struct file *filp,char _user *buf, size_t size,loff_t *ppos) { ... if(size>8) copy_to_user(buf,...,...); //当数据较大时,使用copy_to_user(),效率较高 else put_user(...,buf); //当数据较小时,使用put_user(),效率较高 ... } //写函数 static ssize_t xxx_write(struct file *filp,const char _user *buf,size_t size,loff_t *oppos) { ... if(size>8) copy_from_user(...,buf,...); //数据较大时 else get_user(...,buf); //数据较小时 ... } //ioctl 设备控制函数 static long xxx_ioctl(struct file *file,unsigned int cmd,unsigned long arg) { ... switch(cmd) { case xxx_cmd1: ... break; ... break; default: return - EINVAL; //内核和驱动程序都不支持该命令时,返回无效的命令 } return 0; }
驱动程序与应用程序的数据交换函数
unsigned long copy_to_user(void _user *to,const void *from,unsigned long n); unsigned long copy_from_user(void *to,const _user *from,unsigned long n); put_user(local,user); get_user(local,user);
相关文章推荐
- 编写Linux并行接口字符设备驱动
- Linux字符设备驱动学习
- Linux内核开发之简单字符设备驱动(下)
- Linux字符设备驱动(二)
- Linux字符设备驱动总结
- Linux字符设备驱动的register_chrdev()与unregister_chrdev()
- s3c2440基于linux的gpio led字符设备驱动实践 [转]
- LINUX—字符设备驱动之-globalmem
- Linux 字符设备驱动中的数据结构
- Linux字符设备驱动的register_chrdev()与unregister_chrdev()
- linux2.6字符设备驱动编程第一例:globalmem
- Linux嵌入式驱动初体验(七)--- LED驱动之字符设备篇
- linux分类驱动对字符设备框架压力的卸载
- 嵌入式Linux字符设备入门之--LED驱动详解
- 第12章 Linux字符设备驱动综合实例
- s3c2440基于linux的gpio led字符设备驱动实践
- Linux 驱动开发-字符设备驱动
- Linux 驱动开发-字符设备驱动一些函数用法
- Linux字符设备驱动的register_chrdev()与unregister_chrdev()
- LINUX—字符设备驱动之-globalmem