Что такое контекст процессора

Обновлено: 04.07.2024

Потоки, процессы, контексты.

Системный вызов (syscall). Данное понятие, вы будете встречать достаточно часто в данной статье, однако несмотря на всю мощь звучания, его определение достаточно простое :) Системный вызов — это процесс вызова функции ядра, из приложение пользователя. Режим ядра — код, который выполняется в нулевом кольце защиты процессора (ring0) с максимальными привилегиями. Режим пользователя — код, исполняемый в третьем кольце защиты процессора (ring3), обладает пониженными привилегиями. Если код в ring3 будет использовать одну из запрещенных инструкций (к примеру rdmsr/wrmsr, in/out, попытку чтения регистра cr3, cr4 и т.д.), сработает аппаратное исключение и пользовательский процесс, чей код исполнял процессор в большинстве случаях будет прерван. Системный вызов осуществляет переход из режима ядра в режим пользователя с помощью вызова инструкции syscall/sysenter, int2eh в Win2k, int80h в Linux и т.д.

И так, что же такое поток? Поток (thread) — это, сущность операционной системы, процесс выполнения на процессоре набора инструкций, точнее говоря программного кода. Общее назначение потоков — параллельное выполнение на процессоре двух или более различных задач. Как можно догадаться, потоки были первым шагом на пути к многозадачным ОС. Планировщик ОС, руководствуясь приоритетом потока, распределяет кванты времени между разными потоками и ставит потоки на выполнение.

На ряду с потоком, существует также такая сущность, как процесс. Процесс (process) — не что более иное, как некая абстракция, которая инкапсулирует в себе все ресурсы процесса (открытые файлы, файлы отображенные в память. ) и их дескрипторы, потоки и т.д. Каждый процесс имеет как минимум один поток. Также каждый процесс имеет свое собственное виртуальное адресное пространство и контекст выполнения, а потоки одного процесса разделяют адресное пространство процесса.

  • Регистры процессора.
  • Указатель на стек потока/процесса.
  • Если ваша задача требует интенсивного распараллеливания, используйте потоки одного процесса, вместо нескольких процессов. Все потому, что переключение контекста процесса происходит гораздо медленнее, чем контекста потока.
  • При использовании потока, старайтесь не злоупотреблять средствами синхронизации, которые требуют системных вызовов ядра (например мьютексы). Переключение в редим ядра — дорогостоящая операция!
  • Если вы пишете код, исполняемый в ring0 (к примеру драйвер), старайтесь обойтись без использования дополнительных потоков, так как смена контекста потока — дорогостоящая операция.

Классификация потоков

  • По отображению в ядро: 1:1, N:M, N:1
  • По многозадачной модели: вытесняющая многозадачность (preemptive multitasking), кооперативная многозадачность (cooperative multitasking).
  • По уровню реализации: режим ядра, режим польователя, гибридная реализация.

Классификация потоков по отображению в режим ядра

  • Центральный планировщик ОС режима ядра, который распределяет время между любым потоком в системе.
  • Планировщик библиотеки потоков. У библиотеки потоков режима пользователя может быть свой планировщик, который распределяет время между потоками различных процессов режима пользователя.
  • Планировщик потоков процесса. Уже рассмотренные нами волокна, ставятся на выполнение именно таким способом. К примеру свой Thread Manager есть у каждого процесса Mac OS X, написанного с использованием библиотеки Carbon.

Модель N:M отображает некоторое число потоков пользовательских процессов N на M потоков режима ядра. Проще говоря имеем некую гибридную систему, когда часть потоков ставится на выполнение в планировщике ОС, а большая их часть в планировщике потоков процесса или библиотеки потоков. Как пример можно привести GNU Portable Threads. Данная модель достаточно трудно реализуема, но обладает большей производительностью, так как можно избежать значительного количества системных вызовов.

Модель N:1. Как вы наверное догадались — множество потоков пользовательского процесса отображаются на один поток ядра ОС. Например волокна.

Классификация потоков по многозадачной модели

