Как избежать высокого использования процессора при чтении / записи символьного устройства? - PullRequest
1 голос
/ 16 октября 2019

Мне нужно написать драйвер ядра Linux для устройства PCIe с SRAM.

Для первой попытки я написал драйвер для доступа к SRAM из PCIe с символьным устройством.

Все работает как положено, но есть одна проблема. SRAM работает медленно 1 МБ для чтения / записи занимает около 2 секунд, это аппаратное ограничение. Процессор на 100% занят во время чтения / записи. Ведьма это проблема. Мне не нужна скорость, чтение / запись может быть медленным, но почему это занимает столько ЦП?

Буфер инициализируется с помощью pci_iomap:

  g_mmio_buffer[0] = pci_iomap(pdev, SRAM_BAR_H, g_mmio_length);

функций чтения / записивыглядит так:

static ssize_t dev_read(struct file *fp, char *buf, size_t len, loff_t *off) {
  unsigned long rval;
  size_t copied;

  rval = copy_to_user(buf, g_mmio_buffer[SRAM_BAR] + *off, len);

  if (rval < 0) return -EFAULT;

  copied = len - rval;
  *off += copied;

  return copied;
}

static ssize_t dev_write(struct file *fp, const char *buf, size_t len, loff_t *off) {
  unsigned long rval;
  size_t copied;

  rval = copy_from_user(g_mmio_buffer[SRAM_BAR] + *off, buf, len);

  if (rval < 0) return -EFAULT;

  copied = len - rval;
  *off += copied;

  return copied;
}

Вопрос в том, что можно сделать при высокой загрузке ЦП?

Стоит ли переписать драйвер, чтобы использовать блочное устройство вместо символа?

Разрешить ЦПУ работать с другим процессом при чтении / сохранении данных?

1 Ответ

0 голосов
/ 17 октября 2019

Как отметил @ 0andriy, вы не должны иметь прямой доступ к iomem. Существуют такие функции, как memcpy_toio() и memcpy_fromio(), которые могут копировать между iomem и обычной памятью, но они работают только с виртуальными адресами ядра.

Для копирования адресов из пользовательского пространства в iomem без использования промежуточных данныхбуфер, страницы памяти пользовательского пространства должны быть «закреплены» в физической памяти. Это можно сделать с помощью get_user_pages_fast(). Однако закрепленные страницы могут находиться в «старшей памяти» (highmem), которая находится вне постоянно отображаемой памяти в ядре. Такие страницы должны быть временно отображены в виртуальное адресное пространство ядра на короткое время с помощью kmap_atomic(). (Существуют правила, регулирующие использование kmap_atomic(), и есть другие функции для более долгосрочного отображения highmem. Подробности смотрите в документации highmem .)

Как только страница пользовательского пространства имеет beemсопоставленные с виртуальным адресным пространством ядра, memcpy_toio() и memcpy_fromio() могут использоваться для копирования между этой страницей и iomem.

Страница, временно отображаемая с помощью kmap_atomic(), должна быть отображена с помощью kunmap_atomic().

Страницы пользовательской памяти, закрепленные get_user_pages_fast(), необходимо откреплять по отдельности, вызывая put_page(), но если в память страницы была записана (например, memcpy_fromio(), она должна сначала помечаться как "грязная" *)1023 * перед вызовом put_page().

Собрав все это вместе, можно использовать следующие функции для копирования между пользовательской памятью и iomem:

#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/highmem.h>

/**
 * my_copy_to_user_from_iomem - copy to user memory from MMIO
 * @to:     destination in user memory
 * @from:   source in remapped MMIO
 * @n:      number of bytes to copy
 * Context: process
 *
 * Returns number of uncopied bytes.
 */
