Перед стартом

В этом разделе мы поговорим о том, как после подготовки нескольких ресурсов для операционных систем (распределители страниц, таблица страниц ядра, таблица процессов и т. Д.) Создается и работает первый процесс.

Шаги создания первого процесса следующие:

  1. Выделить структуру процесса (контекст, каталог таблицы страниц, указатель стека ядра…)
  2. Создайте таблицу страниц для пространства ядра
  3. Выделить страницу для кода инициализации пользователя
  4. Настроить фрейм-ловушку для 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

  1. Подготовьте кадр прерывания в 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 (), которая найдет ВЫПОЛНЯЕМЫЙ процесс и выполнит переключение контекста.

Резюме

В этом посте рассказывается, как начинается первый процесс. Он делает следующее:

  1. Подготовьте пространство памяти ядра (userinit (), setupkvm ())
  2. Подготовьте стек ядра (allocproc ())
  3. Подготовьте пространство пользовательской памяти (userinit (), inituvm ())
  4. Настройте Trap Frame (allocproc (), userinit ())
  5. Настройте Контекст (allocproc ())

Наконец, у нас есть хорошо подготовленный процесс для запуска XV6. Ага!

Ссылка

  1. блестящий курс ОС Антона Бурцева в UC Irvine