2015-04-29 3 views
1

Я пытаюсь создать «общую картину» того, как все работает в ядре Linux и в пользовательском пространстве, и я совершенно смущен. Я знаю, что пользовательское пространство использует системные вызовы для «разговора» с ядром, но я не знаю, как это сделать. Я попытался прочитать библиотеку C и исходные коды ядра, но они сложны и нелегко понять. Я также прочитал несколько книг, касающихся концептуальных фактов об операционных системах, таких как управление процессами, памятью, устройствами, но они не делают ядро ​​«переход» (userpace-> kernel) понятным. Итак, где происходит переход между пользовательским пространством и пространством ядра? Как библиотека C запускает код, находящийся внутри ядра Linux, работающего на машине?Как ядро ​​Linux «слушает» библиотеку C?

Чтобы сделать аналогию: представьте, что есть дом. Дом заперт. Ключ к открытию дома находится внутри самого дома. В доме есть только один человек, ядро. Пользовательское пространство - это кто-то пытается войти в дом. Мой вопрос будет следующим: как ядро ​​знает, что кто-то за пределами дома хочет ключ, и какой механизм позволяет открыть дом с помощью этого ключа?

+2

Они используют дверной звонок. Этот вопрос зависит от платформы. На 'x86-64' ядро ​​настраивает процессор для отправки' syscall' в ядро. 'syscall' - инструкция сборки, которая по аналогии действует как дверной звонок. –

+0

Что вы подразумеваете под «ядро устанавливает процессор для отправки syscall в ядро»? – nowat

+0

В общем, ядро ​​настраивает процессор, записывая значения в определенные регистры и места в памяти. Это разумно сделать, как только ядро ​​загрузится при загрузке. Эта информация используется процессором для обработки инструкции 'syscall' и множества других вещей, как предполагалось ядром. –

ответ

9

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

Так процедура выглядит так:

  1. Прежде всего, необходимо знать номер системного вызова. Каждый syscall имеет уникальный номер, и внутри ядра есть таблица, которая сопоставляет эти числа определенным функциям. Каждая архитектура имеет разную таблицу с одним и тем же номером на двух разных архитектурах, которые могут отображать различные системные вызовы.
  2. Затем вы настраиваете свои аргументы. Это также специфично для архитектуры, но не сильно отличается от передачи аргументов между обычными вызовами функций. Обычно вы ставите свои аргументы в конкретный регистр процессора. Это описано в ABI этой архитектуры.
  3. Затем вы вводите системный вызов. В зависимости от архитектуры это может означать причинение некоторого исключения или использование выделенной команды CPU.
  4. Ядро имеет специальную функцию обработчика, которая запускается в режиме ядра при вызове syscall. Он приостанавливает выполнение процесса, сохраняя всю информацию, относящуюся к этому процессу (это называется context switch), прочитайте номер и аргументы системного вызова и вызовите правильную процедуру syscall. Он также обязательно поставит возвращаемое значение в нужное место для чтения в пользовательском пространстве и заплатит процесс обратно, когда будет выполняться процедура syscall (восстановление его контекста).

В качестве примера, чтобы ядро ​​знает, что вы хотите вызвать системный вызов на x86_64 можно использовать sysenter команды с номером в %rax системных вызовах регистра.Аргументы передаются с использованием регистров (если я правильно помню) %rdi, %rsi, %rdx, %rcx, %r8 и %r9.

Вы также можете использовать старый способ, который использовался на 32-разрядных процессорах x86 - программный номер прерывания 0x80 (int 0x80 инструкция). Опять же, номер системного вызова указан в %rax. Регистр и аргументы идут (опять же, если я не ошибаюсь) %ebx, %ecx, %edx, %esi, %edi, %ebp.

ARM очень похож - вы будете использовать инструкцию «supervisor call» (SVC#0). Номер вашего системного номера перейдет в регистр r7, все аргументы перейдут на регистры r0-r6, а возвращаемое значение syscall будет сохранено в r0.

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

+0

Являются ли эти регистры виртуальными или физическими? (ядро создает виртуальную память для каждого процесса, регистры также являются виртуальными? или библиотека C записывает прямо в процессор при настройке системного вызова?) – nowat

+0

@yuri: Я не знаю, что вы подразумеваете под виртуальным регистром. В пользовательском пространстве всегда используются реальные регистры процессора. В системе может быть несколько процессов, но только один может работать на каждом процессоре в то время. Если этот процесс приостановлен, происходит переключение контекста, и все значения регистра сохраняются в памяти. Они восстанавливаются, когда процесс снова запланирован. Это то, как регистры никогда не перезаписываются другим процессом, даже если все они разделяют их. –

+0

Вот что я имел в виду, спасибо. – nowat

1

Многие процессоры имеют инструкцию для вызова определенной «ловушки» или «прерывания», ядро ​​Linux настраивает такую ​​«ловушку» или «прерывание» специально для системных вызовов.

Библиотека настраивает регистры процессора определенным образом, а затем выполняет специальную команду прерывания или прерывания, которая заставляет процессор входить в привилегированный режим и вызывать функцию обработчика ловушек/прерываний ядра, которая декодирует значения в регистрирует и вызывает соответствующую функцию для обработки системного вызова.

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