one_page_table_init()

本文详细介绍了Linux内核中页表初始化的过程,特别是one_page_table_init函数如何为物理页面设置页表项,并返回线性地址。文章深入探讨了该函数的工作原理,包括分配内存、设置pmd表项以及错误检查等步骤。
pagetable_init() --> kernel_physical_mapping_init() --> one_page_table_init()

one_page_table_init(pmd_t *pmd)初始化pmd索引的页表项(将该项索引的物理页面的首地址填写到pmd中),并返回pmd索引的物理页面的首地址(线性地址)
static  pte_t  *  __init one_page_table_init(pmd_t  * pmd)
{
    
if (pmd_none(*pmd)) {
        pte_t 
*page_table = (pte_t *alloc_bootmem_low_pages(PAGE_SIZE);
        set_pmd(pmd, __pmd(__pa(page_table) 
| _PAGE_TABLE));
        
if (page_table != pte_offset_kernel(pmd, 0))
            BUG(); 

        
return page_table;
    }

   
    
return pte_offset_kernel(pmd, 0);
}


#define  __pmd(x)                ((pmd_t) { __pud(x) } )
#define  _PAGE_TABLE (_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | _PAGE_ACCESSED | _PAGE_DIRTY)















 
你提供的函数 `vrdma_init_sg` 是用于在 Linux 内核中为 DMA 操作初始化一个 scatterlist(分散-聚集列表)的辅助函数。它的主要作用是根据输入缓冲区 `buf` 和大小 `nbytes`,构建一个或多个 `scatterlist` 条目,以便后续进行 DMA 传输。 当前代码已经处理了两种情况: 1. 如果 `buf` 是通过 `vmalloc()` 分配的虚拟内存(非线性映射),则需要跨多个物理页,因此要拆分成多个 `scatterlist` 条目。 2. 如果 `buf` 是线性内核地址(如 `kmalloc` 分配的),则只需一个 `scatterlist` 条目。 但该函数存在几个潜在问题和可改进点: --- ### ✅ 改进点说明 1. **`vmalloc_to_page(buf)` 的调用不安全**: `vmalloc_to_page()` 只能用于指向页对齐地址的指针。如果 `buf` 不是对齐到页边界的,直接传入会出错。必须先用 `offset_in_page()` 调整偏移量,并确保每次取正确的页面。 2. **循环中 `len` 更新逻辑有误**: 第一次之后的 `len = min(nbytes, PAGE_SIZE);` 是对的,但初始 `len` 设置后未正确更新后续长度。 3. **`sg_set_page()` 中的参数顺序需注意**: 应该传入正确的 `struct page *`、长度、以及页内偏移。 4. **缺少错误清理机制**:虽然简单函数可以接受,但建议增强健壮性。 5. **使用 `GFP_ATOMIC` 是合理的(比如中断上下文),但如果上下文允许,可用 `GFP_KERNEL` 提高成功率。** 6. **返回前应验证 sg list 是否真的被正确初始化。** --- ### ✅ 修改后的代码(修复并优化) ```c static struct scatterlist* vrdma_init_sg(void* buf, unsigned long nbytes) { struct scatterlist* need_sg; int num_page = 0; unsigned long offset; void* ptr; if (is_vmalloc_addr(buf)) { int i; unsigned long remaining = nbytes; unsigned long current_offset; ptr = buf; offset = offset_in_page(ptr); num_page = 1; if (offset + nbytes > PAGE_SIZE) { num_page += (offset + nbytes - PAGE_SIZE + PAGE_SIZE - 1) / PAGE_SIZE; } need_sg = kmalloc_array(num_page, sizeof(*need_sg), GFP_ATOMIC); if (!need_sg) return NULL; sg_init_table(need_sg, num_page); for (i = 0; i < num_page; i++) { struct page *page; unsigned int len; unsigned int off_in_page; off_in_page = offset_in_page(ptr); len = min((unsigned long)(PAGE_SIZE - off_in_page), remaining); page = vmalloc_to_page(ptr); if (!page) { kfree(need_sg); return NULL; } sg_set_page(&need_sg[i], page, len, off_in_page); ptr += len; remaining -= len; } } else { need_sg = kmalloc(sizeof(*need_sg), GFP_ATOMIC); if (!need_sg) return NULL; sg_init_one(need_sg, buf, nbytes); } return need_sg; } ``` --- ### 🔍 代码解释 | 部分 | 解释 | |------|------| | `is_vmalloc_addr(buf)` | 判断是否是 `vmalloc` 区域的地址,决定是否需要多页映射 | | `num_page` 计算 | 正确计算所需页数:起始偏移 + 总长度跨越了多少页 | | `sg_init_table(need_sg, num_page)` | 初始化 scatterlist 数组 | | `vmalloc_to_page(ptr)` | 将虚拟地址转换为对应物理页的 `struct page *` | | `sg_set_page(..., len, off_in_page)` | 设置每个 segment 的页、长度、页内偏移 | | `ptr += len; remaining -= len;` | 移动指针并减少剩余字节数 | | `kfree(need_sg)` on error | 若某页无法获取(理论上很少见),释放已分配内存 | | `else` branch | 线性内存直接使用 `sg_init_one` 快速初始化单个 entry | --- ### 📌 注意事项 - `vmalloc_to_page()` 在 `CONFIG_MMU` 下有效;无 MMU 架构可能不支持。 - 该函数假设调用者会在适当时候释放返回的 `scatterlist` 和其包含的 `struct page` 引用。 - 若用于 RDMA 或 DMA,请确保调用 `dma_map_sg()` 前已完成一致性设置(cache flush等)。 - 散列列表使用完毕后,应调用 `kfree()` 手动释放 `need_sg`。 --- ### ❓ 如何进一步提升? 你可以考虑封装成带自动 cleanup 的结构体,或者提供配套的销毁函数: ```c void vrdma_free_sg(struct scatterlist* sg, int nents, bool is_vmalloc) { if (sg) { // 如果是 vmalloc 地址对应的 sg,不需要 unmap 吗?视具体 driver 而定 kfree(sg); } } ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值