LINUX.ORG.RU

Intel MP & cache coherence (продолжение)


0

0

Не было доступа к сети - не мог ответить
и тема спустилась

>очень может быть. но. вы не знаете наверное, когда этот mov начнет
>выполняться, даже на pentium'ах есть write buffers. причем здесь
>я не имею в виду переупорядочивание кода, считаем, что mov уже
>выполнена. то есть у нас уже выполняются следующие инструкции, а
>mov пока еще только _считывает_ cache line, в случае cache miss.

Хорошо
на уровне snoop сие не пройдет (из-за буферов)
пусть запись ведется через выход на шину lock xchg..., а чтение - через mov. Тогда имеет место атомарность(при условии выровненных данных - единственных, которые можно получить компиляцией gcc при условии кодирования по стандарту ISO C, т.е. без пакования структур и некорректных приведений вида void *a; ...(int *)a...)

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

Для выровненных данных порядок не может измениться, тк все данные передаются в одной шинной транзакции(абсолютно одновременно)

★★
Ответ на: комментарий от idle

>вот как бы и нет. я припоминаю (вроде бы :), что не один >раз встречал патчи, где добавлялся барьер после unlock() >с комментарием, что unlock не достаточно.

Мммм... тогда имеет место большая проблема с Linux kernel, т.к. ето полностью дискредитирует концепцию spin-блокировки в том смысле в котором она везде описана.

Murr ★★
() автор топика
Ответ на: комментарий от Murr

Еще варианты - посмотреть в DDK/2000 source, что пишет MS на тему спина и барьеров, ну и во фрюниксах. На диске есть еще несколько proprietary юниксов, но они, к сожалению, не поддерживают SMP.

Murr ★★
() автор топика
Ответ на: комментарий от Murr

> вот как бы и нет. я припоминаю (вроде бы :), что не один
> раз встречал патчи, где добавлялся барьер после unlock()

вот, например:
http://linux.bkbits.net:8080/linux-2.5/gnupatch@418aac44xNhoSaqW3Ln-qJqA9k8Hew

> Попробую сейчас выкачать manuals по Alpha и IA-32

только вряд ли там будет что-то про то, что обещает linux
реализация spin lock.

зато, может, сможете мне обьяснить read_barrier_depends() !
то есть, я способен понять комментарий в system.h, но
не могу представить, что должен делать процессор такого,
чтобы этот read_barrier_depends() был бы не nop.

> Мммм... тогда имеет место большая проблема с Linux kernel,
> т.к. ето полностью дискредитирует концепцию spin-блокировки

да ладно :) я, признаться, вообще не знал, что существует
какое-то общее определение. но даже если оно в linux и
отличается, в чем проблема? лишь бы правильно использовалось

idle ★★★★★
()
Ответ на: комментарий от idle

>вот, например:
http://linux.bkbits.net:8080/linux-2.5/gnupatch@418aac44xNhoSaqW3Ln-qJqA9k8Hew

Дык тут вообще нет ничего про spin-блокировки, обычный memory barrier (может чуть более оптимальный из-за того, что заточен под конкретную операцию).

>только вряд ли там будет что-то про то, что обещает linux
>реализация spin lock.

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

>да ладно :) я, признаться, вообще не знал, что существует
>какое-то общее определение. но даже если оно в linux и
>отличается, в чем проблема? лишь бы правильно использовалось

Дык во всех книжках (в том числе и по Linux kernel) spin блокировка определяется как critical section. Если там что-то может переупорядочиваться, то какой нафиг тут critical section? :)

Murr ★★
() автор топика
Ответ на: комментарий от Murr

> Дык тут вообще нет ничего про spin-блокировки, обычный memory barrier

нет, смотрите комментарий:
> #   The spin_unlock() in rotate_reclaimable_page() is not a sufficient memory
> #   barrier.

и сам патч:
        if (!TestClearPageReclaim(page) || rotate_reclaimable_page(page)) {
-               smp_mb__after_clear_bit();
        }
+       smp_mb__after_clear_bit();
        wake_up_page(page, PG_writeback);

rotate_reclaimable_page() делает spin_unock и возвращает 0,
до этого патча в этом случае не было барьера перед wakeup().

> всех книжках (в том числе и по Linux kernel) spin блокировка определяется
> как critical section Если там что-то может переупорядочиваться, то какой
> нафиг тут critical section? :)

вот такое, у нас, дяденька, хреновое лето :)

но ведь там, я думаю, не написано, что при выходе из критической секции
все pending stores должны завершиться?

idle ★★★★★
()
Ответ на: комментарий от idle

>но ведь там, я думаю, не написано, что при выходе из критической секции >все pending stores должны завершиться?

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

В Windows, как я вижу в DDK, поступили грамотно - все примитивы синхронизации (в т.ч. spin-блокировка) подразумевают memory barriers.

Торвальдс опять отличился, мля.

Murr ★★
() автор топика
Ответ на: комментарий от Murr

Да и не только в Windows, в BSD MD-код тоже не виден пользователю. Только в Linux все кишки торчат наружу... :-/

Murr ★★
() автор топика
Ответ на: комментарий от Murr

прошу прощения что влезаю в вашу бесседу
но у меня такой насущный вопрос

int var;
rwb();
var++;
wmb();

будут ли при таком коде все процессора
гарантированно видеть изменение переменной ?
если планировщик заберет управление у процесcа
после var++; достаточно ли rmb();
для того чтобы процесор на котором выполняется
эта операция увидел изменение ?
и вобще атомарна ли операция var++ или var = xxx ?
сорри за несколько муторное изложение вопросов...





anonymous
()
Ответ на: комментарий от idle

idle:

