当前位置:网站首页>UCORE lab5 user process management experiment report

UCORE lab5 user process management experiment report

2022-07-06 15:14:00 Wuhu hanjinlun

The experiment purpose

  • Understand the first user process creation process
  • Understand the implementation mechanism of the system call framework
  • understand ucore How to implement system call sys_fork/sys_exec/sys_exit/sys_wait For process management

Experimental content

experiment 4 Completed the kernel thread , But so far , All operations are executed in kernel mode . experiment 5 The user process... Will be created , Let the user process execute in user mode , And when necessary ucore When supported , You can use system calls to make ucore Provide services . To do this, you need to construct the first user process , And through system call sys_fork/sys_exec/sys_exit/sys_wait To support running different applications , Complete the basic management of the execution process of the user process . See the appendix for the introduction of relevant principles B.

practice

practice 0: Fill in the existing experiment

This experiment relies on experiments 1/2/3/4. Please take your experiment 1/2/3/4 Fill in the code in this experiment. There are “LAB1”/“LAB2”/“LAB3”/“LAB4” The corresponding part of the notes . Be careful : In order to execute correctly lab5 Test application for , It may be necessary to test the completed experiments 1/2/3/4 Code for further improvements .

experiment 0 Mainly before 4 The code of this experiment is added , Include

  • kdebug.c
  • trap.c
  • default_pmm.c
  • pmm.c
  • swap_fifo.c
  • vmm.c
  • proc.c

The relevant codes of seven documents . In addition to direct supplement , There are some places where the code needs to be modify perhaps improvement

It is suggested that you can directly copy answer The file of

alloc_proc function :

stay lab4 On the basis of , Added wait_state ,proc->cptr , proc->optr , proc->yptr Four variables , In fact, we only need take wait_state Initialize to 0, The three pointers are initialized to NULL that will do . Avoid errors when managing user processes due to undefined or uninitialized processes .

static struct proc_struct *
alloc_proc(void) {
    
    struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
    if (proc != NULL) {
    
        // .....
        // The following two sentences are new 
        proc->wait_state = 0;
        proc->cptr = proc->optr = proc->yptr = NULL;
    }
    return proc;
}

These two lines of code are mainly about the waiting state of the initialization process 、 And process related pointers , For example, the parent process 、 Subprocesses 、 Compatriots, etc . Among them wait_state Is a new entry in the process control block .

Because this involves user processes , Naturally, scheduling problems need to be involved , So the process waiting state and various pointers need to be initialized .

do_fork function

Add a check on the waiting state of the current process , And the use of set_links Function to set the relationship between processes .

The codes added and deleted are :

    proc->parent = current;
    // The following sentence is the added code 
    assert(current->wait_state == 0);
    if (setup_kstack(proc) != 0) {
    
        goto bad_fork_cleanup_proc;
    }
    if (copy_mm(clone_flags, proc) != 0) {
    
        goto bad_fork_cleanup_kstack;
    }
    copy_thread(proc, stack, tf);

    bool intr_flag;
    local_intr_save(intr_flag);
    {
    
        proc->pid = get_pid();
        hash_proc(proc);
        //list_add(&proc_list, &(proc->list_link));
        //nr_process ++; These two sentences are lab4 To be deleted from 
        // The following sentence is to be added 
        set_links(proc);
    }
    local_intr_restore(intr_flag);

trap_dispatch function :

Set every 100 After time interruption , The currently executing process is ready to be scheduled . meanwhile , Comment out the original "100ticks" Output

ticks ++;
        if (ticks % TICK_NUM == 0) {
    
            assert(current != NULL);
            current->need_resched = 1;
            // In the original code print_ticks(); Change to the above two sentences 
        }
        break;

idt_init function

Set the interrupt gate of a specific interrupt number , Dedicated for user process access to system calls , That is, set interrupt T_SYSCALL The trigger privilege level of is DPL_USER

oid idt_init(void) {
    
    // ......
    //  Add the following sentence 
    SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);
    // ......
}

practice 1: Load the application and execute ( Need to code )

do_execve Function call load_icode( be located kern/process/proc.c in ) To load and parse a in memory ELF Execute file format applications , Set up the corresponding user memory space to place the code segments of the application 、 Data segments, etc , And it should be set proc_struct Member variables in the structure trapframe The content in , Make sure that after executing this process , It can be executed from the starting address set by the application . Set the correct trapframe Content .

Please briefly describe your design and implementation process in the experimental report .

