调bug第二天

今天开始分析昨天提到的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()会尝试在计算新的页表项的同时保留noncachedwritecombine以及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位。

最终, 问题得以解决, 我也开始运行实验。

发表评论

注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

:wink: :twisted: :roll: :oops: :mrgreen: :lol: :idea: :evil: :cry: :arrow: :?: :-| :-x :-o :-P :-D :-? :) :( :!: 8-O 8)

本文链接:https://twd2.me/archives/16187QrCode