2.设备操作接口
(1).注册与注销
注册时,我们必须考虑到两种管理方式(传统方式与devfs方式)的兼容性。在这里,
我们用条件编译来解决这个问题。由于许多主设备号已经静态地分配给了公用设备,Linux
提供了动态分配机制以获取空闲的主设备号。传统方式下,如果调用devfs_register_chrdev( )
时的major 为零的话,它所调用的register_chrdev( )函数就会选择一个空闲号码作为返回值
返回。主设备号总是正的,因此不会和错误码混淆。在devfs方式下,如果devfs_register( )
的flags 参数值为DEVFS_FL_AUTO_DEVNUM,注册时就会自动生成设备号。
动态分配的缺点是:由于分配的主设备号不能保证总是一样的,无法事先创建设备节点。
但是这并不是什么问题,因为一旦分配了设备号,我们就可以从/proc/devices 读到。为了加
载一个设备驱动程序,我们可以用一个简单的脚本替换对insmod的调用,它通过/proc/devices
获得新分配的主设备号,并创建节点。加载动态分配主设备号驱动程序的脚本可以利用awk
这类工具从/proc/devices 中获取信息,并在/dev中创建文件。在我们的实例程序中,为了简
单起见,仍然使用静态分配的主设备号。
你也许会注意到我们并没有使用统一的函数名init_module( )和cleanup_module( ),这是
由于内核编程风格的变化。自从2.3.13 版的内核以来,Linux提供了两个宏module_init( )和
module_exit( )来显式地命名模块的注册和注销函数。通常在源文件的末尾写上这两个宏,例
如:
module_init(vfifo_init_module);
module_exit(vfifo_exit_module);
注意,在使用这两个宏之前必须先包含头文件<linux/init.h>。这样做的好处是,内核中
的每一个注册和注销函数都有一个唯一的名字,有助于调试。我们知道驱动程序既可以设计
成模块,又可以静态地编译进内核,用了这两个宏后就能更方便地支持这两种方式。实际上,
对于模块来说,它们所做的工作仅仅是把给出的函数名重新命名为 init_module( )和
cleanup_module( )。当然,如果你已使用了init_module( )和cleanup_module( )作为函数名,
那就没必要再使用这两个宏了。
在函数名之前,我们可以看到一个表示属性的词“__init”,加了这个属性之后,系统
会在初始化完成之后丢弃初始化函数,收回它所占用的内存。这样可以减小内核所占用的内
存空间。但它只对内建的驱动程序有用,对于模块则没有影响。
vfifo.c
char vfifoname[8];
static int __init vfifo_init_module(void)
{
int result,i;
SET_MODULE_OWNER(&vfifo_fops);
#ifdef CONFIG_DEVFS_FS
vfifo_devfs_dir=devfs_mk_dir(NULL,"vfifo",NULL);
if(!vfifo_devfs_dir)
return -EBUSY;
#endif
result=devfs_register_chrdev(vfifo_major,"vfifo",&vfifo_fops);
if(result<0){
printk(KERN_WARNING "vfifo: can't get major %d",vfifo_major);
return result;
}
if(vfifo_major==0)
vfifo_major=result;
vfifo_devices = kmalloc(vfifo_nr_devs*sizeof(Vfifo_Dev),GFP_KERNEL);
if(!vfifo_devices){
return -ENOMEM;
}
memset(vfifo_devices,0,vfifo_nr_devs*sizeof(Vfifo_Dev));
for(i=0;i<vfifo_nr_devs;i++){
init_waitqueue_head(&vfifo_devices.rdq);
init_waitqueue_head(&vfifo_devices.wrq);
sema_init(&vfifo_devices.sem,1);
#ifdef CONFIG_DEVFS_FS
sprintf(vfifoname,"vfifo%d",2*i);
vfifo_devices.w_handle=
devfs_register(vfifo_devfs_dir,vfifoname,
DEVFS_FL_NON,
vfifo_major,2*i,S_IFCHR|S_IRUGO|S_IWUGO,
&vfifo_fops,vfifo_device+i);
sprintf(vfifoname,"vfifo%d",2*i+1);
vfifo_devices.r_handle=
devfs_register(vfifo_devfs_dir,vfifoname,
DEVFS_FL_NON,
vfifo_major,2*i+1,S_IFCHR|S_IRUGO|S_IWUGO,
&vfifo_fops,vfifo_device+i);
if(!vfifo_devices.r_handle||!vfifo_devices.w_handle){
printk(KERN_WARNING "vfifo: can't register vfifo device nr %i\n",i);
}
#endif
}
#ifdef VFIFO_DEBUG
create_proc_read_entry("vfifo",0,NULL,vfifo_read_mem,NULL);
#endif
return 0;
}
[此贴子已经被linuxarm于2006-7-20 15:17:57编辑过]
一步步设计自己的驱动程序
实验目的:
通过一个简单的设备驱动的实现过程。学会Linux中设备驱动程序的编写
实验内容:
设计和实现一个虚拟命名管道(FIFO)的字符设备。写一个模块化的字符设备驱动程序
实验提示:
一、设备的功能
设计和实现一个虚拟命名管道(FIFO)的字符设备。我们知道,管道是进程间通信的一种
方式:一个进程向管道中写数据,另一个进程从管道中读取数据,先写入的数据先读出。我
们的驱动程序要实现N(N=4)个管道,每个管道对应两个设备,次设备号是偶数的设备是只
写设备,次设备号是奇数的是只读设备。写入设备i(i是偶数)的字符可以从设备i+1读出。
这样,我们一共就需要2N 个次设备号。
我们的目标是写一个模块化的字符设备驱动程序。设备所使用的主设备号可以从尚未分
配的主设备号中任选一个,/Documentation/devices.txt 记录了当前版本内核的主设备号分配
情况。如果设备文件系统(devfs)尚未激活,我们在加载模块之后,还必须用mknod 命令创
建相应的设备文件节点。
如果FIFO 的写入端尚未打开,FIFO 中就不会有数据可读,所以此时试图从FIFO 中读
取数据的进程应该返回一个错误码。如果写入端已经打开,为了保证对临界区的互斥访问,
调用读操作的进程必须被阻塞。如果存在被阻塞的读者,在写操作完成后(或者关闭一个写
设备时)必须唤醒它。
如果写入的数据太多,超出了缓冲区中空闲块的大小,调用写操作的进程必须睡眠,以
等待缓冲区中有新的空闲块。
二、设备的实现
1. 数据结构:
首先,我们要包含一些必要的头文件、宏和全局变量。
vfifo.c
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#define __NO_VERSION__
#include<linux/config.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/malloc.h>
#include<linux/fs.h>
#include<linux/proc_fs.h>
#include<linux/errno.h>
#include<linux/types.h>
#include<linux/fcntl.h>
#include<linux/init.h>
#include<asm/system.h>
#include<asm/uaccess.h>
#ifndef VFIFO_MAJOR
#define VFIFO_MAJOR 241
#endif
#ifndef VFIFO_NR_DEVS
#define VFIFO_NR_DEVS 4
#endif
#ifndef VFIFO_BUFFER
#define VFIFO_BUFFER 4000
#endif
#include<linux/devfs_fs_kernel.h>
devfs_handle_t vfifo_devfs_dir;
struct file_operations vfifo_fops;
int vfifo_major=VFIFO_MAJOR;
int vfifo_nr_devs=VFIFO_NR_DEVS;
int vfifo_buffer=VFIFO_BUFFER;
MODULE_PARM(vfifo_major,"i");
MODULE_PARM(vfifo_nr_devs,"i");
MODULE_PARM(vfifo_buffer,"i");
MODULE_AUTHOR("EBUDDY");
每个实际的FIFO 设备都对应于一个Vfifo_Dev{ }结构体。其中,rdq 是阻塞读的等待
队列,wrq 是阻塞写的等待队列,base 是所分配缓冲区的起始地址,buffersize 是缓冲区的
大小,len表示管道中已有数据块的长度,start 表示当前应该读取的缓冲区位置相对于base
的偏移量,即缓冲区起始数据的偏移量,readers和writers分别表示VFIFO 设备当前的读者
个数和写者个数,sem是用于互斥访问的信号量,r_handle和w_handle用于保存设备文件系
统的注册句柄,r_handle对应的是只读设备,w_handle对应的是同一管道的只写设备。具体
的定义如下所示:
vfifo.c
typedef struct Vfifo_Dev{
wait_queue_head rdq,wrq;
char* base;
unsigned int buffersize;
unsigned int len;
unsigned int start;
unsigned int readers,writers;
struct semaphore sem;
devfs_handle_t r_handle,w_handle;
}Vfifo_Dev;
注销的工作相对简单。需要注意的是在卸载驱动程序之后要删除设备节点。如果设备节
点是在加载时创建的,可以写一个简单的脚本在卸载时删除它们。如果动态节点没有从/dev
中删除,就可能造成不可预期的错误:系统可能会给另一个设备分配相同的主设备号,这样
在打开设备时就会出错。
我们可以看到在函数名前标有属性“__exit”,它的作用类似于“__init”,即使内建的
驱动程序忽略它所标记的函数。同样的,它对模块也没有影响。
vfifo.c
static void __exit vfifo_cleanup_module(void)
{
int i;
devfs_unregister_chrdev(vfifo_major,"vfifo");
#ifdef VFIFO_DEBUG
remove_proc_entry("vfifo",NULL);
#endif
if(vfifo_devices){
for(i=0;i<vfifo_nr_devs;i++){
if(vfifo_devices.base)
kfree(vfifo_devices.base);
devfs_unregister(vfifo_devices.r_handle);
devfs_unregister(vfifo_devices.w_handle);
}
kfree(vfifo_devices);
devfs_unregister(vfifo_devfs_dir);
}
}
}
(2). 打开与释放
打开设备主要是完成一些初始化工作,以及增加引用计数,防止模块在设备关闭前被注
销。我们知道内核用主设备号区分不同类型的设备,而驱动程序用次设备号识别具体的设备。
利用这一特性,我们可以用不同的方式打开同一个设备。
vfifo.c
static int vfifo_open(struct inode *inode,struct file *filp)
{
Vfifo_Dev *dev;
int num=MINOR(inode->i_rdev);
/*检查读写权限是否合法 */
if((flip->f_mode&FMODE_READ)&&!(num%2)||(filp->f_mode&FMODE_WRITE)&&(num%2))
return -EPERM;
if(!filp->private_data){
if(num>=vfifo_nr_devs*2)
return -ENODEV;
dev=&vfifo_nr_devices[num/2];
filp->private_data=dev;
}
else{
dev=filp->private_data;
}
/*获得互斥访问的信号量 */
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
/*如果尚未分配缓冲区,则分配并初始化 */
if(!dev->base){
dev->base=kmalloc(vfifo_buffer,GFP_KERNEL);
if(!dev->base){
up(&dev->sem);
return -ENOMEN;
}
dev->buffersize=vfifo_buffer;
dev->len=dev->start=0;
}
if(filp->mode&FMODE_READ)
dev->readers++;
if(filp->mode&FMODE_WRITE)
dev->writers++;
filp->private_data=dev;
MOD_INC_USE_COUNT;
return 0;
}
释放(或关闭)设备就是打开设备的逆过程。
vfifo.c
static int vfifo_release(struct inode *inode,struct file *filp)
{
Vfifo_Dev *dev=filp->private_data;
/*获得互斥访问的信号量 */
down(&dev->sem);
if(filp->f_mode&FMODE_READ)
dev->readers--;
if(filp->f_mode&FMODE_WRITE){
dev->writes--;
wake_up_interruptible(&dev->sem);
}
if((dev->readers+dev->writers==0)&&(dev->len==0)){
kfree(dev->base);
dev->base=NULL;
}
up(&dev->sem);
MOD_DEC_USE_COUNT;
return 0;
}
[此贴子已经被linuxarm于2006-7-20 15:26:53编辑过]
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) | Powered by Discuz! 7.0.0 |