Пользовательское пространство Linux, что это такое

Обновлено: 04.07.2024

Введение в API памяти и пользовательского пространства Linux

М. Джонс
Опубликовано 11 августа 2010 г.

Хотя байт может быть наименьшей адресуемой единицей памяти в Linux®, именно страница служит управляемой абстракцией памяти. Эта статья начинается с обсуждения управления памятью в Linux, а затем исследует методы управления адресным пространством пользователя из ядра.

Память Linux

В Linux память пользователя и память ядра независимы и реализованы в разных адресных пространствах. Адресные пространства виртуализированы, что означает, что адреса абстрагируются от физической памяти (через процесс, подробно описанный ниже). Поскольку адресные пространства виртуализированы, их может быть много. Фактически само ядро ​​находится в одном адресном пространстве, и каждый процесс находится в своем собственном адресном пространстве. Эти адресные пространства состоят из адресов виртуальной памяти, что позволяет многим процессам с независимыми адресными пространствами обращаться к значительно меньшему физическому адресному пространству (физической памяти машины). Это не только удобно, но и безопасно, поскольку каждое адресное пространство является независимым и изолированным, а значит безопасным.

Но с этой безопасностью связаны затраты. Поскольку у каждого процесса (и ядра) могут быть одинаковые адреса, относящиеся к разным областям физической памяти, совместное использование памяти невозможно. К счастью, существует несколько решений. Пользовательские процессы могут совместно использовать память с помощью механизма совместной памяти Portable Operating System Interface for UNIX® (POSIX) ( shmem ) с оговоркой, что каждый процесс может иметь другой виртуальный адрес, относящийся к одной и той же области физической памяти.

Сопоставление виртуальной памяти с физической памятью происходит с помощью таблиц страниц, которые реализованы в базовом оборудовании (см. рис. 1). Само оборудование обеспечивает отображение, но ядро ​​управляет таблицами и их конфигурацией. Обратите внимание, что, как показано здесь, процесс может иметь большое адресное пространство, но оно является разреженным, а это означает, что небольшие области (страницы) адресного пространства ссылаются на физическую память через таблицы страниц. Это позволяет процессу иметь массивное адресное пространство, определенное только для страниц, которые необходимы в любой момент времени.

Рисунок 1. Таблицы страниц обеспечивают сопоставление виртуальных адресов с физическими адресами

Возможность разреженного выделения памяти для процессов означает, что базовая физическая память может быть перераспределена. С помощью процесса, называемого paging (хотя в Linux он обычно называется swap), менее используемые страницы динамически перемещаются на более медленное устройство хранения (например, на диск) для размещения другие страницы, к которым необходимо получить доступ (см. рис. 2). Такое поведение позволяет физической памяти компьютера обслуживать страницы, которые более нужны приложению, при переносе менее необходимых страниц на диск для более эффективного использования физической памяти. Обратите внимание, что некоторые страницы могут ссылаться на файлы, и в этом случае данные могут быть сброшены, если они загрязнены (через кеш страницы), или, если страница чистая, просто отброшена.

Рисунок 2. Обмен данными позволяет лучше использовать пространство физической памяти за счет переноса менее используемых страниц в более медленное и менее дорогое хранилище
Архитектуры без MMU

Не все процессоры имеют MMU. Поэтому дистрибутив uClinux (микроконтроллер Linux) поддерживает единое адресное пространство работы. В этой архитектуре отсутствует защита, обеспечиваемая MMU, но она позволяет Linux работать на процессорах другого класса. См. раздел ресурсов для получения информации о uClinux.

Процесс, с помощью которого выбирается страница для замены в хранилище, называется алгоритмом замены страницы и может быть реализован с использованием ряда алгоритмов (например, наименее использовавшегося). Этот процесс может произойти, когда запрашивается ячейка памяти, страница которой не находится в памяти (отсутствует отображение в блоке управления памятью [MMU]). Это событие называется ошибкой страницы и обнаруживается аппаратным обеспечением (MMU), а затем управляется микропрограммой после возникновения прерывания ошибки страницы. На рисунке 3 показан этот стек.

Linux предоставляет интересную реализацию подкачки, обладающую некоторыми полезными характеристиками. Система подкачки Linux позволяет создавать и использовать несколько разделов подкачки и приоритетов, что позволяет создать иерархию подкачки на устройствах хранения, обеспечивающих различные характеристики производительности (например, подкачка первого уровня на твердотельном диске [SSD] и большее пространство подкачки второго уровня на более медленном устройстве хранения). Присвоение более высокого приоритета подкачке SSD позволяет использовать его до тех пор, пока он не будет исчерпан; только тогда страницы будут записываться в раздел подкачки с более низким приоритетом (более медленный).

Рисунок 3. Адресные пространства и элементы сопоставления виртуальных и физических адресов

Не все страницы подходят для замены.Рассмотрим код ядра, реагирующий на прерывания, или код, управляющий таблицами страниц и логикой подкачки. Это очевидные страницы, которые никогда не следует выгружать, и поэтому они закреплены или постоянно находятся в памяти. Хотя страницы ядра не являются кандидатами на обмен, страницы пользовательского пространства являются кандидатами, но их можно закрепить с помощью функции mlock (или mlockall ), чтобы заблокировать страницу. Это цель функций доступа к памяти пользовательского пространства. Если ядро ​​предположило, что адрес, который передал пользователь, действителен и доступен, в конечном итоге произошла паника ядра (например, из-за того, что пользовательская страница была выгружена, что привело к ошибке страницы в ядре). Этот интерфейс прикладного программирования (API) гарантирует правильную обработку таких крайних случаев.

API ядра

Теперь давайте рассмотрим API-интерфейсы ядра для управления пользовательской памятью. Обратите внимание, что это касается ядра и интерфейса пользовательского пространства, но в следующем разделе рассматриваются некоторые другие API памяти. Функции доступа к памяти пользовательского пространства, которые необходимо изучить, перечислены в таблице 1.

Таблица 1. API доступа к памяти пользовательского пространства
< /tr>

Как и следовало ожидать, реализация этих функций может зависеть от архитектуры. Для архитектур x86 вы можете найти эти функции и символы, определенные в ./linux/arch/x86/include/asm/uaccess.h, с исходным кодом в ./linux/arch/x86/lib/usercopy_32.c и usercopy_64.c.

Роль функций перемещения данных показана на рис. 4, поскольку она относится к типам, используемым для копирования (простой или агрегатный).

Рис. 4. Перемещение данных с помощью API доступа к пользовательской памяти

Функция access_ok

Вы используете функцию access_ok для проверки правильности указателя в пользовательском пространстве, к которому вы собираетесь получить доступ. Вызывающий предоставляет указатель, который относится к началу блока данных, размеру блока и типу доступа (независимо от того, предназначена ли область для чтения или записи). Прототип функции определяется как:

Аргумент типа может быть указан как VERIFY_READ или VERIFY_WRITE. Символ VERIFY_WRITE также определяет, доступна ли область памяти как для чтения, так и для записи. Функция возвращает ненулевое значение, если область, вероятно, доступна (хотя доступ все равно может привести к -EFAULT ). Эта функция просто проверяет, что адрес скорее всего находится в пользовательском пространстве, а не в ядре.

Функция get_user

Чтобы прочитать простую переменную из пользовательского пространства, используйте функцию get_user. Эта функция используется для простых типов, таких как char и int, но для больших типов данных, таких как структуры, вместо этого должна использоваться функция copy_from_user. Прототип принимает переменную (для хранения данных) и адрес в пространстве пользователя для операции чтения:

Функция get_user сопоставляется с одной из двух внутренних функций. Внутри эта функция определяет размер доступной переменной (на основе переменной, предоставленной для хранения результата) и формирует внутренний вызов через __get_user_x. Эта функция возвращает ноль в случае успеха. В целом, функции get_user и put_user работают быстрее, чем их аналоги блочного копирования, и их следует использовать при перемещении небольших типов.

Функция put_user

Вы используете функцию put_user для записи простой переменной из ядра в пространство пользователя. Как и get_user , он принимает переменную (содержащую значение для записи) и адрес пользовательского пространства в качестве цели записи:

Как и get_user , функция put_user внутренне сопоставляется с функцией put_user_x и возвращает 0 в случае успеха или -EFAULT в случае ошибки.

Функция clear_user

Функция clear_user используется для обнуления блока памяти в пространстве пользователя. Эта функция принимает указатель в пользовательском пространстве и размер, равный нулю, который определяется в байтах:

Внутренне функция clear_user сначала проверяет, доступен ли для записи указатель пространства пользователя (через access_ok ), а затем вызывает внутреннюю функцию (закодированную на встроенном ассемблере) для выполнения операции Clear.Эта функция оптимизирована как очень тесный цикл с использованием строковых инструкций с префиксом повторения. Возвращает количество байтов, которые невозможно очистить, или ноль, если операция прошла успешно.

Функция copy_to_user

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

После проверки возможности записи в пользовательский буфер (через access_ok) вызывается внутренняя функция __copy_to_user, которая в свою очередь вызывает __copy_from_user_inatomic (в ./linux/arch/x86/include/asm/uaccessXX.h , где _XX равно 32 или 64 в зависимости от архитектуры). Эта функция (после определения того, следует ли выполнять копирование 1, 2 или 4 байта) наконец вызывает `copy_to_user_ll`, где и выполняется настоящая работа. В сломанном оборудовании (до i486, где бит WP не учитывался в режиме супервизора) таблицы страниц могли измениться в любое время, что требовало закрепления нужных страниц в памяти, чтобы их нельзя было выгрузить во время загрузки. на имя. После i486 процесс представляет собой не более чем оптимизированную копию.

Функция copy_from_user

Функция copy_from_user копирует блок данных из пространства пользователя в буфер ядра. он принимает целевой буфер (в пространстве ядра), исходный буфер (из пользовательского пространства) и длину, определенную в байтах. Как и в случае с copy_to_user , функция возвращает ноль в случае успеха и ненулевое значение, если не удалось скопировать некоторое количество байтов.

Функция начинается с проверки возможности чтения из исходного буфера в пользовательском пространстве (через access_ok), затем вызывается __copy_from_user и, наконец, __copy_from_user_ll. Отсюда, в зависимости от архитектуры, делается вызов для копирования из пользовательского буфера в буфер ядра с обнулением (недоступных байтов). Оптимизированные функции сборки включают возможность управления.

Функция strnlen_user

Функция strnlen_user используется так же, как и функция strnlen, но предполагает, что буфер доступен в пользовательском пространстве. Функция strnlen_user принимает два аргумента: адрес буфера пространства пользователя и максимальную длину для проверки.

Функция strnlen_user сначала проверяет доступность чтения пользовательского буфера с помощью вызова access_ok . Если она доступна, вызывается функция strlen, а аргумент максимальной длины игнорируется.

Функция strncpy_from_user

Функция strncpy_from_user копирует строку из пользовательского пространства в буфер ядра с учетом исходного адреса пользовательского пространства и максимальной длины.

В качестве копии из пользовательского пространства эта функция сначала проверяет доступность буфера для чтения с помощью access_ok . Подобно copy_from_user, эта функция реализована как оптимизированная функция сборки (внутри ./linux/arch/x86/lib/usercopy__XX._c).

Другие схемы отображения памяти

В предыдущем разделе были рассмотрены методы перемещения данных между ядром и пользовательским пространством (при этом ядро ​​инициирует операцию). Linux предоставляет ряд других методов, которые можно использовать для перемещения данных как в ядре, так и в пользовательском пространстве. Хотя эти методы могут не обязательно обеспечивать идентичные функции, описанные в функциях доступа к памяти пространства пользователя, они схожи в своей способности отображать память между адресными пространствами.

Обратите внимание, что в пользовательском пространстве, поскольку пользовательские процессы находятся в разных адресных пространствах, перемещение данных между ними должно происходить с помощью какого-либо механизма межпроцессного взаимодействия. Linux предоставляет множество схем (например, очереди сообщений), но наиболее примечательной является разделяемая память POSIX (shmem). Этот механизм позволяет процессу создать область памяти, а затем совместно использовать эту область с одним или несколькими процессами. Обратите внимание, что каждый процесс может отображать область общей памяти на разные адреса в соответствующих адресных пространствах. Поэтому требуется адресация с относительным смещением.

Функция mmap позволяет приложению пользовательского пространства создавать сопоставление в виртуальном адресном пространстве. Эта функциональность распространена в определенных классах драйверов устройств (для повышения производительности), позволяя отображать память физического устройства в виртуальное адресное пространство процесса. В драйвере функция mmap реализована через функцию ядра remap_pfn_range, которая обеспечивает линейное отображение памяти устройства в адресное пространство пользователя.

Дальше

В этой статье мы рассмотрели тему управления памятью в Linux (чтобы добраться до точки, лежащей в основе подкачки), а затем рассмотрели функции доступа к памяти в пользовательском пространстве, использующие эти концепции. Перемещение данных между пользовательским пространством и ядром не так просто, как кажется, но Linux включает в себя простой набор API-интерфейсов, которые управляют сложностью этой задачи на разных платформах за вас.

Возможно, вам поручили разработать инфраструктуру приложений на основе контейнеров?Если это так, вы, скорее всего, понимаете ценность, которую контейнеры могут предоставить вашим разработчикам, архитекторам и операционным группам. На самом деле, вы, вероятно, читали о контейнерах и хотите более подробно изучить эту технологию. Однако прежде чем с головой погрузиться в обсуждение архитектуры и развертывания контейнеров в производственной среде, разработчики, архитекторы и системные администраторы должны знать три важные вещи:

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

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

Все процессы выполняют системные вызовы:

Пространство пользователя и пространство ядра — простое пространство пользователя

Поскольку контейнеры являются процессами, они также выполняют системные вызовы:

Пространство пользователя и пространство ядра — простой контейнер

Хорошо, вы понимаете, что такое процесс и что контейнеры — это процессы, но как насчет файлов и программ, которые находятся внутри образа контейнера? Эти файлы и программы составляют так называемое пользовательское пространство. При запуске контейнера программа загружается в память из образа контейнера. После того как программа в контейнере запущена, ей все еще необходимо выполнять системные вызовы в пространстве ядра. Способность пространства пользователя и пространства ядра взаимодействовать детерминированным образом имеет решающее значение.

Иллюстрация контейнеров
Хотите получить больше от универсального базового образа Red Hat (UBI)?

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

Пользовательское пространство — это весь код операционной системы, находящийся вне ядра. Большинство Unix-подобных операционных систем (включая Linux) поставляются с готовыми пакетами всевозможных утилит, языков программирования и графических инструментов — это приложения пользовательского пространства. Мы часто называем это "пользовательской зоной".

Приложения Userland могут включать программы, написанные на C, Java, Python, Ruby и других языках. В мире контейнеров эти программы обычно поставляются в формате образа контейнера, таком как Docker. Когда вы извлекаете и запускаете образ контейнера Red Hat Enterprise Linux 7 из реестра Red Hat, вы используете предварительно упакованное минимальное пространство пользователя Red Hat Enterprise Linux 7, которое содержит такие утилиты, как bash, awk, grep и yum ( чтобы вы могли установить другое программное обеспечение).

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

Пространство ядра

