OSDev

для всех
Текущее время: 10 май 2024, 12:41

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 160 ]  На страницу Пред.  1 ... 10, 11, 12, 13, 14, 15, 16  След.
Автор Сообщение
СообщениеДобавлено: 22 авг 2013, 17:49 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
phantom-84 писал(а):
нужно будет потом сделать распределение прикладного стека в прикладном пространстве.

Конечно, куча и прочее будут свои для ring 3
phantom-84 писал(а):
es лучше тоже проинициализировать

Cделал.
Написал ещё одну чисто ассемблерную функцию - ну не так уже это и страшно, тем более что раньше меня смущала в основном невозможнось трассировать этот код в eclipse (из-за глупости с забытым флагом -g у GAS). Зато точно знаешь что не выползет какая-нибудь leave...
Вообще есть жуткое желание убрать все крупные асм волатилы из кода нафиг, ну за исключением тех мест где 1,2-3 инструкции ими вызываются. Тем более в gcc они просто ужасны на вид.

Описал давешнюю ситуацию с #TS в новой статье


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 23 авг 2013, 07:51 

Зарегистрирован: 10 май 2007, 11:33
Сообщения: 1206
Код:
002034eb:   sub $0x18,%esp             /* Эта инструкция увеличила стековый кадр на 6 байт!!! */
Не понял, почему на 6?

Код:
pop    %ebp   /* Эпилог функции C */
ret потерял. Пролог/эпилог функции Си здесь уже упоминать не вполне корректно, т.к. видимо "mov ebp,esp" является обязательным элементом пролога, а у тебя его нет, другие элементы тоже в определенной ситуации являются обязательными. Это пролог/эпилог функции, совместимой с Си по вызову (благодаря сохранению ebp; насчет ebx, esi, edi нужно смотреть отдельно), если конечно передавать аргументы в правильном порядке и не удалять их ret'ом.

Цитата:
При формировании стека задачи мы не учитывали этих манипуляций со стеком, рассчитывая на стандартный пролог и эпилог функции C и последующий ret. Компилятор решил всё по другому. Учитывая что тут же вызывалась функция модифицирующая TSS это привело к непредсказуемым для последнего последствиям.
Читателям может показаться, что #TS произошло именно из-за попытки модификации содержимого TSS, хотя это совсем не так.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 23 авг 2013, 10:31 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
phantom-84 писал(а):
Не понял, почему на 6?

тю, точно на 24 же. Перепутал с индексом массива 4-х байтного типа :oops: Вернее перемешал эти вещи вообще между собой невнятно. Исправил
phantom-84 писал(а):
Читателям может показаться, что #TS произошло именно из-за попытки модификации содержимого TSS, хотя это совсем не так.

Сложно сказать почему именно #TS, не настолько я искушен в ассемблере и отладке низкоуровневого кода, но мне кажется что модификация именно TSS непосредственно перед выходом из функции повлияла на характер генерируемого исключения, хотя на его месте конечно могло быть любое другое. Уже столкнулся с тем что ошибки работы со стеком порождают или #UD или #PF при передаче управления "в никуда". А при реализации системных вызовов к ним добавляется ещё #GP. Выяснить бы точно почему именно #TS было....


Последний раз редактировалось maisvendoo 23 авг 2013, 11:01, всего редактировалось 2 раз(а).

Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 23 авг 2013, 21:48 

Зарегистрирован: 21 сен 2007, 17:24
Сообщения: 1088
Откуда: Балаково
gcc -save-temps -fverbose-asm -masm=intel


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 23 авг 2013, 21:56 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
Посмотрел как у меня отрабатываются системные вызовы. Занимательно. Привожу результат - в качестве теста читаю байт состояния клавиатуры
Перед системным вызовом мы в режиме пользователя
Изображение
Осуществлен вызов - мы перед входом в обработчик прерывания
Изображение
Выполнение главной работы - данная команда недоступна в моих условиях через user mode (никаких специальных иструкций по модификации уровня привилегий команд не выполнялось)
Изображение
После обработки - мы снова в Ring 3
Изображение

Himik писал(а):
gcc -save-temps -fverbose-asm -masm=intel

спасибо, попробую

Added: Himik, огромное спасибо. Только поправочка - -masm=intel включает синтаксис интел в асемблерных вставках, а я его не использую, посему не стал ставить этот ключ


