Добрый день, уважаемые читатели. Я думаю настал тот момент, когда пора бы уже и перейти на уровень разработки повыше. Лучший помощник в этом язык программирования Си. Этот язык появился в 1972 году.
Кстати, есть одна несправедливость, автор языка Си, Деннис Ритчи, умер на третий день, после смерти Стива Джобса. О смерти основателя Apple не написал только ленивый. Легендарный разработчик же ушел тихо, будто никому не нужный. Так вот, это не так. Мы помним о тебе, Деннис! Надеюсь читатели поймут меня и не осудят за этот эмоциональный абзац.
Деннис Ритчи — создатель языка программирования Си
Так вот, Деннис Ритчи, работая в Bell Labs, разработал язык Си. Дизайн языка предполагал, что использоваться он будет в тех местах, где раньше использовался ассемблер. Более того, данный язык программирования разрабатывался специально для создания операционной системы Unix.
Исходник на Си
Что-же, начнем. Нужно создать новый файл с наименованием — kernel.с. Исходники на языке Си используют расширение файла — .c. Реализуем функцию вывода наименования и версии операционной системы. Язык программирования Си очень похож на ассемблер, поэтому мы, фактически, просто перепишем наш код, который я написал в статье: Ядро операционной системы — первые шаги, используя синтаксис Си.
/* * kernel.c */ void kmain(void) { const char *str = "Simple OS, version 0.0.1"; /* Помещаем в указатель vidptr адрес первого байта видеопамяти */ char *vidptr = (char*)0xb8000; unsigned int i = 0; unsigned int j = 0; /* Очистка экрана */ while(j < 80 * 25 * 2) { vidptr[j] = ' '; vidptr[j+1] = 0x07; j = j + 2; } /* Не забываем обнулять переменные, мы в ядре */ j = 0; /* Вывод наименования ОС и версии ядра */ while(str[j] != '\0') { vidptr[i] = str[j]; vidptr[i+1] = 0x07; /* Указатель на строку увеличиваем на 1, а указатель видеопамяти на 2 байта (символ + атрибут цвета) */ ++j; i = i + 2; } return; }
Очень просто, не так ли. Комментарии в коде ясно показывают где и что происходит.
Доработка загрузчика
Для внедрения данного кода в загрузчик ядра Simple OS, нужно немного поправить ассемблерный исходник. Как Вы помните там есть список инструкций для вывода текста. Удалим их! Должен остаться такой код:
section .text bits 32 global start start: cli mov esp, stack_space ; Удаляем весь код, связанный с выводом строки и очисткой экрана ; Удаляем метку done hlt ; Удаляем строку OS_DETAILS section .bss
Настала пора подняться на уровень выше! (барабанная дробь) Добавляем в код вызов процедуры из исходника Си. Функция называется kmain. Покажем загрузчику кто есть кто, перед меткой start, что она находится в другом файле:
extern kmain
А теперь вызов после установки адреса стека:
call kmain
В целом код получается такой:
section .multiboot_header header_start: dd 0xE85250D6 dd 0 dd header_end - header_start dd 0x100000000 - (0xE85250D6 + 0 + (header_end - header_start)) dw 0 dw 0 dd 8 header_end: section .text bits 32 global start extern kmain ;kmain определена в файле Си start: cli mov esp, stack_space call kmain ;Вызов функции hlt section .bss resb 8192 stack_space:
Сборка с помощью GNU LD
Теперь у нас есть два файла исходников: boot.asm и kernel.c. Чтобы собрать единое ядро используем компоновщик, или как его правильно называют: редактор связей, из пакета GNU LD. По ссылке можно посмотреть документацию на русском языке. Мой файл — link.ld — это файл с конфигурацией для компоновщика на языке управления линкером,очень простой:
OUTPUT_FORMAT(elf32-i386) ENTRY(start) SECTIONS { . = 0x100000; .boot : { *(.multiboot_header) } .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } }
В нем указан формат выходного файла, точка входа и секции бинарника.
В терминале нужно будет написать следующую команду:
ld -m elf_i386 -T link.ld -o kernel.bin boot.o kernel.o
kernel.o и boot.o — это файлы объектного кода, компилированного из исходников: kernel.c и boot.asm. На выходе получается собранное ядро — kernel.bin.
После этого стандартно собираем iso-образ диска, запускаем в эмуляторе. И, вуаля:
Автоматизация сборки
Добавлю знания, почерпнутые в теоретической обзорной статье: Автоматизация — используем make.
BUILD_DIR=./build
CC=/usr/bin/gcc
CC_FLAGS=-m32 -c
ASM=/usr/bin/nasm
ASM_FLAGS=-f elf32
LD=/usr/bin/ld
MKDIR=mkdir -p
RM=rm -f -r
project_structure:
${MKDIR} ${BUILD_DIR}
boot: project_structure boot.asm
${ASM} ${ASM_FLAGS} boot.asm -o ${BUILD_DIR}/boot.o
kernel: project_structure kernel.c
${CC} ${CC_FLAGS} kernel.c -o ${BUILD_DIR}/kernel.o
link: boot kernel link.ld
${LD} -m elf_i386 -T link.ld -o ${BUILD_DIR}/kernel.bin ${BUILD_DIR}/boot.o ${BUILD_DIR}/kernel.o
iso: link
${MKDIR} ${BUILD_DIR}/isofiles/boot/grub
cp grub.cfg ${BUILD_DIR}/isofiles/boot/grub
cp ${BUILD_DIR}/kernel.bin ${BUILD_DIR}/isofiles/boot
grub-mkrescue -o ${BUILD_DIR}/simpleos.iso ${BUILD_DIR}/isofiles
qemu: iso
qemu-system-i386 build/simpleos.iso
qemu_cd: iso
qemu-system-i386 -cdrom build/simpleos.iso
clean:
${RM} ${BUILD_DIR}/*
Пробегусь быстренько по целям:
- project_structure — подготовка структуры проекта
- boot — компиляция загрузчика ядра ОС
- kernel — компиляция ядра операционной системы, написанного на языке программирования Си
- link — компоновка ядра
- iso — сборка образа диска
- qemu — запуск подготовленного образа в эмуляторе в качестве жесткого диска
- qemu_cd — запуск образа в качестве cd-диска
- clean — очистка директории сборки
Ну, вот. Немного облегчил себе жизнь. Теперь для того, чтобы пройти весь процесс нужно, всего лишь, в терминале написать команду:
make qemu_cd
Надеюсь Вам понравилась статья. А, пока, до скорых встреч!
Картинки найдены на просторах интернета.