toor / lambdaOS

A very basic operating system written in Rust, with some sprinklings of Assembly.
GNU General Public License v3.0
24 stars 2 forks source link

Resolve all known deadlock issues with PIT scheduling #6

Closed robert-w-gries closed 6 years ago

robert-w-gries commented 6 years ago

I've combined various fixes from robert-w-gries/rxinu#47, robert-w-gries/rxinu#49, and robert-w-gries/rxinu#52 to stabilize scheduling in lambdaOS.

There was significant refactoring effort to ensure that all critical sections of the kernel is safely wrapped in disable_interrupts_and_then(). I've generally kept the naming conventions I used in rxinu. If you have any preferences for naming or code organization, let me know.

I can comfortably say that the scheduling code is stable now because lambdaOS is able to create and run 1500 processes without deadlocking or running out of memory. Keyboard input still works while the null process is running and input also works after the final process is created and ran.

Restoring interrupts instead of enabling all interrupts

Derived from robert-w-gries/rxinu#47

Previously, the disable_interrupts_and_then() function would globally disable interrupts, run some important kernel code, then it would globally enable interrupts.

This procedure worked when the null process called resched() because we could ensure that resched() would only be called at one point in the kernel.

After moving resched() to the timer handler, the kernel could now see a resched() call at any time when interrupts are enabled. This led to various deadlock issues where interrupts would be called when we didn't expect it.

My solution was to add many disable_interrupts_and_then() wrappers to any section of the kernel that held a lock (such as the Heap Allocator). This led to some nested calls to disable_interrupts_and_then(). Since the wrapper globally enables interrupts after running code, interrupts were re-enabled inside of a disable_interrupts_and_then() lower in the call stack. Thus, code that was expected to run interrupted was actually being interrupted.

To solve this problem, I modified disable_interrupts_and_then() to save the interrupt masks of the Chained PICS. Now, we can nest calls to disable_interrupts_and_then() as much as we want because the inner calls will see that the interrupt mask registers should be 0xFF.

Refactoring the Heap Allocator

A consequence of using the PIT to schedule processes is that the Heap Allocator could be interrupted during allocation or de-allocation. Since our allocator is implemented with a Mutex wrapper, we need to ensure that allocation is not interrupted while the lock is held.

Your previous organization of the global allocator did not allow me to wrap the alloc methods in disable_interrupts_and_then(), so I needed to move the heap allocator code to src/memory/heap_allocator.rs.

Notes

toor commented 6 years ago

Thanks, this looks really good. I don't know how I missed the reset of PIT_TICKS - I feel like an idiot now D: . Regarding the printing to screen, this is something to do with the fact that the buffer methods are not directly accessing the VGA buffer, but instead call into an intermediary that then syncs the VGA buffer itself.