Понимание кольцевого буфера ядра Linux
На http://lwn.net/Articles/378262/ есть статья, в которой описывается реализация циклического буфера в ядре Linux. У меня есть несколько вопросов:
Вот "производитель":
spin_lock(&producer_lock);
unsigned long head = buffer->head;
unsigned long tail = ACCESS_ONCE(buffer->tail);
if (CIRC_SPACE(head, tail, buffer->size) >= 1) {
/* insert one item into the buffer */
struct item *item = buffer[head];
produce_item(item);
smp_wmb(); /* commit the item before incrementing the head */
buffer->head = (head + 1) & (buffer->size - 1);
/* wake_up() will make sure that the head is committed before
* waking anyone up */
wake_up(consumer);
}
spin_unlock(&producer_lock);
Вопросы:
- Так как этот код явно имеет дело с упорядочением памяти и атомарностью, в чем смысл spin_lock ()?
- Пока что я понимаю, что ACCESS_ONCE останавливает переупорядочивание компилятора, правда?
- Производит ли_продукт (элемент) просто все записи, связанные с элементом?
- Я полагаю, что smp_wmb () гарантирует, что все записи в product_item (item) завершаются ДО "записи" публикации, следующей за ней. правда?
- Комментарий на странице, где я получил этот код, по-видимому, подразумевает, что smp_wmb () обычно требуется после обновления индекса заголовка, но wake_up (потребитель) делает это, так что в этом нет необходимости. Это правда? Если так, то почему?
Вот "потребитель":
spin_lock(&consumer_lock);
unsigned long head = ACCESS_ONCE(buffer->head);
unsigned long tail = buffer->tail;
if (CIRC_CNT(head, tail, buffer->size) >= 1) {
/* read index before reading contents at that index */
smp_read_barrier_depends();
/* extract one item from the buffer */
struct item *item = buffer[tail];
consume_item(item);
smp_mb(); /* finish reading descriptor before incrementing tail */
buffer->tail = (tail + 1) & (buffer->size - 1);
}
spin_unlock(&consumer_lock);
Вопросы, специфичные для "потребителя":
- Что делает smp_read_barrier_depends ()? Из некоторых комментариев на форуме кажется, что вы могли бы выполнить smp_rmb () здесь, но на некоторых архитектурах это излишне (x86) и слишком дорого, поэтому smp_read_barrier_depends () был создан, чтобы делать это по выбору... Тем не менее, я не очень понимаю, почему smp_rmb () когда-либо необходим!
- Есть ли smp_mb (), чтобы гарантировать, что все чтения до его завершения завершены до записи после него?
1 ответ
Для производителя:
-
spin_lock()
здесь необходимо предотвратить одновременное изменение очереди двумя производителями. ACCESS_ONCE
предотвращает переупорядочение, а также предотвращает перезагрузку значения компилятором позже. (Там есть статья оACCESS_ONCE
на LWN, который расширяет это дальше)- Правильный.
- Тоже правильно.
- (Подразумеваемый) барьер записи здесь необходим перед пробуждением потребителя, так как в противном случае потребитель может не увидеть обновленный
head
значение.
Потребитель:
smp_read_barrier_depends()
является барьером зависимости данных, который является более слабой формой барьера чтения (см. 2). Эффект в этом случае состоит в том, чтобы гарантировать, чтоbuffer->tail
читается перед использованием его в качестве индекса массива вbuffer[tail]
,smp_mb()
здесь есть полный барьер памяти, гарантирующий, что все операции чтения и записи совершаются к этому моменту.
Дополнительные ссылки:
(Примечание: я не совсем уверен в своих ответах на 5 для производителя и 1 для потребителя, но я считаю, что они справедливо приближают факты. Я настоятельно рекомендую прочитать страницу документации о барьерах памяти, так как это больше всеобъемлющий, чем я мог бы написать здесь.)