Что же. В предыдущих статьях (часть
№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

Как работает Linux: от нажатия кнопки включения до рабочего стола

Лучший способ понять, как работает операционная система, — это проследить поэтапно ее загр…