Please describe in the experiment report when you create a user mode process and load the application ,CPU How to make this application finally execute in user mode . That is, the user state process is ucore Select occupancy CPU perform (RUNNING state ) The whole process of implementing the first instruction of the application program .

do_execve function

do_execve Function call load_icode To load and parse a in memory ELF Execute file format applications

// The main purpose is to clean up the memory space of the original process , Prepare space and resources for the implementation of new processes 
int do_execve(const char *name, size_t len, unsigned char *binary, size_t size) 
{
    
    struct mm_struct *mm = current->mm;
    if (!user_mem_check(mm, (uintptr_t)name, len, 0)) {
    
        return -E_INVAL;
    }
    if (len > PROC_NAME_LEN) {
    
        len = PROC_NAME_LEN;
    }

    char local_name[PROC_NAME_LEN + 1];
    memset(local_name, 0, sizeof(local_name));
    memcpy(local_name, name, len);
// If mm Not for NULL, Then do not execute the process 
    if (mm != NULL) 
    {
    
        // take cr3 The base address of the page table points to boot_cr3, That is, the kernel page table 
        lcr3(boot_cr3);// Switch to kernel mode 
        if (mm_count_dec(mm) == 0) 
        {
      
            // The next three steps are to empty the memory management area of the process 
            exit_mmap(mm);// Empty the memory management part and the corresponding page table 
            put_pgdir(mm);// Empty the page table 
            mm_destroy(mm);// Clear Memory 
        }
        current->mm = NULL;// Finally, let its current page table point to null , It's convenient to put your own things 
    }
    int ret;
    // Fill in the new content ,load_icode The execution program will be loaded , Create a new memory mapping relationship , So as to complete the new implementation 
    if ((ret = load_icode(binary, size)) != 0) {
    
        goto execve_exit;
    }
    // Give the process a new name 
    set_proc_name(current, local_name);
    return 0;

execve_exit:
    do_exit(ret);
    panic("already exit: %e.\n", ret);
}

do_execve The main work of function is to Reclaim the user space occupied by itself , And then call load_icode, Cover the memory space with a new program , Form a new process to execute the new program .

Let's analyze load_icode function

load_icode function

load_icode The main job of the function is to give the user process Create a user environment that allows user processes to run normally . This function has more than a hundred lines , The following important work has been completed :

  1. call mm_create Function to apply for the memory management data structure of the process mm Required memory space , Also on mm Conduct initialization ;
  2. call setup_pgdir To apply for a page size memory space required for a page directory table , And describe ucore Kernel virtual space mapping Kernel page table (boot_pgdir To refer to ) The content of Copy In this new catalog table , Finally let mm->pgdir Point to the table of contents on this page , This is the new page directory table of the process , And can correctly map the kernel virtual space ;
  3. This code is parsed according to the starting position of the application execution code ELF Format execution program , And call mm_map Functions are based on ELF The format of the execution program describes the various paragraphs ( Code segment 、 Data segment 、BSS Duan et al ) The starting position and size of the corresponding vma structure , And put vma Insert into mm In structure , Thus, it shows the legal user state virtual address space of user process ;
  4. The call depends on the size of each segment of the executing program Allocate physical memory space , And according to the starting position of each segment of the execution program Determine the virtual address , And establish the mapping relationship between physical address and virtual address in the page table , Then copy the contents of each segment of the execution program to the corresponding kernel virtual address , So far, the application execution code and data have been placed in the virtual memory according to the address set at compile time ;
  5. You need to set the user stack for the user process , Call... For this mm_mmap Function to build the user stack vma structure , Make it clear that the position of the user stack is at the top of the user virtual space , The size is 256 A page , namely 1MB, Allocate a certain amount of physical memory and establish the virtual address of the stack <------> Physical address mapping relationship ;
  6. thus , In process memory management vma and mm The data structure has been established , So the mm->pgdir Assign values to the cr3 In the register , That is, the virtual memory space of the user process is updated , At this time initproc Has been hello Code and data coverage , Became the first user process , But at this time, the execution site of the user process has not been established ;
  7. First Clear the interrupt frame of the process , Again Reset the interrupt frame of the process , Make the interrupt return instruction “iret” after , To be able to make CPU Go to the user state privilege level , And return to the user state memory space , Code snippets using user mode 、 Segments and stacks , And can jump to the first instruction execution of the user process , And ensure that the interrupt can be responded to in the user state ;

The specific implementation part is as follows :

