Что такое спинлок в Linux?

32

Я хотел бы узнать о спин-блокировки Linux в деталях; кто-нибудь может мне их объяснить?

сен
источник

Ответы:

34

Спин-блокировка - это способ защитить общий ресурс от изменения двумя или более процессами одновременно. Первый процесс, который пытается изменить ресурс, «получает» блокировку и продолжает свой путь, делая то, что ему нужно с ресурсом. Любые другие процессы, которые впоследствии пытаются получить блокировку, останавливаются; говорят, что они «вращаются на месте» в ожидании блокировки, которая будет снята первым процессом, и, таким образом, получат название «спин-блокировка».

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

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

Эта ситуация меняется, и была на протяжении большей части существования Linux. До Linux 2.0 ядро ​​было почти чисто однозадачной программой: всякий раз, когда в ЦП выполнялся код ядра, использовалось только одно ядро ​​ЦП, поскольку существовала единая спин-блокировка, защищающая все общие ресурсы, называемая Большой блокировкой ядра (BKL). ). Начиная с Linux 2.2, BKL постепенно разбивается на множество независимых блокировок, каждая из которых защищает более сфокусированный класс ресурсов. Сегодня, с ядром 2.6, BKL все еще существует, но он используется только действительно старым кодом, который не может быть легко перемещен в более детальную блокировку. Теперь для многоядерного блока вполне возможно, чтобы каждый процессор выполнял полезный код ядра.

Существует ограничение на утилиту разбиения BKL, потому что в ядре Linux отсутствует общая многозадачность. Если ядро ​​ЦП блокируется вращением при спин-блокировке ядра, его нельзя повторно выполнить, чтобы сделать что-то еще до снятия блокировки. Он просто сидит и вращается, пока замок не будет снят.

Спин-блокировки могут эффективно превратить монстр 16-ядерный блок в одноядерный блок, если рабочая нагрузка такова, что каждое ядро ​​всегда ожидает одну спин-блокировку. Это основной предел масштабируемости ядра Linux: удвоение ядер ЦП со 2 до 4, вероятно, почти удвоит скорость Linux-бокса, но удвоение его с 16 до 32, вероятно, не будет, при большинстве рабочих нагрузок.

Уоррен Янг
источник
@Warren: пара сомнений - я хотел бы узнать больше об этом Big Kernel Lock и его последствиях. Я также не понимаю, что последний абзац «удвоение ядер ЦП со 2 до 4, вероятно, почти удвоит скорость Linux-бокса, но удвоение его с 16 до 32, вероятно, не будет»
Сен
2
Re: последствия BKL: я думал, что ясно дал понять выше. Имея только одну блокировку в ядре, каждый раз, когда два ядра пытаются сделать что-то защищенное BKL, одно ядро ​​блокируется, а первое завершает работу, используя свой защищенный ресурс. Чем более детализирована блокировка, тем ниже вероятность того, что это произойдет, и тем выше будет загрузка процессора.
Уоррен Янг
2
Re: удвоение: я имею в виду, что существует закон убывающей отдачи при добавлении процессорных ядер к компьютеру. По мере увеличения количества ядер возрастает вероятность того, что двум или более из них потребуется доступ к ресурсу, защищенному определенной блокировкой. Увеличение степени детализации блокировок снижает вероятность таких коллизий, но при добавлении слишком большого количества данных также возникают издержки. Вы можете легко увидеть это в суперкомпьютерах, которые сегодня часто имеют тысячи процессоров: большинство рабочих нагрузок на них неэффективны, поскольку они не могут избежать простоя многих процессоров из-за конфликта общих ресурсов.
Уоррен Янг
1
Хотя это интересное объяснение (+1 за это), я не думаю, что оно эффективно для передачи разницы между спин-блокировками и другими видами блокировок.
Жиль "ТАК - перестань быть злым"
2
Если кто-то хочет узнать разницу между спин-блокировкой и, скажем, семафором, это другой вопрос. Еще один хороший, но косвенный вопрос: что такое дизайн ядра Linux, который делает спин-блокировки хорошим выбором вместо чего-то более распространенного в пользовательском коде, например мьютекса? Этот ответ расстраивает много как есть.
Уоррен Янг
11

Спин-блокировка - это когда процесс непрерывно запрашивает удаление блокировки. Это считается плохим, потому что процесс потребляет циклы (обычно) без необходимости. Это не специфичный для Linux, а общий шаблон программирования. И хотя это, как правило, считается плохой практикой, на самом деле это правильное решение; Есть случаи, когда стоимость использования планировщика выше (с точки зрения циклов ЦП), чем стоимость нескольких циклов, которые, как ожидается, продлится спин-блокировка.

Пример спин-блокировки:

#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
  sleep 1
done
#do stuff

Часто есть способ избежать блокировки вращения. Для этого конкретного примера есть инструмент Linux под названием inotifywait (он обычно не устанавливается по умолчанию). Если бы он был написан на C, вы бы просто использовали API inotify, который предоставляет Linux.

В том же примере с использованием inotifywait показано, как выполнить то же самое без спин-блокировки:

#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff
Шон Дж. Гофф
источник
Какова роль планировщика там? Или это имеет какую-то роль?
Сен
1
В методе спин-блокировки планировщик возобновляет процесс каждую ~ 1 секунду, чтобы выполнить свою задачу (которая просто проверяет наличие файла). В примере inotifywait планировщик возобновляет процесс только при выходе из дочернего процесса (inotifywait). Inotifywait также спит; планировщик возобновляет его только тогда, когда происходит событие inotify.
Шон Дж. Гофф
Так как же этот сценарий обрабатывается в одноядерной процессорной системе?
Сен
@Sen: Это довольно хорошо объясняется в драйверах устройств Linux .
Жиль "ТАК - перестать быть злым"
1
Этот скрипт bash является плохим примером спин-блокировки. Вы приостанавливаете процесс, заставляя его идти спать. Спинлок никогда не спит. На одноядерном компьютере он просто приостанавливает работу планировщика и продолжает работу (без занятого ожидания)
Martin
7

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

