一、 实现思路:
Linux将硬件设备看做一类特殊的文件(/dev/*),设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得linux的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open()、close()、read()、write() 等。
所以,一个字符( char ) 设备也就是一种可以当作一个字节流来存取的设备( 如同一个文件 ),可以通过一系列的文件操作实现字符设备驱动程序。
二、 实验步骤:
(1) 设置内核支持模块(make menuconfig),如图2.1所示:
图2.1 模块支持的设置
不过,这一步可以省略,因为所使用的linux系统――linux-up内核,默认支持模块的。
(2) 所需头文件及宏定义
#include <linux/kernel.h> // for kernel programming
#include <linux/module.h> // for kernel module struct.
#include <linux/fs.h> // struct file_operations
#define RWBUF_NAME “rwbuf” // 设备文件 /dev/rwbuf
#define RWBUF_DEV “/dev/rwbuf” // device path
#define RWBUF_MAJOR 60 // 主设备号
#define RWBUF_CLEAR 0x909090 // IO Ctrl Command
(3) 重要的数据结构:file_operations,file,inode
<linux/fs.h>中定义了这三个结构. 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 *); 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); int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long arg); } file_operations 结构定义了一组函数指针,每一个打开的文件(用struct file表示)和它自己的一组函数(包含在一个叫f_op的域中,它指向一个 struct file_operations结构)相联系.这些操作都是来实现系统调用的,所以才被命名为open,read,等等.对于那些不需要的 功能(比如你的设备不需要write功能,即不需要向设备写数据),可以给write指针付NULL.
struct file
{ struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; mode_t f_mode; int f_error; loff_t f_pos; struct fown_struct f_owner; unsigned int f_uid, f_gid; struct file_ra_state f_ra;
unsigned long f_version; void *f_security;
/* needed for tty driver, and maybe others */ void *private_data;
#ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; spinlock_t f_ep_lock; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; }; 每个打开的文件对应一个struct file.它在被打开时由内核创建,并传给它所有可以操作该文件的函数,到文件被关闭时才被删除.
The inode Structure. Inode结构是用来在内核内部表示文件的.同一个文件可以被打开好多次,所以可以对应很多struct file,但是只对应一个struct inode.该结构里面包含了很多信息,但是,驱动开发者只关心里面两个重要的域: dev_t i_rdev;//含有真正的设备号 struct cdev *i_cdev;//struct cdev是内核内部表示字符设备的结构.
(4) 注册设备号
定义好major和minor number 后就可以在内核中注册一个设备了.注册一个字符设备需要用到下面几个函数: int register_chrdev_region(dev_t first,unsigned int count,char *name); first是要注册的设备号范围的开始(其中minor号一般设置为0),count是所申请的连续设备号的总数.name是设备的名称.它会在/proc/devices中出现. int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name); 这个函数是用来动态分配设备号的.有余开发者不知道所要用的major号是多少,便让内核动态分配一个.参数dev是个output-only参数. void unregister_chrdev_region(dev_t first,unsigned int count); 一般在模块清除函数中调用.
(5) 用户接口实现
定义操作集合:
static struct file_operations rwbuf_fops =
{
open: rwbuf_open,
release: rwbuf_close,
read: rwbuf_read,
write: rwbuf_write,
ioctl: rwbuf_ioctl,
};
然后分别实现各个操作,详见代码。
(6) 编译及加载设备模块
编译:
gcc -c rwbuf.c -D__KERNEL__ -DMODULE -DMODVERSIONS -Wall -include /usr/src/linux/include/linux/modversions.h -I/usr/src/linux/include
安装与卸载:
mknod /dev/rwbuf c 60 0 创建设备文件
/sbin/insmod rwbuf.o 安装设备驱动
/sbin/rmmod rwbuf 卸载设备驱动
(7) 测试驱动程序 void writer() { char yourmsg[1000]="Hello OYZ!"; char c; int i = 0, h = 0, n = 0 ;
h=open(RWBUF_DEV,O_WRONLY);
n = write(h, yourmsg, sizeof(yourmsg)+1);
close(h);
printf("write OK!/n");
}
void reader()
{
char yourmsg[1000];
int h=open(RWBUF_DEV,O_RDONLY);
int n=read(h,yourmsg,sizeof(yourmsg));
close(h);
printf("read OK!/n");
}
void cleaner()
{
char yourmsg[1000];
int h=open(RWBUF_DEV,O_RDWR);
int n=ioctl(h,RWBUF_CLEAR,0);
close(h);
printf("rwbuf successfully removed!/n");
}
三、 实验结果:
图2.2 字符设备测试结果
测试结果如上图2所示,可以说明字符驱动设备添加成功。
如果将rwbuf模块卸载,则测试程序将不能输入写入的字符串,如下图所示:
图2.3 卸载先前已装入的字符设备 |