Operating system Experiment Four experiment report _ Operating system

Source: Internet
Author: User
Tags prev
Experiment IV: Kernel thread management Exercise 1: Allocate and initialize a Process control block

First, take a look at some of the more important data structures, the process control blocks defined in kern/process/proc.h and the interrupt frames defined in Kern/trap/trap.h

struct Proc_struct {enum proc_state state;                                    Process State int PID;                                   Process ID int runs;                           The running times of Proces uintptr_t Kstack;                 Process Kernel stack volatile bool need_resched;
    BOOL Value:need to is rescheduled to release CPU?                 struct Proc_struct *parent;                       The parent process struct mm_struct *mm;                     Process ' s memory management field struct context;                       The Switch here to run process is the processes context struct trapframe *tf;                              The Trap frame for the current interrupt interrupts the context uintptr_t CR3;                             CR3 register:the Base addr of Page Directroy Table (PDT) uint32_t flags;               Process Flag char Name[proc_name_len + 1];       Process name list_entry_t List_link;              Process link list list_entry_t hash_link;
    Process Hash List} struct Trapframe {/* below here are a struct of general registers and defined by software * *                    
    When the interrupt anomaly occurs, the general register information in the structure is stored struct Pushregs tf_regs by the software.
    /* Below are segement registers and defined by software *///When interrupt exception occurs, the segment register information here is stored by the software responsible for the stack uint16_t tf_gs;
    uint16_t TF_PADDING0;
    uint16_t Tf_fs;
    uint16_t tf_padding1;                             
    uint16_t Tf_es;
    uint16_t tf_padding2;
    uint16_t Tf_ds;
    uint16_t Tf_padding3;
    uint32_t Tf_trapno;
    /* below defined by x86 hardware///When interrupt exception occurs, the information here is saved by hardware stack uint32_t tf_err;
    uintptr_t Tf_eip;                             
    uint16_t Tf_cs;
    uint16_t Tf_padding4;
    uint32_t tf_eflags; /* below here is crossing rings, such as from user to kernel, defined by hardware///only if the privilege level change occurs, the additional information here is guaranteed by the hardware pressure stack
 Save uintptr_t Tf_esp;   uint16_t Tf_ss;
uint16_t Tf_padding5;
    } struct Context {uint32_t eip;
    uint32_t esp;
    uint32_t ebx;
    uint32_t ecx;
    uint32_t edx;
    uint32_t ESI;
    uint32_t EDI;
uint32_t EBP; }

Here you can see that there are a lot of registers in the struct context and struct trapframe that are used for process contexts switching, which interrupts context switching. Note that the meaning of the two is not the same, in this experiment, a process begins to execute requires the system to initialize, when the TF is used to save the interrupt frame, and process execution is through the context to complete the switch, see detailed Practice 3 instructions.

Some important member variables in the structure describe the following mm memory management information, including memory map list, page table pointer, etc. state process, there are proc_uninit, proc_sleeping, proc_runnable, Proc_zombie four species, Defined in the enum proc_state parent process, in all processes only the kernel created by the first kernel thread Idleproc No parent process, the kernel based on the parent-child relationship to establish a tree structure to maintain some special operation context process, save registers, For process switching TF interrupt frames, always point to a location on the kernel stack, when the process breaks down, the interrupt frame records the state of the process before the interrupt, and as the user jumps to the kernel, the Ucore maintains the TF chain on the kernel stack, ensuring that the TF always points to the current Trapframe Kstack each thread has a kernel stack, and is located in different locations in the kernel address space, for kernel threads, the stack is the stack used by the Run-time program, and for ordinary processes, the stack is the one that needs to be saved when a privilege level change occurs.

In addition, in order to manage all the process control blocks in the system, Ucore maintains some important global variable static struct proc *current the process control block pointer that is currently occupied by the processor and in the running state is static list_entry_t Hash_list_size] a hash table of all process control blocks, Hash_link will access this hash table based on the PID chain

According to the test manual and Code prompts, the code for Exercise 1 is as follows

The initial initialization of the proc (that is, the proc_struct of each member of the variable clear 0). But some member variables set a special value

Alloc_proc-alloc a proc_struct and init all fields of proc_struct
static struct proc_struct *
alloc_proc (void ) {
    struct proc_struct *proc = kmalloc (sizeof (struct proc_struct));
    if (proc!= NULL) {
        proc->state = proc_uninit;                 Set process to initial state
        proc->pid =-1;                            Sets the uninitialized value of the process pid
        proc->runs = 0;
        Proc->kstack = 0;
        proc->need_resched = 0;
        Proc->parent = NULL;
        proc->mm = NULL;
        memset (& (Proc->context), 0, sizeof (struct context));
        PROC->TF = NULL;
        PROC->CR3 = BOOT_CR3;                      Use the base address of the Kernel page catalog table
        proc->flags = 0;
        memset (proc->name, 0, Proc_name_len + 1);
    }
    return proc;
}

Then Proc_init will further initialize IDLEPROC kernel thread Exercise 2: Allocate resources for newly created kernel threads

Do_fork is the primary function to create a thread, Kernel_thread to complete the creation of a kernel thread by calling the Do_fork function, the Do_fork function required in Exercise 2 completes 6 assignments and initializes the Process Control block (ALLOC_PROC) Allocate and initialize the kernel stack (setup_stack) put the set of process control blocks into hash according to the CLONE_FLAG flag copy or shared memory management structure (COPY_MM) setup processes needed for the kernel to run and schedule the interrupt frames and contexts (copy_thread) _list and proc_list the process state to the ready state in the two global process list.

/* Do_fork-parent process for a new child process * @clone_flags: Used to guide how to clone the child process * @ Stack:the Parent ' s user stack pointer.
 If stack==0, it means to fork a kernel thread. * @tf: The Trapframe info, which'll be copied to child process ' s PROC->TF/int do_fork (uint32_t clone_fla
    GS, uintptr_t stack, struct trapframe *tf) {int ret =-e_no_free_proc;
    struct Proc_struct *proc;
    if (nr_process >= max_process) {goto fork_out;
    ret =-e_no_mem; LAB4:EXERCISE2 YOUR CODE * * Some useful MACROs, functions and defines, can use them in below Implementati
     On.  * MACROs or functions: * alloc_proc:create a proc struct and init fields (LAB4:EXERCISE1) * Setup_kstack:  Alloc pages with size kstackpage as process kernel stack * copy_mm:process "proc" duplicate OR share process "Current" ' s mm according clone_flags * If Clone_flags & clone_VM, then "share";                 Else "Duplicate" * Copy_thread:setup the Trapframe on the process ' s kernel stack top and *      Setup the kernel entry point and stack of process * Hash_proc:add proc into proc hash_list * Get_pid: Alloc a unique PID for process * Wakeup_proc:set proc->state = proc_runnable * VARIABLES: * Pro C_list:the process Set ' s list * nr_process:the number of process set *///1. Call Alloc_proc to allocate a proc_struct//2. Call Setup_kstack to allocate a kernel stack for child process//3. Call copy_mm to DUP OR share mm according Clone_flag//4. Call Copy_thread to setup TF & context in Proc_struct//5. Insert proc_struct into hash_list && proc_list//6. Call Wakeup_proc to make the new child process RUNNABLE//7.      SET ret vaule using child proc ' s pid if (proc = Alloc_proc ()) = = NULL) {goto fork_out;                        Allocation failure, direct return} if (Setup_kstack (proc)!= 0) {goto bad_fork_cleanup_kstack;                 Stack initialization failed, freeing occupied space and returning} if (Copy_mm (Clone_flags, proc)!= 0) {goto bad_fork_cleanup_proc;                   Failed to copy or share the memory management structure, freeing occupied space and returning} copy_thread (proc, Stack, TF);                          Copy interrupt frame and Context proc->pid = Get_pid ();                                Distribution PID Hash_proc (proc);
    Add a new process to the hash table List_add (&proc_list, & (Proc->list_link));
    Nr_process + +;                              Wakeup_proc (proc);                                Wakeup process ret = proc->pid;

Returns the process PID Fork_out:return ret;
Bad_fork_cleanup_kstack:put_kstack (proc);
    Bad_fork_cleanup_proc:kfree (proc);
Goto Fork_out; }