long my_copy_to_user_from_iomem(void __user *to, const void __iomem *from,
                unsigned long n)
{
    might_fault();
    if (!access_ok(to, n))
        return n;
    while (n) {
        enum { PAGE_LIST_LEN = 32 };
        struct page *page_list[PAGE_LIST_LEN];
        unsigned long start;
        unsigned int p_off;
        unsigned int part_len;
        int nr_pages;
        int i;

        /* Determine pages to do this iteration. */
        p_off = offset_in_page(to);
        start = (unsigned long)to - p_off;
        nr_pages = min_t(int, PAGE_ALIGN(p_off + n) >> PAGE_SHIFT,
                 PAGE_LIST_LEN);
        /* Lock down (for write) user pages. */
        nr_pages = get_user_pages_fast(start, nr_pages, 1, page_list);
        if (nr_pages <= 0)
            break;

        /* Limit number of bytes to end of locked-down pages. */
        part_len =
            min(n, ((unsigned long)nr_pages << PAGE_SHIFT) - p_off);

        /* Copy from iomem to locked-down user memory pages. */
        for (i = 0; i < nr_pages; i++) {
            struct page *page = page_list[i];
            unsigned char *p_va;
            unsigned int plen;

            plen = min((unsigned int)PAGE_SIZE - p_off, part_len);
            p_va = kmap_atomic(page);
            memcpy_fromio(p_va + p_off, from, plen);
            kunmap_atomic(p_va);
            set_page_dirty_lock(page);
            put_page(page);
            to = (char __user *)to + plen;
            from = (const char __iomem *)from + plen;
            n -= plen;
            part_len -= plen;
            p_off = 0;
        }
    }
    return n;
}

/**
 * my_copy_from_user_to_iomem - copy from user memory to MMIO
 * @to:     destination in remapped MMIO
 * @from:   source in user memory
 * @n:      number of bytes to copy
 * Context: process
 *
 * Returns number of uncopied bytes.
 */
long my_copy_from_user_to_iomem(void __iomem *to, const void __user *from,
                unsigned long n)
{
    might_fault();
    if (!access_ok(from, n))
        return n;
    while (n) {
        enum { PAGE_LIST_LEN = 32 };
        struct page *page_list[PAGE_LIST_LEN];
        unsigned long start;
        unsigned int p_off;
        unsigned int part_len;
        int nr_pages;
        int i;

        /* Determine pages to do this iteration. */
        p_off = offset_in_page(from);
        start = (unsigned long)from - p_off;
        nr_pages = min_t(int, PAGE_ALIGN(p_off + n) >> PAGE_SHIFT,
                 PAGE_LIST_LEN);
        /* Lock down (for read) user pages. */
        nr_pages = get_user_pages_fast(start, nr_pages, 0, page_list);
        if (nr_pages <= 0)
            break;

        /* Limit number of bytes to end of locked-down pages. */
        part_len =
            min(n, ((unsigned long)nr_pages << PAGE_SHIFT) - p_off);

        /* Copy from locked-down user memory pages to iomem. */
        for (i = 0; i < nr_pages; i++) {
            struct page *page = page_list[i];
            unsigned char *p_va;
            unsigned int plen;

            plen = min((unsigned int)PAGE_SIZE - p_off, part_len);
            p_va = kmap_atomic(page);
            memcpy_toio(to, p_va + p_off, plen);
            kunmap_atomic(p_va);
            put_page(page);
            to = (char __iomem *)to + plen;
            from = (const char __user *)from + plen;
            n -= plen;
            part_len -= plen;
            p_off = 0;
        }
    }
    return n;
}

Во-вторых, вы могли бы иметь возможность ускорить доступ к памяти, сопоставив iomem как «запись в комбинации», заменив pci_iomap() на pci_iomap_wc().

В-третьих, единственный реальный способ избежать ожидания ЦП при медленном доступепамять должнане используйте процессор и используйте вместо этого передачи DMA. Детали этого во многом зависят от возможностей DMA мастеринга шины вашего устройства PCIe (если они вообще есть). Страницы памяти пользователя все еще должны быть закреплены (например, get_user_pages_fast()) во время передачи DMA, но не должны быть временно отображены kmap_atomic().

...