Устройство жесткого диска

На нем по физическому адресу 0-0-1 располагается главная загрузочная запись (MBR). В структуре MBR находятся следующие элементы:

    * Внесистемный загрузчик (NSB);
    * Таблица описания разделов диска (таблица разделов, PT).
      Располагается в MBR по смещению 0x1BE и занимает 64 байта; Таблица разделов описывает размещение и характеристики имеющихся на винчестере разделов. Разделы диска могут быть двух типов - primary (первичный, основной) и extended (расширенный). Максимальное число primary-разделов равно четырем. Наличие на диске хотя бы одного primary-раздела является обязательным. Extended-раздел может быть разделен на большое количество подразделов - логических дисков.
    * Сигнатура MBR. Последние два байта MBR должны содержать число 0xAA55.
      Т.е. структура MBR выглядит так: программа анализа таблицы разделов и загрузки System Bootstrap с активного раздела (смещение 0, размер 446) --> Partition 1/2/3/4 entry ( элемент таблицы разделов) (Смещение для первого 0x1BE, размер каждого 16), Сигнатура 0xAA55 (Смещение - 0x1FE, размер 2)

Partition entry выглядит так:

- признак активности (0 - раздел не активный, 0x80 - раздел активный). Он служит для определения, является ли раздел системным загрузочным и есть ли необходимость производить загрузку операционной системы с него при старте компьютера. Активным может быть только один раздел. Элемент первичного раздела указывает сразу на загрузочный сектор логического диска (в первичном разделе всегда имеется только один логический диск), а элемент расширенного раздела - на список логических дисков, составленный из структур, которые именуются вторичными MBR (SMBR). SMBR имеет структуру, аналогичную MBR, но загрузочная запись у него отсутствует (заполнена нулями), а из четырех полей описателей разделов используются только два. Первый элемент раздела при этом указывает на логический диск, второй элемент указывает на следующую структуру SMBR в списке. Последний SMBR списка содержит во втором элементе нулевой код раздела. (1 байт)
- Номер головки диска, с которой начинается раздел. (1 байт)
- Номер цилиндра и номер сектора, с которых начинается раздел. Биты 0-5 содержат номер сектора, биты 6-7 - старшие два бита 10-разрядного номера цилиндра, биты 8-15 - младшие восемь битов номера цилиндра. (2 байта)
- Код типа раздела System ID. Указываюет на принадлежность данного раздела к той или иной операционной системе. (1 байт)
- Номер головки диска, на которой заканчивается раздел (1 байт)
- Номер цилиндра и номер сектора, которыми заканчивается раздел Три байта на каждый номер. (2 байта)
- Абсолютный (логический) номер начального сектора раздела, т.е. число секторов перед разделом (4 байта)
- Размер раздела (число секторов). Размер раздела в секторах (4 байта)

Теперь можно подобраться чуть ближе к практике. Рассмотрим модуль для Linux, показывающий информацию обо всех раздела жесткого диска.

#define signt 0xAA55
#define DEV "/dev/hda"
#define pt_s 0x10 // размер элемента таблицы разделов
struct stype { u8 part_type; u8 *part_name; }; // Соответствие кода раздела с его символьным отображением
struct stype 386_stype[] = {
{0x00, "Empty"},
{0x01, "FAT12"},
{0x04, "FAT16 <32M"},
{0x05, "Extended"}, /* DOS 3.3 */
{0x06, "FAT16"}, /* DOS при >=32M */
{0x0b, "Win95 FAT32"},
{0x0c, "Win95 FAT32 (LBA)"},
{0x0e, "Win95 FAT16 (LBA)"},
{0x0f, "Win95 Ext'd (LBA)"},
{0x82, "Linux swap"}, /* и для солярис */
{0x83, "Linux"},
{0x85, "Linux extended"},
{0x07, "HPFS/NTFS"} }; // Именно такая таблица принята для файловых систем
#define p_n (sizeof(386_stype) / sizeof(386_stype[0])) // определяем здесь число элементов в массиве, что определен выше.
int disk; // дескриптор файла устройства
u8 mbr[512]; // сюда считаем MBR
struct pt { u8 boot; u8 startp[3]; u8 typep; u8 endp[3]; u32 sectbef; u32 secttot; } pt_k[max]; // Структура по таблице разделов
#define max 20 // max логических дисков
// Главная функция:
int main()
{
int i = 0;
u64 sk;
// Открываем файл устройства, получаем таблицу разделов, потом сверяем сигнатуру:
hard = open(DEV, O_RDONLY);
if(disk < 0) {
perror("open");
exit(-1);
}
rmaintab();
if(csign() < 0) {
printf("Invalid sign!\n");
exit(-1);
}
// Поиск идентификатора расширенного раздела. Если есть – считаем смещение и получаем информацию по логическим разделам.

for(; i < 4; i++) {
if((pt_k[i].typep == 0xF) || \
(pt_k[i].typep == 0x5) || \
(pt_k[i].typep == 0x0C)) {
sk = (u64)pt_k[i].sectbef * 512;
rexttab(sk);
break;
}
}

// Отображаем информацию:

showinf();
return 0;
}

