文件系统和USB MSC

时间:2024-03-26 11:31:51

编辑博客时,回车的意思是切换段落,shift+回车才是换行。

1 File System
1.1 Windows分区知识
因为保存主分区信息的MBR(Master Boot Record)只能容纳4个分区信息,也就是说只能有4个主分区。如果你想要更多的分区,只能将其中的一个主分区再划分,再划分出来的分区叫做逻辑分区,被划分的主分区又叫扩展分区,逻辑分区信息是保存在EBR(Extended Boot Record)里的。Linux系统中规定了主分区号为sda1-sda4或者hda1-hda4,而逻辑分区只能从sda5开始。

Q:U盘被识别为/dev/sda4,而不是sda1?
A:因为U盘的这个分区写在分区表(MBR DPT,Disk Partition Table)的第四项所以是sda4(DPT每个entry的第一个字节为引导标志,0x80表示活动分区,0x00表示非活动分区),可以用命令重新写到第一项。

sudo sfdisk -d /dev/sda > sda_table
gedit sda_table
sudo sfdisk /dev/sda < sda_table
或者
dd if=/dev/sda of=./mbr.bin bs=512 count=1

1.2 MBR和DBR
1.2.1 MBR - Main Boot Record
MBR实际上也是一个bootloader,可以使用如下命令获得MBR的内容
dd if=/dev/sda of=./mbr.bin bs=512 count=1

MBR:扇区内偏移地址0 ~ 0x1BD
DPT:DOS Partition Table,扇区内偏移地址0x1BE~ 0x1FD,其中又分为4个分区表:

第一个分区表:0x1BE ~ 0x1CD
第二个分区表:0x1CE ~ 0x1DD
第三个分区表:0x1DE ~ 0x1ED
第四个分区表:0x1EE ~ 0x1FD

DPT阐述
字节偏移  说明
0       引导标志。若值为80H表示活动分区,若值为00H表示非活动分区。
1-3   本分区的起始磁头号、扇区号、柱面号。其中:磁头号--第1字节;扇区号--第2字节的低6位;柱面号—为第2字节高2位+第3字节8位
4       分区类型符:
00H——表示该分区未用(即没有指定);
01h——FAT12基本分区
04H——FAT16基本分区
06H——big FAT16基本分区;
0BH——FAT32基本分区;
05H——扩展分区;
07H——NTFS分区;
0FH——(LBA模式)扩展分区(83H为Linux分区等)
5-7   本分区的结束磁头号、扇区号、柱面号。其中:
磁头号——第1字节;
扇区号——第2字节的低6位;
柱面号——第2字节的高2位+第3字节
8-11          分区起始扇区数,指分区相对于记录该分区的分区表的扇区位置之差 (该分区表:LBA = 0x0)
12-15        本分区的总扇区数

1.2.2 DBR - DOS Boot Record
偏移量 字节数 含义
00--02H 3  跳转到引导代码
03--0AH 8  厂商标识和DOS版本
0B--0CH 2  BPB参数信息,每个扇区的字节数
0DH 1  每个分配簇的扇区数(2的整数倍)
0E--0FH 2  保留扇区数
10H 1  FAT个数
11--12H 2  根目录登记项数(所允许的最大数值)
13--14H 2  磁盘扇区总数
15H 1  磁介质类型说明
16--17H 2  每个FAT表所占的扇区数
18--19H 2  每个磁道(柱面)的扇区数
1A--1BH 2  磁头的个数
1C--1FH 4  当前DOS分区前面的隐含扇区数
27--2AH 4  FAT16格式磁盘系列号
2B--35H 10  FAT16卷标名
36--3AH 5  FAT16磁盘格式标志

2 UFS
2.1 UFS固件
UFS固件分成如下的三部分:
- 前端,host接口协议
- 中端,FTL(Flash Transport Layer)算法
- 后端,NFC(Nand Flash Controller)驱动

3 USB Mass Storage
3.1 为何已经有了CBI,又再弄出个BBB
USB 2.0 MSC传输协议分CBI(Control/Bulk/Interrupt)和BOT(Bulk-only Transport),而BOT又称为BBB。

对于USB MSC设备来说,USB设备和USB主机之间的通信,既然已经定义了一个CBI规范,那么为何还要再新定义一个BBB呢?

因为最开始USB协议定义的时候,那时候市场上的Floppy Disk还是用的很多的。所以针对Floppy设备特点,分别定义了多个端点来传输不同的信息,即Control端点传命令块,Bulk传数据,Interrupt传状态信息。而后来计算机行业发展了,Floppy类的设备很少用了,存储数据多数开始用Flash Memory了,再加上通过合理规划,可以用同一种端点(Bulk),传输上述三种信息,即命令块,数据,状态。因此,只需要物理上实现2个Bulk端点,节省掉了其他两个端点(Control端点和Interrupt端点),而达到同样的信息传输的目的。

