Всем привет, сегодня мы с вами поговорим о DMA: именно о той технологии, которая помогает вашему компьютеру воспроизводить для вас музыку, выводить изображение на экран, записывать информацию на жесткий диск, и при этом оказывать на центральный процессор просто мизерную нагрузку.
PIO по своей сути прост: чтобы получить данные с устройства, драйвер операционной системы (или же firmware другого устройства), должен был читать эти данные из регистров устройства. Давайте разберемся на примере:
В итоге, если чтение одного байта отнимает около 1 мс процессорного времени, то чтение 1500 байт – соответственно 1500 мс. Но это всего лишь один Ethernet пакет, представте себе, сколько пакетов получает сетевая карта, когда вы читаете любимый хабрахабр. Конечно в реальности чтение в PIO режиме можно организовывать по 2, 4 байта, однако потери производительности при этом все равно будут катастрофическими.
Когда объемы данных, которыми оперирует процессор начали возрастать, стало понятно, что нужно минимизировать участие процессора в цепочке обмена данными, а то прийдется туго. И вот тогда активное применение нашла технология прямого доступа к памяти.
Кстати говоря, DMA используется не только для обмена данными между устройством и ОЗУ, но также между устройствами в системе, возможен DMA трансфер между двумя участками ОЗУ (хотя данный маневр не применим к x86 архитектуре). Также в своем процессоре Cell, IBM использует DMA как основной механизм обмена данными между синергетическими процессорными элементами (SPE) и центральным процессорным элементом (PPE). Также каждый SPE и PPE может обмениватся данными через DMA с оперативной памятью. Данный прием – на самом деле большое преимущество Cell, ибо избавляет от проблем когерентности кешей при мультипроцессорной обработке данных.
Я вскользь упомянул о регистрах устройства, но как же к ним имеет доступ центральный процессор? Как многие из вас знают, есть такая сущность в компьютерных технологиях, как IO порты (Input/Output ports). Они предназначены для обмена информацией между центральным процессором и периферийными устройствами, а доступ к ним возможен с помощью специальных ассемблерных инструкций — in/out. BIOS (или OpenFirmware на PPC based системах) на ранних этапах инициализации PCI устройств, а также некоторых других (Super IO контроллера, контроллера PS/2 устройств, ACPI timer и т.д.), закрепляет за определенным контроллером собственный диапазон IO портов, куда и отображаются регистры устройства.
Также регистры устройства могут отображатся в ОЗУ (Memory Mapped Registers), т.е. на физическое адресное пространство. Данный метод имеет ряд преимуществ, а именно:
Данные о том, какой диапазон IO портов или ОЗУ закреплен за устройством, хранятся в конфигурационном пространстве PCI, а именно в регистрах BAR0, BAR1, BAR2, BAR4, BAR5 [1].
Итак, существует два метода утилизации DMA: contiguous DMA и scatter/gather DMA.
Как видите все достаточно просто, и как только шина ISA обзавелась поддержкой DMA, данный метод нашел очень широкое применение. Например драйвера сетевых карт имели два таких DMA буфера: один на прием данных (rx), другой на отсылку (tx).
Решение напрашивается само собой: использовать вместо одного большого участка памяти несколько, но в разных регионах этой самой памяти. Возникает вопрос, но как же сообщить контроллеру устройства, как инициировать DMA трансфер и по какому адресу писать данные? И тут нашли решение, использовать дескрипторы, чтобы описывать каждый вот такой участок в оперативной памяти.
Типичный дескриптор DMA буфера содержит следующие поля:
Структура дескрипторов определяется конкретным производителем контроллера устройства, и может содержать какие-либо другие поля. Дескриптор также как и DMA буфер, размещается в оперативной памяти.
Алгоритм scatter/gather DMA следующий:
Порядок в каком контроллер устройства заполняет DMA буферы, определяется производителем. Контроллер может писать в первый свободный 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 буфера содержит следующие поля:
- Адрес участка ОЗУ (именно bus address), который предназначен для DMA трансфера.
- Размер описываемого участка ОЗУ.
- Опциональные флаги и другие специфические аргументы.
- Адрес следующего дескриптора в памяти.
Структура дескрипторов определяется конкретным производителем контроллера устройства, и может содержать какие-либо другие поля. Дескриптор также как и DMA буфер, размещается в оперативной памяти.
Алгоритм scatter/gather DMA следующий:
- Драйвер операционной системы выделяет и иницилизирует дескрипторы DMA буферов.
- Драйвер выделяет DMA буферы (участки ОЗУ для DMA трансфера) и записывает необходимую информацию о них в дескрипторы.
- Устройство по мере возникновения потребности, заполняет DMA буферы, и после того, как заполнен один или несколько буферов инициирует прерывание.
- Драйвер ОС просматривает все дескрипторы DMA буферов, определяет какие из них были заполнены контроллером устройства, пересылает данные из буфера далее по стеку устройств и помечает буфер как готовый к DMA трансферу.
Порядок в каком контроллер устройства заполняет DMA буферы, определяется производителем. Контроллер может писать в первый свободный DMA буфер, либо просто писать подряд (дескрипторы DMA буферов в данном случае образуют односвязный кольцевой список) во все буфера и т.д.