Быстрый переход по пути системных вызовов на xv6

В этой статье мы не будем сосредотачиваться ни на том, как реализовать системные вызовы, ни на концепции, лежащей в основе всего. Вместо этого мы кратко рассмотрим путь к системному вызову в коде после вызова из программы.

usys.S

Взгляните на этот код. Это называется макросом, вы можете думать о макросе как о функции, которую компилятор заменяет шаблоном до того, как будет выполнена настоящая компиляция.

Возьмем системный вызов getpid и посмотрим, как он будет заменен в этом макросе.

Теперь ассемблерный код понятен: он определит глобальное имя под названием getpid, а затем опишет, что он делает. getpid переместит номер SYS_getpid, определенный в syscall.h, в регистр % eax и вызовет прерывание ( INT для краткости) с номером T_SYSCALL (64 и определено в traps.h).

вектор.S

vector.S определяет гигантскую и скучную таблицу векторных изображений. Важно то, что INT $ 64 выполнит некоторые действия, а затем перейдет к инструкции vector64, и эта команда поместит 0 и 64 в стек перед окончательным вызовом alltraps . Важно знать, что структура на C - это просто способ абстрагироваться от нескольких последовательных байтов, теперь я могу сказать вам, что мы уже создаем trapframe (см. x86.h ), а 64 - это атрибут trapno.

trapasm.S

Был вызван alltraps, и он завершит построение trapframe. Поскольку для этого мы используем стек памяти, мы строим структуру снизу вверх: после errno у нас есть ds, es, fs и gs, и это именно то, что alltraps помещает в стек (поскольку они короткие, а регистры длинные, trapframe.h заполняет пустые места отступами). И pushal подтолкнет регистры общего использования (eax, ecs, …, esi , edi). Я не знаю, почему он помещает SEG_KDATA * 8 в % ax, % ds и % es, но это так (полезные сведения в конце ). И, наконец, вызывает trap (), передавая начало trapframe в качестве параметра.

Чтобы не возвращаться к этому коду, давайте теперь рассмотрим инструкцию trapret: она восстанавливает все регистры, сохраненные в trapframe, и именно так программа продолжает работу после выполнения ловушки. Достаточно сборки для этой статьи.

trap.c

Инструкция INT привела нас к функции trap после сборки trapframe. errno - это значение, помещенное в vector.S, и соответствует T_SYSCALL, попадая в первое значение if, установите myproc () - ›tf перед вызовом syscall ().

syscall.c

Это так же просто, как и выше, но использует классный трюк, который может быть не понятен программистам, не владеющим C. Давайте посмотрим на определение системных вызовов и разберемся с ним.

Что это значит? Эта сигнатура означает массив указателей на функции, который принимает в качестве параметра void и возвращает int. Таким образом, внутри фигурных скобок ожидаются функции с подписью типа int function (void), и это именно то, что sys_fork, sys_exit, sys_wait (и так далее) есть. И что слева от каждой функции? Это просто индекс, в который мы хотим поместить элемент в массив, числа которого определены в syscall.h.

Хорошо, теперь перейдем к функции syscall (): она принимает значение регистра eax, которое мы поместили в usys.S с соответствующим Число SYS_ *, вызывает sys_syscall из массива (это syscalls [num] ()) и помещает значение результата в регистр eax (это curproc - ›tf-› eax =…).

sysproc.c

Возвращаясь к примеру, мы можем найти объявление int sys_kill (void) в файле syscall.c и его реализацию в sysproc.c, что оно делает? Он пытается получить значение int из стека процессов (манипулируя регистром % esp) и сохранить его значение в переменной pid, если все работает, он, наконец, вызывает функцию, которая найдет процесс с этим id и убивает его.

Почему sys_ функционирует?

Зачем связывать системные вызовы с функцией sys_? a Что ж, без этого нам пришлось бы писать инструкцию сборки для каждого системного вызова, которая брала бы правильные параметры из стека вместо простого макроса (# define) и хороший массив функций в syscall.c не будут работать из-за разных сигнатур.

Где инструкция int (прерывание)?

Это определено в архитектуре x86, и я не буду вдаваться в подробности, но если вам нужна эта информация, вы можете начать поиск по этой ссылке, которая ссылается на огромное руководство по системному программированию Intel.

Ловушка построена на стеке? Переключение контекста произошло?

Эта статья с хорошими иллюстрациями объясняет, как xv6 переключает контекст и помогает визуализировать стек, когда происходит прерывание.

Это не подход с глубокой концепцией и не подход с поверхностным кодом: как программист мне нравится хорошо понимать, как все работает в теории и в реализации. Руководство xv6 дает хорошее (но не полное) объяснение теории, поэтому я решил дополнить объяснение реального кода.