remap_pfn_range()详解

本文详细介绍了remap_pfn_range函数的使用方法及参数意义,解释了如何利用此函数映射内存中的保留页和设备I/O内存。此外,还深入解析了vm_area_struct结构体,帮助读者理解内存映射的底层实现。
部署运行你感兴趣的模型镜像
remap_pfn_range()函数的原型:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
该函数的功能是创建页表。其中参数vma是内核根据用户的请求自己填写的,而参数addr表示内存映射开始处的虚拟地址,因此,该函数为addr~addr+size之间的虚拟地址构造页表。
另外,pfn(Page Fram Number)是虚拟地址应该映射到的物理地址的页面号,实际上就是物理地址右移PAGE_SHIFT位。如果PAGE_SHIFT为4kb,则 PAGE_SHIFT为12,因为PAGE_SHIFT等于1<<PAGE_SHIFT。最后一个参数prot是新页所要求的保护属性。
在驱动程序中,一般能使用remap_pfn_range()映射内存中的保留页(如X86系统中的640KB~1MB区域)和设备I/O内存。因此,如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。

下面是struct vm_area_struct结构体的定义:

QUOTE:
/*
* This struct defines a memory VMM memory area. There is color: black; background-color: #a0ffff;">vm_area_struct {
struct mm_struct * vm_mm; /* VM area parameters */
unsigned long vm_start;
unsigned long vm_end;
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot;
unsigned long vm_flags;
/* AVL tree of VM areas per task, sorted by address */
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right;
/* For areas with an address space and backing store,
* font-size: 10px;">vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
struct vm_operations_struct * vm_ops;
unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
struct file * vm_file;
unsigned long vm_raend;
void * vm_private_data; /* was vm_pte (shared mem) */
};

vm_area_struct结构所描述的虚存空间以vm_start、vm_end成员表示,它们分别保存了该虚存空间的首地址和末地址后第一个字节的地址,以字节为单位,所以虚存空间范围可以用[vm_start, vm_end)表示。

  通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了vm_avl_hight(树高)、vm_avl_left(左子节点)、vm_avl_right(右子节点)三个成员来实现AVL树,以提高vm_area_struct的搜索速度。

  假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。

  一个程序可以选择MAP_SHARED或MAP_PRIVATE共享模式将一个文件的某部分数据映射到自己的虚存空间里面。这两种映射方式的区别在于:MAP_SHARED映射后在内存中对该虚存空间的数据进行修改会影响到其他以同样方式映射该部分数据的进程,并且该修改还会被写回文件里面去,也就是这些进程实际上是在共用这些数据。而MAP_PRIVATE映射后对该虚存空间的数据进行修改不会影响到其他进程,也不会被写入文件中。

  来自不同进程,所有映射同一个文件的vm_area_struct结构都会根据其共享模式分别组织成两个链表。链表的链头分别是:vm_file->f_dentry->d_inode->i_mapping->i_mmap_shared, vm_file->f_dentry->d_inode->i_mapping->i_mmap。而vm_area_struct结构中的vm_next_share指向链表中的下一个节点;vm_pprev_share是一个指针的指针,它的值是链表中上一个节点(头节点)结构的vm_next_share(i_mmap_shared或i_mmap)的地址。

  进程建立vm_area_struct结构后,只是说明进程可以访问这个虚存空间,但有可能还没有分配相应的物理页面并建立好页面映射。在这种情况下,若是进程执行中有指令需要访问该虚存空间中的内存,便会产生一次缺页异常。这时候,就需要通过vm_area_struct结构里面的vm_ops->nopage所指向的函数来将产生缺页异常的地址对应的文件数据读取出来。

  vm_flags主要保存了进程对该虚存空间的访问权限,然后还有一些其他的属性。vm_page_prot是新映射的物理页面的页表项pgprot的默认值。

例子:

QUOTE:

int memdev_mmap(struct file *filp, struct vm_area_struct *vma)

{

    vma->vm_flags |= VM_IO;

    vma->vm_flags |= VM_RESERVED;

    if(remap_pfn_range(vma, vma->vm->start, virt_to_phys(dev->data)>>PAGE_SHIFT, size, vma->vm_page_prot))

        return -EAGAIN;

return 0;  

}


转自:http://blog.163.com/zheng_he_xiang/blog/static/186505326201171510521466/

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