Во времена DOS, когда однозадачные ОС перестали удовлетворять потребителя, программисты и архитекторы задумали реализовать многозадачную ОС. Самое простое решение было следующим: взять общее количество потоков, определить какой-нибудь минимальный интервал выполнения одного потока, да взять и разделить между всеми -братьями- потоками время выполнения поровну. Так и появилось понятие кооперативной многозадачности (cooperative multitasking), т.е. все потоки выполняются поочередно, с равным временем выполнения. Никакой другой поток, не может вытеснить текущий выполняющийся поток. Такой очень простой и очевидный подход нашел свое применение во всех версиях Mac OS вплоть до Mac OS X, также в Windows до Windows 95, и Windows NT. До сих пор кооперативная многозадачность используется в Win32 для запуска 16 битных приложений. Также для обеспечения совместимости, cooperative multitasking используется менеджером потоков в Carbon приложениях для Mac OS X.

Однако, кооперативная многозадачность со временем показала свою несостоятельность. Росли объемы данных хранимых на винчестерах, росла также скорость передачи данных в сетях. Стало понятно, что некоторые потоки должны иметь больший приоритет, как-то потоки обслуживания прерываний устройств, обработки синхронных IO операций и т.д. В это время каждый поток и процесс в системе обзавелся таким свойством, как приоритет. Подробнее о приоритетах потоков и процессов в Win32 API вы можете прочесть в книге Джефри Рихтера, мы на этом останавливатся не будем ;) Таким образом поток с большим приоритетом, может вытеснить поток с меньшим. Такой прицип лег в основу вытесняющей многозадачности (preemptive multitasking). Сейчас все современные ОС используют данный подход, за исключением реализации волокон в пользовательском режиме.

Классификация потоков по уровню реализации

  1. Реализация потоков на уровне ядра. Проще говоря, это классическая 1:1 модель. Под эту категорию подпадают:
    • Потоки Win32.
    • Реализация Posix Threads в Linux — Native Posix Threads Library (NPTL). Дело в том, что до версии ядра 2.6 pthreads в Linux был целиком и полностью реализован в режиме пользователя (LinuxThreads). LinuxThreads реализовывалf модель 1:1 следующим образом: при создании нового потока, библиотека осуществляла системный вызов clone, и создавало новый процесс, который тем не менее разделял единое адресное пространство с родительским. Это породило множество проблем, к примеру потоки имели разные идентификаторы процесса, что противоречило некоторым аспектам стандарта Posix, которые касаются планировщика, сигналов, примитивов синхронизации. Также модель вытеснения потоков, работала во многих случаях с ошибками, по этому поддержку pthread решено было положить на плечи ядра. Сразу две разработки велись в данном направлении компаниями IBM и Red Hat. Однако, реализация IBM не снискала должной популярности, и не была включена ни в один из дистрибутивов, потому IBM приостановила дальнейшую разработку и поддержку библиотеки (NGPT). Позднее NPTL вошли в библиотеку glibc.
    • Легковесные ядерны потоки (Leight Weight Kernel Threads — LWKT), например в DragonFlyBSD. Отличие этих потоков, от других потоков режима ядра в том, что легковесные ядерные потоки могут вытеснять другие ядерные потоки. В DragonFlyBSD существует множество ядерных потоков, например поток обслуживания аппаратных прерываний, поток обслуживания программных прерываний и т.д. Все они работают с фиксированным приоритетом, так вот LWKT могут вытеснять эти потоки (preempt). Конечно это уже более специфические вещи, про которые можно говорить бесконечно, но приведу еще два примера. В Windows все потоки ядра выполняются либо в контексте потока инициировавшего системный вызов/IO операцию, либо в контексте потока системного процесса system. В Mac OS X существует еще более интересная система. В ядре есть лишь понятие task, т.е. задачи. Все операции ядра выполняются в контексте kernel_task. Обработка аппаратного прерывания, к примеру, происходит в контексте потока драйвера, который обслуживает данное прерывание.
  2. Реализация потоков в пользовательском режиме. Так как, системный вызов и смена контекста — достаточно тяжелые операции, идея реализовать поддержку потоков в режиме пользователя витает в воздухе давно. Множество попыток было сделано, однако данная методика популярности не обрела:
    • GNU Portable Threads — реализация Posix Threads в пользовательском режиме. Основное преимущество — высокая портабельность данной библиотеки, проще говоря она может быть легко перенесена на другие ОС. Проблему вытиснения потоков в данной библиотеке решили очень просто — потоки в ней не вытесняются :) Ну и конечно ни о какой мультмпроцессорности речь идти не может. Данная библиотека реализует модель N:1.
    • Carbon Threads, которые я упоминал уже не раз, и RealBasic Threads.
  3. Гибридная реализация. Попытка использовать все преимущества первого и второго подхода, но как правило подобные мутанты обладают гораздо бОльшими недостатками, нежели достоинствами. Один из примеров: реализация Posix Threads в NetBSD по модели N:M, которая была посже заменена на систему 1:1. Более подробно вы можете прочесть в публикации Scheduler Activations: Effective Kernel Support for the User-Level Management of Parallelism.

