Operating System Course Design of Shandong University School of Software Nachos-Experiment 6-System Calls and Multi-User Programs

Note: The experiments written in the blog are not comprehensive and are not the final version of the experimental report (it is a simple note recorded by typora during the experiment), the complete content (including code + experimental report) can be passed through ( The operating system course design of the School of Software, Shandong University ) download, or follow the WeChat public account "Moxi blog" to get it for free
insert image description here

1. System call

One, nachos system call analysis

System calls are the interface between user programs and the operating system kernel. User programs obtain system services from system call functions. When the CPU control is switched from the user program to the system state, the working mode of the CPU changes from the user state to the system state. When the kernel completes the system call function, the CPU working state changes from the system state back to the user state and returns the control to the user program again. Two different CPU working states provide the basic protection of the operating system.

1. nachos compiles the user program

By reading the experimental guide, we know that in the nachos system, the C language program written by the user is linked to a target module called start.o generated by the MIPS assembler start.s after cross-compilation by gcc MIPS. Actually start is the real startup entry of the user program, which calls the main function of the C program. Therefore, users are not required to use the main function as the first function when programming. The assembler also provides an assembly language stub for Nachos system calls. The following is the assembly listing of the assembler start.s:

2. System call

In the nachos system, some system call functions are defined in the nachos/userprog/syscall.h file

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-zLbdBwu1-1642244999317) (experimental report.assets/image-20211122214152206.png)]

User programs can call a series of system call functions defined in this file, and the implementation of these functions is the assembly code defined in nachos/test/start.s.

Take Exec() as an example:

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-DEYzwgpg-1642244999318) (experimental report.assets/image-20211122214322952.png)]

The implementation process here is to put SC_Exec into register No. 2 first, and then call syscall.

In fact, by observing the implementation of other system calls, it is found that all system calls are syscalls that first put SC_*** into register 2, and then execute them. This part is also explained in the lab guide:

The interface program code of these system calls in start.s is the same. which is:

  • Send the code of the corresponding system call to the $2 register
  • Execute the system call instruction SYSCALL and
  • Return to user program

When a system call is issued by a user process, the program written in assembly language corresponding to the stub is executed. The stub program will then raise an exception or trap by executing a system call instruction. The function RaiseException(ExceptionType which, int badVAddr) in the Machine class emulates the exception and trap management of a MIPS computer. The first parameter which is a variable of the ExceptionType enumeration type. The definition of the ExceptionType type is also in the machine/machine.h file.

A system call is one of these exceptions . The "SYSCALL" instruction for a MIPS computer is simulated in Nachos by issuing a system call exception on lines 534-536 in machine/mipssim.cc:

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-SUoeuUXB-1642244999319) (experimental report.assets/image-20211122215818120.png)]

Note that the next statement after the system call exception handling is a return statement, not a break statement . It is important that the return statement does not advance the program counter PC so that the same instruction will be started again after exception handling . So in order to avoid repeated execution, we need to manually advance the PC.

The code of the function RaiseException() is in the machine/machine.cc file, and the RaiseException function actually finally calls the function ExceptionHandler(ExceptionType which) in nachos/userprog/exception.cc.

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-YbiG6egU-1642244999319) (experimental report.assets/image-20211122220302309.png)]

Analyze this function, first read the contents of register 2, and then have an if judgment, if the incoming exception type is a system call, and the content read from the register is SC_Halt, then execute Halt(), Otherwise, it is the normal exception catch processing.

It can be seen that only system calls with SC__Halt code can be processed here. If we want to implement Exec(), we should add a judgment branch here so that it can process the system calls of SC_Exec.

Regarding the parameter problem, the experimental instructions and the comments in the code are also mentioned

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-JJOVftTZ-1642244999320) (experimental report.assets/image-20211122221004621.png)]

Registers 4 - 7 contain the first 4 arguments when the system call begins processing. The return value of the system call, if any, will return to the No. 2 register.

3. Exec system call

Look again at the definition of the Exec system call in syscall.h

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-kYX29dkR-1642244999321) (experimental report.assets/image-20211123102704398.png)]

