小结
本章我们的工作有:
- 理清线程和进程的概念
- 通过设置
Context
,可以构造一个线程的初始状态 - 通过
__restore
标签,直接进入第一个线程之中 - 用
Context
来保存进程的状态,从而实现在时钟中断时切换线程 - 实现内核栈,提供安全的中断处理空间
- 实现调度器,完成线程的调度
同时,可以发现我们这一章的内容集中在内核线程上面,对用户进程还没有过多的提及。而为了让用户进程可以在我们的系统上运行起来,一个优美的做法将会是隔开用户程序和内核。需要注意到现在的内核还直接放在了内存上,在下一个章节,我们暂时跳过用户进程,实现可以放置用户数据的文件系统。
思考
可以看到我们的设计中用了大量的锁结构,很多都是为了让 Rust 知道我们是安全的,而且大部分情况下我们仅仅会在中断发生的时候来使用这些逻辑,这意味着,只要内核线程里面不用,就不会发生死锁,但是真的是这样吗?即使我们不在内核中使用各种 Processor
和 Thread
等等的逻辑,仅仅完成一些简单的运算,真的没有死锁吗?
Click to show
会有死锁,比如我们在内核线程中构造一个 Vec
,然后在里面 push 几个元素,这个时候就可能产生死锁。
需要注意到,我们的动态分配器是一个 LockedHeap
,是外面加了锁的一个分配器,如果在线程里面 push 的过程中需要动态分配,然后正好在上完锁而且没有释放锁的时候产生了中断,而中断中我们的 Scheduler
也用到了 Vec
,这个时候会再去锁住,但是又拿不到,同时需要注意的是在处理中断本身时,我们的时钟中断是关掉的,这意味着我们的锁会一直去申请,就形成了类似死锁的死循环。
解决这个问题需要把申请到锁之后加上关闭中断,通过这种抢占式的方法彻底执行完分配逻辑之后再关闭锁同时打开中断。这个问题是一个设计上的取舍,如果我们不支持内核抢占,就需要很多精妙的设计来绕开这个问题。在这里,我们先不会理会这个问题。
</blockquote> </div></div>