/* load_icode -  Load the contents of the binary program (ELF Format ) As a new content of the current process  1. @binary:  Memory address of binary program contents  2. @size:  Size of binary program contents  */
static int
load_icode(unsigned char *binary, size_t size) {
    
...
     /* tf_cs Set to user mode  tf_ds=tf_es=tf_ss= User mode  tf_esp Set as the top of the user stack  tf_eip Set as the entry of binary program  tf_eflags Set to open interrupt  */
    // below 6 The sentence is the part supplemented according to the notes 
    tf->tf_cs = USER_CS;// take tf_cs Set to user mode 
    tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;//tf_ds=tf_es=tf_ss It also needs to be set to user mode 
    tf->tf_esp = USTACKTOP;// Need to put esp Set as the top of the user stack , Directly use the parameters when creating the user stack before USTACKTOP Can .
    tf->tf_eip = elf->e_entry;//eip It's the entrance to the program ,elf Class e_entry Function directly declares , Use it directly .
    tf->tf_eflags = FL_IF;//FL_IF Turn on interrupt 
    ret = 0;
...
}

Question answering

Please describe in the experiment report when you create a user mode process and load the application ,CPU How to make this application finally execute in user mode . That is, the user state process is ucore Select occupancy CPU perform (RUNNING state ) The whole process of implementing the first instruction of the application program .

  1. call schedule function , The scheduler occupies CPU After all the resources , User mode process called exec system call , Thus, it goes to the processing routine of the system call
  2. After that, the normal interrupt processing routine , Then control shifted to syscall.c Medium syscall function , Then it is transferred to according to the system call number sys_exec function , Called in this function do_execve Function to complete the loading of the specified application
  3. stay do_execve Several settings are made in , Including the page table of the current process , Switch to the kernel PDT, call load_icode Function to initialize the memory space of the entire user thread , Including stack settings and adding ELF Loading of executables , After through current->tf The pointer modifies the current system call trapframe, This enables the user state to be switched when the final interrupt returns , And at the same time, it can correctly transfer control to the entrance of the application
  4. At the completion of the do_exec After the function , The process of normal interrupt return , Because the interrupt processing routine is on the stack eip It has been modified to be the entrance of the application , and CS Upper CPL It's user mode , therefore iret When the interrupt returns, the stack will be switched to the user's stack , And complete the privilege level switch , And jump to the entrance of the required application
  5. Start executing the first code of the application

practice 2: The parent process copies its own memory space to the child process ( Need to code )

The function that creates the child process do_fork The current process will be copied in execution ( The parent process ) Of the user memory address space to the new process ( Subprocesses ), Finish copying memory resources . Specifically through copy_range function ( be located kern/mm/pmm.c in ) Realized , Please add copy_range The implementation of the , Ensure that... Can be performed correctly .

Please briefly explain how to design and implement in the experiment report ”Copy on Write Mechanism “, Give the outline design , Encourage detailed design .

Copy-on-write( abbreviation COW) The basic concept of is that if there are multiple users for a resource A( Such as memory block ) Read it , Then each user only needs to get one pointing to the same resource A The pointer to , You can use this resource . If a user needs to access this resource A Write operation , The system will copy the resource , So that the “ Write operations ” The user gets a copy of the resource A Of “ private ” Copy — resources B, Resources can be B Write operation . The “ Write operations ” Users are interested in resources B Changes to the are invisible to other users , Because other users still see resources A.

We first analyze the parent process call fork The system calls the process of generating sub processes :

  1. Parent process call fork system call , Enter the normal interrupt processing mechanism , It's up to syscall Function to process
  2. stay syscall Function , According to the system call , Leave it to sys_fork Function processing
  3. This function further calls do_fork function , This function is mainly used to create sub processes 、 And copy the memory space of the parent process to the logic of the child process
  4. stay do_fork Function , call copy_mm Copy memory space , In this function , Called further dup_mmap, In this function , Traversed all the legal virtual memory space of the parent process , And copy the contents of these spaces into the memory space of the child process , The specific function of memory replication is what we need to improve in this exercise copy_range
  5. stay copy_range Function , Copy the memory space to be copied from the memory space of the parent process to the memory space of the child process in page units

The conclusion is :copy_range Calling procedure of function :do_fork()---->copy_mm()---->dup_mmap()---->copy_range()

(1)do_fork() function