Еще такой момент по поводу спина и барьеров если спин не подразумевает write barrier, то получается, что в конце практической каждой критической spin-секции нужно ставить самому барьер (ведь основная задача спина - не что иное, как защищенный доступ на изменение shared data), но тем не менее в коде Linux барьеры - достаточно редкое явление.

Murr ★★
() автор топика
Ответ на: комментарий от anonymous

anonymous:

Должны видеть (если в реализации барьеров для твоей платформы не допущено ошибок).

А сама операция ++ неатомарна.

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

Так, переупорядочивать код доступа может компилятор (тут можно бороться с помощью объявлений volatile), может процессор (тут - барьеры). При етом еще непонятно как видны одни и те же данные в любой момент на каждом процессоре в MP конфигурации между барьерами (ведь на том же IA32 при обращении к невыровненным данным генерируется несколько шинных запросов, неясно на всех ли архитектурах атомарно обновление выровненных данных).

В твоем примере ты решил проблему с порядком кода (еще желательно переменную объявить volatile), но в промежутке между барьерами у тебя, вообще говоря, при обращении к твоей переменной возможны самые разные варианты (например, классический race condition при изменении твоей переменной или во время обновления на втором процессоре может быть вообще виден мусор в переменной - на IA32 такое возможно для невыровненной переменной, посколько новое значение будет записано в нескольких шинных транзакциях).

Мое мнение заключается в том, что все критические секции должны скрывать все подобные архитектурные нюансы. То есть человек понимает, что при обращении к shared data нужно использовать критическую секцию (как на чтение - так и на запись) - и в секции реализованы и атомарность доступа к блоку с данными на уровне компилятора и процессора и все барьеры и всё-всё-всё... Барьеры и прочая глупость должны всплывать при написании сильно оптимизированного для платформы кода, в Linux же ето, похоже, essential.

Murr ★★
() автор топика
Ответ на: комментарий от Murr

> Барьеры и прочая глупость должны всплывать при написании > сильно оптимизированного для платформы кода, в Linux же ето, > похоже, essential.

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

пример. есть __set_task_state(), и есть set_task_state(), то же самое, но с барьером. и есть куча ошибок, связанных с использованием первого вместо второго. стоила ли овчинка выделки? я не знаю, но барьеры действетельно очень дороги.

а сравнивать с BSD кодом вообще неинтересно, вот когда они будут поддерживать SMP в той же степени, тогда и можно будет посмотреть.

_лично_ я бы предпочел более простое и маленькое ядро, уж больно быстрые компьтеры сейчас. и пусть бы оно работало на 10% медленнее, но меня что-то не спрашивают :)

anonymous
()
Ответ на: комментарий от anonymous

idle:

все же насчет pending writes вне критических секций. Правильно ли я понял, что, например, drivers/char/n_tty.c:n_tty_receive_buf (пример с балды) может в режиме real_raw в той же критической секции на другом процессоре получить старое (необновленное предыдущим n_tty_receive_buf) значение read_head и read_cnt?

Murr ★★
() автор топика
Ответ на: комментарий от Murr

> все же насчет pending writes вне критических секций.

это я, блин, опять фигню спорол. то, что остаются pending writes,
это не проблема, даже явный wmb() не гарантирует, что они будут
flushed. это если говорить вообще, не имея в виду, например
pentium.

wmb() означает, что любая запись _после_ этого примитива попадет
в ram не раньше, чем любая запись _до_ него.

> Правильно ли я понял, что, например, drivers/char/n_tty.c:n_tty_receive_buf
> (пример с балды) может в режиме real_raw в той же критической секции на другом
> процессоре получить старое (необновленное предыдущим n_tty_receive_buf)
> значение read_head и read_cnt?

здесь все должно быть корректно, иначе в linux вообще ничего бы
не работало. lock()/unlock() должны правильно защищать изменение
данных _внутри_ критической секции. иными словами, вот такой код
должен работать правильно:

int var;
spinlock_t lock;

void inc_var(void)
{
        spin_lock(&lock);
        ++var;
        spin_unlock(&lock);
}

на сегодняшний день мое понимание таково. spin_lock() является
барьером по чтению, spin_unlock() - по записи. их комбинация
является полным барьером.

то, что spin_unlock() "is not a sufficient memory barrier" я
понимаю следующим образом: если у нас есть чтение _после_
spin_unlock(), оно может выполниться раньше, чем завершится
критическая секция.

то есть вот такой код, например, неправильный:

int condition;
wait_queue_head_t* Q;

my_sleep()
{
        current->state = TASK_INTERRUPTIBLE;
        add_wait_queue(Q, ...); // lock()+unlock()

        // ЗДЕСЬ НУЖЕН БАРЬЕР! mb()

        if (!condition) schedule();

        current->state = TASK_RUNNING;
        remove_wait_queue(&wq, &__wait);
}

my_wake()
{
        condition = 1;
        mb();
        if (waitqueue_active(&Q)
                wake_up(&Q);
}

                                имеем:
my_sleep() на CPU_0:                            my_wake() на CPU_1

во время добавления процесса в очередь
процессор спекулятивно читает condition == 0.
add_wait_queue() делает unlock()
                                                condition = 1;
                                                изменения в Q здесь еще не видны,
                                                wake_up не вызывается.
вызывается schedule(), и событие пропущено.


если бы там был mb(), то либо CPU_0 увидит condition == 1,
после того, как my_wake() сделает свой mb(), либо CPU_1 увидит
добавление процесса в очередь.

idle ★★★★★
()
Ответ на: комментарий от idle

> на сегодняшний день мое понимание таково

только вот я не уверен в его правильности, забыл добавить :)

idle ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.