воскресенье, 25 августа 2013 г.

PhantomEx: Вывод на экран из нескольких потоков. Реализация "виртуальных" экранов

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

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


И происходит так потому, что позиция вывода текста на экран сохраняется внутри модуля вывода текста в единственном экземпляре, а потоков в примере на рисунке - три! И все пытаются выводить данные на экран, перехватывая переменные видеобуфера друг у друга.

Такая ситуация не редкость в многозадачных системах. Использование общего ресурса несколькими потоками приводит нас к необходимости решать проблемы синхронизации доступа к общим данным потоков.

Наша проблема может быть решена двумя способами:

  1. Исключение использования потоками общих переменных при выводе на экран.
  2. Введение примитивов синхронизации, разграничивающих доступ к общим ресурсам системы.
Мы пойдем первым путем, так как примитивы синхронизации это отдельная большая тема, а в нашей ситуации можно пока обойтись и без них.


1. Модификация библиотеки работы с экраном


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

Листинг 86. Структура виртуального экрана (text_framebuffer.h)

/*---------------------------------------------------
//    Структура виртуального экрана
//-------------------------------------------------*/

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;
}

/*---------------------------------------------------
//    Уничтожение виртуального экрана
//-------------------------------------------------*/

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_char(...) данная функция отличается тем, что положение курсора сохраняется в структуре виртуального экрана, указатель на который передается в функцию в качестве параметра. На основе этой функции реализуем вывод на экран строки

Листинг 89. Вывод строки на экран (text_framebuffer.c)

/*------------------------------------------------------------
//    Вывод строки на экран
//----------------------------------------------------------*/

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 */

vscreen_t* vs01;      /* Виртуальный экран потока #1 */
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);
}

Теперь функции потоков в цикле выводят значение счетчика, причем позиция вывода запоминается для каждого потока отдельно и не теряется в процессе переключения задач. 
 

2. Тестирование виртуальных экранов

 
Проверяем, что у нас получилось

 
Отлично! Теперь отчетливо видно, что наши потоки действительно выполняются на одном процессоре псевдопараллельно, то есть нами реализована настоящая многозадачность, и теперь мы можем убедится в этом и без отладчика.

Заключение


В этой статье мы рассмотрели важный аспект работы многозадачной системы - корректность её работы при доступе к общим ресурсам. В данном примере нет острой необходимости использовать общие данные в потоках и можно организовать структуры, индивидуальные для каждого потока. 

Но всё же довольно часто необходим доступ к одним и тем же данным из разных потоков, и об этом мы обязательно поговорим.