if (copy_mm(clone_flags, proc) != 0) {
    //3. call copy_mm() Function copies the memory information of the parent process to the child process 
        goto bad_fork_cleanup_kstack;

do_fork Function called copy_mm function , Create a process , And put in CPU Medium scheduling , This time we mainly focus on how to copy memory between parent and child processes .

(2)copy_mm() function

 lock_mm(oldmm);// Open the mutex , Avoid multiple processes accessing memory at the same time 
    {
    
        ret = dup_mmap(mm, oldmm);// call dup_mmap function 
    }
    unlock_mm(oldmm);// Release the mutex 

use The mutex , Used to avoid multiple processes accessing memory at the same time , Here is the next level call : Called dup_mmap() function .

(3)dup_mmap() function

bool share = 0;
        if (copy_range(to->pgdir, from->pgdir, vma->vm_start, vma->vm_end, share) != 0) {
    // call copy_range function 
            return -E_NO_MEM;
        }

Let's first look at the parameters passed in , It's two memories mm, In the last function copy_mm in , The two memories passed in are called mm and oldmm, among , first mm Just called mm_create() Statement , But not initialized , There is no distribution ; the second oldmm yes current Memory space of the process , thus it can be seen , Previous mm Is the memory to be copied , The copied source content is oldmm( The parent process ) In content .

(4) Realization copy_range function

copy_range The specific execution process of the function is as follows : Traverse Every virtual page in a certain memory space specified by the parent process , If this virtual page exists , The same address corresponding to the child process ( But the page table of contents is different , So it's not a memory space ) also Apply to allocate a physical page , Then put all the contents in the former Copy Go to the latter , Then the physical page and the corresponding virtual address of the child process ( It's actually a linear address ) Establish a mapping relationship ; In this exercise, you need to complete the copy of memory and the establishment of mapping , The specific process is as follows :

  1. Find the kernel virtual address corresponding to a physical page specified by the parent process
  2. Find the virtual address of the kernel corresponding to the corresponding physical page of the child process that needs to be copied
  3. Copy the contents of the former into the latter
  4. This physical page is currently allocated to the child process mapping A virtual page in the virtual address space of the child process corresponding to

The complete code is :

// Move the actual code segment and data segment into the new sub process , Then set the relevant contents of the page table 
int
copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) {
    
    // Make sure start and end Divisibility PGSIZE
   	assert(start % PGSIZE == 0 && end % PGSIZE == 0);
    assert(USER_ACCESS(start, end));
    // Copy in pages 
    do {
    
     // obtain A&B Of pte Address 
        pte_t *ptep = get_pte(from, start, 0), *nptep;
        if (ptep == NULL) 
        {
    
            start = ROUNDDOWN(start + PTSIZE, PTSIZE);
            continue ;
        }

        if (*ptep & PTE_P) {
    
            if ((nptep = get_pte(to, start, 1)) == NULL) {
    
                return -E_NO_MEM;
            }
        uint32_t perm = (*ptep & PTE_USER);
        //get page from ptep
        struct Page *page = pte2page(*ptep);
        // by B One page space 
        struct Page *npage=alloc_page();
        assert(page!=NULL);
        assert(npage!=NULL);
        int ret=0;
       // The following four sentences of code are the implementation part of this exercise 
       //1. Find the kernel virtual page address of the parent process 
        void * kva_src = page2kva(page);
       //2. Find the kernel virtual page address of the child process  
        void * kva_dst = page2kva(npage);
        //3. Copy the contents of the parent process to the child process  
        memcpy(kva_dst, kva_src, PGSIZE);
       //4. Establish the mapping relationship between the physical address and the starting position of the page address of the child process 
        ret = page_insert(to, npage, start, perm);
        assert(ret == 0);
        }
        start += PGSIZE;
    } while (start != 0 && start < end);
    return 0;
}

practice 2 The problem is challenge Middle answer

practice 3: Read and analyze the source code , Understand process execution fork/exec/wait/exit The implementation of the , And the implementation of system call ( No coding required )

Please briefly explain your understanding of fork/exec/wait/exit Analysis of functions . And answer the following questions :

  • Please analyze fork/exec/wait/exit How the implementation affects the execution state of the process ?
  • Please give me ucore Execution state lifecycle diagram of a user state process in ( Package execution status , Execute the transformation relationship between states , And the event or function call that produces the transformation ).( Characters can be drawn )

fork

