分类: LINUX
2012-08-24 09:58:32
写字符设备驱动的基本步骤;
A. 确定设备号
B. 分配一个struct cdev结构
C. 设置
D. 注册
1.设备号
设备号分为主设备号,次设备号。主设备号是用来标识与设备文件相连的驱动程序;次设备号是驱动程序辨别操作的是那个设备。
2.主设备号的确定
有两种方式可以获得主设备号:一种是在开发板上运行 cat /proc/devices命令,找出没有使用的主设备号,然后使用register_chrdev_region函数静态申请;另一种方法是只用alloc_chrdev_region动态获取。
3.分配一个cdev结构
可以使用cdev_alloc函数动态分配,也可以使用直接定义。
3.设置cdev结构
使用函数cdev_init,它具有两个参数,一个是cdev结构的地址,一个是file_operations结构的地址,故在初始化cdev时应创建一个file_operations结构体,并实现里面的操作函数集。其中的成员很多有open,read,write,release函数,当应用程序调用open,read,write,close函数时,最终就会调用到file_operations结构中与之对应的函数。
下面简单介绍file_operations结构中的几个操作函数:
1).ioctl 函数的作用是对硬件有控制能力,比如设置波特率。通过调用ioctl函数可 以从应用程序传一些参数到的驱动程序中去。
在用户空间,使用ioctl 系统调用来控制设备,原型如下:
int ioctl(int fd,unsigned long cmd,...)
在内核空间,定义ioctl函数的在file_operations结构中的原型是:
int (*ioctl)(struct inode *inode,struct file *filp,
unsigned int cmd,unsigned long arg)
cmd参数从用户空间传下来,可选的参数arg 以一个unsigned long 的形式传递,不管它是一个整数或一个指针。如果cmd命令不涉及数据传输,则第3 个参数arg的值无任何意义
实现的方发可分为两步:
a. 定义命令;
b. 实现命令。
ioctl函数的实现通常是通常是一个条件判断的语句,当传入的命令与驱动的实现的命令相同时,执行该部分的代码。当命令号不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)。
2).poll 函数的作用是监视文件,对应的系统调用有select 和 poll函数。实现的概括为两个:a. 将进程放入等待队列;b. 返回掩码。
poll系统调用返回值有如下几种情况:
1.正常情况下返回满足要求的文件描述符个数;
2.经过了timeout等待后仍无文件满足要求,返回值为0;
3.如果select被某个信号中断,它将返回-1并设置errno为EINTR。
4.如果出错,返回-1并设置相应的errno。
poll驱动函数的模板为(以按键为例):
static unsigned int bottons_poll(struct file *file,
struct poll_table_struct *wait)
{
int mask = 0;
/* 将把进程添加到等待队列 */
poll_wait( file, &button_waitq, wait);
/* 当有按键按下时,返回掩码 */
if(ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
使用poll系统调用时:
/* 定义结构体数组 */
struct pollfd fds[1];
/* 设置struct pollfd中的成员 */
fds[0].fd = fd; //设置监视文件的文件描述符
fds[0].events = POLLIN; //设置监视文件的属性
while(1)
{
/*
* 第一个参数传入结构体数组,第二个参数传入监视文件的个数, * 第三个参数设置超时时间单位ms
*/
res = poll(fds, 1, 5000);
if( 0 == res)
printf("time out!\n");
else
{
read(fd, &keynum, 1);
printf("keyval = 0x%x\n", keynum);
}
}
使用select系统调用时:
Select函数的原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数:
nfds:文件描述符的范围,比待检测的最大文件描述符大1
readfs: 被读监控的文件描述符集
writefds: 被写监控的文件描述符集
exceptfds:被异常监控的文件描述符集
timeout: 定时器
系统提供了4个宏对描述符集进行操作:
#include
void FD_SET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
void FD_ZERO(fd_set *fdset)
void FD_ISSET(int fd, fd_set *fdset)
宏FD_SET将文件描述符fd添加到文件描述符集fdset中;
宏FD_CLR从文件描述符集fdset中清除文件描述符fd;
宏FD_ZERO清空文件描述符集fdset;
在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件
fd发生了变化。
应用程序代码:
/* 设置超时时间 */
static struct timeval time = {
.tv_sec = 5, //秒
.tv_usec = 0, //微秒
};
while(1)
{
FD_SET(fd, &set); //将要监视的文件的描述符放入文件描述符集中
res = select(fd+1, &set, NULL, NULL, &time);
if( 0 == res)
printf("time out!\n");
else
{
read(fd, &keynum, 1);
printf("keyval = 0x%x\n", keynum);
}
time.tv_sec = 5; // 必须重新设置超时时间,否则第一次超时后, // 超时时间就变成了零,故必须重新设置。
}
4.注册
使用cdev_add函数注册字符设备,可以理解为将cdev结构添加到内核的链表中。
在入口函数中注册字符设备,在出口函数中就应释放字符设备;
在入口函数中申请了设备号,在出口函数中就应释放设备号;
如果是动态分配到cdev结构应该在出口函数中释放,防止内存泄露。
在2.6的内核中还经常见到使用register_chrdev注册字符设备驱动,该函数有三个参数,主设备号(为零时表示让系统自动分配),名字,file_operations结构。这种方式注册的字符设备只能通主设备号来区分,占用大量的设备号。但是使用时简单。
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
5.自动创建设备节点
使用class_create函数创建一个类
使用device_create函数在类下创建一个设备
以led驱动程序为例
以上内容全是本人凭个人理解写出的:如有错误请多包涵,若能指出,本人将万分感激。