linux系统设备的3种类型:字符设备驱动、块设备驱动和网络设备驱动
字符设备:只能一个一个字节读写数据的设备,不能随机读取设备内存中的某一数据
块设备:可以从设备的任意位置读取一定长度数据的设备
在 /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 fbBlock 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 *);
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);