Complete the copy of the process , from do_fork Function completion , The main process is as follows :

  1. First, check whether the current total number of processes has reached the limit , If the limit is reached , Then the return E_NO_FREE_PROC
  2. Allocate and initialize process control blocks (alloc_proc function )
  3. Allocate and initialize the kernel stack (setup_stack function )
  4. according to clone_flag Flag copy or share process memory management structure (copy_mm function )
  5. Set the process in the kernel ( In the future, it also includes user mode ) Interrupt frames and execution context required for normal operation and scheduling (copy_thread function )
  6. Put the set process control block into hash_list and proc_list In the linked list of two global processes
  7. Since then , The process is ready to execute , Set the process status to “ be ready ” state
  8. Set the return code to that of the child process id Number
    int ret = -E_NO_FREE_PROC;//1. First, check whether the current total number of processes has reached the limit 
if ((proc = alloc_proc()) == NULL) {
    //2. call alloc_proc() Function request memory block 
        goto fork_out;
    }

    proc->parent = current;// Set the parent node of the child process as the current process 
    assert(current->wait_state == 0);// Make sure that the current process is waiting 
    if (setup_kstack(proc) != 0) {
    //3. call setup_stack() Function to allocate a kernel stack for the process 
        goto bad_fork_cleanup_proc;
    }
    if (copy_mm(clone_flags, proc) != 0) {
    //4. call copy_mm() Function copies the memory information of the parent process to the child process 
        goto bad_fork_cleanup_kstack;
    }
    copy_thread(proc, stack, tf);//5. call copy_thread() Function copies the interrupt frame and context information of the parent process 
    //6. Add a new process to the process's (hash) In the list 
    bool intr_flag;
    local_intr_save(intr_flag);// Mask interrupt ,intr_flag Set as 1
    {
    
        proc->pid = get_pid();// Get the current process PID
        hash_proc(proc); // establish hash mapping 
        set_links(proc);// Set the process link 
    }
    local_intr_restore(intr_flag);// Resume interrupt 

    wakeup_proc(proc);//7. Wake up a new process 

    ret = proc->pid;//8. Returns the current process's PID

exec

Complete the creation of user process , At the same time, it enables the user process to enter the execution . from do_exec Function completion , The main process is as follows :

  1. Check whether the address and length of the process name are legal , If the legitimate , Then save the name temporarily in the function stack , Otherwise return to E_INVAL;
  2. take cr3 The page table base address points to the kernel page table , Then realize the memory management area of the process Release ;
  3. call load_icode Load the code into memory and establish a new memory mapping relationship , If loading error , So called panic Report errors ;
  4. call set_proc_name Reset the process name .

The complete code is practicing 1 It's done

wait

Complete the recycling of the memory space occupied by the kernel stack and process control block of the sub process . from do_wait Function completion , The main process is as follows :

  • First check the code_store The pointer address is within the legal range
  • according to PID Find the child process that needs to wait PCB, Loop to ask the status of the waiting child process , Until the state of a child process changes to ZOMBIE
    • If there is no child process to wait , Then the return E_BAD_PROC
    • If the child process is in an executable state , Then sleep the current process , Try again after being awakened
    • If the child process is in an executable state , Then sleep the current process , Try again after being awakened
if (pid != 0) {
    // If pid!=0, Then find the process id by pid Child process in exit state of  
        proc = find_proc(pid);
        if (proc != NULL && proc->parent == current) {
    
            haskid = 1;
            if (proc->state == PROC_ZOMBIE) {
    
                goto found;// Find the process 
            }
        }
    }
    else {
    // If pid==0, Then randomly find a child process in the exit state 
        proc = current->cptr;
        for (; proc != NULL; proc = proc->optr) {
    
            haskid = 1;
// If the child process is not in zombie state , Then it will become sleep , Because you need to wait for the subprocess to exit , Then call schedule Function suspends itself , Select another process to execute . If it is zombie , Then the process will be cleared .
            if (proc->state == PROC_ZOMBIE) {
    
                goto found;
            }
        }
    }
    if (haskid) {
    // If not , Then the parent process goes back to sleep , And repeat the search process 
        current->state = PROC_SLEEPING;
        current->wait_state = WT_CHILD;
        schedule();
        if (current->flags & PF_EXITING) {
    
            do_exit(-E_KILLED);
        }
        goto repeat;
    }
    return -E_BAD_PROC

exit

Complete some resource recycling during the exit process of the current process . from do_exit Function completion , The main process is as follows :

  1. Release Virtual memory space of the process
  2. Set the current process status to PROC_ZOMBIE That is, marked as a zombie process
  3. If the parent process is waiting for the current process to exit , Then wake up the parent process
  4. If the current process has child processes , Set the subprocess to initproc Can be inherited by child processes. , And complete the final recycling of the process in the zombie state in the sub process
  5. Active call scheduling function To schedule , Choose a new process to execute

