Добрый день, уважаемые читатели. Некоторое время я был занят, но теперь снова возвращаюсь к продолжению хождений по мукам разработки операционной системы. Эта статья посвящена работе с курсором. Курсор в текстовом режиме, как показала практика, обладает аппаратной поддержкой.
Порты ввода/вывода
Порты ввода/вывода — это решение для взаимодействия процессора и внешних устройств. В процессорах x86 порты не связаны с основной оперативной памятью. Для взаимодействия с портами в ассемблере используются команды in — для чтения из порта и out — для записи в порт. Увы, прямой возможности работать на языке Си с портами не получится.
От ассемблера не уйти
Я старался! Правда! Но действительно не получится. Однако есть небольшое удобство. Команды ассемблера можно писать прямо в исходном коде на Си. Для этого предназначена команда:
__asm__
Встроенный ассемблер в GCC использует синтаксис AT&T. Для меня это открытие было очень болезненным. Конечно, можно перевести GCC в режим использования формат Intel, но такой синтаксис, хоть и похож на синтаксис nasm, но имеет отличия. Поэтому я решил писать на ассемблере AT&T. Ниже структура инлайновой ассемблерной строки:
__asm__( Инструкции ассемблера
: выходные операнды
: входные операнды
: список изменяемых регистров
Я не планирую развивать тему далее, так как она требует минимум отдельной статьи. Больше можно прочитать на HOWTO-странице по ассемблеру GCC или в переводе. Вернемся к нашим задачам.
Рефакторинг библиотеки
В прошлой статье мы начали создавать библиотеку. Теперь продолжаем улучшать. Для начала возьмусь за типизацию. Точнее сделаю ее понятнее. Для этого создадим файл с наименованием types.h, в котором определим несколько новых типов:
#ifndef TYPES_H #define TYPES_H typedef unsigned char uint8; typedef unsigned short uint16; typedef char int8; typedef short int16; typedef unsigned int uint32; #endif
Также я вынес глобальные константы в единый файл — constants.h
#ifndef CONSTANTS_H #define CONSTANTS_H #define VIDEO_MEMORY_ADDRESS 0xB8000 #define SPACE_CHAR ' ' #define BACKGROUND_COLOR_DEFALT 0x07 #define MAX_ROWS 25 #define MAX_COLS 80 #define REG_SCREEN_CTRL 0x3d4 #define REG_SCREEN_DATA 0x3d5 #endif
Взаимодействие с портами
Выше, я писал об аппаратной поддержке курсора в текстовом режиме. Он управляется с помощью портов ввода/вывода. Поэтому нам надо подготовить библиотечную функцию, которая будет работать с портами. Это нужно также и для того, чтобы не писать инлайновые ассемблерные вставки везде.
#include "ports.h" #include "types.h" uint8 inb (uint16 port) { uint8 result; __asm__("in %%dx, %%al" : "=a" (result) : "d" (port)); return result; } void outb (uint16 port, uint8 data) { __asm__("out %%al, %%dx" : : "a" (data), "d" (port)); } uint16 inw (uint16 port) { uint16 result; __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port)); return result; } void outw (uint16 port, uint16 data) { __asm__("out %%ax, %%dx" : : "a" (data), "d" (port)); }
Эта часть кода чрезвычайно проста. Здесь содержится четыре функции 2 из которых работают с одним байтом:
- outb — выводит байт в указанный порт
- inb — получает байт из порта
Две другие — со словом (2 байта):
- outw — выводит слово
- inw — получает слово
Курсор
Теперь можно начинать работу с курсором. Условно всю работу с курсором можно разделить на три части:
- Включение курсора
- Изменение местоположения
- Отключение курсора
Пока мы реализуем только две части. В исходном коде ниже представлены функции для работы с экраном в текстовом режиме.
#include "screen.h" #include "ports.h" #include "constants.h" #include "types.h" void screen_init() { clear_screen(); init_cursor(14, 15); set_cursor(0, 0); } void clear_screen() { uint8* video_memory = (uint8*)VIDEO_MEMORY_ADDRESS; uint16 i = 0; while(i < MAX_COLS * MAX_ROWS * 2) { video_memory[i] = SPACE_CHAR; video_memory[i+1] = BACKGROUND_COLOR_DEFALT; i = i + 2; } } void init_cursor(uint8 cursor_start, uint8 cursor_end) { outb(0x3D4, 0x0A); outb(0x3D5, (inb(0x3D5) & 0xC0) | cursor_start); outb(0x3D4, 0x0B); outb(0x3D5, (inb(0x3D5) & 0xE0) | cursor_end); } void set_cursor(uint8 row, uint8 col) { uint16 pos = row * MAX_COLS + col; outb(0x3D4, 0x0F); outb(0x3D5, (uint8) (pos & 0xFF)); outb(0x3D4, 0x0E); outb(0x3D5, (uint8) ((pos >> 8) & 0xFF)); }
Функция init_cursor занимается настройкой внешнего вида курсора и, самое главное, его включением. Другая функция — set_cursor — занимается установкой курсора в нужную позицию на экране. Также я перенес сюда функцию очистки экрана, что на мой взгляд естественно. Вызов инициализации экрана осуществляется в основном файле ядра — kernel.c. Сейчас он немного изменился:
#include "library/string.h" #include "library/screen.h" void kmain(void) { const char *str = "Simple OS, version 0.0.1"; screen_init(); /* Вывод наименования ОС и версии ядра */ print_string(0, 0, str); return; }
Все изменения внесены в репозиторий: Simple OS
В результате получился вот такой курсор.
P.S. Запущен сайт с документацией, формируемой с помощью утилиты doxygen. Документация доступна по двум адресам: github и здесь, на моем сайте.
Картинки, кроме последней, найдены на просторах интернета.