一、设备号
设备号又分为:主设备号和次设备号。
“主设备号” 用来标示“设备文件”与哪个驱动程序相对应。“次设备号”被驱动程序使用,用来辨别操作的是哪个设备。
小结:主设备号用来辨别设备类型,次设备号用来辨别同一类型设备中的具体哪台设备。
1、内核中如何描述设备号?
答:用一个 dev_t 类型的变量来描述;
其实质为一个 unsigned int 32 位的整数,其中高12位为主设备号,低20位为次设备号。
2、如何从 dev_t 中分解出主设备号
答:使用宏 MAJOR (dev_t dev) 来分解
3、如何从dev_t 中分解出次设备号
答: 使用宏 MINOR (dev_t dev)
4、如何分配主设备号?
两种方法: 静态申请 和 动态分配
4.1 静态申请
4.1.1 方法:
(1) 根据Documentation/devices.txt ,确定一个没有使用的主设备号。
(2) 使用 register_chardev_region 函数注册设备号
优点:简单
缺点:容易冲突。
4.1.2 register_chardev_region 函数的介绍:
(1)函数原型: int register_chardev_region (dev_t from, unsigned count, const char * name)
(2)功能:申请使用从from 开始的count个设备号(主设备号不变,次设备号增加)
(3)参数:
from:希望申请使用的设备号
count:希望申请使用设备号数目
name:设备名(体现在/proc/devices)
4.2 动态申请
4.2.1 方法:
使用函数alloc_chardev_region 分配设备号
优点:简单,易于驱动推广
缺点:无法在安装驱动前创设备文件
解决办法:安装驱动后,从 /proc/devices 中查询设备号
4.2.2 alloc_chardev_region 函数讲解
(1)函数原型: int alloc_chardev_region (dev_t * dev, unsigned baseminor, unsigned count,const char * name);
(2)功能:请求内核动态分配count个设备号,且次设备号从baseminor开始。
(3)参数:
dev :分配到的设备号
baseminor:起始次设备号
count:需要分配的设备号数目
name:设备名(体现在/proc/devices)
5、注销设备号
不论使用何种方法分配设备号,都应该在不使用它们时释放这些设备号;
函数: void unregister_chardev_region(dev_t from, unsigned count);
功能:释放用from开始的count个设备号
二、创建设备文件
2种方法:(1)使用mknod 命令,手工创建 (2)自动创建
1、手工创建
(1)mknod方法:mknod filename type major minor
(2)参数:
filename:设备文件名
type:设备文件类型
major:主设备号
minor:次设备号
三、常见数据结构
1、struct File
代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的struct file 。它由内核在打开文件时创建,在文件关闭时释放。
重要成员:
(1) loff_t f_pos /* 文件读写位置,loff_t 就是一个整形的变量*/
(2)struct file_operations * f_op
2、struct inode
用来记录文件的物理上的信息。它和代表打开文件的file结构不同。
一个文件可以对应多个 file 结构,但只有一个 inode 结构。
重要成员:
(1)dev_t i_rdev :设备号
3、struct file_operations
一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为NULL。
小结:它实际上就是一个表,用来把应用程序中的函数调用转换为驱动程序中相应的函数,eg:
- struct file_operations mem_fops={
- .owner = THIS_MODULE,
- .llseek = mem_seek,
- .read = mem_write,
- .ioctl = mem_ioctl,
- .open = mem_open,
- .release = mem_release,
- };
四、设备注册
在linux 2.6 内核中,字符设备使用 struct cdev 来描述
1、字符设备的注册可分为3个步骤:
(1)分配 cdev
(2)初始化 cdev
(3)添加 cdev
2、设备分配方法: cdev_alloc
struct cdev 的分配可使用 cdev_alloc函数来完成: struct cdev * cdev_alloc (void);
3、设备初始化方法:cdev_init
struct cdev 的初始化使用 cdev_init 函数来完成。
void cdev_init (struct cdev * cdev, const struct file_operations * fops)
参数:
cdev : 待初始化的cdev结构
fops: 设备对应的操作函数集
4、设备添加方法:cdev_add
struct cdev 的注册使用 cdev_add 函数来完成。
int cdev_add ( struct cdev *p,dev_t dev , unsigned count)
参数:
p: 待添加到内核的字符设备结构
dev: 设备号
count:添加的设备个数
完成上面的3步操作,那么就完了驱动程序的注册,下一步要做的就是实现设备所支持的操作,即:实现 const struct file_operations * fops
五、设备操作
1、 int (*open)(struct inode *, struct file *)
(1)在设备文件上的第一个操作,可以不实现这个函数而直接赋值成空。如果该项为NULL,设备的打开操作永远成功。
(2)open方法是驱动程序用来为以后的操作完成初始化准备工作的。在大部分驱动程序中,open完成如下工作:
==初始化设备,如设置一些寄存器
==标明此设备号
2、void (* release) (struct inode *, struct file *)
(1)它对应close这个系统调用,当设备文件被关闭时调用这个操作。与open一样,release也可以没有。
(2)与open相反,就是关闭设备
3、ssize_t (* read) (struct file * filp, char __user * buff, size_t count, loff_t * offp);
从设备中读取数据
4、ssize_t (* write) (struct file * filp, char __user * buff, size_t count, loff_t * offp);
向设备发送数据。
读和写小结:
(1)读和写方法都完成类似的工作:从设备中读取数据到用户空间;将数据传递给驱动程序。
(2)对于两个方法, filp 是文件指针,count是请求传输的数据量。buff参数指向数据缓存。offp指出文件当前的访问位置。
(3)Read 和 Write 方法的buff参数是用户空间指针。因此,它不能被内核代码直接引用,因为:用户控件指针在内核空间时可能根本是无效的,没有那个地址的映射。
对此,内核提供了专门的函数用于访问用户控件的指针,例如:
int copy_from_user(void * to,const void __user * from ,int n);
int copy_to_user(void __user * to, const void * from,int n);
5、unsigned int (*poll) (struct file *, struct poll_table_struct *)
对应select 系统调用
6、Int (* ioctl) (struct inode *, struct file *, unsigned int ,unsigned long)
控制设备
7、int (* mmap) (struct file *,struct vm_area_struct *)
将设备映射到进程虚拟地址空间中
8、off_t (*llseek) (struct file *, loff_t , int)
修改文件的当前读写位置,并将新位置作为返回值