The code is as follows :

do_exit(int error_code) {
    
    if (current == idleproc) {
    
        panic("idleproc exit.\n");
    }
    if (current == initproc) {
    
        panic("initproc exit.\n");
    }
    
    struct mm_struct *mm = current->mm;
    if (mm != NULL) {
    // If the process is a user process , Ready to reclaim memory , First of all, it should not be empty 
        lcr3(boot_cr3);// Switch to the page table in kernel mode , Switch from user mode to kernel mode 
        if (mm_count_dec(mm) == 0) {
    
            exit_mmap(mm);

            put_pgdir(mm);// Free the memory occupied by the page directory  
            mm_destroy(mm);// Release mm Memory footprint , Recycle page directory 、 Free memory 
        }
        current->mm = NULL;// Finally, point its memory address to null , Complete memory recycling 
    }
    current->state = PROC_ZOMBIE;// Set zombie status , Wait for the parent process to recycle 
    current->exit_code = error_code;// Wait for the parent process to do the final recycling 
    
    bool intr_flag;
    struct proc_struct *proc;
    local_intr_save(intr_flag);
    {
    
        proc = current->parent;
        if (proc->wait_state == WT_CHILD) {
    
            wakeup_proc(proc);// If the parent process is waiting for the child process , Then wake up 
        }
        while (current->cptr != NULL) {
    
/* If the current process has child processes ( Orphan process ), You need to set the parent process pointer of these child processes to the kernel thread initproc, And each sub process pointer needs to be inserted   To initproc In the linked list of child processes . If the execution state of a child process is PROC_ZOMBIE, You need to wake up initproc To complete the final recycling of this sub process .*/
            proc = current->cptr;
            current->cptr = proc->optr;
    
            proc->yptr = NULL;
            if ((proc->optr = initproc->cptr) != NULL) {
    
                initproc->cptr->yptr = proc;
            }
            proc->parent = initproc;
            initproc->cptr = proc;
            if (proc->state == PROC_ZOMBIE) {
    
                if (initproc->wait_state == WT_CHILD) {
    
                    wakeup_proc(initproc);
                }
            }
        }
    }
    local_intr_restore(intr_flag);
    
    schedule();// Select a new process to execute 
    panic("do_exit will not return!! %d.\n", current->pid);
}

syscall system call

The definition of system call is mainly in syscall.c in , Many system call functions are defined here , Include sys_exitsys_forksys_waitsys_exec etc. .syscall Is a way for kernel programs to provide kernel services for user programs .

In the interrupt handling routine , The program will , perform syscall function ( Pay attention to syscall Functions are kernel code , Not in the user library syscall function ). kernel syscall The function retrieves the values of the six registers one by one , And execute different system calls according to the system call number . The essence of those system calls is that of other kernel functions wrapper. The following is a syscall Function implementation code :

COPYvoid syscall(void) {
    
    struct trapframe *tf = current->tf;
    uint32_t arg[5];
    int num = tf->tf_regs.reg_eax;
    if (num >= 0 && num < NUM_SYSCALLS) {
    
        if (syscalls[num] != NULL) {
    
            arg[0] = tf->tf_regs.reg_edx;
            arg[1] = tf->tf_regs.reg_ecx;
            arg[2] = tf->tf_regs.reg_ebx;
            arg[3] = tf->tf_regs.reg_edi;
            arg[4] = tf->tf_regs.reg_esi;
            tf->tf_regs.reg_eax = syscalls[num](arg);
            return ;
        }
    }
    print_trapframe(tf);
    panic("undefined syscall %d, pid = %d, name = %s.\n",
            num, current->pid, current->name);
}

After the corresponding kernel functions are completed , Reserved before the program passes trapframe Return to user status . A system call ends .

Question answering

  • Please analyze fork/exec/wait/exit How the implementation affects the execution state of the process ?
  1. fork A new sub thread will be created , Change the status of the child thread from UNINIT The state becomes RUNNABLE state , Do not change the state of the parent process
  2. exec Complete the creation of user process , At the same time, it enables the user process to enter the execution , Do not change the process state
  3. wait Complete subprocess resource recycling , If there are finished subprocesses or no subprocesses , Then the call will end immediately , It does not affect the process state ; otherwise , The process needs to wait for the child process to end , Process from RUNNIG The state becomes SLEEPING state .
  4. exit Complete the recycling of resources , Process from RUNNIG The state becomes ZOMBIE state .
  • Please give me ucore Execution state lifecycle diagram of a user state process in ( Package execution status , Execute the transformation relationship between states , And the event or function call that produces the transformation ).( Characters can be drawn )

 Insert picture description here