Теперь попытка и продолжение занимает намного меньше времени, чем попытка и блокировка. Скажем на минуту, что попытка продолжения займет единицу времени, а попытка блокировки - сотню.

Теперь давайте предположим, что в среднем поток занимает 4 единицы времени, удерживая блокировку. Тратить 100 единиц расточительно. Таким образом, вместо этого вы пишете цикл «попробуй и продолжай». На четвертой попытке вы обычно получаете замок. Это спиновый замок. Это называется потому, что поток продолжает вращаться на месте, пока не получит блокировку.

Дополнительной мерой безопасности является ограничение количества циклов. Таким образом, в примере вы выполняете цикл for, например, шесть раз, если он терпит неудачу, тогда вы «пытаетесь и блокируете».

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

Таким образом, в конце, спин-блокировка может быть очень эффективной или расточительной. Расточительно, когда «типичное» время удержания блокировки превышает время, необходимое для «попытки и блокировки». Эффективно, когда типичное время удержания блокировки намного меньше, чем время «попробовать и заблокировать».

Ps: Книга для чтения по темам - это «Учебник по темам», если вы все еще можете ее найти.

HandyGandy
источник
нить продолжает вращаться на месте, пока не получит блокировку . Не могли бы вы сказать мне, что это вращение? Это похоже на wait_queue и запускается планировщиком? Может быть, я пытаюсь попасть на низкий уровень, но все еще не могу сдержать мои сомнения.
Сен
Спиннинг среди 3-4 инструкций в цикле, в основном.
Пол Стелиан
5

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

take the lock
use the resource
release the lock

Взять блокировку невозможно, если другая задача уже взяла ее. (Думайте о замке как о физическом объекте токена. Либо объект находится в ящике, либо кто-то держит его в руках. Только ресурс, имеющий объект, может получить доступ к ресурсу.) Таким образом, «взять замок» действительно означает «подождите, пока Ни у кого другого нет замка, тогда возьми его ».

С точки зрения высокого уровня, существуют два основных способа реализации блокировок: спин-блокировки и условия. В случае спин-блокировки взятие блокировки означает просто «вращение» (т.е. ничего не делать в цикле) до тех пор, пока никто другой не получит блокировку. При определенных условиях, если задача пытается взять блокировку, но заблокирована из-за того, что ее удерживает другая задача, новичок попадает в очередь ожидания; операция освобождения сигнализирует любой ожидающей задаче, что блокировка теперь доступна.

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

Спин-блокировки явно расточительны: задача ожидания продолжает проверять, снята ли блокировка. Так почему и когда это используется? Спинлоки часто очень дешевы, если их не удерживать. Это делает его привлекательным, когда вероятность удержания замка невелика. Кроме того, спин-блокировки жизнеспособны только в том случае, если ожидается, что получение блокировки не займет много времени. Таким образом, спин-блокировки обычно используются в ситуациях, когда они будут удерживаться в течение очень короткого времени, поэтому ожидается, что большинство попыток будут успешными с первой попытки, а те, которые нуждаются в ожидании, не ждут долго.

Существует хорошее объяснение спин-блокировок и других механизмов параллелизма ядра Linux в драйверах устройств Linux , глава 5.

Жиль "ТАК - прекрати быть злым"
источник
Что было бы хорошим способом реализовать другие примитивы синхронизации? Возьмите спин-блокировку, проверьте, есть ли у кого-то еще реализованный фактический примитив, тогда или используйте планировщик или предоставьте доступ? Можем ли мы считать блок synchronized () в Java формой спин-блокировки, а в правильно реализованных примитивах просто использовать спин-блокировку?
Пол Стелиан
@PaulStelian Действительно, «медленные» блокировки реализуются таким образом. Я не знаю достаточно Java, чтобы ответить на эту часть, но я сомневаюсь, что synchronizedэто будет реализовано с помощью спин-блокировки: synchronizedблок может работать очень долго. synchronizedявляется языковой конструкцией, облегчающей использование блокировок в определенных случаях, а не примитивом для создания больших примитивов синхронизации.
Жиль "ТАК - перестань быть злым"
3

Спин-блокировка - это блокировка, которая работает путем отключения планировщика и, возможно, прерывает (вариант irqsave) на том конкретном ядре, на котором установлена ​​блокировка. Он отличается от мьютекса тем, что отключает планирование, так что только ваш поток может работать, пока удерживается спин-блокировка. Мьютекс позволяет планировать другие потоки с более высоким приоритетом, пока он удерживается, но не позволяет им одновременно выполнять защищенный раздел. Поскольку спин-блокировки отключают многозадачность, вы не можете взять спин-блокировку, а затем вызвать другой код, который попытается получить мьютекс. Ваш код внутри секции спин-блокировки никогда не должен спать (код обычно спит, когда встречается с заблокированным мьютексом или пустым семафором).

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

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

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

Спинлок не расточит, где это имеет смысл. Для очень маленьких критических секций было бы расточительно выделить очередь задач мьютекса по сравнению с простым приостановлением работы планировщика на несколько микросекунд, которые требуются для завершения важной работы. Если вам нужно спать или удерживать блокировку во время операции ввода-вывода (которая может спать), используйте мьютекс. Конечно, никогда не блокируйте спин-блокировку, а затем попытайтесь разблокировать ее внутри прерывания. Хотя это будет работать, это будет похоже на ардуинскую хрень из while (flagnotset); в таком случае используйте семафор.

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

Мартин
источник