• Партнер

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

    Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии