Разбираемся с переключением контекста CPU в Linux
Linux — многозадачная операционная система, которая поддерживает гораздо больше задач, выполняемых одновременно, чем число доступных CPU. Однако эти задачи на самом деле не выполняются одновременно; скорее, система переключает CPU между ними очень быстро, создавая иллюзию многозадачности.
Подписывайтесь на канал usr_bin, где я публикую много полезного по Linux, в том числе ссылки на статьи в этом блоге.
Перед запуском каждой задачи CPU должен знать, откуда она загружена и где она должна начать выполняться. Это означает, что система должна заранее настроить регистры CPU и счетчик программ (PC) для нее.
Регистры CPU — это небольшие, быстрые блоки памяти, встроенные в CPU. Счетчик программ используется для хранения текущей инструкции, выполняемой CPU, или адреса следующей инструкции, которая будет выполнена. Это существенные зависимости для CPU, необходимые для выполнения любой задачи, и поэтому они называются контекстом CPU.
Понимая, что такое контекст CPU, легко понять переключение контекста CPU. Переключение контекста CPU включает сохранение контекста CPU (регистры CPU и счетчик программ) предыдущей задачи, загрузку контекста новой задачи в эти регистры и счетчик программ, а затем переход в новое место, указанное счетчиком программ, для запуска новой задачи.
Сохраненные контексты хранятся в ядре системы и загружаются обратно при повторном планировании задачи, чтобы гарантировать, что исходное состояние задачи не будет затронуто, поэтому создается впечатление, что задача выполняется непрерывно.
В зависимости от типа задачи переключение контекста CPU можно разделить на несколько различных сценариев: переключение контекста процесса, переключение контекста потока и переключение контекста прерывания.
В этой статье поговорим о том, как понимать различные сценарии переключения контекста и почему они могут приводить к проблемам с производительностью CPU.
Переключение контекста процесса
Linux делит адресное пространство процесса на пространство ядра и пространство пользователя в соответствии с уровнями привилегий, соответствующими уровням привилегий CPU кольца 0 и кольца 3, как показано на схеме ниже.
- Пространство ядра (кольцо 0) имеет наивысшие привилегии и может напрямую получать доступ ко всем ресурсам.
- Пользовательское пространство (кольцо 3) может получить доступ только к ограниченным ресурсам и не может напрямую получить доступ к памяти и другим аппаратным устройствам. Оно должно войти в ядро через системный вызов, чтобы получить доступ к этим привилегированным ресурсам.
Если посмотреть на это с другой точки зрения, процесс может работать как в пространстве пользователя, так и в пространстве ядра. Когда процесс работает в пространстве пользователя, он называется пользовательским режимом, а когда он переходит в пространство ядра, он называется режимом ядра.
Переход из пользовательского режима в режим ядра осуществляется через системный вызов. Например, когда мы просматриваем содержимое файла, для завершения операции требуется несколько системных вызовов: сначала системный вызов open() используется для открытия файла, затем системный вызов read() используется для чтения содержимого файла, затем системный вызов write() записывается в стандартный вывод, и, наконец, системный вызов close() используется для закрытия файла.
Происходит ли переключение контекста CPU во время системного вызова? Ответ — да.
Необходимо сохранить исходную позицию инструкции пользовательского режима в регистрах CPU. Затем, чтобы выполнить код режима ядра, регистры CPU необходимо обновить до новой позиции инструкции режима ядра. Наконец, CPU переходит к выполнению задачи ядра в режиме ядра.
После завершения системного вызова регистры CPU должны восстановить сохраненный контекст пользовательского режима, а затем переключиться обратно в пользовательское пространство, чтобы продолжить выполнение процесса. Таким образом, во время системного вызова фактически происходит два переключения контекста CPU.
Однако важно отметить, что во время системного вызова не задействованы ресурсы процесса в пользовательском режиме, такие как виртуальная память, и не происходит переключения процесса. Это отличается от того, что мы обычно называем переключением контекста процесса:
- Переключение контекста процесса означает переключение с одного процесса на другой для выполнения.
- Напротив, во время системного вызова тот же процесс продолжает выполняться.
Поэтому процесс во время системного вызова обычно называют переключением режима привилегий, а не переключением контекста. Однако в действительности переключение контекста CPU все еще неизбежно во время системного вызова.
Так в чем же разница между переключением контекста процесса и системным вызовом?
Во-первых, нужно понять, что процессы управляются и планируются ядром, а переключение процессов может происходить только в режиме ядра. Таким образом, контекст процесса включает в себя не только ресурсы пользовательского пространства, такие как виртуальная память, стек и глобальные переменные, но и состояния пространства ядра, такие как стек ядра и регистры.
В результате переключение контекста процесса включает дополнительный шаг по сравнению с системным вызовом: перед сохранением состояния ядра текущего процесса и регистров CPU необходимо сохранить его виртуальную память, стек и другие ресурсы пользовательского пространства. Аналогично, после загрузки состояния ядра следующего процесса необходимо обновить виртуальную память и пользовательский стек этого процесса.
Как показано на схеме, сохранение и восстановление контекста не является «бесплатным» и требует для своего завершения работы ядра на CPU.
Каждое переключение контекста требует от десятков наносекунд до нескольких микросекунд времени CPU. Это время довольно значительно, особенно когда есть много переключений контекста процесса, которые могут легко заставить CPU тратить много времени на сохранение и восстановление ресурсов, таких как регистры, стеки ядра и виртуальная память, тем самым значительно сокращая фактическое время выполнения процессов. Это также важный фактор, приводящий к увеличению средней нагрузки, как упоминалось в предыдущем разделе.
Кроме того, мы знаем, что Linux управляет отношением между виртуальной памятью и физической памятью через буфер Translation Lookaside (TLB). Когда виртуальная память обновляется, TLB также необходимо очистить, что замедляет доступ к памяти. Особенно в многопроцессорных системах кэш совместно используется несколькими процессорами, поэтому очистка кэша влияет не только на процессы текущего процессора, но и на процессы других процессоров, которые совместно используют кэш.
Зная о потенциальных проблемах с производительностью, связанных с переключением контекста процесса, рассмотрим, когда происходит переключение контекста процесса.
Очевидно, что переключение контекста процесса происходит только тогда, когда требуется планирование процесса. Другими словами, контекст процесса переключается только во время планирования процесса. Linux поддерживает очередь готовности для каждого CPU, которая сортирует активные процессы (т. е. процессы, которые работают или ждут CPU) по приоритету и времени, в течение которого они ждали CPU, а затем выбирает процесс, которому больше всего нужен CPU, т. е. процесс с наивысшим приоритетом и самым длительным временем ожидания CPU для запуска.
Когда же будет запланировано выполнение процесса на CPU?
Наиболее очевидным моментом является момент, когда процесс завершает выполнение и освобождает используемый им CPU, в этот момент из очереди готовых процессов берется новый процесс для запуска. Ниже разберем другие сценариеи, которые также запускают планирование процессов.
Во-первых, чтобы гарантировать, что все процессы могут быть справедливо спланированы, процессорное время делится на сегменты времени, называемые временными срезами, которые затем по очереди назначаются процессам. Когда временной срез процесса исчерпывается, процесс приостанавливается системой, и другой ожидающий процесс планируется для запуска.
Во-вторых, когда системных ресурсов недостаточно (например, недостаточно памяти), процессу приходится ждать, пока ресурсы станут доступны, прежде чем он сможет запуститься, после чего процесс также приостанавливается, а система планирует запуск других процессов.
В-третьих, когда процесс добровольно приостанавливает себя, например, с помощью функции сна, он также будет перенесен.
В-четвертых, когда выполняется процесс с более высоким приоритетом, текущий процесс приостанавливается, чтобы обеспечить возможность выполнения процесса с более высоким приоритетом.
Наконец, когда происходит аппаратное прерывание, процесс на CPU приостанавливается, и в ядре выполняется процедура обработки прерывания.
Крайне важно понимать эти сценарии, поскольку они являются виновниками проблем с производительностью переключения контекста, если они возникают.
Переключение контекста потока
Наибольшее различие между потоками и процессами заключается в том, что поток является базовой единицей планирования, в то время как процесс является базовой единицей владения ресурсами. Проще говоря, когда мы говорим о планировании задач в ядре, мы на самом деле планируем потоки; процессы просто предоставляют потокам ресурсы, такие как виртуальная память и глобальные переменные. Поэтому мы можем понимать потоки и процессы следующим образом:
Если процесс имеет только один поток, процесс можно считать эквивалентным потоку.
Когда процесс имеет несколько потоков, эти потоки совместно используют одну и ту же виртуальную память и глобальные переменные. Эти ресурсы не нужно изменять во время переключений контекста.
Кроме того, потоки имеют свои собственные частные данные, такие как стеки и регистры, которые необходимо сохранять во время переключений контекста.
В этом свете переключения контекста для потоков можно разделить на два случая:
- В первом случае два потока принадлежат разным процессам. В этой ситуации, поскольку ресурсы не являются общими, процесс переключения контекста аналогичен переключениям контекста процесса.
- Во втором случае два потока принадлежат одному и тому же процессу. Здесь, поскольку виртуальная память является общей, коммутатору нужно только изменить частные данные потока, регистры и другие неразделяемые данные.
Из этого также нужно понимать, что хотя оба являются переключениями контекста, переключение между потоками в пределах одного процесса потребляет меньше ресурсов, чем переключение между несколькими процессами. Это одно из преимуществ использования нескольких потоков вместо нескольких процессов.
Прерывание переключения контекста
Для быстрого реагирования на аппаратные события обработка прерываний прерывает нормальное планирование и выполнение процессов и вызывает обработчик прерываний для реагирования на события устройства. При прерывании других процессов необходимо сохранить текущее состояние процесса, чтобы процесс мог возобновить работу из исходного состояния после завершения прерывания.
В отличие от переключения контекста процесса, переключение контекста прерывания не затрагивает пользовательское пространство процесса. Поэтому, даже если прерывание прерывает процесс, находящийся в пользовательском пространстве, нет необходимости сохранять и восстанавливать ресурсы пользовательского пространства процесса, такие как виртуальная память и глобальные переменные. Контекст прерывания включает только состояние, необходимое для выполнения процедуры обслуживания прерывания режима ядра, включая регистры CPU, стек ядра, параметры аппаратных прерываний и т. д.
Для одного и того же CPU обработка прерываний имеет более высокий приоритет, чем процессы, поэтому переключение контекста прерываний не происходит одновременно с переключением контекста процессов. Аналогично, поскольку прерывания прерывают нормальное планирование и выполнение процессов, большинство обработчиков прерываний являются короткими и лаконичными, чтобы выполняться как можно быстрее.
Кроме того, как и переключение контекста процесса, переключение контекста прерывания также потребляет циклы CPU. Чрезмерное переключение может потреблять значительное количество CPU и даже серьезно ухудшать общую производительность системы. Поэтому, когда вы обнаруживаете, что количество прерываний слишком велико, вам необходимо выяснить, не вызовет ли это серьезных проблем с производительностью вашей системы.
На этом все! Спасибо за внимание! Если статья была интересна, подпишитесь на телеграм-канал usr_bin, где будет еще больше полезной информации.