Win32 API Threads

Если вы все еще не устали, предлагаю небольшой обзор API для работы с потоками и средствами синхронизации в win32 API. Если вы уже знакомы с материалом, можете смело пропускать этот раздел ;)

Потоки в Win32 создаются с помощью функции CreateThread, куда передается указатель на функцию (назовем ее функцией потока), которая будет выполнятся в созданом потоке. Поток считается завершенным, когда выполнится функция потока. Если же вы хотите гарантировать, что поток завершен, то можно воспользоватся функцией TerminateThread, однако не злоупотребляйте ею! Данная функция «убивает» поток, и отнюдь не всегда делает это корректно. Функция ExitThread будет вызвана неявно, когда завершится функция потока, или же вы можете вызвать данную функцию самостоятельно. Главная ее задача — освободить стек потока и его хендл, т.е. структуры ядра, которые обслуживают данный поток.

Поток в Win32 может пребывать в состоянии сна (suspend). Можно «усыпить поток» с помощью вызова функции SuspendThread, и «разбудить» его с помощью вызова ResumeThread, также поток можно перевести в состояние сна при создании, установив значение параметра СreateSuspended функции CreateThread. Не стоит удивлятся, если вы не увидите подобной функциональности в кроссплатформенных библиотеках, типа boost::threads и QT. Все очень просто, pthreads просто не поддерживают подобную функциональность.

Средства синхронихации в Win32 есть двух типов: реализованные на уровне пользователя, и на уровне ядра. Первые — это критические секции (critical section), к второму набору относят мьютексы (mutex), события (event) и семафоры (semaphore).

Критические секции — легковесный механизм синхронизации, который работает на уровне пользовательского процесса и не использует тяжелых системных вызовов. Он основан на механизме взаимных блокировок или спин локов (spin lock). Поток, который желает обезопасить определенные данные от race conditions вызывает функцию EnterCliticalSection/TryEnterCriticalSection. Если критическая секция свободна — поток занимает ее, если же нет — поток блокируется (т.е. не выполняется и не отъедает процессорное время) до тех пор, пока секция не будет освобождена другим потоком с помощью вызова функции LeaveCriticalSection. Данные функции — атомарные, т.е. вы можете не переживать за целостность ваших данных ;)

  • Они использует примитивы ядра при выполнении, т.е. системные вызовы, что сказывается не производительности.
  • Могут быть именованными и не именованными, т.е. каждому такому объекту синхронизации можно присвоить имя.
  • Работают на уровне системы, а не на уровне процесса, т.е. могут служить механизмом межпроцессного взаимодействия (IPC).
  • Используют для ожидания и захвата примитива единую функцию: WaitForSingleObject/WaitForMultipleObjects.

Posix Threads или pthreads

Сложно представить, какая из *nix подобных операционных систем, не реализует этот стандарт. Стоит отметить, что pthreads также используется в различных операционных системах реального времени (RTOS), потому требование к этой библиотеке (вернее стандарту) — жестче. К примеру, поток pthread не может пребывать в состоянии сна. Также в pthread нет событий, но есть гораздо более мощный механизм — условных переменных (conditional variables), который с лихвой покрывает все необходимые нужды.

Поговорим об отличиях. К примеру, поток в pthreads может быть отменен (cancel), т.е. просто снят с выполнения посредством системного вызова pthread_cancel в момент ожидания освобождения какого-нибудь мьютекса или условной переменной, в момент выполнения вызова pthread_join (вызывающий поток блокируется до тех пор, пока не закончит свое выполнение поток, приминительно к которому была вызвана функция) и т.д. Для работы с мьютексами и семафорами существует отдельные вызовы, как-то pthread_mutex_lock/pthread_mutex_unlock и т.д.

Conditional variables (cv) обычно используется в паре с мьютексами в более сложных случаях. Если мьютекс просто блокирует поток, до тех пор, пока другой поток не освободит его, то cv создают условия, когда поток может заблокировать сам себя до тех пор, пока не произойдет какое-либо условия разблокировки. Например, механизм cv помогает эмулировать события в среде pthreads. Итак, системный вызов pthread_cond_wait ждет, пока поток не будет уведомлен о том, что случилось определенное событие. pthread_cond_signal уведомляет один поток из очереди, что cv сработала. pthread_cond_broadcast уведомляет все потоки, которые вызывали pthread_cond_wait, что сработала cv.

