嵌入式操作系统实验五 设备驱动.docx
- 文档编号:2226160
- 上传时间:2023-05-02
- 格式:DOCX
- 页数:27
- 大小:463.52KB
嵌入式操作系统实验五 设备驱动.docx
《嵌入式操作系统实验五 设备驱动.docx》由会员分享,可在线阅读,更多相关《嵌入式操作系统实验五 设备驱动.docx(27页珍藏版)》请在冰点文库上搜索。
嵌入式操作系统实验五设备驱动
实验五字符设备驱动
王威SA12226437芯片设计班
准备Makefile
使用了设备驱动课上的编译驱动模块的“万能makefile”只需改一下依赖的文件名即可
LINUX_KERNEL:
=$(shelluname-r)
KERNELDIR=/usr/src/linux-headers-$(LINUX_KERNEL)
PWD:
=$(shellpwd)
//KERNELDIR为内核路径,/usr/src/下存放内核,pwd变量取得当前的路径。
CC=$(CROSS_COMPILE)gcc
obj-m:
=driver_demo.o
//CC为编译器的路径,CROSS_COMPILE为已经设定好的内核交叉编译器路径,如arm-linux-,与后面的gcc链接即为arm-linux-gcc。
定义生成的目标为driver_demo.ko的模块,所依赖的文件时.o文件,编译时发现没有,会自动生成,本机没有配置CROSS_COMPILE所以为空,使用默认编译器gcc
moudles:
$(MAKE)-C$(KERNELDIR)M=$(PWD)modules//执行makemodules命令,make会进入KERNEL_DIR目录执行此目录下的Makefile,然后在返回PWD目录执行自己写的Makefile。
modules_install:
cpdriver_demo.ko$(INSTALLDIR)
//执行makeinstall命令,会将驱动拷贝到INSTALLDIR目录下。
clean:
rm-rf*.o*~core.depend.*.cmd*.ko*.mod.c.tmp_versions
//执行makeclean命令,会将生成的.o及其它中间文件删除。
也可以使用PPT上给出的makefile格式(千万注意格式,特别是空格)
obj-m:
=xxx.o
KD?
=/lib/modules/$(shelluname-r)/build
PWD:
=$(shellpwd)
default:
$(MAKE)-C$(KD)M=$(PWD)modules
clean:
rm$(obj-m)*.o*.koModule*module**.mod.c
Makefile的语法机制比较复杂,而且非常重要,比如分布在内核源码各个文件夹中的makefile文件定义了内核的编译规则等等,可以作为一个专项来学习,它的可读性比较差,使用了大量的简化符号,但本质都是gcc命令在makefile规则下的集合。
Makefile的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成Linux内核二进制文件。
1.编译模块
将5个源文件拷贝到工作文件夹(确保之前的工作文件夹没有makefile文件),执行make命令,进行编译。
其中driver_demo.c和test_demo.c、driver_demo2.c和test_demo2.c、是两套实验程序,即本实验的步骤从这两套程序重复两次。
其中XX_demo.c严格按照模块编程要求(四个宏两个函数),加上驱动程序的要求(cdev结构体的填充,包括申请设备号并填充,编写file_operation并填充)。
执行make后如下:
生成如下文件:
包含模块driver_demo.ko
2.安装驱动模块
即将模块加载到内核,在加载的过程中会向内核注册cdev结构体,即注册一个字符设备。
insmoddriver_demo.ko
Lsmod显示加载进内核的所有模块
cat/proc/devices查看系统中设备
相关:
cat主要有三大功能:
1.一次显示整个文件。
$catfilename
2.从键盘创建一个文件。
$cat>filename
只能创建新文件,不能编辑已有文件.
3.将几个文件合并为一个文件。
$catfile1file2>file
相关:
/proc/devices/下的设备是驱动程序生成的,它可产生一个major供mknod作为参数。
/dev/下的设备是通过mknod加上去的,用户通过此设备名来访问驱动。
也就是说proc/device是显示系统拥有的所有外部设备(驱动程序注册),而dev是通过建立节点生成的所有的设备文件,外部设备面向程序员的接口
252memdevice是我们加载驱动模块时向内核注册的设备,它就是一个cdev结构体。
系统为我们分配了主设备号252。
3、在/dev下建立设备文件(结点):
结点的名称根据程序中你定义的,要访问的结点名称而定。
建立节点对应的设备号与系统为驱动模块分配的一致即可。
test_demo.c里访问的设备文件名称是memdevice0,所以我们建立设备文件(结点)时使用该名称,但设备号要与系统分配的设备号保持一致。
然后执行mknod/dev/memdevice0c2520来建立结点,随后查看/dev下建立的结点。
一定要在dev目录下建立节点,linux系统默认从那个目录下寻找驱动文件。
结点建立成功。
4、编译并运行应用程序,它使用模块中的函数,用以验证驱动的可动性。
执行gcc-otest_demotest_demo.c命令,生成test_demo可执行文件。
或者直接gcctest_demo.c默认生成a.out
验证驱动程序a.out运行结果如下
5、驱动模块卸载
卸载驱动模块,并删除设备结点。
sudormmoddriver_demo.ko
rm-rfmmdevice0像删普通文件一样就行
另:
对于实验的第二个使用driver文件夹下的driver_demo2.c和test_demo2.c这两个文件是陈博老师的设备驱动课上的实验源码。
自己写难度太大了,将makefile文件中的编译目标改一下,用这两个文件将实验再做一遍。
重复步骤1到步骤5
一些小收获:
1、编写makefile文件时一定要注意,首字母一定要大写,Makefile
2、Makefile文件中的目标文件与依赖文件分写两行,依赖文件前面有一个tab空格,而PPT中给的是两个tab空格,会报错。
3、建立文件时可以再工作文件夹右键直接建立一个空文档,然后双击进入编辑。
若是在终端里直接用gedit建立的文件,可能会没有修改权限。
4、重命名:
mvabcABC
5、虚拟机设置一定要“共享粘贴板”,不然很麻烦。
6、VI两个新命令:
X删除当前字符。
I在光标前添加字符。
A是在光标后添加字符。
**********************************************************************************************附:
试着分析一下驱动程序源码和验证程序源码。
linux博大精深,需要慢慢积累,很多时候问问题,没有一个人能给出全面的回答
**********************************************************************************************
Driver_demo.c的源码如下:
黑色粗字及彩色为注释
/*****************************************************
*@File:
driver_demo.c
*@Created:
Tue28Dec201009:
43:
37AMCST
*@Author:
muryo
*@Description:
linuxdriverexample
*@
*******************************************************/
编译器找头文件默认在/usr/include里找,如果/usr/include里找不到就根据编译参数-I指定的路径里找.
/usr/src/linux/include/linux是给编译内核用的
/usr/include/linux是给编译应用程序用的
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
上面的头文件均在内核源码中,里面有模块编程和驱动编程的内核函数linux-2.6.22/include/linux/xx.h
/*全局变量设置*/
#defineMEM_DRIVER_MAJOR249//静态定义主设备号
#defineMEM_DRIVER_MAJOR0//动态定义主设备号
#defineMEM_DRIVER_SIZE128//定义内存空间大小
#define_MEM_CLEAR1//定义ioctl命令,清空内存
#define_MEM_REVERSE5//定义ioctl命令,将内存中的数据倒置
#define_MEM_LOGO3//定义ioctl命令,打印菱形
/*设备结构声明*/
structmem_dev
{
structcdevcdev;//字符设备结构,
内核用cdev代表一个字符设备,我们要填充其成员,并在模块加载的时候向内核注册这个结构体,相当于添加了一个设备。
其成员如下
unsignedcharmem_space[MEM_DRIVER_SIZE];//设备内存空间
};
structmem_dev*dev;
staticintmem_driver_major=MEM_DRIVER_MAJOR;
/*函数声明*/
staticintmemdriver_init(void);//设备初始化函数
staticvoidmemdriver_exit(void);//设备释放函数,
这两个函数在insmod和rmmod是执行,放在init和exit宏里。
下面的这几个函数非常重要,是file_operation的成员,是系统调用的真正有作用的驱动函数。
//设备读取操作函数,
staticssize_tmemdriver_read(structfile*filp,char__user*buff,size_tcount,loff_t*offp);
//设备写操作函数
staticssize_tmemdriver_write(structfile*filp,constchar__user*buff,size_tcount,loff_t*offp);
//设备控制函数
驱动程序一般需支持通过Ioctl实现各种控制与参数设置,如串口可设置波特率等多参数
cmd变量存放命令,驱动代码根据cmd里面的值进行switch-case处理分支
staticintmemdriver_ioctl(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg);
//设备打开函数
staticintmemdriver_open(structinode*inode,structfile*filp);
//设备关闭函数
staticintmemdriver_release(structinode*inode,structfile*filp);
定义一个file_operation结构体类型的变量memdriver_fops,填充cdev
staticconststructfile_operationsmemdriver_fops=
{
.owner=THIS_MODULE,
.open=memdriver_open,
.release=memdriver_release,
.read=memdriver_read,
.write=memdriver_write,
.ioctl=memdriver_ioctl,
};
//初始化设备memdriver_cdev结构函数,我们定义的memdriver_cdev中包含了cdev和设备内存空间
staticvoidmemdriver_cdev_setup(structmem_dev*dev,intindex)
{
interr;
intdevno=MKDEV(mem_driver_major,index);//由主次得到设备号dev_t,index为次设备号
cdev结构体的dev_t成员定义了设备号为32位
高12位为主设备号
低20位为次设备号
获取主设备号
MAJOR(dev_tdev)
获取次设备号
MINOR(dev_tdev)
通过主设备号和次设备号生成dev_t
MKDEV(intmajor,intminor)
linux-2.6.22/include/linux/cdev.h
voidcdev_init(structcdev*cdev,conststructfile_operations*fops);
@cdev:
thestructuretoinitialize
@fops:
thefile_operationsforthisdevice
cdev_init(&dev->cdev,&memdriver_fops);//将file_operation填充到memdriver_cdev中cdev中
dev->cdev.owner=THIS_MODULE;
dev->cdev.ops=&memdriver_fops;
err=cdev_add(&dev->cdev,devno,1);利用已经初始化的cdev结构体,和申请了的设备号,向内核注册一个字符设备。
cdev_add()—addachardevicetothesystem
-------------------------------------------------
linux-2.6.22/include/linux/cdev.h
intcdev_add(structcdev*p,
dev_tdev,
unsignedcount);
@p:
thecdevstructureforthedevice
@dev:
thefirstdevicenumberforwhichthisdeviceisresponsible
@count:
thenumberofconsecutiveminornumberscorrespondingtothisdevice
cdev_add()addsthedevicerepresentedby@ptothesystem,makingitliveimmediately.Anegativeerrorcodeisreturnedonfailure.
if(err)
printk(KERN_NOTICE"memdriver:
Error%daddingmem_device%d\n",err,index);
}
对于做嵌入式或者熟悉linux内核的人来说,对printk这个函数一定不会感到陌生。
printk相当于printf的孪生姐妹,她们一个运行在用户态,另一个则在内核态被人们所熟知。
printk是在内核中运行的向控制台输出显示的函数,Linux内核首先在内核空间分配一个静态缓冲区,作为显示用的空间,然后调用sprintf,格式化显示字符串,最后调用tty_write向终端进行信息的显示。
printk与printf的差异,是什么导致一个运行在内核态而另一个运行用户态?
其实这两个函数的几乎是相同的,出现这种差异是因为tty_write函数需要使用fs指向的被显示的字符串,而fs是专门用于存放用户态段选择符的,因此,在内核态时,为了配合tty_write函数,printk会把fs修改为内核态数据段选择符ds中的值,这样才能正确指向内核的数据缓冲区,当然这个操作会先对fs进行压栈保存,调用tty_write完毕后再出栈恢复。
总结说来,printk与printf的差异是由fs造成的,所以差异也是围绕对fs的处理。
staticintmemdriver_init(void)
{
intresult;
dev_tdevno=MKDEV(mem_driver_major,0);
if(mem_driver_major)//宏值不为零的时候,用该宏值与index生成的设备号直接申请
{
result=register_chrdev_region(devno,1,"memdevice");
intregister_chrdev_region(dev_tfirst,unsignedintcount,char*name);
向内核申请设备号,该设备号可用?
如果分配成功进行,register_chrdev_region的返回值是0。
出错的情况下,返回一个负的错误码。
相当于手动申请设备号
}
else
{
result=alloc_chrdev_region(&devno,0,1,"memdveice");
如果设备号未知,向系统动态申请未被占用的设备号的情况,当调用成功后,会把得到的设备号放入第一个参数dev中
mem_driver_major=MAJOR(devno);
}
if(result<0)
returnresult;
dev=kmalloc(sizeof(structmem_dev),GFP_KERNEL);
分配内存
kmalloc--分配内存
语法
void*kmalloc(size_tsize,intflags);
参数
size
size要分配内存的大小.以字节为单位.
flag
flags要分配内存的类型.
GFP_KERNEL是在linux/gfp.h中定义的一个宏,是分配内核空间的内存时的一个标志位。
这个标志位分配内存的一个选项,GFP_KERNEL是内核内存分配时最常用的,无内存可用时可引起休眠。
if(!
dev)//申请内核空间不成功的话,则释放已经申请的设备号。
一般在调用cdev_del函数从系统注销字符设备之后调用
{
result=-ENOMEM;
unregister_chrdev_region(devno,1);
returnresult;
}
else
{
memset(dev,0,sizeof(structmem_dev));
void*memset(void*s,intch,size_tn);
函数解释:
将s中前n个字节用ch替换并返回s。
memset:
作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
memdriver_cdev_setup(dev,0);//初始化cdev结构体,并向内核注册设备。
return0;
}
}
staticvoidmemdriver_exit(void)
{
cdev_del(&dev->cdev);调用cdev_del函数从系统注销字符设备
kfree(dev);//释放空间
unregister_chrdev_region(MKDEV(mem_driver_major,0),1);//释放设备号
}
staticintmemdriver_open(structinode*inode,structfile*filp)
{
try_module_get(dev->cdev.owner);//设备打开计数器,打开设备计数器+1
try_module_get如果模块已经插入内核,则递增该模块引用计数;如果该模块还没有插入内核,则返回0表示出错include/linux/module.h,在lsmod后,数字即引用数
printk(KERN_NOTICE"memdriver:
DeviceOpensucceed!
\n");
return0;
}
staticintmemdriver_release(structinode*inode,structfile*filp)
{
module_put(dev->cdev.owner);//释放设备,计数器-1
模块在被使用时,是不允许被卸载的。
2.4内核中,模块自身通过MOD_INC_USE_COUNT,MOD_DEC_USE_COUNT宏来管理自己被使用的计数。
2.6内核提供了更健壮、灵活的模块计数管理接口try_module_get(&module),module_put(&module)取代2.4中的模块使用计数管理宏;模块的使用计数不必由自身管理,而且在管理模块使用计数时考虑到SMP与PREEMPT机制的影响。
inttry_module_get(structmodule*module);用于增加模块使用计数;若返回为0,表示调用失败,希望使用的模块没有被加载或正在被卸载中。
voidmodule_put(structmodule*module);减少模块使用计数。
try_module_get与module_put的引入与使用与2.6内核下的设备模型密切相关。
模块是用来管理硬件设备的,2.6内核为不同类型的设备定义了structmodule*owner域,用来指向管理此设备的模块。
printk(KERN_NOTICE"memdriver:
DeviceReleasesucceed!
\n");
return0;
}
staticssize_tmemdriver_read(structfile*filp,char__user*buff,size_tcount,loff_t*offp)
{
if(count>MEM_DRIVER_SIZE)
count==MEM_DRIVER_SIZE;
/*
*将内核空间数据拷贝到用户空间
*/
if(copy_to_user(buff,dev->mem_space,count))
{
printk(KERN_NO
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式操作系统实验五 设备驱动 嵌入式 操作系统 实验 设备 驱动