DMA для новичков или то, что вам нужно знать

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

    DMA, что это? О чем вы говорите?

    DMA, или Direct Memory Access – технология прямого доступа к памяти, минуя центральный процессор. В эпоху 486-ых и первых Pentium во всю царствовала шина ISA, а также метод обмена данными между устройствами – PIO (Programmed Input/Output).

    PIO по своей сути прост: чтобы получить данные с устройства, драйвер операционной системы (или же firmware другого устройства), должен был читать эти данные из регистров устройства. Давайте разберемся на примере:
    • На сетевую карту пришло 1500 байт данных.
    • Сетевая карта инициирует прерывание с целью сообщить процессору, что данные необходимо забрать с устройства, иначе произойдет так называемый buffer overrun.
    • Операционная система ловит прерывание от контроллера прерываний и отдает его на обработку драйверу.
    • Драйвер в цикле побайтно читает данные с регистров сетевой карты.

    В итоге, если чтение одного байта отнимает около 1 мс процессорного времени, то чтение 1500 байт – соответственно 1500 мс. Но это всего лишь один Ethernet пакет, представте себе, сколько пакетов получает сетевая карта, когда вы читаете любимый хабрахабр. Конечно в реальности чтение в PIO режиме можно организовывать по 2, 4 байта, однако потери производительности при этом все равно будут катастрофическими.

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

    Кстати говоря, DMA используется не только для обмена данными между устройством и ОЗУ, но также между устройствами в системе, возможен DMA трансфер между двумя участками ОЗУ (хотя данный маневр не применим к x86 архитектуре). Также в своем процессоре Cell, IBM использует DMA как основной механизм обмена данными между синергетическими процессорными элементами (SPE) и центральным процессорным элементом (PPE). Также каждый SPE и PPE может обмениватся данными через DMA с оперативной памятью. Данный прием – на самом деле большое преимущество Cell, ибо избавляет от проблем когерентности кешей при мультипроцессорной обработке данных.

    И снова теория

    Прежде чем мы перейдем к практике, я бы хотел осветить несколько важных аспектов программирования PCI, PCI-E устройств.

    Я вскользь упомянул о регистрах устройства, но как же к ним имеет доступ центральный процессор? Как многие из вас знают, есть такая сущность в компьютерных технологиях, как IO порты (Input/Output ports). Они предназначены для обмена информацией между центральным процессором и периферийными устройствами, а доступ к ним возможен с помощью специальных ассемблерных инструкций — in/out. BIOS (или OpenFirmware на PPC based системах) на ранних этапах инициализации PCI устройств, а также некоторых других (Super IO контроллера, контроллера PS/2 устройств, ACPI timer и т.д.), закрепляет за определенным контроллером собственный диапазон IO портов, куда и отображаются регистры устройства.

    Также регистры устройства могут отображатся в ОЗУ (Memory Mapped Registers), т.е. на физическое адресное пространство. Данный метод имеет ряд преимуществ, а именно:
    • Скорость доступа к физической памяти выше, нежели к IO портам.
    • IO порты могут отображать не более 65535 байт регистров, в то время как размер ОЗУ современных компьютеров в разы больше.
    • Читать регистры устройства из ОЗУ проще, нежели с помощью IO портов :)

    Данные о том, какой диапазон IO портов или ОЗУ закреплен за устройством, хранятся в конфигурационном пространстве PCI, а именно в регистрах BAR0, BAR1, BAR2, BAR4, BAR5 [1].

    Итак, существует два метода утилизации DMA: contiguous DMA и scatter/gather DMA.

    Contiguous DMA

    Данный метод очень прост и сейчас практически отжил свое, однако до сих пор используется для программирования звуковых контроллеров (к примеру Envy24HT). Его принцип следующий:
    • Выделяется один буфер достаточно большого размера в оперативной памяти.
    • Физический адрес (точнее сказать адрес на шине участка памяти, потому как physical address и bus address – равны в x86 архитектуре, но не равны в PPC) этого буфера записывается в регистр устройства.
    • Во время того, как приходят данные на устройство, контроллер устройства инициирует DMA трансфер.
    • После того, как буфер полностью заполнен, контроллер устройства инициирует прерывание, чтобы сообщить центральному процессору, что буфер следует передать операционной системе.
    • Драйвер операционной системы обрабатывает прерывание, и передает полученные данные из буфера, далее по стеку устройств операционной системы.

    Как видите все достаточно просто, и как только шина ISA обзавелась поддержкой DMA, данный метод нашел очень широкое применение. Например драйвера сетевых карт имели два таких DMA буфера: один на прием данных (rx), другой на отсылку (tx).

    Scatter/gather DMA

    С ростом скорости Ethernet адаптеров, contiguous DMA показал свою несостоятельность. В основном из-за того, что требовались области памяти достаточно большого размера, которые подчас невозможно было выделить, так как в современных системах фрагментация физической памяти достаточно высока. Во всем виноват механизм виртуальной памяти, без которого нынче никуда :)

    Решение напрашивается само собой: использовать вместо одного большого участка памяти несколько, но в разных регионах этой самой памяти. Возникает вопрос, но как же сообщить контроллеру устройства, как инициировать DMA трансфер и по какому адресу писать данные? И тут нашли решение, использовать дескрипторы, чтобы описывать каждый вот такой участок в оперативной памяти.

    Типичный дескриптор DMA буфера содержит следующие поля:
    1. Адрес участка ОЗУ (именно bus address), который предназначен для DMA трансфера.
    2. Размер описываемого участка ОЗУ.
    3. Опциональные флаги и другие специфические аргументы.
    4. Адрес следующего дескриптора в памяти.

    Структура дескрипторов определяется конкретным производителем контроллера устройства, и может содержать какие-либо другие поля. Дескриптор также как и DMA буфер, размещается в оперативной памяти.

    Алгоритм scatter/gather DMA следующий:
    • Драйвер операционной системы выделяет и иницилизирует дескрипторы DMA буферов.
    • Драйвер выделяет DMA буферы (участки ОЗУ для DMA трансфера) и записывает необходимую информацию о них в дескрипторы.
    • Устройство по мере возникновения потребности, заполняет DMA буферы, и после того, как заполнен один или несколько буферов инициирует прерывание.
    • Драйвер ОС просматривает все дескрипторы DMA буферов, определяет какие из них были заполнены контроллером устройства, пересылает данные из буфера далее по стеку устройств и помечает буфер как готовый к DMA трансферу.

    Порядок в каком контроллер устройства заполняет DMA буферы, определяется производителем. Контроллер может писать в первый свободный DMA буфер, либо просто писать подряд (дескрипторы DMA буферов в данном случае образуют односвязный кольцевой список) во все буфера и т.д.

    Стоп...

    На сегодня пожалуй все, иначе информации станет слишком много. В следующей статье я покажу вам, как с этой уличной магией работает IOKit. Жду отзывов и дополнений ;)

    Ссылки

    [1] PCI Local Bus Specification
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 29

      +1
      Довольно интересная статья, я так понимаю это начало цикла статей про программирование драйверов под дарвин?
      0
      Статья просто супер.
      Думаю, её можно рекомендовать всем, кто интересуется архитектурой компов.
      • UFO just landed and posted this here
          +2
          Блога по системному программированию нет, а данная статья задумывалась как вторая в серии статей про системное программирование в Mac OS X. В итоге материала получилось достаточно много, решил реализацию и примеру с IOKit выделить в отдельную статью.
            0
            Мне кажется что тут нет никакой привязки к конкретной OS, поэтому лучше завести тогда отдельный блог есть нет до сих пор его.
              0
              В этой статье — нет. Убедили, создал отдельный блог :)
                0
                Отлично!
        • UFO just landed and posted this here
            0
            У вас очень странная логика, как буд-то эти самые программы не люди пишут, а создают роботы, аля «Гамбургеры продаются в магазине».
              0
              Не реагируйте на этого тролля, минусуйте и все тут, только нервы тратить
              0
              А откуда берутся программы нужные?
              • UFO just landed and posted this here
                  0
                  А кто при бабках?
              +2
              да что вы спорите с toshirskij? посмотрите как он себя ведет, на его аватар… я не могу описать своего презрения к этому человеку!
                +1
                Do not feed trolls
                +1
                Интересно, спасибо. Только я не нашел расшифровку PPU. И опечатка в части Contiguous DMA принцим->принцип.
                  0
                  Спасибо, все поправил.
                  0
                  Очень интересно. Только возник такой вопрос касательно алгоритма работы Scatter/gather DMA:

                  Сначала драйвер(кстати, чей дравер? ОС?) выделяет память под DMA буферы. Далее устройства имеют доступ к буферам через указатели в дескрипторах. Но кто следит за overhead'ами? Если буферов оказывается недостаточно, кем, как и когда данная ситуация распознаётся и как/на каком этапе решается?

                  И ещё вопрос касательно этого: «Драйвер ОС просматривает все дескрипторы DMA буферов, определяет какие из них были заполнены контроллером устройства, пересылает данные из буфера далее по стеку устройств и помечает буфер как готовый к DMA трансферу.»

                  Каков обычно размер этого односвязаного списка DMA-буферов? Это ведь нерационально каждый раз просматривать его. Причём я так понимаю под «пересылает данные из буфера» имеется ввиду копирование этих данных? Т.к. иначе блок не может быть помечен как «готовый к DMA трансферу» пока на эти данные кто-то ссылается.

                  Заранее спасибо, надеюсь понятно изложил.
                    0
                    Понятно :) По порядку:

                    > Сначала драйвер(кстати, чей дравер? ОС?) выделяет память под DMA буферы.
                    Драйвер устройства, я его тут называл драйвером ОС, чтобы никто не запутался.

                    > Но кто следит за overhead'ами? Если буферов оказывается недостаточно, кем, как и когда данная ситуация распознаётся и как/на каком этапе решается?
                    Зависит от ситуации. Если у нас к примеру есть драйвер сетевой карты (я именно с ними имел достаточно много дела), то мы можем говорить о двух случаях:
                    1. overrun при отсылке пакета.
                    2. overrun при приеме.

                    Если имеет место первый случай, то очевидно, что overrun обнаруживает драйвер устройства в одной из своих процедур (например в Mac OS X, всмысле IOKit, это метод IOEthernetController:: outputPacket). overrun происходит из-за того, что устройство по какой-либо причине не успевает отправлять пакеты. Решение в данном случае, вернуть в процедуре драйвера код ошибки по типу output queue stalled. ОС просто перешлет этот пакет посже. Ну и если такая ситуация будет повторятся подряд на протяжении какого-либо времени, например контроллер повис, то значит нужно в драйвере это отслеживать и вовремя ресетнуть контроллер.

                    Если второй, то overrun обнаруживает сетевой контроллер. Он посылает прерывание и в регистре статуса прерываний выставляет какой-нибудь флажок, типа input queue overrun. Это сигнал драйверу, что нужно что-то делать. Как вариант, дропнуть старые пакеты в очереди и выставить в дескрипторе флажок, что буфер свободен, или же провести ресет контроллера.

                    > Каков обычно размер этого односвязаного списка DMA-буферов?
                    Зависит от конкретной железки. 64, 256 может быть. Может быть и больше.

                    > Это ведь нерационально каждый раз просматривать его.
                    Просмотреть 256 дескрипторов и проверить наличие установленного флага, что буфер был получен и его необходимо переслать на уровень выше — не такая уж и тяжелая задача. Есть разные методики. Например сетевые контроллеры от Realtek раньше просто писали подряд в каждый буфер в этом кольцевом списке дескрипторов, и после заполнения одного буфера давали прерывание. Посему драйвер мог хранить индекс текущего буфера и легко инкрементировать его после каждого прерывания.

                    > Причём я так понимаю под «пересылает данные из буфера» имеется ввиду копирование этих данных?
                    Верно, в случае сетевых драйверов, создается сетевой пакет (в Mac OS X — это mbuf, в линуксе — skb) и данные из буфера копируются в этот пакет.
                      0
                      На линуксе количество дескрипторов можно посмотреть так:
                      root@eva00:~# ethtool -g eth0
                      Ring parameters for eth0:
                      Pre-set maximums:
                      RX: 4096
                      RX Mini: 0
                      RX Jumbo: 0
                      TX: 4096
                      Current hardware settings:
                      RX: 256
                      RX Mini: 0
                      RX Jumbo: 0
                      TX: 256

                        0
                        Прекрасная статья! Хотя не очень интересуюсь тем, что происходит на таком низком уровне, однако, даже в обзеобразовательных целях было очень интересно почитать! Спасибо!

                        И, поправьте, пожалуйста: «следует передать операционной системы».
                          0
                          Спасибо, поправил. Что-то я не внимателен в последнее время :)
                          +1
                          Кстати говоря, DMA используется не только для обмена данными между устройством и ОЗУ, но также между устройствами в системе, возможен DMA трансфер между двумя участками ОЗУ (хотя данный маневр не применим к x86 архитектуре)

                          Применим. DMA просто генерирует RD/WR запросы на шине. И, например, если в качестве места назначения указана видеопамять, то успешно можно загадить экран какими-нибудь кракозяблами. Хотя, конечно, PCI-E — это не общая шина. Возможно, там такой трюк не проходит. Но на PCI системах случалось ошибаться.

                          Также регистры устройства могут отображатся в ОЗУ (Memory Mapped Registers), т.е. физическую память системы. Данный метод имеет ряд преимуществ, а именно:

                          * Скорость доступа к физической памяти выше, нежели к IO портам.
                          * IO порты могут отображать не более 65535 байт регистров, в то время как размер ОЗУ современных компьютеров в разы больше.
                          * Читать регистры устройства из ОЗУ проще, нежели с помощью IO портов :)


                          Лучше написать: на физическое адресное пространство. Потому что память никуда не девается, просто host-контроллер перенаправляет запросы на чтение/запись по определённым адресам на шину PCI. На скорость доступа, кстати, это никак не влияет.
                            0
                            > И, например, если в качестве места назначения указана видеопамять, то успешно можно загадить экран какими-нибудь кракозяблами.
                            Да, можно ошибится с адресами и запортить содержимое памяти по случайному адресу, но если честно я не знаю, как можно было бы копировать участок физической памяти с одного адреса, на другой. Я так понимаю для этого нужен какой-нибудь центральный DMA контроллер или аппаратный IO MMU, но x86 этого нет, вернее в PCI нету централизированного контроллера, вместо этого bus mastering и придумали ведь ;)

                            > Лучше написать: на физическое адресное пространство. Потому что память никуда не девается, просто host-контроллер перенаправляет запросы на чтение/запись по определённым адресам на шину PCI.
                            Согласен, изменил формулировку. Правда память от этого свободной не становится, и int 15h сообщает системе, что память занята. Кстати надо будет в этом месте дополнить статью о memory remap.

                            > На скорость доступа, кстати, это никак не влияет.
                            Это только так кажется. На самом деле на IO порты обязательно вешается в SMI хендлере IO Trap, для самых разных нужд, как-то эмулирование для приложений PS/2 мыши и клавиатуры, когда вместо них — USB. Перенаправлением запросов в этом случае занимается SMI хендлер. Так что тут тоже есть определенный оверхед, хоть и небольшой :)
                              0
                              На самом деле на IO порты обязательно вешается в SMI хендлере IO Trap, для самых разных нужд, как-то эмулирование для приложений PS/2 мыши и клавиатуры, когда вместо них — USB.

                              Ну. Я про железо говорил. А на что там SMI вешается — никому не известно. Его можно и на доступ в память повешать, если железо это поддерживает. Но для того, чтобы железо с in и out работало никакого SMI не нужно.
                            –3
                            Ну и это. +1. Аффтар, пиши ещё :)
                              0
                              … возможен DMA трансфер между двумя участками ОЗУ (хотя данный маневр не применим к x86 архитектуре).

                              Ну давно уже применим, только не для всех SoC, конечно, а только в тех, где есть DMA контроллер с поддержкой такого режима.

                              Only users with full accounts can post comments. Log in, please.