实验四 linux驱动程序的编写
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
中国石油大学(北京)计算机科学与技术系
实验报告
实验名称:linux驱动程序的编写
学号:2017215538 姓名:于宁班级:信息研17-4班
完成日期:2018 年 4 月16 日
一、实验目的
1.掌握linux驱动程序的编写方法;
2.掌握驱动程序动态模块的调试方法;
3.掌握驱动程序填加到内核的方法。
二、实验内容
1. 学习linux驱动程序的编写流程;
2. 学习驱动程序动态模块的调试方法;
3. 学习驱动程序填加到内核的流程。
三、实验设备
1. PentiumII以上的PC机,LINUX操作系统,EL-ARM830实验箱。
四、linux的驱动程序的编写
嵌入式应用对成本和实时性比较敏感,而对linux的应用主要体现在对硬件的驱动程序的编写和上层应用程序的开发上。
嵌入式linux驱动程序的基本结构和标准Linux的结构基本一致,也支持模块化模式,所以,大部分驱动程序编成模块化形式,而且,要求可以在不同的体系结构上安装。
linux是
可以支持模块化模式的,但由于嵌入式应用是针对具体的应用,所以,一般不采用该模式,而是把驱动程序直接编译进内核之中。
但是这种模式是调试驱动模块的极佳方法。
设备驱动程序是操作系统内核和机器硬件之间的接口。
设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。
同时,设备驱动程序是内核的一部分,它完成以下的功能:对设备初始化和释放;把数据从内核传送到硬件和从硬件读取数据;读取应用程序传送给设备文件的数据和回送应用程序请求的数据;检测和处理设备出现的错误。
在linux操作系统下有字符设备和块设备两类主要的设备文件类型。
字符设备和块设备的主要区别是: 在对字符设备发出读写请求时,实际的硬件I/O一般就紧接着发生了;块设备利用一块系统内存作为缓冲区,当用户进程对设备请求满足用户要求时,就返回请求的数据。
块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。
4.1 设备驱动程序的 file_operations 结构
通常,一个设备驱动程序包括两个基本的任务:驱动设备的某些函数作为系统调用执行;而某些函数则负责处理中断(即中断处理函数)。
而file_operations 结构的每一个成员的名称都对应着一个系统调用。
用户程序利用系统调用,比如在对一个设备文件进行诸如read操作时,这时对应于该设备文件的驱动程序就会执行相关的ssize_t (*read) (struct file *, char *, size_t, loff_t *);函数。
在操作系统内部,外部设备的存取是通过一组固定入口点进行的,这些入口点由每个外设的驱动程序提供,由file_operations结构向系统进行说明,因此,编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。
file_operations结构在kernel/include/linux/fs.h中可以找到。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
#ifdef MAGIC_ROM_PTR
int (*romptr) (struct file *, struct vm_area_struct *);
#endif /* MAGIC_ROM_PTR */
};
其中主要的函数说明如下:
1.open 是驱动程序用来完成设备初始化操作的, open还会增加设备计数,以防止文件
在关闭前模块被卸载出内核。
open主要完成以下操作:检查设备错误(诸如设备未就
绪或相似的硬件问题);如果是首次打开,初始化设备;标别次设备号;分配和填写要
放在file→private_data内的数据结构;增加使用计数。
2. read 用来从外部设备中读取数据。
当其为NULL指针时,将引起read系统调用返
回-EINVAL(“非法参数”)。
函数返回一个非负值表示成功地读取了多少字节。
3. write 向外部设备发送数据。
如果没有这个函数,write 系统调用向调用程序返回
一个-EINVAL。
如果返回值非负,就表示成功地写入的字节数。
4. release是当设备被关闭时调用这个操作。
release的作用正好与open相反。
这个设
备方法有时也称为close。
它应该完成以下操作:使用计数减1;释放open分配在file
→private_data中的内存,在最后一次关闭操作时关闭设备。
5.llseek 是改变当前的读写指针。
6.readdir 一般用于文件系统的操作。
7.poll 一般用于查询设备是否可读可写或处于特殊的状态。
8.ioctl 执行设备专有的命令。
9.mmap 将设备内存映射到应用程序的进程地址空间。
4.2 设备驱动程序编写的具体内容
通过了解驱动程序的file_operations 结构,用户就可以编写出相关外部设备的驱动程序。
首先,用户在自己的驱动程序源文件中定义file_operations结构,并编写出设备需要的各操作函数,对于设备不需要的操作函数用NULL初始化,这些操作函数将被注册到内核,当应用程序对设备相应的设备文件进行文件操作时,内核会找到相应的操作函数,并进行调用。
如果操作函数使用NULL,操作系统就进行默认的处理。
定义并编写完file_operations结构的操作函数后,要定义一个初始化函数,比如函数名可device_init(),在linux初始化的时候要调用该函数,因此,该函数应包含以下几项工作:
a. 对该驱动所使用到的硬件寄存器进行初始化。
包括中断寄存器。
b. 初始化设备相关的参数。
一般来说每个设备要定义一个设备变量,用来保存设备相
关的参数。
c. 注册设备。
Linux内核通过主设备号将设备驱动程序同设备文件相连。
每个设备有且
仅有一个主设备号。
通过查看linux系统中/proc下的devices文件,该文件记录已经使用的主设备号和设备名,选择一个没有使用的主设备号,调用下面的函数来注册设备。
int register_chrdev(unsigned int,const char*,struct file_operations*),其中的三个参数代表主设备号,设备名,file_operations的结构地址。
d.注册设备使用的中断。
注册中断使用的函数。
int request_irq(unsigned irq,void(*handler)(int,void*,struct
pt_regs*),unsigned long flags, const char* device, void* dev_id);其中,irq是中断向量。
硬件系统将IRQn映射成中断向量。
handler-----中断处理函数。
flags-----中断处理中的一些选项的掩码。
device-----设备的名称
dev_id------在中断共享时使用的id。
e.其他的一些初始化工作,比如给设备分配I/O,申请DMA通道等。
当设备的驱动程序使用了如下的函数方式,则设备驱动可以动态的加载和卸载
int __init device_init (void)
void __exit device_exit(void)
module_init(device _init);
module_exit(device _exit);
当然,也可以编译进内核中。
4.3 将设备驱动加到linux内核中
设备驱动程序写完后,就可以加到linux的内核中了,这需要修改linux的源码,然后重新编译linux内核。
1. 将设备驱动文件(比如device_driver.c)复制到kernel/drivers/char目录下,该
目录保存了linux的字符型设备的设备驱动程序。
该驱动程序中,使用 int __int device_init(void)方式编写。
2.在 kernel/drivers/char目录下的Makefile文件中填加如下代码:
ifeq($(CONFIG_DEVICE_DRIVER),y)
L_OBJS+= DEVICE_DRIVER.o
endif
或obj-$(CONFIG_DEVICE_DRIVER) += DEVICE_DRIVER.o
如果在配置linux内核的时候,选择了支持我们定义的设备,则在编译内核的时候,会编译DEVICE_DRIVER.c,生成DEVICE_DRIVER.o文件。
3.在kernel/drivers/char目录下修改config.in文件。
在comment 'Character
devices'下面填加:
bool ‘support for DEVICE_DRIVER’ CONFIG_DEVICE_DRIVER
这样在编译内核时,运行make menuconfig时,在配置字符设备时就会出现support for DEVICE_DRIVER的字样。
当选中它时,编译通过,则驱动程序就加到内核中去了。
在文件系统cramfs中加上设备驱动程序对应的设备文件。
挂在操作系统中的设备都使用了设备驱动程序,要使一个设备成为应用程序可以访问的设备,必须在文件系统中有一个代表此设备的设备文件,通过使用设备文件,就可以对外部设备进行具体操作。
设备文件都包含在/dev目录下,linux使用的根文件系统是cramfs文件系统。
这个系统是一个只读压缩文件系统,要在制作cramfs文件系统之前,在root_tech 目录结构中的/usr/etc/rc.local文件下,添加相应的设备文件。
用mknod命令来创建一个设备文件:mknod device_driver c 120 0, device_driver为设备文件名,c指的是字符设备,120是主设备号,0为次设备号。
device_driver这个名字与注册函数中使用的字符串要一致!
4.4 将设备驱动编译成驱动模块
使用同一个驱动程序的源代码,当然一定要如下定义某些函数int __init
device_init (void);void __exit device_exit(void);module_init(device
_init);module_exit(device_exit);利用相应的交叉编译器,以及编译命令,就能
把device_driver.c编译成device_driver.o这样的动态驱动模块。
当编译通过后,
利用nfs网络文件系统,mount到根文件系统下,在存有驱动模块的文件系统下,在
linux系统的终端中,键入加载驱动模块命令 insmod device_driver.o,则系统安
装上驱动模块,如果在/dev目录下没有相应的设备文件,就可以使用 mknod
device_name c 主设备号从设备号来创建一个设备文件。
从而正确使用驱动模
块。
当卸载驱动模块时,使用 rmmod device_driver即可。
删除设备文件则使用 rm
device_name. 具体的实验操作步骤见各驱动程序的章节。
五、实验总结
1. 实验步骤:
(1)编写驱动程序;
(2)将驱动程序加载到内核;
(3)利用交叉编译器编译,加载到开发板。
程序:
#define MODULE
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE(“GPL”);
static int __init hello_init(void)
{
printk(“<0>Hello!”);
return (0);
}
static void __exit hello_exit(void)
{
printk(“<0>Exit!”);
}
module_init(hello_init);
module_exit(hello_exit);
2.实验结果:
(1)编写驱动程序
(2)将驱动程序加载到内核
(3)利用交叉编译器编译,加载到开发板。
2.实验现象结论:编写驱动程序,并成功加载到了内核。
3.分析说明:正确编写c文件是保证试验成功的因素之一,其次是改写驱动程序,然后将驱动加载到内核就可以得出正确的显示结果。
4.实验体会:在本实验中,学习了如何在Linux环境中编写驱动程序并将其加载到内核,遇到了很多问题,上网寻求帮助,并在老师的帮助下,解决了很多问题。