There are more considerations in the reference answer, with different sections and descriptions as follows

    Proc->parent = current;                         Sets the parent process bool Intr_flag for the current process
    ;                                 Because you want to manipulate global data structures, and Ucore allows you to interrupt nesting, in order to avoid thread-safety problems, you need to take advantage of locks in kern/sync/sync.h, and thread-safety issues can refer to the relevant blog
    Local_intr_save (intr_ Flag);
    {
        proc->pid = Get_pid ();
        Hash_proc (proc);
        List_add (&proc_list, & (Proc->list_link));
        Nr_process + +;
    }
    Local_intr_restore (Intr_flag);
Exercise 3: Read the code to understand how the Proc_run function and the functions it calls complete process switching

Refer to the piazza of students on the one-step

https://piazza.com/class/i5j09fnsl7k5x0?cid=331

1. Kern_init calls the Proc_init function, which initiates the steps to create a kernel thread Idleproc = Alloc_proc () obtains the Kmalloc as the control block for the No. 0 process through the PROC_STRUCT function and initializes the PROC_ The INIT function further initializes the IDLEPROC kernel thread

idleproc->pid = 0;                                Set Pid=0, that is, the No. 0 kernel thread
idleproc->state = proc_runnable;                  Set the state to run and wait for the processor to schedule the process to execute
idleproc->kstack = (uintptr_t) bootstack;          The kernel stack of the Ucore boot is directly used as the kernel stack of the thread (later the kernel stack of other threads needs to be allocated)
idleproc->need_resched = 1;                       Set to be scheduled, according to the Cpu_idle function, as long as the idleproc is executing and can be scheduled, immediately perform the schedule function switch to other useful process execution
set_proc_name (idleproc, "idle");                  The thread is named idle
nr_process + +;                                    Number of threads +1 current
= Idleproc;                               Set current thread to Idleproc
Call PID = Kernel_thread (Init_main, "Hello world!!", 0) Create a kernel thread Init_main

