今天开始分析昨天提到的mprotect()
系统调用。不过, 在我开始分析之前, 有国外网友找到了产生昨天所述的异常的HDL代码, 发现产生异常(ag_pipe_access_fault_with_page
)的条件为: ag_pipe_inst_vld && mmu_pa_vld && (mmu_so && ag_pipe_vls || !mmu_ca && ag_pipe_amo)
(ref)。考虑到昨天的测试表明产生异常的指令是原子操作指令, 导致异常的原因应当是mmu_ca
条件不满足。此处需要说明的是, C906处理器核对RISC-V指令集进行了扩展, 在每个页表项的高4位中加入了地址属性的设置位, 包括该页的地址是否是外设地址(SO位)、地址是否开启缓存(cache) (C位)等。因此, !mmu_ca && ag_pipe_amo
的含义是原子操作指令(AMO指令)仅能对开启了缓存的地址进行, 对别的地址进行会产生异常。进一步, mmu_ca
条件不满足就说明对应页表项中的C位没有被置1。然而, 对于普通内存, 它应当是置1的, 昨天对于pthread的各种测试也验证了这一点。
经过查看mprotect()
源代码, 我注意到其利用vma_set_page_prot()
来修改页表项。其中, vma_set_page_prot()
利用vm_pgprot_modify()
和pgprot_modify()
来计算新的页表项。进一步查看内核源代码, 我发现pgprot_modify()
会尝试在计算新的页表项的同时保留noncached
、writecombine
以及device
相关的页表标志位。然而, D1那帮人改出来的Linux内核中, writecombine
对应的标志位是全0, 并且PROT_NONE
对应的标志位是0000000000000002
(注意到高4位为0)。于是, 在使用mprotect()
将PROT_NONE
的普通内存页变更为其他正确权限的时候, 页表项的高4位会保持为0, C位没有被置1, 原子指令会产生异常!
最终, 为了解决这个问题, 要么删除writecombine
相关的代码(从而内核会使用默认的处理方式), 要么在PROT_NONE
对应的页表标志位中加入C位。一开始, 我担心修改PROT_NONE
的页表标志位会有其他副作用, 所以选择删除了D1添加的writecombine
相关的代码。删除后, 之前提到的问题不再出现, pthread可以正常使用。
后来, 经过冰淇淋姐姐指点, 我了解到在别的架构里, PROT_NONE
对应的页表标志位可以包含开启缓存与否的标志, 而删除writecombine
相关的代码可能会导致相应的内存操作(比如对于可以writecombine
的内存区域的操作, 如对于framebuffer的操作)性能降级。最后, 她给出了一个更好的patch, 即在PROT_NONE
对应的页表标志位中加入C位。
最终, 问题得以解决, 我也开始运行实验。
发表评论