Комментарии 54
В целом, я бы оценил вероятность того, что текст был переведен или составлен с помощью ChatGPT, как достаточно высокую, но не могу подтвердить это с абсолютной уверенностью без дополнительных данных.
PS сама тема интересная
ну, наверно можно задать автору какой-нибудь вопрос, попросить какие-то подробности чтобы это проверить.
Эта тема для меня близка, но вот читать было тяжело.
Эксперт говорит, что вопрос или вопросы не совсем корректные
Не то слово "некорректны".
Использование DMA для копирования память-память - это совершенно не понятный случай.
DMA - это, например, писать из регистра АЦП данные в память (или любое подобное).
Так что, пунктом 0 я бы (хоть и не претендую на эксперта) сказал бы "чувак ты не для того используешь DMA и просто не понимаешь спрашиваешь и это видно по вопросам."
А все остальные ответы "эксперта" вроде бы в целом верные, при этом весьма обтекаемые, и прямого отношения к вопросу "в чем концептуальное не понимание темы у спрашивающего" просто не имеют.
while (dma_async_is_tx_complete(chan, cookie, NULL, NULL) == DMA_IN_PROGRESS)
Ждать окончания DMA операции по опросу - это еще более странное решение (ну разве что для демонстрации..).
"И если мы говорим о взаимодействии с каким-либо другим устройством обработки данных вне центрального процессора, то это автоматически не уровень приложения", это уровень драйверов или уровень модулей ядра системы.
Условность все это..
Для отладки логики/железа (конкретного железа) можно и не оформлять ПО как драйвер ядра.
Драйвер - это, в основном, скрыть фичи конкретной железяки за общим API. Это не всегда нужно в частных случаях.
"Ждать окончания DMA операции по опросу " не совсем - процессор в это время может "выполнять" другие задачи, ну или хотя бы обработчики прерываний. Но прерывания прекрасно могут проходить и при "традиционном" memcpy
" писать из регистра АЦП данные " - есть большая вероятность того что автора данного материала так никогда не делал. Ну и вообще если судить по контексту то в коментарии рассуждения переходят от линуксов на что то более низкоуровневое.
Но прерывания прекрасно могут проходить и при "традиционном" memcpy
да, только они будут задерживать копирование (по крайней мере на одноядерной системе), а на длительность копирования через DMA, вроде как не дожны влиять.
DMA (ну я так думаю :)...) используют ту же шину для доступа к ОЗУ, что и для доступа к программному коду в ОЗУ. Тут (а в одноядерных (? какой) часто кэша нету) без кэша будет явное замедление (если DMA приоритет выше). С кэшем не влияет.
Кэш привязан не к шине, кэш привязан к типу операций, в процессоре четко различаются операции по доступу к памяти программ и к памяти данных даже если они идут через одну шину (а в АРМах например даже физические шины разные). Соответственно там два разных кэша физически, кеш данных, кэш инструкций.
в АРМах например даже физические шины разные
Любая программа и есть данные. Разные шины для разных видов памяти, но не разной информации. Я не встречал CPU, где при рефлексии необходимо было копировать память, а не просто передать управление на неё.
Думаю, что руки7 несколько неточно выразились или просто не поняли шинную структуру, скажем того же АРМ. Ибо в ARM7 фон Неймановская архитектура..... Такие вот дела.
Так даже в случае Гарвардской архитектуры, например, в AVR, все равно есть возможность выполнять код из RAM.
Другое дело, что в Cortex-M7 есть Гарвардская архитектура кеша, с раздельными кешами для инструкций и данных. Но в дополнении к этому есть две области кеша, выделенные для доступа, как к данным, так и к инструкциям, что позволяет избежать копирования при рефлексии. Строго говоря, загрузка кода из внешнего устройства (например, флеш-карты) в память - это уже рефлексия, так как то, что было данными, в результате обработки загрузчиком превращается в исполняемый код.
Ну, вы упоминали все-же ARM, где до ARM9 была фон Неймановская. Ну и наличие MMU в тех-же Cortex никто не отменял.И данные в ОЗУ вдруг становятся потоком команд, если их запрашивает дешифратор (через кеш, кстати). Вот TCM там разный для данных и программ, - это да. Ну и M7 - все-же для embending-а.
если проанализировать к каким типам памяти обращается самая универсальная инструкция абстрактного процессора для вызова функции, в виде:
call func;
мы бы выяснили что она меняет указатель для памяти из которой грузятся инструкции и
сохраняет текущий адрес в стек в памяти данных,
это не зависит от того какая у нас Гарвардская архитектура или Парижская или какая другая.
То есть существует два типа памяти при ближайшем рассмотрении работы процессора, безотносительно того к какой физической архитектуре шин он подключен.
Сами поняли что написали? Указатель - это данные. Вне зависимости от того, указывает он на структуру данных, функцию или адрес возврата. Просто в Гарвардской архитектуре указатель на область кода не может быть преобразован в указатель на область данных и наоборот.
А я писал о рефлексии. И тут уже грань между данными и кодом оказывается полностью стерта. И для ее поддержки даже чисто Гарвардской архитектуры CPU поддерживают частично Фон-Неймановскую.
или Парижская
Впервые о такой слышу. Дайте ссылку на описание.
или Парижская
вы совершенно правильно поняли, "Парижская архитектура" - это архитектура которой нет или "пока нет", то есть я хотел подчеркнуть, что то, что я там написал справедливо даже для архитектуры, которой "пока нет".
Указатель - это данные
по моему, это все таки зависит от контекста, но "данные" - это конечно более общее понятие. Для кого-то код это данные, а для кого-то побуждение к действию.
И также про указатель, для кого-то указатель это данные, а для кого-то это адрес, по которому лежат какие-то данные.
"Парижская архитектура" - это архитектура которой нет или "пока нет", то есть я хотел подчеркнуть, что то, что я там написал справедливо даже для архитектуры, которой "пока нет".
Это называется подмена понятия, и является ключевым признаком демагогии.
по моему, это все таки зависит от контекста,
Еще раз повторю то, что Вы принципиально игнорируете: РЕФЛЕКСИЯ! Для CPU один и тот адрес в памяти с одним и тем же содержимым может быть как данными, так и кодом.
Еще раз повторю то, что Вы принципиально игнорируете: РЕФЛЕКСИЯ! Для CPU один и тот адрес в памяти с одним и тем же содержимым может быть как данными, так и кодом.
я не игнорирую, я в общем-то согласен, поэтому не очень понимаю что тут обсуждать.
Единственное я бы не сужал так понимание о рефлексии, потому что оно все-таки гораздо шире, как мне кажется, и наверно не только мне:
поэтому не очень понимаю что тут обсуждать
Мы обсуждаем моё утверждение, что любая программа и есть данные.
Где я сужаю? Я даже расширяю, по сравнению с Википедией, считая, что обработка исполняемого файла загрузчиком - тоже рефлексия. А уж мой любимый динамический SQL, CompileToAssembly в C# или запуск кода из RAM на AVR - это вообще классика.
запуск кода из RAM на AVR - это вообще классика.
честно говоря я такой возможности не помню, чтобы на AVR можно было выполнять код из РАМ, но мне такое не требовалось. Наверно я что-то пропустил.
Но то что в AVR ассемблере есть инструкция
LPM (Load from Program Memory)
для загрузки данных из памяти программ это я хорошо помню, приходилось пользоваться. То есть, то что содержимое памяти программ можно использовать как данные, а не как инструкции, это само собой разумеется.
Ну а для STM8L исполнение кода из RAM - вообще необходимость, так как в low power run mode иначе не попасть
вот это я понимаю, понимаю так: однажды увидев такую супер-хитрую, но красивую штуку, хочется разделить с кем-то свой восторг от этой интелектуальной красоты, с кем-то этим поделиться.
У меня много раз возникало такое состояние! Я, собственно, и статьи писать начал поэтому. Кроме рефлексии еще есть двойственность как в квантовой физике, эфекты наблюдателя, замена рекурсии простым циклом, INonDelegatingUnknown, ... Разные еще есть супер-хитрые хитрости, осознание которых вдохновляет.
Вот здесь вот:
RTOS или не RTOS вот в чем вопрос 2, или Windows тоже RTOS?
например, я нашел то что мне тоже показалось проявлениями рефлексии.
вот это я понимаю, понимаю так: однажды увидев такую супер-хитрую, но красивую штуку, хочется разделить с кем-то свой восторг от этой интелектуальной красоты,
Так там это штатный метод минимизации потребления для батарейных устройств. Вполне себе описан в дейташите.
Ну а для STM8L исполнение кода из RAM
только прерываний тама нету..... В LPR
мы бы выяснили что она меняет указатель для памяти из которой грузятся инструкции и
Точно? А, простите-извините, вы писали на ассемблере? Ибо у "абстрактного процессора" текущий программный счетчик пушится в стек а в него загружается адрес вызываемой подпрограммы. Где вы увидели указатель (или вы считаете програмный счетчик указателем? Что в какой-то мере правда, хотя это чисто регистр АЛУ).
И по "Парижской" - а раскажите, мне ну очень интересно.
Кеш привязан к шине (в зависимости от архитектуры могут быть 2 и больше), шина(ы) привязана(ы) к шине ОЗУ. Шина ОЗУ одна. Есть, правда, двухпортовое ОЗУ, но малоиспользуемое в качестве основной. Шина DMA тоже привязана к шине памяти. Так что пофиг, все равно упираемся в доступ к шине ОЗУ. И с кешем (в пределах до половины думаю) при копировании будет 1 цикл доступа (и впотом фоновое копрование) и при работе DMA - 2 цикла и + необходимость обновления кеша. В процах без кеша сильно не так. Я чуток подетальнее пояснил свою точку зрения ибо ну нифига не понял сообщение руки7.
вот здесь вот можно схему с шинами посмотреть, для примера:
Какие бывают Cortex-M7 ARM-ы, периферия, шины, память, … DMA
я по ней работал, в том числе, если что.
Может на STM32 делал. Авторы они такие...
Но DMA в хорошо авария круто нагружает шину процессора. Плюс данные из кэша вымываются. И сам кэш тоже прилично нагружается. В результате смысла особого в этом нет.
Использование DMA для копирования память-память - это совершенно не понятный случай. DMA - это, например, писать из регистра АЦП данные в память (или любое подобное).
Это если у Вас физически один CPU. При множестве CPU использование DMA может быть вполне оправдано, например, для CoW, если модифицирующий страницу процесс выполняется совсем на другом CPU (именно CPU, а не ядре), чем родительский и исходной страницы у него в кеше ещё нет.
Отдельная история с ссNUMA, где, по сути, DMA операции инициируются аппаратно и для программы прозрачны.
Оба CPU все равно работают с одним адресным пространством.
Конечно можно придумать частный случай когда это понадобится (копирование памяти).
Но в пример (на основе чего статья основана) - это точно "сову на глобус".
Оба CPU все равно работают с одним адресным пространством.
Пространство одно, но физически память у CPU разная (NUMA). Именно поэтому я и указал, что в: "ссNUMA, по сути, DMA операции инициируются аппаратно". То есть, там уже пошли через DMA, не загружая такими пересылками CPU.
Ну и в случае CoW у нас наоборот. До записи в страницу виртуальные адреса страницы разные, а физически страница одна. Она будет скопирована только при попытке записи в неё. И если её не было в кеше CPU, то грузить её в кеш не имеет никакого смысла, раз все равно запись пойдет в скопированную страницу.
Использование DMA для копирования память-память - это совершенно не понятный случай.
я хорошо помню что когда начал разбираться с конкретным DMA в конкретном АРМ-е, я начал с того что сделал копирование из памяти в память чтобы проверить, что я все правильно понял в настройках контроллера ДМА и данные действительно копируются.
Потом есть некоторые девайсы которые сразу пишут в память, а не в регистры, чтобы выгребать эти данные вовремя, может понадобиться копирование память-память. Насколько я помню у нас в АРМе Ethernet контроллер имеет свое встроенное ДМА, он сразу пишет пакеты в ОЗУ. Ему может понадобиться внешнее ДМА чтобы расширить буферизацию, что-ли, хотя тоже натянутый пример получился,
я в принципе согласен, для копирование память-память трудно придумать адекватную задачу на однопроцессорной системе.
Использование DMA для копирования память-память - это совершенно не понятный случай.
Не всё итак однозначно. У меня был случай когда это был вполне понятный случай. Есть система на FPGA которая занимается вводом/выводом видео с разных хитрых интерфейсов. Одна из возможностей системы делать скриншоты. Для этого надо просто из куска памяти куда складываются видеодананные, скопировать эти самые данные в другой кусок, откуда их уже можно по медленному интерфейсу долго и мучительно пересылать дальше. Там вот процессор которые реализован в FPGA с этим ну никак не справится, а DMA память-память справляется ну отлично.
Использование DMA для копирования память-память - это совершенно не понятный случай.
Ну, а другие пользуют, хотя вам и не понятный. Пример: копирование данных из област данных приемного USB буфера в буфер для передачи по USART (буфер освобожден будет явно быстрее а проц при этом что-то другое сделает).
Ждать окончания DMA операции по опросу
тут согласен, - есть легкий идиотизм. Хотя он тут асинк, то есть проц переключится на обработку других потоков.
Ну и вообще так, если DMA не ускоряет то это означает что испортили хорошую вещь. По сути это аппаратно реализованный поток узко заточенный под копирование блоков данных, да еще без оверхеда на всякие сохранения стэка. Только надо продумать цепочку обработки. На микропроцессорных контроллерах это может быть прямо таки панацеей. Не говоря уж о старых добрых процах типа z80 где копирование блока данных было отдельным исскуством, просчитываемого до такта процессора
Тут нельзя забывать что шина DMA в SoC-ах обычно одна на всех. И её пытаются использовать одновременно многие - файловая система, системные шины типа I3C, PCI , мелкая периферия типа SPI, I2C, UART-ы, SDIO, дисплей проч.
![](https://habrastorage.org/getpro/habr/upload_files/152/4c4/1de/1524c41de193f2ea73ba55d5f8df6689.png)
И разные потоки DMA имеют приоритеты.
Короче, очередь до выполнения канала DMA по пересылки в память может дойти, когда memcpy уже десять раз могло бы выполнить пересылку. Тут надо еще помнить, что у процессора всегда приоритет доступа выше чем у DMA и он всегда может прервать любой DMA.
Кстати DMA могут сбоить с быстрой периферией и периферией без FIFO (об этом отдельно пишется в errata-х) и отладка надежного DMA особо сложный вопрос в разработке драйверов. Поэтому любая пересылка по DMA должна быть готова повторить или возобновить DMA в случае отказа или конфликта с другой периферией.
Количество аппаратных каналов DMA ограниченно. А пользовательских процессов можно создать огромное количество. Лучше DMA для работы с аппаратурой в режиме ядра грамотно использовать. А пользовательские процессы не допускать к DMA напрямую.
Логично предположить, что операционка жестко резервирует один канал DMA для юзера.
Этот единственный канал используется всякий раз когда вызывается функция dma_async_memcpy_buf_to_buf
Скорее всего у канала самый низкий приоритет и вызов функции dma_async_memcpy_buf_to_buf вызывает постановку запроса в очередь на DMA и ожидание в очереди. Т.е. очень затратный по времени процесс. Такой DMA может быть оправдан только если речь идет о мегабайтах пересылаемой информации.
Тут нечего особо думать. Аппаратная реализация DMA и IOMMU может кардинально различаться у разных систем. И если у вас не embedded или вы не пишете под конкретную платформу/железку - просто забудьте. Оставьте эти оптимизации писателям драйверов.
Та же реализация memcpy вполне может под капотом использовать DMA, где это действительно нужно. Люди, занимающиеся сопровождением платформ, ядра и драйверов, лучше вас знают где и что использовать.
Попытки перехитрить систему и прыгнуть выше пупка как правило имеют обратный эффект, как и показано в данной статье.
Как-то ни о чём статья.
Можно? Да, можно.
Нужно? От железа зависит. Помню программировал на старых AXIS чипах, так там DMA каналы повсюду можно было использовать, не только как ext<->RAM, но и как RAM<->RAM тоже. Примеры и рекомендации, когда такой memcpy лучше, были сразу в документации.
старых AXIS чипах
ну так чипы весьма специализированные, там упор на видео идет. Да и японские корни видны в решениях...
Я про etrax 100 отсюда https://en.m.wikipedia.org/wiki/ETRAX_CRIS
Тогда ни видео ни японцев ещё не было ;)
В микроконтроллерах очень удобно использовать DMA для memcpy.
Вот буквально код для Artery:
bool DMAxChannelyIRQHandler(uint8_t dma_num, DmaChannel_t channel) {
bool res = false;
DmaChannelHandle_t * Node=DmaChannelGetNodeItem( dma_num, channel);
if(Node){
DmaChannelInfo_t * Info=DmaChannelGetInfo( dma_num, channel);
if(Info) {
res = true;
flag_status ret=RESET;
ret = dma_interrupt_flag_get( Info->Flag.tx_done);
if(SET==ret){
dma_flag_clear(Info->Flag.tx_done);
Node->tx_done=true;
Node->tx_done_cnt++;
res = true;
}
ret = dma_interrupt_flag_get( Info->Flag.half_tx_done);
if(SET==ret){
dma_flag_clear(Info->Flag.half_tx_done);
Node->half_tx_done=true;
Node->half_tx_done_cnt++;
res = true;
}
ret = dma_interrupt_flag_get( Info->Flag.error);
if(SET==ret){
dma_flag_clear(Info->Flag.error);
Node->error_cnt++;
res = true;
}
ret = dma_interrupt_flag_get( Info->Flag.global);
if(SET==ret){
dma_flag_clear(Info->Flag.global);
Node->global_done=true;
Node->global_cnt++;
res = true;
}
}
}
return res;
}
void DMA1_Channel1_IRQHandler(void) {
#ifdef HAS_DMA
DMAxChannelyIRQHandler(1, 1);
#endif
}
bool dma_wait_tx_done(uint8_t num,DmaChannel_t channel) {
bool res = false;
DmaChannelHandle_t *Node= DmaChannelGetNodeItem( num, channel);
if(Node) {
bool loop = true;
uint32_t cur_ms = 0 ;
uint32_t diff_ms = 0 ;
uint32_t start_ms = time_get_ms32() ;
while(loop) {
if(Node->tx_done){
res = true;
loop = false ;
}
res= wait_in_loop_ms(10);
cur_ms = time_get_ms32();
diff_ms = cur_ms-start_ms;
if(DMA_TRANSFER_TIMEOUT_MS<diff_ms){
loop = false ;
res = false;
}
}
}
return res;
}
bool dma_memcpy(void* const destination,
const void* const source,
size_t size) {
bool res = false;
if(destination) {
if(source) {
if(size) {
res = true;
}
}
}
if(res) {
res = false;
/* enable dma1 clock */
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);
dma_reset(DMA1_CHANNEL1);
dma_init_type InitStruct = {0};
dma_default_para_init(&InitStruct);
InitStruct.buffer_size = size;
InitStruct.direction = DMA_DIR_MEMORY_TO_MEMORY;
InitStruct.memory_base_addr = (uint32_t)destination;
InitStruct.peripheral_base_addr = (uint32_t)source;
InitStruct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
InitStruct.memory_inc_enable = TRUE;
InitStruct.peripheral_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
InitStruct.peripheral_inc_enable = TRUE;
InitStruct.priority = DMA_PRIORITY_MEDIUM;
InitStruct.loop_mode_enable = FALSE;
dma_init(DMA1_CHANNEL1, &InitStruct);
/* enable transfer full data interrupt */
/* dma full data transfer interrupt */
dma_interrupt_enable(DMA1_CHANNEL1, DMA_FDT_INT, TRUE);
/* dma half data transfer interrupt */
dma_interrupt_enable(DMA1_CHANNEL1, DMA_HDT_INT, TRUE);
/* dma errorr interrupt */
dma_interrupt_enable(DMA1_CHANNEL1, DMA_DTERR_INT, TRUE);
/* dma1 channel1 interrupt nvic init */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(DMA1_Channel1_IRQn, 1, 0);
dma_channel_enable(DMA1_CHANNEL1, TRUE);
res = dma_wait_tx_done(1,1);
}
return res;
}
разве вы не видите насколько эта реализация НЕ-эффективна если вам надо скопировать скажем 100 байт? Простой цикл копирования отработает быстрее чем просто выполнится весь этот код инициализации и возврата через прерывание!
и это вот:
res= wait_in_loop_ms(10);
что такое? мне кажется это слип на 10мс, если это так это просто беда какая-то, а не реализация memcpy()!
На самом деле dma_memcpy это только для модульных тестов. Чтобы убедиться, что DMA работает.
вообще DMA нужно в абсолютном большинстве практических случаев чтобы избавиться от слишком частых прерываний, по крайней мере я только такой пользы на практике смог добиться:
вместо 2-х тысяч прерываний на каждый байт из SPI одно прерывание от ДМА при заполнении 2к буфера. Это такие штучки на аппаратном уровне, фактически.
блин, мышка сломалась, по два раза нажатие срабатывает, и почему то сайт два раза коммитит пост, мне кажется это недоработка со стороны сайта тоже.
В микроконтроллерах очень удобно использовать DMA для memcpy.
Вот только в МК по производительности узкое место CPU, а в компьютерах - память. Поэтому в компьютере пересылка память-память по DMA, скорее всего, лишь приведет к циклам ожидания CPU при обращениях в память, не дав никакой выгоды. А потери на общение с контроллером DMA и обработку прерывания останутся.
Можно ли использовать DMA вместо memcpy в Linux