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