CBI:主要是Floppy设备,对应用的是UFI Command Set;
BOT:就是我们常见的Flash Memory,对应的是用SCSI Command Set。

3.2 SCSI-2
3.2.1 Inquiry
标准Inquiry返回数据
文件系统和USB MSC
Peripheral Device Type;标识当前连接逻辑单元的类型,0x00为直接存储设备,0x05为光盘
RMB(Removable Media Bit):置1表示可移除设备
Response Data Format:UFI设备置1,SCSI-2设备置2
Additional Length:参数长度,此值为31
Vendor Information:8个字节长度的ASCII字符串,不包括'\0'
Product Identification:16个字节长度的ASCII字符串,不包括'\0'
Product Revision Level:4 hexadecimal digits,不包括'\0'

3.2.2 Read Capacity
返回的是最大扇区数目减1,即是num_sectors - 1。

3.2.3 Request Sense
文件系统和USB MSC
Valid:表示Information域是否有效
Information:4个字节的sense_data_info,表示出错的逻辑块地址
Additional Sense Length:值是10
SK、ASC、ASCQ:组成一个24bit的sense_data,表示错误代码
Linux中实施:
#define SS_NO_SENSE             0
#define SS_COMMUNICATION_FAILURE        0x040800
#define SS_INVALID_COMMAND          0x052000
#define SS_INVALID_FIELD_IN_CDB         0x052400
#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE   0x052100
#define SS_LOGICAL_UNIT_NOT_SUPPORTED       0x052500
#define SS_MEDIUM_NOT_PRESENT           0x023a00
#define SS_MEDIUM_REMOVAL_PREVENTED     0x055302
#define SS_NOT_READY_TO_READY_TRANSITION    0x062800
#define SS_RESET_OCCURRED           0x062900
#define SS_SAVING_PARAMETERS_NOT_SUPPORTED  0x053900
#define SS_UNRECOVERED_READ_ERROR       0x031100
#define SS_WRITE_ERROR              0x030c02
#define SS_WRITE_PROTECTED          0x072700

#define SK(x)       ((u8) ((x) >> 16))
#define ASC(x)      ((u8) ((x) >> 8))
#define ASCQ(x)     ((u8) (x))

3.3 Linux Mass Storage Flow
do_scsi_command() - 假如读写时由N个buffer,那么这个函数传输的buffer范围是从[1, N - 1],最后一个buffer由finish_reply()完成传输
finish_reply()            - 传输最后一个buffer
send_status()           - 完成CSW状态

3.4 SCSI Host注册使用到的函数
scsi_host_alloc()
scsi_add_host()
scsi_scan_host() - 执行INQUIRY命令,每扫描到一个lun(函数scsi_probe_and_add_lun())就创建一个struct scsi_device(譬如U盘一般只有一个lun,而读卡器和UFS可能有多个lun;每个struct scsi_device创建一个disk,可能有多个分区),与sd_probe()匹配。

3.5 sync执行流程
3.5.1 sys_sync
sys_sync()
wakeup_bdflush(0)
sync_inodes(0)
sync_supers()
sync_filesystems(0)
sync_filesystems(1)
sync_inodes(1)

3.5.2 命令行执行sync流程
SYSCALL_DEFINE0(sync)
->
wakeup_flusher_threads()

3.6 TUR执行流程
3.6.1 从文件系统到SCSI块设备
第一步:
static struct file_system_type xxx_fs_type = {
    [...]
    .mount = xxx_mount,
    [...]
};

第二步:
@ fs/block_dev.c
const struct file_operations def_blk_fops = {
    .open       = blkdev_open,
    [...]
};
->
blkdev_get()
->
__blkdev_get()

第三步:
@ drivers/scsi/sd.c
static const struct block_device_operations sd_fops = {
    [...]
    .open           = sd_open,
    [...]
    .revalidate_disk    = sd_revalidate_disk,
    [...]
};

第四步:
sd_open()
->
sd_revalidate_disk()
->
sd_spinup_disk()

3.6.2 SCSI spinup流程
@ drivers/scsi/sd.c - sd:scsi disk
sd_probe()
->
sd_probe_async()
->
sd_revalidate_disk()
->
sd_spinup_disk()

3.6.3 SG_IO例子
用户也可以使用该方法实现对SCSI磁盘分区的轮训
- 返回值为0时,表示磁盘分区存在,不做操作
- 返回值小于0时,表示磁盘分区不存在,在vold中umount该分区,umount时,内核会重新扫描分区

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <scsi/scsi_ioctl.h>

#define TUR_CMD 0x0
#define INQ_CMD 0x12
#define CMDLEN 6
#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */

#define BLOCK_LEN 32
#define SCSI_TIMEOUT 20000

static void show_vendor(struct sg_io_hdr *hdr)
{
    unsigned char *buffer = hdr->dxferp;
    int i;

    printf("the device type: %d \n", buffer[0] & (1<<5 -1));
    printf("vendor id: ");
    for (i = 8; i < 16; ++i) {
        putchar(buffer[i]);
    }
    putchar('\n');
}

