Само ядро вообще не имеет стека. То же самое и с процессом. У него тоже нет стека. Потоки - это только граждане системы, которые рассматриваются как исполнительные единицы. Благодаря этому можно планировать только потоки, и только потоки имеют стеки. Но есть один момент, который интенсивно используется в коде режима ядра - система в каждый момент времени работает в контексте текущего активного потока. Благодаря этому само ядро может повторно использовать стек текущего активного стека. Обратите внимание, что только один из них может одновременно выполнять либо код ядра, либо код пользователя. Из-за этого, когда ядро вызывается, оно просто повторно использует стек потока и выполняет очистку, прежде чем вернуть управление прерванным действиям в потоке. Тот же механизм работает для обработчиков прерываний. Тот же механизм используется обработчиками сигналов.
В свою очередь стек потоков делится на две изолированные части, одна из которых называется пользовательским стеком (потому что она используется, когда поток выполняется в пользовательском режиме), а вторая называется стеком ядра (потому что она используется, когда поток выполняется в режиме ядра). . Как только поток пересекает границу между пользовательским режимом и режимом ядра, ЦП автоматически переключает его из одного стека в другой. Оба стека по-разному отслеживаются ядром и процессором. Для стека ядра ЦП постоянно сохраняет в памяти указатель на вершину стека ядра потока. Это просто, потому что этот адрес постоянен для потока. Каждый раз, когда поток входит в ядро, он обнаруживает пустой стек ядра и каждый раз, когда он возвращается в пользовательский режим, он очищает стек ядра. В то же время CPU не учитывает указатель на вершину пользовательского стека, когда поток работает в режиме ядра. Вместо этого во время входа в ядро ЦП создает специальный кадр стека «прерывания» на вершине стека ядра и сохраняет значение указателя стека пользовательского режима в этом кадре. Когда поток выходит из ядра, ЦП восстанавливает значение ESP из ранее созданного кадра стека «прерывания» непосредственно перед его очисткой. (на устаревшей x86 пара инструкций int / iret обрабатывает вход и выход из режима ядра)
Во время перехода в режим ядра сразу после того, как ЦП создаст кадр стека «прерывания», ядро помещает содержимое остальных регистров ЦП в стек ядра. Обратите внимание, что он сохраняет значения только для тех регистров, которые могут использоваться кодом ядра. Например, ядро не сохраняет содержимое регистров SSE только потому, что оно никогда не коснется их. Точно так же непосредственно перед тем, как попросить CPU вернуть управление обратно в пользовательский режим, ядро выталкивает ранее сохраненное содержимое обратно в регистры.
Обратите внимание, что в таких системах, как Windows и Linux, существует понятие системного потока (часто называемого потоком ядра, я знаю, что это сбивает с толку). Системные потоки - это своего рода специальные потоки, потому что они выполняются только в режиме ядра и из-за этого не имеют пользовательской части стека. Ядро использует их для вспомогательных хозяйственных задач.
Переключение потоков выполняется только в режиме ядра. Это означает, что и исходящие, и входящие потоки выполняются в режиме ядра, оба используют свои собственные стеки ядра, и у обоих стеки ядра имеют кадры «прерывания» с указателями на вершину пользовательских стеков. Ключевым моментом переключения потоков является переключение между стеками потоков ядра, а именно:
pushad; // save context of outgoing thread on the top of the kernel stack of outgoing thread
; here kernel uses kernel stack of outgoing thread
mov [TCB_of_outgoing_thread], ESP;
mov ESP , [TCB_of_incoming_thread]
; here kernel uses kernel stack of incoming thread
popad; // save context of incoming thread from the top of the kernel stack of incoming thread
Обратите внимание, что в ядре есть только одна функция, которая выполняет переключение потоков. Благодаря этому каждый раз, когда ядро переключает стеки, оно может находить контекст входящего потока на вершине стека. Просто потому, что каждый раз перед переключением стека ядро помещает контекст исходящего потока в свой стек.
Также обратите внимание, что каждый раз после переключения стека и перед возвратом в пользовательский режим ядро перезагружает сознание процессора новым значением вершины стека ядра. Делая это, он гарантирует, что когда новый активный поток попытается войти в ядро в будущем, он будет переключен процессором на свой собственный стек ядра.
Также обратите внимание, что не все регистры сохраняются в стеке во время переключения потока, некоторые регистры, такие как FPU / MMX / SSE, сохраняются в специально выделенной области в TCB исходящего потока. Ядро использует здесь другую стратегию по двум причинам. Во-первых, не все потоки в системе их используют. Перенос их содержимого в стек и извлечение его из стека для каждого потока неэффективно. А во втором - специальные инструкции для «быстрого» сохранения и загрузки их содержимого. И в этих инструкциях стек не используется.
Также обратите внимание, что фактически часть ядра стека потоков имеет фиксированный размер и выделяется как часть TCB. (верно для Linux, и я верю и для Windows)
person
ZarathustrA
schedule
25.10.2016