2. The Kernel_thread function creates a temporary interrupt frame for the kernel thread and calls the Do_fork function to further produce a new kernel thread

//Kernel_thread-create a kernel thread using "FN" function//note:the contents of temp trapframe TF would be Copi Ed to//PROC->TF in do_fork-->copy_thread function int kernel_thread (int (*FN) (void *), void *arg, uint32_t                            clone_flags) {struct trapframe tf;       Temporary variable TF to save the interrupt frame and pass it to Do_fork memset (&TF, 0, sizeof (struct trapframe));                           TF Clear 0 Initialize tf.tf_cs = kernel_cs;     Set code snippet to kernel code snippet Kernel_cs tf.tf_ds = Tf.tf_es = Tf.tf_ss = Kernel_ds;
    Sets the data segment to the kernel data segment Kernel_ds TF.TF_REGS.REG_EBX = (uint32_t) fn;
    Tf.tf_regs.reg_edx = (uint32_t) arg;      Tf.tf_eip = (uint32_t) kernel_thread_entry; Set the entry for function kernel_thread_entry (defined in Kern/process/entry. s), this function is mainly for the function FN to prepare return Do_fork (Clone_flags | CLONE_VM, 0, &TF); The call Do_fork further completes the creation work, and the second parameter, 0, represents the kernel thread that was created. 
Kernel_thread_entry:        # void Kernel_thread (void)
    PUSHL%edx              # Push ARG pushes the parameters of FN call
    *%ebx              # call FN  invoke
    the FN PUSHL%eax              # Save the return value of FN (ARG) to press the FN back to stack call
    Do_exit # calls Do_exit to            terminate Current thread exit thread

