Понимание кольцевого буфера ядра 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);

Вопросы:

  1. Так как этот код явно имеет дело с упорядочением памяти и атомарностью, в чем смысл spin_lock ()?
  2. Пока что я понимаю, что ACCESS_ONCE останавливает переупорядочивание компилятора, правда?
  3. Производит ли_продукт (элемент) просто все записи, связанные с элементом?
  4. Я полагаю, что smp_wmb () гарантирует, что все записи в product_item (item) завершаются ДО "записи" публикации, следующей за ней. правда?
  5. Комментарий на странице, где я получил этот код, по-видимому, подразумевает, что 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);

Вопросы, специфичные для "потребителя":

  1. Что делает smp_read_barrier_depends ()? Из некоторых комментариев на форуме кажется, что вы могли бы выполнить smp_rmb () здесь, но на некоторых архитектурах это излишне (x86) и слишком дорого, поэтому smp_read_barrier_depends () был создан, чтобы делать это по выбору... Тем не менее, я не очень понимаю, почему smp_rmb () когда-либо необходим!
  2. Есть ли smp_mb (), чтобы гарантировать, что все чтения до его завершения завершены до записи после него?

1 ответ

Для производителя:

  1. spin_lock() здесь необходимо предотвратить одновременное изменение очереди двумя производителями.
  2. ACCESS_ONCE предотвращает переупорядочение, а также предотвращает перезагрузку значения компилятором позже. (Там есть статья о ACCESS_ONCE на LWN, который расширяет это дальше)
  3. Правильный.
  4. Тоже правильно.
  5. (Подразумеваемый) барьер записи здесь необходим перед пробуждением потребителя, так как в противном случае потребитель может не увидеть обновленный head значение.

Потребитель:

  1. smp_read_barrier_depends() является барьером зависимости данных, который является более слабой формой барьера чтения (см. 2). Эффект в этом случае состоит в том, чтобы гарантировать, что buffer->tail читается перед использованием его в качестве индекса массива в buffer[tail],
  2. smp_mb() здесь есть полный барьер памяти, гарантирующий, что все операции чтения и записи совершаются к этому моменту.

Дополнительные ссылки:

(Примечание: я не совсем уверен в своих ответах на 5 для производителя и 1 для потребителя, но я считаю, что они справедливо приближают факты. Я настоятельно рекомендую прочитать страницу документации о барьерах памяти, так как это больше всеобъемлющий, чем я мог бы написать здесь.)

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