ImageVerifierCode 换一换
格式:DOCX , 页数:13 ,大小:44.27KB ,
资源ID:7841720      下载积分:12 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/7841720.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(实验八Linux模块和设备驱动程序.docx)为本站会员(b****6)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

实验八Linux模块和设备驱动程序.docx

1、实验八Linux模块和设备驱动程序实验八 Linux模块和设备驱动程序一. 实验目的1. 通过实验了解linux 下文件驱动程序的框架; 2. 通过驱动程序的编写,理解linux对设备管理的方式; 3. 理解设备驱动程序中与内核交互部分。二. 实验指导Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它和dos或window环境下的驱动程序有很大的区别。1. Linux 设备在Linux中,用户进程不能直接对物理设备进行操作,必须通过系统调用向内核提出设备请求,由内核实现对物理设备的分配并完成进程请求的操作。在内核中实现对设备进行操作

2、的一组程序称为设备驱动程序。系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口,为应用程序屏蔽了硬件的细节,在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:1).对设备初始化和释放2)把数据从内核传送到硬件和从硬件读取数据3)读取应用程序传送给设备文件的数据和回送应用程序请求的数据4)检测和处理设备出现的错误在Linux系统把设备分为3类:块设备、字符设备和网络设备。每类设备都有独特的管理控制方式和不同的驱动程序。字符设备:以字符为单位进行输入输出的设备,并且以字符

3、为单位对设备中的信息进行组织和处理。包括:显示器、键盘、打印机、绘图仪、串口等。通常对字符设备传送的数据是顺序处理。字符设备以访问文件的方式访问。块设备:以一定大小的数据块为单位进行输入输出,设备中的数据也以物理块为单位进行组织和管理。块设备可以采取随机存取方法。包括硬盘、软盘、光盘、RAM盘等。通常作为外存使用,Linux文件系统建立在外存中,块设备通过文件系统访问。为匹配CPU与块设备间的速度差异,通常使用缓冲区传送数据。网络设备:与网络通信线路连接的网络适配器。Linux使用套接口以文件I/O方式提供对网络数据的访问。本实验主要介绍字符设备驱动程序的编写方法。2. 有关设备操作的系统调用

4、 在Linux系统中,应用程序操作设备就是访问设备对应的设备文件,与操作普通文件的访问方式基本相似。设备访问的系统调用主要如下:打开设备系统调用open:实现分配和打开设备的功能;读设备系统调用read:从设备读取数据;写设备系统调用write:向设备写数据;设备控制系统调用ioctl:控制设备的工作模式和状态;关闭设备系统调用close:释放设备,将设备归还系统。3. Linux设备驱动程序的构成 Linux系统的每个系统调用都有一个相应的内核函数实现该系统调用的功能。Linux设备驱动程序需要实现的内核函数由以下结构定义:struct file_operations int (*lseek

5、) (struct inode * ,struct file *, off_t ,int); int (*read) (struct inode * ,struct file *, char * ,int); /读例程,实现读系统调用int (*write) (struct inode * ,struct file *, const char * ,int); /写例程,实现读写系统调用int (*readdir) (struct inode * ,struct file *, void *, filldir * ,int);int (*select) (struct inode * ,str

6、uct file *, int ,select_table *);int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long); /控制例程,实现设备控制int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);int (*open) (struct inode * ,struct file *); /打开例程,打开设备int (*release) (struct inode * ,struct file *); /释放例程,设备释

7、放int (*fsync) (struct inode * ,struct file *);int (*fasync) (struct inode * ,struct file *,int);int (*check_media_change) (kdev_t dev);int (*revalidate) (dev_t dev); 可根据实际情况只实现必不可少的部分内核函数。如果int init_module(void)/驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules)。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不