static void show_product(struct sg_io_hdr *hdr)
{
    unsigned char *buffer = hdr->dxferp;
    int i;

    printf("product id: ");
    for (i = 16; i < 32; ++i) {
        putchar(buffer[i]);
    }
    putchar('\n');
}

static void show_product_rev(struct sg_io_hdr *hdr)
{
    unsigned char *buffer = hdr->dxferp;
    int i;

    printf("product ver: ");
    for (i = 32; i < 36; ++i) {
        putchar(buffer[i]);
    }
    putchar('\n');
}

static void show_sense_data(struct sg_io_hdr *hdr)
{
    unsigned char *buffer = hdr->sbp;
    int i;

    printf("ss: ");
    for (i = 0; i < hdr->mx_sb_len; ++i) {
        printf("%02x ", buffer[i]);
    }
    putchar('\n');
}

static int8_t do_sg_io(const char *dev, unsigned char test_tur)
{
    unsigned char inq_cdb[CMDLEN] = {INQ_CMD, 0, 0, 0, 0xff, 0};
    unsigned char tur_cdb[CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0};
    unsigned char data_buffer[BLOCK_LEN * 256];
    unsigned char sense_b[SENSE_BUFF_LEN];
    struct sg_io_hdr hdr;
    int fd, ret;

    hdr.interface_id = 'S'; /* this is the only choice we have! */
    //hdr.flags = SG_FLAG_LUN_INHIBIT; /* this would put the LUN to 2nd byte of cdb*/
    hdr.timeout = SCSI_TIMEOUT;

    if (!test_tur) {
        hdr.dxferp = data_buffer;
        hdr.dxfer_len = BLOCK_LEN * 256;
        hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    } else {
        hdr.dxfer_direction = SG_DXFER_NONE;
    }

    hdr.sbp = sense_b;
    hdr.mx_sb_len = SENSE_BUFF_LEN;

    hdr.cmdp = test_tur ? tur_cdb : inq_cdb;
    hdr.cmd_len = CMDLEN;

    fd = open(dev, O_RDWR);
    if (fd > 0) {
        ret = ioctl(fd, SG_IO, &hdr);
        if (ret < 0) {
            printf("failed to do SG_IO\n");
            close(fd);
            return -1;
        }
        if (!test_tur) {
            show_vendor(&hdr);
            show_product(&hdr);
            show_product_rev(&hdr);
        }
        show_sense_data(&hdr);
        close(fd);
        return hdr.status;
    }
    return -1;
}

int main(int argc, char **argv)
{
    unsigned char test_tur = 0;

    if (argc < 2) {
        printf("my_sg_io <DEV_NODE> [CMD]\n");
        printf("for example:\n");
        printf("my_sg_io /dev/sda1 inq\n");
        printf("my_sg_io /dev/sda1 tur\n");
        exit(1);
    }

    if ((3 == argc) && !strcmp(argv[2], "tur")) {
        test_tur = 1;
    }
    printf("ioctl ret = %d\n", do_sg_io(argv[1], test_tur));
    return 0;
}

4 URLs
MBR与分区表
http://blog.chinaunix.net/uid-20649697-id-1592536.html

自己整理的MBR数据结构
https://blog.csdn.net/Firas/article/details/8665366

https://blog.csdn.net/melvda/article/details/38370437
USB Mass Storage协议

Linux块设备IO子系统(二) _页高速缓存
https://www.linuxidc.com/Linux/2017-03/142205.htm

5 Abbreviations
MBR:Main Boot Record
DBR:DOS Boot Record
EBR:Extended Boot Record
bdi:backing device info - 脏页写回核心结构;文件系统page cache,使用struct address_space_operations(每个文件系统都有这个结构体,譬如 fat_aops)
IDA & IDR:Identification,IDR机制在Linux内核中指的是整数ID管理机制。实质上来讲,这就是一种将一个整数ID号和一个指针关联在一起的机制。IDA是用IDR来实现的ID分配机制,与IDR的区别是IDA仅仅分配与管理ID,并不将ID与指针相关联。
Linux dd命令参数:if表示input file,of表示output file,bs表示block size
NTFS:n ei f ts
RCU:Read-Copy Update,RCU支持一个更新者和多个读者同时访问。通过维护对象的多个版本,RCU保证读者看到的对象是前后一致的,并且保证在所有之前已存在的读者离开临界区时,这些版本才会被释放。
TUR:Test Unit Ready(类似心跳信号),sd_spinup_disk()执行该命令,底层排队函数usb_stor_host_template.queuecommand()和ufshcd_queuecommand()。Linux工具sg_turs(SCSI Generic,隶属于sg3_utils)用来手工发送TUR命令。
UASP:USB Attached SCSI Protocol
W-LUNs:UFS Well Known Logical Units