As you can see from the comments, the function of this system call is to run a new user program from the executable file name, execute it in parallel, and return the memory space identifier SpaceId of the new program, which will be used as the parameter of the Join system call. The Join system call can be used to return the exit status at the end of the process identified by SpaceId.

2. Implement the nachos system call: Exec()

1. Realize Print

Referring to the experimental guide, in order to understand the resident memory of multi-user programs in Nachos, and view the allocation and corresponding relationship of the memory page table, you can add the following print member function Print to the AssSpace class:

declared in the AssSpace class public in addrspace.h

void Print();

Implemented in addrspace.cc

void
AddrSpace::Print() {
    
    
    printf("page table dump: %d pages in total\n", numPages);
    printf("============================================\n");
    printf("\tVirtPage, \tPhysPage\n");
    for (int i=0; i < numPages; i++) {
    
    
        printf("\t%d, \t\t%d\n", pageTable[i].virtualPage, pageTable[i].physicalPage);
    }
    printf("============================================\n\n");
}

Add Print() at the end of the AddrSpace constructor for easy debugging.

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-fbykCctB-1642244999322) (experimental report.assets/image-20211123161459956.png)]

2, to achieve memory expansion

Next, we need to further expand the user memory space so that multi-user programs can reside in memory at the same time , so that multi-user processes can execute concurrently .

First look at the current Nachos initialization function of the user memory page table, that is, the relevant fragment in AddrSpace.cc:

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-RHfW10wd-1642244999324) (experimental report.assets/image-20211123162511293.png)]

Note that the physical frame assignment on line 89 is always the value of the loop variable i . Because when the second program allocates, it also starts at 0, so when two programs reside in memory at the same time, the latter program will be loaded into the physical address of the previous program, thereby placing the previously loaded program cover. It can be seen that the basic Nachos does not have the function of multiple programs resident in memory at the same time.

Next we need to improve the design of Nachos' memory allocation algorithm.

Design idea: Use the Bitmap class defined by Nachos in the file .../userprog/bitmap.h. Use bitmap to record and apply for memory physical frames, so that different programs can be loaded into different physical spaces.

This requires the AddrSpace class to maintain a static BitMap to mark the page table allocation in physical memory.

First in addrspace.h, add #include "bitmap.h" to import the bitmap class.

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-I4AtaLZW-1642244999325) (experimental report.assets/image-20211123163123108.png)]

Then in the definition of the AddrSpace class, add the declaration in the private block

static BitMap *bitmap;

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-MUllWrZq-1642244999326) (experimental report.assets/image-20211123163236377.png)]

In addrspace.cc, create a static object bitmap before the constructor

BitMap* AddrSpace::bitmap = new BitMap(NumPhysPages);

In this way, we have finished creating the BitMap object in the AddrSpace class to manage the free memory page table.

Next, you only need to directly allocate the original physical page table as i and change it to call bitmap to allocate.

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-PZTLX03g-1642244999327) (experimental report.assets/image-20211123163617228.png)]

Finally, remember to release this space in the destructor to ensure that the space can be released in time when the memory space is deleted.

for (int i = 0; i< numPages; i++){
    
    
    bitmap->Clear(pageTable[i].physicalPage);
}

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-JXMFkbAw-1642244999328) (experimental report.assets/image-20211123163929844.png)]

Modify the logic of the executable file read into memory below

The code that reads the contents of the executable file into memory in addrspace.cc is implemented as follows:

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-nY2FBcVl-1642244999329) (experimental report.assets/image-20211123165326508.png)]

Obviously, the logical address is read as a physical address here, so the corresponding relationship between the logical address and the physical address is meaningless.