8、利于调试,所以使用模块方式。采用模块方式还需要编写2个例程:init_module例程:加载设备驱动程序模块时执行;cleanup_module例程:卸载设备驱动程序时模块。4. 与设备有关的数据结构 Linux系统中与设备操作有关的数据结构如图8.1所示,包括如下几部分:(1) 设备文件通常位于/dev目录下,如/dev/mydrv,是用户程序或用户命令操作设备的接口。可以像操作普通文件一样来操作设备,一般先使用open系统调用打开设备,然后使用read、write、ioctl等系统调用控制设备和读写设备,最后使用close系统调用关闭设备。设备文件在外存上并没有存放信息的数据块,只包含一个

9、主设备号和一个次设备号。主设备号用于指明在该设备对应设备注册表的哪一个设备。如图8.1中设备文件mydrv的主设备号是1,对应设备注册表中的1号设备。次设备号表示该设备是同类设备中的第几个设备。图8.1 Linux系统中与设备驱动有关的数据结构(2) 字符设备注册表chrdevs Linux系统支持的所有字符设备都需要在字符设备注册表中注册,这是Linux内核中的一个数组,是设备在Linux系统是否存在的唯一标志。每个数组元素代表一类设备,数组元素的下标就是主设备号。设备注册表中保存了设备名和设备的file_operations结构的操作函数指针。(3) file_operations 是Li

10、nux内核中的数据结构,保存了设备各操作内核函数的指针。每类设备都有一个file_operations结构。(4) 设备驱动程序例程 对有关操作设备的每一个系统调用都在该设备的驱动程序中有一个对应的内核例程。在安装设备驱动程序时要把设备驱动程序的各个例程加载到内核中。 因此,安装设备驱动程序的主要工作包括以下3个方面:将设备加载到Linux内核对设备进行注册创建设备文件5. 设备访问过程根据图8.1,不难理解硬件设备的访问过程。(1) 打开设备:当用户程序执行open()系统调用时,从设备文件中得到主设备号,访问字符设备注册表,得到设备的file_operations数据结构,其中含有该设备驱

11、动程序各例程的指针。(2) 读设备:当用户程序执行read()系统调用时,将调用该设备驱动程序的read()例程。read()例程从设备读入数据到内核缓冲区,再从内核缓冲区传给用户程序的缓冲区。(3) 写设备:当用户程序执行write()系统调用时,将调用该设备驱动程序的write()例程。write()例程从用户缓冲区把数据复制到内核缓冲区,再从内核缓冲区把数据输出到硬件设备。(4) 设备控制:当用户执行设备控制系统调用ioctl()时,ioctl()例程从用户态复制命令和数据到内核,再对设备进行操作。6. 设备驱动程序中可引用的函数和数据结构设备驱动程序不在用户态运行,而在操作系统内核运行

12、,不能访问系统的任何程序库,因此平常编写应用程序能够调用的所有库函数几乎都不能在设备驱动程序中调用。如printf、scanf、atoi、open、fopen等函数都不可使用。设备驱动程序不能包含系统include目录下的stdio.h、stdlib.h等头文件。设备驱动程序只能调用Linux内核代码中实现的函数和例程。声明这些函数和例程的头文件通常在include/linux和include/asm目录下。设备驱动程序需要调用的函数和例程主要有以下几类:(1) 设备注册和卸载函数#include int register_chrdev(unsigned int major, const ch

13、ar *name , struct file_operations *fops); /设备注册 /注册设备,在设备注册表的major位置设置设备名和设备操作指针,/参数::/major:希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。/name::设备文件名,/fops:: 用来登记驱动程序实际执行操作的函数的指针。如果登记成功,返回设备的主设备 /号,不成功,返回一个负值unregister_chrdev(int major, char *name); /设备卸载 /参数为设备的主设备号和设备名(2) 用户空间与内核空间之间复制数据函数#include unsigned

14、 long copy_from_user (void *to, const void *from, unsigned long count); /将数据从用户态存储器from复制到核心态存储器tounsigned long copy_to_user (void *to, const void *from, unsigned long count); /将数据从核心态存储器from复制到用户态存储器to(3) 操作硬件设备函数读写I/O口的函数#include unsigned inb(unsigned port); /从I/O端口port读入一个字节的数据并返回该数据void outb(uns