<think>嗯,用户现在问到了在开发传统PCIe驱动时使用`pci_ioremap_bar`的问题。我之前已经给他介绍过`pci_rebar_get_possible_sizes`和`pci_resize_resource`这两个函数,现在他想了解`pci_ioremap_bar`的相关内容。首先,我需要确认用户的使用场景。他可能在处理PCI设备的内存映射,尤其是在BAR资源调整后需要重新映射地址空间。 用户可能是在调整BAR大小之后,遇到需要重新映射的问题。这时候,正确使用`pci_ioremap_bar`就变得重要了。我需要解释这个函数的作用,它和`ioremap`的区别,以及使用时的注意事项。例如,这个函数会自动处理BAR的资源和标志,简化了开发者的工作。 接下来,我需要考虑用户可能遇到的常见问题。比如,在调整BAR大小之后,是否需要先取消之前的映射?是的,这时候可能需要先调用`pci_iounmap_bar`,然后再用新的参数重新映射。此外,用户可能对如何选择正确的BAR索引、处理64位地址空间、或者错误处理不太清楚,这些都需要在回答中涵盖。 另外,用户可能对内核版本兼容性有疑问,比如`pci_ioremap_bar`是否适用于他们的内核版本。需要指出这个函数在较新的内核中可用,并建议他们检查内核文档或源代码确认可用性。 还要考虑到用户的实际操作步骤,比如代码示例,这样他们可以直接参考。给出一个典型的流程:检查BAR是否启用,调整大小后重新映射,处理错误情况,并在模块卸载时释放资源。 最后,用户可能需要调试技巧,比如如何验证映射是否正确,使用`devm`函数自动管理资源,或者如何处理多设备共享同一BAR的情况。这些都需要在回答中提及,确保用户能够全面理解并正确应用这个函数。</think>以下是关于在Linux PCI驱动开发中使用`pci_ioremap_bar()`函数的详细说明: \#\#\# 核心功能解析 ```c void __iomem *pci_ioremap_bar(struct pci_dev *pdev, int bar); ``` 该函数用于将PCI设备的BAR空间映射到内核虚拟地址空间,相比传统方法具有以下优势: 1. **自动资源管理**:自动检查BAR是否已启用 2. **标志处理**:自动处理`IORESOURCE_IO`和`IORESOURCE_MEM`类型 3. **地址转换**:支持32/64位地址空间映射 \#\#\# 典型使用场景 **场景1:标准BAR映射** ```c struct pci_dev *pdev; void __iomem *ioaddr; // 映射BAR0 ioaddr = pci_ioremap_bar(pdev, 0); if (!ioaddr) { dev_err(&pdev->dev, "Failed to map BAR0\n"); return -ENOMEM; } // 访问寄存器示例 iowrite32(0x12345678, ioaddr + REG_OFFSET); ``` **场景2:与BAR调整配合使用** ```c // 调整BAR尺寸后需要重新映射 pci_resize_resource(pdev, 0, 1024); // 调整到1GB pci_iounmap_bar(pdev, 0); // 解除旧映射 ioaddr = pci_ioremap_bar(pdev, 0); // 建立新映射 ``` \#\#\# 参数详解 | 参数 | 说明 | 典型值 | |-------|--------------------------------|--------| | pdev | PCI设备结构体指针 | - | | bar | BAR索引号(0-5) | 0-5 | \#\#\# 内存管理建议 1. **资源释放**: ```c // 模块卸载时必须调用 pci_iounmap_bar(pdev, bar); ``` 2. **设备管理版本**: ```c // 推荐使用devm自动管理生命周期 void __iomem *ioaddr = devm_pci_ioremap_bar(&pdev->dev, bar); ``` \#\#\# 常见问题处理 **问题1:映射失败** ```c if (!ioaddr) { // 检查步骤: // 1. 确认已调用pci_enable_device() // 2. 验证BAR是否已分配资源 // 3. 检查/proc/iomem确认地址范围 } ``` **问题2:64位地址处理** ```c // 对于64位BAR(BARn和BARn+1组合) if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM_64) { dev_info(&pdev->dev, "Using 64-bit BAR%d\n", bar); } ``` **问题3:DMA操作配合** ```c // 建立DMA映射 dma_addr = dma_map_single(&pdev->dev, buffer, size, DMA_TO_DEVICE); // 直接使用映射后的地址进行DMA iowrite32(dma_addr, ioaddr + DMA_ADDR_REG); ``` \#\#\# 性能优化建议 1. **预取优化**: ```c // 针对prefetchable BAR使用wc缓存模式 ioaddr = ioremap_wc(pci_resource_start(pdev, bar), pci_resource_len(pdev, bar)); ``` 2. **大页映射**: ```c // 对于大尺寸BAR(>2MB) remap_pfn_range(vma, vma->vm_start, pci_resource_start(pdev, bar) >> PAGE_SHIFT, pci_resource_len(pdev, bar), vma->vm_page_prot); ``` \#\#\# 调试技巧 1. **查看映射状态**: ```bash cat /proc/iomem | grep -i pci # 输出示例: # 7f800000-7fffffff : PCI Bus 0000:00 # 7f800000-7fbfffff : 0000:00:01.0 ``` 2. **地址验证**: ```c dev_info(&pdev->dev, "BAR%d物理地址: %llx\n", bar, (u64)pci_resource_start(pdev, bar)); dev_info(&pdev->dev, "虚拟地址: %px\n", ioaddr); ``` \#\#\# 关键注意事项 1. **并发访问**: ```c // 需要同步时使用原子操作 u32 val = readl(ioaddr + REG_OFFSET); val |= BIT(0); writel(val, ioaddr + REG_OFFSET); ``` 2. **字节序处理**: ```c // 小端设备使用标准函数,大端设备需要转换 #if __BIG_ENDIAN val = ioread32be(ioaddr + REG_OFFSET); #else val = ioread32(ioaddr + REG_OFFSET); #endif ``` 3. **安全检查**: ```c // 验证访问范围 if (offset > pci_resource_len(pdev, bar) - sizeof(u32)) { dev_err(&pdev->dev, "访问越界: offset=%d\n", offset); return -EINVAL; } ``` 这些方法需要结合具体硬件手册使用,建议通过`PCITEST`工具验证基础功能后再进行驱动开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值