Hi,
osdevuser1923 wrote:
I'm writing task switching, which must occur on timer interrupt.
No.
Task switches occur when:
- The scheduler has to find something else for CPU to do, because:
- The currently running task blocks for any reason (e.g. has to wait for disk IO or network or IPC or ....)
- The currently running task terminates itself (e.g. calls "exit()")
- The currently running task crashes
- The scheduler decides that higher priority task should preempt the currently running (lower priority) task immediately, because:
- The higher priority task was unblocked (whatever it was waiting for happened)
- A new high priority task is being spawned/created
- There are 2 or more tasks running (for the CPU), and the currently running task used all of the time slice it was given, and scheduler wants to give other task/s some CPU time
Note that the last reason ("currently running task used all of the time it was given") is relatively unimportant. Most task switches are caused by tasks blocking/unblocking (and it is possible to have perfectly sane scheduler without bothering with "currently running task used all of the time it was given" - e.g. using a "highest priority task that can run does run" approach).
osdevuser1923 wrote:
But some of threads are in ring 0, others - in ring 3.
No; all tasks are running in ring 0 when you find out that a task switch needs to happen.
For ring 3 tasks; something causes the CPU to switch to ring 0 (a kernel API call, an exception, an IRQ), then
while you're running in ring 0 anyway the kernel decides it should do a task switch for one of the reasons listed above.
Essentially, switching rings (and TSS, etc) has nothing to do with task switching at all.
osdevuser1923 wrote:
How to force stack switch even if privilege level don't change?
Because all task switches happen when CPU is running in ring 0; you only ever have to worry about switching from a task that is running at ring 0 to another task that was running at ring 0. The low level task switch code can push a few registers onto the old task's kernel stack, save "kernel stack top" (RSP register) somewhere, load a new "kernel stack top" (RSP register) from somewhere, switch to the new task's virtual address space if necessary, then pop a few registers from the new task's kernel stack.
After the task switch happened, the task might or might not switch back to ring 3; but that still has nothing to do with task switching at all.
For a simple example, imagine that:
A task calls the kernel API's "read()" function (causing switch from ring 3 to ring 0)
- The kernel's "read()" function checks if the data is cached or not, finds out the data isn't cached, and asks the file system code to fetch the data; and the task is blocked (causing a task switch) because it has to wait while the data is read from a hard disk
- Later on the data arrives from disk/file system/whatever; and the task is unblocked
- Sooner or later (possibly immediately) the scheduler does a task switch back to the (now unblocked) task
- Then the kernel figures out how much data was read (or if there was an error, etc) and returns this information from the kernel API's "read()" function (causing a switch from ring 3 back to ring 0).
Cheers,
Brendan