Внутренности переключателя контекста


Я хочу учиться и заполнять пробелы в моих знаниях с помощью этого вопроса

Итак, пользователь запускает поток (на уровне ядра), и теперь он вызывает yield (системный вызов, который я предполагаю) Планировщик теперь должен сохранить контекст текущего потока в TCB (который хранится где-то в ядре) и выбрать другой поток для запуска и загрузки его контекста и перехода к его CS:EIP. Чтобы сузить круг вопросов, я работаю над Linux, работающим поверх архитектуры x86. Теперь, я хочу получить в деталях:

Итак, сначала у нас есть системный вызов:

1) функция-оболочка для yield будет толкать аргументы системного вызова в стек. Нажмите обратный адрес и поднимите прерывание с номером системного вызова, нажатым на какой-то регистр (скажем, EAX).

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

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

4) планировщик теперь сохраняет состояние в TCB и загружает другой TCB

5) когда планировщик запускает исходный поток, элемент управления возвращается к функции-оболочке, которая очищает стек и поток возобновляется

побочные вопросы: работает ли планировщик как поток только для ядра (т. е. поток, который может запускать только код ядра)? Существует ли отдельный стек ядра для каждого потока ядра или каждого процесса?

3 52

3 ответа:

на высоком уровне, есть два отдельных механизма для понимания. Первый-это механизм входа/выхода ядра: это переключает один запущенный поток от запуска кода usermode к запуску кода ядра в контексте этого потока и обратно. Во-вторых, сам механизм переключения контекста, который переключается в режиме ядра от выполнения в контексте одного потока к другому.

Итак, когда поток A вызывает sched_yield() и заменяется потоком B, что происходит это:

  1. поток A входит в ядро, переходя из режима пользователя в режим ядра;
  2. поток A в контексте ядра-переключается на поток B в ядре;
  3. поток B выходит из ядра, переходя из режима ядра обратно в режим пользователя.

каждый пользовательский поток имеет как стек пользовательского режима, так и стек режима ядра. Когда поток входит в ядро, текущее значение стека пользовательского режима (SS:ESP) и указатель команд (CS:EIP) являются сохранено в стеке режима ядра потока, а процессор переключается на стек режима ядра-с помощью int механизм системных вызовов, это делается с помощью самого процессора. Остальные значения регистров и флаги также сохраняются в стеке ядра.

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

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

Что вы пропустили во время шага 2, так это то, что стек переключается из стека пользовательского уровня потока (где вы нажали args) в стек защищенного уровня потока. Текущий контекст потока, прерванного syscall, фактически сохраняется в этом защищенном стеке. Внутри ISR и непосредственно перед входом в ядро этот защищенный стек снова переключается на the стек ядра, о котором вы говорите. После того, как внутри ядра, функции ядра, такие как функции планировщика в конечном итоге используйте стек ядра. Позже поток выбирается планировщиком, и система возвращается к ISR, он переключается обратно из стека ядра на вновь избранный (или первый, если не активен поток с более высоким приоритетом) стек защищенного уровня потока, который в конечном итоге содержит новый контекст потока. Поэтому контекст восстанавливается из стека код автоматически (в зависимости от архитектуры). Наконец, специальная инструкция восстанавливает последние обидчивые resgisters например, указатель стека и указатель инструкции. Обратно в страну пользователей...

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

некоторые ресурсы:

  • 3.3.3 выполнение переключения процесса на понимание ядра Linux, О'Рейли
  • 5.12.1 процедуры обработки исключений или прерываний на руководство Intel 3A (sysprogramming). Номер главы может варьироваться от издания к другому, поэтому поиск по "использованию стека при передаче для прерываний и процедур обработки исключений" должен привести вас к хорошему.

Надежда это помощь!

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

в свою очередь стек потоков делится на две изолированные части, одна из которых называется пользовательским стеком (так как используется при выполнении потока в пользовательском режиме), и вторая называется стек ядра (потому что он используется, когда поток выполняется в режиме ядра). Как только поток пересекает границу между Пользователем и режимом ядра, процессор автоматически переключает его из одного стека в другой. Оба стека отслеживаются ядром и процессором по-разному. Для стека ядра CPU постоянно держит в виду указатель на верхнюю часть стека ядра потока. Это легко, потому что этот адрес является постоянным для потока. Каждый раз, когда поток входит в ядро, он находит пустое ядро стек и каждый раз, когда он возвращается в пользовательский режим, он очищает стек ядра. В то же время CPU не имеет в виду указатель на верхнюю часть пользовательского стека, когда поток работает в режиме ядра. Вместо этого во время входа в ядро CPU создает специальный кадр стека "прерывания" в верхней части стека ядра и сохраняет значение указателя стека пользовательского режима в этом кадре. Когда поток выходит из ядра, CPU немедленно восстанавливает значение ESP из ранее созданного кадра стека " прерывания перед его очисткой. (на устаревшем x86 пара инструкций int / iret обрабатывает вход и выход из режима ядра)

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

обратите внимание, что в таких системах, как 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 тоже)