15、igned char byte, unsigned port); /向I/O端口port写一个字节的数据byteunsigned inw(unsigned port);void outw(unsigned short word, unsigned port);unsigned inl(unsigned port);void outl(unsigned doubleword, unsigned port);unsigned inb_p(unsigned port);在内存和I/O端口之间传递字符串.void insb(unsigned port, void *addr, unsigned lon

16、g count); /从设备的I/O端口port读入count个字节的数据存入addr指定的内核存储器位置void outsb(unsigned port, void *addr, unsigned long count); /将addr指定的内核存储器位置的count个字节的数据从I/O端口port输出到设备 void insw(unsigned port, void *addr, unsigned long count);void outsw(unsigned port, void *addr, unsigned long count);void insl(unsigned port, v

17、oid *addr, unsigned long count);void outsl(unsigned port, void *addr, unsigned long count);访问I/O存储器的函数unsigned readb(address); /从I/O设备存储器地址address读入一个字节的数据,通常要求物理地址与逻辑地址一致unsigned readw(address); unsigned readl(address);void writeb(unsigned value, address);/将一个字节的数据value写入I/O设备存储器地址address void writ

18、ew(unsigned value, address);void writel(unsigned value, address);memset_io(address, value, count);memcpy_fromio(dest, source, nbytes);memcpy_toio(dest, source, nbytes);(4) 内核调试函数#include int printk(int level, const char * fmt, .); 该函数的语法与printf相似,fmt是格式串,后面是一些需要通过fmt显示的参数。而level对输出信息的严重程度对其分级。level参

19、数的取值通常有:KERN_EMERG:紧急信息KERN_CRIT:关键条件,常常与硬件或软件故障有关KERN_WARNING:警告信息,本身不产生严重问题KERN_INFO:通常用于系统启动时打印找到的软件、硬件及配置信息KERN_DEBUG:用于调试信息(5) 内核变量和数据结构 Linux内核中定义的数据结构、变量都可以有设备驱动程序访问。其中与进程管理有关的全局变量有:struct task_struct *current; 当前运行的进程。int nr_tasks=1; 系统中存在的进程数目。进程管理相关的头文件是:#include 任务结构体task_struct的主要成员有:int

20、 pid; : 进程号struct task_struct *next_run, *prev_run ; 可运行进程队列指针struct task_struct *p_pptr; 父进程任务结构体指针struct task_struct *p_cptr; 子进程任务结构体指针struct task_struct *p_osptr; 兄进程任务结构体指针struct task_struct *p_ysptr; 弟进程任务结构体指针exec_domain-name; 进程启动命令名7. 设备驱动程序开发实例剖析我们来写一个最简单的字符设备驱动程序,从用户进程传一些信息给内核,从内核传一些信息给用户

21、态进程。通过它可以了解Linux的设备驱动程序的工作原理。该设备驱动程序是一个C语言程序,文件名为testdrv.c,作为模块加载,模块名为testdrv,注册的设备名为testdev,与用户程序接口的设备文件为/dev/testdev。(1) 编写设备驱动程序的代码编写设备驱动程序的主要工作就是操作设备的各个内核函数,并填充file_operations的各个域。首先保护开发设备驱动程序所需的头文件:#include #include #include #include #include #include #include #include #include #include #define

22、 MAX_BUFFER_LEN 1000unsigned int test_major = 0;char data MAX_BUFFER_LEN;1) read() 例程:static int read_test (struct inode * inode ,struct file * file, char *buf ,int count)int i;copy_to_user(buf, k_buffer,count); /将数据从内核态缓冲区k_buffer传送到用户态缓冲区bufreturn count; /返回传送的字节数+当执行read系统调用时, read_test()被调用,它把用户

23、的缓冲区全部写1,buf 是read系统调用的一个参数,它是用户进程空间的一个地址。在test_read被调用时,系统进入核心态,将核心态数组缓冲区k_buffer的各个元素置1,并用核心态函数copy_to_user将数据从核心态存储器传送到用户态存储器,并返回实际传送的字节数。2)write ()例程 static int write_test(struct inode *inode, struct file *file, const char *buf,int count)int i;copy_from_user(data,buf, count); /将数据从用户态缓冲区buf传送到内核

