嵌入式Linux設(shè)備驅(qū)動開發(fā)之:實驗內(nèi)容——test驅(qū)動
掃描二維碼
隨時隨地手機看文章
該實驗是編寫最簡單的字符驅(qū)動程序,這里的設(shè)備也就是一段內(nèi)存,實現(xiàn)簡單的讀寫功能,并列出常用格式的Makefile以及驅(qū)動的加載和卸載腳本。讀者可以熟悉字符設(shè)備驅(qū)動的整個編寫流程。
2.實驗內(nèi)容該實驗要求實現(xiàn)對虛擬設(shè)備(一段內(nèi)存)的打開、關(guān)閉、讀寫的操作,并要通過編寫測試程序來測試虛擬設(shè)備及其驅(qū)動運行是否正常。
3.實驗步驟(1)編寫代碼。
這個簡單的驅(qū)動程序的源代碼如下所示:
/*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;
/*讀函數(shù)*/
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))/*將內(nèi)核緩沖的數(shù)據(jù)拷貝到用戶空間*/
{
return-EFAULT;
}
returncount;
}
/*寫函數(shù)*/
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))/*將用戶緩沖的數(shù)據(jù)復制到內(nèi)核空間*/
{
return-EFAULT;
}
returncount;
}
/*打開函數(shù)*/
staticinttest_open(structinode*inode,structfile*file)
{
printk("Thisisopenoperation\n");
/*分配并初始化緩沖區(qū)*/
data=(char*)kmalloc(sizeof(char)*BUFF_SZ,GFP_KERNEL);
if(!data)
{
return-ENOMEM;
}
memset(data,0,BUFF_SZ);
return0;
}
/*關(guān)閉函數(shù)*/
staticinttest_release(structinode*inode,structfile*file)
{
printk("Thisisreleaseoperation\n");
if(data)
{
kfree(data);/*釋放緩沖區(qū)*/
data=NULL;/*防止出現(xiàn)野指針*/
}
return0;
}
/*創(chuàng)建、初始化字符設(shè)備,并且注冊到系統(tǒng)*/
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);
}
}
/*虛擬設(shè)備的file_operations結(jié)構(gòu)*/
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)
{/*靜態(tài)注冊一個設(shè)備,設(shè)備號先前指定好,并設(shè)定設(shè)備名,用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)編譯代碼。
虛擬設(shè)備的驅(qū)動程序的Makefile如下所示:
ifeq($(KERNELRELEASE),)
KERNELDIR?=/lib/modules/$(shelluname-r)/build/*內(nèi)核代碼編譯路徑*/
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)加載和卸載模塊。
通過下面兩個腳本代碼分別實現(xiàn)驅(qū)動模塊的加載和卸載。
加載腳本test_drv_load如下所示:
#!/bin/sh
#驅(qū)動模塊名稱
module="test_drv"
#設(shè)備名稱。在/proc/devices中出現(xiàn)
device="test_dev"
#設(shè)備文件的屬性
mode="664"
group="david"
#刪除已存在的設(shè)備節(jié)點
rm-f/dev/${device}
#加載驅(qū)動模塊
/sbin/insmod-f./$module.ko$*||exit1
#查到創(chuàng)建設(shè)備的主設(shè)備號
major=`cat/proc/devices|awk"\\$2==\"$device\"{print\\$1}"`
#創(chuàng)建設(shè)備文件節(jié)點
mknod/dev/${device}c$major0
#設(shè)置設(shè)備文件屬性
chgrp$group/dev/${device}
chmod$mode/dev/${device}
卸載腳本test_drv_unload如下所示:
#!/bin/sh
module="test_drv"
device="test_dev"
#卸載驅(qū)動模塊
/sbin/rmmod$module$*||exit1
#刪除設(shè)備文件
rm-f/dev/${device}
exit0
(6)編寫測試代碼。
最后一步是編寫測試代碼,也就是用戶空間的程序,該程序調(diào)用設(shè)備驅(qū)動來測試驅(qū)動的運行是否正常。以下實例只實現(xiàn)了簡單的讀寫功能,測試代碼如下所示:
/*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"/*設(shè)備文件名*/
#defineBUFF_SZ1024/*緩沖大小*/
intmain()
{
intfd,nwrite,nread;
charbuff[BUFF_SZ];/*緩沖區(qū)*/
/*打開設(shè)備文件*/
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)/*向設(shè)備寫入數(shù)據(jù)*/
{
perror("write");
break;
}
if(read(fd,buff,BUFF_SZ)<0)/*從設(shè)備讀取數(shù)據(jù)*/
{
perror("read");
break;
}
else
{
printf("Thereadstringisfromkernel:%s\n",buff);
}
}while(strncmp(buff,"quit",4));
close(fd);
exit(0);
}
4.實驗結(jié)果首先在虛擬設(shè)備驅(qū)動源碼目錄下編譯并加載驅(qū)動模塊。
$makeclean;make
$./test_drv_load
接下來,編譯并運行測試程序
$gcc–otesttest.c
$./test
測試程序運行效果如下:
Inputsomewordstokernel(enter'quit'toexit):Hello,everybody!
Thereadstringisfromkernel:Hello,everybody!/*從內(nèi)核讀取的數(shù)據(jù)*/
Inputsomewordstokernel(enter'quit'toexit):Thisisasimpledriver
Thereadstringisfromkernel:Thisisasimpledriver
Inputsomewordstokernel(enter'quit'toexit):quit
Thereadstringisfromkernel:quit
最后,卸載驅(qū)動程序
$./test_drv_unload
通過dmesg命令可以查看內(nèi)核打印的信息:
$dmesg|tail–n10
……
Themajorofthetestdeviceis250/*當加載模塊時打印*/
Thisisopenoperation/*當打開設(shè)備時打印*/
Thisisreleaseoperation/*關(guān)閉設(shè)備時打印*/
Testdeviceuninstalled/*當卸載設(shè)備時打印*/