It should be noted that , from RUNNABLE To RUNNING when , The process is proc_run Function called as an argument , from RUNNING To RUNABLE when , It is the process that actively calls proc_run function

This completes the exercise , function make qemu View the run results :
 Insert picture description here
function make grade Check your grades
 Insert picture description here

Extended exercises Challenge : Realization Copy on Write (COW) Mechanism

Give the implementation source code , Test cases and design reports ( Included in cow Various state transitions in case ( Similar to finite state automata ) Explanation ).

This extended exercise involves this experiment and the previous experiment “ Virtual memory management ”. stay ucore Operating system , When a user parent process creates its own child process , The parent process will set its requested user space to read-only , Child processes can share pages in the user memory space occupied by the parent process ( This is a shared resource ). When any one of these processes modifies a page in this user's memory space ,ucore Will pass page fault The exception learns the operation , And finish copying memory pages , So that both processes have their own memory pages . Changes made by such a process are not visible to another process . Please be there. ucore To achieve such COW Mechanism .

because COW The implementation is more complex , Easy to introduce bug, Please refer to https://dirtycow.ninja/ See if you can ucore Of COW Simulate this error and solution in the implementation . Need an explanation .

This is a big challenge.

First ,Copy on Write When copying an object, it is not really copying the original object to another location in memory , Instead, set a in the memory mapping table of the new object The pointer , Point to the location of the source object , And put that piece of memory Copy-On-Write Bit is set to 1. Generally speaking, the benefits of doing this : If the copied object is only for the content " read " operation , It doesn't really need to be copied , This pointer to the source object can complete the task , This saves replication time and memory . But the problem is , If the copied object needs to write the content , A single pointer may not meet the requirements , Because such changes to the content will affect the correct execution of other processes , So we need to copy this area , Of course, you don't need to copy all , It only needs Copy some areas that need to be modified , This greatly saves memory and improves efficiency .

Because if you set the original content to read only , When writing this paragraph, it will cause Page Fault, At this time, we know that this paragraph needs to be written , stay Page Fault You can handle it accordingly . That is to say, using Page Fault To realize the judgment of authority , Or it's a sign of true replication .

Based on the principle and previous user process creation 、 Copy 、 Operation and other mechanisms , design idea :

  1. Set a marker bit , Used to mark whether a piece of memory is shared , actually dup_mmap There is a pair of... In the function share Set up , So first we need to share Set to 1, Can be said share .
  2. stay pmm.c In Chinese, it means copy_range Add processing for shared pages , If share by 1, Then map the page of the child process to the page of the parent process . Because after two processes share a page , No matter any process modifies the page , Will affect another page , Therefore, both the child process and the parent process need to maintain the shared page read-only .
  3. When the program tries to modify a read-only memory page , Will trigger Page Fault interrupt , At this time, we can detect that the interruption is caused by access beyond permission , It indicates that the process accesses the shared page and needs to be modified , Therefore, the kernel needs to reallocate pages for the process at this time 、 Copy page content 、 Establish a mapping relationship

Based on this idea , Modify the code

First , take vmm.c Medium dup_mmap Function squadron share Modify the setting of variables , because dup_mmap The function is called copy_range function ,copy_range The function has an argument of share, So modify share by 1 It marks the start of the sharing mechanism .

int dup_mmap(struct mm_struct *to, struct mm_struct *from) {
    
		...
        bool share = 1;
		if (copy_range(to->pgdir, from->pgdir, vma->vm_start, vma->vm_end, share)!= 0) 			{
    
            return -E_NO_MEM;
         }
        ...
}

after , stay pmm.c In Chinese, it means copy_range Add processing for shared pages , If share by 1, Then map the page of the child process to the page of the parent process . Because after two processes share a page , No matter any process modifies the page , Will affect another page , Therefore, both the child process and the parent process need to maintain the shared page read-only .