3, Do_fork completed work in Exercise 2 has been fully explained, here in detail the most important copy_thread function

Copy_thread-setup the Trapframe on the  process ' s kernel stack top and
//             -Setup The kernel entry point a nd stack ' process
static void
copy_thread (struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) {
    P ROC->TF = (struct Trapframe *) (Proc->kstack + kstacksize)-1;  Proc->kstack point to the size of the kstackpage allocated to the process, and then to the highest address after the size, and then to force type conversion-1-the space for a trap frame size to be vacated at the top of the memory, used to copy incoming temporary frames
    * ( PROC->TF) = *TF;                                               Copy temporary frame
    proc->tf->tf_regs.reg_eax = 0;
    PROC->TF->TF_ESP = ESP;
    Proc->tf->tf_eflags |= fl_if;

    Proc->context.eip = (uintptr_t) Forkret;                          Set the instruction register to the Forkret function, the forkret
    proc->context.esp = (uintptr_t) (PROC->TF) is invoked after the child process is entered;                       Set the top of the stack to store the maximum address for the kernel stack after the TF, that is, the child process stack starts under the TF
}

Then go to Do_fork to complete all work, return to Kernel_thread and return to Proc_init and return to Kern_init

4, at this time in Kern_init follow-up has cpu_idle and schedule process scheduling

