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