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

Я читаю следующую статью Роберта Лава

http://www.linuxjournal.com/article/6916

это говорит

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

Я не понимаю AFAIK, планировщик в ядре O(1), который реализуется через растровое изображение. Так что же мешает Scehduler перевести контекст прерывания в режим сна и запустить следующий запланированный процесс и передать ему управление?

11 ответов

Решение

Я думаю, что это дизайнерская идея.

Конечно, вы можете спроектировать систему, в которой вы можете спать в режиме прерывания, но кроме того, чтобы сделать систему сложной для понимания и сложной (многие ситуации, которые вы должны принять во внимание), это ничего не поможет. Таким образом, с точки зрения дизайна, объявить обработчик прерываний, так как не может спать очень ясно и легко реализовать.


От Роберта Лав (хакер ядра): http://permalink.gmane.org/gmane.linux.kernel.kernelnewbies/1791

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

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

Причина, по которой обработчик ошибок страницы может находиться в спящем режиме, заключается в том, что он вызывается только кодом, который выполняется в контексте процесса. Поскольку собственная память ядра не поддерживается, доступ к памяти только из пользовательского пространства может привести к сбою страницы. Таким образом, только несколько определенных мест (например, вызовы copy_{to,from}_user()) могут вызвать ошибку страницы в ядре. Все эти места должны быть созданы кодом, который может спать (т. Е. Контекстом процесса, без блокировок и т. Д.).

Так что же мешает Scehduler перевести контекст прерывания в режим сна и запустить следующий запланированный процесс и передать ему управление?

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

Когда происходит прерывание, процессор сохраняет регистры в стек и переходит к началу процедуры обработки прерывания. Это означает, что когда работает обработчик прерываний, он работает в контексте процесса, который выполнялся, когда произошло прерывание. Прерывание выполняется в стеке этого процесса, и когда обработчик прерываний завершает работу, этот процесс возобновляет выполнение.

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

Простым сценарием, когда что-то может пойти не так, будет тупик между обработчиком прерываний и процессом, который он прерывает.

  1. Процесс1 входит в режим ядра.
  2. Процесс1 получает LockA.
  3. Прерывание происходит.
  4. ISR начинает выполнение, используя стек Process1.
  5. ISR пытается приобрести LockA.
  6. ISR вызывает sleep для ожидания освобождения LockA.

На данный момент у вас тупик. Process1 не может возобновить выполнение, пока ISR не завершит работу со своим стеком. Но ISR заблокирован, ожидая, пока Process1 освободит LockA.

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

Как правило, ваша единственная цель в процедуре прерывания - заставить устройство перестать прерывать и поставить что-то в очередь на более низком уровне прерывания (в Unix это обычно уровень без прерываний, но для Windows это уровень диспетчеризации, apc или пассивный уровень), чтобы сделать тяжелую работу, где у вас есть доступ к дополнительным функциям ядра / ОС. Смотрите - Реализация обработчика.

Это свойство того, как должны работать O/S, а не то, что присуще Linux. Процедура прерывания может выполняться в любой момент, поэтому состояние прерывания не соответствует. Если вы прервали выполнение кода планирования потока, его состояние не согласовано, поэтому вы не можете быть уверены, что можете "спать" и переключать потоки. Даже если вы защищаете код переключения потоков от прерывания, переключение потоков является функцией высокого уровня O/S, и если вы защитили все, на что оно опирается, прерывание становится скорее предложением, чем императивом, подразумеваемым его именем.

Запрет на обработку обработчика прерываний - это выбор дизайна. Когда некоторые данные находятся на устройстве, обработчик прерываний перехватывает текущий процесс, подготавливает передачу данных и разрешает прерывание; до того, как обработчик активирует текущее прерывание, устройство должно зависнуть. Мы хотим, чтобы наш ввод-вывод был занят и наша система реагировала, тогда нам лучше не блокировать обработчик прерываний.

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

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

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

Так что же мешает Scehduler перевести контекст прерывания в режим сна и запустить следующий запланированный процесс и передать ему управление?

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

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

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

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

Но в ARM это невозможно, поскольку "current" не имеет отношения к обработке в режиме прерывания. Смотрите код ниже:

#linux/arch/arm/include/asm/thread_info.h 
94 static inline struct thread_info *current_thread_info(void)
95 {
96  register unsigned long sp asm ("sp");
97  return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
98 }

sp в режиме USER и SVC являются "одинаковыми" ("то же самое" здесь не означает, что они равны, вместо этого точка sp пользовательского режима указывает на стек пространства пользователя, в то время как sp r13_svc режима svc указывает на стек ядра, где процесс пользователя task_structure была обновлена ​​при предыдущем переключении задач. Когда происходит системный вызов, процесс снова входит в пространство ядра, когда sp (sp_svc) по-прежнему не изменяется, эти 2 sp связаны друг с другом, в этом смысле они "одинаковы". '), Поэтому в режиме SVC код ядра может получить действительный' текущий '. Но в других привилегированных режимах, скажем, в режиме прерывания, sp "отличается", указывает на выделенный адрес, определенный в cpu_init(). "Текущий", рассчитанный в этих режимах, не будет иметь отношения к прерванному процессу, и доступ к нему приведет к неожиданному поведению. Вот почему всегда говорят, что системный вызов может спать, а обработчик прерываний - нет, системный вызов работает в контексте процесса, но прерывания нет.

Ядро Linux имеет два способа выделения стека прерываний. Один находится в стеке ядра прерванного процесса, другой - выделенный стек прерываний на процессор. Если контекст прерывания сохраняется в выделенном стеке прерываний для каждого ЦП, то действительно, контекст прерывания совершенно не связан ни с каким процессом. "Текущий" макрос создаст недопустимый указатель на текущий запущенный процесс, поскольку "текущий" макрос с некоторой архитектурой вычисляется с помощью указателя стека. Указатель стека в контексте прерывания может указывать на выделенный стек прерывания, а не на стек ядра какого-либо процесса.

Это просто выбор дизайна / реализации в ОС Linux. Преимущество этого дизайна простое, но оно может не подходить для требований ОС в реальном времени.

Другие ОС имеют другие конструкции / реализации.

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

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

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

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

Другие вопросы по тегам