Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CPU Registers and Multitasking

I'm currently learning Assembly and I'm confused how the CPU registers work with Multitasking. So in a Multitasking System. CPU can pause the execution of a certain program at any time and run another program. So how are the register values preserved during this step ? Are the registers pushed to stack or any other way ?

like image 736
Dilhan Geeth Avatar asked Jun 25 '16 03:06

Dilhan Geeth


1 Answers

how the CPU registers work with Multitasking.

CPU basically may not work with multitasking, task switch may be (and is) implemented in software. Some CPU (intel x86) may have hardware state (TSS https://en.wikipedia.org/wiki/Task_state_segment) and TR (task register) to atomically change state from one task to another. (TSS may still be used to switch protection rings ring0/ring3; but not to switch tasks.)

So in a Multitasking System, CPU can pause the execution of a certain program at any time and run another program.

Almost.

In most CPU, capable of running OS and user-space tasks, there are interrupts which are used in case of some external event (signal from hardware, interrupt request, IRQ) to pause execution of current code (task) and to jump to one of several special kernel functions, called interrupt handlers (ISR, interrupt service routine).

So how are the register values preserved during this step ?

Most registers just stays untouched on interrupt entry. Some are preserved by CPU in interrupt entry process.

Interrupt support mechanism is implemented inside CPU, it does save some cpu registers of current task to some space (yes, it can save to stack, x86 cpu pushes *FLAGS, CS, IP). CPU jumps to these routines, registered in Interrupt Vector Table / Interrupt Descriptor Table (IDT) arrays (stored at special memory location in case of IVT or pointed by special CPU register IDT for IDT); for IRQ 1 it will select record number 1 (routine 1); for IRQ 20 it will select routine 20.

Are the registers pushed to stack or any other way ?

Both. Some are pushed by CPU in interrupt, and some are pushed by commands of interrupt handler. (And there is also code to change EIP in kernel mode...)

ISR routine knows what was saved by CPU, and if it want to use some more registers (it basically wants), it will push to stack (or save to other place) other user registers, then do its interrupt work, then restore manually saved registers back, and then will exit from interrupt using special command (iret in x86 word, it will reload "CS:IP and FLAGS" back from stack). If original IP was untouched in the stack, CPU will continue execution of original user code (original task), and it will have all registers unchanged. If author or interrupt handler wants, he may change IP address in the stack before doing iret, so it will return in other place (it may return to kernel mode or to some task or reboot PC and so on).

Changing current running task (context switch, wiki, osdev) is one of problems which may be done using interrupts. There are basically two kinds of context switching: involuntary (when the task just runs and do not want to leave CPU) and voluntary (when task asks OS to do the context switch, either because current task may no run further or if current task is polite and gives other tasks chance to run - sched_yield in linux world).

Involuntary context switch is usually done with help of periodic timer interrupt. Several years ago this timer used to generate some specific IRQ (timer/RTC IRQ) every 10, 1 or 3 milliseconds; and timer interrupt handler calls OS scheduler to decide is current task runs for too much time (exceeds its time slice, the quantum of time-sharing) or is there some task with higher priority ready to run.

Voluntary context switch is usually done with help of system call (when OS used privilege separation, runs user-space code in ring3 and kernel-space code in ring0 - concept of protection rings), which is special CPU instruction to switch between privilege levels (it may be / was implemented with software-generated interrupts; int 80h in older linux/BSD). User-space code asks kernel to do some work, for example read from file or socket or write to file. It also may ask kernel to run scheduler to switch to other task if there is any - with sched_yield syscall. If syscall code decides that request need some time and the task can't run further before the request is done (system call blocks - blocks current task from running; switches it state from TASK_RUNNING), it will also call OS scheduler.

OS scheduler may keep current task as running (if it is in runnable state) or may decide to switch to other task (actually to kernel-mode of other task; there also will be return-from-syscall to restore task's CS:IP+FLAGS) and do it using switch_to asm macro: http://lxr.free-electrons.com/source/arch/x86/include/asm/switch_to.h?v=4.6#L27:

 33         /*                                                              \
 34          * Context-switching clobbers all registers, so we clobber      \
 35          * them explicitly, via unused output variables.                \
 36          * (EAX and EBP is not listed because EBP is saved/restored     \
 37          * explicitly for wchan access and EAX is the return value of   \
 38          * __switch_to())                                               \
 39          */                                                             \
 40         unsigned long ebx, ecx, edx, esi, edi;                          \
 41                                                                         \
 42         asm volatile("pushfl\n\t"               /* save    flags */     \
 43                      "pushl %%ebp\n\t"          /* save    EBP   */     \
 44                      "movl %%esp,%[prev_sp]\n\t"        /* save    ESP   */ \
 45                      "movl %[next_sp],%%esp\n\t"        /* restore ESP   */ \
 46                      "movl $1f,%[prev_ip]\n\t"  /* save    EIP   */     \
 47                      "pushl %[next_ip]\n\t"     /* restore EIP   */     \
 48                      __switch_canary                                    \
 49                      "jmp __switch_to\n"        /* regparm call  */     \
 50                      "1:\t"                                             \
 51                      "popl %%ebp\n\t"           /* restore EBP   */     \
 52                      "popfl\n"                  /* restore flags */     \
 53                                                                         \

If there was the only non-sleeping user task and it goes to sleep, there are no visible tasks ready to be run. But in fact there is invisible task with pid 0, sometimes called swapper or idle, which has lowest priority and always ready to run. It will run some special CPU instruction in loop to cool down the CPU - HLT; it may also do some checks for events / calls to scheduler to find runnable tasks.

Some strange realtime OS (with name starting from "V" and ending in version 5) without user-space/kernel-space separation and isolation (all code runs in ring 0) may implement voluntary context switch without syscall or software interrupts; but by usual call to scheduler.

Useful links:

  • https://en.wikibooks.org/wiki/X86_Assembly/Advanced_Interrupts
  • http://wiki.osdev.org/Interrupt
  • http://wiki.osdev.org/Context_Switching
  • http://www.informit.com/articles/article.aspx?p=364068 - How Multitasking Works at the Hardware Level, 2005, chapter of very good book "Unabridged Pentium 4, The: IA32 Processor Genealogy".
  • Task management on x86 mentions "Protected Mode Software Architecture" book and "Understanding the Linux Kernel, 3rd Ed.", Bovet, chapter 3 Processes, 3.3.2. Task State Segment.
  • http://wiki.osdev.org/Task_State_Segment of hardware task switching
like image 85
osgx Avatar answered Sep 28 '22 16:09

osgx