Operating System代写:CS416 Adding Signals to XV6

实现操作系统中信号捕捉的相关逻辑部分。作业给了框架代码,需要代写的工作量其实就是信号处理前后,寄存器状态的保存部分。

Introduction

As you know from class (and the previous project), a signal, aka software interrupt/exception, is a notification to a process that an event has occurred. Just like hardware interrupts, signals interrupt the normal flow of execution of a program. A signal can be sent by different sources:

  • The kernel
  • Some process in the system
  • A process sending a signal to itself

In this homework, we are going to focus on signals sent by the kernel to the process. Different types of events can cause the kernel to send a signal to a process:

  • A hardware exception: For example executing malformed instructions, dividing by 0, or referencing inaccessible memory locations.
  • Typing the terminal’s special characters: For example the interrupt character (usually Ctrl-C) and the suspend character (usually Ctrl-Z).
  • A software event: For example a timer going off or a child of the process terminating.

There are different options when handling a signal: the default action occurs (for example terminating the process upon receiving SIGINT), the signal is ignored, or a user specified signal handler is executed.

Signaling on xv6

You need to extend the xv6 kernel to add support for signals. For this part, you must modify the xv6 kernel to allow support for handling signals generated as a result of a hardware exception.

To simplify the implementation you should implement support for only one new signal at this time: SIGFPE, which should occur when the processor encounters a division by zero.

At a high level, the flow of a signal delivery should look like the following:

  • An event that occurs that requires that a signal be delivered (Example: division by zero).
  • An exception occurs, which causes the OS trap/exception handler to be invoked. (See trap.c)
  • Code in the trap handler determines what type of exception has occured, and that a signal may need to be delivered.
  • Code in the trap handler checks to see if the user has registered a signal handler for the type of event that has occurred.
  • If a handler has been registered, call a function to set up a signal for the process.
  • This function pushes the address of the excepting instruction, the volatile registers (eax, ecx, and edx on x86), as well as some other critical information onto the process’s stack..
  • This function also changes the instruction pointer eip to point to the signal handler function.
  • Function returns, allowing the trap to return control to the process that caused it.
  • Process resumes, with instruction pointer now moved to the signal handler, so the signal handler executes.
  • Signal handler returns, but return address on the stack has been set to the address of a signal trampo- line.
  • Signal trampoline executes. This function is very simple. It consists of only a call to the sigreturn() system call.
  • sigreturn() executes, calling an internal kernel function that cleans up the stack.
  • This function recovers the saved volatile registers from the stack, as well as the saved excepting in- struction address. It restores the volatile registers and sets the eip back to the excepting instruction, as well as moves the stack pointer to where it was before the exception occurred.
  • When this function returns, everything should be as it was before the exception occurs. If nothing was changed in the handler, the excepting instruction restarts, and the exception occurs again.

Detailed Description

Summary

We will provide a considerable framework to help you get started on this new signal facility. In particular, you will need to implement code to do the following:

  • Add a new case to the xv6 trap handler trap() to determine when a trap was caused by a division by zero, and call the signal deliver() function.

  • Implement the signal deliver() function to construct a signal frame on the user stack and change the instruction pointer (eip) to point to the signal handler.

  • Implement the signal return() function to remove all traces of the signal frame from the user stack and change the instruction pointer (eip) back to the faulting instruction.

More details will follow in the subsequent sections.

Starting Point

To start on this project, use the following commands to check out a copy of xv6 with the skeleton code already in place.

To compile xv6, type make. To start the compiled OS in QEMU, type make qemu (for a graphical terminal) or make qemu-nox if you are connecting via SSH.

Trap Handler

In xv6, the trap/exception handler is implemented in trap.c as trap(). This function includes a large switch() statement that switches between different cases (type of exceptions). You will add a case for T DIVIDE that checks for a signal handler for SIGFPE, and if this exists, calls signal deliver() to set up a signal. We have added a field to the struct proc structure (each process has one of these) that holds pointers to any signal handlers that the user has registered (See proc.h). The proc struct of the current process is always accessible in the kernel using the variable name proc. For example, you can check for a handler for SIGFPE by checking proc->signal handlers[SIGFPE].

Deliver Signal

We have provided a function stub called signal deliver() in proc.c. This function must construct the signal frame on the process’ call stack, change the instruction pointer to the signal handler, and return control to the process.

Whenever an exception occurs, all of the excepting process’ state (registers) is saved into a structure called the trapframe. These registers are accessible via proc->tf->register, e.g. proc->tf->eip.

Any change made in the kernel to these registers will be applied when the process resumes execution.

Knowing this, and using proc->tf->esp (stack pointer) as reference point, you should make the user stack look like the following:

-------------------------
| top of stack          | <- esp when starting
-------------------------
| saved eip             | <- instruction where exception occurred
-------------------------
| saved eax             | \
-------------------------  \
| saved ecx             |   -> saved volatile register state
-------------------------  /
| saved edx             | /
-------------------------
| signum (e.g. SIGFPE)  | \
-------------------------  -> stack frame for signal handler function
| address of trampoline | /
-------------------------

Keep in mind, that the stack grows down in memory on x86, so the bottom of this diagram represents the top of the stack.

After you have put all of this into memory, you should update the esp (on the trapframe) to point to the address of the trampoline (the original esp - 24 bytes). You should also update the eip (also on the trapframe) to point to the address of the signal handler.

In our framework, you can find the address of the trampoline in proc->signal trampoline.

Restore Stack After Signal

We have also provided a function stub called signal restore() in proc.c. This is the function that executes when the system call sigrestore() is called. This function must essentially reverse what happened in signal deliver(), restoring the volatile registers (to the trapframe), and setting the eip (also in the trapframe) to the excepting instruction. All of these register values must be recovered from where you placed them on the stack.

After this is all done, you must also change the esp on the trapframe back to its original location.