24、态缓冲区k_bufferreturn count; /返回传送的字节数3) 打开和关闭例程static int open_test(struct inode *inode,struct file *file )MOD_INC_USE_COUNT; /模块计数增加1return 0; static void release_test(struct inode *inode,struct file *file )MOD_DEC_USE_COUNT; /模块计数减14) 模块安装和初始化例程 如果要求设备驱动程序作为一个模块加载到系统内核中,则应该提供init_module函数,并在其中注册设备。#

25、define MODULE /必须定义MODULE宏#include int init_module(void) /初始化file_operations结构struct file_operations test_fops = NULL,read_test,write_test,NULL, /* test_readdir */NULL,NULL, /* test_ioctl */NULL, /* test_mmap */open_test,NULL,release_test, NULL, /* test_fsync */NULL, /* test_fasync */NULL,NULL,NULL,

26、NULL,NULL,/* nothing more, fill with NULLs */;int result;result = register_chrdev(0, testdrv, &test_fops); /注册字符设备驱动程序,设备名testdev/返回系统选择的主设备号if (result 0) printk(KERN_INFO test: cant get major number ); return result;if (test_major = 0) test_major = result; /* dynamic */return 0;在用insmod命令将编译好的模块调入内

27、存时,init_module 函数被调用,调用register_chrdev向系统的字符设备表登记了一个字符设备。5) 模块卸载例程 同样,设备驱动程序作为模块加载时,还应提供cleanup_module函数,实现模块的卸载,并释放字符设备test在系统字符设备表中占有的表项。在用rmmod卸载模块时,cleanup_module函数被调用。cleanup_module函数的代码如下:void cleanup_module(void)/ 模块卸载,名字是规定的unregister_chrdev(test_major, testdrv );一个极其简单的字符设备可以说写好了,文件名就叫testd

28、rv.c,把以上全部代码放到一个文件testdrv.c中。完整的程序为testdrv.c,请见附录8。(2) 编译设备驱动程序# gcc -c -DMODULE -D_KERNEL_ -o testdrv.o testdrv.c -I/usr/src/linux-2.4/include各编译选项说明:-c 仅生成目标代码,不生成可执行程序,不需要连接语言库-O2 采用O2级优化-DMODULE 定义宏MODULE,表示设备驱动程序将作为一个模块加载到内核中-D_KERNEL_ 定义一个宏_KERNEL_,表示设备驱动程序将作为内核代码运行-o testdrv.o 指明生成的目标代码文件是tes

29、tdrv.otestdrv.c 设备驱动程序源代码-I/usr/src/linux2.4/include 从该目录下查找编译设备驱动程序需要的头文件得到文件testdrv.o就是一个设备驱动程序的目标代码。(3) 安装设备驱动程序# insmod ./testdrv.o执行该命令要求具有超级用户才能有权限,将设备驱动程序插入内核时,将执行设备驱动程序的init_module例程,完成设备驱动程序的注册。(4) 查看驱动程序主设备号如果设备驱动程序安装成功,在/proc/modules文件中可看到该设备驱动程序模块名:testdrv。在/proc/devices文件中就可以看到设备testdev

30、,并可以看到它的主设备号。查看这两个文件的命令分别为:# more /proc/modules# more /proc/devices(5)创建设备文件根据上一步查到的主设备号,任选一个次设备号,创建设备文件。假定查到的主设备号是253,则可用以下命令为其创建一个设备文件:次设备号# mknod /dev/testdrv c 253 1为了让所有用户都可对其执行读写操作,设置该设备文件的访问权限: # chmod 666 /dev/testdrv(6) 测试设备驱动程序我们现在可以通过设备文件来访问我们的驱动程序。写一个测试程序test.c。test.c的参考程序请见附录8。以下编译和运行test.c。$ gcc test.c o test 编译$ ./test

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1