xv6: чтение тиков напрямую, не снимая блокировку тиков?

Я работаю над заданием в курсе операционной системы на Xv6. Мне нужно реализовать структуру состояния данных для процесса для времени его создания, времени завершения, времени сна и т. Д.

На данный момент я решил использовать переменную ticks напрямую, без использования tickslock, потому что не рекомендуется использовать блокировку и замедлять работу системы для такой цели с низким приоритетом.

Поскольку переменная тиков используется только так: ticks++, есть ли способ, при помощи которого я попытаюсь получить текущее количество тиков и получить неправильное число?

Я не против ошибиться числом на + -10 делений, но есть ли способ, при котором он действительно будет отключен. Например, когда число 01111111111111111 будет увеличиваться, потребуется изменить 2 байта. Итак, мой вопрос: возможно ли, что ЦП хранит данные поэтапно, а другой ЦП сможет извлекать данные из этой области памяти между началом и завершением операции сохранения?

На мой взгляд, если компилятор создаст инструкцию mov или инструкцию inc, я хочу знать, видна ли операция сохранения между ее началом и концом.


person Adam Jensen    schedule 25.03.2019    source источник
comment
Остерегайтесь кеширования.   -  person Fred Larson    schedule 25.03.2019
comment
Когда происходит прерывание, оно активируется в конце текущей выполняемой инструкции, но до начала следующей.   -  person Weather Vane    schedule 25.03.2019
comment
Вы спрашиваете о ассемблере или о c? Семантика C не указана в инструкциях ассемблера.   -  person Antti Haapala    schedule 25.03.2019
comment
@AnttiHaapala на самом деле не имеет значения, потому что для меня важно просто хранение данных. я не возражаю, если компилятор разделяет выборку и сохранение. Я хочу быть уверенным, что хранение не может быть просмотрено другим процессом до его завершения. поскольку я вижу, что будет одна инструкция для хранения, я не думаю, что компилятор разделит сохранение на несколько инструкций, поэтому я в порядке, предполагая, что, даже если она не указана.   -  person Adam Jensen    schedule 25.03.2019
comment
Использование tick в цикле в C позволит компилятору прочитать его один раз и повторно использовать одно и то же значение. Вам понадобится макрос READ_ONCE, который используется в ядре Linux, например *(volatile int*)&tick. Но да, для переменной, достаточно узкой, чтобы поместиться в один целочисленный регистр, обычно можно с уверенностью предположить, что нормальный компилятор запишет ее с одним хранилищем двойных слов. С одним писателем и несколькими читателями, да, читатели могут просто читать его без какой-либо блокировки.   -  person Peter Cordes    schedule 26.03.2019
comment
(работаю над ответом, но, вероятно, вы просто хотите превратить ticks в volatile unsigned ticks. Ссылки по теме: Почему целочисленное присвоение естественно выровненной переменной атомарно на x86? / Программирование MCU - оптимизация C ++ O2 прерывается, пока цикл. С одним писателем вы правы, что не делаете нужен атомарный inc, только для того, чтобы его часть хранилища была атомарной. Но см. Может ли num ++ быть атомарным для 'int num'? на случай, если вы любопытно.   -  person Peter Cordes    schedule 26.03.2019
comment
@PeterCordes thx, был очень полезен   -  person Adam Jensen    schedule 26.03.2019


Ответы (2)


В asm нет проблем: выровненные загрузки / сохранения, выполненные с помощью одной инструкции на x86, являются атомарными до ширины qword (8 байт). Почему целочисленное присваивание выполняется с естественным выравниванием переменная atomic на x86?

(На 486 гарантия распространяется только на 4-байтовые выровненные значения, и, возможно, даже не на 386, так что, возможно, именно поэтому Xv6 использует блокировку? Я не уверен, что это должно быть многоядерно безопасным на 386; мой понимание состоит в том, что редкие 386 SMP-машины не совсем точно реализуют современную модель памяти x86 (порядок памяти и т. д.).

Но C - это не asm. Использование простой переменной, отличной от atomic, из нескольких «потоков» одновременно является неопределенным поведением, если только все потоки не только читают. Это означает, что компиляторы могут предположить, что обычная переменная C не изменяется асинхронно другими потоками.

Использование ticks в цикле на C позволит компилятору прочтите его один раз и продолжайте использовать одно и то же значение постоянно. Вам понадобится макрос READ_ONCE, который используется в ядре Linux, например *(volatile int*)&ticks. Или просто объявите это как volatile unsigned ticks;


Для переменной, достаточно узкой, чтобы поместиться в один целочисленный регистр, вероятно, можно с уверенностью предположить, что нормальный компилятор запишет ее с одним хранилищем двойных слов, будь то mov или адрес памяти inc или add dword [mem], 1. (Вы не можете предположить, что компилятор будет использовать назначение памяти inc / add, поэтому вы не можете зависеть от одноядерного атомарного приращения по отношению к прерываниям.)

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

Даже в портативном ISO C в volatile sig_atomic_t есть очень ограниченные гарантии безопасной работы при написании обработчиком сигнала и чтении потоком, запустившим обработчик сигнала. (Не обязательно другими потоками: в ISO C volatile не избегает UB гонки данных. Но на практике на x86 с не враждебными компиляторами это нормально.)

(Сигналы POSIX являются эквивалентом прерываний в пользовательском пространстве.)

См. Также Может ли num ++ быть атомарным вместо int num?

Чтобы один поток опубликовал более широкий счетчик из двух половин, вы обычно используете SeqLock. С 1 записывающим устройством и несколькими считывателями фактической блокировки нет, просто повторите попытку считывателя, если запись перекрывается с их чтением. См. Реализация 64-битного атомного счетчика с 32-битным атомаром

person Peter Cordes    schedule 04.06.2019
comment
(Я получала этот ответ во вкладке браузера в течение нескольких месяцев ›.‹) - person Peter Cordes; 04.06.2019

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

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

person Omer Efrat    schedule 26.03.2019
comment
да, вы правы, но я хотел убедиться, что не прибегаю к блокировкам, потому что боюсь, что это состояние гонки. Я предпочитаю не реализовывать такую ​​цель с низким приоритетом с блокировками, если есть состояние гонки. - person Adam Jensen; 27.03.2019
comment
Ваше рассуждение о том, что ++ было бы безопасно без атомарности, неверно: если имел место разрыв между байтами ticks, читатель мог бы увидеть большие скачки и немонотонное поведение часов. например во время увеличения от 0x0001ffff до 0x00020000 считыватель, который сначала читает младшую половину, может видеть 0x0002ffff. Затем, вскоре после этого, смотрите 0x00020001, так что время идет вспять. Вот почему и сохранение производителем, и загрузка потребителем должны быть атомарными. (Которая есть на x86). Причина, по которой здесь используется блокировка, заключается в том, чтобы обойти неопределенное поведение C data-race undefined, но да, это не настоящая гонка. - person Peter Cordes; 27.03.2019
comment
Или, может быть, код использует блокировку для правильной работы даже без гарантии атомарности для volatile unsigned int. Только до C11 у нас будет языковая поддержка для atomic_load_explicit(&ticks, memory_order_relaxed);. (Вы бы не хотели использовать ticks++ на _Atomic unsigned ticks, атомарный RMW не нужен в случае с одним производителем.) - person Peter Cordes; 27.03.2019