U盘枚举(自己总结)

更新时间:2024-03-16 01:37:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

插入U盘

直接拔出

安全拔出

A9枚举

Linux USB gadget设备驱动解析(2)---驱动调试 作者:刘洪涛, 华清远见嵌入式学院金牌讲师。

这一节主要把在实现“linuxU盘功能”过程中的一些调试过程记录下来,并加以解析。

一、背景知识

1、USB Mass Storage类规范概述

USB 组织在universal Serial Bus Mass Storage Class Spaceification 1.1版本中定义了海量存储设备类(Mass Storage Class)的规范,这个类规范包括四个

独立的子类规范,即:

1. USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport 2.USB Mass Storage Class Bulk-Only Transport 3.USB Mass Storage Class ATA Command Block

4.USB Mass Storage Class UFI Command Specification

前 两个子规范定义了数据/命令/状态在USB 上的传输方法。Bulk- Only 传输规范仅仅使用Bulk 端点传送数据/命令/状态,CBI 传输规范则使用

Control/Bulk/Interrupt 三种类型的端点进行数据/命令/状态传送。后两个子规范则定义了存储介质的操作命令。ATA 命令规范用于硬盘,UFI 命令规范是针对USB 移动存储。 Microsoft Windows 中提供对Mass Storage 协议的支持,因此USB 移动设备只需要遵循 Mass Storage 协议来组织数据和处理命令,即可实现与PC 机交换数据。而Flash 的存储单元组织形式采用FAT16 文件系统,这样,就可以直接在Windows的浏览器中通过可移动磁盘来交换数据了,Windows 负责对FAT16 文件系统的管理,USB 设备不需要干预FAT16 文件系统操作的具体细节。

USB(Host)唯一通过描述符了解设备的有关信息,根据这些信息,建立起通信,在这 些描述符中,规定了设备所使用的协议、端点情况等。因此,正确地提供描述符,是USB 设备正常工作的先决条件。

Linux- 2.6.26内核中在利用USB gadget驱动实现模拟U盘时主要涉及到

file_storage.c、s3c2410_udc.c等驱动文件(这些文件的具体结构,将在下一篇文章中 描述)。此时我们想先从这些代码中找到USB描述描述符,从中确定使用的存储类规范,从而确定协议。确定通讯协议是我们调试的基础。 存储类规范是由接口描述符决定的。接口描述符各项的定义义如下:

其中,bInteaceClass、bInterfaceSubClass、bInterfaceProtocol可以判断出设备是否是存储类,以及属于哪种存储子类和存储介质的操作命令。 在file_storage.c文件中,

/* USB protocol value = the transport method */

#define USB_PR_CBI 0x00 // Control/Bulk/Interrupt #define USB_PR_CB 0x01 // Control/Bulk w/o interrupt #define USB_PR_BULK 0x50 // Bulk-only

/* USB subclass value = the protocol encapsulation */

#define USB_SC_RBC 0x01 // Reduced Block Commands (flash) #define USB_SC_8020 0x02 // SFF-8020i, MMC-2, ATAPI (CD-ROM)

#define USB_SC_QIC 0x03 // QIC-157 (tape) #define USB_SC_UFI 0x04 // UFI (floppy)

#define USB_SC_8070 0x05 // SFF-8070i (removable) #define USB_SC_SCSI 0x06 // Transparent SCSI

默认的情况是:

