Эта заметка будет совсем короткой, и посвящена она ещё одному аспекту работы с потоками.
Представим себе ситуацию, когда нам нужен поток, выполняющий свои функции, и завершающий свою работу. Попробуем создать такой поток, и посмотрим что у нас получится. Для этого уберем бесконечный цикл из одного из наших потоков
Листинг 97. Поток с конечным временем выполнения (main.c)
/*-----------------------------------------------------------------
// Поток #1
//---------------------------------------------------------------*/
void task01(void)
{
char tmp_str[256];
vs01 = (vscreen_t*) get_vscreen();
vs01->cur_x = 0;
vs01->cur_y = start_y;
dec2dec_str(count01, tmp_str);
vprint_text(vs01, "I'm kernel thread #1: ");
vs01->cur_x = 22;
vprint_text(vs01, tmp_str);
count01++;
destroy_vscreen(vs01);
// Поток #1
//---------------------------------------------------------------*/
void task01(void)
{
char tmp_str[256];
vs01 = (vscreen_t*) get_vscreen();
vs01->cur_x = 0;
vs01->cur_y = start_y;
dec2dec_str(count01, tmp_str);
vprint_text(vs01, "I'm kernel thread #1: ");
vs01->cur_x = 22;
vprint_text(vs01, tmp_str);
count01++;
destroy_vscreen(vs01);
}
Теперь пересоберем ядро и загрузим нашу ОС.
Такс, приплыли... Исключение #PF. Чем была вызвана эта ошибка? Отладка по шагам может дать ответ на причину такого поведения системы, но и без неё можно найти ответ на этот вопрос.
Дело в том, что переключившись на этот поток после его создания, процессор выполнил вход в него, вывел на экран значение счетчика, и даже удалил не нужный более виртуальный экран. А вот по завершении он взял из стека хранящееся там произвольное значение и использовал его в качестве адреса возврата. Естественно этот адрес привел процессор в недоступную область памяти. Хотя мог привести и на отображенную страницу, где, скажем, хранились бы некие статические данные, и приняв их за исполняемый код, процессор выплюнул бы нам исключение #UD.
Таким образом необходимо обеспечить корректную передачу управления после завершения функции потока. Куда мы можем передать управление? Например в планировщик. Напишем такую функцию
Листинг 98. Функция завершения потока (scheduler.c)
/*----------------------------------------------------------------
* Выход из потока
*--------------------------------------------------------------*/
void thread_exit(thread_t* thread)
{
/* Отключаем прерывания */
asm volatile ("cli");
/* Удаляем поток из очереди */
list_remove(&thread->list_item);
* Выход из потока
*--------------------------------------------------------------*/
void thread_exit(thread_t* thread)
{
/* Отключаем прерывания */
asm volatile ("cli");
/* Удаляем поток из очереди */
list_remove(&thread->list_item);
/* Уменьшаем число потоков в процессе, которому он принадлежит */
thread->process->threads_count--;
/* Освобождаем память, занятую потоком под стек и собственный описатель */
kfree(thread->stack);
kfree(thread);
/* Грузим в ECX адрес планировщика */
asm volatile ("mov %0, %%ecx"::"a"(&task_switch));
/* Включаем прерывания */
asm volatile ("sti");
/* Вызываем планировщик */
asm volatile ("call *%ecx");
}
thread->process->threads_count--;
/* Освобождаем память, занятую потоком под стек и собственный описатель */
kfree(thread->stack);
kfree(thread);
/* Грузим в ECX адрес планировщика */
asm volatile ("mov %0, %%ecx"::"a"(&task_switch));
/* Включаем прерывания */
asm volatile ("sti");
/* Вызываем планировщик */
asm volatile ("call *%ecx");
}
В общем-то ничего сложного: мы убираем поток из очереди, изменяем число потоков в породившем данный поток процессе, освобождаем память занятую под стек и описатель потока, а затем передаем управление планировщику, который будет работать с очередь потоков дальше.
Теперь модифицируем код функции потока
Листинг 99. Поток с конечным временем выполнения (main.c)
thread_t* thread01;
thread_t* thread02;
thread_t* thread02;
/*-----------------------------------------------------------------
// Поток #1
//---------------------------------------------------------------*/
void task01(void)
{
char tmp_str[256];
vs01 = (vscreen_t*) get_vscreen();
vs01->cur_x = 0;
vs01->cur_y = start_y;
dec2dec_str(count01, tmp_str);
vprint_text(vs01, "I'm kernel thread #1: ");
vs01->cur_x = 22;
vprint_text(vs01, tmp_str);
count01++;
destroy_vscreen(vs01);
/* Завершаем данный поток */
thread_exit(thread01);
// Поток #1
//---------------------------------------------------------------*/
void task01(void)
{
char tmp_str[256];
vs01 = (vscreen_t*) get_vscreen();
vs01->cur_x = 0;
vs01->cur_y = start_y;
dec2dec_str(count01, tmp_str);
vprint_text(vs01, "I'm kernel thread #1: ");
vs01->cur_x = 22;
vprint_text(vs01, tmp_str);
count01++;
destroy_vscreen(vs01);
/* Завершаем данный поток */
thread_exit(thread01);
}
Только надо поместить описание указателей потоков перед описанием их функций, чтобы не нарваться на синтаксическую ошибку. Что мы имеем теперь
Теперь всё в порядке! Поток, отработав однократно, завершается и система продолжает нормальную работу. В нашем распоряжении минимально необходимый набор функций для работы с потоками уровня ядра.
Комментариев нет:
Отправить комментарий