Провел небольшой эксперимент с процессами в своей системе. Хотя как сказать - небольшой: пришлось написать простейшую 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.: Ааааа! Всё отлаживается!!!
Достаточно настроить удаленную отладку в проекте приложения, и при запуске ВМ, а затем профиля отладки, срабатывает останов на main(). Черт, я обожаю Eclipse!!!
Add: Решил вопрос с динамической памятью. Оказалось довольно просто - если у каждого из процессов ВАП имеет один и тот же диапазон виртуальных адресов, то никто не мешает при создании процесса создать и инициализировать его "кучу", место её расположения в ВАП будет фиксированным, а размещение её в физической памяти - прерогатива ядра.