标签:
via:http://blog.sina.com.cn/s/blog_7ec8fc2c010157lc.html
1、驱动程序设计
1)驱动分类
驱动这里分为 字符设备驱动、网络接口驱动、块设备驱动!这三类,其中前两者是重点。
①、字符设备
字符设备是一种 按自己来访问 的设备,字符驱动则负责驱动字符设备,这样的驱动通常是先 open、close、read和write 系统调用!
②、块设备
在大部分 Unix 系统中,块设备不能按照字节处理数据,只能一次传送一个或则会多个长度是 512 字节(或者更大的 2 次幂的数)的整块数据。
而对于 Linux 则允许块设备传送任意数目的字节。因此,块和字符设备的区别仅仅是驱动的与内核的接口不同。
③、网络接口
任何网络实物都通过一个接口来进行,一个接口通常是一个硬件设备(eth0),但是它也可以是一个纯粹的软件设备,比如回环接口(lo)。一个网络接口负责发送和接收数据报文。
2)驱动程序安装
方法一、模块方式
方法二、直接编译进内核
这种方法就是直接修改 Kconfig 以及 Makefie(这两个文件都要与你的程序放置的位置相对应,比如:/drivers/char 目录下你要放个 hello.c 的程序,然后修改这个目录下的 Kconfig 以及 Makefile 就哦了)
3)驱动程序的使用
直接拿国嵌的图就哦了:
2、字符设备驱动程序设计
首先呢,介绍下知识点:
▲:设备号
▲:创建设备文件
▲:设备注册
▲:重要数据结构
▲:设备操作
1)主次设备号
字符设备通过自出设备文件来存取。字符设备文件使用 ls -l 的输出的第一列的 "c" 标识。
如果使用 ls -l 命令,会看到在设备文件项中友 2 个数(由一个逗号分隔)这些数字就是设备文件的主次设备编号。
①、设备号作用
主设备号用来标识与设备文件相连的驱动程序(就是说,主设备号连接了字符设备文件和字符设备驱动)。次设备号被驱动程序用来判别操作的是哪个设备。
也就是说:主设备号用来反映设备类型,次设备号用来区分同类型的设备。
②、设备号的描述
▲:内核中用 dev_t 来描述设备号(其实质就是 unsigned int 32 位整数,其中高 12 位为主设备号,低 20 位为次设备号)。
▲:MAJOR(dev_t dev)
从 dev_t中分解出主设备号!
▲:MINOR(dev_t dev)
从 dev_t 中分解出次设备号!
③、分配主设备号
可以采用 静态申请、动态分配 两种方法。
A、静态申请
方法:
首先根据 Documentation/devices.txt,确定一个没有使用的主设备号,然后使用 register_chrdev_region 函数注册设备号!
优点:
简单
缺点:
一旦驱动被广泛使用,这个随机选定的主设备号可能会导致设备号冲突,而使驱动程序无法注册。
register_chrdev_region
函数功能:
申请使用从 from 开始的 count 个设备(主设备号不变,次设备号增减)。
函数原型:
int register_chrdev_region(dev_t from,unsigned count,const char *name)
参数说明:
from:希望申请使用第一个设备号
count:希望申请使用的设备号数目
name:设备名(体现在 /proc/devices)。
提示:设备号由主设备号和次设备号构成,内核提供宏 MKDEV(ma,mi) 来构成设备号,通过宏 MAJOR(dev) 可以从设备号中提取主设备号,通过宏 MINOR(dev) 可以从设备号中提出从设备号。
返回值:
成功返回 0,失败返回值为负的错误号。
B、动态分配
方法:
使用 alloc_chrdev_region 分配设备号
优点:
简单,易于驱动推广
缺点:
无法再安装驱动前创建设备文件(因为安装前还没有分配到主设备号)。
解决方法:
安装驱动后,从 /proc/devices 中查询设备号
alloc_chrdev_region
函数功能:
请求内核动态分配 count 个设备号,且此设备号从 baseinor 开始
函数原型:
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)
参数说明:
dev:分配到的设备号
baseminor:起始此设备号
count:需要分配的次设备号数目
name:设备名(体现在 /proc/devices)
返回值:
成功返回 0,失败返回值为负的错误号。
④、注销设备号
不论使用何种方法分配设备号,都应该在不在使用它们时释放这些设备号。
unregister_chrdev_region
函数功能:
释放从 from 开始的 count 个设备号
函数原型:
void unregister_chrdev_region(dev_t from,unsigned count)
参数说明:
from:要删除的第一个设备号
count:要删除的设备号的个数
返回值:
无
⑤、创建设备文件
方法一、
使用 mknod 命令手工创建
其中 mknod 用法:
mknod filename type major minor
参数含义:
filename:设备文件名
type:设备文件类型
major:主设备号
minor:次设备号
例程:
mknod serial0 c 100 0
方法二、自动创建
他说会后面介绍到,那咱就后面再说!
2)重要结构
在 Linux 字符设备驱动程序设计中,有三种非常重要的数据结构:
struct file
struct innod
struct file_operations
①、struct file
代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的 struct file(注意:这里是每打开一次都会产生这个文件)。它由内核在打开文件时创建,在文件关闭后释放!
重要成员:
loff_t f_pos
struct file_operation *f_op
②、struct inode
用来记录文件的物理上的信息。因此,它和代表打开文件的 file 结构是不同的。一个文件可以对应多个 file 结构,但是只有一个 inode 结构!
重要成员:
dev_t i_rdev:设备号
③、struct file_operations
一个函数指针的集合,定义能在设备上运行的操作。
结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为 NULL。
例如,这个结构体可以这样写:
struct file_operation mem_fops = {
.owner = THIS_MODULE,
.llseek = mem_seek,
.read = mem_read,
.write = mem_write,
.ioctl = mem_ioctl,
.open = mem_open,
.release = mem_release,
};
3)设备注册
在 Linux 2.6 内核中,字符设备使用 struct cdev 来描述。
字符设备的注册可分为如下 3 个步骤:
▲:分配 cdev
▲:初始化 cdev
▲:添加 cdev
①、设备注册(分配)
分配一个 struct cdev
函数原型:
stuct cdev *cdev_alloc(void)
返回值:
成功返回一个指向 cdev 结构体的指针,失败返回 NULL。
②、设备注册(初始化)
struct cdev 的分配可以使用 cdev_alloc 函数来完成。
函数功能:
初始化一个 struct cdev
函数原型:
void cdev_init(cdev *cdev,const struct file_operations *fops)
参数说明:
cdev:待初始化的 cdev 结构
fops:设备对应的操作函数集(其中 file_operations 结构的主要成员已经在上面介绍过了)!
返回值:
无
③、设备注册(添加)
struct cdev 的初始化使用 cdev_init 函数来完成。
函数功能:
注册一个 struct_cdev,添加系统添加该字符设备
函数原型:
int cdev_add(struct cdev *p,dev_t dev,unsigned count)
参数说明:
p:待添加到内核的字符设备结构
dev:设备号
count:添加的设备个数
返回值:
成功返回 0,失败返回值为负的错误号。
④、注销字符设备
函数原型:
int cdev_del(struct cdev *p)
参数说明:
p:要注销的字符设备结构
返回值:
成功返回 0,失败返回负的错误号
4)设备操作
在完成了驱动程序的注册,下一步就要实现设备所支持的操作!!!
也就是实现 file_operations 结构中对应的成员。
①、open
功能描述:
在设备文件进行的第一个操作,不要求驱动声明一个对应的方法。
如果这个项为 NULL,设备打开一直成功,但是你的驱动不会得到通知。
函数原型:
int (*open)(struct inode *inode,struct file *filp)
参数说明:
inode:为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个 inode 结构。
filp:只要打开一个文件,就对应着一个 file 结构体,file 结构体通常用来追踪文件在运行时的状态信息。
②、release
函数描述:
当最后一个打开设备的用户进程执行 close() 系统调用的时候,内核将调用驱动程序 release() 函数。函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排它标志的复位等。
在文件结构被释放时引用这个操作,如同 open,release 可以为 NULL。
函数原型:
int (*release)(struct inode *inode,struct file *filp)
参数说明:
同 open 操作
③、read
功能描述:
用来从设备中获取数据。返回值代表了成功读取的字节数
函数原型:
ssize_t (*read)(struct file *filp,char __user *buffer,size_t size,loff_t *p)
函数说明:
filp:目标文件结构体指针
buffer:对应放置信息的缓冲区(既用户空间内存地址)
size:要读取的信息长度
p:读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值)
④、write
功能描述:
发送数据给设备,返回值代表成功写的字节函数(注:这个操作和上面的对文件文件进行读的操作均为阻塞操作)。
函数原型:
ssize_t (*write)(struct file *filp,contst char __user *buffer,size_t count,loff_t *ppos)
参数说明:
filp:目标文件结构体指针
buffer:要写入文件的嘻嘻缓冲区
size:要写入信息的长度
poos:当前的偏移位置,这个值通常是用来判断写文件是否越界
⑤、llseek
功能描述:
初始化一个 struct cdev
函数原型:
loft_t (*llseek)(struct file *filp,loff_t p,int orig)
参数说明:
filp:目标文件结构体指针
p:文件定位的目标偏移量
orig:对文件定位的起始地址,这个值可以为文件开头(SEEK_SET、0),当前位置(SEEK_CUR、1),文件末尾(SEEK_END、2)。
5)读和写
关于读和写,这里要说明一下:
filp 是文件指针,count 是请求传输的数据量。buff 参数指向数据缓存,最后,offp 指出文件当前的访问位置!
其中: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)
顺便再多介绍点吧:
能将内核空间的变量传递给用户空间的地址的函数类似的还有:
get_user(var,ptr);
put_user(var,ptr);
long stmcpy_from_user(char *dst,const char __user *src,long count);
strlen_user(str);
这些函数用来使用内核空间可以与用户空间进行必要的数据交互,毕竟使用系统调用的返回值来传递数据局限性太大。
标签:
原文地址:http://www.cnblogs.com/izhangzhne/p/4494033.html