Что же. В предыдущих статьях (часть
№1
, часть №2) мы
дошли до работы с жестким диском. Давайте как раз теперь им и займемся. Я знаю,
что вам хочется сразу получить тексты программ, но давайте все-таки сначала
познакомимся с теорией.

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

На нем по физическому адресу 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,0×100);
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,0×100);
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,0×100*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.

Оставить мнение

Check Also

LUKS container vs Border Patrol Agent. Как уберечь свои данные, пересекая границу

Не секрет, что если ты собрался посетить такие страны как США или Великобританию то, прежд…