驱动框架设计
§ 代码结构分层
- 代码结构分层好处
- 有组织的安排代码编写的文件;
- 分层结构能简化代码开发;
- 代码分层设计:测试层 -> fat层 -> disk层
- 测试层:使用文件读写接口;实现文件的读写测试;
- fat层:使用设备读写接口;实现设备上FAT数据的解析;实现文件读写接口,如file_read等;
- disk层:抽象存储设备结构;包含设备属性及相关操作;
- 实现原理
- 设备特性:块大小、块数量
- 操作接口:打开、关闭、块的读取、块的写入
- 具体实现
- 定义基本数据类型和错误码;
- 定义设备类结构,定义操作接口结构;
- 添加驱动实例;
- 分层目录
xfat_test.c ---- xfat.c ---- xdisk.c ---- driver.c
§ 实现虚拟磁盘驱动
- 具体实现
- 修改xdisk结构;
- 使用C接口实现打开、关闭、读取、写入接口;
- 添加测试代码;
- 驱动实现
- 主要完成:xdisk_hw_open,xdisk_hw_close,xdisk_hw_read_sector,xdisk_hw_write_sector四个函数开发;
1. 驱动程序实现
/* driver.c */
#include <stdio.h>
#include <assert.h>
#include "xdisk.h"
#include "xfat.h"
static xfat_err_t xdisk_hw_open(struct _xdisk_t* disk, void* init_data)
{
assert(disk);
assert(init_data);
const char* path = (const char*)init_data;
FILE* file = fopen(path, "rb+");
if (NULL == file)
{
printf("open disk failed: %s\n", path);
return FS_ERR_IO;
}
disk->data = file;
disk->sector_size = 512;
fseek(file, 0, SEEK_END);
disk->total_sector = ftell(file) / disk->sector_size;
return FS_ERR_OK;
}
static xfat_err_t xdisk_hw_close(struct _xdisk_t* disk)
{
assert(disk);
FILE* file = (FILE*)disk->data;
fclose(file);
return FS_ERR_OK;
}
static xfat_err_t xdisk_hw_read_sector(struct _xdisk_t* disk, u8_t* buffer, u32_t start_sector, u32_t count)
{
assert(disk);
assert(buffer);
u32_t offset = start_sector * disk->sector_size;
FILE* file = (FILE*)disk->data;
int err = fseek(file, offset, SEEK_SET);
if (err == -1)
{
printf("seek disk failed: 0x%x\n", offset);
return FS_ERR_IO;
}
err = fread(buffer, disk->sector_size, count, file);
if (err == -1)
{
printf("read disk failed: sector : %d, count:%d\n", start_sector, count);
return FS_ERR_IO;
}
return FS_ERR_OK;
}
static xfat_err_t xdisk_hw_write_sector(struct _xdisk_t* disk, u8_t* buffer, u32_t start_sector, u32_t count)
{
assert(disk);
assert(buffer);
u32_t offset = start_sector * disk->sector_size;
FILE* file = (FILE*)disk->data;
int err = fseek(file, offset, SEEK_SET);
if (err == -1)
{
printf("seek disk failed: 0x%x\n", offset);
return FS_ERR_IO;
}
err = fwrite(buffer, disk->sector_size, count, file);
if (err == -1)
{
printf("write disk failed: sector : %d, count:%d\n", start_sector, count);
return FS_ERR_IO;
}
fflush(file);
return FS_ERR_OK;
}
xdisk_driver_t vdisk_driver = {
.open = xdisk_hw_open,
.close = xdisk_hw_close,
.read_sector = xdisk_hw_read_sector,
.write_sector = xdisk_hw_write_sector
};
其中C库IO操作函数(<stdio.h>)说明如下(摘自菜鸟教程):
- fopen()
- FILE *fopen(const char *filename, const char *mode)
- C 库函数 FILE *fopen(const char *filename, const char *mode) 使用给定的模式 mode 打开 filename 所指向的文件。q其中mode值如下表所示:
模式 | 描述 |
---|---|
“r” | 打开一个用于读取的文件。该文件必须存在。 |
“w” | 创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。 |
“a” | 追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。 |
“r+” | 打开一个用于更新的文件,可读取也可写入。该文件必须存在。 |
“w+” | 创建一个用于读写的空文件。 |
“a+” | 打开一个用于读取和追加的文件。 |
- fseek()
- int fseek(FILE *stream, long int offset, int whence)
- C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。其中whence参数值如下所示:
常量 | 描述 |
---|---|
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
- ftell()
- long int ftell(FILE *stream)
- C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置。
代码中依次使用:
fseek(file, 0, SEEK_END); disk->total_sector = ftell(file) / disk->sector_size;
首先使用fseek将偏移值移动到文件末尾,然后使用ftell获得文件末尾的偏移值,除以扇区大小后获得该文件有共有多少个扇区;
-
fclose()
- int fclose(FILE *stream)
- C 库函数 int fclose(FILE *stream) 关闭流 stream。刷新所有的缓冲区。
-
fread()
- size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
- C 库函数 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 从给定流 stream 读取数据到 ptr 所指向的数组中;
-
fwrite()
- size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
- C 库函数 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 把 ptr 所指向的数组中的数据写入到给定流 stream 中.
-
fflush()
- int fflush(FILE *stream)
- C 库函数 int fflush(FILE *stream) 刷新流 stream 的输出缓冲区。
驱动写代码中使用该函数是为了确保写入内容已经刷新到文件中;避免保存在程序的缓冲区中;
2. 测试程序实现
/* xfat_test.c */
#include <stdio.h>
#include <string.h>
#include "xfat.h"
#include "xdisk.h"
extern xdisk_driver_t vdisk_driver;
const char* disk_path_test = "disk_test.img";
u32_t read_buffer[160 * 1024];
u32_t write_buffer[160 * 1024];
int disk_io_test(void)
{
int err;
xdisk_t disk_test;
disk_test.driver = &vdisk_driver;
memset(read_buffer, 0, sizeof(read_buffer));
err = disk_test.driver->open(&disk_test, disk_path_test);
if (err)
{
printf("open disk failed!\n");
return -1;
}
err = disk_test.driver->write_sector(&disk_test, (u8_t*)write_buffer, 0, 2);
if (err)
{
printf("write disk failed!\n");
return -1;
}
err = disk_test.driver->read_sector(&disk_test, (u8_t*)read_buffer, 0, 2);
if (err)
{
printf("write disk failed!\n");
return -1;
}
err = memcmp((u8_t*)read_buffer, (u8_t*)write_buffer, disk_test.sector_size * 2);
if (err != 0)
{
printf("data not equal!\n");
return -1;
}
err = disk_test.driver->close(&disk_test);
if (err)
{
printf("disk close failed!\n");
return -1;
}
printf("disk io test ok!\n");
return 0;
}
int main(void)
{
int err;
for (int i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++)
{
write_buffer[i] = i;
}
err = disk_io_test();
if (err)
return err;
printf("test end");
return 0;
}
另外说明一下disk_test.img文件如何创建:
- img文件说明(摘自维基百科)
IMG是一种文件归档格式(archive format),主要是为了建立磁盘的映像文件(disk image),它可以用来封装存储整个磁盘(通常指软磁盘,Floppy Disk或Diskette)或整片光盘的内容,使用".IMG"这个扩展名的文件就是利用这种文件格式来建立的。
.IMG这个文件格式可视为.ISO格式的一种超集合。由于.ISO只能封存使用ISO9660和UDF这两种文件系统的存储介质,意即.ISO只能拿来封存CD或DVD,因此才发展出了.IMG,它是以.ISO格式为基础另外新增可封存使用其它文件系统的存储介质的能力,.IMG可向后兼容于.ISO,如果是拿来封存CD或DVD,则使用.IMG和.ISO这两种格式所产生出来的内容是一样的。
不要将这个文件格式误认为是麦金塔磁盘映像档(Macintosh Disk Image),麦金塔磁盘映像档是由Aladdin Systems(现在称为Allume Systems)ShrinkWrap与Apple Disk Copy for Mac OS这两个工具软件所共享的扩展名。
- 创建
- 该测试代码中使用img文件作为目标磁盘操作,是为了降低成本:不需要额外硬件、又避免弄坏系统;
- 生成img文件可使用软件DiskGenius,操作步骤可参考链接:创建.img 磁盘映像 - DiskGenius
- 需要注意的是代码中使用相对路径存放img文件,需放在工作目录下。获修改工作目录,以VS为例,查询工作目录可通过以下方式:右击项目文件->属性->配置属性->调试->工作目录->点击下拉窗->编辑;即可显示工作目录的绝对路径;
§ 实现磁盘操作接口
-
目标
- 最好是专门提供一组disk层的接口给上层使用,而不是暴露更底层的driver层接口;
- 不方便进行功能拓展,如磁盘读写缓存;
- 上一章节中应用层读写disk使用的是driver层接口,通过在disk层进行隔离,实现解耦的目的;
1. disk层实现
xdisk.h如下;
#ifndef __XDISK_H
#define __XDISK_H
#include "xtypes.h"
struct _xdisk_t;
typedef struct _xdisk_driver_t {
xfat_err_t(*open)(struct _xdisk_t* disk, void* init_data);
xfat_err_t(*close)(struct _xdisk_t* disk);
xfat_err_t(*read_sector)(struct _xdisk_t* disk, u8_t* buffer, u32_t start_sector, u32_t count);
xfat_err_t(*write_sector)(struct _xdisk_t* disk, u8_t* buffer, u32_t start_sector, u32_t count);
}xdisk_driver_t;
typedef struct _xdisk_t {
const char* name;
u32_t sector_size;
u32_t total_sector;
xdisk_driver_t* driver;
void* data;
}xdisk_t;
xfat_err_t xdisk_open(xdisk_t* disk, const char* name, xdisk_t*driver, void* init_data);
xfat_err_t xdisk_close(xdisk_t* disk);
xfat_err_t xdisk_read_sector(xdisk_t* disk, u8_t* buffer, u32_t start_sector, u32_t count);
xfat_err_t xdisk_write_sector(xdisk_t* disk, u8_t* buffer, u32_t start_sector, u32_t count);
#endif // __XDISK_H
xdisk.c如下;
#include "xdisk.h"
xfat_err_t xdisk_open(xdisk_t* disk, const char* name, xdisk_t*driver, void* init_data)
{
xfat_err_t err;
disk->driver = driver;
err = disk->driver->open(disk, init_data);
return err;
}
xfat_err_t xdisk_close(xdisk_t* disk)
{
xfat_err_t err;
err = disk->driver->close(disk);
return err;
}
xfat_err_t xdisk_read_sector(xdisk_t* disk, u8_t* buffer, u32_t start_sector, u32_t count)
{
xfat_err_t err;
if (start_sector + count >= disk->total_sector)
{
return FS_ERR_PARAM;
}
err = disk->driver->read_sector(disk, buffer, start_sector, count);
return err;
}
xfat_err_t xdisk_write_sector(xdisk_t* disk, u8_t* buffer, u32_t start_sector, u32_t count)
{
xfat_err_t err;
if (start_sector + count >= disk->total_sector)
{
return FS_ERR_PARAM;
}
err = disk->driver->write_sector(disk, buffer, start_sector, count);
return err;
}
2. 测试程序修改
xfat_test.c修改如下;
#include <stdio.h>
#include <string.h>
#include "xfat.h"
#include "xdisk.h"
extern xdisk_driver_t vdisk_driver;
const char* disk_path_test = "disk_test.img";
u32_t read_buffer[160 * 1024];
u32_t write_buffer[160 * 1024];
int disk_io_test(void)
{
int err;
xdisk_t disk_test;
memset(read_buffer, 0, sizeof(read_buffer));
err = xdisk_open(&disk_test, "vdisk", &vdisk_driver, disk_path_test);
if (err)
{
printf("open disk failed!\n");
return -1;
}
err = xdisk_write_sector(&disk_test, (u8_t*)write_buffer, 0, 2);
if (err)
{
printf("write disk failed!\n");
return -1;
}
err = xdisk_read_sector(&disk_test, (u8_t*)read_buffer, 0, 2);
if (err)
{
printf("write disk failed!\n");
return -1;
}
err = memcmp((u8_t*)read_buffer, (u8_t*)write_buffer, disk_test.sector_size * 2);
if (err != 0)
{
printf("data not equal!\n");
return -1;
}
err = xdisk_close(&disk_test);
if (err)
{
printf("disk close failed!\n");
return -1;
}
printf("disk io test ok!\n");
return 0;
}
int main(void)
{
int err;
for (int i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++)
{
write_buffer[i] = i;
}
err = disk_io_test();
if (err)
return err;
printf("test end");
return 0;
}