虚拟字符驱动设备程序
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
虚拟字符驱动设备
一、内容提要:
编写一个完整的字符设备驱动程序。
由于这类驱动程序适合于大多数简单的硬件设备,我们首先实现一个字符设备驱动程序。
Linux 下的设备驱动程序被组织为一组完成不同任务的函数的集合,在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作,如open()、close()、read()、write()等。
然后Linux主要将设备分为二类:字符设备和块设备。
字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。
字符设备的驱动相对比较简单。
由于驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作.并调用
register_chrdev()函数注册字符设备:
static int__init gobalvar_init(void){
if(register_chrdev(MAJOR_NUM,"gobalvar",&gobalvar_fops) )
{
//…注册失败
}else
{
//…注册成功
}
}
其中,register_chrdev函数中的参数MAJOR_NUM为主设备号,"gobalvar"为设备名,gobalvar_fops为包含基本函数入口点的结构体,类型为file_operations。
当gobalvar模块被加载时,gobalvar_init被执行,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。
与模块初始化函数对应的就是模块卸载函数,需要调用
register_chrdev()的"反函数"unregister_chrdev():static void__exit gobalvar_exit(void)
{
if(unregister_chrdev(MAJOR_NUM,"gobalvar"))
{
//…卸载失败
}else
{
//…卸载成功
}
}
二、下面是该字符驱动设备的整体函数代码及分步的分析:Test_drv.c:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define TEST_DEVICE_NAME "test_dev"
#define BUFF_SZ 1024
/* 全局变量 */
static struct cdev test_dev;
unsigned int major=0;
static char *data=NULL;
dev_t dev;
/* 读函数 */
static ssize_t test_read(struct file *file,char *buf,size_t count,loff_t *f_pos)
{ /buf指向缓冲区的指针
int len;
if(count<0) /count读取和写入的字节数
{
return -EINVAL;
}
len=strlen(data);
count=(len>count) count:len;
if(copy_to_user(buf,data,count)) //将内核缓冲的数据复制到用户空间
{
return -EFAULT;
}
return count;
}
/* 写函数 */
static ssize_t test_write(struct file *file,const char
*buffer,size_t count,loff_t *f_pos)
{
if(count<0)
{
return -EINVAL;
}
memset(data,0,BUFF_SZ);
count=(BUFF_SZ>count) ?count:BUFF_SZ;
if(copy_from_user(data,buffer,count)) //将用户缓冲的数据复制到内核空间
{
return -EFAULT;
}
return count;
}
/* 打开函数 */
static int test_open(struct inode *inode,struct file *file) {
printk("This is open operation.\n");
/* 分配并初始化缓冲区 */
data=(char*)kmalloc(sizeof(char)*BUFF_SZ,GFP_KERNEL);
if(!data)
{
printk("malloc error!");
return -ENOMEM;
}
memset(data,0,BUFF_SZ);
return 0;
}
/* 关闭函数 */
static int test_release(struct inode *inode,struct file *file) {
printk("This is release operation.\n");
if(data)
{
kfree(data); //释放缓冲区
data=NULL; //防止出现野指针
}
return 0;
}
/* 创建、初始化字符设备,并且注册到系统 */
static void test_setup_cdev(struct cdev *cdev,int minor,struct file_operations *fops)
{
int err;
cdev_init(cdev,fops);
cdev->owner=THIS_MODULE;
cdev->ops=fops;
err=cdev_add(cdev,dev,1);
if(err)
{
printk(KERN_NOTICE"Error %d adding test %d",err,minor);
}
}
/* 虚拟设备的file——operation结构 */
static struct file_operations test_fops=
{
.owner=THIS_MODULE,
.read=test_read,
.write=test_write,
.open=test_open,
.release=test_release,
};
/* 模块注册入口 */
static int __init init_module(void)
{
int result;
dev=MKDEV(major,0);
if(major)
{
//静态注册一个设备,设备号事先指定好,用
cat/proc/devices来查看
result=register_chrdev_region(dev,1,TEST_DEVICE_NAME); }
else
{
result=alloc_chrdev_region(&dev,0,1,TEST_DEVICE_NAME); }
if(result<0)
{
printk(KERN_WARNING"Test device:unable to get major %d\n",major);
return result;
}
test_setup_cdev(&test_dev,0,&test_fops);
printk("The major of the test device is %d\n",dev); return 0;
}
/* 卸载模块 */
static void __exit cleanup_module(void)
{
cdev_del(&test_dev);
unregister_chrdev_region(MKDEV(major,0),1);
printk("Test device uninstalled.\n");
}
module_init(init_module);
module_exit(cleanup_module);
随着内核不断增加新的功能,file_operations结构体已逐渐变得越来越大,但是大多数的驱动程序只是利用了其中的一部分。
对于字符设备来说,要提供的主要入口有:open()、release()、read()、write()等。
下面就介绍一下以下的函数:
1.open()函数:
对设备特殊文件进行open()系统调用时,将调用驱动程序的open()函数:int(*open)(struct inode*,struct file*),其中参数inode为设备特殊文件的inode(索引结点)结构的指针,参数file 是指向这一设备的文件结构的指针。
open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用
MINOR(inode->i-rdev)取得)、控制使用设备的进程数、根据执行情况返回状态码(0表示成功,负数表示存在错误)等;
/* 打开函数 */
static int test_open(struct inode *inode,struct file *file) {
printk("This is open operation.\n");
/* 分配并初始化缓冲区 */
data=(char*)kmalloc(sizeof(char)*BUFF_SZ,GFP_KERNEL);
if(!data)
{
printk("malloc error!");
return -ENOMEM;
}
memset(data,0,BUFF_SZ);
return 0;
}
2.release()函数:
当最后一个打开设备的用户进程执行close()系统调用时,内核将调用驱动程序的release()函
数:void(*release)(struct inode*,struct file*),release函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。
/* 关闭函数 */
static int test_release(struct inode *inode,struct file *file) {
printk("This is release operation.\n");
if(data)
{
kfree(data); //释放缓冲区
data=NULL; //防止出现野指针
}
return 0;
}
3.read()函数:
当对设备特殊文件进行read()系统调用时,将调用驱动程序read()函
数:ssize_t(*read)(struct file*,char*,size_t,loff_t*);用来从设备中读取数据。
当该函数指针被赋为NULL值时,将导致
read系统调用出错并返回-EINVAL("Invalid argument,非法参数")。
函数返回非负值表示成功读取的字节数(返回值为"signed size"数据类型,通常就是目标平台上的固有整数类型)。
/* 读函数 */
static ssize_t test_read(struct file *file,char *buf,size_t count,loff_t *f_pos)
{
int len;
if(count<0)
{
return -EINVAL;
}
len=strlen(data);
count=(len>count) ? count:len;
if(copy_to_user(buf,data,count)) //将内核缓冲的数据复制到用户空间
{
return -EFAULT;
}
return count;
}
4.write()函数:
当设备特殊文件进行write()系统调用时,将调用驱动程序的
write()函
数:ssize_t(*write)(struct file*,const char*,size_t,loff _t*);向设备发送数据。
如果没有这个函数,write系统调用会向调用程序返回一个-EINVAL。
如果返回值非负,则表示成功写入的字节数。
/* 写函数 */
static ssize_t test_write(struct file *file,const char
*buffer,size_t count,loff_t *f_pos)
{
if(count<0)
{
return -EINVAL;
}
memset(data,0,BUFF_SZ);
count=(BUFF_SZ>count) ?count:BUFF_SZ;
if(copy_from_user(data,buffer,count)) //将用户缓冲的数据复制到内核空间
{
return -EFAULT;
}
return count;
}
5.全局变量:
/* 全局变量 */
static struct cdev test_dev;
unsigned int major=0;
static char *data=NULL;
dev_t dev;
三、接下来需要用的Makefile文件:
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONE:
modules modules_install clean
else
obj-m := test_drv.o
Endif
四、再接下来的是test.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#define TEST_DEVICE_FILENAME "/dev/test_dev" //设备文件名
#define BUFF_SZ 1024
int main()
{
int fd,nwrite,nread;
char buff[BUFF_SZ]; //缓冲区
/* 打开文件 */
fd=open("/dev/test_dev",O_RDWR);
if(fd<0){
perror("open");
exit(1);
}
do{
printf("Input some words to kernel(enter 'quit' to exit):");
memset(buff,0,BUFF_SZ);
if(fgets(buff,BUFF_SZ,stdin)==NULL){
perror("fgets");
break;
}
buff[strlen(buff)-1]='\0';
if(write(fd,buff,strlen(buff))<0) //向设备写入数据
{
perror("write");
break;
}
if(read(fd,buff,BUFF_SZ)<0) //从设备读取数据
{
perror("write");
break;
}
else{
printf("The read string is from kernel:%s\n",buff);
}
}while(strncmp(buff,"quit",4));
close(fd);
exit(0);
}
五、最后是调试运行结果:
[root@localhost ~]# cd /usr/
[root@localhost ye]# ls
Makefile test.c test_drv.c test_drv_load
Makefile~ test.c~ test_drv.c~ test_drv_unload
[root@localhost liang]# make
make -C /lib/modules/2.6.23.1-42.fc8/build M=/usr/liang modules
make[1]: Entering directory
`/usr/src/kernels/2.6.23.1-42.fc8-i686'
CC [M] /usr/liang/test_drv.o
Building modules, stage 2.
MODPOST 1 modules
CC /usr/liang/test_drv.mod.o
LD [M] /usr/liang/test_drv.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.23.1-42.fc8-i686'
[root@localhost liang]# insmod test_drv.ko
[root@localhost liang]# cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
116 alsa
128 ptm
136 pts
180
usb
189 usb_device 216 rfcomm
226 drm
229 hvc
250 test_dev 251
usb_endpoint 252 usbmon
253 bsg
254 pcmcia Block devices:
1 ramdisk
2 fd
7
loop
8 sd
9 md
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130
sd
131 sd
132 sd
133 sd
134 sd
135 sd
253 device-mapper
254 mdp
[root@localhost liang]# mknod /dev/test_dev c 250 0 [root@localhost liang]# gcc test.c -o c
[root@localhost liang]# ./c
Input some words to kernel(enter 'quit' to exit):3456 The read string is from kernel:3456
Input some words to kernel(enter 'quit' to exit):find The read string is from kernel:find
Input some words to kernel(enter 'quit' to exit):good The read string is from kernel:good
Input some words to kernel(enter 'quit' to exit):5678 The read string is from kernel:5678
这样的话可以发现该字符设备驱动可以正确的读写!。