Я понимаю, что операции async ввода/вывода с помощью select()
и poll()
не используют процессорное время, то есть его не занятый цикл, а затем как они действительно реализованы под капотом? Поддерживается ли это как-то в аппаратных средствах и почему не так много очевидных затрат на процессор для их использования?
Как системные вызовы, такие как select() или poll(), работают под капотом?
Ответ 1
Это зависит от того, что ждет select
/poll
. Рассмотрим несколько случаев; Я собираюсь принять одноядерную машину для упрощения.
Сначала рассмотрим случай, когда select
ожидает другой процесс (например, другой процесс может выполнять некоторые вычисления, а затем выводит результат по конвейеру). В этом случае ядро будет отмечать ваш процесс как ожидающий ввода, и поэтому он не будет предоставлять процессорного времени вашему процессу. Когда другой процесс выводит данные, ядро пробудит ваш процесс (дайте ему время на CPU), чтобы он мог обрабатывать входные данные. Это произойдет, даже если другой процесс все еще запущен, потому что современные ОС используют превентивную многозадачность, а это означает, что ядро будет периодически прерывать процессы, чтобы дать другим процессам возможность использовать CPU ( "временное масштабирование" ).
Изображение меняется, когда select
ожидает ввода-вывода; сетевые данные, например, или ввод с клавиатуры. В этом случае, в то время как архаичное оборудование должно было бы открутить процессор, ожидающий ввода, все современное оборудование может поместить процессор в состояние ожидания "малой мощности", пока аппаратное обеспечение не обеспечит прерывание - событие с особым управлением, которое обрабатывает ядро. В обработчике прерываний ЦП будет записывать входящие данные, а после возвращения из прерывания пробудит ваш процесс, чтобы он мог обрабатывать данные.
Ответ 2
Нет поддержки аппаратного обеспечения. Ну, есть... но ничего особенного, и это зависит от того, какой дескриптор файла вы смотрите. Если есть драйвер устройства, реализация зависит от драйвера и/или устройства. Например, сокеты. Если вы ожидаете чтения некоторых данных, есть последовательность событий:
- Некоторые вызовы вызова poll()/select()/epoll() для ожидания данных в сокете. Существует переключатель контекста из пользовательского режима в ядро.
- Сетевой адаптер прерывает процессор, когда приходит какой-либо пакет. Процедура прерывания в драйвере нажимает пакет в конце очереди.
- Существует поток ядра, который берет данные из этой очереди и просыпает сетевой код внутри ядра для обработки этого пакета.
- Когда пакет обрабатывается, ядро определяет ожидающий его сокет, сохраняет данные в буфере сокета и возвращает системный вызов обратно в пространство пользователя.
Это просто краткое описание, есть много деталей, но я думаю, что этого достаточно, чтобы понять суть.
Другой пример, в котором не задействованы никакие драйверы, - это unix-сокет. Если вы ждете данных от одного из них, процесс, который ждет, добавляется в список. Когда другой процесс с другой стороны сокета записывает данные, ядро проверяет этот список, и точка 4 применяется снова.
Надеюсь, это поможет. Я думаю, что примеры лучше всего использовать.