Chapter 17

14章 マルチタスク(2)

misawa
misawa
2022.01.08に更新
このチャプターの目次

ここから 2022/1/5

Messaging queue

これ に従って MPSC を作った.
タスクのエントリポイントの引数として * mut Receiver<T> みたいなのを取らせたかったが, Box使ってよいらしい. やったね!

... と思ったら何か実行中にハングする.
立ち上げてすぐではなく数十秒立ってから止まるし, タスク間のメッセージが増えるようにすると止まりやすくなるので, heap をよく使っているのが悪そうな気がする.
ヒープ領域を増やしたりしても, あまり改善は見られない. メモリ不足ではなさそう.

ここから 2022/1/6

更に色々調べていると, 割とタイトなループにとらわれているようだ.

0x0345972f:  cc                       int3
0x03459730:  b1 01                    movb     $1, %cl
0x03459732:  66 2e 0f 1f 84 00 00 00  nopw     %cs:(%rax, %rax)
0x0345973a:  00 00
0x0345973c:  0f 1f 40 00              nopl     (%rax)
0x03459740:  31 c0                    xorl     %eax, %eax
0x03459742:  f0 0f b0 0f              lock cmpxchgb %cl, (%rdi)
0x03459746:  74 09                    je       0x3459751
0x03459748:  0f b6 07                 movzbl   (%rdi), %eax
0x0345974b:  84 c0                    testb    %al, %al
0x0345974d:  75 f9                    jne      0x3459748
0x0345974f:  eb ef                    jmp      0x3459740
0x03459751:  c3                       retq
0x03459752:  cc                       int3

確かに Atomic* はいくつか使っているが, 思い当たる節が無い. そもそも Atomic*fetch_* などでこういうビジーループにはならんし.
よく読むと, どうやらこれは spin lock のようだ. デッドロックにはそこそこ気を使い, lock を取る所は基本的に interruption されないように書いたつもりだったのだが.

.... ところで, allocator として使っているものが, 内部で spin lock を使っている, そのロックを取っている最中に interruption され, context switch した後, 別のタスクが interruption されないようにしつつ alloc をすると, デッドロックが完成する. すなわち,

// task A
x86_64::instructions::interrupts::enable();
let _ = Box::new(0u32); // alloc 中に task B に context switch

// task B
let _ = x86_64::instructions::interrupts::without_interrupts(||
  Box::new(0u32)
);

などが起きると詰む. 仕方ないので linked_list_allocator::LockedHeap をラップして x86_64::instructions::interrupts::without_interrupts 中で呼ぶような GlobalAlloc を作ったら解決したっぽい.