Во-вторых, сегодня мы начнём писать функции для поддержки многозадачности. Как обычно вначале определимся с прототипами функций в заголовочном файле multitasking.h:
Структуры описателей процессов и потоков предусматривают множество полей, которые понадобятся нам в будущем, а вот набор функций пока очень скуден - не реализовано даже завершение нитей, работы с процессами нет вообще. Но для начала хватит, остальное допишем в следующих выпусках.
Многозадачность как таковую мы сегодня не сделаем - функция create_thread будет реализована не до конца, но мы вплотную приблизимся к ней.
Начинается файл multitasking.c с простой функции init_multitasking,
которая создаёт структуры процесса и нити ядра, а также устанавливает обработчик прерывания таймера:
Эта функция в настоящий момент упрощена - мы не создаём нормального личного адресного пространства для процесса ядра, а также не задаём базовый адрес стека для главной нити ядра. Второе впрочем нужно только для будущей функции завершения нити (чтобы освободить память из-под стека), а главная нить ядра по моей задумке никто завершена не будет (но может уходить в спячку).
Дальше следует новый обработчик прерывания таймера:
Вот так просто переключаются задачи. Запоминаем текущий указатель стека, ищем следующую нить для выполнения, переключаем каталог страниц, переключаем стек. Всё! Мы теперь находимся в контексте новой нити. Однако на самом деле такой код использовать не получится - как создавать новые нити? Функция create_thread должна заполнить стек новой нити как-будто он находится в середине обработчика прерывания. То, что помещается в стек процессором (адрес возврата, регистр флагов), а также ассемблерной вставкой
(все регистры процессора) известно, но вот поведение самого task_switch_int_handler - нет. При разных настройках компилятора он может как создавать стековый фрейм, так и нет. В итоге в стеке будет на одно двойное слово больше или меньше, а это всё портит. До следующего выпуска предстоит придумать как с этим бороться. Я пока не знаю красивого решения на Си (если бы мы писали ОС на чистом Assembler решение проблемы было бы элементарно и я так уже много раз делал).
Ну а пока идём дальше - функция switch_task.
Её вызывает процесс, если ему больше нечего делать - система должна отдать остаток времени другой задаче. Это обозначает, что нить хочет дать возможность выполнится другим, потому что она сможет продолжить лишь получив данные от кого-то другого. Никакого механизма обмена данными между нитями у нас пока нет, поэтому просто остановим процессор инструкцией HLT. Она заставляет его перейти в режим пониженного энергопотребления и продолжить работу лишь после прихода прерывания.
void switch_task() {
asm("hlt");
}
Сразу же приведу пример, где такая команда уместна - в функции in_char tty.c:
Такое изменение в будущем повысит быстродействие системы, а уже сейчас - энергопотребление.
Ну вот осталась последняя функция файла multitasking.c - create_thread:
Thread *create_thread(Process *process, void *entry_point, size_t stack_size, bool kernel, bool suspend) {
Thread *thread = alloc_virt_pages(&kernel_address_space, NULL, -1, 1, PAGE_PRESENT | PAGE_WRITABLE);
thread->process = process;
thread->suspend = suspend;
// ... вот тут должно быть создание и заполнение стека нити ...
list_append((List*)&thread_list, (ListItem*)thread);
process->thread_count++;
return thread;
}
Это заготовка, но не готовая функция. Её вызов в том виде, в котором она сейчас есть с suspend != false приведёт к краху системы на следующем тике таймера, потому что мы не создали для нити нормальный стек. Нам ещё многое предстоит написать.
Пока добавим в kernel_main инициализацию многозадачности:
Ну вот и всё. В поведении ядра пока ничего не изменилось, но мы подготовили всё к тому, чтобы начать создавать новые нити. Заодно по новому обработчику таймера вы можете наглядно понять новый принцип работы нашего переключателя задач.
Заключение
У меня сразу две хороших новости - во-первых, моя рассылка получила статус "Серебряной", во-вторых, у неё уже 152 подписчика (вероятно, столь быстрый рост их числа связан с появлением рассылки в общем каталоге из-за
изменения статуса).
Теперь я могу прикладывать к выпускам файлы, поэтому сразу же воспользуюсь этим, приложив полный архив исходных текстов нашей ОС, который точно работоспособен. Я не стал убирать скомпилированный образ диска (всё равно ZIP его очень не плохо сжал), чтобы можно было легко проверить систему.
Раз вас стало больше, я могу вновь задать свои вопросы:
1) Как должна называться наша ОС? 2) Как по вашему мнению лучше реализовать переключение задач (сделать стек при обработке прерывания
более предсказуемым)?
И вообще, пишите любые свои замечания, предложения, пожелания, вопросы мне на адрес kiv.apple@gmail.com. До встречи!