I’ve been learning ” Operating Systems and Systems Programming ” Online for a while. I use University of California, Berkley’s Online Webcast lectures and Operating Systems Design and Implementation by Andrew S.Tanenbaum as reading material.
I’m blogging here something which is a simple concept though, performs complex working.
Multithreading is a concept, which makes an illusion of one or more threads running in parallel. To put up in a simple way, only one thread runs at a time, after a specific threshold time the 1st thread stops and 2nd thread continues running, again after the threshold time 2nd thread stops and 1st resumes running thereby creating a illusion of two threads running at same time.
I’m diving straight into the topic, as the basic concepts are out of scope to this article.
Here is an extract from Prof. John kubiatowicz slide
Consider two threads S and T. Consider S is running. A and B are the routines in each thread. After a specific threshold time or some boundary value or why not, sometimes even after receiving a interrupt signal the running thread (Thread S) makes a yield call. The yield returns the control of execution to the kernel. The kernel checks for waiting threads i.e the threads in runnable state [sometimes based on priority] and makes a call to switch routine, The sweetest of all.
Switch routine accepts two inputs [current thread pointer and new thread pointer]. The basic functionality of switch looks like this.
Seems simple enough, Registers of current thread [Thread S] is saved to TCB[tCur].regs.rx and the values of new thread’s registers TCB[tNew].regs.rx is over written to the CPU registers CPU.rx. The overwritten registers include stack pointer and return address. After the switch performs its process the control is shifted back to the kernel and the kernel hands over the control to the routine whose address is stored in newly overwritten Instruction pointer. In our case thread T starts running. As instructed by Prof.john kubiatowicz, i had a look at the nachos source code for switch.
Switch.S is written in assembly. It has 4 subroutines [MIPS,SPARC,HP RISC,INTEL], each subroutine is called in specific to the CPU architecture. I’ll discuss here the INTEL’s subroutine alone.
/* void SWITCH( thread *t1, thread *t2 ) ** ** on entry, stack looks like this: ** 8(esp) -> thread *t2 ** 4(esp) -> thread *t1 ** (esp) -> return address ** ** we push the current eax on the stack so that we can use it as ** a pointer to t1, this decrements esp by 4, so when we use it ** to reference stuff on the stack, we add 4 to the offset. */ .comm _eax_save,4 .globl _SWITCH _SWITCH: movl %eax,_eax_save # save the value of eax movl 4(%esp),%eax # move pointer to t1 into eax movl %ebx,_EBX(%eax) # save registers movl %ecx,_ECX(%eax) movl %edx,_EDX(%eax) movl %esi,_ESI(%eax) movl %edi,_EDI(%eax) movl %ebp,_EBP(%eax) movl %esp,_ESP(%eax) # save stack pointer movl _eax_save,%ebx # get the saved value of eax movl %ebx,_EAX(%eax) # store it movl 0(%esp),%ebx # get return address from stack into ebx movl %ebx,_PC(%eax) # save it into the pc storage movl 8(%esp),%eax # move pointer to t2 into eax movl _EAX(%eax),%ebx # get new value for eax into ebx movl %ebx,_eax_save # save it movl _EBX(%eax),%ebx # retore old registers movl _ECX(%eax),%ecx movl _EDX(%eax),%edx movl _ESI(%eax),%esi movl _EDI(%eax),%edi movl _EBP(%eax),%ebp movl _ESP(%eax),%esp # restore stack pointer movl _PC(%eax),%eax # restore return address into eax movl %eax,4(%esp) # copy over the ret address on the stack movl _eax_save,%eax ret
4(esp) points to thread S and 8(esp) points to thread T, thread S’s address is pulled to eax and all the registers viz ebx,ecx,edx etc are stored with respect to eax. ex: register ecx gets stored to _ECX(%eax) . Then 8(esp) is loaded to eax, and all registers of thread T are loaded to cpu register. ex: register _ECX(%eax) is loaded to %ecx. The return address of each threads are saved and exchanged respectively.
ps: The diagrams were taken from Prof. John kubiatowicz‘s slides.