Наше ядро уже является многозадачным. Однако явно это сложно обнаружить по тому выводу, который осуществляют потоки на экран. Для проверки да и просто для эффектной демонстрации многозадачности было бы не плохо организовать вывод на экран динамически меняющихся данных, например значений каких-нибудь счетчиков, изменяющихся при работе потоков.
Имеющиеся средства не позволят нам сделать это - ради интереса можете модифицировать наш предыдущий код. На экране будет творится полный хаос, что-то вроде этого
И происходит так потому, что позиция вывода текста на экран сохраняется внутри модуля вывода текста в единственном экземпляре, а потоков в примере на рисунке - три! И все пытаются выводить данные на экран, перехватывая переменные видеобуфера друг у друга.
Такая ситуация не редкость в многозадачных системах. Использование общего ресурса несколькими потоками приводит нас к необходимости решать проблемы синхронизации доступа к общим данным потоков.
Наша проблема может быть решена двумя способами:
- Исключение использования потоками общих переменных при выводе на экран.
- Введение примитивов синхронизации, разграничивающих доступ к общим ресурсам системы.
Мы пойдем первым путем, так как примитивы синхронизации это отдельная большая тема, а в нашей ситуации можно пока обойтись и без них.
1. Модификация библиотеки работы с экраном
Добавим в код модуля работы с экраном несколько функций, реализующих независимый вывод на экран для каждого из потоков. Введем понятие "виртуального" экрана - некой структуры, индивидуальной для каждого потока, и хранящей текущую позицию вывода текста на экран
Листинг 86. Структура виртуального экрана (text_framebuffer.h)
/*---------------------------------------------------
// Структура виртуального экрана
//-------------------------------------------------*/
typedef struct
{
u8int cur_x; /* Горизонтальная координата символа */
u8int cur_y; /* Вертикальная координата символа */
u16int* vmemory; /* Указатель на видеопамять */
}__attribute__((packed)) vscreen_t;
// Структура виртуального экрана
//-------------------------------------------------*/
typedef struct
{
u8int cur_x; /* Горизонтальная координата символа */
u8int cur_y; /* Вертикальная координата символа */
u16int* vmemory; /* Указатель на видеопамять */
}__attribute__((packed)) vscreen_t;
Кроме того, реализуем функцию, создающую такой виртуальный экран
Листинг 87. Создание и уничтожение виртуального экрана (text_framebuffer.c)
/*---------------------------------------------------
// Создание виртуального экрана
//-------------------------------------------------*/
vscreen_t* get_vscreen(void)
{
/* Выделяем память под виртуальный экран */
vscreen_t* tmp = (vscreen_t*) kmalloc(sizeof(vscreen_t));
/* Очистка памяти */
memset(tmp, 0, sizeof(vscreen_t));
/* Возвращаем указатель */
return tmp;
}
// Создание виртуального экрана
//-------------------------------------------------*/
vscreen_t* get_vscreen(void)
{
/* Выделяем память под виртуальный экран */
vscreen_t* tmp = (vscreen_t*) kmalloc(sizeof(vscreen_t));
/* Очистка памяти */
memset(tmp, 0, sizeof(vscreen_t));
/* Возвращаем указатель */
return tmp;
}
/*---------------------------------------------------
// Уничтожение виртуального экрана
//-------------------------------------------------*/
void destroy_vscreen(vscreen_t* vscr)
{
kfree(vscr);
}
// Уничтожение виртуального экрана
//-------------------------------------------------*/
void destroy_vscreen(vscreen_t* vscr)
{
kfree(vscr);
}
По сути здесь мы работаем с динамической памятью, для создания и удаления структуры, реализующей виртуальный экран.
Теперь создадим функции для вывода символа в такой виртуальный экран
Листинг 88. Вывод символа в виртуальный экран (text_framebuffer.c)
/*------------------------------------------------------------
// Put character on virtual screen
//----------------------------------------------------------*/
void vput_char(vscreen_t* vscr, char c)
{
u8int attrib_byte = (background_color << 4) | (text_color & 0x0F);
u16int attrib_word = attrib_byte << 8;
u16int* location;
if ( c == 0x08 && vscr->cur_x ) /* Удаление символа */
{
vscr->cur_x--;
}
else if (c == 0x09) /* TAB */
{
vscr->cur_x = (vscr->cur_x + 8) &~(8-1);
}
else if (c == '\r') /* Return */
{
vscr->cur_x = 0;
}
else if (c == '\n') /* Enter */
{
vscr->cur_x = 0;
vscr->cur_y++;
}
else /* Прочие символы */
{
location = video_memory + (vscr->cur_y*width + vscr->cur_x);
*location = c | attrib_word;
vscr->cur_x++;
}
if (vscr->cur_x > width)
{
vscr->cur_x = 0;
vscr->cur_y++;
}
}
// Put character on virtual screen
//----------------------------------------------------------*/
void vput_char(vscreen_t* vscr, char c)
{
u8int attrib_byte = (background_color << 4) | (text_color & 0x0F);
u16int attrib_word = attrib_byte << 8;
u16int* location;
if ( c == 0x08 && vscr->cur_x ) /* Удаление символа */
{
vscr->cur_x--;
}
else if (c == 0x09) /* TAB */
{
vscr->cur_x = (vscr->cur_x + 8) &~(8-1);
}
else if (c == '\r') /* Return */
{
vscr->cur_x = 0;
}
else if (c == '\n') /* Enter */
{
vscr->cur_x = 0;
vscr->cur_y++;
}
else /* Прочие символы */
{
location = video_memory + (vscr->cur_y*width + vscr->cur_x);
*location = c | attrib_word;
vscr->cur_x++;
}
if (vscr->cur_x > width)
{
vscr->cur_x = 0;
vscr->cur_y++;
}
}
От функции put_char(...) данная функция отличается тем, что положение курсора сохраняется в структуре виртуального экрана, указатель на который передается в функцию в качестве параметра. На основе этой функции реализуем вывод на экран строки
Листинг 89. Вывод строки на экран (text_framebuffer.c)
/*------------------------------------------------------------
// Вывод строки на экран
//----------------------------------------------------------*/
void vprint_text(vscreen_t* vscr, char* s)
{
int i = 0;
while (s[i])
{
vput_char(vscr, s[i++]);
}
}
// Вывод строки на экран
//----------------------------------------------------------*/
void vprint_text(vscreen_t* vscr, char* s)
{
int i = 0;
while (s[i])
{
vput_char(vscr, s[i++]);
}
}
Теперь мы модем модифицировать функции потоков
Листинг 90. Модификация функции потоков
u32int count01 = 0; /* Счетчик потока #1 */
u32int count02 = 0; /* Счетчик потока #2 */
u32int count02 = 0; /* Счетчик потока #2 */
vscreen_t* vs01; /* Виртуальный экран потока #1 */
vscreen_t* vs02; /* Виртуальный экран потока #2 */
vscreen_t* vs02; /* Виртуальный экран потока #2 */
u8int start_y = 15; /* Начальная вертикальная позиция вывода на экран */
/*----------------------------------------------------------
// Поток #1
//--------------------------------------------------------*/
void task01(void)
{
/* Временная строка для преобразования чисел */
char tmp_str[256];
/* Создаем виртуальный экран */
vs01 = (vscreen_t*) get_vscreen();
/* Вывод на экран в бесконечном цикле */
while (1)
{
/* Задаем позицию вывода */
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);
/* Наращиваем значение счетчика на 1 */
count01++;
}
destroy_vscreen(vs01);
}
/*----------------------------------------------------------
// Поток #2
//--------------------------------------------------------*/
void task02(void)
{
/* Временная строка для преобразования чисел */
char tmp_str[256];
/* Создаем виртуальный экран */
vs02 = (vscreen_t*) get_vscreen();
/* Вывод на экран в бесконечном цикле */
while (1)
{
/* Задаем позицию вывода */
vs02->cur_x = 0;
vs02->cur_y = start_y + 1;
/* Преобразуем счетчик в строку */
dec2dec_str(count02, tmp_str);
/* Печатаем приветствие из потока */
vprint_text(vs02, "I'm kernel thread #2: ");
/* Сдвигаем позицию вывода вправо */
vs02->cur_x = 22;
/* Выводим счетчик на экран */
vprint_text(vs02, tmp_str);
/* Наращиваем значение счетчика на 2*/
count02 += 2;
}
destroy_vscreen(vs02);
}
// Поток #1
//--------------------------------------------------------*/
void task01(void)
{
/* Временная строка для преобразования чисел */
char tmp_str[256];
/* Создаем виртуальный экран */
vs01 = (vscreen_t*) get_vscreen();
/* Вывод на экран в бесконечном цикле */
while (1)
{
/* Задаем позицию вывода */
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);
/* Наращиваем значение счетчика на 1 */
count01++;
}
destroy_vscreen(vs01);
}
/*----------------------------------------------------------
// Поток #2
//--------------------------------------------------------*/
void task02(void)
{
/* Временная строка для преобразования чисел */
char tmp_str[256];
/* Создаем виртуальный экран */
vs02 = (vscreen_t*) get_vscreen();
/* Вывод на экран в бесконечном цикле */
while (1)
{
/* Задаем позицию вывода */
vs02->cur_x = 0;
vs02->cur_y = start_y + 1;
/* Преобразуем счетчик в строку */
dec2dec_str(count02, tmp_str);
/* Печатаем приветствие из потока */
vprint_text(vs02, "I'm kernel thread #2: ");
/* Сдвигаем позицию вывода вправо */
vs02->cur_x = 22;
/* Выводим счетчик на экран */
vprint_text(vs02, tmp_str);
/* Наращиваем значение счетчика на 2*/
count02 += 2;
}
destroy_vscreen(vs02);
}
Теперь функции потоков в цикле выводят значение счетчика, причем позиция вывода запоминается для каждого потока отдельно и не теряется в процессе переключения задач.
2. Тестирование виртуальных экранов
Проверяем, что у нас получилось
Отлично! Теперь отчетливо видно, что наши потоки действительно выполняются на одном процессоре псевдопараллельно, то есть нами реализована настоящая многозадачность, и теперь мы можем убедится в этом и без отладчика.
Заключение
В этой статье мы рассмотрели важный аспект работы многозадачной системы - корректность её работы при доступе к общим ресурсам. В данном примере нет острой необходимости использовать общие данные в потоках и можно организовать структуры, индивидуальные для каждого потока.
Но всё же довольно часто необходим доступ к одним и тем же данным из разных потоков, и об этом мы обязательно поговорим.
Комментариев нет:
Отправить комментарий