当前位置:文档之家› 驱动程序

驱动程序

linux 驱动程序设计实验一实验目的1.了解LINUX操作系统中的设备驱动程序的组成2.编写简单的字符设备驱动程序并进行测试3.编写简单的块设备驱动程序并进行测试4.理解LINUX操作系统的设备管理机制二准备知识1. LINUX下驱动程序基础知识Linux抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。

在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。

字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。

块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。

块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。

一般说来,PCI卡通常都属于字符设备。

我们常见的驱动程序就是作为内核模块动态加载的,比如声卡驱动和网卡驱动等,这些驱动程序源码可以修改到内核中,也可以把他们编译成模块形势,在需要的时候动态加载. 而Linux最基础的驱动,如CPU、PCI总线、TCP/IP协议、APM (高级电源管理)、VFS等驱动程序则编译在内核文件中。

有时也把内核模块就叫做驱动程序,只不过驱动的内容不一定是硬件罢了,比如ext3文件系统的驱动。

当我们加载了设备驱动模块后,应该怎样访问这些设备呢?Linux是一种类Unix系统,Unix的一个基本特点是“一切皆为文件”,它抽象了设备的处理,将所有的硬件设备都像普通文件一样看待,也就是说硬件可以跟普通文件一样来打开、关闭和读写。

系统中的设备都用一个设备特殊文件代表,叫做设备文件,设备文件又分为Block (块)型设备文件、Character(字符)型设备文件和Socket (网络插件)型设备文件。

Block设备文件常常指定哪些需要以块(如512字节)的方式写入的设备,比如IDE硬盘、SCSI硬盘、光驱等。

而Character型设备文件常指定直接读写,没有缓冲区的设备,比如并口、虚拟控制台等。

Socket(网络插件)型设备文件指定的是网络设备访问的BSD socket 接口。

设备文件都放在/dev目录下,比如硬盘就是用/dev/hd*来表示,/dev/hda表示第一个IDE 接口的主设备,/dev/hda1表示第一个硬盘上的第一个分区;而/dev/hdc 表示第二个IDE接口的主设备。

对于Block和Character型设备,使用主(Major)和辅(minor)设备编号来描述设备。

主设备编号来表示某种驱动程序,同一个设备驱动程序模块所控制的所有设备都有一个共同的主设备编号,而辅设备编号用于区分该控制器下不同的设备,比如,/dev/hda1(block 3/1)、/dev/hda2(block 3/2 )和/dev/hda3( block3/3 )都代表着同一块硬盘的三个分区,他们的主设备号都是3,辅设备号分别为1、2、3。

所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices 文件中得到。

使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。

2.设备驱动程序的接口每种类型的驱动程序,不管是字符还是块设备都为内核提供乡土的调用接口,故内核能以相同的方式处理不同的设备。

LINUX为不同类型的设备驱动程序维护和各自的数据结构,以便定义统一的接口并实现驱动程序的可装载性和动态性。

Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口,这是通过include/linux/fs.h中的数据结构file_operations来完成的: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);};当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。

例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数3.设备驱动程序结构驱动程序的注册与注销在系统初启,或者模块加载时候,必须将设备登记到相应的设备数组,并返回设备的主驱动号,例如:对快设备来说调用refister_blkdec()将设备添加到数组blkdev中.并且获得该设备号. 并利用这些设备号对此数组进行索引。

对于字符驱动设备来说,要用module_register_chrdev()来获得设备的驱动号.对这个设备的调用都用这个设备号来实现;而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev( )或unregister_blkdev( )从内核中注销设备,同时释放占用的主设备号。

设备的打开与释放打开设备是通过调用file_operations结构中的函数open( )来完成的,它是驱动程序用来为今后的操作完成初始化准备工作的。

在大部分驱动程序中,open( )通常需要完成下列工作:(1).检查设备相关错误,如设备尚未准备好等。

(2).如果是第一次打开,则初始化硬件设备。

(3).识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。

(4).分配和填写要放在file->private_data里的数据结构。

(5).使用计数增1。

释放设备是通过调用file_operations结构中的函数release( )来完成的,这个设备方法有时也被称为close( ),它的作用正好与open( )相反,通常要完成下列工作:(1).使用计数减1。

(2).释放在file->private_data中分配的内存。

(3).如果使用计算为0,则关闭设备。

设备的读写操作字符设备的读写操作相对比较简单,直接使用函数read( )和write( )就可以了。

但如果是块设备的话,则需要调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。

由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。

如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn( )来完成的。

设备的控制操作除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl( )来完成。

ioctl( )的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。

设备的中断和轮询处理三实验内容:编写一个简单的字符设备驱动程序。

要求该字符设备包括以下几个基本操作。

