[Перевод] Пишем макет 16-битного ядра на C/C++

i5sj8et-ud2o21hmws-261nxxsy.png

В первой и второй статьях я лишь коротко представил процесс написания загрузчика на ассемблере и 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.

Вот схема для лучшего понимания идеи:

9ms5zetqen6cayirl6bmv7614nk.png

Запуск из загрузчика других файлов диска


Как мы теперь знаем, у нас есть возможность передачи управления от загрузчика (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).

Я постарался максимально понятно изобразить эту структуру в виде схемы:

jesgjo8i1m3c2hdvjcugzpjmx8m.png

Рассмотрим каждую часть подробнее.

Загрузочный сектор


Загрузочный сектор содержит служебную информацию, на основе которой ОС распознает тип файловой системы диска, после чего уже переходит к чтению его содержимого.

Информация о файловой системе FAT, содержащаяся в загрузочном секторе, называется блоком параметров BIOS.

Блок параметров BIOS


Ниже я привел пример значений из этого блока:
mtb-edquy0atubwr1qvjrov1fva.png

Таблица размещения файлов


Эта таблица представляет своего рода связанный список, в котором каждая ячейка, соответствующая кластеру файла, содержит значение следующего кластера, формируя таким образом связанную цепочку всех кластеров этого файла.

Это значение кластера служит для:
• определения окончания файла. Если оно находится между 0x0ff8 и 0x0fff, значит файл не содержит данных в других секторах, т.е. достигнут его конец.
• определения следующего кластера с данными этого файла.

К сведению: на схеме выше я отметил две таблицы FAT. Вторая является резервной копией первой и используется в случае ее повреждения.

Корневой каталог


Корневой каталог выступает в роли индекса всех находящихся на диске файлов. Именно здесь загрузчик ищет имя нужного файла и в случае обнаружения обращается к его первому кластеру для начала загрузки.

После обнаружения и начала считывания первого кластера загрузчик с помощью FAT находит все последующие занимаемые файлом кластеры.

Область данных


Здесь содержаться фактические данные файлов.
Как только программа обнаруживает искомый сектор файла, именно из этой области извлекаются соответствующие данные.

Принцип работы FAT


Продолжим наш пример с kernel.bin, который загрузчик помещает в память для выполнения. Теперь в этом сценарии нужно прописать для загрузчика следующую функциональность:

• Сравнить первые 11 байт данных с kernel.bin, начиная со смещения 0 в таблице корневого каталога.
• В случае совпадения этой строки — извлечь первый кластер kernel.bin из смещения 26 корневого каталога.
• Далее преобразовать этот кластер в соответствующий сектор и загрузить его данные в память.
• После загрузки первого сектора в память перейти к поиску в FAT следующего кластера файла и определить, является он последним, или есть еще данные в других кластерах.

Ниже я привел очередную схему.

xbfndboroljn5yvwgdrv3bwhr6e.png

Среда разработки


Для успешной реализации этой задачи нам нужно иметь представление о следующем. Более подробную информацию по этим пунктам можете найти в двух предыдущих статьях.

• Операционная система (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 

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 
call _findFile
addw $0x02, %sp 

clusterToLinearBlockAddress:
• Макрос для преобразования ID заданного кластера в номер сектора.
• Количество аргументов: 1

Применение:

pushw 
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:
Это первый экран, отображаемый при выполнении ядра.
i5sj8et-ud2o21hmws-261nxxsy.png

Экран 2:
Дальше идет экран приветствия:

qp6fkitrpbwifgjwgxttpbgwsw8.png

Экран 3:
Это командная строка, в которой можно ввести текст.

ow2zfjrk1avglhr3th1afz0qkzc.png

Экран 4:
Здесь я привожу пример написания команд и перехода строки при нажатии Ввода.

m918nsjwkuiiqytdscohli7tgx8.png

Если у вас возникнут какие-либо сложности, смело пишите в комментариях (ссылка на оригинал публикации), буду рад помочь разобраться.

Заключение


Надеюсь, что эта статья помогла вам понять принцип работы файловой системы, а также освоить написание загрузчика для ее считывания. Теперь вы можете смело продолжать экспериментировать с внедрением в написанное нами ядро другой функциональности.

oug5kh6sjydt9llengsiebnp40w.png

© Habrahabr.ru