Hi,
StringCheesian wrote:1. Once I've done multitasking, if I want to play with VESA 2.0 switching to (un)real mode for VESA calls would be simplest, right? GRUB probably leaves the important parts of the first megabyte untouched, right?
GRUB does leave everything useful in the first 1 MB untouched (it does use free RAM, but that shouldn't matter) - I've switched back to real mode and used VESA/VBE, keyboard, etc before without problems.
StringCheesian wrote:2. Does software multitasking usually involve any TSSs? or are they only for hardware multitasking?
Usually it involves one TSS to hold SS0 and ESP0 (used by the CPU when switching from CPL = 3 to CPL = 0), and possibly SS1, ESP1, SS2 and ESP2 (if you use CPL=1 and CPL=2), and maybe an I/O permission bitmap and/or interrupt redirection bitmap (if you need I/O port protection or faster virtual 8086 mode).
It is possible to use no TSSs (if all of your code runs at CPL=0) and it's also possible to use software task switching and hardware task switching - for e.g. use hardware task switching for some exception handlers (page fault, double fault, etc). I wouldn't bother using hardware task switching for exception handlers unless you allow those exceptions to be handled by non-kernel code.
StringCheesian wrote:3. Would it be a bad idea to have only one kernel stack? Why?
That depends. With only one kernel stack, the kernel can't be re-entrant, which can increase response times. For a monolithic kernel this can be very bad, but for a micro-kernel the response times might not be increased by much and the kernel itself might be faster (due to reduced complexity).
The main thing to consider would be how long the most expensive "kernel operation" takes, as this would determine the worse case increase in response times.
For example, if there's something in the kernel that does 2 seconds of processing that is being used by a very low priority task, and the user presses a key which causes a very high priority task to become "ready to run", then the very high priority task would have to wait for up to 2 seconds for the kernel stack to become available before the very high priority task could get CPU time.
It's not quite that simple though - there's things that a "single stack" kernel can do to improve response times. For e.g. split lengthy operations into several smaller operations seperated by "interruption points" (where the kernel stops, checks for pending task switches and aborts or postpones the operation if something is pending). Of course anything like this would increase the complexity again.
There's also the "memory usage" - an OS with 1024 tasks where every task has a 4 KB kernel stack will consume 4 MB just for kernel stacks, even though most of those kernel stacks don't need to be used at the same time.
There is also a third option - dynamic kernel stacks. Here you have "N stacks per CPU" rather than "one stack per CPU" or "1 stack per task". This solves the kernel re-entrancy/response time problem and also minimizes the memory usage, but it's probably the most complicated method. Depending on the how the rest of the kernel is designed it might be pointless.
StringCheesian wrote:4. User stacks in a flat memory model: a fixed size like 16K is reasonable for a small toy OS, right? I don't need something complicated like stacks that dynamically expand downward, do I?
The amount of stack space required by any piece of code depends on that piece of code, and can't be determined accurately without first analyzing that code. I've done software that needs less than 256 bytes of stack space, but I've done applications (in C with recursion) that use a lot more stack space.
The "normal" approach is to reserve a large amount of space for the stack, and then allocate actual RAM only when it's needed. For example, you could reserved 2 MB of space but only use 4 KB of RAM. It's also relatively easy to allow the user code to specify how much space to reserve for it's stack. For example, you could read a value from the executable's header and then reserve between 512 MB and 4 KB of space (and still only allocate RAM for it if/when it's needed).
Cheers,
Brendan