Прощальное слово

На сегодня пожалуй все, иначе информации станет слишком много. Для интересующихся, есть несколько полезных ссылок и книг внизу ;) Также высказывайте свое мнение, интересны ли вам статьи по данной теме.

UPD: дополнил статью небольшой информацией о режиме ядра и режиме пользователя.
UPD2: исправил досадные промахи и ошибки. Спасибо комментаторам ;)

Используйте RequestContext когда вы не желаете указывать тот же набор переменных в серии шаблонов. Например, рассмотрим эти четыре представления:

Следует отметить, что мы явно не используем сокращение render_to_response() — мы вручную загружаем шаблоны, конструируем объекты с контекстом и обрабатываем шаблоны. Мы делаем так с целью облегчения понимания вами этих примеров.

Каждое представление принимает три одинаковые переменные, app , user и ip_address , для своего шаблона. Неплохо было бы убрать эту избыточность.

Класс RequestContext и контекстные процессоры были созданы для решения этой задачи. Контекстные процессоры позволяют вам указывать ряд переменных, которые будут устанавливаться автоматически для каждого контекста, без необходимости их указания в каждом вызове render_to_response() . Надо лишь использовать RequestContext вместо Context при обработке шаблона.

Самый низкоуровневый метод использования контекстных процессоров заключается в создании нескольких процессоров и в передачи им RequestContext . Перепишем вышеприведённые примеры с использованием контекстных процессоров:

Разберём этот код:

Теперь каждое представление не нуждается больше во включении переменных app , user и ip_address в её контекстную конструкцию, потому что они предоставляются функцией custom_proc .

Каждое представление всё ещё обладает возможностью введения любых своих шаблонных переменных, которые могут потребоваться. В данном примере шаблонная переменная message была передана в каждое представление.

В главе « Шаблоны » мы описали сокращение render_to_response() , которое позволяет вам не вызывать loader.get_template() , затем создавать Context , а затем вызывать метод render() для шаблона. Для того, чтобы продемонстрировать низкоуровневую работу контекстных процессоров, вышеприведённые примеры не используют сокращение render_to_response() . Но это возможно и предпочтительно. Сделаем это с помощью аргумента context_instance :

Здесь мы привели в порядок код обработки шаблона в каждой функции представления.

This is an improvement, but, evaluating the conciseness of this code, we have to admit we're now almost overdosing on the other end of the spectrum. FIXME. Мы убрали избыточность в данных (переменные нашего шаблона) за счёт добавления избыточности в код (в вызов processors ). Использование контекстных процессоров не избавляет вас от необходимости набирать много строк кода.

По этим причинам Django предоставляет поддержку для глобальных контекстных процессоров. Параметр TEMPLATE_CONTEXT_PROCESSORS [14] определяет то, какой процессор должен всегда применяться к RequestContext . Он исключает необходимость указания processors при каждом использовании RequestContext .

По умолчанию параметр TEMPLATE_CONTEXT_PROCESSORS определён так:

Этот параметр представляет собой кортеж вызываемых сущностей FIXME. которые используют тот же интерфейс, что и вышеописанная функция custom_proc — функции, принимающие объект запроса и возвращающие словарь элементов, который затем включается в контекст. Следует отметить, что значения параметра TEMPLATE_CONTEXT_PROCESSORS указаны в виде строк, которые определяют процессоры (они должны быть доступны интерпретатору языка Python).

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

Django предоставляет ряд простых контекстных процессоров, включая активные по умолчанию:

Процессор django.core.context_processors.auth

Если параметр TEMPLATE_CONTEXT_PROCESSORS содержит этот процессор, то каждый объект RequestContext будет содержать следующие переменные:

perms : Экземпляр класса django.core.context_processors.PermWrapper , который представляет собой права текущего авторизованного пользователя.

django.core.context_processors.debug

Этот процессор помещает отладочную информацию в слой шаблона. Если параметр TEMPLATE_CONTEXT_PROCESSORS содержит этот процессор, то каждый экземпляр класса RequestContext будет содержать следующие переменные:

debug : Значение параметра DEBUG ( True или False ). Вы можете использовать эту переменную в шаблоне для определения работы в режиме отладки.

