问题
硬盘读写数据时需要注意哪些问题?
硬盘读写注意事项一
硬盘以扇区为基本读写单位
读写数据时需要计算目标扇区的逻辑地址 (LBA)
确定逻辑地址后,读取扇区数据到内存 (512字节)
- 读:从读取的扇区中拷贝目标数据
- 写:数据写入读取的扇区中,之后将扇区数据写回硬盘
硬盘读写注意事项二
硬盘数据端口 (0x1F0) 的读写需要双字节为单位 (WORD)
void WritePortW(ushort port,ushort* buf,uint n)
void ReadPortW(ushort port,ushort* buf,uint n)
汇编小贴士
cld => 清空方向标志位,向高地址方向增加地址值
insw => 端口操作,从端口读取一个字 (双字节)
outsw => 端口操作,从端口写入一个字 (双字节)
rep => 重复指令,重复次数由 ecx 寄存器指定
端口读写
数据结构创建
硬盘数据读写示例
硬盘驱动程序设计
kentry.asm
;
; void ReadPortW(ushort port, ushort* buf, int n)
;
ReadPortW:
push ebp
mov ebp, esp
mov edx, [esp + 8] ; port
mov edi, [esp + 12] ; buf
mov ecx, [esp + 16] ; n
cld
rep insw
nop
nop
nop
leave
ret
;
; void WritePortW(ushort port, ushort* buf, int n)
;
WritePortW:
push ebp
mov ebp, esp
xor eax, eax
mov edx, [esp + 8] ; port
mov esi, [esp + 12] ; buf
mov ecx, [esp + 16] ; n
cld
rep outsw
nop
nop
nop
leave
ret
hdraw.c
typedef struct
{
byte lbaLow;
byte lbaMid;
byte lbaHigh;
byte device;
byte command;
} HDRegValue;
static HDRegValue MakeRegVals(uint si, uint action)
{
HDRegValue ret = {0};
ret.lbaLow = si & 0xFF;
ret.lbaMid = (si >> 8) & 0xFF;
ret.lbaHigh = (si >> 16) & 0xFF;
ret.device = MakeDevRegVal(si);
ret.command = action;
return ret;
}
static void WritePorts(HDRegValue hdrv)
{
WritePort(REG_FEATURES, 0);
WritePort(REG_NSECTOR, 1);
WritePort(REG_LBA_LOW, hdrv.lbaLow);
WritePort(REG_LBA_MID, hdrv.lbaMid);
WritePort(REG_LBA_HIGH, hdrv.lbaHigh);
WritePort(REG_DEVICE, hdrv.device);
WritePort(REG_COMMAND, hdrv.command);
WritePort(REG_DEV_CTRL, 0);
}
uint HdRawSectors()
{
static uint ret = -1;
if((ret == -1) && IsDevReady())
{
HDRegValue hdrv = MakeRegVals(0, ATA_IDENTIFY);
byte* buf = (byte*)Malloc(SECT_SIZE);
WritePorts(hdrv);
if(!IsBusy() && IsDataReady() && buf)
{
ushort* data = (ushort*)buf;
ReadPortW(REG_DATA, data, SECT_SIZE >> 1);
ret = ((data[61] << 16) | data[60]);
}
Free(buf);
}
return ret;
}
uint HdRawWrite(uint si, byte* buf)
{
uint ret = 0;
if((si < HdRawSectors()) && buf && !IsBusy())
{
HDRegValue hdrv = MakeRegVals(si, ATA_WRITE);
WritePorts(hdrv);
if(ret = (!IsBusy() && IsDataReady()))
{
WritePortW(REG_DATA, (ushort*)buf, SECT_SIZE >> 1);
}
}
return ret;
}
uint HdRawRead(uint si, byte* buf)
{
uint ret = 0;
if((si < HdRawSectors()) && buf && !IsBusy())
{
HDRegValue hdrv = MakeRegVals(si, ATA_READ);
WritePorts(hdrv);
if(ret = (!IsBusy() && IsDataReady()))
{
ReadPortW(REG_DATA, (ushort*)buf, SECT_SIZE >> 1);
}
}
return ret;
}
kmain.c
void KMain()
{
void (*AppModInit)() = (void*)BaseOfApp;
byte* pn = (byte*)0x475;
PrintString("L.J.OS\n");
PrintString("GDT Entry: ");
PrintIntHex((uint)gGdtInfo.entry);
PrintChar('\n');
PrintString("GDT Size: ");
PrintIntDec((uint)gGdtInfo.size);
PrintChar('\n');
PrintString("IDT Entry: ");
PrintIntHex((uint)gIdtInfo.entry);
PrintChar('\n');
PrintString("IDT Size: ");
PrintIntDec((uint)gIdtInfo.size);
PrintChar('\n');
PrintString("Num Of Hard Disk: ");
PrintIntDec(*pn);
PrintChar('\n');
MemModInit((void*)KernelHeapBase, HeapSize);
KeyBoardModInit();
MutexModInit();
uint n = HdRawSectors();
PrintString("Num Of Sectors: ");
PrintIntDec(n);
PrintChar('\n');
pn = (byte*)Malloc(512);
if(pn)
{
pn[1] = 0xAB;
pn[127] = 0xCD;
pn[511] = 0xEF;
HdRawWrite(2, pn);
pn[1] = 0;
pn[127] = 0;
pn[511] = 0;
HdRawRead(2, pn);
PrintString("pn[1] = ");
PrintIntHex(pn[1]);
PrintChar('\n');
PrintString("pn[127] = ");
PrintIntHex(pn[127]);
PrintChar('\n');
PrintString("pn[511] = ");
PrintIntHex(pn[511]);
PrintChar('\n');
Free(pn);
}
// AppModInit();
TaskModInit();
IntModInit();
ConfigPageTable();
// LaunchTask();
}
读写扇区都是以1个扇区为基本单位的,我们建立了512字节大小的内存,并向这片内存的3个部分写了数据,将这片内存写入第2个扇区,然后又将这片扇区读取到内存中,我们打印之前我们修改这片内存的3个部分。
读取到的数据和我们之前写入的数据是一样的。
我们的目标是建立一个文件系统,我们选择在应用层上利用 Qt 进行开发,因为 Qt 拥有良好的调试环境。
ljfser
utility.c
void* MemCpy(byte* dst, const byte* src, uint n)
{
int i = 0;
uint dAddr = (uint)dst;
uint sAddr = (uint)src;
if(dAddr < sAddr)
{
for(i = 0; i < n; i++)
{
dst[i] = src[i];
}
}
if(dAddr > sAddr)
{
for(i = n - 1; i >= 0; i--)
{
dst[i] = src[i];
}
}
return dst;
}
void MemSet(byte* dst, uint n, int num)
{
int i = 0;
for(i = 0; i < n; i++)
{
dst[i] = num;
}
}
char* StrCpy(char* dst, const char* src, uint n)
{
int i = 0;
uint dAddr = (uint)dst;
uint sAddr = (uint)src;
if((dst != NULL) && (src != NULL))
{
if(dAddr < sAddr)
{
for(; (src[i] != '\0') && (i < n); i++)
{
dst[i] = src[i];
}
dst[i] = '\0';
}
if(dAddr > sAddr)
{
int length = StrLen(src);
n = (length > n) ? n : length;
dst[n] = 0;
for(i = n - 1; i >= 0; i--)
{
dst[i] = src[i];
}
}
}
return dst;
}
hdraw.c
#include "hdraw.h"
#include "utility.h"
#include <stdio.h>
#include <malloc.h>
static byte* gHDBuf = NULL;
static uint gSectors = 0;
static char gHDName[64] = {0};
void HDRawSetName(const char* name)
{
if(name)
{
StrCpy(gHDName, name, -1);
}
}
void HDRawFlush()
{
FILE* fp = fopen(gHDName, "w");
if(fp)
{
fwrite(gHDBuf, 1, HdRawSectors() * SECT_SIZE, fp);
fclose(fp);
}
}
void HdRawModInit()
{
FILE* fp = fopen(gHDName, "r");
if(fp)
{
long length = 0;
fseek(fp, 0L, SEEK_END);
length = ftell(fp);
gHDBuf = (byte*)malloc(length);
fseek(fp, 0, SEEK_SET);
fread(gHDBuf, 1, length, fp);
gSectors = length / SECT_SIZE;
fclose(fp);
}
}
uint HdRawSectors()
{
return gSectors;
}
uint HdRawWrite(uint si, byte* buf)
{
uint ret = 0;
if(ret = ((si < HdRawSectors()) && buf))
{
byte* p = (byte*)AddrOff(gHDBuf, si * SECT_SIZE);
MemCpy(p, buf, SECT_SIZE);
}
return ret;
}
uint HdRawRead(uint si, byte* buf)
{
uint ret = 0;
if(ret = ((si < HdRawSectors()) && buf))
{
byte* p = (byte*)AddrOff(gHDBuf, si * SECT_SIZE);
MemCpy(buf, p, SECT_SIZE);
}
return ret;
}
main.c
#include <stdio.h>
#include "hdraw.h"
int main(void)
{
byte buf[512] = {0};
HDRawSetName("hd.img");
HdRawModInit();
HdRawRead(2, buf);
HDRawFlush();
printf("%x, %x, %x\n", buf[1], buf[127], buf[511]);
return 0;
}
内存拷贝时要注意内存覆盖的问题。
硬盘的本质是一片连续的空间,我们可以将这片空间读取到内存中,在内存中修改数据,并写入硬盘中,就可以完成硬盘的读写。
我们在 main 函数中读取第二扇区之前被我们修改过的三个字节的数据,并打印,运行结果如下。
我们成功读取到了硬盘上的数据。