// Make changes here , Make the child process share a page with the parent process , But keep both read-only 
int
copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) {
    
    assert(start % PGSIZE == 0 && end % PGSIZE == 0);
    assert(USER_ACCESS(start, end));
    do {
    
        //call get_pte to find process A's pte according to the addr start
        pte_t *ptep = get_pte(from, start, 0), *nptep;
        if (ptep == NULL) {
    
            start = ROUNDDOWN(start + PTSIZE, PTSIZE);
            continue ;
        }
   if (*ptep & PTE_P) {
    
            if ((nptep = get_pte(to, start, 1)) == NULL) {
    
                return -E_NO_MEM;
            }
            uint32_t perm = (*ptep & PTE_USER);
            //get page from ptep
            struct Page *page = pte2page(*ptep);
            assert(page!=NULL);
  //----------------- Modification part ---------------------
            int ret=0;
            // Due to the previous setting of sharable , Therefore, we will continue to implement if sentence 
            if (share) {
    	
              	//  If share=1, Complete page sharing 
                page_insert(from, page, start, perm & (~PTE_W));
                ret = page_insert(to, page, start, perm & (~PTE_W));
            } else {
    
                // If you don't share   Just allocate the page normally , Consistent with the previous implementation 
                struct Page *npage=alloc_page();
                assert(npage!=NULL);
                uintptr_t src_kvaddr = page2kva(page);
                uintptr_t dst_kvaddr = page2kva(npage);
                memcpy(dst_kvaddr, src_kvaddr, PGSIZE);
                ret = page_insert(to, npage, start, perm);
            }
            assert(ret == 0);
        }
//----------------- Modification part ---------------------
        start += PGSIZE;
    } while (start != 0 && start < end);
    return 0;
}

Finish here , The sharing of reading has been realized , But there is no treatment for writing , Therefore, we need to deal with page errors caused by writing pages that can only be read : When the program tries to modify a read-only memory page , Will trigger Page Fault interrupt , At this time, we can detect that the interruption is caused by access beyond permission , It indicates that the process accesses the shared page and needs to be modified , Therefore, the kernel needs to reallocate pages for the process at this time 、 Copy page content 、 Establish a mapping relationship .

These steps are mainly do_pgfault in , We can detect the error and deal with it accordingly .

  int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr)
  {
    
     .....
   
    if (*ptep == 0)
 	{
     // If the physical page does not exist , Allocate physical pages and establish relevant mapping relationships 
        if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) 
        {
    
            cprintf("pgdir_alloc_page in do_pgfault failed\n");
            goto failed;
        }
    } 
     // adopt error_code & 3==3 Judge to be COW The resulting error 
    else if (error_code & 3 == 3)
    {
    	
        // Therefore, we need to complete the allocation of physical pages , And realize the replication of code and data 
        // actually , We will copy_range The process is executed here , Execute the process only when it must be executed 
        struct Page *page = pte2page(*ptep);
        struct Page *npage = pgdir_alloc_page(mm->pgdir, addr, perm);
        uintptr_t src_kvaddr = page2kva(page);
        uintptr_t dst_kvaddr = page2kva(npage);
        memcpy(dst_kvaddr, src_kvaddr, PGSIZE);
    } 
    else 
    {
    
		...
   	}
	...
} 

function make qemu View the run results
 Insert picture description here
Here is another COW Realization

Summary of the experiment

This experiment mainly involves some knowledge of processes , Such as creating , management , The specific implementation of switching to user mode process ; load ELF Specific implementation of executable file ; The specific implementation of the system call mechanism . And learned that through system calls sys_fork/sys_exec/sys_exit/sys_wait To support running different applications , Complete the basic management of the execution process of the user process . In the study , There is also something you don't understand . Not very understanding of virtual memory space , I don't understand the code tf->tf_cs = USER_CS; And other related parameters , And the relevant knowledge of interruption is not in place . In the future, we will increase our understanding of interruption , Review what you didn't understand in the experiment .

The teacher of the theory class said that process management is a very important knowledge point of the operating system , The five states of the process are also mentioned in particular , This is also reflected in the experiment . Several exercises also basically have notes , It's not very complicated , But the workload is not small ( This should be the longest experimental report I have ever written ).challenge Realized COW, stay Linux It is also reflected on the website, so there are many relevant materials , It was very detailed , Yes challenge The implementation of is very helpful , and challenge There are also notes .

Finish this experiment , Have a deeper understanding of process management , At the same time, I have a further understanding of the knowledge learned in the theory class , It can also check omissions and fill vacancies , Find the knowledge points you have forgotten or failed to learn well .

原网站

版权声明
本文为[Wuhu hanjinlun]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/187/202207060919099298.html