Последний раз редактировалось maisvendoo 30 авг 2013, 23:12, всего редактировалось 3 раз(а).

Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 30 авг 2013, 23:19 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
Провел небольшой эксперимент с процессами в своей системе. Хотя как сказать - небольшой: пришлось написать простейшую VFS и "драйвер" RAM-диска, для того чтобы грузить настоящие исполняемые файлы. С форматом исполняемых файлов мудрить не стал - взял ELF32 как наиболее документированный. Получилось вот что
Изображение
Запущены два приложения, делают они то же самое что и предыдущие потоки - здороваются и выводят счетчик. Код планировщика не претерпел существенных изменений
Код:
/*-----------------------------------------------------------------------------
/
/      Switch tasks
/      (c) maisvendoo, 21.08.2013
/
/----------------------------------------------------------------------------*/
.extern      current_thread
.extern      tss


.global      task_switch

task_switch:

         push   %ebp             /* C-function prolog */
         
         pushf                  /* Save EFLAGS in thread stack */
         cli                     /* Disable all interrupts */
         
         /* Save current task's ESP */
         mov   current_thread, %edx
         mov   %esp, 28(%edx)
         
         /* Get next task handler */
         mov   4(%edx), %ecx
         mov %ecx, current_thread
         
         /* Set new ESP */
         mov current_thread, %edx
         mov 28(%edx), %esp
         
         /* Set page directory */
         mov 12(%edx), %ebx          /* current_thread->process --> EBX */
         mov 12(%ebx), %ecx          /* process->page_dir --> ECX */
         mov %ecx, %cr3              /* reload CR3 */
         
         /* Set TSS kernel stack */
         mov 40(%edx), %eax         /* current_thread->stack_top --> EAX */
         mov $tss, %edx            /* tss address --> EDX */
         mov %eax, 4(%edx)         /* EAX --> tss.esp */
                  
         
         popf                  /* Restore EFLAGS from new task stack */
         
         pop      %ebp            /* C-function epilog */
         ret

Добавленный участок отвечает за переключение каталога страниц
Код:
 /* Set page directory */
mov 12(%edx), %ebx          /* current_thread->process --> EBX */
mov 12(%ebx), %ecx          /* process->page_dir --> ECX */
mov %ecx, %cr3              /* reload CR3 */

В PhantomEx принята модель, когда процесс - это совокупность (или хотя бы один - главный) потоков. В структуре потока есть указатель на породивший его процесс. Потоки всех процессов стоят в одной кольцевой очереди, при переключении через указатель процесса-родителя получаем физический адрес каталога страниц этого процесса и переключаемся на него. Вуаля!
Создание адресного пространства, а конкретно клонирование директории свелось к простому копированию содержимого каталога таблиц ядра в новом месте. Потом в этой новой директории отображается адресное пространство для процесса
Код:
/*-----------------------------------------------------------------------------
 *
 *---------------------------------------------------------------------------*/
physaddr_t clone_kernel_dir(u32int* vaddr)
{
   physaddr_t new_dir = 0;
        /* Память под каталог таблиц выделяем в куче ядра */
   u32int* vnew_dir = (u32int*) kmalloc_ap(PAGE_SIZE, &new_dir);

   if (vnew_dir == NULL)
      return 0;
        /* Копируем в новый каталог содержимое каталога ядра */
   memcpy(vnew_dir, (void*) kernel_page_dir, PAGE_SIZE);
        /* Запоминаем виртуальный адрес для последующего удаления структуры */
   *vaddr = (u32int) vnew_dir;

   return new_dir;
}

По сравнению с монструозным вариантом из мануала JM смотрится очень неплохо. Создание процесса выглядит так
Код:
/*-----------------------------------------------------------------------------
 *      Execute kernel process
 *---------------------------------------------------------------------------*/
