Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей.
Здесь обсуждаются безопасность, программирование, технологии и многое другое.
Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
 |
|

28.04.2008, 03:52
|
|
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме: 2360904
Репутация:
1393
|
|
[10] - форматы файлов ELF и PE
Формат ELF.
В данном обзоре мы будем говорить только о 32-х битной версии этого формата, ибо 64-х битная нам пока ни к чему.
Любой файл формата ELF (в том числе и объектные модули этого формата) состоит из следующих частей:
* Заголовок ELF файла;
* Таблица программных секций (в объектных модулях может отсутствовать);
* Секции ELF файла;
* Таблица секций (в выполняемом модуле может отсутствовать);
Ради производительности в формате ELF не используются битовые поля. И все структуры обычно выравниваются на 4 байта.
Теперь рассмотрим типы, используемые в заголовках ELF файлов:
Тип Размер Выравнивание Комментарий
Elf32_Addr 4 4 Адрес
Elf32_Half 2 2 Беззнаковое короткое целое
Elf32_Off 4 4 Смещение
Elf32_SWord 4 4 Знаковое целое
Elf32_Word 4 4 Беззнаковое целое
unsigned char 1 1 Безнаковое байтовое целое
Теперь рассмотрим заголовок файла:
Код:
#define EI_NIDENT 16
struct elf32_hdr {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry; /* Entry point */
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
};
Массив e_ident содержит в себе информацию о системе и состоит из нескольких подполей.
Код:
struct {
unsigned char ei_magic[4];
unsigned char ei_class;
unsigned char ei_data;
unsigned char ei_version;
unsigned char ei_pad[9];
}
ei_magic - постоянное значение для всех ELF файлов, равное { 0x7f, 'E', 'L', 'F'}
ei_class - класс ELF файла (1 - 32 бита, 2 - 64 бита который мы не рассматриваем)
ei_data - определяет порядок следования байт для данного файла (этот порядок зависит от платформы и может быть прямым (LSB или 1) или обратным (MSB или 2)) Для процессоров Intel допустимо только значение 1.
ei_version - достаточно бесполезное поле, и если не равно 1 (EV_CURRENT) то файл считается некорректным.
В поле ei_pad операционные системы хранят свою идентификационную информацию. Это поле может быть пустым. Для нас оно тоже не важно.
Поле заголовка e_type может содержать несколько значений, для выполняемых файлов оно должно быть ET_EXEC равное 2
e_machine - определяет процессор на котором может работать данный выполняемый файл (Для нас допустимо значение EM_386 равное 3)
Поле e_version соответствует полю ei_version из заголовка.
Поле e_entry определяет стартовый адрес программы, который перед стартом программы размещается в eip.
Поле e_phoff определяет смещение от начала файла, по которому располагается таблица программных секций, используемая для загрузки программ в память.
Не буду перечислять назначение всех полей, не все нужны для загрузки. Лишь еще два опишу.
Поле e_phentsize определяет размер записи в таблице программных секций.
И поле e_phnum определяет количество записей в таблице программных секций.
Таблица секций (не программных) используется для линковки программ. мы ее рассматривать не будем. Так же мы не будем рассматривать динамически линкуемые модули. Тема эта достаточно сложная, для первого знакомства не подходящая.
Теперь про программные секции. Формат записи таблицы программных секций таков:
Код:
struct elf32_phdr {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
};
Подробнее о полях.
p_type - определяет тип программной секции. Может принимать несколько значений, но нас интересует только одно. PT_LOAD (1). Если секция именно этого типа, то она предназначена для загрузки в память.
p_offset - определяет смещение в файле, с которого начинается данная секция.
p_vaddr - определяет виртуальный адрес, по которому эта секция должна быть загружена в память.
p_paddr - определяет физический адрес, по которому необходимо загружать данную секцию. Это поле не обязательно должно использоваться и имеет смысл лишь для некоторых платформ.
p_filesz - определяет размер секции в файле.
p_memsz - определяет размер секции в памяти. Это значение может быть больше предыдущего. Поле p_flag определяет тип доступа к секциям в памяти. Некоторые секции допускается выполнять, некоторые записывать. Для чтения в существующих системах доступны все.
Загрузка формата ELF.
С заголовком мы немного разобрались. Теперь я приведу алгоритм загрузки бинарного файла формата ELF. Алгоритм схематический, не стоит рассматривать его как работающую программу.
Код:
int LoadELF (unsigned char *bin)
{
struct elf32_hdr *EH = (struct elf32_hdr *)bin;
struct elf32_phdr *EPH;
if (EH->e_ident[0] != 0x7f || // Контролируем MAGIC
EH->e_ident[1] != 'E' ||
EH->e_ident[2] != 'L' ||
EH->e_ident[3] != 'F' ||
EH->e_ident[4] != ELFCLASS32 || // Контролируем класс
EH->e_ident[5] != ELFDATA2LSB || // порядок байт
EH->e_ident[6] != EV_CURRENT || // версию
EH->e_type != ET_EXEC || // тип
EH->e_machine != EM_386 || // платформу
EH->e_version != EV_CURRENT) // и снова версию, на всякий случай
return ELF_WRONG;
EPH = (struct elf32_phdr *)(bin + EH->e_phoff);
while (EH->e_phnum--) {
if (EPH->p_type == PT_LOAD)
memcpy (EPH->p_vaddr, bin + EPH->p_offset, EPH->p_filesz);
EPH = (struct elf32_phdr *)((unsigned char *)EPH + EH->e_phentsize));
}
return ELF_OK;
}
По серьезному стоит еще проанализировать поля EPH->p_flags, и расставить на соответствующие страницы права доступа, да и просто копирование здесь не подойдет, но это уже не относится к формату, а к распределению памяти. Поэтому сейчас об этом не будем говорить.
Формат PE.
Во многом он аналогичен формату ELF, ну и не удивительно, там так же должны быть секции, доступные для загрузки.
Как и все в Microsoft  формат PE базируется на формате EXE. Структура файла такова:
* 00h - EXE заголовок (не буду его рассматривать, он стар как Дос. 
* 20h - OEM заголовок (ничего существенного в нем нет);
* 3сh - смещение реального PE заголовка в файле (dword).
* таблица перемещения stub;
* stub;
* PE заголовок;
* таблица объектов;
* объекты файла;
stub - это программа, выполняющаяся в реальном режиме и производящая какие-либо предварительные действия. Может и отсутствовать, но иногда может быть нужна.
Нас интересует немного другое, заголовок PE.
Структура его такая:
Код:
struct pe_hdr {
unsigned long pe_sign;
unsigned short pe_cputype;
unsigned short pe_objnum;
unsigned long pe_time;
unsigned long pe_cofftbl_off;
unsigned long pe_cofftbl_size;
unsigned short pe_nthdr_size;
unsigned short pe_flags;
unsigned short pe_magic;
unsigned short pe_link_ver;
unsigned long pe_code_size;
unsigned long pe_idata_size;
unsigned long pe_udata_size;
unsigned long pe_entry;
unsigned long pe_code_base;
unsigned long pe_data_base;
unsigned long pe_image_base;
unsigned long pe_obj_align;
unsigned long pe_file_align;
// ... ну и еще много всякого, неважного.
};
Много всякого там находится. Достаточно сказать, что размер этого заголовка - 248 байт.
И главное что большинство из этих полей не используется. (Кто так строит?) Нет, они, конечно, имеют назначение, вполне известное, но моя тестовая программа, например, в полях pe_code_base, pe_code_size и тд содержит нули но при этом прекрасно работает. Напрашивается вывод, что загрузка файла осуществляется на основе таблицы объектов. Вот о ней то мы и поговорим.
Таблица объектов следует непосредственно после PE заголовка. Записи в этой таблице имеют следующий формат:
Код:
struct pe_ohdr {
unsigned char o_name[8];
unsigned long o_vsize;
unsigned long o_vaddr;
unsigned long o_psize;
unsigned long o_poff;
unsigned char o_reserved[12];
unsigned long o_flags;
};
o_name - имя секции, для загрузки абсолютно безразлично;
o_vsize - размер секции в памяти;
o_vaddr - адрес в памяти относительно ImageBase;
o_psize - размер секции в файле;
o_poff - смещение секции в файле;
o_flags - флаги секции;
Вот на флагах стоит остановиться поподробнее.
* 00000004h - используется для кода с 16 битными смещениями
* 00000020h - секция кода
* 00000040h - секция инициализированных данных
* 00000080h - секция неинициализированных данных
* 00000200h - комментарии или любой другой тип информации
* 00000400h - оверлейная секция
* 00000800h - не будет являться частью образа программы
* 00001000h - общие данные
* 00500000h - выравнивание по умолчанию, если не указано иное
* 02000000h - может быть выгружен из памяти
* 04000000h - не кэшируется
* 08000000h - не подвергается страничному преобразованию
* 10000000h - разделяемый
* 20000000h - выполнимый
* 40000000h - можно читать
* 80000000h - можно писать
Опять таки не буду с разделяемыми и оверлейными секциями, нас интересуют код, данные и права доступа.
В общем, этой информации уже достаточно для загрузки бинарного файла.
Загрузка формата PE.
Код:
int LoadPE (unsigned char *bin)
{
struct elf32_hdr *PH = (struct pe_hdr *)
(bin + *((unsigned long *)&bin[0x3c]));
// Конечно комбинация не из понятных... просто берем dword по смещению 0x3c
// И вычисляем адрес PE заголовка в образе файла
struct elf32_phdr *POH;
if (PH == NULL || // Контролируем указатель
PH->pe_sign != 0x4550 || // сигнатура PE {'P', 'E', 0, 0}
PH->pe_cputype != 0x14c || // i386
(PH->pe_flags & 2) == 0) // файл нельзя запускать!
return PE_WRONG;
POH = (struct pe_ohdr *)((unsigned char *)PH + 0xf8);
while (PH->pe_obj_num--) {
if ((POH->p_flags & 0x60) != 0)
// либо код либо инициализированные данные
memcpy (PE->pe_image_base + POH->o_vaddr,
bin + POH->o_poff, POH->o_psize);
POH = (struct pe_ohdr *)((unsigned char *)POH +
sizeof (struct pe_ohdr));
}
return PE_OK;
}
Это опять таки не готовая программа, а алгоритм загрузки.
И опять таки многие моменты не освещаются, так как выходят за пределы темы.
Но теперь стоит немного поговорить про существующие системные особенности.
Системные особенности.
Не смотря на гибкость средств защиты, имеющихся в процессорах (защита на уровне таблиц дескрипторов, защита на уровне сегментов, защита на уровне страниц) в существующих системах (как в Windows, так и в Unix) полноценено используется только страничная защита, которая хотя и может уберечь код от записи, но не может уберечь данные от выполнения. (Может быть, с этим и связано изобилие уязвимостей систем?)
Все сегменты адресуются с нулевого линейного адреса и простираются до конца линейной памяти. Разграничение процессов производится только на уровне страничных таблиц.
В связи с этим все модули линкуются не с начальных адресов, а с достаточно большим смещением в сегменте. В Windows используется базовый адрес в сегменте - 0x400000, в юникс (Linux или FreeBSD) - 0x8048000.
Некоторые особенности так же связаны со страничной организацией памяти.
ELF файлы линкуются таким образом, что границы и размеры секций приходятся на 4-х килобайтные блоки файла.
А в PE формате, не смотря на то, что сам формат позволяет выравнивать секции на 512 байт, используется выравнивание секций на 4к, меньшее выравнивание в Windows не считается корректным.
Последний раз редактировалось z01b; 29.04.2008 в 14:21..
|
|
|

28.04.2008, 04:00
|
|
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме: 2360904
Репутация:
1393
|
|
[11] - процесс загрузки
Процесс загрузки.
То, что я до сих пор сделал пока рассчитано только на работы с дисками 1,4Мб, то есть с флопами. Это конечно ограничение в некоторой степени, но пока система еще далеко не готова, этого достаточно. Естественно это еще не окончательный вариант. Да и можно ли говорить об окончательности программных продуктов? Нет предела совершенству.
В обязанности бутсектора входит следующее:
1. Загрузить с диска дополнительные части кода и служебную информацию файловой системы.
2. Загрузить с диска файл сценария (конфигурации) загрузки.
3. Загрузить с диска ядро и модули.
4. Перейти в защищенный режим.
5. Передать управление ядру.
Если с первым и двумя последними пунктами все просто и компактно, то второй и третий пункт требуют возможности работы с файловой системой, а третий пункт помимо этого должен знать структуру бинарных форматов. На все это не хватает 512 байт, отводимых для бутсектора. Наш бутсектор занимает больше - один килобайт.
В файловой системе EXT2 с этим не возникает никаких проблем, поскольку первый килобайт файловой системы не используется.
В FAT это немного сложнее. Служебная структура, именуемая Boot Sector Record (BSR), содержит в себе все необходимые поля для выделения для загрузочного сектора места более чем 512 байт. Но как это сделать при форматировании, стандартными средствами, я не нашел. И если формат диска не соответствует каким-то внутренним представлениям Windows, то содержимое такого нестандартного диска может быть испорчено. Выход был найден случайно. Как оказалось утилита format хоть и не имеет таких параметров командной строки, но перед форматированием берет информацию из BSR. И если предварительно заполнить эту структуру (с нужными нам параметрами), а потом уже форматировать, то все получается так, как хочется нам. Таким образом, у меня получилось сделать диск, у которого два сектора зарезервированы (там будет размещаться boot), и одна копия FAT.
Ну теперь давайте по порядку рассмотрим все этапы работы бутсектора.
Загрузка с диска дополнительной части кода и служебной информации файловой системы.
Бутсектор загружается БИОСом по адресу 0:7c00h занимает он 512 байт. Память начиная с адреса 0:7e00h свободна. но в эту память мы загрузим второй сектор бута. Одновременно загружается информация необходимая для обслуживания файловой системы. Для EXT2 дополнительно необходимо загрузить два килобайта (суперблок и дескрипторы групп), для FAT немного больше - 4,5 килобайта (первая копия FAT).
Код:
mov ax, 0x7e0
mov es, ax
Адрес 0:7e00h идентичен адресу 7e0h:0. Вторым вариантом мы и будем пользоваться, потому что наша процедура загрузки секторов размещает их по сегментному адресу, хранящемуся в es.
В ax номер сектора, с которого начинается чтение (первый сектор является нулевым (каламбур  . И далее все зависит от файловой системы.
Код:
%ifdef EXT2FS
mov cx, 5
Для EXT2 загружается 5 секторов - второй сектор бутсектора (1 сектор), суперблок файловой системы (2 сектора) и дескрипторы групп (2 сектора).
Код:
%elifdef FATFS
mov cx, 10
Для FAT загружается 10 секторов - второй сектор бутсектора (1 сектор), таблица FAT - 9 секторов (такой размер она имеет на floppy дисках).
Код:
%else
%error File system not specified
%endif
call load_block
Все. первый пункт загрузки выполнен.
Функции обслуживания файловых систем имеют одинаковый интерфейс. Cобственно их всего две fs_init и fs_load_file. Естественно у них различаются реализации, но в процессе компиляции выбирается используемая файловая система. Для совместного использования нам никак не хватит одного килобайта, да и не за чем это.
Загрузка с диска файла сценария (конфигурации) загрузки.
Из-за сложности VFAT (FAT с длинными именами) он не реализован. Все имена на диске FAT должна иметь формат 8.3
В файловой системе FAT я не оперирую принятыми в MS системах именами дисков и при указании пути использую путь относительно корневой директории диска (как это делается в юникс системах).
Файл конфигурации у нас пока называется boot.rc и находится в каталоге /etc. Формат у этого файла достаточно нестрогий. Из-за нехватки места в boot секторе там сделана реакция только на ключевые слова, которыми являются:
* kern[el] - файл ядра;
* modu[le] - файл модуля;
* #end - конец файла конфигурации.
Использование этих слов в другом контексте недопустимо.
Предварительно проинициализировав файловую систему
call fs_init
Мы загружаем этот файл с диска.
mov si, boot_config
call fs_load_file
...
boot_config:
db '/etc/boot.rc', 0
Содержимое файла конфигурации такое:
kernel /boot/kernel
#end
Модулей у нас пока никаких нет, да и ядро еще в зачаточном состоянии. Но речь сейчас не об этом.
Загрузка с диска ядра и модулей.
Про этот момент я не буду особо расписывать, желающие могут посмотреть в исходниках, которые в скором времени появятся на сайте.
Скажу только, что программа анализирует файл конфигурации, в соответствии с ключевыми словами загружает ядро (которое может быть в единственном экземпляре) и любое количество модулей. Общий объем ядра и модулей ограничен свободным размером базовой памяти (около 600к).
Перейдем к предпоследнему пункту.
Переход в защищенный режим.
Бутсектор не особо беспокоится об организации памяти в системе - это забота ядра. Для перехода в защищенный режим он описывает всего два сегмента: сегмент кода и сегмент данных. оба сегмента имеют базовый адрес - 0 и предел в 4 гигабайта (это нам пригодиться для проверки наличия памяти).
Перед переходом в защищенный режим нам необходимо включить адресную линию A20. По моим сведениям этот механизм ввели в пору 286 для предотвращения несанкционированных обращений к памяти свыше одного мегабайта (непонятно зачем?). Но поскольку это имеет место быть - нам это нужно обрабатывать, иначе каждый второй мегабайт будет недоступен. Делается это почему-то через контроллер клавиатуры (еще одна загадка).
Код:
mov al, 0xd1
out 0x64, al
mov al, 0xdf
out 0x60, al
После этого можно переходить в защищенный режим.
В регистр gdtr загружается дескриптор GDT.
Очищается регистр флагов.
Код:
mov eax, cr0
or al, 1
mov cr0, eax
Включается защищенный режим. Не смотря на то, что процессор уже находится в защищенном режиме, мы пока работаем в старых адресах. Чтобы от них уйти нам нужно сделать дальный переход в адреса защищенного режима.
Код:
jmp 16:.epm
BITS 32
.epm:
16 в этом адресе перехода - это не сегмент. Это селектор сегмента кода.
Код:
mov ax, 8
mov ds, ax
mov es, ax
; Ставим стек.
mov ss, ax
movzx esp, sp
После всего этого мы инициализируем сегментные регистры соответствующими селекторами, в том числе и сегмент стека, но указатель стека у нас не меняется, только теперь он становится 32-х битным.
Код:
...
gd_table:
; пеpвый дескpиптоp - данные и стек
istruc descriptor
at descriptor.limit_0_15, dw 0xffff
at descriptor.base_0_15, dw 0
at descriptor.base_16_23, db 0
at descriptor.access, db 0x92
at descriptor.limit_16_19_a, db 0xcf
at descriptor.base_24_31, db 0
iend
; втоpой дескpиптоp - код
istruc descriptor
at descriptor.limit_0_15, dw 0xffff
at descriptor.base_0_15, dw 0
at descriptor.base_16_23, db 0
at descriptor.access, db 0x9a ; 0x98
at descriptor.limit_16_19_a, db 0xcf
at descriptor.base_24_31, db 0
iend
Это GDT - Глобальная таблица дескрипторов. Здесь всего два дескриптора, но во избежание ошибок в адресации обычно вводится еще один дескриптор - нулевой, который не считается допустимым для использования. Мы не будем резервировать для него место специально, просто начало таблицы сместим на 8 байт выше.
Код:
gd_desc:
dw 3 * descriptor_size - 1
dd gd_table - descriptor_size
А это содержимое регистра GDTR. Здесь устанавливается предел и базовый адрес дескриптора. обратите внимание на базовый адрес, здесь происходит резервирование нулевого дескриптора.
Теперь процессор находится в защищенном режиме и уже не оперирует сегментами, а оперирует селекторами. Селекторов у нас всего три. Нулевой - недопустим. восьмой является селектором данных и шестнадцатый - селектором кода.
После этого управление можно передать ядру. дальше со всем этим будет разбираться оно.
Передача управления ядру.
Здесь вообще все просто. Когда мы загрузили ядро, в файле ядра мы определили адреса сегмента кода и сегмента данных. Не смотря на то, что ядро имеет вполне конкретные смещения в сегменте (которые задаются при компиляции), код инициализации ядра рассчитан на работу без привязки к адресам. Это нужно для определения количества памяти, после перевода ядра на свои адреса доступ ко всей памяти будет для ядра затруднен в связи с включением механизма страничного преобразования.
Итак, переходим к выполнению кода ядра.
Код:
mov ebx, kernel_data
mov eax, [ebx + module_struct.code_start]
jmp eax
В этом фрагменте в eax записывается адрес начала кодового сегмента ядра.
Так как сегмент кода у нас занимает всю виртуальную память, нам не важно где находится ядро (хотя мы знаем, что оно было загружено в базовую память). Мы просто передаем ему управление.
|
|
|

28.04.2008, 04:04
|
|
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме: 2360904
Репутация:
1393
|
|
[12] - определение количества памяти
Определение количества памяти через BIOS.
Ну, начнем с исторических функций.
Давным-давно, когда даже Билл Гейтс говорил что 640 килобайт хватит всем, но не у всех были эти 640 килобайт.  в биосах существовала функция определения количества базовой памяти.
int 12h
Выходные параметры:
* ax - размер базовой памяти в килобайтах.
Сейчас уже вряд ли кому придет в голову, что базовой памяти может быть меньше 640 килобайт. но мало ли...
Появлялись новые процессоры, и размеры памяти стали расти. в связи с чем появилась функция определения количества расширенной памяти.
int 15h fn 88h
Входные параметры:
* ah = 88h
Выходные параметры:
* ax - размер расширенной памяти в килобайтах.
Возможно из за архитектуры 286-х процессоров (которым размер шины адреса не позволяет иметь больше чем 16 мегабайт памяти) эта функция часто имеет аналогичное ограничение и результат в ax не может превышать 3с00h (Что составляет 15Мб).
Но, опять таки, появились новые процессоры. 16 мегабайт стало мало. Вследствие этого появилась еще одна функция BIOS:
int 15h fn e801h
Входные параметры:
* ax = e801h.
Выходные параметры:
* ax - размер расширенной памяти в килобайтах до 16Mb;
* bx - размер расширенной памяти в блоках по 64к свыше 16Мб;
* cx - размер сконфигурированный расширенной памяти в килобайтах до 16Mb;
* dx - размер сконфигурированной расширенной памяти в блоках по 64к свыше 16Мб.
Не знаю, что означает сконфигурированная память. Так написано в описании.
Здесь производители BIOS видимо оказались неединодушны. Некоторые версии в ax и bx возвращают 0, это значит что размер памяти следует определять из cx, dx.
Но видимо и 4 гигабайт оказалось мало. В новых BIOS появилась еще одна функция.
int 15h fn e820h
Входные параметры:
* eax = e820h;
* edx = 534d4150h ('SMAP');
* ebx - смещение от начала карты памяти;
* eсx - Размер буфера;
* es:di - Адрес буфера для размещения карты памяти.
Выходные параметры:
* eax - 534d4150h ('SMAP');
* ebx - следующее смещение от начала карты памяти, если = 0, то вся карта передана;
* ecx - Количество возвращенных байт;
* буфер заполнен информацией;
Эту функцию нужно вызывать в цикле до тех пор, пока не будет прочитана вся карта памяти.
Формат структуры таков:
Код:
struct {
long long base;
long long length;
long type;
};
Поле type может содержать следующие значения:
* 1 - Доступно для использования операционной системой;
* 2 - Зарезервировано (например, ROM);
* 3 - ACPI reclaim memory (Доступно для операционной системы после прочтения таблицы ACPI;
* 4 - ACPI NVS memory (Операционной системе требуется сохранять эту память между NVS сессиями).
Проверить как работает эта функция у меня не получилось, мой BIOS ее не поддерживает. 
Но в заключение скажу следующее. Все функции в случае ошибки (если функция не поддерживается) возвращают установленный флаг cf. В случае отсутствия новых функций необходимо обращаться к более старым.
Функции BIOS не работают в защищенном режиме, поэтому все эти операции необходимо производить еще до перехода в защищенный режим.
Определение размера памяти другими способами:
Помимо функций BIOS есть еще много других способов.
Самый простой - помереть память самому.  Делается это из защищенного режима, страничное преобразование должно быть выключено, адресная линия A20 должна быть включена.
Можно мереть от нуля, но поскольку в первом мегабайте есть дыры (видеопамять, биосы, просто дыры), удобнее делать это начиная с первого мегабайта.
Вовсе не обязательно проверять каждый байт, достаточно проверять один байт на какое-то определенное количество памяти. Определенным количеством памяти можно посчитать мегабайт, но лучше (хотя и медленнее) за единицу памяти принять одну страницу памяти (4к).
Во избежание неприятностей память лучше не разрушать, а восстанавливать в первоначальном виде. делается это примерно так:
Код:
xchg [ebx], eax
xchg [ebx], eax
Если после этого в eax содержится то же значение, которое было до того, значит память присутствует по данному адресу. Если возвратилось 0ffffffffh, значит память отсутствует, если же что ни будь другое - то это может быть ROM, хотя после мегабайта вы вряд ли встретите какой либо BIOS. В любом случае если память по текущему адресу не обнаружена, значит, память закончилась и дальше искать чревато... существуют еще различные типы памяти (ACPI например) которую не стоит трогать.
Из защищенного режима можно воспользоваться содержимым CMOS, некоторые ячейки в нем BIOS заполняет определенными при начальном тесте системы значениями. Но здесь все не так однозначно как хотелось бы. Разные версии BIOS могут хранить значения в разных местах.
* 15h - Базовая память в килобайтах (младший байт) (IBM);
* 16h - Базовая память в килобайтах (старший байт) (IBM);
* 17h - Расширенная память в килобайтах (младший байт) (IBM);
* 18h - Расширенная память в килобайтах (старший байт) (IBM);
* 30h - Расширенная память в килобайтах (младший байт) (IBM);
* 31h - Расширенная память в килобайтах (старший байт) (IBM);
* 34h - Расширенная память более 16Мб (блоками по 64к) (младший байт) (AMI);
* 35h - Расширенная память более 16Мб (блоками по 64к) (старший байт) (AMI);
* 35h - Расширенная память (блоками по 64к) (младший байт) (AMI WinBIOS);
* 36h - Расширенная память (блоками по 64к) (старший байт) (AMI WinBIOS);
Байты 30-31 принято считать стандартными, но они определяют только 64Мб памяти. Не очень то подходят для использования.
Динамическое распределение памяти.
Почти любое приложение пользуется динамически выделяемыми блоками памяти (известная, наверное, всем функция malloc в c). Сейчас мы поговорим о том, как это все работает.
Подходить к этому можно по разному, но принцип везде прослеживается один. На каждый блок памяти необходимо иметь структуру, описывающую занятось блока, его размер. В примитивной реализации это может выглядеть так, как это сделано в DOS.
В ДОСе вся память на равных правах принадлежит всем запущенным программам. Но чтобы операционная система могла как-то контролировать использование памяти, в ДОСе применяются MCB (Memory Control Block). Формат этого блока таков:
Код:
struct {
char Signature;
unsigned short OwnerId;
unsigned short SizeParas;
char Reserved[3];
char OwnerName[8];
};
Размер структуры 16 байт (1 параграф памяти) и эта структура непосредственно предшествует описываемому блоку памяти.
Размер блока указывается в параграфах в поле SizeParas. Такая структура вполне подходит для ограниченной по размерам памяти DOS, но для приложений она не очень то применима. Разница состоит в том, что в случае ДОС, чтобы найти блок свободной памяти (Такие блоки помечаются нулевым OwnerId), необходимо пройти по всем блокам от начала цепочки, до тех пор, пока не встретится свободный блок соответствующего размера. В ДОСе имеется функция, с помощью которой можно получить адрес первого блока (Base MCB) (int 21h, fn 52h).
Столь медленный поиск не страшен для DOS, у которого количество блоков редко превышает несколько десятков, но в приложениях поиск по цепочке блоков может быть достаточно долгой процедурой.
Поэтому в приложениях обычно применяется другой алгоритм, который заключается в следующем. (Я рассмотрю наиболее быстрый алгоритм, вариантов, конечно, может быть множество):
У каждого блока, как я уже говорил, есть два основных параметра: размер и флаг занятости. Оба эти параметра размещаются в одном двойном слове памяти. Поскольку как начало блока, так и его размер обычно выравниваются на четное число байт, младшие биты размера остаются неиспользуемыми (всегда равны нулю) и флаг занятости размещается в одном из них.
Этот параметр блока размещается перед началом и по окончанию блока. Начальный параметр следующего блока соответственно будет размещен непосредственно после конечного параметра предыдущего, что позволит анализировать цепочку блоков с одинаковым успехом в обоих направлениях.
Свободные блоки памяти размещаются в списках в соответствии со своим размером. Размер блоков в списках увеличивается в геометрической прогрессии. К примеру, в первом списке хранятся блоки до 16 байт длиной, во втором до 32-х байт длиной и так далее. Такая система позволяет, зная размер необходимого блока, сразу же выбирать из соответствующего списка подходящий блок и не требует поиска по всем блокам. Для организации списков к блоку добавляются несколько параметров (поскольку блок свободен, и его внутреннее пространство может быть использовано для любых целей, эти параметры размещаются в самом блоке). К этим параметрам относятся ссылка на следующий свободный блок в списке, и номер списка в котором находится блок. (Это позволяет ускорить удаление блока из списка).
Для выделения блока необходимого размера сперва проверяется список соответствующего размера, в котором может потребоваться поиск блока. Если соответствующий список пуст, то проверяется следующий список, в котором уже не требуется проводить поиска, поскольку любой блок заведомо больше нужного размера. Найденный пустой блок делится на две части, вторая - не нужная часть оформляется как свободная и помещается в соответствующий список, а первая часть оформляется как занятая и возвращается программе.
Из-за необходимости введения дополнительных параметров для свободных блоков памяти минимальный размер блока не может быть меньше 8 байт. Даже если пользователь захочет получить блок меньшего размера, выделится блок в 8 байт длиной.
При освобождении блока, если предыдущий или последующий блоки пусты, он объединяется с ними в один блок и добавляется в список соответствующего размера. Использованные окружающие блоки удаляются из тех списков, в которых они были записаны ранее.
Для того, чтобы предотвратить попытку объединения первого блока памяти (при его освобождении) с предшествующим ему, перед первым блоком ставится параметр с флагом занятости. То же самое делается и для последнего блока памяти, но только после него.
Последний раз редактировалось z01b; 30.04.2008 в 21:31..
|
|
|

28.04.2008, 04:10
|
|
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме: 2360904
Репутация:
1393
|
|
Файловый архив
disk2.zip (9 kb) - KernelNG PreAlpha-1. Новая организация ресурсов.
imgtools.rar (32 kb) - Image tools - fdread.exe, fdwrite.exe (dos/win32) - утилиты для работы с имиджами дисков от Дрона
i586-elf-gnu.rar (2.2 Mb) - i586-elf GNU (bin) - i586 elf binutils и gcc
nd_gnutools.rar (449 kb) - ND GNU-Tools (bin) - гнутые тулзы, выдранные из cygwin для корректной работы компайлеров
Tech Help 6.0 - Замечательный справочник по прерываниям BIOS и DOS. Имеет массу другой информации. Схож с HelpPC (если Вы знаете, что это такое).
bootprog.zip (50 kb) - Пару примеров работы с бут сектором на паскале и ассемблере
os.zip (377 kb) - Исходник операционной системы, написанной Алексеем Фрунзей
SolarOS (3145 kb) - Исходник операционной системы, написанной моим земляком, Богданом Онтану.
Kolibri OS 0.6.0.0 (2117 kb) - Kolibri OS - операционная система, развившаяся из Menuet 32. Отличается ориентированностью на ассемблере - большая часть приложений для неё также написаны на ассемблере.
bin86-0.16.17.tar.gz (700 kb) - This is a simple assember and linker for 8086 - 80386 machine code.
MS-DOS 6.0 (19 mb) - Исходник известной нам ОС, MS-DOS 6.0 . Сегодня только осилил залить. Пароль на архив: antichat.ru~!@#
PS Пост будет обновляться. "Горячие" файлы, буду выделять красным, остальные - оливковым.
Последний раз редактировалось z01b; 30.04.2008 в 21:23..
|
|
|

28.04.2008, 04:15
|
|
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме: 2360904
Репутация:
1393
|
|
(C) Dron, 2001 г.
Статью я нашел на просторах народа и побоялся, как-бы она не потерялась. Решил выложить ее здесь. Над оформлением буду еще работать.
Статья не моя, но всетаки предлагаю супер-модератору, закрепить ее как важное.
Последний раз редактировалось z01b; 29.04.2008 в 00:04..
|
|
|

28.04.2008, 16:02
|
|
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме: 2360904
Репутация:
1393
|
|
Сообщение от slesh
Статей довольно интересная. год назад еще читал, жаль что там почти не описано как работать по нормальному в защищенном режиме
В общем нарыл еще один интересный исходник ядра, для винды 9х, который работает в защищенном режиме.
;---------------------------------------------------;
; TINY OS KERNEL (C) BY ALEXEI A. FROUNZE ;
; 28th OF MAY, 2000 ;
; ;
; !!! ABSOLUTELY NO ANY WARRANTIES !!! ;
; ;
; ! AUTHOR CAN NOT BE RESPONSIBLE FOR ANY DAMAGE, ! ;
; ! DATA OR HEALTH/LIFE LOSS ! ;
; ;
; E-Mail: alexfru@chat.ru ;
; Homepage: http://www.chat.ru/~alexfru ;
; Mirror: http://members.xoom.com/alexfru ;
; ;
; FEATURES: ;
; - 32-BIT PROTECTED MODE PROGRAMMING WITH 386+ ;
; - EXCEPTIONS HANDLING ;
; - TIMER & KEYBOARD HARDWARE INTERRUPTS HANDLING ;
; - TASK SWITCHING (RING 0 AND RING 3) ;
; ;
; SYSTEM REQUIREMENTS: ;
; - 386 OR BETTER COMPUTER ;
; - EGA/VGA OR BETTER COLOR VIDEO SYSTEM ;
; - DOS 5.0 OR BETTER (WITHOUT ANY MEMORY ;
; MANAGERS SUCH AS EMM386.EXE) OR ;
; WINDOWS 9x LOADED IN COMMAND PROMPT ONLY MODE ;
;---------------------------------------------------;
[DOWNLOAD]
|
|
|

29.04.2008, 18:38
|
|
Reservists Of Antichat - Level 6
Регистрация: 04.02.2007
Сообщений: 1,152
Провел на форуме: 3008839
Репутация:
1502
|
|
__________________
Bedankt euch dafür bei euch selbst.
H_2(S^3/((z1, z2)~(exp(2pi*i/p)z1, exp(2pi*q*i/p)z2)))=Z/pZ
|
|
|

29.04.2008, 18:57
|
|
Moderator - Level 7
Регистрация: 21.03.2007
Сообщений: 1,200
Провел на форуме: 7134052
Репутация:
1204
|
|
Запосчу-ка я сюда же примерчик =)
The Real "Hello World"
1. Идея (hello.c)
Изучение нового языка программирования начинается, как правило, с написания простенькой программы, выводящей на экран краткое приветствие типа "Hello World!". Например, для C это будет выглядить приблизительно так.
Код:
main()
{
printf("Hello World!\n");
}
Показательно, но совершенно не интересно. Программа, конечно работает, режим защищенный, но ведь для ее функционирования требуется ЦЕЛАЯ операционная система. А что если написать такой "Hello World", для которого ничего не надо. Вставляем дискетку в компьютер, загружаемся с нее и ..."Hello World". Можно даже прокричать это приветствие из защищенного режима.
Сказано - сделано. С чего бы начать?.. Набраться знаний, конечно. Для этого очень хорошо полазить в исходниках Linux и Thix. Первая система всем хорошо знакома, вторая менее известна, но не менее полезна.
Подучились? ... Понятно, что сперва надо написать загрузочный сектор для нашей мини-опрерационки (а ведь это именно мини-операционка). Поскольку процессор грузится в 16-разрядном режиме, то для созджания загрузочного сектора используется ассемблер и линковщик из пакета bin86. Можно, конечно, поискать еще что-нибудь, но оба наших примера используют именно его и мы тоже пойдет по стопам учителей. Синтаксис этого ассемблера немколько странноватый, совмещающий черты, характерные и для Intel и для AT&T (за подробностями направляйтесь в Linux-Assembly-HOWTO), но после пары недель мучений можно привыкнуть.
2. Загрузочный сектор (boot.S)
Сознательно не буду приводить листингов программ. Так станут понятней основные идеи, да и вам будет намного приятней, если все напишите своими руками.
Для начала определимся с основными константами.
START_HEAD = 0 - Головка привода, которою будем использовать.
START_TRACK = 0 - Дорожка, откуда начнем чтение.
START_SECTOR = 2 - Сектор, начиная с которого будем считывать наше ядрышко.
SYSSIZE = 10 - Размер ядра в секторах (каждый сектор содержит 512 байт)
FLOPPY_ID = 0 - Идентификатор привода. 0 - для первого, 1 - для второго
HEADS = 2 - Количество головок привода.
SECTORS = 18 - Количество дорожек на дискете. Для формата 1.44 Mb это количество равно 18.
В процессе загрузки будет происходить следующее. Загрузчик BIOS считает первый сектор дискеты, положит его по адресу 0000:0x7c00 и передаст туда управление. Мы его получим и для начала переместим себя пониже по адресу 0000:0x600, перейдем туда и спокойно продолжим работу. Собственно вся наша работа будет состоять из загрузки ядра (сектора 2 - 12 первой дорожки дискеты) по адресу 0x100:0000, переходу в защищенный режим и скачку на первые строки ядра. В связи с этим еще несколько констант:
BOOTSEG = 0x7c00 - Сюда поместит загрузочный сектор BIOS.
INITSEG = 0x600 - Сюда его переместим мы.
SYSSEG = 0x100 - А здесь приятно расположится наше ядро.
DATA_ARB = 0x92 - Определитель сегмента данных для дескриптора
CODE_ARB = 0x9A - Определитель сегмента кода для дескриптора.
Первым делом произведем перемещение самих себя в более приемлемое место.
Код:
cli
xor ax, ax
mov ss, ax
mov sp, #BOOTSEG
mov si, sp
mov ds, ax
mov es, ax
sti
cld
mov di, #INITSEG
mov cx, #0x100
repnz
movsw
jmpi go, #0 ; прыжок в новое местоположение
загрузочного сектора на метку go
Теперь необходимо настроить как следует сегменты для данных (es, ds) и для стека. Это конечно неприятно, что все приходится делать вручную, но что делать. Ведь нет никого в памяти компьютера, кроме нас и BIOS.
Код:
go:
mov ax, #0xF0
mov ss, ax
mov sp, ax ; Стек разместим как 0xF0:0xF0 = 0xFF0
mov ax, #0x60 ; Сегменты для данных ES и DS зададим в 0x60
mov ds, ax
mov es, ax
Наконец можно вывести победное приветствие. Пусть мир узнает, что мы смогли загрузиться. Поскольку у нас есть все-таки еще BIOS, воспользуемся готовой функцией 0x13 прерывания 0x10. Можно конечно презреть его и написать напрямую в видеопамять, но у нас каждый байт команды на счету, а байт таких всего 512. Потратим их лучше на что-нибудь более полезное.
Код:
mov cx,#18
mov bp,#boot_msg
call write_message
Функция write_message выгдядит следующим образом
Код:
write_message:
push bx
push ax
push cx
push dx
push cx
mov ah,#0x03 ; прочитаем текущее положение курсора,
дабы не выводить сообщения где попало.
xor bh,bh
int 0x10
pop cx
mov bx,#0x0007 ; Параметры выводимых символов :
видеостраница 0, аттрибут 7 (серый на черном)
mov ax,#0x1301 ; Выводим строку и сдвигаем курсор.
int 0x10
pop dx
pop cx
pop ax
pop bx
ret
А сообщение так
Код:
boot_msg:
.byte 13,10
.ascii "Booting data ..."
.byte 0
К этому времени на дисплее компьютера появится скромное "Booting data ..." . Это в принципе уже "Hello World", но давайте добьемся чуточку большего. Перейдем в защищенный режим и выведем этот "Hello" уже из программы написаной на C.
Ядро 32-разрядное. Оно будет у нас размещаться отдельно от загрузочного сектора и собираться уже gcc и gas. Синтаксис ассемблера gas соответсвует требованиям AT&T, так что тут уже все проще. Но для начала нам нужно прочитать ядро. Опять воспользуемся готовой функцией 0x2 прерывания 0x13.
Код:
recalibrate:
mov ah, #0
mov dl, #FLOPPY_ID
int 0x13 ; производим переинициализацию дисковода.
jc recalibrate
call read_track ; вызов функции чтения ядра
jnc next_work ; если во время чтения не произошло ничего
плохого то работаем дальше
bad_read:
; если чтение произошло неудачно то
выводим сообщение об ошибке
mov bp,#error_read_msg
mov cx,7
call write_message
inf1: jmp inf1 ; и уходим в бесконечный цикл.
Теперь нас спасет только ручная перезагрузка
Сама функция чтения предельно простая: долго и нудно заполняем параметры, а затем одним махом считываем ядро. Усложнения начнуться, когда ядро перестанет помещаться в 17 секторах ( то есть 8.5 kb), но это пока только в будущем, а пока вполне достаточно такого молниеносного чтения.
Код:
read_track:
pusha
push es
push ds
mov di, #SYSSEG ; Определяем
mov es, di ; адрес буфера для данных
xor bx, bx
mov ch, #START_TRACK ;дорожка 0
mov cl, #START_SECTOR ;начиная с сектора 2
mov dl, #FLOPPY_ID
mov dh, #START_HEAD
mov ah, #2
mov al, #SYSSIZE ;считать 10 секторов
int 0x13
pop ds
pop es
popa
ret
Вот и все. Ядро успешно прочитано и можно вывести еще одно радостное сообщение на экран.
Код:
next_work:
call kill_motor ; останавливаем привод дисковода
mov bp,#load_msg ; выводим сообщение
mov cx,#4
call write_message
Вот содержимое сообщения
Код:
load_msg:
.ascii "done"
.byte 0
А вот функция остановки двигателя привода.
Код:
kill_motor:
push dx
push ax
mov dx,#0x3f2
xor al,al
out dx,al
pop ax
pop dx
ret
На данный момент на экране выведено "Booting data ...done" и лампочка привода флоппи-дисков погашена. Все затихли и готовы к смертельному номеру - прыжку в защищенный режим.
Для начала надо включить адресную линию A20. Это в точности означает, что мы будем использовать 32-разрядную адресацию к данным.
Код:
mov al, #0xD1 ; команда записи для 8042
out #0x64, al
mov al, #0xDF ; включить A20
out #0x60, al
Выведем предупреждающее сообщение, о том, что переходим в защищенный режим. Пусть все знают, какие мы важные.
Код:
protected_mode:
mov bp,#loadp_msg
mov cx,#25
call write_message
(Сообщение:
loadp_msg:
.byte 13,10
.ascii "Go to protected mode..."
.byte 0
)
Пока еще у нас жив BIOS, запомним позицию курсора и сохраним ее в известном месте ( 0000:0x8000 ). Ядро позже заберет все данные и будет их использовать для вывода на экран победного сообщения.
Код:
save_cursor:
mov ah,#0x03 ; читаем текущую позицию курсора
xor bh,bh
int 0x10
seg cs
mov [0x8000],dx ;сохраняем в специальном тайнике
Теперь внимание, запрещаем прерывания (нечего отвлекаться во время такой работы) и загружаем таблицу дескрипторов
Код:
cli
lgdt GDT_DESCRIPTOR ; загружаем описатель таблицы
дескрипторов.
У нас таблица дескрипторов состоит из трех описателей: Нулевой (всегда должен присутствовать), сегмента кода и сегмента данных
Код:
.align 4
.word 0
GDT_DESCRIPTOR: .word 3 * 8 - 1 ; размер таблицы
дескрипторов
.long 0x600 + GDT ; местоположение
таблицы дескрипторов
.align 2
GDT:
.long 0, 0 ; Номер 0: пустой
дескриптор
.word 0xFFFF, 0 ; Номер 8:
дескриптор кода
.byte 0, CODE_ARB, 0xC0, 0
.word 0xFFFF, 0 ; Номер 0x10:
дескриптор данных
.byte 0, DATA_ARB, 0xCF, 0
Переход в защищенный режим может происходить минимум двумя способами, но обе ОС , выбранные нами для примера (Linux и Thix) используют для совместимости с 286 процессором команду lmsw. Мы будем действовать тем же способом
Код:
mov ax, #1
lmsw ax ; прощай реальный режим. Мы теперь
находимся в защищенном режиме.
jmpi 0x1000, 8 ; Затяжной прыжок на 32-разрядное ядро.
Вот и вся работа загрузочного сектора - немало, но и немного. Теперь мы попрощаемся с ним и направимся к ядру.
В конце ассемблерного файла полезно добавить следующую инструкцию.
Код:
.org 511
end_boot: .byte 0
В результате скомпилированный код будет занимать ровно 512 байт, что очень удобно для подготовки образа загрузочного диска.
3. Первые вздохи ядра (head.S)
Ядро к сожалению опять начнется с ассемблерного кода. Но теперь его будет совсем немного.
Мы собственно зададим правильные значения сегментов для данных (ES, DS, FS, GS). Записав туда значение соответствующего дескриптора данных.
Код:
cld
cli
movl $(__KERNEL_DS),%eax
movl %ax,%ds
movl %ax,%es
movl %ax,%fs
movl %ax,%gs
Проверим, нормально ли включилась адресная линия A20 простым тестом записи. Обнулим для чистоты эксперимента регистр флагов.
Код:
xorl %eax,%eax
1: incl %eax
movl %eax,0x000000
cmpl %eax,0x100000
je 1b
pushl $0
popfl
Вызовем долгожданную функцию, уже написанную на С.
Код:
call SYMBOL_NAME(start_my_kernel)
И больше нам тут делать нечего.
4. Поговорим на языке высокого уровня (start.c)
Вот теперь мы вернулись к тому с чего начинали рассказ. Почти вернулись, потому что printf() теперь надо делать вручную. поскольку готовых прерываний уже нет, то будем использовать прямую запись в видеопамять. Для любопытных - почти весь код этой части , с незначительными изменениями, повзаимствован из части ядра Linux, осуществляющей распаковку (/arch/i386/boot/compressed/*). Для сборки вам потребуется дополнительно определить такие макросы как inb(), outb(), inb_p(), outb_p(). Готовые определения проще всего одолжить из любой версии Linux.
Теперь, дабы не путаться со встроенными в glibc функциями, отменим их определение
Зададим несколько своих
Код:
static void puts(const char *);
static char *vidmem = (char *)0xb8000; /*адрес видеопамати*/
static int vidport; /*видеопорт*/
static int lines, cols; /*количество линий и строк на экран*/
static int curr_x,curr_y; /*текущее положение курсора */
И начнем, наконец, писать код на языке высокого уровня... правда с небольшими ассемблерными вставками.
Код:
/*функция перевода курсора в положение (x,y). Работа ведется через ввод/вывод в видеопорт*/
void gotoxy(int x, int y)
{
int pos;
pos = (x + cols * y) * 2;
outb_p(14, vidport);
outb_p(0xff & (pos >> 9), vidport+1);
outb_p(15, vidport);
outb_p(0xff & (pos >> 1), vidport+1);
}
/*функция прокручивания экрана. Работает, используя прямую запись в видеопамять*/
static void scroll()
{
int i;
memcpy ( vidmem, vidmem + cols * 2, ( lines - 1 ) * cols * 2 );
for ( i = ( lines - 1 ) * cols * 2; i < lines * cols * 2; i += 2 )
vidmem[i] = ' ';
}
/*функция вывода строки на экран*/
static void puts(const char *s)
{
int x,y;
char c;
x = curr_x;
y = curr_y;
while ( ( c = *s++ ) != '\0' ) {
if ( c == '\n' ) {
x = 0;
if ( ++y >= lines ) {
scroll();
y--;
}
} else {
vidmem [ ( x + cols * y ) * 2 ] = c;
if ( ++x >= cols ) {
x = 0;
if ( ++y >= lines ) {
scroll();
y--;
}
}
}
}
gotoxy(x,y);
}
/*функция копирования из одной области памяти в другую. Заместитель стандартной функции glibc */
void* memcpy(void* __dest, __const void* __src,
unsigned int __n)
{
int i;
char *d = (char *)__dest, *s = (char *)__src;
for (i=0;i<__n;i++) d[i] = s[i];
}
/*функция издающая долгий и протяжных звук. Использует только ввод/вывод в порты поэтому очень полезна для отладки*/
make_sound()
{
__asm__("
movb $0xB6, %al\n\t
outb %al, $0x43\n\t
movb $0x0D, %al\n\t
outb %al, $0x42\n\t
movb $0x11, %al\n\t
outb %al, $0x42\n\t
inb $0x61, %al\n\t
orb $3, %al\n\t
outb %al, $0x61\n\t
");
}
/*А вот и основная функция*/
int start_my_kernel()
{
/*задаются основные параметры */
vidmem = (char *) 0xb8000;
vidport = 0x3d4;
lines = 25;
cols = 80;
/*считывается предусмотрительно сохраненные координаты курсора*/
curr_x=*(unsigned char *)(0x8000);
curr_y=*(unsigned char *)(0x8001);
/*выводится строка*/
puts("done\n");
/*уходим в бесконечный цикл*/
while(1);
}
Вот и вывели мы этот "Hello World" на экран. Сколько проделано работы, а на экране только две строчки
Booting data ...done
Go to proteсted mode ...done
Немного, но и немало. Закричала новая операционная система. Мир с радостью воспринял ее. Кто знает, может быть это новый Linux ...
5. Подготовка загрузочного образа (floppy.img)
Итак, подготовим загрузочный образ нашей системки.
Для начала соберем загрузочный сектор.
as86 -0 -a -o boot.o boot.S
ld86 -0 -s -o boot.img boot.o
Обрежем 32 битный заголовок и получим таким образом чистый двоичный код.
dd if=boot.img of=boot.bin bs=32 skip=1
Соберем ядро
gcc -traditional -c head.S -o head.o
gcc -O2 -DSTDC_HEADERS -c start.c
При компоновке НЕ ЗАБУДБЬТЕ параметр "-T" он указывает относительно которого смещения вести расчеты, в нашем случае поскольку ядро грузится по адресy 0x1000, то и смещение соотетствующее
ld -m elf_i386 -Ttext 0x1000 -e startup_32 head.o start.o -o head.img
Очистим зерна от плевел, то есть чистый двоичный код от всеческих служебных заголовков и комментариев
objcopy -O binary -R .note -R .comment -S head.img head.bin
И соединяем воедино загрузочный сектор и ядро
cat boot.bin head.bin >floppy.img
Образ готов. Записываем на дискетку (заготовьте несколько для экспериментов, я прикончил три штуки) перезагружаем компьютер и наслаждаемся.
cat floppy.img >/dev/fd0
6. Е-мое, что ж я сделал (...)
Здорово, правда? Приятно почувствовать себя будущим Торвальдсом или кем-то еще. Красная линия намечена, можно смело идти вперед, дописывать и переписывать систему. Описанная процедура пока что едина для множества операционных систем, будь то UNIX или Windows. Что напишете Вы? ... не знает не кто. Ведь это будет Ваша система.
Stanislav Ievlev, linux.ru.net
|
|
|

29.04.2008, 19:51
|
|
Banned
Регистрация: 06.01.2008
Сообщений: 904
Провел на форуме: 4037638
Репутация:
1821
|
|
Сообщение от z01b
(C) Dron, 2001 г.
Статью я нашел на просторах народа и побоялся, как-бы она не потерялась. Решил выложить ее здесь. Над оформлением буду еще работать.
Статья не моя, но всетаки предлагаю супер-модератору, закрепить ее как важное.
к сожалению, это было написано ранее и на специализированных форумах
Для новичков, да и не только будет полезно....
|
|
|

30.04.2008, 21:00
|
|
Постоянный
Регистрация: 16.04.2007
Сообщений: 398
Провел на форуме: 3371897
Репутация:
1462
|
|
|
|
|
|
 |
|
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|