Перед стартом
В этом разделе мы поговорим о том, как после подготовки нескольких ресурсов для операционных систем (распределители страниц, таблица страниц ядра, таблица процессов и т. Д.) Создается и работает первый процесс.
Шаги создания первого процесса следующие:
- Выделить структуру процесса (контекст, каталог таблицы страниц, указатель стека ядра…)
- Создайте таблицу страниц для пространства ядра
- Выделить страницу для кода инициализации пользователя
- Настроить фрейм-ловушку для iret
Выделить структуру процесса
Загляните в proc.h. В xv6 есть структура под названием proc, иллюстрирующая структуру процесса.
struct proc { uint sz; // Size of process memory (bytes) pde_t* pgdir; // pointer for Page table Directory char *kstack; // pointer that points to the bottom of the kernel stack for this process enum procstate state; // Process state int pid; // Process ID struct proc *parent; // Parent Process struct trapframe *tf; // pointer points to trapframe of current syscall struct context *context; // swtch() here to run process void *chan; // If non-zero, sleeping on chan; for multi-process void *killed; // If non-zero, have been killed struct file *ofile[NOFILE]; //Open files struct inode *cwd; // Current directory char name[16]; // Process name (debugging) }
Эта структура процедуры включает в себя несколько концепций, таких как синхронизация, управление памятью, ловушка / прерывание, файловые системы.
void userinit(void) { struct *p; extern char _binary_initcode_start[], _binary_initcode_size[]; // initialize a runnable process // prepare kstack, trapframe, trapret p = allocproc(); ..... }
Создайте таблицу страниц для пространства ядра
В void userinit (void) (proc.c) он устанавливает первый пользовательский процесс. Затем посмотрите, как он создает таблицу страниц.
static struct proc *initproc; void userinit(void) { struct *p; extern char _binary_initcode_start[], _binary_initcode_size[]; // alloc resources for proc p = allocproc(); // initproc is a static global pointer initproc = p; // setup kernel virtual memory if(p->pgdir = setupkvm()==0) panic("userinit: out of memory?"); .... }
Как работает setupkvm (), мы начнем еще одну публикацию, чтобы обсудить ее подробнее. В общем, этот метод получает свободное пространство из списка фрилансеров и отображает виртуальную память на физическую.
Важно отметить, что при назначении таблицы страниц ядра все процессы используют одну и ту же таблицу страниц ядра. Это сделано для экономии ограниченного пространства в памяти XV6 (только 234 МБ). Реализация находится в pte_t * walkpgdir (pde_t * pgdir, const void * va, int alloc). Зациклив ту же struct kmap в vm.c, становится очевидным, что только одна копия таблицы страниц ядра среди всех процессов.
Выделить страницу для кода инициализации пользователя
Тем не менее, посмотрите на фрагмент кода в proc.c
// init user virtual memory inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size);
inituvm (pde_t * pgdir, char * init, uint, sz) (vm.c)
void inituvm(pde_t *pgdir, char *init, uint sz) { char *mem; if(sz >= PGSIZE) panic("inituvm: more than a page"); mem = kalloc(); memset(mem, 0, PGSIZE); // PTE_U means user accessible mappages(pgdir, 0, PGSIZE, V2P(mem), PTE_W|PTE_U); memmove(mem, init, sz); }
Судя по всему, inituvm назначает пробелы в списке freelist для данных _binary_initcode_start.
Настроить фрейм-ловушку для iret
- Подготовьте кадр прерывания в allocproc (void) (proc.c 76)
static struct proc* allocproc(void) { ..... // stack ptr sp = p->kstack+KSTACKSIZE; // Leave room for trap frame sp -= sizeof *p->tf; p->tf = (struct trapframe*)sp; ... }
2. Установите значения в кадре ловушки (userinit proc.c)
void userinit(void) { ... // set up trap frame memset(p->tf, 0, sizeof(*p->tf)); // Descriptor privilege level to 3 p->tf->cs = (SEG_UCODE << 3) | DPL_USER; p->tf->ds = (SEG_UDATA << 3) | DPL_USER; p->tf->es = p->tf->ds; p->tf->ss = p->tf->ds; p->tf->eflags = FL_IF; p->tf->esp = PGSIZE; p->tf->eip = 0; // beginning of initcode.S ... }
Ну, здесь настраивается уровень привилегий дескриптора на 3, который используется для управления привилегиями. Этот вопрос важен, когда речь идет о системном вызове. Это будет еще один пост, посвященный этой проблеме.
Остальная часть userinit должна изменить cwd на «/» и обновить состояние процесса на RUNNABLE.
Затем, после завершения создания первого процесса, main.c запустит mpmain (), которая найдет ВЫПОЛНЯЕМЫЙ процесс и выполнит переключение контекста.
Резюме
В этом посте рассказывается, как начинается первый процесс. Он делает следующее:
- Подготовьте пространство памяти ядра (userinit (), setupkvm ())
- Подготовьте стек ядра (allocproc ())
- Подготовьте пространство пользовательской памяти (userinit (), inituvm ())
- Настройте Trap Frame (allocproc (), userinit ())
- Настройте Контекст (allocproc ())
Наконец, у нас есть хорошо подготовленный процесс для запуска XV6. Ага!
Ссылка
- блестящий курс ОС Антона Бурцева в UC Irvine