LinuxU盘驱动设计
成 绩
评 阅 人
评阅日期
计算机科学与技术系
《计算机操作系统》
课程设计
设计题目: Linux下 USB驱动设计 班 级: 学 号: 姓 名: 指导老师:
2011年 6 月 16 日
实验目的:
实现在Linux下对硬件设备Kingston U盘的驱动,
实验环境:
Linux 系统:Red Hat Enterprise Linux 5
内核:2.6.18-53.el5(系统内核版本)、2.6.16(编写USB驱动内核版本)
实验原理:
1、设备驱动和文件系统的关系
图1显示了Linux内核的体系结构~ 从图中可以看出应用程序是通过文件子系统来访问底层设备的。一个物理设备~在文件系统中对应一个或多个逻辑结点~ 设备文件的属性由三部分信息组成:文件的类型(c/b)、主设备号、次设备号~其中设备类型和主设备号结合在一起唯一地确定了
而次设备号则说明目标设备是同类设备中设备文件的驱动程序及其界面~
的第几个。
2、Linux驱动程序的基本原理
Linux下开发设备驱动程序的原理较之Windows系统来说结构简单层次
2
清楚。挂在Linux上的每个设备都被描述为设备驱动程序文件~ 一些与设备有关的设备参数文件被保存在/dev目录下。用户自己提供或编写设备驱动时~也需要在/dev目录下有一个设备文件。设备驱动程序可以分为三个主要组成部分:?自动配置和初始化子程序,?服务于I/O请求的子程序,?中断服务子程序。骨架关系如图2:
3、USB骨架驱动程序
(1)USB驱动的注册
Linux USB驱动程序需要做的第一件事情就是在Linux USB子系统里注册~并提供一些相关信息~例如这个驱动程序支持那种设备。注册时会通过初始化函数发送一个命令给usb_register。
当 USB 设备插入时~为了使 linux_hotplug,Linux 中PCI、USB等设备热插拔支持,系统自动装载驱动程序~就需要创建一个
MODULE_DEVICE_TABLE,代码如下:
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID,
USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, skel_table);
USB_DEVICE 宏~利用厂商 ID 和产品 ID 为我们提供了一个设备的唯一标识。 当系统插入一个ID 匹配的USB设备到USB总线时~驱动会在USB core中注册~驱动程序中probe 函数也就会被调用。USB_DEVICE 结构指针、接口号和接口ID都会被传递到函数中。
驱动程序需要确认插入的设备是否可以被接受~ 如果不接受~或者在初始化的过程中发生任何错误~probe 函数返回一个 NULL 值~否则返回
3
一个含有设备驱动程序状态的指针~通过这个指针~就可以访问所有结构
中的回调函数。
当被支持的设备从系统插入或拔出时~会有哪些动作~所有这些信息都传送到USB子系统中。在USB骨架程序中可以这样来完成:
static struct usb_driver skel_driver = {
name: "skeleton",
probe: skel_probe,
disconnect: skel_disconnect,
fops: &skel_fops,
minor: USB_SKEL_MINOR_BASE,
id_table: skel_table,
};
变量 name 是一个字符串~它对驱动程序进行描述,probe和disconnect是函数指针~ 当设备与在id_table中变量信息匹配时~此函数被调用,fops和 minor变量是可选的。大多数USB驱动程序钩住另外一个驱动系统~例如SCSI、网络或 tty 子系统。这些驱动程序在其他驱动系统中注册~同时任何用户空间的交互操作通过那些接口提供~比如把SCSI设备驱动作为USB驱动所钩住的另外一个驱动系统~那么我们对USB设备的read、write等操作~就相应按SCSI设备的read、write函数进行访问。
)USB驱动的注销 (2
USB驱动从系统卸载驱动程序时~ 需要注销USB子系统~即需要
usb_unregister 函数处理:
static void_exit usb_skel_exit(void)
{ /* deregister this driver with the USB subsystem */
usb_deregister(&skel_driver); }
module_exit(usb_skel_exit);
(3)注册 devfs
在骨架驱动程序里~最后一点是要注册 devfs。创建一个缓冲区来保存那些被发送给 USB 设备的数据和那些从设备上接受的数据~同时USB urb被初始化~并且在devfs子系统中注册设备~ 允许devfs 用户访问设备。 注册过程如下:
/* initialize the devfs node for this device and register it */
sprintf(name, "skel%d", skel->minor);
skel->devfs = devfs_register
(usb_devfs_handle, name,
DEVFS_FL_DEFAULT, USB_MAJOR,
4
USB_SKEL_MINOR_BASE + skel->minor,
S_IFCHR | S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP | S_IROTH,
&skel_fops, NULL);
如果devfs_register函数失败~ devfs子系统会将此情况
给用户。当然最后~如果设备从 USB 总线拔掉~设备指针会调用disconnect函数。驱动程序就需要清除那些被分配了的所有私有数据、 关闭urbs~ 并且从devfs上注销自己。
devfs_unregister(skel->devfs);
现在~skeleton驱动就已经和设备绑定上了~任何用户态程序要操作此设备都可以通过 file_operations 结构所定义的函数进行了。首先~要 open 此设备~在 open 函数中MODULE_INC_USE_COUNT 宏是一个关键~它的作用是起到一个计数的作用~有一个用户态程序打开一个设备~计数器就加1~例如~我们以模块方式加入一个驱动~若计数器不为零~就说明仍然有用户程序在使用此驱动~这时候~不能通过rmmod命令卸载驱动模块。
)skel的write、和read函数 (4
它们负责响应驱动程序的读写操作。在skel_write 中~一个FILL_BULK_URB函数~就完成了urb 系统callbak和skel_write_bulk_callback之间的联系。
skel_read函数并没有用urb将数据从设备传送到驱动程序~ 而是用usb_bulk_msg函数代替~ 它可以在没有创建urbs和urb函数的情况下~来发送和接收数据。在此个过程中~调用 usb_bulk_msg 函数并传递一个存储空间~用来缓冲和放置驱动收到的数据~若没有收到数据~则失败并返回一个错误信息。
(5)usb_bulk_msg函数
当对usb设备进行一次读或者写时~ usb_bulk_msg函数是非常有用的,但是~需要连续地对设备进行读/写时~就应该建立一个自己的urbs~同 时将urbs提交给usb子系统。
实验说明:
1、驱动过程
对于一个硬件~Linux是这样来进行驱动的:首先~我们必须提供一
个.o的驱动模块文件,这里我们只说明模块方式~其实内核方式是类似
的,。我们要使用这个驱动程序~首先要加载运行它,insmod *.o,。这
样驱动就会根据自己的类型,字符设备类型或块设备类型~例如鼠标就
5
是字符设备而硬盘就是块设备,向系统注册~注册成功系统会反馈一个主设备号~这个主设备号就是系统对它的唯一标识,例如硬盘块设备在/proc/devices中显示的主设备号为3 ~我们用ls -l /dev/had看到的主设备就肯定是3,。驱动就是根据此主设备号来创建一个一般放置在/dev目录下的设备文件,mknod命令用来创建它~它必须用主设备号这个参数,。在我们要访问此硬件时~就可以对设备文件通过open、read、write等命令进行。而驱动就会接收到相应的read、write操作而根据自己的模块中的相应函数进行了。
2、U盘驱动配置
在Linux下这些设备通常都是以一种叫做usb-storage的方式进行驱动~要使用他们必须加载此模块usb-storage。本次驱动开发也就是在2.6.16内核目录/drivers/usb/storage下修改相应的文件信息~进行驱动的开发(/drivers/usb/storage目录对应为U盘SISC设备驱动源文件),这样就可以产生类似于usb-storage的驱动文件~即为我们需要的目标文件。
在加载安装类似于usb-storage的驱动文的时候~usbcore.o 和usb-uhci.o或usb-ohci也肯定是必须加载安装的(但一般系统会自动进行安装)。另外~若你系统中SCSI支持也是模块方式~那么scsi_mod模块和sd_mod模块也要加载安装。
在加载完这些模块后~我们插入U盘或存储卡~就会发现系统中多了一个SCSI硬盘~通过正确地mount它~就可以使用了。
mount /dev/你的U盘设备文件名 /目标挂载目录
实验过程:
1、查看内核源码
查看系统目录/usr/src/kernels~看在这目录下是否有2.6.x 的内核版本源码~并且确保内核源码的完整性。如果这个目录下没有相应的内核源码~那可以到网上下载等同于系统版本或低于系统版本的源码~如果该目录下有系统源码~那么可以跳过以下对下载来的内核源码的配置及编译。本次的Linux系统安装时在该目录下产生的源码并不完整~因此本次用2.6.16版本的内核做USB驱动开发。
2、配置与编译2.6.16版本内核
进入2.6.16内核版本目录~将系统源码/usr/src/kernels/
2.6.18-53.el5-i686目录下的隐藏文件.config拷贝到此目录~隐藏文件.config为硬件配置与系统设置文件~是安装系统时根据用户的配置
6
信息自动生成的~为了方便本次开发~直接拷贝到此目录。也可以在内
核根目录下执行:make menuconfig,基于文本模式的菜单型配置,进
行相应的内核配置。
在内核根目录下执行:make bzImage~实现对整个内核重新编译~这
个过程可能要持续一个小时左右。执行结束后~可以看到在当前目录下
生成了一个新的文件: vmlinux, 其属性为-rwxr-xr-x。
这样就完成了对2.6.16版本内核的配置与编译~下面在此版本内核
中实现对U盘驱动的开发。
3、获取、配置目标U盘出厂信息
将Kingston U盘设备插到主机上~通过cat /proc/bus/usb/devices得到当前系统探测到的USB总线上的U盘的信息。它包括Vendor、
ProdID、Product等。如图3所示:
图3 U盘出厂信息
得到Vendor=0951 ProdID=1624和Manufacturer=Kingston ,Product= DataTraveler G2。
进入linux-2.6.16内核目录~打开drivers/usb/storage/ unusual_devs.h文件~可以看到所有已知的产品登记表~都是以
UNUSUAL_DEV(idVendor,idProduct,bcdDeviceMin,bcdDeviceMax,vend
or_name,product_name,use_protocol,use_transport,init_function
,Flags)方式登记的。只要填入U盘的信息注册~就可以让驱动去认识
和发现它。为了实现我们的U盘驱动~添加如下代码:
UNUSUAL_DEV( 0x0951, 0x1624, 0x0001, 0xffff,
"Kingston",
"DataTraveler G2",
US_SC_SCSI, US_PR_BULK, NULL,
US_FL_FIX_INQUIRY),
4、U盘驱动模块的注册与注销
打开drivers/usb/storage/ usb.c文件~可以看到该文件完成了对
7
U盘设备的注册、注销等一系列的功能。修改如下代码:
//将U盘驱动的名称改为:myUSBDriver
static struct usb_driver usb_storage_driver = {
.name = "myUSBDriver",
.probe = storage_probe,
.disconnect = storage_disconnect,
#ifdef CONFIG_PM
.suspend = storage_suspend,
.resume = storage_resume,
#endif
.id_table = storage_usb_ids,
};
//修改注册函数~让注册驱动时~在/var/log/message中输出:myUSBDriver Initializing 2011-6-15
static int __init usb_stor_init(void)
{
……
printk(KERN_INFO " myUSBDriver Initializing 2011-6-15
”);
……
return retval;
}
//修改注销函数~注销驱动时输出:myUSBDriver removing 2011-6-15
static void __exit usb_stor_exit(void)
{
……
printk(KERN_INFO " myUSBDriver removing 2011-6-15”);
……
}
5、修改Makefile文件
为了单独编译U盘驱动模块(与内核编译脱离开来~有利于节省编译
时间)~修改drivers/usb/storage目录下的Makefile文件为:
/***********************************************************/
EXTRA_CFLAGS := -Idrivers/scsi
8
ifneq ($(KERNELRELEASE),)
obj-$(CONFIG_USB_STORAGE) += myUSBDriver.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_DEBUG) += debug.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_USBAT) += shuttle_usbat.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_SDDR09) += sddr09.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_SDDR55) += sddr55.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_FREECOM) += freecom.o myUSBDriver-obj-$(CONFIG_USB_STORAGE_DPCM) += dpcm.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_ISD200) += isd200.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_DATAFAB) += datafab.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_JUMPSHOT) += jumpshot.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_ALAUDA) += alauda.o
myUSBDriver-obj-$(CONFIG_USB_STORAGE_ONETOUCH) +=
onetouch.o
myUSBDriver-objs := scsiglue.o protocol.o transport.o usb.o \
initializers.o $(myUSBDriver-obj-y)
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD)
endif
ifneq ($(CONFIG_USB_LIBUSUAL),)
obj-$(CONFIG_USB) += libusual.o
endif
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions /***********************************************************/ 执行该Makefile文件~将会产生myUSBDriver.ko的U盘驱动文件。
9
6、Make产生myUSBDriver.ko驱动文件
在drivers/usb/storage目录下(也可以将storage文件夹拷贝到别
的地方)执行命令:make~如下所示:
最后产生了myUSBDriver.ko驱动文件~如下所示:
7、卸掉原来系统中U盘驱动:usb-storage.ko
在/lib/modules/2.6.18-53.el5/kernel/drivers/usb/storage/目
录下~将已存在的文件usb-storage.ko修改为usb-storage~因为该目
录下的硬件驱动存在依赖关系~即使U盘驱动没有安装~当插入U盘时~
该目录下的.ko驱动文件会自动进行安装~这样修改是为了避免自动安
装U盘驱动~以便调试我们编写的U盘驱动。执行如下:
插入U盘~执行命令:cat /proc/bus/usb/devices~但是依然会发
现我们的U盘乃然使用usb-storage驱动。如下所示
拔出U盘~执行命令:rmmod usb-storage~执行对usb-storage模
块驱动的卸载~执行如下所示~由命令:lsmod可知系统中已经没有了
usb-storage驱动模块。
插入U盘~这时会发现U盘没有使用任何驱动程序~如下所示
10
这样就完成了对原来系统中U盘驱动usb-storage.ko的卸载。
8、安装myUSBDriver.ko U盘驱动模块
在storage目录中执行命令:insmod myUSBDriver.ko,这样就完成
了对我们U盘驱动模块的安装~如下所示:
执行命令:cat /var/log/messge 查看日志文件~如果输出我们的注
册信息:myUSBDriver Initializing 2011-6-15~则表示我们的驱动安
装成功~如下所示:
可知~我们的U盘驱动已成功注册安装:
也可执行命令:lsmod查看已安装的模块驱动~如下所示:
9、实现驱动myUSBDriver绑定到我们的U盘
把我们的U盘插入到主机中~执行命令:cat /proc/bus/usb/devices,
可以查看U盘使用的驱动~如下所示:
可见我们的驱动已经和我们的U盘绑定在一起了。执行命令:cat
/var/log/messge 查看日志文件~如下所示:
11
可见在/dev/下生成设备文件sdb4~现在就可以对这个设备文件进行挂载~并且打开、读写、关闭等的操作了。
执行命令:ls –la /dev/sdb4,如下
可见~逻辑设备节点sdb4为块类型的设备文件~对应于我们的U盘~该设备文件的主设备号为:8~次设备号为:20~我们可以对块设备文件进行直接存取操作。
同样在窗体界面操作时~我们会发现在计算机中存在了我们的U盘SCSI 设备~如下所示:
我们可以在窗体界面下直接对我们的U盘进行访问。 这样我们的U盘驱动程序的编写就实现了。
10、卸载、注销myUSBDriver驱动
执行命令:rmmmod myUSBDriver 对驱动myUSBDriver进行卸载(当U盘在使用该驱动时~无法卸载)~如下所示:
执行命令:cat /var/log/messge 查看日志文件~如下:
可见~在日志文件中输出:myUSBDriver removing 2011-6-15,即为我们修改的注销函数输出信息~这样更进一步表明了我们驱动开发的正确性。
执行命令:lsmod可以查看到已经没有了myUSBDriver驱动:
11、实现自动加载myUSBDriver驱动模块
为了实现当插上U盘时可以自动加载安装myUSBDriver驱动模块,可以把myUSBDriver驱动模块拷贝到/lib/modules/ 2.6.18-53.el5/
kernel/drivers/usb/storage/目录下~然后在该目录下depmod一下
12
(depmod用于分析可载入模块的相依性~depmod可检测模块的相依性~供modprobe在安装模块时使用),modprobe与insmod的区别:modprobe主要加载系统已经通过depmod登记过的模块~insmod一般是针对具体.o文件进行加载,~那么你在插入USB设备的时候~系统就会自动为你加载驱动模块的~当然这个得有hotplug(热插拔的挂载机制)的支持。自动挂载磁盘分区的操作从底层来说~是要内核支持的~2.6 内核的sysfs 虚拟文件系统就提供了这一支持~这个文件系统 (/sys/) 通常用于反应系统硬件信息~总线上的设备变化、网络设备的变化等事件在这里都能反应出来~这个文件系统的变化配合上内核的 hotplug 机制就可以掌握硬件改动相关的信息。如下所示:
确保系统没有安装加载myUSBDriver驱动模块后~插入我们的U盘~这时在计算机中会发现我们的U盘的SCSI 设备~执行命令:cat /proc/bus/usb/devices,如下:
可以发现~系统已经为我们自动加载了myUSBDriver驱动~这样就方便了我们重启电脑后的一系列驱动加载安装的麻烦。同样可以执行cat /var/log/message查看相关的日志信息。
这样就完成了我们需要的自动加载myUSBDriver驱动模块:
实验
:
本次课程设计~已经完全达到了开发U盘驱动的需求~也为开发别的硬件的U盘提供了在Linux系统下开发的参考~更为开发别的USB设备提供了思路。通过本次课程设计~使我们对Linux内核有了更深的了解~进一步为内核的移植打下了基础~同时在这过程中也让我们体会到了Linux系统源代码开放的极大好处与带来的乐趣。
13