void kexec_proc(char* name)
{
   u32int      pdir_vaddr = 0;         /* Виртуальный адрес каталога таблиц процесса */
   physaddr_t   page_dir = 0;         /* Физический адрес каталога таблиц  */
   size_t      page_count;            /* Число отображаемых страниц */
   physaddr_t   tmp_paddr = 0;         /* Временный физический адрес */
   s8int      err = -1;            /* Код ошибки отображения страниц */
   int         i = 0;
   size_t      sz = 0;               /* Размер считанных данных */
   process_t*   proc = 0;            /* Описатель процесса */
   thread_t*   thread = 0;            /* Описатель потока */
   u32int      stack = 0;            /* Начало области памяти под стек */
   u32int      stack_size = 0x4000;   /* Размер стека */
   u32int      eflags = 0;            /* буфер для EFLAGS  */
   u32int      seg_size = 0;

   /* Грузим информационную часть ELF-файла */
   elf_sections_t* elf = load_elf(name);

   /* Проверяем, исполняемый ли это файл */
   if (elf->elf_header->e_type != ET_EXEC)
   {
      print_text("This file is not executable...FAIL\n");
      return;
   }

   /* Проверяем, наш ли процессор */
   if (elf->elf_header->e_mashine != EM_386)
   {
      print_text("This file is not for i386 architecture...FAIL\n");
      return;
   }

   /* Клонируем каталог ядра */
   page_dir = clone_kernel_dir(&pdir_vaddr);

   /* Отображаем страницы для загрузки сегментов ELF-файла */
   for (i = 0; i < elf->elf_header->e_phnum; i++)
      seg_size += elf->p_header[i].p_memsz;

   page_count = seg_size / PAGE_SIZE + 1;

   tmp_paddr = alloc_phys_pages(page_count);

   err = map_pages(page_dir,
                 (void*) elf->elf_header->e_entry,
                 tmp_paddr,
                 page_count,
                 0x07);

   if (err == -1)
   {
      print_text("Memory mapping error...FAIL\n");
      return;
   }

   /* Отображаем страницы под стек сразу за страницами программы */
   stack = elf->elf_header->e_entry + page_count*PAGE_SIZE;

   page_count = stack_size / PAGE_SIZE;

   tmp_paddr = alloc_phys_pages(page_count);

   err = map_pages(page_dir,
                (void*) stack,
                tmp_paddr,
                page_count,
                0x07);

   if (err == -1)
   {
      print_text("Memory mapping error...FAIL\n");
      return;
   }

   /* Копируем сегменты исполняемого файла в память */
   asm volatile ("cli");

   /* Для этого переключаемся на новую директорию страниц!!! Иначе схлопочем #PF */
   write_cr3(page_dir);

   for (i = 0; i < elf->elf_header->e_phnum; i++)
   {
          /* Встаем на нужную позицию в файле */   
               fseek(elf->file, elf->p_header[i].p_offset);

      /*  Читаем ненулевую часть из файла в память */
      sz = fread(elf->file, (void*) elf->p_header[i].p_vaddr, elf->p_header[i].p_filesz);
      /* Если есть пустое место в сегменте - забиваем его нулями */
      memset((void*) (elf->p_header[i].p_vaddr + sz), 0, elf->p_header[i].p_memsz - sz);
   }

   /* Формируем стековый кадр */
   u32int* esp = (u32int*) (stack + stack_size);

   asm volatile ("pushf; pop %0":"=r"(eflags));

   eflags |= (1 << 9);

   esp[-1] = elf->elf_header->e_entry;
   esp[-3] = eflags;

   /* Работа с новым ВАП окончена - переключаемся назад на каталог ядра */
   write_cr3(get_kernel_dir());

   /* Создаем процесс */
   proc = (process_t*) kmalloc(sizeof(process_t));

   proc->page_dir = page_dir;
   proc->pid = next_pid++;
   proc->list_item.list = NULL;
   strcpy(proc->name, name);
   proc->suspend = false;
   proc->threads_count = 1;

   list_add(&process_list, &proc->list_item);

   /* Создаем главный поток процесса - вручную создаем, из-за кастомного размещения стека не в куче ядра */
   thread = (thread_t*) kmalloc(sizeof(thread_t));

   thread->id = next_thread_id++;
   thread->process = proc;
   thread->entry_point = elf->elf_header->e_entry;
   thread->list_item.list = NULL;
   thread->stack = (void*) stack;
   thread->stack_size = stack_size;
   thread->stack_top = stack + stack_size;
   thread->esp = stack + stack_size - 12;

   list_add(&thread_list, &thread->list_item);

   asm volatile ("sti");
}

Вот примерно так всё и устроено :)

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

