N>Я совсем не понял логику этого решения. Можно переформулировать? N>По попыткам понять получилось что-то сильно более сложное, чем у меня. N>Можно написать на условном Verilog/VHDL, разберу.
Здравствуйте, netch80, Вы писали:
N>Я совсем не понял логику этого решения. Можно переформулировать? N>По попыткам понять получилось что-то сильно более сложное, чем у меня. N>Можно написать на условном Verilog/VHDL, разберу.
Здравствуйте, netch80, Вы писали:
N>Это был пример на заголовок IPv4 пакета. Там ровно эта проблема — порядок битов (даже не байтов). Сейчас из-за отсутствия таких определений приходится писать так:
N>
N>struct iphdr
N> {
N>#if __BYTE_ORDER == __LITTLE_ENDIAN
N> unsigned int ihl:4;
N> unsigned int version:4;
N>#elif __BYTE_ORDER == __BIG_ENDIAN
N> unsigned int version:4;
N> unsigned int ihl:4;
N>#else
N># error"Please fix <bits/endian.h>"
N>#endif
N>
N>ну и далее по тексту.
N>Мало того, в IPv6 ещё злобнее получилось — поле flow label занимает в BE нумерации биты 12-31 (не знаю, нафига им столько), и если смотреть по байтам или шекам, то на LE машине оно разорвано на два несвязанных куска.
Ну было бы странно из-за одного IP заголовка тащить в стандарт такое сложное описание всяких возможных раскладок в памяти. Будет проще, если проблему big-endian/little-endial рассматривать просто как вариант очередной упаковки при сериализации. Мне кажется идея сразу работать с сетевым порядком пакетов в памяти не работает в общем виде. Дальше у нас появляются пакеты, где переменная (в битах) длина полей или наличие отсутствие полей зависит от каких-то дополнительных условий и что делать в этом случае?
Часто, лучше "распаковать" пакет, поработать с ним, изменить как угодно, и "запаковать" обратно, чем пытаться работать с маппингом в памяти. Конечно, если у нас есть какая-то уже готовая огромная структура (несколько ГБ), например на диске, и в ней нужно заменить один бит, тогда подход с распаковкой/запаковкой не сработает эффективно.
Здравствуйте, netch80, Вы писали:
N>Ты имеешь в виду то, что поле какого-то полного размера (2, 4, 8 байт) хранится постоянно в памяти в чужом порядке (обычно — в BE при машине в LE; обратный вариант крайне редок) и переворачивается при чтении или записи?
Хранить и обрабатывать надо как раз в родном для архитектуры порядке, а переворачивать при отправке и приеме. Так оно вроде и делается в сетевых протоколах.
N>я имел в виду не столько оптимизацию или её отсутствие, сколько переносимость описания.
Для чего такие описания делать непременно на ЯВУ? Ну хорошо, C/C++ позволяет описать битовые поля в структуре, а как станете описывать параметры сигнала на линии? Все равно придется делать отдельное техническое описание, так что возможность определить структуру на ЯВУ ничего к этому не добавит. Поэтому такие вещи всегда определяются независимо: для сигналов в линии — амплитуды, длительности, промежутки, двоичная интерпретация и т.п., для битовой последовательности — порядок раскладки по байтам/словам на конкретной архитектуре, и только с этого уровня имеет смысла определять структуры.
Re[10]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, netch80, Вы писали:
N>На уровне C/C++ ты вообще отображённые на память регистры (если это те регистры процессора, в которые компилятор кладёт рабочие данные) не влазят в основную концепцию
Нет, я про регистры контроллеров внешних устройств. На PDP, для которого изначально делался C, вообще не было отдельных команд типа IN/OUT — каждый контроллер отображал группу своих регистров в адресное пространство, и для программ они были видны, как обычные ячейки памяти. Но с развитием кэширования и прочих оптимизаций от этого отказались, теперь в адресное пространство отображают только обычную память (основную и внешних устройств).
Re[9]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, netch80, Вы писали:
N>на чтение из этого таймера они не поставили защёлку
Выглядит, кстати, крайне странно. Как будто проектирование этой части схемы поручили новичку, он накосячил, начальник не проконтролировал, и оно так и пошло в продакшн, а когда спохватились — решили сохранить лицо и объявить, будто так и задумано.
Re[11]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Нет, я про регистры контроллеров внешних устройств. На PDP, для которого изначально делался C, вообще не было отдельных команд типа IN/OUT — каждый контроллер отображал группу своих регистров в адресное пространство, и для программ они были видны, как обычные ячейки памяти. Но с развитием кэширования и прочих оптимизаций от этого отказались, теперь в адресное пространство отображают только обычную память (основную и внешних устройств).
Не знаю кто от этого отказался, но в ARM (по крайней мере 32бит)
все управление IP блоками отображаются в память.
Нужно тебе узнать что за прерывание, читаешь с такого-то адреса,
нужна тебе послать по DMA N байт, в один определенный адрес пишешь
откуда, в другой куда, в третий сколько слов, по четвертому адресу записаываешь 1,
чтобы передача началась,
и так со всем остальным NAND, SPI, UART/RS232.
При включенной виртаульной памяти просто помечаешь диапазаны адресов
как некэшируемые и все.
Re[12]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, Zhendos, Вы писали:
Z>Не знаю кто от этого отказался, но в ARM
В ARM используется memory-mapped IO, для программиста это удобнее. Отказались прежде всего на "больших" архитектурах, где многоуровневая память, кэширование, тэги и прочее. Возможно, так проще рулить магистралью. Команды типа IN/OUT по определению выставляют сигнал обращения ко внешнему устройству, а в MMIO надо выделять зоны, декодировать адреса, использовать разные протоколы при работе с памятью и ВУ, дабы не усложнять жизнь контроллерам, и т.п.
Re[13]: Как вышло, что наложение предполагается по умолчанию
ЕМ>Команды типа IN/OUT по определению выставляют сигнал обращения ко внешнему устройству, а в MMIO надо выделять зоны, декодировать адреса, использовать разные протоколы при работе с памятью и ВУ, дабы не усложнять жизнь контроллерам, и т.п.
Для системного контроллера никакой разницы
И там и там надо декодировать адреса, выделять зоны, и использовать разные протоколы для общения с устройствами
Обычная рутина. Сидишь себе и пишешь state машины
ЕМ>Возможно, так проще рулить магистралью.
Не..
В процессоре обычно все сложности со временем
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Нет, я про регистры контроллеров внешних устройств. На PDP, для которого изначально делался C, вообще не было отдельных команд типа IN/OUT — каждый контроллер отображал группу своих регистров в адресное пространство, и для программ они были видны, как обычные ячейки памяти. Но с развитием кэширования и прочих оптимизаций от этого отказались, теперь в адресное пространство отображают только обычную память (основную и внешних устройств).
Всё как раз наоборот. Наличие отдельного пространства I/O — специфика, насколько я знаю, только линии 8080 -> x86, и изначально была вызвана просто разными методами подключения памяти и шины устройств. Потом в x86 их объединили, но отдельное I/O пространство сохранялось ради легаси.
Уже на PCI старались в существенной мере выносить вполне себе I/O на память. Сейчас есть варианты без I/O пространства вообще. Вот мне lspci рассказывает:
01:00.0 USB controller: Advanced Micro Devices, Inc. [AMD] 300 Series Chipset USB 3.1 xHCI Controller (rev 02) (prog-if 30 [XHCI])
Subsystem: ASMedia Technology Inc. 300 Series Chipset USB 3.1 xHCI Controller
Flags: bus master, fast devsel, latency 0, IRQ 36
Memory at fcea0000 (64-bit, non-prefetchable) [size=32K]
01:00.1 SATA controller: Advanced Micro Devices, Inc. [AMD] 300 Series Chipset SATA Controller (rev 02) (prog-if 01 [AHCI 1.0])
Subsystem: ASMedia Technology Inc. 300 Series Chipset SATA Controller
Flags: bus master, fast devsel, latency 0, IRQ 55
Memory at fce80000 (32-bit, non-prefetchable) [size=128K]
и так далее. В I/O к этим устройствам вообще нет доступа. (Часть настройки делается через configuration space, это третье адресное пространство — но самые базовые вещи.)
У этого подхода есть ещё несколько вкусностей, например:
1. Устройство можно целиком отдать в пользование виртуальному гостю, просто разрешив ему маппинг соответствующей области памяти.
2. Устройство можно отдать на чтение в userland. Это типовая манера для HPET таймера. В таком случае получение текущего времени вообще не требует переключения в ядро.
Обратная сторона — что то, что размещается в памяти, желательно чтобы не имело побочных эффектов от случайного чтения.
The God is real, unless declared integer.
Re[13]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Здравствуйте, Zhendos, Вы писали:
Z>>Не знаю кто от этого отказался, но в ARM
ЕМ>В ARM используется memory-mapped IO, для программиста это удобнее. Отказались прежде всего на "больших" архитектурах, где многоуровневая память, кэширование, тэги и прочее. Возможно, так проще рулить магистралью. Команды типа IN/OUT по определению выставляют сигнал обращения ко внешнему устройству, а в MMIO надо выделять зоны, декодировать адреса, использовать разные протоколы при работе с памятью и ВУ, дабы не усложнять жизнь контроллерам, и т.п.
У x86 это всё тоже есть. Только даже обычному системщику с драйверами не нужно.
Что какой-то регион пространства памяти объявлен uncacheable — задаётся в сервисных MTRR регистрах процессора тем кодом, что настраивает PCI мосты — обычно это ещё BIOS.
Что очень много реального I/O переносят в пространство памяти — я написал рядом.
The God is real, unless declared integer.
Re[10]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Здравствуйте, netch80, Вы писали:
N>>на чтение из этого таймера они не поставили защёлку
ЕМ>Выглядит, кстати, крайне странно. Как будто проектирование этой части схемы поручили новичку, он накосячил, начальник не проконтролировал, и оно так и пошло в продакшн, а когда спохватились — решили сохранить лицо и объявить, будто так и задумано.
Вполне возможно — у Intel (особенно тогда, середина 90-х) очень неровная политика в деталях. В процессорах это новые фичи — половина из них сделана, как будто кто-то запретил даже на полшага думать вперёд.
В 90-х они как раз создавали свои сетевухи ряда 8255x, там тоже ой не с первого раза получилось надёжно.
Но зато уже к середине 2000-х они отшлифовали средства разработки, заодно помогя индустрии сформировать всякие SystemVerilog...
The God is real, unless declared integer.
Re[10]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Здравствуйте, netch80, Вы писали:
N>>Ты имеешь в виду то, что поле какого-то полного размера (2, 4, 8 байт) хранится постоянно в памяти в чужом порядке (обычно — в BE при машине в LE; обратный вариант крайне редок) и переворачивается при чтении или записи?
ЕМ>Хранить и обрабатывать надо как раз в родном для архитектуры порядке, а переворачивать при отправке и приеме. Так оно вроде и делается в сетевых протоколах.
Ну ладно, поля размером в 2 или 4 байта ты перевернёшь так. А вот это вот "старшие 4 бита — версия, младшие — длина"?
А flow label в IPv6, который при рассмотрении в LE оказывается разрезан пополам? Переворачивать всё 4-байтное значение и резать на части?
Ну и ещё и не забыть сделать это вовремя во всех случаях (вроде бы просто, но можно пропустить).
N>>я имел в виду не столько оптимизацию или её отсутствие, сколько переносимость описания.
ЕМ>Для чего такие описания делать непременно на ЯВУ? Ну хорошо, C/C++ позволяет описать битовые поля в структуре, а как станете описывать параметры сигнала на линии?
А при чём тут параметры сигнала на линии? Это вообще напрямую никак не доступно ЦП, только периферии, а для неё уже свои языки типа Verilog. Куда-то ты тут загнался не туда.
EM> Все равно придется делать отдельное техническое описание, так что возможность определить структуру на ЯВУ ничего к этому не добавит. Поэтому такие вещи всегда определяются независимо: для сигналов в линии — амплитуды, длительности, промежутки, двоичная интерпретация и т.п., для битовой последовательности — порядок раскладки по байтам/словам на конкретной архитектуре, и только с этого уровня имеет смысла определять структуры.
Ну а вот авторы C# так не думают, раз ввели LayoutKind аж с двумя специализированными вариантами — упакованным и с конкретными смещениями.
Потому что копировать из одного в другое на каждый чих — дорого в рантайме. Легче чуть-чуть уточнить компилятору метод доступа.
The God is real, unless declared integer.
Re[8]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, Videoman, Вы писали:
V>Ну было бы странно из-за одного IP заголовка тащить в стандарт такое сложное описание всяких возможных раскладок в памяти.
Почему из-за одного IP заголовка? Таких структур десяток только у IETF и несколько сотен включая остальные вполне распространённые протоколы.
V> Будет проще, если проблему big-endian/little-endial рассматривать просто как вариант очередной упаковки при сериализации.
И гонять байты в памяти только ради финальной упаковки или начальной распаковки.
V> Мне кажется идея сразу работать с сетевым порядком пакетов в памяти не работает в общем виде. Дальше у нас появляются пакеты, где переменная (в битах) длина полей или наличие отсутствие полей зависит от каких-то дополнительных условий и что делать в этом случае?
И такие есть. Ну на такое двигают указатели.
V>Часто, лучше "распаковать" пакет, поработать с ним, изменить как угодно, и "запаковать" обратно, чем пытаться работать с маппингом в памяти.
И сколько таких случаев вы реально видели?
Даже на x86 прочесть другой порядок бит или байт банально. На каком-нибудь ARM ещё проще.
V> Конечно, если у нас есть какая-то уже готовая огромная структура (несколько ГБ), например на диске, и в ней нужно заменить один бит, тогда подход с распаковкой/запаковкой не сработает эффективно.
Ну такие и читают/пишут по блоку, там время даже полного копирования в памяти заведомо меньше времени I/O.
The God is real, unless declared integer.
Re[14]: Как вышло, что наложение предполагается по умолчанию
Здравствуйте, TailWind, Вы писали:
TW>Для системного контроллера никакой разницы TW>И там и там надо декодировать адреса, выделять зоны, и использовать разные протоколы для общения с устройствами
Под "разными протоколами" имеются в виду протоколы работы процессора с обычной памятью и областями ВУ. Традиционно в такой архитектуре и основная память, и ВУ висят на общей магистрали, устроены примитивно и асинхронно, и контроллеру, конечно, по барабану — увидел сигнал, опознал адрес, выставил ответ, ничего сложного. А когда добавляются особые режимы доступа к памяти (те же сигналы выборки/записи отдельных байтов), каждому контроллеру приходится все это отрабатывать.
TW>Обычная рутина. Сидишь себе и пишешь state машины
Это сейчас. А в 60-70-х все делалось практически вручную.
Re[11]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, netch80, Вы писали:
N>Ну ладно, поля размером в 2 или 4 байта ты перевернёшь так. А вот это вот "старшие 4 бита — версия, младшие — длина"?
А насколько часто это встречается, чтобы оправдать внесение в стандарт и реализацию в каждом компиляторе?
Re[12]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, Евгений Музыченко, Вы писали:
N>>Ну ладно, поля размером в 2 или 4 байта ты перевернёшь так. А вот это вот "старшие 4 бита — версия, младшие — длина"?
ЕМ>А насколько часто это встречается, чтобы оправдать внесение в стандарт и реализацию в каждом компиляторе?
Битовые поля, если ты не заметил, уже есть в стандарте и в любом компиляторе. Значит, оправдано.
The God is real, unless declared integer.
Re[9]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, netch80, Вы писали:
N>Почему из-за одного IP заголовка? Таких структур десяток только у IETF и несколько сотен включая остальные вполне распространённые протоколы.
Я не спорю, что есть частные случаи, когда можно читать или менять данные сохраняя раскладку, но таких < 1%. Во всех остальных протоколах нужно, как минимум, собрать несколько пакетов, что бы получить из них целиковое полезное содержимое и что-то там менять.
N>И гонять байты в памяти только ради финальной упаковки или начальной распаковки.
По скорости будет одинаково, т.к. по сравнению с памятью сеть это тормоз. Во всяком случае, это не медленнее чем каждый раз при работе с полем вызывать htol() + ltoh().
N>И такие есть. Ну на такое двигают указатели.
Только в каких-то уж очень частных случаях. В общем виде никакими указателями не отделаешься. Возьмем какой-нибудь mpeg — там просто меняешь значение поля и его длина в битах тут же меняется, да и еще где-нибудь в конце CRC нужно будет пересчитать.
N>И сколько таких случаев вы реально видели? N>Даже на x86 прочесть другой порядок бит или байт банально. На каком-нибудь ARM ещё проще.
Видимо мне так не везет, но почти все протоколы мультимедиа так устроены: сжатие (переменная длина), поля в битах, СRC, всякие маски сверху и т.д.
N>Ну такие и читают/пишут по блоку, там время даже полного копирования в памяти заведомо меньше времени I/O.
Полностью согласен, но тогда и формат данных стараются подобрать удобным для этого, выровнять структуры на границы секторов и т.д.
Здравствуйте, netch80, Вы писали:
N>Битовые поля, если ты не заметил, уже есть в стандарте и в любом компиляторе. Значит, оправдано.
Я-то заметил, но определены они там чисто формально, фактическое размещение зависит от реализации. Как при таком подходе определить в стандарте размещения для различных архитектур? Если просто стандартизировать атрибуты с благими пожеланиями по их реализации, то бОльшая часть компиляторов на это забьет.
А если это все-таки протащить в стандарт, то сразу же всплывут и предложения атрибутов для LE/BE. Затем кто-то захочет (и наверняка уже захотел), чтобы на любой архитектуре он мог автоматом читать/писать float/double в формате любой другой архитектуры. И где будет пора остановиться?
Re[10]: Как вышло, что наложение предполагается по умолчанию?
Здравствуйте, Videoman, Вы писали:
N>>И сколько таких случаев вы реально видели? N>>Даже на x86 прочесть другой порядок бит или байт банально. На каком-нибудь ARM ещё проще. V>Видимо мне так не везет, но почти все протоколы мультимедиа так устроены: сжатие (переменная длина), поля в битах, СRC, всякие маски сверху и т.д.
Вот именно. Поля в битах, маски...
Вот представим себе ситуацию: в байте 3 битовых поля, начиная со старшего: в 1, 4 и 3 бита. Думаю, в каком-нибудь аудио-видеокодеке найдёте что-то очень похожее.
Наверняка для чтения/записи подобной структуры двигаете значения операторами сдвига и читаете/пишете масками? А почему, если это заморочно? (ну да, я давно привык, и вы наверняка тоже. а другим?)
Ну да, ваша реплика была к другому — к ситуации типа "при наличии бита Z в позиции X возникает дополнительное двухбайтовое поле по смещению Y". Вот про это я и говорил, что указателями оно (обычно) разбирается.
N>>Почему из-за одного IP заголовка? Таких структур десяток только у IETF и несколько сотен включая остальные вполне распространённые протоколы. V>Я не спорю, что есть частные случаи, когда можно читать или менять данные сохраняя раскладку, но таких < 1%. Во всех остальных протоколах нужно, как минимум, собрать несколько пакетов, что бы получить из них целиковое полезное содержимое и что-то там менять.
Пакетов чего?
Если речь про какие-нибудь данные TCP, это уже не задача прикладного уровня.
N>>И гонять байты в памяти только ради финальной упаковки или начальной распаковки. V>По скорости будет одинаково, т.к. по сравнению с памятью сеть это тормоз. Во всяком случае, это не медленнее чем каждый раз при работе с полем вызывать htol() + ltoh().
Боюсь, как раз в случае всяких видеокодеков это уже совсем не так. Иначе Intel не занимался бы введением всяких BMI2.
Но я согласен, что конкретный выхлоп там ещё мерять надо. Мой поинт был за то, насколько удобство описания для программиста помогает верификации программы.
N>>И такие есть. Ну на такое двигают указатели. V>Только в каких-то уж очень частных случаях. В общем виде никакими указателями не отделаешься. Возьмем какой-нибудь mpeg — там просто меняешь значение поля и его длина в битах тут же меняется, да и еще где-нибудь в конце CRC нужно будет пересчитать.
Ну так я и не претендовал на решение всех проблем. Но как по мне проблема с выбором порядка описания весьма типична.
N>>Ну такие и читают/пишут по блоку, там время даже полного копирования в памяти заведомо меньше времени I/O. V>Полностью согласен, но тогда и формат данных стараются подобрать удобным для этого, выровнять структуры на границы секторов и т.д.
Или просто подровнять адрес позиции read/write под типовой размер блока.