Представьте себе простой вид упреждающей многозадачности. У нас есть две пользовательские задачи, обе из которых выполняются все время без использования ввода-вывода или выполнения вызовов ядра. Эти две задачи не должны делать ничего особенного, чтобы иметь возможность работать в многозадачной операционной системе. Ядро, обычно основанное на прерывании по таймеру, просто решает, что пора одной задаче сделать паузу, чтобы запустить другую. Задача, о которой идет речь, совершенно не знает, что что-то случилось.
Однако большинство задач время от времени запрашивают ядро через системные вызовы. Когда это происходит, тот же пользовательский контекст существует, но ЦП выполняет код ядра от имени этой задачи.
В старых ядрах Linux никогда не разрешалось прерывать задачу, пока она была занята выполнением кода ядра. (Обратите внимание, что операции ввода-вывода всегда добровольно планируются. Я говорю о случае, когда в коде ядра выполняются некоторые ресурсоемкие операции, такие как сортировка списка.)
Если система разрешает выполнение этой задачи во время выполнения кода ядра, , то у нас есть то, что называется "вытесняющим ядром". Такая система невосприимчива к непредсказуемым задержкам, которые могут возникнуть во время системных вызовов, поэтому она может лучше подходить для встроенных задач или задач в реальном времени.
Например, если на конкретном процессоре доступно две задачи, и для выполнения одной из них требуется системный вызов, выполнение которого занимает 5 мс, а для другой - приложение MP3-плеера, которому необходимо подавать аудиоканал каждые 2 мс, вы можете услышать заикание звук.
Аргумент против вытеснения состоит в том, что весь код ядра, который может быть вызван в контексте задачи, должен быть в состоянии пережить вытеснение - например, существует много плохого кода драйвера устройства, который может быть лучше, если он всегда может завершиться операция, прежде чем позволить какой-либо другой задаче работать на этом процессоре. (В многопроцессорных системах в наши дни это правило, а не исключение, весь код ядра должен быть повторно введен, так что сегодня этот аргумент не так актуален.) Кроме того, если та же цель может быть достигнута путем улучшения системных вызовов с плохими задержка, возможно, прерывание не требуется.
Компромисс - CONFIG_PREEMPT_VOLUNTARY, который позволяет переключать задачи в определенных точках внутри ядра, но не везде. Если есть только небольшое количество мест, где код ядра мог бы зависнуть, это дешевый способ уменьшить задержку при сохранении управляемости сложности.