"Context" is a very common abstraction for kernels whenever some component performs generically programmed actions on behalf of one object of many, such as the kernel executing a system call on behalf ("in the context of") of a process, scheduler scheduling threads, etc. Since the code to execute is the same for all processes, the only difference in the control flow (and in the results) must be due to the object's internal data. On the assembly level, this abstraction of "context" is typically implemented by addressing all of the internals of an object via a single pointer. This pointer is passed to any code working on objects instances, and all the data members of an object are accessed starting from this pointer (either at fixed offsets from it, or via a chain of pointers starting at it). ---Side note:--- This pattern is the main one behind implementations of "Object Oriented" programming. For example, C++ methods are essentially functions that take an additional implicit argument "this", which is a pointer to the object on which they are called. Objects also store a pointer to the function-pointer array, called a "vtable", that contains pointers to the class' "virtual" methods; whenever a virtual method is called via a pointer to an object, the compiler emits code that first dereferences the slot in the vtable that corresponds to a method, then passes the object pointer to the found function. ---end side note--- In case of complex abstractions such as threads and processes that include both data and an execution environment for code, several pointers (used by different functional parts of the processor and thus not easily reducible to a single value) may be used to represent an object. Specifically, these are: a pointer to the thread or process descriptor (stored for threads at a fixed offset from the %gs:0 virtual address and for processes in the kthread_t structure addressed through the above), a pointer to the page tables describing the virtual address space (stored in the special %cr3 control register), and a pointer to the function call stack inside that virtual address space (stored in the %esp or %rsp registers, and also affected by the stack segment selector %ss). Note that the actual address in physical memory that %gs:0 points to depends on _both_ the value in %cr3 and the value in the GDT slot that %gs is pointing to. Specifically, %gs (a 16 bit register split into the 13 upper bits for the slot number and the 3 lower bits for other info) contains the number of the slot in the GDT. That slot in the GDT contains a 32-bit _descriptor_ of a memory segment (i.e., a contiguous range of virtual addresses), which includes a base address and the length (see, e.g., Fig. 5-1 on page 1785 of the Intel manual). But where is the GDT itself? It's location is pointed to by the special register GDTR (see, e.g., Fig. 2-1 on page 1971 of the above). So all of these are in fact parts of the thread context. The same is true for the stack segment selector %ss and the stack segment descriptor in the GDT that it points to. Study the diagram Fig. 2-1 on page 1971 to see the whole picture at a glance. Note that the locations of GDT and IDT are known to MDB as gdt0 and idt0 (on a 64-bit machine, gdt0=K and idt0=K will give you the addresses, ::idt will interpret the IDT entries and show the functions they point to, and gdt0,100/K will show the first 100 entries raw). NOTE: MDB provides the command ::idt (a "DCMD", in MDB termninology), but appears to have no command to examine GDT nor to show the contents of GDTR. Writing these and a few others would make a nice class project. Consult Chapter 10 of the MDB Guide, http://docs.oracle.com/cd/E18752_01/html/816-5041/ and the code for various DCMDS including ::idt in http://src.illumos.org/source/xref/illumos-gate/usr/src/cmd/mdb/i86pc/modules/unix/unix.c (full search for 'idt' limited to the file path '/mdb/') ==== Loading and storing special registers GDTR, IDTR, LTDR ==== These registers are loaded on system bootup to keep the entire memory translation system operating and to enable all of the above abstractions (and some others). Each register has a dedicated "store" and "load" instructions (privileged to ring0, so only kernel can call them; they'll trap in userland ring3). For example, IDTR is stored with "sidt", loaded with "lidt" instructions. Find the functions that load and store these registers in http://src.illumos.org/source/xref/illumos-gate/usr/src/uts/intel/ia32/ml/desctbls_asm.s as {rd,rw}_{i,g,l}dtr . See the same in MDB with "rd_idtr::dis", "wr_idtr", etc. ==== Switching from thread to thread: context switch in scheduler ===== This code (in assembler) is in the function resume() and its helper macros: http://src.illumos.org/source/xref/illumos-gate/usr/src/uts/intel/ia32/ml/swtch.s It is explained in "Solaris on x86" (in the course directory) on pages 122-123 (Sec. 6.5) Read through both and make sure you can see how the source code is mapped into the disassembly ("resume::dis"). Previous material in Chapter 6 (pages 111-121) helps understand the memory setup and gives many examples of complex but useful MDB commands. Study and understand these examples (e.g., observe the command on p. 114 that exracts the contents of IDT and feeds them line-by-line in and out of shell commands). ==== Getting a pointer to the current CPU struct ==== In OpenSolaris/Illumos, for each CPU a separate cpu_t structure (http://src.illumos.org/source/xref/illumos-gate/usr/src/uts/common/sys/cpuvar.h#80) is allocated at boot up. That per-CPU structure starts with a processor_id , which, together with all the rest of the information in cpu_t, is looked up through the CPU macro (http://src.illumos.org/source/xref/illumos-gate/usr/src/uts/common/sys/cpuvar.h#572) The CPU structure serves as the OS "identity" of a CPU. What CPU is this code currently running on? This question is not as simple as it sounds. A typical Multi-processor system (MP) has CPUs of exactly the same model and capabilities; although the boot process requires one of them to take as lead as a Boot Service Processor (BSP) while the other so-called Application Processors (AP) are halted for the duration of the boot process, this distinction is arbitrary and need not continue after the system boots (see "MP specification" http://www.intel.com/design/archives/processors/pro/docs/242016.htm for more information; note the year of its release -- this standard survived longer than most!). Some details of OS dealing with multiple processors can be gleaned from http://src.illumos.org/source/xref/illumos-gate/usr/src/uts/i86pc/os/mp_startup.c (e.g., mp_cpu_configure_common()). Note how tables are copied from the _current_ CPU to the one being set up (line 387-8: 387 cp->cpu_gdt = kmem_zalloc(PAGESIZE, KM_SLEEP); 388 bcopy(CPU->cpu_gdt, cp->cpu_gdt, (sizeof (*cp->cpu_gdt) * NGDT)); ^^^macro ^^pointer to area just allocated and similarly lines 408-9 for creating and copying a new IDT. ) NOTE: A detailed kernel tracing analysis of how threads can be "pinned" to the CPU and what makes it work would make a nice project. You can observe how the cpu[0] structure is filled and the CPU's various descriptor tables are set up in http://src.illumos.org/source/xref/illumos-gate/usr/src/uts/i86pc/os/mlsetup.c#103 Observe the init_desctbls() on line 166 and the enumeration of CPU features (http://en.wikipedia.org/wiki/CPUID explains the CPUID instruction that querying a processor about its features relies on). Further in the same function you can see the filling-in of the first thread t0 and its containing process descriptor p0 , and then setting up t0 as the idle thread of the first CPU (lines 309--320). If you follow this function to its end, you will see many stages of the pre-main() OS setup. Note the call to the function ucode_check() neaar the end of the setup process. This is the point at which the OS may update the CPU's actual microcode! ==== Getting a pointer to the current thread on the CPU ==== http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/intel/asm/thread.h -- thread pointer context: extern __inline__ struct _kthread *threadp(void) { void *__value; #if defined(__amd64) __asm__ __volatile__( "movq %%gs:0x18,%0" /* CPU_THREAD */ : "=r" (__value)); #elif defined(__i386) __asm__ __volatile__( "movl %%gs:0x10,%0" /* CPU_THREAD */ : "=r" (__value)); #else #error "port me" #endif return (__value); } (extern __inline__ explained: http://publib.boulder.ibm.com/infocenter/compbgpl/v9v111/index.jsp?topic=/com.ibm.xlcpp9.bg.doc/language_ref/cplr243.htm -- "If you specify the __inline__ keyword, with the trailing underscores, the compiler uses the GNU C semantics for inline functions. In contrast to the C99 semantics, a function defined as __inline__ provides an external definition only; a function defined as static __inline__ provides an inline definition with internal linkage (as in C99); and a function defined as extern __inline__, when compiled with optimization enabled, allows the co-existence of an inline and external definition of the same function. For more information on the GNU C implementation of inline functions, see the GCC documentation, available at http://gcc.gnu.org/onlinedocs/." Why all this? See http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/intel/ia32/ml/i86_subr.s#2381 ) ==== From threads to process descriptors and other abstractions ==== http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/thread.h extern kthread_t *threadp(void); /* inline, returns thread pointer */ #define curthread (threadp()) /* current thread pointer */ #define curproc (ttoproc(curthread)) /* current process pointer */ #define curproj (ttoproj(curthread)) /* current project pointer */ #define curzone (curproc->p_zone) /* current zone pointer */ cf: in getpid() code: int64_t getpid(void) { rval_t r; proc_t *p; p = ttoproc(curthread); r.r_val1 = p->p_pid; if (p->p_flag & SZONETOP) r.r_val2 = curproc->p_zone->zone_zsched->p_pid; else r.r_val2 = p->p_ppid; return (r.r_vals); } Observe this is disassembly ("getpid::dis") if you haven't done this already. ========================================================================== Scheduler visits: Linux: task_struct OpenSolaris: kthread_t Per process: proc_t, kthread_t, kwlp_t (see Fig. 2.3) Linux: lower priorities have precedence, 0 is most critical OpenSolaris: higher priorities have precedence, highest are most critical 0-59 Time Shared, Interactive, Fixed, Fair Share Scheduler 60-99 System Class 100-159 Real-Time (note real-time higher than system threads) 160-169 Low level Interrupts