Ядро обеспечивает абстракцию безопасности, оборудования и внутренних структур данных. Системный вызов open() обычно используется для получения дескриптора файла в Python, C, Ruby и других языках. Вы бы не хотели, чтобы ваша программа могла вносить изменения на уровне битов в файловую систему XFS, поэтому ядро ​​обеспечивает системный вызов и обрабатывает драйверы. На самом деле этот системный вызов настолько распространен, что является частью библиотеки POSIX.

Обратите внимание на следующий рисунок, что bash выполняет вызов getpid(), который запрашивает идентификатор своего собственного процесса. Также обратите внимание, что команда cat запрашивает доступ к /etc/hosts с помощью вызова файла open(). В следующей статье мы рассмотрим, как это работает в контейнерном мире, но обратите внимание, что часть кода находится в пользовательском пространстве, а часть — в ядре.

Пространство пользователя и пространство ядра — основные системные вызовы

Обычные программы пользовательского пространства постоянно вызывают системные вызовы для выполнения работы, например:

Вот некоторые программы пользовательского пространства, которые почти напрямую сопоставляются с системными вызовами, например:

Если копнуть глубже, ниже приведены некоторые примеры системных вызовов, которые вызываются перечисленными выше программами. Обычно эти функции вызываются через библиотеки, такие как glibc, или через интерпретатор, такой как Ruby, Python или виртуальная машина Java.

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

Пространство пользователя и пространство ядра — системные вызовы Gears

Чтобы получить представление о том, какие системные вызовы доступны в ядре Linux, посетите справочную страницу системных вызовов. Интересно, что я вызываю эту команду на моем ноутбуке с Red Hat Enterprise Linux 7, но я использую образ контейнера Red Hat Enterprise Linux 6 (он же пространство пользователя), потому что я хочу увидеть системные вызовы, которые были добавлены/удалены в старом ядре:

Обратите внимание на справочной странице, что определенные системные вызовы (также известные как интерфейсы) были добавлены и удалены в разных версиях ядра. Линус Торвальдс и др. др. позаботьтесь о том, чтобы поведение этих системных вызовов было понятным и стабильным. Начиная с Red Hat Enterprise Linux 7 (ядро 3.10) доступно 382 системных вызова. Время от времени добавляются новые системные вызовы, а старые системные вызовы устаревают; это следует учитывать при рассмотрении жизненного цикла контейнерной инфраструктуры и приложений, которые будут работать в ней.

Заключение

Есть несколько важных выводов о пользовательском пространстве и пространстве ядра, которые вам необходимо понять:

  1. Приложения содержат бизнес-логику, но полагаются на системные вызовы.
  2. После компиляции приложения набор системных вызовов, которые оно использует (т. е. на которые опирается), встраивается в двоичный файл (в языках более высокого уровня это интерпретатор или JVM).
  3. Контейнеры не абстрагируются от необходимости совместного использования общего набора системных вызовов пространством пользователя и пространством ядра.
  4. В мире контейнеров это пользовательское пространство объединяется и распределяется по разным узлам, от ноутбуков до рабочих серверов.
  5. В ближайшие годы это создаст проблемы.

Со временем будет сложно гарантировать, что созданный сегодня контейнер будет работать на узлах контейнеров завтрашнего дня. Представьте, что сейчас 2024 год (возможно, у нас наконец-то появятся настоящие ховерборды), а у вас все еще есть приложение на основе контейнера, для которого требуется работающее в рабочей среде пользовательское пространство Red Hat Enterprise Linux 7. Как можно безопасно обновить базовый хост-контейнер и инфраструктуру? Будет ли контейнерное приложение одинаково хорошо работать на любом из последних лучших хостов контейнеров, доступных на рынке?

В разделе "Архитектура контейнеров, часть 2: почему пространство пользователя имеет значение" мы рассмотрим, как взаимосвязь между пространством пользователя и пространством ядра влияет на архитектурные решения, и что вы можете сделать, чтобы свести к минимуму эти проблемы.