// Проверка сигнатуры 0xAA55:
int csign()
{
u16 sign = 0;
memcpy((void *)&sign, (void *)(mbr + 0x1FE), 2);
#ifdef DEBUG printf("Sign - 0x%X\n", sign);
#endif
if(sign != SIGNT) return -1;
return 0;
}
// Чтение таблицы разделов:
void rmaintab()
{
if(read(disk, mbr, 512) < 0) {
perror("read");
close(disk);
exit(-1);
}
memset((void *)pt_k, 0, (PT_SIZE * 4));
memcpy((void *)pt_k, mbr + 0x1BE, (pt_s * 4));
return;
}
// Чтения расширенной таблицы разделов:
void rexttab(u64 sk)
{
int number = 4; // С этой позиции pt_k будет заполняться информацией о логических дисках
u8 smbr[512];
// Она принимает один параметр sk - смещение к расширенному разделу от начала диска. Для получения информации о логических дисках воспользуемся циклом:
for(;;number++) {
// Читаем SMBR, по offset seek от начала диска:
memset((void *)smbr, 0, 512);
pread64(hard, smbr, 512, sk);
// Заполним часть pt_k. Первый элемент будет указывать на логический диск, а следующий - на следующую структуру SMBR:
memset((void *)&pt_k[number], 0, PT_SIZE * 2);
memcpy((void *)&pt_k[number], smbr + 0x1BE, pt_size * 2);
// Поправка в поле "Номер начального сектора" - отсчет ведется от начала диска:
pt_k[number].sectbef += (sk / 512);
// Код типа раздела равен нулю? Больше логических дисков нет!
if(!(pt_k[number + 1].typep)) break;
// Offset к следующей SMBR:
sk = ((u64)(pt_k[number].sectbef + pt_k[number].secttot)) * 512;

}

return;
}

// Покажем информацию о найденных логических дисках:
void showinf()
{
int i = 0, n;
printf("Num - %d\n", P_N);
for(; i < max; i++) {
if(!pt_k[i].typep) break;
printf("\nType %d - ", i);
for(n = 0; n < P_N; n++) {
if(pt_k[i].typep == 386_stype[n].typep) {
printf("%s\n", 386_stype[n].namep);
break;
}
}
if(n == P_N) printf("Unknown\n");
printf(" Boot flag - 0x%X\n", pt_k[i].bootable);
printf(" Number of sectors in partition%d - %d\n", i, pt_k[i].secttot);
printf("Number of sector before partition%d - %d\n\n", i, pt_k[i].sectbef);
}
return;
}

Интерфейс АТА

А теперь опять к теории… У каждого диска есть стандартный набор регистров, состоящий из двух блоков: командных регистров и управляющих регистров. Первый служит для посылки команд устройству и передачи информации о его состоянии. Состав блока командных регистров:

1. Регистр состояния/команд – в режиме read_only отражает текущее состояние устройства в процессе выполнения команды. Чтение регистра состояния разрешает дальнейшее изменение его бит и сбрасывает запрос аппаратного прерывания. В режиме write принимает коды команд для выполнения.

Назначение бит регистра состояния:

Бит 7 - BSY указывает на занятость устройства. При =1 игнорирует попытки записи в блок командных регистров. При =0 регистры командного блока доступны.
Бит 6 - DRDY указывает на готовность устройства к восприятию любых кодов команд.
Бит 5 - DF - индикатор отказа устройства.
Бит 4 - DSC - индикатор завершения поиска трека.
Бит 3 - DRQ - индикатор готовности к обмену словом или байтом данных.
Бит 2 - CORR - индикатор исправленной ошибки данных.
Бит 1 - IDX - индекс, трактуется специфично для каждого производителя.
Бит 0 - ERR - индикатор ошибки выполнения предыдущей операции. Дополнительная информация содержится в регистре ошибок.

2. Регистр номера цилиндра и номера сектора.
3. Регистр номера устройства и головки.