We should convert code.virtualAddr and initData.virtualAddr to the physical address corresponding to the physical page table above, that is, convert logical addresses to physical addresses learned in the course last semester.

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-l0grloqp-1642244999330) (experimental report.assets/image-20211123165633272.png)]

	if (noffH.code.size > 0) {
    
    
        DEBUG('a', "Initializing code segment, at 0x%x, size %d\n", noffH.code.virtualAddr, noffH.code.size);

		//code start from this page
		int code_page = noffH.code.virtualAddr/PageSize;
		//calculate physical address using page and offset (0);
		int code_phy_addr = pageTable[code_page].physicalPage *PageSize;
		//read memory
        executable->ReadAt(&(machine->mainMemory[code_phy_addr]),noffH.code.size, noffH.code.inFileAddr);
    }
    if (noffH.initData.size > 0) {
    
    
        DEBUG('a', "Initializing data segment, at 0x%x, size %d\n", noffH.initData.virtualAddr, noffH.initData.size);
		//data start from this page
		int data_page = noffH.initData.virtualAddr/PageSize;
		//first data's offset of this page
		int data_offset = noffH.initData.virtualAddr%PageSize;
		//calculate physical address using page and offset ;
		int data_phy_addr = pageTable[data_page].physicalPage *PageSize + data_offset;  
		//read memory
        executable->ReadAt(&(machine->mainMemory[data_phy_addr]),noffH.initData.size, noffH.initData.inFileAddr);
    }

Finally, you need to modify the initialization memory, comment out bzero(machine->mainMemory, size) in the constructor;

This is the operation that clears the entire memory when the AddrSpace object is created. However, if we create a new AddrSpace, all the original memory will be emptied. In fact, when a new executable is read into memory, it can be executed normally without clearing the memory, so we simply comment out this line.

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-bb9J63dH-1642244999331) (experimental report.assets/image-20211123165748862.png)]

3. Implement AdvancePC

The analysis of the first part has already mentioned why AdvancePC is implemented, because the operation of the system call simulation instruction in the file.../machine/mipssim.cc is returned by return, which means whether to set the program counter after the system call is executed. The work of moving forward is handed over to the corresponding system call handler to decide. So we should implement AdvancePC to advance the program counter when the system call is successful

The implementation example has been given in the experimental guide. Here we implement AdvancePC in interrupt.cc, because the Exec function will be implemented here later, and AdvancePC needs to be called, and it should be implemented in front of other functions, or declared in advance

void AdvancePC() {
    
    
	machine->WriteRegister(PrevPCReg,machine->ReadRegister(PCReg)); 
	machine->WriteRegister(PCReg, machine->ReadRegister(PCReg) + 4);
	machine->WriteRegister(NextPCReg, machine->ReadRegister(NextPCReg) + 4);
}

4. Implement SpaceID

Two issues need to be addressed regarding the value of SpaceId:

  • How to generate this value in the kernel
  • How to record this value in the kernel, so that the Join system call can find the corresponding user process through this value.

Implementation idea: First, the total number of identifiers should be less than the maximum number of threads (128), and can be accessed globally (such as join and other operations), and can be uniquely identified. Therefore, we need to add an attribute to AddrSpace to record this identifier and assign it a value during initialization. At the same time, in order to avoid repetition, we declare an array in system.h to mark the identifier currently in use, so that the global can be accessed. When the AddrSpace object is destroyed, the corresponding identifier is blanked.

The specific implementation is to declare global variables in system.h. The system.h file is used to declare global variables

extern bool ThreadMap[128];

Initialize the process identifier array in system.cc

[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-oXwRH4X0-1642244999331) (experimental report.assets/image-20211123193232353.png)]

In the Initialize method of system.cc, assign it a value of 0; system.cc is used for Nachos initialization and general cleanup. The Initialize method is used for initialization and the Cleanup method is used for cleanup.

bzero(ThreadMap,128);

Add properties for AddrSpace class in addrspace.h

int spaceID;

add method

int getSpaceID(){
    
    return spaceID;}

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-DwPGz33W-1642244999332) (experimental report.assets/image-20211123110246942.png)]

In addrspace.cc, in front of the constructor, a unique spaceID is obtained and identified by looping through