Используется ли пространство ядра, когда ядро ​​выполняется от имени пользовательской программы, т. е. системного вызова? Или это адресное пространство для всех потоков ядра (например, планировщика)?

Если первый, значит ли это, что нормальная пользовательская программа не может иметь более 3 ГБ памяти (если деление 3 ГБ + 1 ГБ)? Кроме того, как в этом случае ядро ​​может использовать High Memory, потому что на какой адрес виртуальной памяти будут отображаться страницы из high memory, поскольку 1 ГБ пространства ядра будет логически отображаться?

3 ответа 3

Используется ли пространство ядра, когда ядро ​​выполняется от имени пользовательской программы, т. е. системного вызова? Или это адресное пространство для всех потоков ядра (например, планировщика)?

Прежде чем мы двинемся дальше, мы должны сказать следующее о памяти.

Память делится на две отдельные области:

  • Пространство пользователя, представляющее собой набор местоположений, в которых выполняются обычные пользовательские процессы (т. е. все, кроме ядра). Роль ядра состоит в том, чтобы управлять приложениями, работающими в этом пространстве, от взаимодействия друг с другом и с машиной.
  • Пространство ядра, в котором хранится и выполняется код ядра.

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

Пространственный код ядра имеет свойство запускаться в "режиме ядра", который (на типичном настольном -x86- компьютере) называется кодом, выполняемым в кольце 0. Обычно в архитектуре x86 существует 4 кольца защиты. Кольцо 0 (режим ядра), Кольцо 1 (может использоваться гипервизорами или драйверами виртуальных машин), Кольцо 2 (может использоваться драйверами, хотя я не уверен в этом). Кольцо 3 — это то, под чем работают типичные приложения. Это наименее привилегированное кольцо, и приложения, работающие на нем, имеют доступ к подмножеству инструкций процессора. Кольцо 0 (пространство ядра) является наиболее привилегированным кольцом и имеет доступ ко всем инструкциям машины. Например, «простое» приложение (например, браузер) не может использовать инструкции сборки x86 lgdt для загрузки глобальной таблицы дескрипторов или hlt для остановки процессора.

Если первое, то значит ли это, что нормальная пользовательская программа не может иметь более 3 ГБ памяти (если деление 3 ГБ + 1 ГБ)? Кроме того, как в этом случае ядро ​​может использовать High Memory, потому что на какой адрес виртуальной памяти будут отображаться страницы из high memory, поскольку 1 ГБ пространства ядра будет логически отображаться?

Сайт продукта
Сайт документации

Б.5. Пространство пользователя

«Пространство пользователя» относится к среде выполнения обычных (в отличие от ядра) процессов. Это не обязательно означает, что эти процессы фактически запускаются пользователями, потому что стандартная система обычно имеет несколько «демонов» (или фоновых) процессов, запущенных еще до того, как пользователь откроет сеанс. Процессы демона также считаются процессами пользовательского пространства.

B.5.1. Обработать

Во-первых, процесс может клонировать себя (это называется ветвлением). Ядро выделяет новое (но идентичное) пространство памяти для процесса и другой процесс для его использования. На данный момент единственная разница между этими двумя процессами заключается в их pid . Новый процесс обычно называют дочерним процессом, а исходный процесс, pid которого не меняется, называется родительским процессом.

Иногда дочерний процесс продолжает жить независимо от родительского процесса, при этом его собственные данные копируются из родительского процесса. Однако во многих случаях этот дочерний процесс выполняет другую программу. За некоторыми исключениями, его память просто заменяется памятью новой программы, и начинается выполнение этой новой программы. Это механизм, используемый процессом инициализации (с номером процесса 1) для запуска дополнительных служб и выполнения всей последовательности запуска. В какой-то момент один процесс из потомков init запускает графический интерфейс для входа пользователей (фактическая последовательность событий более подробно описана в Разделе 9.1, «Загрузка системы»).