P.S.: Ааааа! Всё отлаживается!!! 8-) Достаточно настроить удаленную отладку в проекте приложения, и при запуске ВМ, а затем профиля отладки, срабатывает останов на main(). Черт, я обожаю Eclipse!!! :)
Изображение

Add: Решил вопрос с динамической памятью. Оказалось довольно просто - если у каждого из процессов ВАП имеет один и тот же диапазон виртуальных адресов, то никто не мешает при создании процесса создать и инициализировать его "кучу", место её расположения в ВАП будет фиксированным, а размещение её в физической памяти - прерогатива ядра.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 31 авг 2013, 18:38 

Зарегистрирован: 10 май 2007, 11:33
Сообщения: 1206
Я большую часть работы выполняю в контексте первичного потока нового процесса - не нужно делать лишние переключения ВАП. В ВАП отображаю только каталог текущего процесса и на время каталог создаваемого процесса. Кстати работа в контексте процесса, у которого пока нет ни одного готового к выполнению или ожидающего потока, тоже является весьма сомнительным занятием.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 01 сен 2013, 17:04 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
Покумекал на тему запуска процессов пользовательского режима - до этого у меня были процессы в ring 0. Заодно пришел к хорошему алгоритму создания потока в пользовательском режиме. Натолкнула на эту мысль меня цитата
phantom-84 писал(а):
Нет. Поток может работать в обоих режимах. Любой поток начинает свою работу в режиме ядра и имеет стек ядра. Но прикладные потоки потом переключаются в user mode (и могут в любой момент повторно войти в kernel mode). А потоки ядра продолжают работу в kernel mode на всем протяжении своей жизни. Естественно, для единообразия и для большей надежности для них тоже можно переустанавливать поле esp0 - я так и делаю, хотя в принципе это не обязательно.

Я подумал, что свою жизнь в режиме ядра поток может начинать с исполнения функции перехода в ring 3, которая затем передаст управление в саму функцию потока/точку входа в программу, загруженную из ELF.

Для потоков в режиме ядра формировался такой стек
Изображение

Для потоков, работающих в ring 3 сделал такой
Изображение
При переключении задач:
1. Планировщик передает управление user_mode_switch(...), которой в качестве параметров нужны - точка входа в поток ring 3 и вершина прикладного стека
2. Параметры мы заранее располагаем в стеке, так, как они лежали бы при обычном вызове user_mode_switch(...)
3. Происходит переключение селекторов и передача управления на запускаемый поток.
Всё! :) Для того чтобы до этого допереть, потребовались сутки возни в отладчике, благо он имеется...


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 01 сен 2013, 19:36 

Зарегистрирован: 10 май 2007, 11:33
Сообщения: 1206
maisvendoo писал(а):
Я подумал, что свою жизнь в режиме ядра поток может начинать с исполнения функции перехода в ring 3, которая затем передаст управление в саму функцию потока/точку входа в программу, загруженную из ELF.
О чем я и говорил в последнем посте. Только у меня для первичного потока нового процесса это не просто функция перехода в user mode, а полноценная функция связывания прикладного пространства с исполняемым файлом (обычно я не загружаю код/данные сразу, а лишь указываю куда и откуда они должны быть загружены). Для первичного потока в этой же функции создается и прикладной стек. В родительском процессе я только проверяю наличие исполняемого файла, потом сразу создаю новый процесс с его первичным потоком и передаю параметры запуска.

Цитата:
Для потоков в режиме ядра формировался такой стек...

Для потоков, работающих в ring 3 сделал такой...
Это вариант. Я поступаю немного по-другому. Использую стартовый стековый фрейм одного и того же формата, но для прикладных потоков указываю в качестве стартового адреса потока адрес одной из двух служебных функций в зависимости от типа создаваемого прикладного потока (первичный или нет), а доп. параметры запуска передаю на дне стека ядра этого потока. Для потоков ядра непосредственно указываю их стартовый адрес.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 01 сен 2013, 19:54 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
phantom-84 писал(а):
не просто функция перехода в user mode, а полноценная функция связывания прикладного пространства с исполняемым файлом (обычно я не загружаю код/данные сразу, а лишь указываю куда и откуда они должны быть загружены)

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


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 160 ]  На страницу Пред.  1 ... 10, 11, 12, 13, 14, 15, 16  След.

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 86


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
Создано на основе phpBB® Forum Software © phpBB Group
Русская поддержка phpBB