AddrSpace::AddrSpace(OpenFile *executable)
{
    
    
	//2021.11.23 add +++++++++++++++++++++++++++++++++++++++++
	bool flag = false;
	//遍历一下,找到未被使用的spaceID,即ThreadMap中对应值为0
	//找到以后,标记为已使用
	for(int i = 0; i < 128; i++){
    
    
		if(!ThreadMap[i]){
    
    
			ThreadMap[i] = 1;
			flag = true;
			spaceID = i;
            printf("spaceID:%d\n",spaceID);
			break;
		}
	}
	ASSERT(flag);
	//2021.11.23 add +++++++++++++++++++++++++++++++++++++++++

Finally, the space needs to be released in the destructor method

AddrSpace::~AddrSpace()
{
    
    
	//2021.11.23 add +++++++++++++++++++++++++++++++++++++++++
	ThreadMap[spaceID] = 0;
	for (int i = 0; i< numPages; i++){
    
    
		bitmap->Clear(pageTable[i].physicalPage);
	}
	//2021.11.23 add +++++++++++++++++++++++++++++++++++++++++
	
	delete [] pageTable;
}

5. Implement Exec

The previous part has been analyzed, the existing ExceptionHandler function can only determine SC_Halt, so it is necessary to add the code of the if SC_Exec branch to the logic determination in the ExceptionHandler function to process the Exec system call. According to the previous analysis and the explanation in the experimental guide, you can Know that when the system call is executed, it is actually the exception handler function Exec. Exception belongs to a kind of interrupt processing, so these processing functions are usually encapsulated in the Interrupt class as a member function of the Interrupt class, so the Exec function of the Interrupt class is called directly here.

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-AUi4AoNy-1642244999333) (experimental report.assets/image-20211123204926997.png)]

Next, implement the Exec function of the Interrupt class

As explained in the experimental guide, for the coding of the exception handling function Exec, you can refer to the StartProcess function in the .../userprog/progtest.cc file. The function code is as follows:

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-fUFGIMMK-1642244999333) (experimental report.assets/image-20211123211902782.png)]

The main difference is that the name of the file to be opened in StartProcess is passed from the command line, while the name of the file to be opened by Exec is passed in the $4 register.

First declare the Exec function in the Interrupt class

[External link image transfer failed, the source site may have an anti-leech mechanism, it is recommended to save the image and upload it directly (img-DoW7dPYz-1642244999334) (experimental report.assets/image-20211123205345042.png)]

Then before implementing the Exec function, implement the StartProcess function first, implement it in front of the interrupt.cc file, or declare it first

Thread* thread;
AddrSpace *space;

void
StartProcess(int n){
    
    
	currentThread->space = space;
	
	currentThread->space->InitRegisters(); // set the initial register values
	currentThread->space->RestoreState();  // load page table register
	
	machine->Run();  //jump to the user progap
	
	ASSERT(FALSE);   //machine->Run never returns;
					 //the address space exits
					 //by doing the syscall "exit"
}

Finally implement the Exec function

Code:

int
Interrupt::Exec()
{
    
    
		printf("Execute system call of Exec()\n");
        //read argument
        char filename[50];
        int addr=machine->ReadRegister(4);
        int i=0;
        do{
    
    
            machine->ReadMem(addr+i,1,(int *)&filename[i]);//read filename from mainMemory
        }while(filename[i++]!='\0');

        printf("Exec(%s):\n",filename);

        //open file
        OpenFile *executable = fileSystem->Open(filename);

        if (executable == NULL) {
    
    
            printf("Unable to open file %s\n", filename);
            return 0;
        }

        //new address space
        space = new AddrSpace(executable);  
		delete executable;          // close file

		//new and fork thread
		thread = new Thread("forked thread");
		thread->Fork(StartProcess, 1);

		//run the new thread
		currentThread->Yield();

		//return spaceID
		machine->WriteRegister(2,space->getSpaceID());

		//advance PC
		AdvancePC();    
}

insert image description here

3. Test

1. Write test files

Create a new file exec.c under nachos/test

#include "syscall.h"

int
main(){
    
    	
	Exec("../test/halt.noff");
	Halt();
}

2. Test

First add exec at the end of the targets sentence in test/MakeFile

insert image description here

Then make and compile in the test directory and lab6 directory respectively.

Command line execution: ./nachos -x …/test/exec.noff

insert image description here

insert image description here

There is a small problem here. If the following situation occurs, the display "cannot open file xxxx" may be a problem of insufficient permissions. Open the test folder and there is this file, but a small lock is displayed on it, so switch to the root user.

insert image description here

Refer to the experimental guide and related online articles to complete

Reference link: https://blog.csdn.net/mottled233/article/details/78633571

Guess you like

Origin blog.csdn.net/m0_47470899/article/details/122514366