Предыдущая: Напишите свою собственную ОС (4) — Процесс загрузки [https://medium.com/@megtechcorner/write-your-own-os-4-boot-process-b7cb7bef2fcb]

Далее: Напишите свою собственную ОС (6) — Управление курсором (скоро)

— — — — — — — — — — — — — — — — — — — — —

Часть 1.2 Печать на экран

В этом разделе мы расширим нашу операционную систему для печати на экране и переместим курсор в нужное место. Сначала мы настроим стек для операционной системы, чтобы мы могли переключиться на язык программирования C. Далее мы будем печатать на экран, используя фреймбуфер. Наконец, мы будем контролировать положение курсора с помощью инструкций ввода-вывода.

Стартовый код для этого руководства можно найти по адресу [https://github.com/megstechcorner/meg-os/tree/part-1.1-bare-bone-os].

Готовый код для этого руководства можно найти по адресу [https://github.com/megstechcorner/meg-os/tree/part-1.2-print-to-screen].

Переход на C

До сих пор мы использовали язык программирования ассемблера. Это работает, однако программировать на ассемблере может быть утомительно. Поэтому мы переключимся на язык программирования с более богатым синтаксисом и более простым для программирования и отладки. Здесь подойдет любой язык программирования более высокого уровня, такой как Java и C++. Однако мы используем C в этом уроке. Причина в том, что C имеет сравнительно простой синтаксис, и читатели, знакомые с другими языками программирования, должны быстро его освоить. C также достаточно мощен, чтобы удовлетворить наши потребности.

Чтобы использовать C, нам нужно выполнить еще одну настройку — стек. Реализация вызовов функций в C требует наличия стека. Давайте используем следующий код в качестве примера, чтобы проиллюстрировать, как стек используется в C. [Обратите внимание, что стек здесь отличается от структуры данных, называемой стеком.]

Стек — это не что иное, как непрерывный кусок памяти. Для операционной системы мы зарезервируем некоторую физическую память для ее стека во время инициализации. Для пользовательских программ операционная система выделяет свободную память для их стеков. Для машин x86 стек растет в сторону более низких адресов. Как показано на рисунке ниже, вершина стека имеет меньший адрес, чем нижняя часть стека. Обычно программа несет ответственность за то, чтобы она не использовала память вне стека, иначе она может повредить память или вызвать ошибку StackOverflow.

На рисунке слева показано состояние стека перед вызовом bar() внутри функции foo(). Регистр ESP содержит адрес вершины стека foo(), esp(1). Все локальные переменные находятся внутри стека.

На рисунке справа показано состояние стека после вызова функции bar(). Другой кадр стека выделен для функции bar(). Регистр ESP теперь имеет адрес вершины стека bar(), esp(2). Локальные переменные bar() также хранятся в стеке.

После того, как функция возвращается из bar(), регистр ESP снова содержит значение вершины стека foo(), esp(1).

Чтобы получить доступ к локальным переменным, ЦП проверяет значение внутри регистра ESP, чтобы выяснить, какой стек использовать. Затем он извлекает значение локальной переменной из этого стека. Поэтому перед вызовом bar() и после возврата из bar() регистр ESP содержит значение esp(1). Таким образом, ЦП извлечет значение локальной переменной из стека функции foo(). Однако, когда мы находимся внутри функции bar(), регистр ESP имеет значение esp(2). ЦП будет правильно извлекать значения из стека bar(). Таким образом, ЦП может устранять неоднозначность локальных переменных для различных функций.

Настроить стек просто. В start.S мы будем использовать следующую строку кода, чтобы зарезервировать 4 КБ памяти в разделе BSS.

.lcomm kernel_stack, KERNEL_STACK_SIZE # Зарезервировать стек ядра размером 4 КБ

После этого нам нужно только заставить регистр ESP хранить значение дна стека. Теперь мы готовы перейти на язык программирования C. [функция main определена в файле kmmain.c.]

Печать на экран

Контент, отображаемый на экране нашего компьютера, управляется запоминающим устройством, называемым фреймбуфером. Первые 8 бит внутри кадрового буфера представляют значение ASCII для символа, который будет отображаться в позиции (строка_0, столбец_0). Следующие 4 бита кодируют цвет переднего плана текста, а следующие 4 бита — цвет фона текста. Сопоставление цвета со значением показано ниже.

Подводя итог, 16 бит используются для представления отображаемого символа. Первые 16 бит предназначены для позиции (строка_0, столбец_1). Следующие 16 бит предназначены для позиции (строка_0, столбец_1) и так далее.

Чтобы разрешить доступ к фреймбуферу, фреймбуфер обычно сопоставляется с памятью по адресу 0xb8000. Мы обсудим отображение памяти более подробно в следующих разделах. Идея высокого уровня заключается в том, что когда мы записываем в память по адресу 0xb8000, вместо записи данных в физическую память данные записываются в фреймбуфер по адресу 0.

Простой пример печати «привет» в левом верхнем углу дисплея показан на рисунке выше. Мы определяем трехмерный массив fb с начальным адресом 0xB8000. fb[i][j][0] представляет символ в позиции (row_i, column_j), а fb[i][j][1] представляет атрибут этого символа.

Например, fb[0][0][0] имеет адрес 0xB8000 и поэтому записывается по адресу 0 фреймбуфера (первые 8 бит). fb[0][0][1] записывается в следующие 8 бит. Затем буфер кадра обновляет экран при получении данных.

Скомпилировав и запустив операционную систему, мы сможем увидеть «привет» в верхнем левом углу.

> сделать

› qemu-system-x86_64 -kernel kernel.elf

Однако в текущем коде есть как минимум две проблемы.

  • Положение курсора неверно
  • Утомительно манипулировать содержимым экрана

Мы рассмотрим обе проблемы в следующем уроке.

— — — — — — — — — — — — — — — — — — — — —

Предыдущая: Напишите свою собственную ОС (4) — Процесс загрузки [https://medium.com/@megtechcorner/write-your-own-os-4-boot-process-b7cb7bef2fcb]

Далее: Напишите свою собственную ОС (6) — Управление курсором (скоро)