Биты 7 и 5 - зарезервированы.
Бит 6 - единичным значением указывает на применение режима адресации LBA. При нулевом значении бита используется режим CHS.
Бит 4 - DEV - выбор устройства. При DEV=0 выбрано устройство Master, при DEV=1 - устройство- Slave.
Биты 3-0 имеют двоякое назначение в зависимости от выбранной системы адресации. В режиме CHS они содержат номер головки, в режиме LBA - старшие биты логического адреса.

4. Регистр данных.
5. Регистр ошибок.
6. Регистр свойств.
7. Регистр счетчика секторов содержит число секторов, участвующих в обмене.

Блок управляющих регистров - для управления устройством и получения байта его состояния. В состав блока входят альтернативный регистр состояния и регистр управления устройством. Альтернативный регистр состояния имеет те же биты, что и основной, но его чтение не приводит ни к каким изменениям состояния устройства.

В регистре управления устройством биты 7-3 зарезервированы, бит 0 всегда нулевой, используются только два бита:
Бит 2 - SRST - программный сброс, действует все время, пока бит не будет сброшен.
Бит 1 - IEN# - инверсный бит разрешения прерывания.

Рассмотрим теперь взаимодействие хоста и устройства:

1. Хост читает регистр состояния устройства, дожидаясь нулевого значения бита BSY.
2. Дождавшись освобождения устройства, хост записывает в регистр номера устройства и головки байт, у которого бит DEV указывает на адресуемое устройство.
3. Хост читает основной регистр состояния адресованного устройства, дожидаясь признака его готовности (DRDY = 1).
4. Хост заносит требуемые параметры в блок командных регистров.
5. Хост записывает код команды в регистр команд.
6. Устройство устанавливает бит BSY и переходит к исполнению команды.

Для команд, не требующих передачи данных (ND):

7. Завершив исполнение команды, устройство сбрасывает бит BSY и устанавливает запрос прерывания. К этому моменту в регистрах состояния и ошибок уже имеется информация о результате выполнения.

Для команд, требующих чтения данных в режиме PIO:

7. Подготовившись к передаче первого блока данных по шине АТА, устройство устанавливает бит DRQ. Если была ошибка, она фиксируется в регистрах состояния и ошибок. Далее устройство сбрасывает бит BSY и устанавливает запрос прерывания.
8. Зафиксировав обнуление бита BSY (или по прерыванию), хост считывает регистр состояния, что приводит к сбросу прерывания от устройства.
9. Если хост обнаружил единичное значение бита DRQ, он производит чтение первого блока данных в режиме PIO (адресуясь к регистру данных). Если обнаружена ошибка, считанные данные могут быть недостоверными.

Не утомились нудной частью? Возвращаемся к практике. Задача в общем-то такая же - получить информацию идентификации устройства и считать MBR.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/hdreg.h>
#define d_data 0x1f0 // регистр данных
#define d_error 0x1f1 // регистр ошибок
#define d_nsector 0x1f2 // регистр счетчика секторов
#define d_sector 0x1f3 // регистр стартового сектора
#define d_lcyl 0x1f4 // регистр младшего байта номера цилиндра
#define d_hcy 0x1f5 // регистр старшего байта номера цилиндра
#define d_current 0x1f6 // 101a???? , a=устройство, ????=головка
#define d_status 0x1f7 // регистр состояния

#define out(val,port)
asm(
"outb %%al, %%dx"
::"a"(val),"d"(port)
)
#define in_byte(val,port)
asm(
"inb %%dx, %%al"
:"=a"(val)
:"d"(port)
)

#define in_word(val,port)
asm(
"inw %%dx, %%ax"
:"=a"(val)
:"d"(port)
)
// Чтение байта из порта, запись байта в порт и запись слова в порт соответственно.

void d_busy()
{
unsigned char stat;

do {
in_b(stat,d_status);
} while (stat & 0x80);
return;
}
void d_ready()
{
unsigned char stat;

do {
in_b(stat,d_status);
} while (!(stat & 0x40));
return;
}
// Занято устройство или свободно?
void cerr()
{
unsigned char a;

ib_b(a,d_status);
if (a & 0x1) {
perror("d_status");
exit(-1);
}
return;
}
// Не было ошибок?

