关键词: 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
The kernel page directory recursively insert page directory in itself as a page table, to form a virtual page table at virtual address
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 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); lcr0(cr0);
JOS keeps track of physical memory with a
struct PageInfo array.
pages = boot_alloc(npages * sizeof(struct 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) ;
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
Environment context switch: an environment keeps the register states and the page directory.
The initial physical address represented by
PageInfo *pp is
The environments are stored in an array named
struct Env *envs with total capacity of
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
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
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
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 will call
trap, the task of dispatching of interrupt numbers to specific routines are actually carried out by
_alltraps, the registers and trap numbers are pushed into stack in the reverse order of which they are defined in
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: local descriptor-table (LDT) segment descriptor, task-state segment (TSS) descriptor
- gate descriptors: call-gate descriptor, interrupt-gate descriptor, trap-gate descriptor, task-gate descriptor
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
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
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; ltr(GD_TSS0);
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:
- Stores the state of the current task in the current TSS.
- Loads the task register TR with the segment selector for the new task
- Accesses the new TSS through a segment descriptor in the GDT.
- Loads the state of the new task from the new TSS into the general-purpose registers, the segment registers, the LDTR, control register CR3 (base address of the paging-structure hierarchy), the EFLAGS register, and the EIP register.
- Begins execution of the new task.
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.