打开、读、写、和释放。

还应编写一个测试程序来测试你所编写的字符设备驱动程序。

四实验指导1.进入/linux/drivers/char目录。

2.编写“globalvar.c”程序。

代码如下:#include <linux/module.h> //模块所需的大量符号和函数定义#include <linux/init.h> //指定初始化和清楚函数#include <linux/fs.h> //文件系统相关的函数和头文件#include <linux/cdev.h> //cdev结构的头文件#include <asm/uaccess.h> //在内核和用户空间中移动数据的函数MODULE_LICENSE("GPL"); //指定代码使用的许可证//文件操作函数的声明int globalvar_open(struct inode *, struct file *);int globalvar_release(struct inode *, struct file *);ssize_t globalvar_read(struct file *, char *, size_t, loff_t *);ssize_t globalvar_write(struct file *, const char *, size_t, loff_t *);int dev_major = 350; //指定主设备号int dev_minor = 0; //指定次设备号struct file_operations globalvar_fops= //将文件操作与分配的设备号相连{owner: THIS_MODULE, //指向拥有该模块结构的指针open: globalvar_open,release: globalvar_release,read: globalvar_read,write: globalvar_write,};struct globalvar_dev //用来表示我们定义设备的结构{int global_var; //这个变量代表要操作的设备struct cdev cdev; //内核中表示字符设备的结构};struct globalvar_dev *my_dev; //设备结构的指针static void __exit globalvar_exit(void) //退出模块时的操作{dev_t devno=MKDEV(dev_major, dev_minor); //dev_t是用来表示设备编号的结构cdev_del(&my_dev->cdev); //从系统中移除一个字符设备kfree(my_dev); //释放自定义的设备结构unregister_chrdev_region(devno, 1); //注销已注册的驱动程序printk("globalvar unregister success\n");}static int __init globalvar_init(void) //初始化模块的操作{int ret, err;dev_t devno=MKDEV(dev_major, dev_minor);//动态分配设备号,次设备号已经指定ret=alloc_chrdev_region(&devno, dev_minor, 1, "globalvar");//保存动态分配的主设备号dev_major=MAJOR(devno);//根据期望值分配设备号//ret=register_chrdev_region(devno, 1, "globalvar");if(ret<0){printk("globalvar register failure\n");globalvar_exit(); //如果注册设备号失败就退出系统return ret;}else{printk("globalvar register success\n");}//为设备在内核空间分配空间my_dev=kmalloc(sizeof(struct globalvar_dev), GFP_KERNEL);if(!my_dev){ret=-ENOMEM; //如果分配失败返回错误信息 printk("create device failed\n");}else //如果分配成功就可以完成设备的初始化{my_dev->global_var=0; //设备变量初始化为0 cdev_init(&my_dev->cdev, &globalvar_fops);//初始化设备中的cdev结构my_dev->cdev.owner=THIS_MODULE; //初始化cdev中的所有者字段 //my_dev->cdev.ops=&globalvar_fops;err=cdev_add(&my_dev->cdev, devno, 1);//向内核添加这个cdev结构的信息if(err<0)printk("add device failure\n"); //如果添加失败打印错误消息}return ret;}//打开设备文件系统调用对应的操作int globalvar_open(struct inode *inode, struct file *filp){struct globalvar_dev *dev;//根据inode结构的cdev字段,获得整个设备结构的指针dev=container_of(inode->i_cdev, struct globalvar_dev, cdev);//将file结构中的private_data字段指向已分配的设备结构filp->private_data=dev;return 0;}//关闭设备文件系统调用对应的操作int globalvar_release(struct inode *inode, struct file *filp){return 0;}//读设备文件系统调用对应的操作size_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off){//获取指向已分配数据的指针struct globalvar_dev *dev=filp->private_data;//将设备变量值复制到用户空间if(copy_to_user(buf, &dev->global_var, sizeof(int))){return -EFAULT;}return sizeof(int); //返回读取数据的大小}//写设备文件系统调用对应的操作size_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off){//获取指向已分配数据的指针struct globalvar_dev *dev=filp->private_data;//从用户空间复制数据到内核中的设备变量if(copy_from_user(&dev->global_var, buf, sizeof(int))){return -EFAULT;}return sizeof(int); //返回写数据的大小}module_init(globalvar_init); //模块被装载时调用globalvar_initmodule_exit(globalvar_exit); //模块被卸载时调用globalvar_exit3.更改Makefile文件,跟改后如下:ifneq ($(KERNELRELEASE), )obj-m := globalvar.oelseKERNELDIR ?= /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(PWD) cleanendif4.make. 如果成功的话可以看到globalvar.ko文件。

相关主题