Cpu_idle-at the end of Kern_init, the kernel thread Idleproc'll do below works
void
cpu_idle (void) { C2/>while (1) {
        if (current->need_resched) {                                 //apparently at this time the current = Idleproc and has been set to need to be scheduled, so enter the schedule function dispatch
            Schedule ();}}

5. Schedule will return the previously created process and invoke Proc_run to run

Proc_run-make process "proc" running on CPU
//Note:before call switch_to, should load  base addr of "proc" ' s New PDT
void
proc_run (struct proc_struct *proc) {
    if (proc!= current) {
        bool Intr_flag;                                              Because you want to switch processes, also pay attention to thread safety issues, add lock
        struct proc_struct *prev = current, *next = proc;
        Local_intr_save (Intr_flag);
        {Current
            = proc;                                          Set the process to switch to the next running process
            load_esp0 (next->kstack + kstacksize);                    Modify the TSS task state stack to point the TSS ts_esp0 to the stack space of the next process, and to the comment
            LCR3 (NEXT->CR3) in the parameter value reference 3;                                         Modify the page table base address
            switch_to (& (Prev->context), & (Next->context));           Toggle, function defined in Kern\process\switch. S medium
        }
        Local_intr_restore (Intr_flag);
    }

6, Proc_run completes some preparation work, the call Switch_to finally completes the switch

High Address--------------------|arg1:next->context|
--------------------|arg0:prev->context|    --------------------|return Address | --->esp--------------------switch_to: # switch_to (from, to) # Save from ' registers MOV                L 4 (%ESP),%eax # eax points to from [esp+4] that is prev->context, put pointer to Prev->context in EAX 0 (POPL) # Save EIP!POPL The registers of the original process in the context order MOVL%esp, 4 (%eax) # Save Esp::context of from Movl%eb  X, 8 (%eax) # Save Ebx::context of From Movl%ecx, [%eax] # Save Ecx::context of from Movl%edx, %eax # Save Edx::context of From Movl%esi (%eax) # Save Esi::context The From Movl%edi, 2 4 (%EAX) # Save Edi::context of From Movl%ebp,%eax) # Save Ebp::context ' from high address-------------
-------|arg1:next->context|   --------------------|arg0:prev->context| --->esp--------------------# Restore to 'S Registers movl 4 (%ESP),%eax # not 8 (%ESP): popped return address already because the front has been popl, so this is not 8 but [esp+4] that next-         >context into EAX # EAX now points the registers of the new process in the context order MOVL (%EAX),%EBP         # Restore Ebp::context to MOVL (%eax),%edi # Restore Edi::context to MOVL (%eax),%esi         # Restore Esi::context to MOVL (%eax),%edx # Restore Edx::context to MOVL (%eax),%ecx          # Restore Ecx::context of to MOVL 8 (%eax),%EBX # Restore Ebx::context of To MOVL 4 (%eax),%esp
    # Restore Esp::context of to PUSHL 0 (%eax) # push EIP before the return address of the original process pops up, now the Next->context Eip is pushed into the stack Ret

Note that the new process is the Init_main created earlier, reference 3 can know at that time Proc->context.eip = (uintptr_t) Forkret, when the switch_to return, the contents of the top of the stack assigned to the EIP Register, Now jump to Forkret for execution

7, Forkret call forkrets complete Preparation and finally enter Init_main

Forkret--the ' the ' kernel entry point of a new thread/process
//note:the addr of Forkret was setted in Copy_thre Ad function
//After       switch_to, the current proc would execute here.
static void
Forkret (void) {
    forkrets (CURRENT->TF);
}

    # return falls through to Trapret ...
. Globl __trapret
__trapret:
    # Restore registers from stack
    popal

    # Restore%ds,%es,%fs and%gs
    p OPL%gs
    popl%fs popl%es popl%ds
    # get

    rid of the trap number and error code
    ADDL $0x8,%esp
  iret                                  

. Globl forkrets
forkrets:
    # Set Stack to this new process ' s trapframe
    movl 4 (%ESP),%ESP
   #esp指向当前进程的中断帧即esp指向current->tf
    jmp __trapret                                

Note that the reference 3 can know PROC->CONTEXT.ESP = (uintptr_t) (PROC->TF), and in 6 switch_to the final pressure into the Proc->context.eip, so in forkrets [esp+ 4] That point to Context.esp, this is the interrupt frame PROC->TF, reference stack content, struct trapframe and __trapret will understand the jump

Switch_to stacks are stored in memory:
High address
-------------
|context.ebp|
| ...........| 
-------------
|context.esp|   --->[esp+4] = proc->tf
-------------
|context.eip|   --->esp

struct trapframe {
    struct pushregs tf_regs;   Universal register, corresponding to __trapret:popal
    uint16_t Tf_gs;            corresponding to POPL%gs
    uint16_t tf_padding0;
    uint16_t Tf_fs;            corresponding to POPL%fs
    uint16_t tf_padding1;
    uint16_t Tf_es;            corresponding to POPL%es
    uint16_t tf_padding2;
    uint16_t Tf_ds;            corresponding to POPL%ds
    uint16_t tf_padding3;
    uint32_t Tf_trapno;        [ESP] this time ESP points here
    /* below here defined by x86 hardware * * uint32_t
    tf_err;           [Esp+4]
    uintptr_t tf_eip;          [Esp+8] corresponds to Addl $0x8,%esp
    uint16_t tf_cs;
    ...
};

When Iret returns, it enters the PROC->TF.TF_EIP that the ESP points to at this time, and in 2 this value is initialized to Kernel_thread_entry

. Text
. Globl kernel_thread_entry
kernel_thread_entry:        # void Kernel_thread (void)

    PUSHL%edx              # Push arg call
    *%ebx              # call fn

    PUSHL%eax              # save ' return value ' fn (ARG) call
    do_exit            # call D O_exit to terminate current thread

This enters the Init_main and calls Do_exit to complete all processes when returned

Use make QEMU to see the results after the code is complete and the program is correct

 (THU. CST) OS is loading ...
Omit partial output check_alloc_page () succeeded!
Check_pgdir () succeeded!
Check_boot_pgdir () succeeded! ...
Omit partial output kmalloc_init () succeeded!
Check_vma_struct () succeeded! ...
Omit partial output check_pgfault () succeeded!
CHECK_VMM () succeeded. ...
Omit partial output check_swap () succeeded!
+ + Setup Timer interrupts this initproc, PID = 1, name = "Init" to U: "Hello world!!".

To U: "En ..., Bye, Bye.:)" Kernel Panic at kern/process/proc.c:344:process exit!!.
Welcome to the kernel debug monitor!! Type ' help ' for a list of commands. 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.