Последовательность переключения контекста

  1. Вызов прерывания (например: прерывание по таймеру (trap.c 109))
  2. Вызов планировщика (yield (proc.c 390))
  3. Запустить переключатель контекста (swtch (proc.c 387))

До того, как произойдет прерывание переключения контекста

Vector.S (сгенерировать из Perl-скрипта)

vector 32:
  pushl $0 //error code
  pushl $32 //vector number of timer interrupt
  jmp alltraps

trapasm.S

Ref: ссылки на некоторые регистры

Регистр сегмента сохраняет физический адрес смещения сегмента

 # trap frame
.globl alltraps
alltraps:
  pushl %ds #data segment register
  pushl %es #extra segment register
  pushl %fs #general segment register
  pushl %gs #general segment register
  pushal #all local registers

Настроить сегменты данных

# Set up data segments.
movw $(SEG_KDATA<<3), %ax
movw %ax, %ds
movw %ax, %es
# Call trap(tf), where tf=%esp by calling convention
pushl %esp
call trap

Затем в trap.c 111 вызывается yield () для вызова планировщика.

Кроме того, после вызова планировщика система запускает переключение контекста, указанное ранее в swtch (struct context ** old, struct context * new)

Переключение контекста начинается!

Переключение контекста записывает в сборку и ссылки компоновщиком!

// defs.h 129
void swtch(struct context** old, struct context* new);

swtch.S

swtch:
  movl 4(%esp) %eax # address of **old !!little endian
  movl 8(%esp) %edx # address of *new
  #save all callee registers
  pushl %ebp
  pushl %ebx
  pushl %esi
  pushl %edi
  # change esp pointer 
  movl %esp, (%eax) # (%eax) means *old (points to address of old)
  movl %edx, %esp
  # load new callee-save registers
  # do not pop the EIP since this is the return address
  popl %edi
  popl %esi
  popl %ebx
  popl %ebp
  ret

Напоминание: соглашение о вызовах в стеке

  • локальные переменные | EBP (указатель кадра) | обратный адрес | параметры

Структура данных контекста (proc.h 28)

// reverse order from pushl registers
struct context {
  uint edi;
  uint esi;
  uint ebx;
  uint ebp;
  uint eip;
}

Напоминание: контекст всегда находится на вершине некоторого стека

Инициализация процессора

main.c

// main.c 39
mpmain(); // finish this processor's set up
// Common CPU setup code
static void
mpmain(void)
{
  cprintf("cpu%d: starting %d\n", cpuid(), cpuid());
  idtinit();       // load idt register
  xchg(&(mycpu()->started), 1); // tell startothers() we're up
  scheduler();     // start running processes
}

Что делает xchg (volatile uint * addr, uint newval)?

xchg произвел обмен значениями с хранилищем значений в addr, которое используется для реализации блокировки.

Образец из курса MIT:

xchg% eax, адрес

  1. освободить другие действия ЦП для доступа к адресу addr
  2. tmp: = * адрес
  3. * адрес: =% eax
  4. % eax: = tmp
  5. Разморозить деятельность других ЦП по адресу адреса

Вернемся к нашей теме, сосредоточимся на функции scheduler ():

Что он делает, так это после инициализации всех требований, которые нам нужны для XV6, он запускает одну процедуру из текущего контекста (планировщик процессора)

proc.c

// proc.c 326
void
scheduler(void)
{
  ...
  // inside loop of the ptable
  acquire(&ptable.lock);
  for(p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
    if(p->state != RUNNABLE)
      continue;
    // switch to chosen process
    c->proc = p;
    switchuvm(p);
    p->state = RUNNING;
    
    // do context switch
    // c->scheduler points to current context & restore the context of p
    swtch(&(c->scheduler), p->context );
    switchkvm();
    c->proc = 0;
 }
}

Затем мы знаем, что контекст переключается на первый запускаемый процесс.

Однако как XV6 подготавливает контекст для процесса?

Как создается контекст?

Что ж, когда первый пользовательский процесс создается в userinit () или выполняет процесс fork (), они оба вызывают allocproc, чтобы найти НЕИСПОЛЬЗУЕМЫЙ процесс в ptable.

статическая структура proc * allocproc (void) (proc.c 75)

static struct proc* 
allocproc(void)
{
// after finding an UNUSED process
found:
  p->state = EMBRYO;
  p->pid = nextpid++;
  
  release(&ptable.lock);
  // Allocate kernel stack.
  if((p->kstack = kalloc()) == 0){
    p->state = UNUSED;
    return 0;
  }
  // stack pointer
  sp = p->kstack + KSTACKSIZE;
  //Leave room for trap frame
  sp -= sizeof *p->tf;
  p->tf = (struct trapframe*)sp;
  
  // Set up new context to start executing at forkret,
  // which returns to trapret.
  sp -= 4;
  *(uint*)sp = (uint)trapret;
  sp -= sizeof *p->context;
  p->context = (struct context*)sp;
  memset(p->context, 0, sizeof *p->context);
  // that's why it is not required to push eip in swtch.S
  p->context->eip = (uint) forkret;
  return p;  
}

Узнайте больше о переключении контекста

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

недействительный график (недействителен) (proc.c 371)

swtch(&p->context, mycpu()->scheduler);

# move esp value to what %eax points to
movl %esp, (%eax) # (%eax) means *old (points to address of old)
movl %edx, %esp

Пусть указатель стека указывает на контекст, который мы хотим переключить. Вот адрес расписания процессора.

movl %edx, %esp

Теперь мы должны знать весь процесс переключения контекста!

Последовательность переключения контекста

  1. Прервать текущий процесс. Затем переключитесь на ›планировщик процессора.
  2. Планировщик находит RUNNABLE proc в таблице ptable и переключает контекст с планировщика на другой RUNNABLE процесс

Процесс - ›Планировщик -› Процесс

Планировщик - это мост между двумя процессами

Следующее - как вернуться в пользовательское пространство?

После переключения контекста

Место назначения: вернуться на уровень пользователя

Стратегия: Calling Convention! Используйте EIP, чтобы получить обратный адрес

switch.S - ›sched (proc.c) -› yield (proc.c) - ›trap (trap.c) -› trapret (trapasm.S, но настраивается в allocproc ()) - ›iret ( trapasm.S)

Напоминание: почему XV6 должен помещать ESP в стек?

Ответ: как параметр для void trap (struct trapframe * tf)

Резюме

В этом посте кратко описан процесс переключения контекста в небольшой системе XV6.

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

Кроме того, планировщик является мостом, который помогает процессам в этом.

использованная литература

  1. блестящий курс ОС Антона Бурцева в UC Irvine
  2. MIT 6.828 Разработка операционной системы