mod_data = { // Default values .transport_parm = \.protocol_parm = \??

默认的赋值如下:

bInterfaceClass=08 表示:存储类

bInterfaceSubClass=0x06 表示:透明的SCSI指令 bInterfaceProtocol=0x50 表示:bulk-only 传输

2、Bulk-Only 传输协议

下面看看Bulk-Only 传输协议:(详细的规范请阅读《Universal Serial BusMass Storage ClassBulk-Only Transport》)

设 备插入到USB 后,USB 即对设备进行搜索,并要求设备提供相应的描述符。在USBHost 得到上述描述符后,即完成了设备的配置,识别出为Bulk-Only 的Mass Storage 设备, 然后即进入Bulk-Only 传输方式。在此方式下,USB 与设备间的所有数据均通过Bulk-In和Bulk-Out 来进行传输,不再通过控制端点传输任何数据。

在 这种传输方式下,有三种类型的数据在USB 和设备之间传送,CBW、CSW 和普通数据。CBW(Command Block Wrapper,即命令块包)是从USB Host 发送到设备的命令, 命令格式遵从接口中的bInterfaceSubClass 所指定的命令块,这里为SCSI 传输命令集。USB设备需要将SCSI 命令从CBW 中提取出来,执行相应的命令,完成以后,向Host 发出反映 当前命令执行状态的CSW(Command Status Wrapper),Host 根据CSW 来决定是否继续发 送下一个CBW 或是数据。Host 要求USB 设备执行的命令可能为发送数据,则此时需要将 特定数据传送出去,完毕后发出CSW,以使Host 进行下一步的操作。USB 设备所执行的操 作可用下图描述:

CBW的格式如下:

dCBWSignature:

CBW的标识,固定值:43425355h (little endian)。 dCBWTag:

主机发送的一个命令块标识,设备需要原样作为dCSWTag(CSW中的一部分)再发送给Host;主要用于关联CSW到对应的CBW。 dCBWDataTransferLength:

本次CBW命令要求在命令与回应之间传输的字节数。如果为0,则不传输数据。 bmCBWFlags:

反映数据传输的方向,0 表示来自Host,1 表示发至Host; bCBWLUN:

对于有多个LUN逻辑单元的设备,用来选择具体目标。如果没有多个LUN,则写

0。

bCBWCBLength:

命令的长度,范围在0~16.

CBWCB:

传输的具体命令,符合bInterfaceSubClass.中定义的命令规范,此处是SCSI CSW命令格式如下:

dCSWSignature:

CSW的标识,固定值:53425355h (little endian) dCSWTag:

设置这个标识和CBW中的dCBWTag一致,参照上面关于dCBWTag的解释 dCSWDataResidue:

还需要传送的数据,此数据根据dCBWDataTransferLength-本次已经传送的数据得到

bCSWStatus:

指示命令的执行状态。如果命令正确执行,bCSWStatus 返回0 即可。 3、SCSI指令集

Bulk-Only 的CBW 中的CBWCB 中的内容即为如下格式的命令块描述符(Command Block Descriptor)。SCSI-2 有三种字长的命令,6 字节、10字节和12字节,Microsoft Windows 环境下支持12 字节长的命令。

Operation Code:

操作代码,表示特定的命令。高3 位为Group Code,共有8 种组合, 即8 个组,低5 五位为Command Code,可以有32 种命令。 Logicol unit Number:

为了兼容SCSI-1 而设的,此处可以不必关心。 Logical block address:

为高位在前,低位在后的逻辑块地址,即扇区地址。第2 位为高位,第3、4、5 依次为低位。

Transfer length:

为需要从逻辑块地址处开始传输的扇区数(比如在Write 命令中)。 Parameter list length:

为需要传输的数据长度(比如在Mode Sense 命令中); Allocation length:

为初始程序为返回数据所分配的最大字节数,此值可以为零,表示不需要传送数据。

SCSI指令集的Direct Accesss 类型存储介质的传输命令有许多, Mass Storage协议只用到了其中的一些。更多的SCSI指令参见:http://en.wikipedia.org/wiki/SCSI_command 指令代码 指令名称 说明

04h Format Unit 格式化存储单元 12h Inquiry 索取器件信息 1Bh Start/Stop load/unload

55h Mode select 允许Host对外部设备设置参数。 5Ah Mode Sense 向host传输参数 Eh Prevent/Allow Medium Removal 写保护

>28h Read(10) Host读存储介质中的二进制数据 A8h Read(12) 同上,不过比较详细一点 25h Read Capacity 要求设备返回当前容量

23h Read Format Capacity 查询当前容量及可用空间 03h Request Sense 请求设备向主机返回执行结果,及状态数据

01h Rexero Unit 返回零轨道

2Bh Seek(10) 为设备分配到特定地址 1Dh Send Diagnostic 执行固件复位并执行诊断

00h Test Unit Ready 请求设备报告是否处于Ready状态 2Fh Verify 在存储中验证数据

2Ah Write(10) 从主机向介质写二进制数据 AAh Write(12) 同上,不过比较详细 2Eh Write and Verify 写二进制数据并验证

对于不同的命令,其命令块描述符略有不同,其要求的返回内容也有所不同,根据相 应的文档,可以对每种请求作出适当的回应。比如,下面是INQUIRY 请求的命令块描述符和其返回内容的数据格式:如:INQUIRY 命令描述符:

返回数据格式

Host 会依次发出INQUIRY、Read Capacity、UFI Mode Sense 请求,如果上述请求的返回结果都正确,则Host 会发出READ 命令,读取文件系统0 簇0 扇区的MBR 数据,进入文件系统识别阶段。

4、利用USB View观察结果

可通过USB View软件查看到USB设置阶段获取到的信息。

二、出现的主要问题

在调试过程中遇到了一个问题。现象是:在目标板加载完驱动后,即执行完: # insmod g_file_storage.ko file=\removable=\

后,接好USB线。此时在windows端设备出有usb storage设备加入,但出现不了盘符。

下面记录下调试过程。

三、调试过程

根据规范,当完成SCSI指令集中Inquiry 命令时,可以出现盘符。所以可以通过bushound软件查看通讯过程,找出原因。

下面是利用bushound工具在出现问题时采集到的数据。

Dev Phase Data Info Time Cmd.Phase. Ofs --- ----- --------------------------------- ---------- ----- -----------

26 CTL 80 06 00 01 - 00 00 12

00 GET DESCRIPTR 0us 1.1.0

26 DI 12 01 10 01 - 00 00 00 10 - 25 05 a5 a4 - 12 03 01 02 ........%....... 4.8ms 1.2.0 03

01 .. 1.2.16 26 CTL 80 06 00 02 - 00 00 09

00 GET DESCRIPTR 14us 2.1.0

26 DI 09 02 20 00 - 01 01 04 c0 -

01 .. ...... 3.9ms 2.2.0

26 CTL 80 06 00 02 - 00 00 20

00 GET DESCRIPTR 16us 3.1.0

26 DI 09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08 06 .. ............. 4.9ms 3.2.0

50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 P.....@......@.. 3.2.16 26 CTL 80 06 00 03 - 00 00 02

00 GET DESCRIPTR 60us 4.1.0

26 DI 09 02 20 00 - 01 01 04 c0 -

01 .. ...... 3.9ms 2.2.0

26 DI 04

03 .. 3.9ms 3.1.0 26 CTL 80 06 00 03 - 00 00 04

00 GET DESCRIPTR 15us 5.1.0 26 DI 04 03 09

04 .... 3.9ms 6.1.0

26 CTL 80 06 03 03 - 09 04 02

00 GET

DESCRIPTR 10us 1.2.16 26 DI 1a

03 .... 4.0ms 6.2.0 26 CTL 80 06 03 03 - 09 04 1a

00 GET DESCRIPTR 18us 7.1.0

26 DI 1a 03 33 00 - 37 00 32 00 - 30 00 34 00 - 31 00 37 00 ..3.7.2.0.4.1.7. 4.9ms 7.2.0 35 00 36 00 - 37 00 37 00 - 35

00 5.6.7.7.5. 7.2.16 26 CTL 00 09 01 00 - 00 00 00

00 SET CONFIG 16us 8.1.0 26 CTL 01 0b 00 00 - 00 00 00

00 SET INTERFACE 60ms 9.1.0 26 CTL a1 fe 00 00 - 00 00 01

00 CLASS 62ms 10.1.0

26 DI 00 . 3.9ms 10.2.0

26 DO 55 53 42 43 - 08 60 e0 86 - 24 00 00 00 - 80 00 06 12 USBC.`..$....... 985us 11.1.0

00 00 00 24 - 00 00 00 00 - 00 00 00 00 - 00 00 00 ...$........... 11.1.16

26 DI 00 80 02 02 - 1f 00 00 00 - 4c 69 6e 75 - 78 20 20 20 ........Linux 1.0ms 12.1.0

46 69 6c 65 - 2d 53 74 6f - 72 20 47 61 - 64 67 65 74 File-Stor Gadget 12.1.16 30 33 31

32 0312 12.1.32

26 CTL 80 06 00 02 - 00 00 20

00 GET DESCRIPTR 893ms 13.1.0

26 DI 09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08 06 .. ............. 4.1ms 13.2.0

50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 P.....@......@.. 13.2.16 26 CTL 80 06 00 02 - 00 00 20

00 GET DESCRIPTR 2.7sc 14.1.0

26 DI 09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08

06 .. ............. 4.4ms 14.2.0

50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 P.....@......@.. 14.2.16 26 USTS 05 00 00

c0 no response 2.8sc 15.1.0

注意上面红色部分的代码,DO发出了55 53 42 43开 始的CBW命令块,命令码是12,即Inquiry命令。要求目标返回Inquiry命令要求的数据,长度是0x24。接下来设备端通过DI返回了设备信 息。按照规范,在返回完了数据后,设备端还应该通过DI向系统返回CSW的值。但实际的捕获内容并没有。所以导致不能正确出现盘符。

在file_storage.c中,发送数据时都会调用到start_transfer() 函数。在此函数中加入printk调试语句,观察现象。发现只要加入的调试语句,windows端就能够正常设别设备了。于是,可以猜测是因为需要在连续 两次发送之间加上一些延时。在函数中加入udelay(800)后,windows系统可以正常发现设备了。具体的代码架构,将在下一遍文章中解析。 下面是程序正常后,用bushound捕获到的数据。

红色部分,可以看出设备正确的按照规范在发送完数据后,返回CSW信息。

四、总结做好USB gadget驱动、或者USB host驱动调试需要: ·掌握一定的知识基础

包括:USB协议、具体的类设备规范、USB驱动程序架构、USB设备端控制器操作等。

·合理利用调试工具。

包括:USB view 、bushound 、及一些硬件USB信号分析仪。

一、追踪USB大容量设备的实现流程

1、从main.c开始

(1)main函数的执行流程

Set_System(); //设置时钟、端口等。

Set_USBClock(); //设置usb的时钟

USB_Interrupts_Config(); //设置中断

Led_Config(); //设置所使用的到的灯。

MSD_Init(); //SD卡初始化

Get_Medium_Characteristics(); //获取SD块总数、每块字节数。

USB_Init(); //USB_init.c提供的初始化函数。从这里开始USB设备被主机检测到。

while (1)

{ //USB的工作都是在中断中完成的,主执行流程什么也没做。

}

(2)与鼠标例程不同的地方

在中断配置中,使能了USB高优先级中断。

NVIC_InitStructure.NVIC_IRQChannel= USB_HP_CAN_TX_IRQChannel;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

用到了几个灯指示,这个我的开发板上用不到,就不详细看了。

MSD_Init(),是对SD卡进行初始化。该函数在msd.c中,我看了一下它的SD卡实现的代码,比我的SD函数代码齐整多了。以后有时间要把我的USB驱动好好的整理一下。不过现在就先不管了。

接下来这个函数获取SD卡的容量,这样的函数我在SD卡驱动中也实现了,改变一下调用方式就行了。

USB_Init()函数在usb_init.c库函数中,但它最终会调用user_prop.c宏的用户初始化例程。下面就追踪进去看一看。

(3)大容量存储设备的初始化

void MASS_init()

{

pInformation->Current_Configuration = 0;

PowerOn(); 连接电缆主机很快发总线复位。

_SetISTR(0);

wInterrupt_Mask = IMR_MSK;

_SetCNTR(wInterrupt_Mask); 开启复位和传输中断。

pInformation->Current_Feature = MASS_ConfigDescriptor[7];

while (pInformation->Current_Configuration == 0)

{

NOP_Process();

}

bDeviceState = CONFIGURED; //这句执行完成后,设备处于已配置状态。我先在这里加一句调试语句。

#if usb_debug

Uart_PutString(“设备已配置”);

#endif

}

2、进入复位中断

(1)先列出中断处理代码

发生总线复位中断以后,处理是在usb_prop.c的Mass_Reset()函数中完成的。

void MASS_Reset()

{

Device_Info.Current_Configuration = 0;

SetBTABLE(BTABLE_ADDRESS);

SetEPType(ENDP0, EP_CONTROL); //端点0控制端点

SetEPTxStatus(ENDP0, EP_TX_NAK); //不响应IN

SetEPRxAddr(ENDP0, ENDP0_RXADDR); //设置接收缓冲区(OUT)

SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); 接收长度。

SetEPTxAddr(ENDP0, ENDP0_TXADDR); //发送缓冲区(IN)

Clear_Status_Out(ENDP0);

SetEPRxValid(ENDP0); //使能端点0的接收。

SetEPType(ENDP1, EP_BULK); //端点1批量模式

SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置发送缓冲区(IN)

SetEPTxStatus(ENDP1, EP_TX_NAK); 发送不响应。

SetEPRxStatus(ENDP1, EP_RX_DIS); //接收无效。对OUT无效

SetEPType(ENDP2, EP_BULK); //端点2批量模式

SetEPRxAddr(ENDP2, ENDP2_RXADDR); //设置接收缓冲区OUT

SetEPRxCount(ENDP2, Device_Property.MaxPacketSize);

SetEPRxStatus(ENDP2, EP_RX_VALID);

SetEPTxStatus(ENDP2, EP_TX_DIS); //发送无效,对IN无效

SetDeviceAddress(0); //使能USB接口模块。

CBW.dSignature = BOT_CBW_SIGNATURE;

Bot_State = BOT_IDLE; //命令状态机初始化为空闲状态

}

在这里没有我没有看到将批量端点设置为双缓冲模式的迹象,难道这个例程没有用它?

-3、进入枚举过程

因为在鼠标例程中已经详细分析过枚举过程,这里主要是大容量设备枚举过程中不同的地方做一下分析。

(1)获取设备描述符、设置地址。

(2)获取配置描述符

(3)获取配置描述符集合,这里主要讲接口描述符分析一下。

0x09, /* bLength: Interface Descriptor size */

0x04, /* bDescriptorType: */

0x00, /* bInterfaceNumber: Number of Interface */

0x00, /* bAlternateSetting: Alternate setting */

0x02, /* bNumEndpoints*/ 使用两个端点

0x08, /* bInterfaceClass: MASS STORAGE Class,大容量存储类 */

0x06, /* bInterfaceSubClass : SCSI transparent,SCSI传输*/

0x50, /* nInterfaceProtocol ,仅批量传输*/

4, /* iInterface: */

(4)获取字符串描述符

(5)类请求实现:

类获取逻辑盘:

一般返回0

类请求复位:

ClearDTOG_TX(ENDP1);

ClearDTOG_RX(ENDP2);

CBW.dSignature = BOT_CBW_SIGNATURE;

Bot_State = BOT_IDLE;

(6)设置配置

在用户设置的回调函数中,又调用

void Mass_Storage_SetConfiguration(void)

{

if (pInformation->Current_Configuration)

{

ClearDTOG_TX(ENDP1);

ClearDTOG_RX(ENDP2);

Bot_State = BOT_IDLE; }

}

这个工作前面已经做过了。

4、主机发命令INQUIRY

(1)首先进入批量输出中断

该中断的回调函数调用Mass_Storage_Out()进行处理。

(2)追踪进入Mass_Storage_Out()

void Mass_Storage_Out (void)

{

u8 CMD;

CMD = CBW.CB[0]; //

Data_Len = GetEPRxCount(ENDP2);

PMAToUserBufferCopy(Bulk_Data_Buff,ENDP2_RXADDR, Data_Len);

switch (Bot_State)

{

case BOT_IDLE:

CBW_Decode(); //第一次收到命令肯定调用这个解码函数。

break; //它的作用应该是填充CBW命令块封包结构

case BOT_DATA_OUT:

if (CMD == SCSI_WRITE10)

{

SCSI_Write10_Cmd();

break;

}

}

(3)追踪进入CBW_Decode()

这个函数的代码较长,我就不列举在这里了,我就分析一下本次的主要工作。

首先将用户缓冲区的数据复制到命令封包结构里面。

然后准备好状态封包结构:

CSW.dTag = CBW.dTag; //这个标志由主机生成,可以用于检查设备是否正确收到该命令。

CSW.dDataResidue = CBW.dDataLength;

然后主要是根据命令操作码,调用相应的SCSI命令处理函数。

switch (CBW.CB[0])

{

case SCSI_REQUEST_SENSE:

SCSI_RequestSense_Cmd ();

break;

case SCSI_INQUIRY:

SCSI_Inquiry_Cmd();

break;

我这里就列出了两项,实际的命令是很多的。本次主要是查询处理。

(4)追踪进入SCSI_Inquiry_Cmd()

以上函数是在usb_bot.c里面,现在跳转到usb_scsi.c里面。

这个函数的主要工作是调用Transfer_Data_Request(Inquiry_Data, Inquiry_Data_Length)来完成。

这个Inquiry_Data = Standard_Inquiry_Data,后面这个Standard_Inquiry_Data是scsi_data.c里面定义的一个数据结构,专门用于inquiry命令的返回。

u8 Standard_Inquiry_Data[] =

{

0x00, /* Direct Access Device */

0x80, /* RMB = 1: Removable Medium */

0x02, /* Version: No conformance claim to standard */

0x02, //这里圈圈的书上说应该为 0x01

36 - 4, //这里圈圈的书上说应该为 31

0x00, 0x00, 0x00, /* SCCS = 1: Storage Controller Component */

'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ',//厂商信息

'S', 'T', 'R', ' ', ' ', 'F', 'l', 'a', 's', 'h', ' ', 'D', 'i', 's', 'k', ' ',//产品信息

'1', '.', '0', ' '

//版本信息。

};

(5)追踪进入Transfer_Data_Request ()

void Transfer_Data_Request(u8* Data_Pointer, u16 Data_Len)

{

UserToPMABufferCopy(Data_Pointer, ENDP1_TXADDR, Data_Len);

SetEPTxCount(ENDP1, Data_Len);

SetEPTxStatus(ENDP1, EP_TX_VALID);

Bot_State = BOT_DATA_IN_LAST;

CSW.dDataResidue -= Data_Len;

CSW.bStatus = CSW_CMD_PASSED; //设置好命令状态封包信息。

}

(6)接下来,主机会发IN,取走查询信息。并进入批量输入中断。

在该中断中,将调用函数Mass_Storage_In (void)

void Mass_Storage_In (void)

{

switch (Bot_State)

{

case BOT_CSW_Send:

case BOT_ERROR:

Bot_State = BOT_IDLE;

SetEPRxStatus(ENDP2, EP_RX_VALID);/* enable the Endpoint to recive the next cmd*/

break;

case BOT_DATA_IN_LAST:

Set_CSW (CSW_CMD_PASSED, SEND_CSW_ENABLE);

SetEPRxStatus(ENDP2, EP_RX_VALID);

break;

default:

break;

}

}

然后设备的命令状态机状态变为Set_CSW()这个函数所设置的状态,一般为BOT_CSW_Send。

(7)追踪进入Set_CSW()

在该函数中:

void Set_CSW (u8 CSW_Status, u8 Send_Permission)

{

CSW.dSignature = BOT_CSW_SIGNATURE;

CSW.bStatus = CSW_Status; //命令状态封包数据已经准备好

UserToPMABufferCopy((&CSW),ENDP1_TXADDR, DATA_LENGTH);

SetEPTxCount(ENDP1, CSW_DATA_LENGTH);

Bot_State = BOT_ERROR;

if (Send_Permission)

{

Bot_State = BOT_CSW_Send;

SetEPTxStatus(ENDP1, EP_TX_VALID);

}

}

然后,主机再次发IN令牌包,取走命令状态封包。

case BOT_ERROR:

Bot_State = BOT_IDLE;

SetEPRxStatus(ENDP2, EP_RX_VALID);

端点2的接收又被使能,重新进入接收命令状态

二、追踪USB大容量设备的实现流程

5、主机发命令READ FORMAT CATPACITIES

再次分析一次命令执行的流程

(1)首先在批量输出端点2产生RX中断

在中断中调用Mass_Storage_Out ()。

在处理过程中先将接收缓冲区的数据、长度保存。

由于此时,命令处理状态机处于BOT_IDLE状态,所以调用命令解码函数CBW_Decode()。

(2)解码函数所做的工作

把接收到的数据先赋值给命令封包结构CBW。同时开始准备填充命令状态封包结构CSW。

switch (CBW.CB[0]),根据命令操作码进行命令处理散转。

(3)操作码0x23,进入相应处理函数SCSI_ReadFormatCapacity_Cmd()

这个命令处理主要是填充用户返回的容量数据结构体,然后调用另外一个函数Transfer_Data_Request()来完成数据的传输。

这个函数接收发送数据缓冲区的起始地址、长度,首先把数据复制到数据输出批量端点1:

UserToPMABufferCopy(Data_Pointer, ENDP1_TXADDR, Data_Len);

SetEPTxCount(ENDP1, Data_Len);

SetEPTxStatus(ENDP1, EP_TX_VALID);

Bot_State = BOT_DATA_IN_LAST; //设置命令处理的新状态

CSW.dDataResidue -= Data_Len;

CSW.bStatus = CSW_CMD_PASSED; //填充命令状态封包。

接下来主机会连发两个“IN”,第一次将容量数据结构体返回。

然后在端点1输入中断中,又将命令状态封包结构复制到批量端点1输出缓冲区。主机的第二个“IN“将取走这个数据。

在第二次输入中断处理程序中,命令状态重新回到“BOT_IDLE”,于是又可以接收新的命令。

6、读容量命令

这个跟上个命令返回的数据差不多,具体什么区别,等到移植调试的时候再看。

7、READ(10)命令

这是一个非常重要的命令,我们获取U盘的文件主要就靠它了。

(1)前面的过程忽略,直接进入读命令解码SCSI_Read10_Cmd()

处理中,主要存在两种情况:

在BOT_IDLE时:

if ((CBW.bmFlags & 0x80) != 0)

{

Bot_State = BOT_DATA_IN;

Read_Memory();

}

在BOT_DATA_IN时:

直接 Read_Memory();

(2)进入Read_Memory()处理函数。

void Read_Memory(void)

{

if (!Block_Read_count)

{ //读入一个扇区512字节,但是一次只能发送64个字节。

MSD_ReadBlock(Data_Buffer, Memory_Offset, 512);

UserToPMABufferCopy(Data_Buffer, ENDP1_TXADDR, BULK_MAX_PACKET_SIZE);

Block_Read_count = 512 - BULK_MAX_PACKET_SIZE;

Block_offset = BULK_MAX_PACKET_SIZE;

}

else

{

UserToPMABufferCopy(Data_Buffer + Block_offset, ENDP1_TXADDR, BULK_MAX_PACKET_SIZE);

Block_Read_count -= BULK_MAX_PACKET_SIZE;

Block_offset += BULK_MAX_PACKET_SIZE;

}

SetEPTxCount(ENDP1, BULK_MAX_PACKET_SIZE);

SetEPTxStatus(ENDP1, EP_TX_VALID);

Memory_Offset += BULK_MAX_PACKET_SIZE;

Transfer_Length -= BULK_MAX_PACKET_SIZE; //剩下的需要传输的字节数。

CSW.dDataResidue -= BULK_MAX_PACKET_SIZE;

Led_RW_ON();

}

这里不清楚的是主机是一次把512字节读完,还是每次发一个命令读取64字节。我觉得应该是发一次读命令,8次“IN”读取一个扇区,再发一个“IN”读取命令状态封包。

8、写命令WRITE(10)

这个命令的处理过程跟读命令的处理差不多,只是最后它会调用Write_Memory()进行处理。

i = 0;

for (; Counter < temp; Counter++)

{

Data_Buffer[Counter] = Bulk_Data_Buff[i];

i++;

}

从接受缓冲区得到的数据总是先放到用户缓冲区,这个缓冲区有512个字节。

if (!(Transfer_Length % 512))

{

Counter = 0;

MSD_WriteBlock(Data_Buffer, Memory_Offset - 512, 512);

}

每次收到512字节的时候,写入SD卡。

9、u盘文件系统的实现

从这个U盘的实现过程来看,设备端只需完成 SD卡扇区的读写工作,所以与文件系统相关的工作都是由主机方完成的。

至于文件系统的详细实现,前面在FatFs的分析过程中已经涉及,这里就不再叙述了。

一、移植前的准备工作

1、有哪些操作必须重新实现?

主要是三个函数:

(1)SD卡读扇区

(2)SD卡写扇区

(3)获取SD卡的块数、每块字节数和总容量

这三个函数我以前实际上都实现了,但是用到这个例程中,还需要改变一些参数的对应问题。

2、为了更清晰的了解U盘的整个工作流程,在关键的地方加一些调试函数,向串口输出信息。

二、开始移植

1、准备源文件

在usb目录下新建udisk目录,在其下建src和inc两个子目录。

将usb_istr.c、usb_pwr.c、usb_desc.c、usb_prop.c、hw_config.c,usb_endp.c、usb_bot.c、usb_scsi.c、scsi_data.c、memory.c、msd.c,main.c等共12个文件复制如src目录。

将相应的头文件复制入inc目录。

在工程里新建文件组,把所有c源文件加入工程。

2、添加命令udisk

当在串口输入udisk命令时,整个开发板将成为一个读卡器。

3、修改各个源文件的包含关系、单独编译

(1)主程序main.c改变成“udisk”命令处理程序。

void UartCmdUsbMouse(u8 argc,void **argv){

Uart_PutString ( \进入U盘实现过程!\\r\\n\

USB_Connect_Init();

USB_Interrupts_Config();

Set_USBClock();

Get_Medium_Characteristics();

USB_Init();

while (1)

}

在这个过程中,去掉了Set_System()函数、led等配置函数等,这些函数都在hw_config.c文件中,该文件编译通过以后,再修改hw_config.c的函数实现。

(2)修改hw_config.c

其它函数的修改在鼠标例程中已经分析过了,这里主要是以下这个函数:

void Get_Medium_Characteristics(void)

{

u8 res;

CardInfo MsdInfo;

res=SD_GetCardInfo(&MsdInfo);

if ( res == 0 ){

Mass_Block_Count = MsdInfo.BlockNumber;

Mass_Block_Size = MsdInfo.BlockLength;

Mass_Memory_Size = MsdInfo.Capacity;

}

}

获取SD卡容量的函数重新修改,也不需要头文件msd.h了。

(3)有9个文件只要修改头文件包含关系就行了

(4)修改文件msd.c

因为我在sduser.c文件中已经实现了大部分的SD操作函数,所以这里实际上读卡、写卡函数调用以前编写的函数就行了。

当然,在入口参数有不一致的地方必须做调整。

u8 MSD_WriteBlock(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)

{

u8 res;

rea = SD_WriteBlock ( WriteAddr>>9, pBuffer);

if ( res == 0 )return res;

return 0xFF;

}

读扇区的修改跟这个类似。

三、下载、测试和修改

1、整个工程编译,生成Hex文件,下载到开发板。

2、输入命令udisk

PC识别了该设备,但是磁盘为空。

这是串口发回来的消息。

Sh>udisk

进入U盘实现过程!

setup中断 80 06 00 01 00 00 40 00 获取设备描述符

设备准备发送12 字节: 12 01 00 02 00 00 00 40 83 04 20 57 00 01 01 02 03 01

IN令牌04 中断

OUT状态中断

setup中断 00 05 02 00 00 00 00 00 设置地址

IN状态中断

setup中断 80 06 00 01 00 00 12 00 获取设备描述符

设备准备发送12 字节: 12 01 00 02 00 00 00 40 83 04 20 57 00 01 01 02 03 01

IN令牌04 中断

OUT状态中断

setup中断 80 06 00 02 00 00 09 00 获取配置描述符

设备准备发送09 字节: 09 02 20 00 01 01 00 80 32

IN令牌04 中断

OUT状态中断

setup中断 80 06 00 03 00 00 FF 00 获取字符串描述符

设备准备发送04 字节: 04 03 09 04

IN令牌04 中断

OUT状态中断

setup中断 80 06 00 02 00 00 FF 00 获取配置描述符

设备准备发送20 字节: 09 02 20 00 01 01 00 80 32 09 04 00 00 02 08 06 50 04 07 05 81 02 40 00 00 07 05 02 02 40 00 00

IN令牌04 中断

OUT状态中断

setup中断 80 06 00 06 00 00 0A 00 这是与高速有关的,这里不支持。

一、USB设备驱动入门

1、学习目的

(1)了解windows系统硬件驱动的一些基本知识。从应用程序给出要求、驱动程序如果处理、底层硬件工作的大致情况有个基本了解。

(2)使用自定义的应用程序、自定义的驱动程序来控制与设备的交互。

2、学习工具

我手边有去年买的一本《windows驱动开发技术详解》,这本书写的挺好的。不过去年我买的时候,看着像天书。到现在,反复看了两遍以后,心里对windows底层的工作原理已经有了那么一点点概念了。

像PCI、USB类型的设备,都是属于WDM驱动模型。特点是即插即用、分层、面向对象、数据驱动(IRP)。

3、WDM驱动特征分析

(1)即插即用

比如对一个USB设备来说,就能够做到即插即用。

USB主机控制器是PCI总线上的一个设备,在windows启动的时候就已经安装好的驱动。系统可以驱动USB HC,其下游端口有设备接入的时候,HCD也有相应的程序进行处理。

比如现在插入一个u盘,在主机的根集线器端口。它向HC报告了这个事件后,有一个叫做即插即用管理器的组件,根据USB总线驱动对象创建一个PDO设备对象。

然后总线驱动获取设备的VID、PID、设备类型等信息,这是通过设备枚举取得的。根据这些信息,windows系统查找相应的功能驱动,比如u盘就是大容量设备类驱动。然后这个驱动再创建一个FDO设备对象。这样这个设备就可以供用户使用了。

(2)分层

分层是现代操作系统的特征,也是我们编写软件时提高可读性、灵活性、可重用性的方法。

分层使得设备对用户提供了统一的操作接口。

比如用户要打卡设备,都是调用 CreateFile()函数、读写用 ReadFile()和WriteFile函数、控制用DeviceIOCtrl()函数。

这些函数都是win32子系统实现的,windows系统将这些函数调用转化为系统调用,进入windows内核。

内核服务函数调用windows的执行组件,一般设备操作是用过IO管理器完成的。IO管理器根据用户传递下来的设备名称,找到相应的驱动对象和设备对象。

IO管理器把用户的要求组合成一个用户输入输出请求包(IRP),然后利用IRP调用相应的驱动程序。这里时间上使用的回调函数的概念,根据具体请求(读、写或其它要求),调用驱动程序的相应函数进行处理。

驱动一般也是多层。上层的驱动程序完成一些工作后,将IRP传递到下一层。比如USB设备的操作,经过功能层次的处理,创建URB请求包附加到IRP中,最后由总线驱动和HCD驱动转换为USB总线上的数据包。

二、USB设备驱动开发

1、开发过程简介

这次只是了解一下windows驱动开发的过程,并没有详细学习windows驱动开发的计划,我连VC都已经不熟悉了。

本次操作是根据《圈圈教你玩USB》第九章的源程序,做一些稍微的修改,使它适合智林开发板的驱动。

主要实现按键信息的读取、led灯的控制两个内容,跟上次实现的自定义HID设备功能一样。只是现在采用自定义设备而不是HID设备类型、使用自定义的文件读写而不是从HID驱动获取的报告描述符中获取数据了。

2、驱动框架的建立

(1)编译vdw_wdm.lib

(2)根据向导建立一个USB WDM驱动程序。

工程名“usbdevice”、“WDM”类型驱动、“功能驱动”、“USB驱动”

“使用端点1的中断输入、中断输出”、“使用缓冲IO”、“VID=8888,PID=1111”。

(3)去掉link选项里的ntstrsafe.lib,整个工程编译成功。

3、驱动程序的修改

主要是在读写函数里修改:

//在这里构建读数据的URB

PURB pUrb = EP1_WRITE.BuildInterruptTransfer(

pBuffer, writeSize, TRUE, NULL,NULL, FALSE );

if (pUrb == NULL){

status = STATUS_INSUFFICIENT_RESOURCES;

}

else {

status = EP1_WRITE.SubmitUrb(pUrb,NULL,NULL,0);

bytesSent = pUrb->UrbInterruptTransfer.TransferBufferLengt

h;

delete pUrb;

}

按照圈圈书里的描述进行修改,实际修改的地方很少。

4、设备固件的修改

将设备描述符里的设备类改为“0xFF”。

将设备的VID、PID改为“8888”和“1111”。

将HID类描述符删除。

将报告描述符删除。

编译、下载,windows弹出安装驱动的界面。安装好以后,在设备管理器可

以看到如下设备。

Mass Storage设备,即大容量存储设备,最典型的莫过于U盘了,而U盘一般以Bulk Only传输方式实现。

四、USB Mass Storage设备的描述符及枚举过程

描述符就是对应标准请求的那些描述符,与HID设备不同,Mass Storage设备没有自己的类描述符。描述符在USB Mass Storage Class Bulk-Only Transport文档中有详细的一对一的描述。所以此处不再赘述,仅举一例:

(设备描述符略,通用定义,与设备类无关) (配置描述符略,通用定义,与设备类无关)

_Interface_Descriptor:

.dw 0x09 //bLength: 0x09 byte

.dw 0x04 //bDescriptorType: INTERFACE .dw 0x00 //bInterfaceNumber: interface 0

.dw 0x00 //bAlternateSetting: alternate setting 0

.dw 0x02 //bNumEndpoints: 3 endpoints(EP0,EP1,EP2) .dw 0x08 //bInterfaceClass: Mass Storage Devices Class .dw 0x06 //bInterfaceSubClass: .dw 0x50 //bInterfaceProtocol

.dw 0x02 //iInterface: index of string _Interface_Descriptor_End:

_Endpoint1:

.dw 0x07 //bLength: 0x07 byte

.dw 0x05 //bDescriptorType: ENDPOINT .dw 0x81 //bEndpointAddress: IN endpoint 1 .dw 0x02 //bmAttributes: Bulk

.dw 0x40, 0x00 //wMaxPacketSize: 64 byte .dw 0x00 //bInterval: ignored

_Endpoint2:

//Endpoint 2 (0x07 byte)

.dw 0x07 //bLength: 0x07 byte

.dw 0x05 //bDescriptorType: ENDPOINT

.dw 0x02 //bEndpointAddress: OUT endpoint 2 .dw 0x02 //bmAttributes: Bulk

.dw 0x40, 0x00 //wMaxPacketSize: 64 byte .dw 0x00 //bInterval: ignored

关于请求:

第一,主机首先会发出一系列标准请求。 第二,在标准请求完成之后,会发出两个类请求:Bulk-Only Mass Storage Reset请求和Get Max LUN请求。这两个请求的格式可以在USB Mass Storage Class Bulk-Only Transport文档中查询。

Bulk-Only Mass Storage Reset没有数据阶段,只在状态阶段告诉主机设备的Reset过程完成与否。如果在状态阶段返回ACK,那么主机就认为设备已经Reset完毕并准备好接收CBW了。 Get Max LUN要求设备返回一个字节的数据给主机,以表明此USB设备有多少个逻辑设备。返回的这个数据就是最大的设备逻辑号(Logic Unit Number),范围是0到15。例如,如果返回2,那么代表有0、1、2三个逻辑设备。

2、USB Mass Storage设备的Bulk数据交换流程

通过bulk端点进行的数据传输,都遵循这样一个过程,即三个阶段: CBW->DATA->CSW

CBW是一个数据块,携带主机发给设备的SCSI命令。接收了CBW后,设备就可以从中知道在接下来的DATA阶段中该干什么。 DATA阶段有三种情况:无数据需要传输,IN传输(设备到主机)或OUT传输(主机到设备)。 CSW阶段反馈这次传输的结果给主机。

其中值得注意的是:

- 在设备枚举完成之后,主机发出的第一个bulk OUT事务就是请求向设备发出CBW。所以设备可以通过这第一次的bulk OUT事务来判定第一次bulk数据传输的开始。此后的bulk数据传输就按照上述的三个阶段反复执行。也就是说,第一次传输CBW后,如果有数据要传输,那么就会经历DATA阶段,然后进入CSW阶段;如果没有数据要传输,则直接进入CSW阶段,就此一次传输结束。接下来,如果又有传输,那么再发出CBW。因此,设备可以认为CSW完成后收到的下一个bulk OUT事务就是主机请求传输新的CBW。

- CBW[12](CBW数据块的第13个字节)指明了传输方向,CBW[8-11]指明了传输的数据长度。实际上,CBW中的SCSI命令就暗含了数据要传输的方向和数据长度,因为SCSI规范中已明确规定这个命令所对应的数据格式。(在完整的应用中,要将CBW中的传输方向、数据长度与SCSI命令所表明的传输方向和数据长度做比较,不对应就要进行错误处理(Mass Storage Bulk-Only文档中有相关描述),不过正常情况下二者是匹配的,试验的时候可以暂时不理)。

- CSW[12](CSW数据块的第13个字节)这个字节很重要,它为0则表示此次传输成功,非0就是不成功。在DATA阶段的数据传完(或者无需数据传输)之后,主机会发出IN事务请求设备返回CSW。如果CSW传送的是不成功的信息,那么主机会接着发送另一个命令来获取失败的详细信息(即RequestSense命令)。

3、Mass Storage设备所使用的SCSI命令集 0x00 TestUnitReady 0x03 RequestSense 0x12 Inquiry

0x1A ModeSense6

0x1B StartStop

0x1E MediumRemoval 0x23 ReadFormatCapacity 0x25 ReadCapacity 0x28 Read(10) 0x2A Write(10) 0x2F Verify

0x5A ModeSense10

其中,

- 主机首先发出Inquiry命令,响应了Inquiry之后就可以看到盘符.

- Inquiry之后会发出ReadFormatCapacity命令,这个命令在SCSI规范中是“厂家自定义命令”,可以参考UFI命令集文档(实际上,U盘所使用的所有SCSI命令集都可以参考UFI文档,它比SCSI标准文档更简洁明了)。注意这个命令在BusHound里是没有描述的,必须在“Device”选项页里勾选上这个U盘所对应的USB Mass Storage Device这个节点,才能看到这个命令的数据流。

- ReadFormatCapacity之后会发出ReadCapacity命令。

- U盘读数据(读扇区)时会发送Read(10)。ReadCapacity完成后就会发送Read(10)读取U盘的第一个扇区。

- U盘写数据时(写扇区)会发送Write(10)。

- TestUnitReady会在无其他数据传输时会定时发送,如果设备没有回应成功的CSW给主机,则主机认为设备已不存在。此时如果再双击磁盘图标,Windows会提示“请插入磁盘”。 - Verify在写数据时有用,表示核实数据,一般直接返回成功的CSW就可以了。一般来说,数据校验的工作在接收和向介质写数据时就已经顺带做了,如果发现错误,则直接告诉主机那次的数据传输有误,不会等到主机Verify时。当然,这不是一个必然的方案。 - RequestSense:如果CSW指示此次传输不成功,那么主机会发出此请求。 - StartStop暂时未发现大用处,一般直接返回成功的CSW。

- MediumRemoval在U盘被Eject的时候有用,处理不正确会Windows会弹出错误信息。 - ModeSense6/10这两个命令可以不支持(不支持不代表不反应,任何一个命令你都要做出反应,对于不支持的命令,可以通过STALL握手来向主机表明),暂时也未遇到过什么异常情况,而且我查看过一些U盘,有相当一部分就是随便回了几个数据给主机。这两个命令只会在U盘插入后发送一次,此后不再发送。

U盘开发的几点体会

如果你开发过USB相关项目,理解USB的一些基本概念,例如设备描述符、配置描述符、子类规范等,那么开发U盘只要概念清晰应该不难。

以下是我开发过程中的几个相关步骤:

? ? ? ? ? ? ?

保证USB 芯片正常工作,用其他USB成功项目验证硬件连接及固件的正确性

按Mass Storage协议 Bulk-Only 模式提供描述符,使PC 机控制面板上设备类型出现Mass Storage Device

响应SCSI指令集中Inquiry 命令,可以出现盘符 实现FAT16文件系统

处理SCSI命令集中READ命令及其他UFI命令,可以访问盘符 处理SCSI命令集中WRITE命令 U盘开发成功

开发U盘有三个工具软件应该必备:USBVIEW 察看设备描述符,端点测试等;BUSHOUND 截取USB总线数据,可分析UFI命令及U盘返回的数据流;串口助手可实时了解U盘所收命令流及程序流程由于每个人知识面不同,我想实现上面的几个步骤遇到的问题也不一样。对我最大的困惑是实现FAT16文件系统,直到在微软网站找到它的白皮书才算解惑。这里简介一下方便大家有的放矢。 USB 组织定义了海量存储设备类(Mass Storage Class)的规范,这个类规范包括四个独立的子类规范,即:

1. USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport 2. USBMass Storage Class Bulk-Only Transport 3. USB Mass Storage Class ATA Command Block

4. USB Mass Storage Class UFI Command Specification。

前两个子规范定义了数据/命令/状态在USB 上的传输方法。Bulk-Only 传输规范仅仅使用Bulk 端点传送数据/命令/状态,CBI 传输规范则使用

Control/Bulk/Interrupt三种类型的端点进行数据/命令/状态传送。后两个子规范则定义了存储介质的操作命令。ATA 命令规范用于硬盘,UFI 命令规范是针对USB 移动存储。

Windows95 OSR2和Windows 98开始支持FAT32 文件系统,它是对早期DOS的FAT16文件系统的增强,由于文件系统的核心--文件分配表FAT由16位扩充为32位,所以称为FAT32文件系统。在一逻辑盘(硬盘的一分区)超过 512 兆字节时使用这种格式,会更高效地存储数据,减少硬盘空间的浪费,一般还会使程序运行加快,使用的计算机系统资源更少,因此是使用大容量硬盘存储文件的极有效的系统。本人对Windows 98下的FAT32 文件系统做了分析实验,总体上与FAT16文件系统变化不大,现将有关变化部分简介如下:

(一)FAT32 文件系统将逻辑盘的空间划分为三部分,依次是引导区(BOOT区)、文件分配表区(FAT区)、数据区(DATA区)。引导区和文件分配表区又合称为系统区。

(二)引导区从第一扇区开始,使用了三个扇区,保存了该逻辑盘每扇区字节数,每簇对应的扇区数等等重要参数和引导记录。之后还留有若干保留扇区。而FAT16文件系统的引导区只占用一个扇区,没有保留扇区。

(三)文件分配表区共保存了两个相同的文件分配表,因为文件所占用的存储空间(簇链)及空闲空间的管理都是通过FAT实现的,FAT如此重要,保存两个以便第一个损坏时,还有第二个可用。文件系统对数据区的存储空间是按簇进行划分和管理的,簇是空间分配和回收的基本单位,即,一个文件总是占用若干个整簇,文件所使用的最后一簇剩余的空间就不再使用,而是浪费掉了。

从统计学上讲,平均每个文件浪费0.5簇的空间,簇越大,存储文件时空间浪费越多,利用率越低。因此,簇的大小决定了该盘数据区的利用率。FAT16系统簇号用16位二进制数表示,从0002H到FFEFH个可用簇号(FFF0H到FFFFH另有定义,用来表示坏簇,文件结束簇等),允许每一逻辑盘的数据区最多不超过FFEDH(65518)个簇。FAT32系统簇号改用32位二进制数表示,大致从00000002H到FFFFFEFFH个可用簇号。FAT表按顺序依次记录了该盘各簇的使用情况,是一种位示图法。每簇的使用情况用32位二进制填写,未被分配的簇相应位置写零;坏簇相应位置填入特定值;已分配的簇相应位置填入非零值,具体为:如果该簇是文件的最后一簇,填入的值为FFFFFF0FH,如果该簇不是文件的最后一簇,填入的值为该文件占用的下一个簇的簇号,这样,正好将文件占用的各簇构成一个簇链,保存在FAT表中。0000000H、00000001H两簇号不使用,其对应的两个DWORD位置(FAT表开头的8个字节)用来存放该盘介质类型编号。FAT表的大小就由该逻辑盘数据区共有多少簇所决定,取整数个扇区。

(四)FAT32系统一簇对应8个逻辑相邻的扇区,理论上,这种用法所能管理的逻辑盘容量上限为16TB(16384GB),容量大于16TB时,可以用一簇对应16个扇区,依此类推。FAT16系统在逻辑盘容量介于128MB到256MB时,一簇对应8个扇区,容量介于256MB到512MB时,一簇对应16个扇区,容量介于512MB到1GB时,一簇对应32个扇区,容量介于1GB到2GB时,一簇对应32个扇区,超出2GB的部分无法使用。显然,对于容量大于512MB的逻辑盘,采用FAT32的簇比采用FAT16的簇小很多,大大减少了空间的浪费。

但是,对于容量小于512MB的盘,采用FAT32虽然一簇8个扇区,比使用FAT16一簇16个扇区,簇有所减小,但FAT32的FAT表较大,占用空间较多,总数据区被减少,两者相抵,实际并不能增加有效存储空间,所以微软建议对小于512M的逻辑盘不使用FAT32。

另外,对于使用FAT16文件系统的用户提一建议,硬盘分区时,不要将分区(逻辑盘)容量正好设为某一区间的下限,例:将一逻辑盘容量设为1100M(稍大于1024M),则使用时其有效存储容量比分区为950M的一般还少,因其簇大一倍,浪费的空间较多。还有,使用FDISK等对分区指定容量时,由于对1MB的定义不一样(标准的二进制的1MB为1048576B,有的系统将1MB理解为1000000B,1000KB等),及每个分区需从新磁道开始等因素,实际分配的容量可能稍大于指定的容量,亦需注意掌握。

(五)根目录区(ROOT区)不再是固定区域、固定大小,可看作是数据区的一部分。因为根目录已改为根目录文件,采用与子目录文件相同的管理方式,一般情况下从第二簇开始使用,大小视需要增加,因此根目录下的文件数目不再受最多512的限制。FAT16文件系统的根目录区(ROOT区)是固定区域、固定大小的,是从FAT区之后紧接着的32个扇区,最多保存512个目录项,作为系统区的一部分。

(六)目录区中的目录项变化较多,一个目录项仍占32字节,可以是文件目录项、子目录项、卷标项(仅跟目录有)、已删除目录项、长文件名目录项等。

目录项中原来在DOS下保留未用的10个字节都有了新的定义,全部32字节的定义如下:

(1) 0-- 7字节 文件正名。 (2) 8--10字节 文件扩展名。

(3) 11字节 文件属性,按二进制位定义,最高两位保留未用,0至5位分别是只读位、隐藏位、系统位、卷标位、子目录位、归档位。

(4) 12--13字节 仅长文件名目录项用,用来存储其对应的短文件名目录项的文件名字节校验和等。 (5) 14--15字节 24位二进制的文件建立时间,其中的高5位为小时,次6位为分钟。

(6) 16--17字节 16位二进制的文件建立日期,其中的高7位为相对于1980年的年份值,次4位为月份,后5位为月内日期。

(7) 18--19字节 16位二进制的文件最新访问日期,定义同(6)。 (8) 20--21字节 起始簇号的高16位。

(9) 22--23字节 16位二进制的文件最新修改时间,其中的高5位为小时,次6位为分钟,后5位的二倍为秒数。

(10)24--25字节 16位二进制的文件最新修改日期,定义同(6)。 (11)26--27字节 起始簇号的低16位。 (12)28--31字节 32位的文件字节长度。

其中第(4)至(8)项为以后陆续定义的。 对于子目录项,其(12)为零;已删除目录项的首字节值为E5H。在可以使用长文件名的FAT32系统中,文件目录项保存该文件的短文件名,长文件名用若干个长文件名目录项保存,长文件名目录项倒序排在文件短目录项前面,全部是采用双字节内码保存的,每一项最多保存十三个字符内码,首字节指明是长文件名的第几项,11字节一般为0FH,12字节指明类型,13字节为校验和,26--27字节为零。 (七)以前版本的 Windows 和DOS与 FAT32 不兼容,不能识别FAT32分区,有些程序也依赖于 FAT16文件系统,不能和 FAT32 驱动器一道工作。将硬盘转换为 FAT32,就不能再用双引导运行以前版本的Windows(Windows 95 [Version 4.00.950]、Windows NT 3.x、Windows NT 4.0 和 Windows 3.x)。

磁盘结构综述 1.硬盘结构

硬盘的内部是由圆形金属片堆叠起来的,每个盘片的两面都有一个磁头(Head)负责读写这个磁面(Side),在每个磁面上划分了一圈一圈的同心圆,叫做柱面(Cylinder)对于软盘通常称之为磁道,在每个柱面中又划分了若干段,称之为扇区(Sector)。由于技术的发展,硬盘的密度越来越大,使得硬盘的实际盘片数越来越少,对磁盘操作的柱面、磁头、扇区被硬盘控制器内部转换,已经不是实际的柱面、磁头、扇区了。由于最早时磁盘存取系统估计不足,传输数据时只给扇区留了6位,柱面留了10位,磁头留了8位。也就是说,柱面最大只能为1023。但是大硬盘的柱面远大于这个数,所以后来就采用将柱面数减少,将磁头数增加的方式来满足磁盘寻址的要求,对于实际地址的转换在BIOS中进行,这叫逻辑块寻址方式(Logical Block Addressing,LBA)硬盘中有关柱面、磁头、扇区的数据都是以此为基准的。

本文来源:https://www.bwwdw.com/article/o8m8.html

Top