Когда процесс завершает задачу, ради которой он был запущен, он завершается. Затем ядро ​​восстанавливает память, назначенную этому процессу, и прекращает выделять ему фрагменты времени выполнения. Родительскому процессу сообщается о завершении его дочернего процесса, что позволяет процессу дождаться завершения задачи, которую он делегировал дочернему процессу. Это поведение хорошо видно в интерпретаторах командной строки (известных как оболочки ). Когда команда вводится в оболочку, приглашение возвращается только после завершения выполнения команды. Большинство оболочек позволяют запускать команду в фоновом режиме, достаточно просто добавить & в конец команды. Подсказка сразу же отображается снова, что может привести к проблемам, если команде необходимо отобразить собственные данные.

B.5.2. Демоны

«Демон» — это процесс, автоматически запускаемый последовательностью загрузки. Он продолжает работать (в фоновом режиме) для выполнения задач обслуживания или предоставления услуг другим процессам. Эта «фоновая задача» на самом деле произвольная и не соответствует чему-то конкретному с точки зрения системы. Это просто процессы, очень похожие на другие процессы, которые запускаются по очереди, когда приходит их отрезок времени. Различие только в человеческом языке: о процессе, работающем без взаимодействия с пользователем (в частности, без какого-либо графического интерфейса), говорят, что он работает «в фоновом режиме» или «как демон».

СЛОВАРЬ Демон, демон, уничижительный термин?

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

B.5.3. Межпроцессное взаимодействие

Изолированный процесс, будь то демон или интерактивное приложение, редко полезен сам по себе, поэтому существует несколько методов, позволяющих отдельным процессам взаимодействовать друг с другом либо для обмена данными, либо для управления друг другом. Общий термин, относящийся к этому, — межпроцессное взаимодействие или сокращенно IPC.

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

Если вы не хотите хранить данные на диске, вы можете использовать трубу , которая представляет собой просто объект с двумя концами; байты, записанные на одном конце, читаются на другом. Если концы контролируются отдельными процессами, это приводит к простому и удобному каналу связи между процессами. Каналы можно разделить на две категории: именованные каналы и анонимные каналы. Именованный канал представлен записью в файловой системе (хотя передаваемые данные там не хранятся), поэтому оба процесса могут открыть его независимо, если расположение именованного канала заранее известно. В случаях, когда взаимодействующие процессы связаны (например, родительский и его дочерний процессы), родительский процесс также может создать анонимный канал перед разветвлением, и дочерний процесс наследует его. После этого оба процесса смогут обмениваться данными через канал без использования файловой системы.

НА ПРАКТИКЕ Конкретный пример

Давайте подробно опишем, что происходит, когда сложная команда (конвейер) запускается из оболочки. Мы предполагаем, что у нас есть процесс bash (стандартная пользовательская оболочка в Debian) с pid 4374; в эту оболочку мы вводим команду: ls | сортировать .

Оболочка сначала интерпретирует введенную команду. В нашем случае она понимает, что есть две программы ( ls и sort ), с потоком данных, перетекающим от одной к другой (обозначается символом |, известным как труба ). bash сначала создает безымянный канал (который изначально существует только внутри самого процесса bash).

Однако не все взаимодействия между процессами используются для перемещения данных. Во многих ситуациях единственной информацией, которую необходимо передать, являются управляющие сообщения, такие как «приостановить выполнение» или «возобновить выполнение». Unix (и Linux) предоставляет механизм, известный как сигналы , с помощью которого процесс может просто отправить определенный сигнал (выбранный из предопределенного списка сигналов) другому процессу. Единственное требование — знать pid цели.

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

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

Для типичной Unix-подобной системы вполне стандартно использовать все эти механизмы в разной степени.

B.5.4. Библиотеки

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

