Linux 如何避免读/写字符设备时cpu的高使用率?

Linux 如何避免读/写字符设备时cpu的高使用率?,linux,linux-kernel,driver,linux-device-driver,Linux,Linux Kernel,Driver,Linux Device Driver,我需要为带有SRAM的PCIe设备编写linux内核驱动程序 在第一次尝试中,我编写了一个使用字符设备从PCIe访问SRAM的驱动程序 一切正常,但有一个问题。SRAM速度慢1MB读/写大约需要2秒,这是一个硬件限制。读/写时CPU 100%忙。女巫是个问题。我不需要速度,读/写速度可能很慢,但为什么需要这么多CPU 使用pci\u iomap初始化缓冲区: g_mmio_buffer[0] = pci_iomap(pdev, SRAM_BAR_H, g_mmio_length); 读/写

我需要为带有SRAM的PCIe设备编写linux内核驱动程序

在第一次尝试中,我编写了一个使用字符设备从PCIe访问SRAM的驱动程序

一切正常,但有一个问题。SRAM速度慢1MB读/写大约需要2秒,这是一个硬件限制。读/写时CPU 100%忙。女巫是个问题。我不需要速度,读/写速度可能很慢,但为什么需要这么多CPU

使用
pci\u 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;
}
static ssize\u t dev\u read(结构文件*fp,字符*buf,大小长度,loff\u t*off){
无符号长rval;
复制的大小;
rval=复制到用户(buf,g\U mmio\U缓冲区[SRAM\U条]+*关闭,len);
如果(rval<0)返回-EFAULT;
复制=len-rval;
*off+=已复制;
返回复印件;
}
静态ssize_t dev_write(结构文件*fp,常量字符*buf,大小长度,loff_t*off){
无符号长rval;
复制的大小;
rval=从用户复制(g\U mmio\U缓冲区[SRAM\U BAR]+*关闭、buf、len);
如果(rval<0)返回-EFAULT;
复制=len-rval;
*off+=已复制;
返回复印件;
}
问题是高CPU使用率下我能做什么

我应该重写驱动程序以使用块设备而不是字符吗


允许CPU在读取/保存数据时在另一个进程上工作?

正如@0andriy所指出的,您不应该直接访问iomem。有一些函数,如
memcpy\u toio()
memcpy\u fromio()
,可以在IOEM和普通内存之间进行复制,但它们只在内核虚拟地址上工作

为了在不使用中间数据缓冲区的情况下从用户空间地址复制到iomem,需要将用户空间内存页“固定”到物理内存中。这可以使用
get\u user\u pages\u fast()
完成。但是,固定页面可能位于内核中永久映射内存之外的“高内存”(highmem)中。这样的页面需要使用
kmap\u atomic()
暂时映射到内核虚拟地址空间中一段时间。(有一些规则规定了使用
kmap_atomic()
,还有其他函数用于highmem的长期映射。有关详细信息,请查看文档。)

一旦用户空间页面映射到内核虚拟地址空间,
memcpy\u toio()
memcpy\u fromio()
就可以用来在该页面和IOEM之间进行复制

kmap\u atomic()
临时映射的页面需要由
kunmap\u atomic()
取消映射

需要通过调用
put\u page()
来分别解除由
get\u User\u pages\u fast()
固定的用户内存页的固定,但是如果页面内存已写入(例如,通过
memcpy\u fromio()
写入),则在调用
put\u page()
之前,必须首先通过
设置页面脏锁()
将其标记为“脏”

综上所述,以下功能可用于在用户内存和IOEM之间进行复制:

#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/io.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;
}
#包括
#包括
#包括
#包括
#包括
/**
*my_copy_to_user_from_iomem-从MMIO复制到用户内存
*@to:用户内存中的目标
*@from:source在重新映射的MMIO中
*@n:要复制的字节数
*背景:过程
*
*返回未复制的字节数。
*/
long my_copy_to_user_from_iomem(void u user*to,const void u iomem*from,
无符号长(n)
{
可能是你的错;
如果(!访问正常(到,n))
返回n;
while(n){
枚举{PAGE_LIST_LEN=32};
结构页面*页面列表[页面列表];
无符号长启动;
无符号整数p_off;
无符号整数部分;
int nr_页面;
int i;
/*确定要执行此迭代的页面*/
p_off=第页中的偏移量(至);
开始=(无符号长)至-p_关闭;
nr_pages=min_t(int,PAGE_ALIGN(p_off+n)>>PAGE_SHIFT,
第页(列表);
/*锁定(用于写入)用户页*/
nr_页面=快速获取用户页面(开始,nr_页面,1,页面列表);
如果(第页第页),
第页(列表);
/*锁定(用于读取)用户页面*/
nr_页面=快速获取用户页面(开始,nr_页面,0,页面列表);

如果(nr_pages的SRAM慢)--SRAM不慢,但访问它的方法慢。从/到用户空间的复制可能会将PCIe传输大小一次减少到一个字节。在等待通过PCIe的每个字节传输时,CPU可能会浪费周期。第一个错误:您不应该直接使用iomem地址(你必须得到一个编译器警告,以免弄乱地址空间)。第二个错误是,IO未缓存,每次读写都需要访问内存并受到相应的惩罚(你必须自己缓存数据)。@Sawdush我把它命名为错误的NVRAM。很明显,CPU在等待时会浪费周期,但我能做些什么?@0andriy 1)我没有收到任何编译警告。是否有任何示例如何正确使用它有没有兑现数据的例子?将数据复制到缓冲区然后再复制到IO内存对我来说毫无意义。@0将数据读/写到NVRAM应该是可靠的。我需要确保数据确实写入了NVRAM芯片。等待没有问题,浪费cpu周期是有问题的。感谢您给出了这么好的答案!在
中,访问\u ok
函数优先p缺少参数,需要验证读取或验证写入。需要试用。@fazibear是,在内核版本5.0之前
access\u ok
需要初始
VERIFY\u READ
VERIFY\u WRITE
参数。