В первой и второй статьях я лишь коротко представил процесс написания загрузчика на ассемблере и C. Для меня это было хоть и непросто, но в то же время интересно, так что я остался доволен. Однако создания загрузчика мне показалось мало, и я увлекся идеей его расширения дополнительной функциональностью. Но так как в итоге размер готовой программы превысил 512 байт, то при попытке запуска системы с несущего ее загрузочного диска я столкнулся с проблемой “This is not a bootable disk”.
О чем эта статья?
В ней мы рассмотрим значимость файловой системы для загрузчика, а также напишем макет ядра, которое будет просто отображать командную строку, позволяя ввод текста. При этом я поясню, почему внедряю загрузчик в дискету, отформатированную в FAT. И поскольку одной статьи для полноценного раскрытия темы файловых систем однозначно недостаточно я постараюсь придерживаться краткости и максимальной простоты изложения.
Нужен ли для этого опыт?
Статья окажется намного полезнее тем, у кого уже есть опыт программирования. Несмотря на ее чисто ознакомительный, казалось бы, характер, написание программ на ассемблере и C может оказаться достаточно сложным. Поэтому новичкам я рекомендую для начала ознакомиться с вводными принципами программирования и уже потом возвращаться к этому материалу.
Как и ранее, процесс изложения построен по принципу вопрос-ответ, что должно упростить восприятие информации.
План статьи
- Ограничения загрузчика
- Вызов из загрузчика других файлов диска
- Файловая система FAT
- Принцип работы FAT
- Среда разработки
- Написание загрузчика для FAT
- Мини-проект: написание 16-битного ядра
- Тестирование ядра
Ограничения загрузчика
В предыдущих статьях я написал загрузчик и, реализовав с его помощью вывод на экран цветных прямоугольников, захотел добавить дополнительную функциональность. Однако при этом я столкнулся с ограничением размера сектора в 512 байт, что не позволило добиться желаемого стандартным путем.
В итоге передо мной стоит две задачи:
- Расширить загрузчик кодом, реализующим дополнительную функциональность.
- Сохранить при этом размер загрузчика в 512 байт.
Как я буду это делать?
Этап 1:
- Напишу программу kernel.c на C, внедрив в нее всю необходимую функциональность.
- Скомпилирую и сохраню исполняемый файл как kernel.bin.
- Скопирую этот файл во второй сектор загрузочного диска.
Этап 2:
В загрузчике мы можем просто загрузить второй сектор, содержащий kernel.bin, в RAM по адресу, к примеру,
0x1000
, а затем перейти к этому адресу из 0x7с00
и запустить kernel.bin.Вот схема для лучшего понимания идеи:
Запуск из загрузчика других файлов диска
Как мы теперь знаем, у нас есть возможность передачи управления от загрузчика (
0x7c00
) в другую область памяти, где размещается, например, наш kernel.bin, после чего продолжить выполнение. Но здесь у меня я хочу кое-что уточнить.Как узнать сколько секторов kernel.bin займет на диске?
Ну это простой вопрос. Для ответа на него нам достаточно выполнить несложную арифметику, а именно разделить размер kernel.bin на размер сектора, который составляет 512 байт. Например, если kernel.bin будет равен 1024 байта, то и займет он 2 сектора.
В связи с этим вам потребуется захардкодить в загрузчике необходимое для ядра количество секторов, и при возможном изменении его размера в дальнейшем не забывать о внесении соответствующих правок. В противном случае при несоответствии прописанного и фактического размеров загрузка ядра будет проваливаться.
Можно ли добавить помимо kernel.bin другие файлы, например office.bin, entertainment.bin, drivers.bin?
В принципе да. Для этого нужно будет поочередно добавить требуемые файлы на дискету, не забывая при этом обновлять загрузчик необходимой информацией об их расположении, занимаемом количестве секторов и прочем. Но тут я отдельно скажу, что в итоге сложность системы сильно увеличивается, и в конечном счете мне такой вариант не очень понравился.
Откуда мы знаем, что после загрузочного сектора выполняются именно желаемые файлы?
Хороший вопрос, так как мы, действительно, только загружаем соответствующий сектор в память и начинаем его выполнение. Это не идеальный вариант, и в нем кое-чего не хватает.
Чего не хватает?
Загрузчик вслепую загружает сектора каждого файла один за другим и затем начинает выполнение этих файлов. Но даже до того, как он попробует загрузить файлы в память, должен присутствовать способ проверить, существуют ли они вообще на диске.
Что произойдет, если по ошибке загрузить во второй сектор не тот файл, обновить загрузчик и начать выполнение?
В этом случае система просто дает сбой. Поэтому на диске должна присутствовать фиксированная область, где подобно индексу в книге будут прописаны все имена файлов.
Тогда загрузчик будет запрашивать индекс этого файла на диске, и в случае обнаружения таковой будет обнаружен в списке – продолжать загрузку этого файла в память.
Мне такой вариант очень нравится, так как он избавляет от лишних действий.
Здесь мы избегаем нескольких проблем. До этого загрузчик вслепую загружал жестко закодированные сектора. Но зачем загружать файл, не будучи уверенным в том, что это именно нужный файл, и что он вообще существует?
Как это решается?
Для этого нужно просто организовать информацию на диске, после чего перепрограммировать загрузчик, повысив тем самым его эффективность при загрузке файлов.
Такой вид организации информации называется файловой системой. Существует много типов файловых систем, среди которых есть как коммерческие, так и бесплатные. Вот некоторые из них:
- FAT
- FAT16
- FAT32
- NTFS
- EXT
- EXT2
- EXT3
- EXT4
FAT
Для лучшего понимания этой файловой системы вам потребуется знать некоторые технические нюансы.
Минимальной единицей измерения пространства FAT является кластер, который занимает 1 сектор накопителя. Иначе говоря, на дискете, отформатированной в FAT, 1 кластер эквивалентен 1 сектору и, соответственно, равен 512 байт.
Для удобства использования файловая система дополнительно разделяется на четыре основные области:
- загрузочный сектор (boot sector);
- таблицу размещения файлов (file allocation table);
- корневой каталог (root directory);
- область данных (data area).
Я постарался максимально понятно изобразить эту структуру в виде схемы:
Рассмотрим каждую часть подробнее.
Загрузочный сектор
Загрузочный сектор содержит служебную информацию, на основе которой ОС распознает тип файловой системы диска, после чего уже переходит к чтению его содержимого.
Информация о файловой системе FAT, содержащаяся в загрузочном секторе, называется блоком параметров BIOS.
Блок параметров BIOS
Ниже я привел пример значений из этого блока:
Таблица размещения файлов
Эта таблица представляет своего рода связанный список, в котором каждая ячейка, соответствующая кластеру файла, содержит значение следующего кластера, формируя таким образом связанную цепочку всех кластеров этого файла.
Это значение кластера служит для:
- определения окончания файла. Если оно находится между
0x0ff8
и0x0fff
, значит файл не содержит данных в других секторах, т.е. достигнут его конец. - определения следующего кластера с данными этого файла.
К сведению: на схеме выше я отметил две таблицы FAT. Вторая является резервной копией первой и используется в случае ее повреждения.
Корневой каталог
Корневой каталог выступает в роли индекса всех находящихся на диске файлов. Именно здесь загрузчик ищет имя нужного файла и в случае обнаружения обращается к его первому кластеру для начала загрузки.
После обнаружения и начала считывания первого кластера загрузчик с помощью FAT находит все последующие занимаемые файлом кластеры.
Область данных
Здесь содержаться фактические данные файлов.
Как только программа обнаруживает искомый сектор файла, именно из этой области извлекаются соответствующие данные.
Принцип работы FAT
Продолжим наш пример с kernel.bin, который загрузчик помещает в память для выполнения. Теперь в этом сценарии нужно прописать для загрузчика следующую функциональность:
- Сравнить первые 11 байт данных с kernel.bin, начиная со смещения 0 в таблице корневого каталога.
- В случае совпадения этой строки – извлечь первый кластер kernel.bin из смещения 26 корневого каталога.
- Далее преобразовать этот кластер в соответствующий сектор и загрузить его данные в память.
- После загрузки первого сектора в память перейти к поиску в FAT следующего кластера файла и определить, является он последним, или есть еще данные в других кластерах.
Ниже я привел очередную схему.
Среда разработки
Для успешной реализации этой задачи нам нужно иметь представление о следующем. Более подробную информацию по этим пунктам можете найти в двух предыдущих статьях.
- Операционная система (GNU Linux).
- Ассемблер (GNU Assembler).
- Набор инструкций (x86).
- Написание инструкций для микропроцессора x86 на GNU Assembler.
- Компилятор (GNU C компилятор GCC).
- Компоновщик (GNU linker ld)
- Эмулятор, например bochs, используемый для тестирования.
Написание загрузчика FAT
Ниже я привожу фрагмент кода для выполнения файла kernel.bin на FAT-диске.
Вот загрузчик.
Файл: stage0.S
/*********************************************************************************
* *
* *
* Name : stage0.S *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : assembly language *
* Author : Ashakiran Bhatter *
* *
* Описание: основная логика подразумевает сканирование файла kernel.bin *
* на дискете fat12 и передачу этому файлу права *
* выполнения. *
* Использование: подробности в файле readme.txt *
* *
* *
*********************************************************************************/
.code16
.text
.globl _start;
_start:
jmp _boot
nop
/*блок параметров BIOS описание каждой сущности */
/*-------------------- -------------------------- */
.byte 0x6b,0x69,0x72,0x55,0x58,0x30,0x2e,0x31 /* метка OEM */
.byte 0x00,0x02 /* байтов в секторе */
.byte 0x01 /* секторов в кластере */
.byte 0x01,0x00 /* зарезервированных секторов */
.byte 0x02 /* таблиц fat */
.byte 0xe0,0x00 /* записей в каталоге */
.byte 0x40,0x0b /* всего секторов */
.byte 0xf0 /* описание среды передачи */
.byte 0x09,0x00 /* размер в каждой таблице fat */
.byte 0x02,0x01 /* секторов в дорожке */
.byte 0x02,0x00 /* головок на цилиндр */
.byte 0x00,0x00, 0x00, 0x00 /* скрытых секторов */
.byte 0x00,0x00, 0x00, 0x00 /* больших секторов */
.byte 0x00 /* идентификатор загрузочного диска*/
.byte 0x00 /* неиспользуемых секторов */
.byte 0x29 /* внешняя сигнатура загрузки */
.byte 0x22,0x62,0x79,0x20 /* серийный номер */
.byte 0x41,0x53,0x48,0x41,0x4b,0x49 /* метка тома 6 байт из 11 */
.byte 0x52,0x41,0x4e,0x20,0x42 /* метка тома 5 байт из 11 */
.byte 0x48,0x41,0x54,0x54,0x45,0x52,0x22 /* тип файловой системы */
/* включение макросов */
#include "macros.S"
/* начало основного кода */
_boot:
/* инициализация среды */
initEnvironment
/* загрузка stage2 */
loadFile $fileStage2
/* бесконечный цикл */
_freeze:
jmp _freeze
/* непредвиденное завершение программы */
_abort:
writeString $msgAbort
jmp _freeze
/* включение функций */
#include "routines.S"
/* пользовательские переменные */
bootDrive : .byte 0x0000
msgAbort : .asciz "* * * F A T A L E R R O R * * *"
#fileStage2: .ascii "STAGE2 BIN"
fileStage2: .ascii "KERNEL BIN"
clusterID : .word 0x0000
/* перемещение от начала к 510-му байту */
. = _start + 0x01fe
/* добавление сигнатуры загрузки */
.word BOOT_SIGNATURE
В этом основном файле загрузки происходит:
- Инициализация всех регистров и настройка стека вызовом макроса
initEnvironment
. - Вызов макроса
loadFile
для загрузки kernel.bin в память по адресу0x1000:0000
и последующей передачи ему права выполнения.
Файл: macros.S
Этот файл содержит все предопределенные макросы и функции.
/********************************************************************************* * *
* *
* Name : macros.S *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : assembly language *
* Author : Ashakiran Bhatter *
* *
* *
*********************************************************************************/
/* предопределенный макрос: загрузчик */
#define BOOT_LOADER_CODE_AREA_ADDRESS 0x7c00
#define BOOT_LOADER_CODE_AREA_ADDRESS_OFFSET 0x0000
/* предопределенный макрос: сегмент стека */
#define BOOT_LOADER_STACK_SEGMENT 0x7c00
#define BOOT_LOADER_ROOT_OFFSET 0x0200
#define BOOT_LOADER_FAT_OFFSET 0x0200
#define BOOT_LOADER_STAGE2_ADDRESS 0x1000
#define BOOT_LOADER_STAGE2_OFFSET 0x0000
/* предопределенный макрос: разметка дискеты */
#define BOOT_DISK_SECTORS_PER_TRACK 0x0012
#define BOOT_DISK_HEADS_PER_CYLINDER 0x0002
#define BOOT_DISK_BYTES_PER_SECTOR 0x0200
#define BOOT_DISK_SECTORS_PER_CLUSTER 0x0001
/* предопределенный макрос: разметка файловой системы */
#define FAT12_FAT_POSITION 0x0001
#define FAT12_FAT_SIZE 0x0009
#define FAT12_ROOT_POSITION 0x0013
#define FAT12_ROOT_SIZE 0x000e
#define FAT12_ROOT_ENTRIES 0x00e0
#define FAT12_END_OF_FILE 0x0ff8
/* предопределенный макрос: загрузчик */
#define BOOT_SIGNATURE 0xaa55
/* пользовательские макросы */
/* макрос для установки среды */
.macro initEnvironment
call _initEnvironment
.endm
/* макрос для отображения строки на экране. */
/* Для выполнения этой операции он вызывает функцию _writeString */
/* параметр: вводная строка */
.macro writeString message
pushw \message
call _writeString
.endm
/* макрос для считывания сектора в памяти */
/* Вызывает функцию _readSector со следующими параметрами */
/* параметры: номер сектора */
/* адрес загрузки */
/* смещение адреса */
/* количество считываемых секторов */
.macro readSector sectorno, address, offset, totalsectors
pushw \sectorno
pushw \address
pushw \offset
pushw \totalsectors
call _readSector
addw $0x0008, %sp
.endm
/* макрос для поиска файла на FAT-диске. */
/* Для этого он вызывает макрос readSector */
/* параметры: адрес корневого каталога */
/* целевой адрес */
/* целевое смещение */
/* размер корневого каталога */
.macro findFile file
/* считывание таблицы FAT в память */
readSector $FAT12_ROOT_POSITION, $BOOT_LOADER_CODE_AREA_ADDRESS, $BOOT_LOADER_ROOT_OFFSET, $FAT12_ROOT_SIZE
pushw \file
call _findFile
addw $0x0002, %sp
.endm
/* макрос для преобразования заданного кластера в номер сектора */
/* Для этого он вызывает _clusterToLinearBlockAddress */
/* параметр: номер кластера */
.macro clusterToLinearBlockAddress cluster
pushw \cluster
call _clusterToLinearBlockAddress
addw $0x0002, %sp
.endm
/* макрос для загрузки целевого файла в память. */
/* Он вызывает findFile и загружает данные соответствующего файла в память */
/* по адресу 0x1000:0x0000 */
/* параметр: имя целевого файла */
.macro loadFile file
/* проверка наличия файла */
findFile \file
pushw %ax
/* считывание таблицы FAT в память */
readSector $FAT12_FAT_POSITION, $BOOT_LOADER_CODE_AREA_ADDRESS, $BOOT_LOADER_FAT_OFFSET, $FAT12_FAT_SIZE
popw %ax
movw $BOOT_LOADER_STAGE2_OFFSET, %bx
_loadCluster:
pushw %bx
pushw %ax
clusterToLinearBlockAddress %ax
readSector %ax, $BOOT_LOADER_STAGE2_ADDRESS, %bx, $BOOT_DISK_SECTORS_PER_CLUSTER
popw %ax
xorw %dx, %dx
movw $0x0003, %bx
mulw %bx
movw $0x0002, %bx
divw %bx
movw $BOOT_LOADER_FAT_OFFSET, %bx
addw %ax, %bx
movw $BOOT_LOADER_CODE_AREA_ADDRESS, %ax
movw %ax, %es
movw %es:(%bx), %ax
orw %dx, %dx
jz _even_cluster
_odd_cluster:
shrw $0x0004, %ax
jmp _done
_even_cluster:
and $0x0fff, %ax
_done:
popw %bx
addw $BOOT_DISK_BYTES_PER_SECTOR, %bx
cmpw $FAT12_END_OF_FILE, %ax
jl _loadCluster
/* выполнение ядра */
initKernel
.endm
/* параметры: имя целевого файла */
/* макрос для передачи права выполнения файлу, загруженному */
/* в память по адресу 0x1000:0x0000 */
/* параметры: none */
.macro initKernel
/* инициализация ядра */
movw $(BOOT_LOADER_STAGE2_ADDRESS), %ax
movw $(BOOT_LOADER_STAGE2_OFFSET) , %bx
movw %ax, %es
movw %ax, %ds
jmp $(BOOT_LOADER_STAGE2_ADDRESS), $(BOOT_LOADER_STAGE2_OFFSET)
.endm
Общая сводка
initEnvironment:
- Макрос для установки сегментных регистров.
- Аргументов не требует.
Применение:
initEnvironment
writeString:
- Макрос для отображения на экране строки с завершающим нулем.
- В качестве аргумента передается строковая переменная с завершающим нулем.
Применение:
writeString <строковая переменная>
readSector:
- Макрос для чтения с диска заданного сектора и его загрузки в целевой адрес памяти.
- Количество аргументов: 4.
Применение:
readSector <номер сектора>, <целевой адрес>, <смещение целевого адреса>, <количество считываемых секторов>
findFile:
- Макрос для проверки наличия файла.
- Количество аргументов: 1.
Применение:
findFile <имя целевого файла>
clusterToLinearBlockAddress:
- Макрос для преобразования заданного кластера в номер сектора.
- Количество аргументов: 1.
Применение:
clusterToLinearBlockAddress <ID кластера>
loadFile:
- Макрос для загрузки целевого файла в память с последующей передачей ему права выполнения.
- Количество аргументов: 1.
Применение:
loadFile <имя целевого файла>
initKernel:
- Макрос для передачи права выполнения конкретному адресу памяти в RAM.
- Аргументов не требует.
Применение:
initKernel
Файл: routines.S
/*********************************************************************************
* *
* *
* Name : routines.S *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : assembly language *
* Author : Ashakiran Bhatter *
* *
* *
*********************************************************************************/
/* Пользовательские подпрограммы. */
/* функция для настройки регистров и стека */
/* параметры: none */
_initEnvironment:
pushw %bp
movw %sp, %bp
_initEnvironmentIn:
cli
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw $BOOT_LOADER_STACK_SEGMENT, %sp
sti
_initEnvironmentOut:
movw %bp, %sp
popw %bp
ret
/* функция для отображения строки на экране */
/* параметр: вводная строка */
_writeString:
pushw %bp
movw %sp , %bp
movw 4(%bp) , %si
jmp _writeStringCheckByte
_writeStringIn:
movb $0x000e, %ah
movb $0x0000, %bh
int $0x0010
incw %si
_writeStringCheckByte:
movb (%si) , %al
orb %al , %al
jnz _writeStringIn
_writeStringOut:
movw %bp , %sp
popw %bp
ret
/* функция для считывания сектора в целевой адрес памяти */
/* параметры: номер сектора */
/* целевой адрес */
/* смещение адреса */
/* количество считываемых секторов */
_readSector:
pushw %bp
movw %sp , %bp
movw 10(%bp), %ax
movw $BOOT_DISK_SECTORS_PER_TRACK, %bx
xorw %dx , %dx
divw %bx
incw %dx
movb %dl , %cl
movw $BOOT_DISK_HEADS_PER_CYLINDER, %bx
xorw %dx , %dx
divw %bx
movb %al , %ch
xchg %dl , %dh
movb $0x02 , %ah
movb 4(%bp) , %al
movb bootDrive, %dl
movw 8(%bp) , %bx
movw %bx , %es
movw 6(%bp) , %bx
int $0x13
jc _abort
cmpb 4(%bp) , %al
jc _abort
movw %bp , %sp
popw %bp
ret
/* функция поиска файла на дискете */
/* параметры: адрес корневого каталога */
/* целевой адрес */
/* целевое смещение */
/* размер корневого каталога */
_findFile:
pushw %bp
movw %sp , %bp
movw $BOOT_LOADER_CODE_AREA_ADDRESS, %ax
movw %ax , %es
movw $BOOT_LOADER_ROOT_OFFSET, %bx
movw $FAT12_ROOT_ENTRIES, %dx
jmp _findFileInitValues
_findFileIn:
movw $0x000b , %cx
movw 4(%bp) , %si
leaw (%bx) , %di
repe cmpsb
je _findFileOut
_findFileDecrementCount:
decw %dx
addw $0x0020, %bx
_findFileInitValues:
cmpw $0x0000, %dx
jne _findFileIn
je _abort
_findFileOut:
addw $0x001a , %bx
movw %es:(%bx), %ax
movw %bp, %sp
popw %bp
ret
/* функция для преобразования заданного кластера в номер сектора */
/* параметры: номер кластера */
_clusterToLinearBlockAddress:
pushw %bp
movw %sp , %bp
movw 4(%bp) , %ax
_clusterToLinearBlockAddressIn:
subw $0x0002, %ax
movw $BOOT_DISK_SECTORS_PER_CLUSTER, %cx
mulw %cx
addw $FAT12_ROOT_POSITION, %ax
addw $FAT12_ROOT_SIZE, %ax
_clusterToLinearBlockAddressOut:
movw %bp , %sp
popw %bp
ret
Общая сводка
_initEnvironment:
- Функция, отвечающая за установку сегментных регистров.
- Аргументов не требует.
Применение:
call _initEnvironment
_writeString:
- Функция для отображения на экране строки с завершающим нулем.
- В качестве аргумента получает строковую переменную с завершающим нулем.
Применение:
pushw <строковая переменная>
call _writeString
addw $0x02, %sp
readSector:
- Макрос для считывания заданного сектора с диска и его загрузки в целевой адрес памяти.
- Количество аргументов: 4.
Применение:
pushw <номер сектора>
pushw <адрес>
pushw <смещение>
pushw <всего секторов>
call _readSector
addw $0x0008, %sp
findFile:
- Функция для проверки наличия файла.
- Количество аргументов: 1
Применение:
pushw <target file variable>
call _findFile
addw $0x02, %sp
clusterToLinearBlockAddress:
- Макрос для преобразования ID заданного кластера в номер сектора.
- Количество аргументов: 1
Применение:
pushw <ID кластера>
call _clusterToLinearBlockAddress
addw $0x02, %sp
loadFile:
- Макрос для загрузки целевого файла в память с последующей передачей ему права выполнения.
- Количество аргументов: 1
Применение:
pushw <целевой файл>
call _loadFile
addw $0x02, %sp
Файл: stage0.ld
Этот файл служит для линковки файла
stage0.object
./*********************************************************************************
* *
* *
* Name : stage0.ld *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : assembly language *
* Author : Ashakiran Bhatter *
* *
* *
*********************************************************************************/
SECTIONS
{
. = 0x7c00;
.text :
{
_ftext = .;
} = 0
}
Файл: bochsrc.txt
Файл-конфигурации, необходимый для запуска эмулятора bochs.
megs: 32
floppya: 1_44=../iso/stage0.img, status=inserted
boot: a
log: ../log/bochsout.txt
mouse: enabled=0
Мини-проект: написание 16-битного ядра
Ниже приведен исходный код макета ядра, используемого как часть процесса тестирования. Нам нужно только скомпилировать этот код, используя
make file
и проверить, загрузит ли его загрузчик.Сначала будет отображаться заставка с головой дракона, сопровождаемая экраном приветствия, после которого откроется командная строка.
Учтите, что здесь не запрограммировано никаких команд или утилит для выполнения. Это просто макет ядра, заготовленный исключительно в целях тестирования.
Файл: kernel.c
/*********************************************************************************
* *
* *
* Name : kernel.c *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : C *
* Author : Ashakiran Bhatter *
* *
* Описание: За загрузку этого файла отвечает stage0.bin, который передает *
* ему право выполнения. Его функциональность *
* заключается в отображении экрана-заставки и командной строки. *
* Внимание : Вводить команды бессмысленно, так как они не запрограммированы*
* *
*********************************************************************************/
/* генерирует 16-битный код */
__asm__(".code16\n");
/* переход к основной функции */
__asm__("jmpl $0x1000, $main\n");
#define TRUE 0x01
#define FALSE 0x00
char str[] = "$> ";
/* функция установки регистров и стека */
/* параметры: none */
void initEnvironment() {
__asm__ __volatile__(
"cli;"
"movw $0x0000, %ax;"
"movw %ax, %ss;"
"movw $0xffff, %sp;"
"cld;"
);
__asm__ __volatile__(
"movw $0x1000, %ax;"
"movw %ax, %ds;"
"movw %ax, %es;"
"movw %ax, %fs;"
"movw %ax, %gs;"
);
}
/* VGA-функции. */
/* функция для установки режима VGA на 80*24 */
void setResolution() {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0003)
);
}
/* функция очистки буфера экрана разделяющими пробелами */
void clearScreen() {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0200), "b"(0x0000), "d"(0x0000)
);
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0920), "b"(0x0007), "c"(0x2000)
);
}
/* функция установки позиции курсора на заданный столбец и строку */
void setCursor(short col, short row) {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0200), "d"((row <<= 8) | col)
);
}
/* функция включения и отключения курсора */
void showCursor(short choice) {
if(choice == FALSE) {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0100), "c"(0x3200)
);
} else {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0100), "c"(0x0007)
);
}
}
/* функция инициализации режима VGA на 80*25, */
/* очистки экрана и установки положения курсора на (0,0) */
void initVGA() {
setResolution();
clearScreen();
setCursor(0, 0);
}
/* I/O-функции. */
/* функция для получения символа с клавиатуры без эха*/
void getch() {
__asm__ __volatile__ (
"xorw %ax, %ax\n"
"int $0x16\n"
);
}
/* эта функция аналогична getch(), */
/* но возвращает скан-код клавиши и соответствующее значение ascii */
short getchar() {
short word;
__asm__ __volatile__(
"int $0x16" : : "a"(0x1000)
);
__asm__ __volatile__(
"movw %%ax, %0" : "=r"(word)
);
return word;
}
/* функция для отображения нажатых клавиш на экране*/
void putchar(short ch) {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0e00 | (char)ch)
);
}
/* функция вывода на экран строки с завершающим нулем */
void printString(const char* pStr) {
while(*pStr) {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0e00 | *pStr), "b"(0x0002)
);
++pStr;
}
}
/* функция, вызывающая задержку на несколько секунд */
void delay(int seconds) {
__asm__ __volatile__(
"int $0x15" : : "a"(0x8600), "c"(0x000f * seconds), "d"(0x4240 * seconds)
);
}
/* Строковая функция. */
/* эта функция вычисляет длину строки и возвращает ее */
int strlength(const char* pStr) {
int i = 0;
while(*pStr) {
++i;
}
return i;
}
/* Функция UI. */
/*эта функция отображает логотип */
void splashScreen(const char* pStr) {
showCursor(FALSE);
clearScreen();
setCursor(0, 9);
printString(pStr);
delay(10);
}
/* Оболочка. */
/* функция для отображения фиктивной командной строки. */
/* При нажатии клавиши Ввод выполняется переход на следующую строку */
void shell() {
clearScreen();
showCursor(TRUE);
while(TRUE) {
printString(str);
short byte;
while((byte = getchar())) {
if((byte >> 8) == 0x1c) {
putchar(10);
putchar(13);
break;
} else {
putchar(byte);
}
}
}
}
/* точка входа в ядро */
void main() {
const char msgPicture[] =
" .. \n\r"
" ++` \n\r"
" :ho. `.-/++/. \n\r"
" `/hh+. ``:sds: \n\r"
" `-odds/-` .MNd/` \n\r"
" `.+ydmdyo/:--/yMMMMd/ \n\r"
" `:+hMMMNNNMMMddNMMh:` \n\r"
" `-:/+++/:-:ohmNMMMMMMMMMMMm+-+mMNd` \n\r"
" `-+oo+osdMMMNMMMMMMMMMMMMMMMMMMNmNMMM/` \n\r"
" ``` .+mMMMMMMMMMMMMMMMMMMMMMMMMMMMMNmho:.` \n\r"
" `omMMMMMMMMMMMMMMMMMMNMdydMMdNMMMMMMMMdo+- \n\r"
" .:oymMMMMMMMMMMMMMNdo/hMMd+ds-:h/-yMdydMNdNdNN+ \n\r"
" -oosdMMMMMMMMMMMMMMd:` `yMM+.+h+.- /y `/m.:mmmN \n\r"
" -:` dMMMMMMMMMMMMMd. `mMNo..+y/` . . -/.s \n\r"
" ` -MMMMMMMMMMMMMM- -mMMmo-./s/.` ` \n\r"
" `+MMMMMMMMMMMMMM- .smMy:.``-+oo+//:-.` \n\r"
" .yNMMMMMMMMMMMMMMd. .+dmh+:. `-::/+:. \n\r"
" y+-mMMMMMMMMMMMMMMm/` ./o+-` . \n\r"
" :- :MMMMMMMMMMMMMMMMmy/.` \n\r"
" ` `hMMMMMMMMMMMMMMMMMMNds/.` \n\r"
" sNhNMMMMMMMMMMMMMMMMMMMMNh+. \n\r"
" -d. :mMMMMMMMMMMMMMMMMMMMMMMNh:` \n\r"
" /. .hMMMMMMMMMMMMMMMMMMMMMMMMh. \n\r"
" . `sMMMMMMMMMMMMMMMMMMMMMMMMN. \n\r"
" hMMMMMMMMMMMMMMMMMMMMMMMMy \n\r"
" +MMMMMMMMMMMMMMMMMMMMMMMMh ";
const char msgWelcome[] =
" *******************************************************\n\r"
" * *\n\r"
" * Welcome to kirUX Operating System *\n\r"
" * *\n\r"
" *******************************************************\n\r"
" * *\n\r"
" * *\n\r"
" * Author : Ashakiran Bhatter *\n\r"
" * Version: 0.0.1 *\n\r"
" * Date : 01-Mar-2014 *\n\r"
" * *\n\r"
" ******************************************************";
initEnvironment();
initVGA();
splashScreen(msgPicture);
splashScreen(msgWelcome);
shell();
while(1);
}
Общая сводка
initEnvironment():
- Устанавливает сегментные регистры и формирует стек.
- Количество аргументов: none
Применение: initEnvironment();
setResolution():
- Устанавливает разрешение экрана 80*25.
- Количество аргументов: none.
Применение: setResolution();
clearScreen():
- Заполняет буфер экрана пробелами.
- Количество аргументов: none
Применение: clearScreen();
setCursor():
- Устанавливает курсор в заданное положение на экране.
- Количество аргументов: 2.
Применение: setCursor(столбец, строка);
showCursor():
- По желанию пользователя активирует или отключает курсор.
- Количество аргументов: 1.
Применение: showCursor(1);
initVGA():
- Устанавливает разрешение 80*25, очищает экран и устанавливает курсор в позицию (0,0).
- Количество аргументов: none
Применение: initVGA();
getch():
- Регистрирует нажатия клавиш без эха.
- Количество аргументов: none
Применение: getch();
getchar():
- Возвращает скан-код нажатой клавиши и соответствующее значение ascii.
- Количество аргументов: none.
Применение: getchar();
putchar():
- Отображает символы нажатых клавиш на экране.
- Количество аргументов: 1.
Применение: putchar(символ);
printString():
- Выводит на экран строку с завершающим нулем.
- Количество аргументов: 1.
Применение: printString();
delay():
- Вызывает задержку на несколько секунд.
- Количество аргументов: 1.
Применение: printString(строковая переменная с завершающим нулем);
strlength():
- Возвращает значение длины строки с завершающим нулем.
- Количество аргументов: 1.
Применение: strlength(строковая переменная с завершающим нулем);
splashScreen():
- Отображает заданную картинку определенное время.
- Количество аргументов: 1.
Применение: splashScreen(строковая переменная с завершающим нулем);
shell():
- Отображает командную строку.
- Количество аргументов: none.
Применение: shell();
Тестирование ядра
Использование исходного кода:
В прикрепленном архиве
sourcecode.tar.gz
находятся все исходные файлы и каталоги, необходимые для генерации исполняемых файлов.Убедитесь, что вы являетесь супер-пользователем системы, после чего распакуйте архив.
Для перехода к компиляции и тестированию кода установите эмулятор bochs-x64 и GNU bin-utils.
После извлечения файлов вы увидите 5 каталогов:
- bin
- iso
- kernel
- log
- src
Подготовив среду, откройте терминал и выполните следующие команды:
cd $(DIRECTORY)/src
make -f Makefile test
bochs
Сриншоты
Экран 1:
Это первый экран, отображаемый при выполнении ядра.
Экран 2:
Дальше идет экран приветствия:
Экран 3:
Это командная строка, в которой можно ввести текст.
Экран 4:
Здесь я привожу пример написания команд и перехода строки при нажатии Ввода.
Если у вас возникнут какие-либо сложности, смело пишите в комментариях (ссылка на оригинал публикации), буду рад помочь разобраться.
Заключение
Надеюсь, что эта статья помогла вам понять принцип работы файловой системы, а также освоить написание загрузчика для ее считывания. Теперь вы можете смело продолжать экспериментировать с внедрением в написанное нами ядро другой функциональности.
alexeymalov
Перепутаны байты с килобайтами.
Размер кластера составляет 512 байт, а не килобайт
Bright_Translate Автор
Да, спасибо за внимательность. Впредь будьте добры отправлять личным сообщением.