стандартная библиотека C ( glibc ), которая содержит основные функции, такие как открытие файлов или сетевых подключений, и другие, облегчающие взаимодействие с ядром;

графические инструменты, такие как Gtk+ и Qt, позволяющие многим программам повторно использовать предоставляемые ими графические объекты;

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

КУЛЬТУРА Путь Unix: одно за другим

Одна из фундаментальных концепций, лежащих в основе семейства операционных систем Unix, заключается в том, что каждый инструмент должен делать только одну вещь, и делать это хорошо; приложения могут затем повторно использовать эти инструменты для создания более сложной логики поверх них. Эту философию можно увидеть во многих воплощениях. Сценарии оболочки могут быть лучшим примером: они собирают сложные последовательности очень простых инструментов (таких как grep, wc, sort, uniq и так далее). Другое воплощение этой философии можно увидеть в библиотеках кода: библиотека libpng позволяет читать и записывать изображения PNG с разными параметрами и разными способами, но она делает только это; не может быть и речи о включении функций, которые отображают или редактируют изображения.

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

Взаимодействие между пользователем и ядром¶

Пространство пользователя взаимодействует с ядром для медленного пути и операций управления ресурсами через символьные устройства /dev/infiniband/uverbsN. Операции быстрого пути обычно выполняются путем записи непосредственно в аппаратные регистры, которые mmap() передаются в пространство пользователя, без системного вызова или переключения контекста в ядро.

Команды отправляются в ядро ​​через write() на этих регистрах. файлы устройства. ABI определяется в файле drivers/infiniband/include/ib_user_verbs.h. Структуры для команд, требующих ответа от ядра, содержат 64-битное поле, используемое для передачи указателя на выходной буфер. Статус возвращается в пространство пользователя как возвращаемое значение системного вызова write().

Управление ресурсами¶

Поскольку создание и уничтожение всех ресурсов IB выполняется командами, передаваемыми через файловый дескриптор, ядро ​​может отслеживать, какие ресурсы присоединены к данному контексту пользовательского пространства. Модуль ib_uverbs поддерживает таблицы idr, которые используются для преобразования между указателями ядра и непрозрачными дескрипторами пользовательского пространства, чтобы указатели ядра никогда не открывались пользовательскому пространству, а пользовательское пространство не могло заставить ядро ​​следовать фиктивному указателю.

Это также позволяет ядро для очистки при завершении процесса и предотвращения прикосновения одного процесса к ресурсам другого процесса.

Закрепление памяти¶

Прямой ввод-вывод в пользовательском пространстве требует, чтобы области памяти, которые являются потенциальными целевыми объектами ввода-вывода, оставались резидентными по одному и тому же физическому адресу. Модуль ib_uverbs управляет закреплением и откреплением областей памяти с помощью вызовов get_user_pages() и put_page(). Он также учитывает объем памяти, закрепленной в файле pinned_vm процесса, и проверяет, не превышает ли непривилегированные процессы лимит RLIMIT_MEMLOCK.

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

/dev-файлы¶

Чтобы автоматически создать соответствующие файлы символьных устройств с помощью udev, введите следующее правило:

можно использовать. Это создаст узлы устройств с именами:

<р>и так далее. Поскольку команды пользовательского пространства InfiniBand должны быть безопасны для использования непривилегированными процессами, может оказаться полезным добавить соответствующий РЕЖИМ или ГРУППУ в правило udev.

Читайте также:

Функция Описание
access_ok Проверяет действительность указателя памяти пользовательского пространства
get_user Получает простую переменную из пространства пользователя
put_user Помещает простую переменную в пространство пользователя
clear_user Очищает или обнуляет блок в пространстве пользователя
copy_to_user Копирует блок данных из ядра в пространство пользователя
copy_from_user Копирует блок данных из пространства пользователя в ядро
strnlen_user Получает размер строкового буфера в пространстве пользователя
strncpy_from_user Копирует строку из пространства пользователя в ядро