My Avatar

Shilong ZHAO

MIT 6.828 JOS Lab 3 Notes

2017-01-30 00:00:00 +0100

In case you have any questions or suggestions, you can leave comments HERE . Thanks!

关键词: environment, address space, user space, kernel space trap, TSS, IDT, GDT

Kernel Page Directory

The kernel page directory kern_pgdir is allocated directly on the physical memory by boot_alloc() The kernel page directory recursively insert page directory in itself as a page table, to form a virtual page table at virtual address UVPT:

kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;

And the page directory will work like a page table itself. In the end of virtual memory mapping of struct PageInfo *pages, struct Env *envs (their physical memory addresses will be stored in kern_pgdir), the kern_pgdir physical address is loaded to register CR3 lcr3(PADDR(kern_pgdir)) and enable paging flags in CR0

cr0 = rcr0();
cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_MP;cr0 &= ~(CR0_TS|CR0_EM);

Physical Memory

JOS keeps track of physical memory with a struct PageInfo array.

pages = boot_alloc(npages * sizeof(struct PageInfo));

the PageInfo pages array keeps track of free physical pages, wherever the page allocation is required, it goes through the array, and returns the first element whose physical piece is available; The total number of available pages is store in a global variable named npages and is set in function static void i386_detect_memory(void) ; The pages array is initialized in page_init(), after that the pages array is mapped to virtual address UPAGES. This kind of mapping is done by function boot_map_region() Environment context switch: an environment keeps the register states and the page directory. The initial physical address represented by PageInfo *pp is page2pa(pp);


The environments are stored in an array named struct Env *envs with total capacity of NENV:

envs = boot_alloc(sizeof(struct Env) * NENV);

and it is mapped to virtual address UENVS in kernel space. Each environment has its own page directory pde_t *env_pgdir (its own memory space, that’s the reason why we say a process sees itself has the whole memory space), and also its own saved registers struct Trapframe *env_tf. An environment is created with Requested Privilege Level (RPL) set to 3 in the registers. So an environment will always run in user mode.

e->env_tf.tf_ds = GD_UD | 3;
e->env_tf.tf_es = GD_UD | 3;
e->env_tf.tf_ss = GD_UD | 3;
e->env_tf.tf_esp = USTACKTOP;
e->env_tf.tf_cs = GD_UT | 3;

When setting up environment Env *e’s virtual memory, first get a physical page from pages, let e->env_pgdir equal to the virtual memory of that memory, and then copy the kernel page directory kern_pgdir the only difference is to set:

e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U;

To load a binary to an environment load_icode(), first switch to the environment’s own page directory lcr3(PADDR(e->env_pagdir)), allocate regions for different ELF segments and store the physical address to e->env_pgdir and set the program entry point e->env_tf.tf_eip according to the ELF header information elfhdr->e_entry For an environment to run in user mode, first switch page directory lcr3(PADDR(curenv->env_pgdir)) and then move trap frame values to corresponding registers env_pop_tf(&(curenv->env_tf))

Interrupts and Traps

Location and size of IDT (interrupt descriptor table) is stored in register IDTR, bits 16..47 are the linear address where the IDT starts, bits 0..15 defines the length of IDT in bytes lidt loads the source operand into interrupt descriptor table register (IDTR) with address and size of IDT; it accepts a linear address as operand.

vectors[i] = routine for interrupt number i. They actually call the same function _alltraps , the difference between these routines are the numbers they’ve pushed into stack before calling _alltraps, and _alltraps will call trap, the task of dispatching of interrupt numbers to specific routines are actually carried out by trap_dispatch Before calling _alltraps, the registers and trap numbers are pushed into stack in the reverse order of which they are defined in Trapframe.


The base linear address and limit of the GDT must be loaded into the GDTR register with lgdt assembly instruction. When the S (descriptor type) flag in a segment descriptor is clear (S = 0, otherwise it’s a code or data segment descriptor), the descriptor type is a system descriptor. The processor recognizes the following types of system descriptors:

System-segment descriptors point to system segments (LDT and TSS segments). Gate descriptors are in themselves “gates,” which hold pointers to procedure entry points in code segments (call, interrupt, and trap gates) or which hold segment selectors for TSS’s (task gates). The types are specified in bits 9..11 of a descriptor.


Like the GDT and LDTs, the IDT is an array of 8-byte descriptors. The IDT entries or the Interrupt Gate is defined as struct Gatedesc or gate descriptor in JOS. The IDT may reside anywhere in the linear address space. The processor locates the IDT using the IDTR register with assembly instruction. This register holds both a 32-bit base address and 16-bit limit for the IDT. The LIDT instruction loads the IDTR register with the base address and limit held in a memory operand The IDT may contain any of three kinds of gate descriptors: task-gate descriptor, interrupt-gate descriptor, trap-gate descriptor. IDT is set up in function trap_init(void).

for (i = 0; i < 256; i++)
        SETGATE(idt[i], 0, GD_KT, vectors[i], 0);


The processor state information needed to restore a task is saved in a system segment called the task-state segment (TSS). TSS (task state segment) is defined as struct Taskstate in JOS, and initialized in trap_init_percpu(void) :

    ts.ts_esp0 = KSTACKTOP;
    ts.ts_ss0 = GD_KD;
    gdt[GD_TSS0 >> 3] = SEG16(STS_T32A, (uint32_t) (&ts),
                    sizeof(struct Taskstate) - 1, 0);
    gdt[GD_TSS0 >> 3].sd_s = 0;

The reason to shift 3 bits right is that in the 16-bits Segment Selectors (CS, DS, ES, SS, FS, GS) the last 3 bits are used as Table Indicator (bit 2, GDT = 0, LDT = 1) and Requested Privilege Level (RPL) (bits 0 and 1). TSS descriptors may only be placed in the GDT; they cannot be placed in an LDT or the IDT. It is defined by a segment descriptor. Only one TSS is created for each CPU and they are used for all tasks. The segment selector for the TSS for the current task is stored in the task register (TR). With TR and GDT, it’s possible to locate TSS. When there is a task switch, the processor performs the following actions:

For each CPU which executes processes possibly wanting to do system calls via interrupts, one TSS is required. The only interesting fields are SS0 and ESP0. Whenever a system call occurs, the CPU gets the SS0 and ESP0-value in its TSS and assigns the stack-pointer to it.