Всем здрасте, и сегодня у нас продолжение низкоуровневого программирования. В этой части мы все разобьем на модули, а так же напишем ввод, благодаря чем мы сможем сделать маленькую командную оболочку!
Шаг 1. Модули
Для более читабельного кода я решил сделать папочку kernel/lib
, в которой у нас будет три файла: stdio.h
, types.h
, string.h
. В первом уже понятно - ввод-вывод, во втором типы, в третьем работа со строками.
В stdio.h
мы пишем сначала 2 функции - printc()
и printl()
, которые выводят символ и строку соответственно
void printc(char* video_memory, char symb, char* color, int charpos) {
video_memory[charpos * 2] = symb;
video_memory[charpos * 2 + 1] = color;
}
int printl(char* video_memory, const char* text, int startpos) {
int nextpos;
for (int i = 0;; i++) {
if (text[i] == '\0') {break;}
printc(video_memory, text[i], (char *)0x07, startpos);
startpos++;
nextpos = startpos;
}
return nextpos;
}
Первая функция - это как строки с выводом с ядре - в видеопамять (которая как аргумент ведь адрес может отличаться) мы записываем по индексу номер символа (порядковый) 2 сам символ, а в номер2+1 - цвет. В printl()
же всего три аргумента - адрес видеопамяти, текст и.... начальный символ. Ведь текст может быть после предыдущего вывода. Кстати указатель на следующий после текста символ возвращается. Тут все просто - просто в цикле вызываем printc()
.
Дальше - интересней. Пишем ввод
static inline unsigned char inb(unsigned short port) {
unsigned char data;
__asm__ volatile ("inb %1, %0" : "=a" (data) : "Nd" (port));
return data;
}
unsigned char getchar(void) {
unsigned char scancode;
do {
scancode = inb(0x64);
scancode &= 1;
} while (!scancode);
return inb(0x60);
}
В первой функции мы получаем данные с порта через ASM и возвращаем их. Во второй - получаем данные с порта состояния PS/2 (да-да, старый добрый разъем, не USB, там надо писать ого-го!), смотрит на бит готовности, и так по циклу пока не будет бит равен 1; дальше возвращается сама клавиша.
На заметку: код нажатой клавишы (и соответственно символ) отличается от того, что нарисован на самой клавише
В types.h
почти ничего нет - просто пишем один typedef
typedef unsinged int u32
В string.h
тож ничего особенного - просто функция для подсчета символов в строке
int strlen(constchar* text) {
return sizeof(text) / sizeof(char);
}
Шаг 2. Пишем ввод в ядре
А сейчас, реализовав вывод и ввод, мы можем включить это в наше ядро. Но для этого нам потребуется проанализировать, при нажатии какой клавиша какой символ будет. Поэтому:
Меняем код вывода на этот (тут вывод и ввод смивола) (а также не забудьте подключить нашу библиотеку (
#include "stdio.h"
)
const char* message = "HabrOS 0.0.2 >>> ";
int nextpos = printl(video_memory, message, 0);
unsigned char ch = getchar();
printc(video_memory, ch, (char *)0x07, nextpos+1);
Сообщение можете взять мое, а можете свое
Вставляем ниже код для отображения сканкода:
char hex_chars[] = "0123456789ABCDEF";
printc(video_memory, hex_chars[(ch >> 4) & 0xF], (char*)0x07, nextpos++);
printc(video_memory, hex_chars[ch & 0xF], (char*)0x07, nextpos++);
Компилируем (скрипт есть, но не забудьте к
i686-elf-gcc -o ...
прибавить-I kernel/lib
)Запускаем и пробуем нажать клавишу
Идем на OSDev Wiki и ищем там наш код
Если клавиша которую вы нажали сходится с клавишей которая указана в таблице - значит у вас этот code scan set (у меня первый набор)
После этого можно идти и писать обработчик.
Мы создадим специальную функцию (в stdio.h
) которая будет превращать символы из сканкодов в символы которые написаны на клавиатуре
unsigned char transform(unsigned char ch) {
unsigned char codes[] = {
(unsigned char)0x01, (unsigned char)0x02, (unsigned char)0x03,
(unsigned char)0x04, (unsigned char)0x05, (unsigned char)0x06,
(unsigned char)0x07, (unsigned char)0x08, (unsigned char)0x09,
(unsigned char)0x0A, (unsigned char)0x0B, (unsigned char)0x0C,
(unsigned char)0x0D, (unsigned char)0x0E, (unsigned char)0x0F,
(unsigned char)0x10, (unsigned char)0x11, (unsigned char)0x12,
(unsigned char)0x13, (unsigned char)0x14, (unsigned char)0x15,
(unsigned char)0x16, (unsigned char)0x17, (unsigned char)0x18,
(unsigned char)0x19, (unsigned char)0x1A, (unsigned char)0x1B,
(unsigned char)0x1C, (unsigned char)0x1D, (unsigned char)0x1E,
(unsigned char)0x1F, (unsigned char)0x20, (unsigned char)0x21,
(unsigned char)0x22, (unsigned char)0x23, (unsigned char)0x24,
(unsigned char)0x25, (unsigned char)0x26, (unsigned char)0x27,
(unsigned char)0x28, (unsigned char)0x29, (unsigned char)0x2A,
(unsigned char)0x2B, (unsigned char)0x2C, (unsigned char)0x2D,
(unsigned char)0x2E, (unsigned char)0x2F, (unsigned char)0x30,
(unsigned char)0x31, (unsigned char)0x32, (unsigned char)0x33,
(unsigned char)0x34, (unsigned char)0x35, (unsigned char)0x36,
(unsigned char)0x37, (unsigned char)0x38, (unsigned char)0x39,
(unsigned char)0x3A, (unsigned char)0x3B, (unsigned char)0x3C,
(unsigned char)0x3D, (unsigned char)0x3E, (unsigned char)0x3F,
(unsigned char)0x40, (unsigned char)0x41, (unsigned char)0x42,
(unsigned char)0x43, (unsigned char)0x44, (unsigned char)0x45,
(unsigned char)0x46, (unsigned char)0x47, (unsigned char)0x48,
(unsigned char)0x49, (unsigned char)0x4A, (unsigned char)0x4B,
(unsigned char)0x4C, (unsigned char)0x4D, (unsigned char)0x4E,
(unsigned char)0x4F, (unsigned char)0x50, (unsigned char)0x51,
(unsigned char)0x52, (unsigned char)0x53, (unsigned char)0x57,
(unsigned char)0x58
};
unsigned char symbols_standart[] = {
(unsigned char)0x00, '1', '2', // 0x00 - null (not bind)
'3', '4', '5',
'6', '7', '8',
'9', '0', '-',
'=', (unsigned char)0x08, (unsigned char)0x09,
'q', 'w', 'e',
'r', 't', 'y',
'u', 'i', 'o',
'p', '[', ']',
(unsigned char)0x0A, (unsigned char)0x01, 'a', //0x01 - l ctrl
's', 'd', 'f',
'g', 'h', 'j',
'k', 'l', ';',
'\'', '`', (unsigned char)0x02, //0x02 - l shift
'\\', 'z', 'x',
'c', 'v', 'b',
'n', 'm', ',',
'.', '/', (unsigned char)0x00,
'*', (unsigned char)0x00, ' ',
(unsigned char)0x03, (unsigned char)0x00, (unsigned char)0x00, //0x03 - caps
(unsigned char)0x00, (unsigned char)0x00, (unsigned char)0x00,
(unsigned char)0x00, (unsigned char)0x00, (unsigned char)0x00,
(unsigned char)0x00, (unsigned char)0x00, (unsigned char)0x00,
(unsigned char)0x00, '7', '8',
'9', '-', '4',
'5', '6', '+',
'1', '2', '3',
'0', '.', (unsigned char)0x00,
(unsigned char)0x00
};
int status = 0;
for (int i = 0; i != (sizeof(codes) / sizeof(char)); i++) {
if (ch == codes[i]) {
status = 1;
return symbols_standart[i];
}
}
if (status == 0) {
return ch;
}
}
Функция как возвращает, так и принимает символ (который из сканкода получается), дальше по кругу проверяет со символами из 1-го массива, если равен, возвращает соответственный по позиции символ из 2-го массива (нажатая клавиша). А если не нашло в 1-м массиве наш символ, то мы его же и возвращаем. Обработчик сам-то несложный, но массивы получаются достаточно большими, а это еще мы не обрабатывали нажатие с CapsLock и Shift, поэтому я привел более упрощенный вариант.
Меняем в ядре строчки вывода и пустой цикл на
while(1) {
unsigned char ch = getchar();
unsigned char ch_descaned = transform(ch);
printc(video_memory, ch_descaned, (char *)0x07, nextpos+1);
nextpos++;
}
А теперь компилируем, и смотрим что получается

Странно как-то. Вроде клавишы обрабатываются, но за ними потом идет еще символ. Странно, да? А теперь попробуйте зажать. Нету того символа, да? Так вот в чем проблема: мы обрабатывали прерывание с отпусканием клавишы, и оно посылало сканкод 0x80 + сканкод клавишы. Давайте вернемся в stdio.h
и вместо return inb(0x60);
напишем данный код:
unsigned char scan = inb(0x60);
if (scan & 0x80) {
return 0;
}
return scan;
Он игнорирует отпускание клавишы. А теперь в kernel.c после получение символа с клавиатуры:
if (ch == 0) continue;
Чтобы пробел не выводился после каждого символа мы добавляем этот код (все равно сканкода 0x00 нету)
Запускаем откомпилированный и собранный в ISO файл - и вуаля

Ну надеюсь вы сможете дописать обработку всех клавиш. А давайте пока составим план на следующие части:
Сделать командную оболочку
Реализовать там функции
echo
,shutdown
иversion
Ссылка на Github: https://github.com/KirillMos2/HabrOS/
Спасибо что прочитали! Буду дальше писать ОС.
Комментарии (10)
LeonidPr
19.08.2025 10:44Интересно, на чем это все закончится.
Я в свое время не осилил работу с жестким диском из защищенного режима, на этом мои потуги с написанием OC в x86 закончились))
SystemSoft
19.08.2025 10:44папка lib: sdtlib.h файл kernel.c: #include <stdlib.h>. Вы хоть смотрите на код и на названия когда выкладывайте
daytona13
Интересно, а автор вообще смотрел на код, который ему нейронка сгенерила?
sizeof(char*) на х86 = 4 байта, на х64 = 8 байт. Тебе любой компилятор покажет варнинг и такая ошибка разбирается в любых, даже самых гнусных и убогих видосах на ют.
Это даже комментировать не буду:
typedef unsinged int u32 // unsinged вместо unsigned
returnsizeof(text) // слитно написано
constchar* text // без пробела
Массив codes[] это просто мем. O(n) вместо O(1).
inb(0x64) в цикле = polling
В реальных ОС используют как бы irq1 для клавы)
Нет проверки для границ экрана.
0x07 захардкожен во всех вызовах.
Дальше читать даже не буду, статья - мусор.
kaspary Автор
Так, давайте все распишем
1. Я переносил статью с vc.ru, могли косяки произойти, как например с return
2. Не понял про массив
3. То что используют IRQ1 я знаю, я решил немного нестандартно сделать
daytona13
Почему тогда авторство оргиниальное не указано? Перенос статьи с WC.ru? Ты издеваешься? Тут перенесенные статьи никому не сдались, окстись и перестань копипастить булщит.
kaspary Автор
это моя статья, сначала я ее писал на хабре, не дождался одобрения первой статьи и пошел на vc.ru дальше я перенес оттуда эту 2 статью которая изначально была здесь и висела в редакторе пока первую не одобрят
daytona13
ну то есть статья на wc.ru такой же кал, как и эта. Оправдания "я просто скопировал с туалет точка ру" не актуальны. Статья сама по себе каловая, допущено огромное кол-во ошибок. Бессмысленная функция transform(), которая создает массив сканкодов, но заполняет их просто по порядку, с излишне сложной логикой. Поллинг вместо прерываний при обработке клавы, нерабочее состояние модификаторов, хардкод для scan set 1 без проверки. Элементарное непонимание того, что sizeof(text) возвращает размер указателя, а не длину строки. Передача video_memory в каждую функцию, вместо глобальной переменной, отсутствие проверки границ экрана, хардкод цвета в виде 0x07. Код просто напросто отвратителен, понимаение автора - нулевое. И все эти ошибки выявлены были просто при беглом осмотре, страшно представить, что там творится, если детально разбирать это достояние ии. Статья написана, чтобы просто быть написанной. Искренне жалко людей, которые кидают это себе в закладки.
kaspary Автор
слушай, если тебе что-то не нравится - пиши свою ОС. То что там все захардкорено это я попроавлю в 3 части (в моем коде уже поправлено если что)
daytona13
не стоит публиковать 3 часть, я думаю никто не выдержит такого же лукового угара от вайбкодера кирилла, только уже в целых трех томах
zeroqxq
.