嵌入式Linux设备驱动开发之:实验内容——test驱动
扫描二维码
随时随地手机看文章
该实验是编写最简单的字符驱动程序,这里的设备也就是一段内存,实现简单的读写功能,并列出常用格式的Makefile以及驱动的加载和卸载脚本。读者可以熟悉字符设备驱动的整个编写流程。
2.实验内容该实验要求实现对虚拟设备(一段内存)的打开、关闭、读写的操作,并要通过编写测试程序来测试虚拟设备及其驱动运行是否正常。
3.实验步骤(1)编写代码。
这个简单的驱动程序的源代码如下所示:
/*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>
#defineTEST_DEVICE_NAME"test_dev"
#defineBUFF_SZ1024
/*全局变量*/
staticstructcdevtest_dev;
unsignedintmajor=0;
staticchar*data=NULL;
/*读函数*/
staticssize_ttest_read(structfile*file,
char*buf,size_tcount,loff_t*f_pos)
{
intlen;
if(count<0)
{
return-EINVAL;
}
len=strlen(data);
count=(len>count)?count:len;
if(copy_to_user(buf,data,count))/*将内核缓冲的数据拷贝到用户空间*/
{
return-EFAULT;
}
returncount;
}
/*写函数*/
staticssize_ttest_write(structfile*file,constchar*buffer,
size_tcount,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;
}
returncount;
}
/*打开函数*/
staticinttest_open(structinode*inode,structfile*file)
{
printk("Thisisopenoperation\n");
/*分配并初始化缓冲区*/
data=(char*)kmalloc(sizeof(char)*BUFF_SZ,GFP_KERNEL);
if(!data)
{
return-ENOMEM;
}
memset(data,0,BUFF_SZ);
return0;
}
/*关闭函数*/
staticinttest_release(structinode*inode,structfile*file)
{
printk("Thisisreleaseoperation\n");
if(data)
{
kfree(data);/*释放缓冲区*/
data=NULL;/*防止出现野指针*/
}
return0;
}
/*创建、初始化字符设备,并且注册到系统*/
staticvoidtest_setup_cdev(structcdev*dev,intminor,
structfile_operations*fops)
{
interr,devno=MKDEV(major,minor);
cdev_init(dev,fops);
dev->owner=THIS_MODULE;
dev->ops=fops;
err=cdev_add(dev,devno,1);
if(err)
{
printk(KERN_NOTICE"Error%daddingtest%d",err,minor);
}
}
/*虚拟设备的file_operations结构*/
staticstructfile_operationstest_fops=
{
.owner=THIS_MODULE,
.read=test_read,
.write=test_write,
.open=test_open,
.release=test_release,
};
/*模块注册入口*/
intinit_module(void)
{
intresult;
dev_tdev=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"Testdevice:unabletogetmajor%d\n",major);
returnresult;
}
test_setup_cdev(&test_dev,0,&test_fops);
printk("Themajorofthetestdeviceis%d\n",major);
return0;
}
/*卸载模块*/
voidcleanup_module(void)
{
cdev_del(&test_dev);
unregister_chrdev_region(MKDEV(major,0),1);
printk("Testdeviceuninstalled\n");
}
(2)编译代码。
虚拟设备的驱动程序的Makefile如下所示:
ifeq($(KERNELRELEASE),)
KERNELDIR?=/lib/modules/$(shelluname-r)/build/*内核代码编译路径*/
PWD:=$(shellpwd)
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
.PHONY:modulesmodules_installclean
else
obj-m:=test_drv.o/*将生成的模块为test_drv.ko*/
endif
(3)加载和卸载模块。
通过下面两个脚本代码分别实现驱动模块的加载和卸载。
加载脚本test_drv_load如下所示:
#!/bin/sh
#驱动模块名称
module="test_drv"
#设备名称。在/proc/devices中出现
device="test_dev"
#设备文件的属性
mode="664"
group="david"
#删除已存在的设备节点
rm-f/dev/${device}
#加载驱动模块
/sbin/insmod-f./$module.ko$*||exit1
#查到创建设备的主设备号
major=`cat/proc/devices|awk"\\$2==\"$device\"{print\\$1}"`
#创建设备文件节点
mknod/dev/${device}c$major0
#设置设备文件属性
chgrp$group/dev/${device}
chmod$mode/dev/${device}
卸载脚本test_drv_unload如下所示:
#!/bin/sh
module="test_drv"
device="test_dev"
#卸载驱动模块
/sbin/rmmod$module$*||exit1
#删除设备文件
rm-f/dev/${device}
exit0
(6)编写测试代码。
最后一步是编写测试代码,也就是用户空间的程序,该程序调用设备驱动来测试驱动的运行是否正常。以下实例只实现了简单的读写功能,测试代码如下所示:
/*test.c*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#defineTEST_DEVICE_FILENAME"/dev/test_dev"/*设备文件名*/
#defineBUFF_SZ1024/*缓冲大小*/
intmain()
{
intfd,nwrite,nread;
charbuff[BUFF_SZ];/*缓冲区*/
/*打开设备文件*/
fd=open(TEST_DEVICE_FILENAME,O_RDWR);
if(fd<0)
{
perror("open");
exit(1);
}
do
{
printf("Inputsomewordstokernel(enter'quit'toexit):");
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("read");
break;
}
else
{
printf("Thereadstringisfromkernel:%s\n",buff);
}
}while(strncmp(buff,"quit",4));
close(fd);
exit(0);
}
4.实验结果首先在虚拟设备驱动源码目录下编译并加载驱动模块。
$makeclean;make
$./test_drv_load
接下来,编译并运行测试程序
$gcc–otesttest.c
$./test
测试程序运行效果如下:
Inputsomewordstokernel(enter'quit'toexit):Hello,everybody!
Thereadstringisfromkernel:Hello,everybody!/*从内核读取的数据*/
Inputsomewordstokernel(enter'quit'toexit):Thisisasimpledriver
Thereadstringisfromkernel:Thisisasimpledriver
Inputsomewordstokernel(enter'quit'toexit):quit
Thereadstringisfromkernel:quit
最后,卸载驱动程序
$./test_drv_unload
通过dmesg命令可以查看内核打印的信息:
$dmesg|tail–n10
……
Themajorofthetestdeviceis250/*当加载模块时打印*/
Thisisopenoperation/*当打开设备时打印*/
Thisisreleaseoperation/*关闭设备时打印*/
Testdeviceuninstalled/*当卸载设备时打印*/