确定内核代码的关键部分
您好,我正在编写内核代码,它打算进行进程调度和多线程执行。 我研究了锁定机制及其功能。 是否存在一个关于应该通过锁定(互斥/信号量/螺旋锁)保护关键部分中的哪种数据结构的拇指规则?
我知道在部分代码中有可能发生并发的情况下,我们需要锁定。 但是,我们如何决定,如果我们错过了并且测试案例不能抓住他们,我们该怎么做。 早些时候,我写了系统调用和文件系统的代码,我从不关心锁的问题。
是否存在一个关于关键部分中的哪种数据结构应该被锁定保护的规则?
当一个访问是写访问时,任何对象 (全局变量,结构对象的字段等)都会同时 访问,这需要访问的一些锁定规则。
但是,我们如何决定,如果我们错过和测试案例会不会抓住他们呢?
好的做法是对变量,结构或结构领域的每个声明的适当评论,这要求对访问进行锁定。 任何使用此变量的人都会读取此评论并编写相应的访问代码。 内核核心和模块倾向于遵循这一策略。
就测试而言,常见测试很少会因并发性问题的可能性较低而显示出来。 在测试内核模块时,我会建议使用Kernel Strider,它试图证明并发内存访问或RaceHound的正确性,这会增加并发问题的发生概率并检查它们。
在任何访问任何共享数据的代码的持续时间内抓取锁始终是安全的,但这是缓慢的,因为这意味着一次只有一个线程可以运行大量的代码。
尽管取决于所讨论的数据,但可能存在安全且快速的快捷方式。 如果它是一个简单的整数(并且我用整数表示CPU的本地字大小,即不是32位cpu上的64位),那么您可能不需要执行任何锁定:如果一个线程试图写入整数,另一个同时读取它,读者将得到旧值或新值,而不是两者的混合。 如果读者不关心他获得了旧的价值,那么就不需要锁定。
但是,如果您将两个整数更新在一起,那么读者将获得一个新的值而另一个获得旧的值,那么您需要锁定。 另一个例子是如果线程递增整数。 通常涉及读取,添加和写入。 如果一个人读取旧值,那么另一个管理员读取,添加和写入新值,然后第一个线程添加并写入新值,两者都认为他们增加了变量,但不是增加两次,而是只增加一次。 这需要锁定或使用原子增量原语来确保读取/修改/写入周期不会中断。 还有原子测试和设置原语,以便您可以读取值,对其进行数学运算,然后尝试将其写回,但如果写入仍保持原始值,则写入成功。 也就是说,如果另一个线程在读取它之后对其进行了更改,则测试和设置将失败,然后您可以放弃新值并重新读取另一个线程设置的值并尝试测试并再次设置它。
指针实际上只是整数,所以如果你设置了一个数据结构,然后存储一个指向它的指针,在那里另一个线程可以找到它,只要你完全设置结构,然后你将它的地址存储在指针。 读取指针的另一个线程(它需要确保只读取一次指针,即将其存储在局部变量中,然后仅使用该指针来引用该结构)将会看到新的结构或旧的一个,但从来没有一个中间状态。 如果大多数线程只通过指针读取结构,并且任何想要写入的结果都是通过锁或者指针的原子测试和设置来完成的,这就足够了。 任何时候你想修改结构的任何成员,你都必须复制它到一个新的,更改新的,然后更新指针。 这基本上是内核的RCU(读,复制,更新)机制的工作原理。
理想情况下,您必须枚举系统中可用的所有资源,设计期间的相关线程和通信以及共享机制。 确定每种资源的下列情况并在发生变化时维护适当的检查清单可能会有很大的帮助:
如果可能的话,最好有一个描述资源,利用率,锁定,负载,通信/共享机制和错误的流程图。
这个过程可以帮助您确定缺失的情况/未知数,关键部分以及确定瓶颈。
除了上述过程之外,您还可能需要某些工具来帮助您进行测试/进一步分析,以排除隐藏的问题(如果有):
Helgrind
- 用于检测同步错误的Valgrind工具。 这可以帮助识别由于不正确锁定导致的数据竞争/同步问题,可能导致死锁的锁定顺序以及可能对后续影响造成的POSIX线程API使用不当。 请参阅:http://valgrind.org/docs/manual/hg-manual.html Locksmith
- 用于确定运行时可能出现的常见锁定错误或可能导致死锁的错误。 ThreadSanitizer
- 用于检测竞争条件。 应显示所有访问涉及的所有访问和锁定。 Sparse
可以帮助列出由函数获取和释放的锁,还可以识别诸如混合指向用户地址空间的指针和指向内核地址空间的指针等问题。 Lockdep
- 用于调试锁 iotop
- 通过监视内核输出的I / O使用信息来确定系统上进程或线程的当前I / O使用情况。 LTTng
- 用于追踪比赛条件并可能中断级联。 (LTT的继承者 - kprobes,tracepoint和perf功能的组合) Ftrace
- Linux内核内部跟踪器,用于分析/调试延迟和性能相关问题。 lsof
和fuser
可以方便地确定具有锁定和锁定类型的进程。 分析可以帮助确定内核正在花费的时间。 这可以使用perf
, Oprofile
等工具完成。 strace
可以拦截/记录进程调用的系统调用以及进程接收到的信号。 它应显示事件的顺序以及呼叫的所有返回/恢复路径。