void gident(struct d_drive *hd)
{

unsigned short b = 0;
int i = 0;

unsigned short buf[0x100];
memset(buf,0,0x100);
d_busy();
// Когда станет свободно, в регистр номера устройства и головки заносим значение 0xA0. Бит 4 равен 0, а значит, нами выбрано ведущее устройство. Бит 6 оставим нулевым:
out_b(0xA0,d_current);
hd_ready();
// Теперь диск полностью готов. В регистр команд заносим код команды идентификации устройства - 0xEC.
Out_b(0xEC,d_status);
// Считываем информацию по блоку данных:
do {
hd_busy();
cerr();
in_w(a,d_data);
if((i>=10 && i<=19) || (i>=27 && i<=46))
asm(
"xchgb %%ah, %%al"
:"=a"(a)
:"0"(a));
buf[i++] = a;
} while(d_request());

// Считанную информацию сохраним в буфере buff1. Копируем полученную информацию из буфера buff1 в структуру dreg. После чего чистим буфер.
memcpy(hd,(struct dreg *)buf,0x100);
memset(buf,0,0x100);
return;
}

Теперь рассмотрим чтение при адресация CHS

void r_chs(unsigned short N, unsigned short sect,
unsigned short cyl, unsigned short head, unsigned short *buf)
// N - число секторов для чтения, sect - стартовый сектор, cyl - стартовый цилиндр, head - номер головки, buf - буфер, куда все помещается.
{

int i = 0;
unsigned short a;

if((!N) || (!sect)) return;
hd_busy();
// В регистр номера устройства и головки заносим соответствующие данные.
Out_b(0xA0|head,d_current);
hd_ready();
// Заполним блок командных регистров:
out_b(N,d_nsector);
out_b(sect,d_sector);
out_b(cyl,d_cyl);
out_b((cyl >> 8),d_cyl);
// В регистр команд записываем код команды чтения секторов с повторами - 0x20.
out_b(0x20,d_status);
// Считываем блок данных в буфер buf:
do {
hd_busy();
cerr();
in_w(a,d_data);
buf[i++] = a;
} while(d_request());
// Считываем последние 4 байта и выходим из функции:
In_w(a,d_data);
buf[i++] = a;
in_w(a,d_data);
buf[i] = a;
return;
}

// Чтения сектора в режиме адресации LBA.
void read_hd_sector_lba(unsigned short N, unsigned int lba, unsigned short *buf)
{
// N - число секторов для чтения, lba - номер блока, buf - буфер, куда все помещается

int i = 0;
unsigned short a;

if(!N) return;
hd_busy();
// В регистре номера устройства и головки бит 6 устанавливаем в 1, а биты 3-0 будут содержать старшие биты логического адреса (27-24):
out_b(0xE0|((lba & 0x0F000000) >> 24),d_current);
hd_ready();
// В блок командных регистров заносим требуемые параметры. В регистр младшего байта номера цилиндра - биты 15-8 логического адреса, а в регистр старшего байта номера цилиндра - биты 23-16 логического адреса. В регистр команд - команду чтения секторов с повторами.
out_b(N,d_sector);
out_b((lba & 0x000000FF),d_sector);
out_b(((lba & 0x0000FF00) >> 8),d_cyl);
out_b(((lba & 0x00FF0000) >> 16),d_cyl);
out_b(0x20,d_status);
do {
d_busy();
cerr();
in_w(a,d_data);
buf[i++] = a;
} while(d_request());
in_w(a,d_data);
buf[i++] = a;

in_w(a,d_data);
buf[i] = a;

return;
}

Рассмотрим главную функцию, на этом и закончим текст программы.

int main ()
{
// N - число секторов для чтения, sect - номер сектора, cyl - номер цилиндра, head - номер головки, lba - номер логического блока
struct d_drive hd;
int out;
unsigned short N = 1;
unsigned int sect, cyl, head, lba;

unsigned short buf[0x100*N];

memset(buf,0,0x100*N);
memset(&hd,0,sizeof(struct d_drive));

// Во избежание лишних ошибок запросим у системы разрешения доступа к портам в диапазоне 0x1f0 - 0x1f7:
ioperm(0x1f0,8,1);
getident(&hd);
// Вуаля:
printf("Number - %s\n",hd.serial_no);
printf("Model - %s\n",hd.model);
printf("Cyl count - %d\n",hd.cur_cyls);
printf("Head count - %d\n",hd.cur_heads);
printf("Sector count - %d\n",hd.cur_sectors);
printf("Blocks count - %d\n",hd.lba_capacity);

Прочтение MBR в режиме CHS:

sect = 1;
cyl = 0;
head = 0;
r_chs(N,sect,cyl,head,buf);

Тоже самое - в режиме LBA:

lba = 0;
r_lba(N,lba,buf);
ioperm(0x1f0,8,0);

Вот и вся программа. Пожалуй, на этом очередную часть можно закончить, в следующей части будет продолжение работы с жесткими дисками, а точнее описание работы с файловой системой, а так же, наверное, мы затронем работу с CD.