前几天有网友反馈, 说自己编译的Linux内核在D1开发板上运行一些应用程序的时候会报段错误。我当时繁忙, 就没详细了解这个事情。现在, 我需要用D1开发板进行科研活动, 因此需要解决这个问题, 否则在运行测试的时候很可能会崩溃。
我查看内核对段错误输出的信息后, 发现导致段错误的处理器异常代码为7, 根据RISC-V手册, 是Store/AMO access fault。这就很奇怪了, 居然不是page fault。经过cyy用objdump
研究, 我们发现出错的指令是pthread库里面的一条amoswap.w
原子操作指令。尝试自己编写一个最小的用pthread启动新线程的小程序, 确定其也可以触发同样的错误。
由于没想到其他办法, 就找来pthread源代码, 自己编译了一份, 方便后续再进行魔改。然后, 将上述小程序链接到自己编译的pthread以供测试。多次魔改pthread后, 我们发现普通的load或store指令不会导致上述异常, 而上述原子操作指令即使紧跟着一个store指令(这意味着CoW等机制已处理完毕), 也会产生异常。另外, 经过阅读pthread代码, 我发现最初产生异常的原子指令访问的目标是新线程的栈(上的TCB)。如果把普通load或store指令或原子指令放到新栈刚刚分配出来之后的程序点, 现象一致。
接下来, 继续考察用来分配新栈的函数。我发现, 在默认设置下, 栈的边界会有一个guard页面, 以便程序在栈溢出时能够有效地产生异常。为了实现guard机制, pthread首先用mmap()
分配了一个没有权限(PROT_NONE
)的新栈, 然后用mprotect()
将新栈的guard以外的部分设置上了正确的权限。我无端猜测, 会不会是这个机制间接导致了上述报错。为了验证这个猜测, 我关闭了guard机制, 这样pthread一开始就会使用mmap()
分配一个正确权限的栈。经过测试, 这样修改后, 原先出问题的原子操作指令确实不再产生异常了。
首先, 我们猜测是不是有什么TLB或缓存没有刷新, 从而导致TLB内容不正确, 接着导致上述原子操作指令产生异常。后来, 经过cyy花式刷TLB和缓存, 效果似乎不太明显, 指令仍然大概率会产生异常。
此外, 接下来还需要分析一下Linux内核mprotect()
的代码, 由于时间较晚, 这个部分留到明天完成。
发表评论