sql_queries : Список словарей вида представляющих каждый SQL запрос, который был выполнен во время обработки запроса и затраченное на это время. Порядок запросов в списке соответствует реальному порядку их выполнения.

Поскольку отладочная информация не предназначена для других глаз, этот контекстный процессор добавляет переменные к контексту только при выполнении обоих условий:

Параметр DEBUG установлен в True .

Запрос пришёл с IP адреса указанного в списке INTERNAL_IPS .

django.core.context_processors.i18n

Если этот процессор включен, то каждый экземпляр класса RequestContext будет содержать следующие переменные:

LANGUAGES : Значение параметра LANGUAGES файла конфигурации.

LANGUAGE_CODE : Будет содержать значение request.LANGUAGE_CODE , если оно существует. В противном случае — значение параметра LANGUAGE_CODE файла конфигурации.

В приложении « Параметры конфигурации » об этих двух параметрах написано подробнее.

django.core.context_processors.request

Как написать свой процессор?

Вот несколько советов:

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

Следует помнить, что любой контекстный процессор, указанный в TEMPLATE_CONTEXT_PROCESSORS , будет доступен в любом шаблоне, подчинённом данному файлу конфигурации. Таким образом, выбирайте имена переменных так, чтобы они не конфликтовали с именами переменных в ваших шаблонах. Так как имена переменных чувствительны к регистру, неплохо будет использовать прописные (т.е., большие) буквы для переменных, которые предоставляет процессор.

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

Пользовательский контекстный процессор

1. Место хранения контекстного процессора:

Вы можете в зависимости от того, к какому приложению относится этот контекстный процессор, а затем создать файл в этом приложении для хранения контекстного процессора. Например, context_processors.py. Или вы можете создать пакет Python специально для хранения всех контекстных процессоров.

2. Напишите контекстный процессор:

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

Зачем использовать контекстный процессор:

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

Создайте новый в каталоге вашего приложения context_processors.py Файл, а затем написать наш процессор контекста, который является функцией.

После написания этой функции нам все еще нужно settings.py Добавить наш контекстный процессор.


Найдите список ШАБЛОНОВ в settings.py, а затем добавьте наш пользовательский контекстный процессор.

Таким образом, мы определили наш собственный контекстный процессор.

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

Встроенный контекстный процессор Django

в settings.TEMPLATES.OPTIONS.context_processors В (то есть, где мы только что добавили контекстный процессор), есть много встроенных контекстных процессоров. Роль этих контекстных процессоров заключается в следующем:

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

Добавьте отображение в основной urls.py

Итак, как мы можем получить полный путь к изображению, которое мы сохранили в шаблоне дальше?

В настоящее время нам нужно использовать процессор медиа-контекста.

Сначала мы добавим этот контекстный процессор в настройках.

Затем мы добавляем тег img в шаблон для отображения нашей картинки.

abc.jpg - это копия моих прошлых фотографий.
Таким образом, мы можем получить наши фотографии. Если вы не знаете, как загружать файлы или изображения, вы можете проверить мой блогЗагрузка файла Django и загрузка изображения。

  1. django.template.context_processors.static: STATIC_URL может использоваться в шаблоне. Это использование статических файлов, таких как css, js и images.
  2. django.template.context_processors.csrf: переменная csrf_token может использоваться в шаблоне для генерации csrf_token , Поскольку промежуточное ПО csrf включено по умолчанию в django, нам нужно использовать > Добавьте это.

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

Статическая часть контекста процесса системного уровня включает следующее:

A.Описатель процесса, т.е. элемент таблицы описателей существующих в системе процессов. Описатель процесса включает, в частности, следующую информацию:

  • состояние процесса;
  • физический адрес в основной или внешней памяти u-области процесса;
  • идентификаторы пользователя, от имени которого запущен процесс;
  • идентификатор процесса;
  • прочую информацию, связанную с управлением процессом.

B. U-область (u-area), индивидуальная для каждого процесса область пространства ядра, обладающая тем свойством, что хотя u-область каждого процесса располагается в отдельном месте физической памяти, u-области всех процессов имеют один и тот же виртуальный адрес в адресном пространстве ядра. Именно это означает, что какая бы программа ядра не выполнялась, она всегда выполняется как ядерная часть некоторого пользовательского процесса, и именно того процесса, u-область которого является "видимой" для ядра в данный момент времени. U-область процесса содержит:

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

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

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