硬中斷,軟中斷,訊號,異常

weixin_45783996發表於2020-10-17

為了搞清楚這幾個概念,查了很多資料和部落格,通過這篇部落格整理下來。

概念明晰1

異常控制流(Exceptional control flow,ECF):Abrupt changes in the control flow in response to some situation in computer system.
異常(Exception):A form of exceptional control flow that are implemented partly by hardware and partly by operation system.
異常有四種型別:

  • Interrupts
  • Traps and System Calls
  • Faults
  • Aborts

訊號(Signal): A higher-level software form of exceptional control flow that allows processes and the kernel to interrupt other processes.

個人理解

所以說異常,中斷和訊號都是一種ECF,只不過是在整個系統的不同層面,這裡的異常和我們在高階語言中的異常不是同一個概念,比如java和C++中的throw,try,catch,但是這些高階異常也是基於ECF的思想實現的。硬中斷就是硬體層面的,在我看來就是exception中的interrupt,軟中斷有兩種說法,在不同於語境下可以有不同的理解,

  • 一種是指由INT指令產生中斷,其實上面提到的trap就是由INT指令實現的,比如呼叫了某個syscall,程式的彙編程式碼就是INT $0x80,該指令會將程式從user態轉到kernel態,執行的指令不是相鄰的,所以是一種ECF,因為它是從軟體層面產生的,所以叫軟中斷。
  • 還有一種說法就是訊號也叫軟中斷訊號,因為一旦程式接收到訊號就要去執行signal handler,指令不相鄰,即ECF,且這也是軟體層面(作業系統或者其它程式)產生的,因此也叫軟中斷。

深入訊號

那麼訊號有什麼用呢?

由於在現在的作業系統中,底層的exception比如一些硬體中斷是被kernel的handler所處理,不能被程式直接看到,訊號就提供了一種機制,由核心給程式傳送訊號,來告訴使用者程式發生了某些異常,從而可以從application level去處理異常。當然,訊號也提供了程式間通訊的機制,這應該也是訊號用的比較多的一個方面,所以訊號大部分都是非同步的,因為不知道硬體或別的程式會什麼時候產生訊號。當然也有同步訊號,比如除以零等錯誤操作,每次執行同樣的程式都是同時產生訊號。

訊號機制分析

關於訊號的機制分析,我覺得這篇部落格寫的非常詳細Linux訊號(signal) 機制分析

訊號處理流程

訊號的處理髮生在程式從kernel態切換回user態的時候,執行是在使用者態,因為handler是使用者自定義程式,如果讓其執行在核心態就能看到一些它不該看到的資料,所以在核心態時軟中斷訊號不立即起作用,且使用者態沒有未處理完的訊號(因為每次返回使用者態之前都會檢查未決訊號,然後處理,每當收到訊號,cpu就會將程式切換到核心態)。執行handler的過程正如上面那篇部落格提到的:

而且執行使用者定義的函式的方法很巧妙,核心是在使用者棧上建立一個新的層,該層中將返回地址的值設定成使用者定義的處理函式的地址,這樣程式從核心返回彈出棧頂時就返回到使用者定義的函式處,從函式返回再彈出棧頂時,才返回原先進入核心的地方。這樣做的原因是使用者定義的處理函式不能且不允許在核心態下執行(如果使用者定義的函式在核心態下執行的話,使用者就可以獲得任何許可權)。

總結

我覺得這篇文章寫的很詳細:Interrupts and Handlers
下面是原文轉述和自己的一些理解

Interrupts

An interrupt is a request of the processor to suspend its current program and transfer control to a new program called the Interrupt Service Routine (ISR). Special hardware mechanisms that are designed for maximum speed force the transfer. The ISR determines the cause of the interrupt, takes the appropriate action, and then returns control to the original process that was suspended.
Why do you need interrupts? The structure of the processor of any computer is conceived so it can carry out instructions endlessly. As soon as an instruction has been executed, the next one is loaded and executed. Even if it appears the computer is inactive, when it is waiting in the DOS prompt or in Windows for your next action, it does not mean it has stopped working , only to start again when instructed to. No, not at all. In fact, many routines are always running in the background independently of your instructions , such as checking the keyboard to determine whether a character has been typed in. Thus, a program loop is carried out. To interrupt the processor in its never-ending execution of these instructions, a so-called interrupt is issued. That is why it is possible for you to reactivate the CPU whenever you press a key (fortunately…). Another example, this time an internal one, is the timer interrupt, a periodic interrupt, that is used to activate the resident program PRINT regularly for a short time.
For the 80x86 ==256 different interrupts ==(ranging from 0-255) are available in total. Intel has reserved the first 32 interrupts for exclusive use by the processor but this unfortunately hasn’t prevented IBM from placing all hardware interrupts and the interrupts of the PC BIOS in exactly this region which can give rise to some strange situations.
Speaking of hardware interrupts, you can distinguish three types of interrupts:
- Software Interrupts
- Hardware Interrupts
- Exceptions

I will give a brief description of the previous categories but a detailed analysis is beyond the scope of this document. Please consult a reference manual, like the excellent “The Indispensable PC Hardware Book” by Hans-Peter Messmer and published by Addisson-Wesley (see the reference section), or send me an email if you wish to know further details.
Software Interrupts
Software interrupts are initiated with an INT instruction and, as the name implies, are triggered via software. For example, the instruction INT 33h issues the interrupt with the hex number 33h.
In the real mode address space of the i386, 1024 (1k) bytes are reserved for the interrupt vector table (IVT). This table contains an interrupt vector for each of the 256 possible interrupts. Every interrupt vector in real mode consists of four bytes and gives the jump address of the ISR (also known as interrupt handler) for the particular interrupt in segment:offset format.
When an interrupt is issued, the processor automatically transfers the current flags, the code segment CS and the instruction pointer EIP (or IP in 16-bit mode) onto the stack. The interrupt number is internally multiplied by four and then provides the offset in the segment 00h where the interrupt vector for handling the interrupt is located. The processor then loads EIP and CS with the values in the table. That way, CS:EIP of the interrupt vector gives the entry point of the interrupt handler. The return to the original program that launched the interrupt occurs with an IRET instruction.
Software interrupts are always synchronized with program execution; this means that every time the program gets to a point where there is an INT instruction, an interrupt is issued. This is very different from hardware interrupts and exceptions as you’ll soon find out.
2) Hardware Interrupts
As the name suggests, these interrupts are set by hardware components (like for instance the timer component) or by peripheral devices such as a hard disk. There are two basic types of hardware interrupts: Non Maskable Interrupts (NMI) and (maskable) Interrupt Requests (IRQ).
An NMI in the PC is, generally, not good news as it is often the result of a serious hardware problem, such as a memory parity error or a erroneous bus arbitration. An NMI cannot be suppressed (or masked as the name suggests). This is quite easy to understand since it normally indicates a serious failure and a computer with incorrectly functioning hardware must be prevented from destroying data.
Interrupt requests, on the other hand, can be masked with a CLI instruction that ignores all interrupt requests. The opposite STI instruction reactivates these interrupts. Interrupt requests are generally issued by a peripherical device.
Hardware interrupts (NMI or IRQ) are, contrary to software interrupts, asynchronous to the program execution. This is understandable because, for example, a parity error does not always occur at the same program execution point. This makes the detection of program errors very difficult if they only occur in connection with hardware interrupts.
3) Exceptions
This particular type of interrupt originates in the processor itself. The production of an exception corresponds to that of a software interrupt. This means that an interrupt whose number is set by the processor itself is issued. When do exceptions occur? Generally, when the processor can’t handle alone an internal error caused by system software.
There are three main classes of exceptions which I will discuss briefly.

  • Fault : A fault issues an exception prior to completing the instruction. The saved EIP value then points to the same instruction that created the exception. Thus, it is possible to reload the EIP (with IRET for instance) and the processor will be able to re-execute the instruction, hopefully without another exception.

  • Trap : A trap issues an exception after completing the instruction execution. The saved EIP points to the instruction immediately following the one that gave rise to the exception. The instruction is therefore not re-executed again. Why would you need this? Traps are useful when, despite the fact the instruction was processed without errors, program execution should be stopped as with the case of debugger breakpoints.

  • Abort : This is not a good omen. Aborts usually translate very serious failures, such as hardware failures or invalid system tables. Because of this, it may happen that the address of the error cannot be found. Therefore, recovering program execution after an abort is not always possible.
    Signals
    A signal is an notification to a process that an event has occurred. Signals are sometimes called software interrupts. And this, causes a few problems… Are signals different from the software interrupts we treated above? Or only a different name for the same thing? Before answering to those questions, you should know that the concept of an interrupt (in particular a software interrupt) has expanded in scope over the years. The problem is that this expansion has not been an organized one, but rather a ‘I’ll do as I please’ rampage. The 80x86 family has only added to the confusion surrounding interrupts by introducing the INT (software interrupts) instruction discussed above. The result of all this mess? There is no clear consensus of what terms to use in a given situation and different authors adopted different terms to their own use. So, words like software interrupts, signals, exceptions, traps,etc came bouncing around in completely different contexts.
    In order to avoid further confusion, this document will attempt to use the most common meaning for these terms. Also, in order to differentiate between signals and software interrupts, we’ll consider that :
    - Software interrupts - Are explicitly triggered with an INT instruction and are therefore synchronous, as discussed previously.
    - Signals - Don’t make use of the INT instruction and usually occur asynchronously, that is, the process doesn’t know ahead of time exactly when a signal will make its appearance.
    Now that we’ve cleared the pathway, let’s dive into the pool. The concept of signal handling was born (or at least it gained strength) with the Unix platform, a protected mode and multi-threaded system. Therefore, I will start by providing a general overview of signal handling and only afterwards will I explain what changes occur in a real-mode system like MS-DOS using a protected mode compiler like Djgpp. So, get ready for a thrill…
    A signal is said to be generated for (or sent to) a process when the event associated with that signal first occurs. Signals can be sent by one process to another process ( or to itself) or by the OS to a process. And what kind of events can raise a signal? Here are a few examples :

  • A program error such as dividing by zero or issuing an address outside the valid range.

  • A user request to interrupt or terminate the program.

  • The termination of a child process.

  • Expiration of a timer or alarm.

  • A call to kill from another (or the same) process.

  • An attempt to perform an illegal I/O operation like for instance reading from a pipe when the link is broken…
    As you can easily see, the events that generate signals fall into three major categories : Errors, external events and explicit requests.
    An error means that the program performed something invalid. But not all kinds of errors generate signals–in fact, most do not. For example, trying to open a nonexistent file is an error but it does not raise a signal. This error is associated with a specific library call. The errors which raise signals are those that can happen anywhere in the program, not just in library calls. These include division by zero and invalid memory addresses.
    An ==external event ==generally has to do with I/O or other processes. These include the arrival of input, the expiration of a timer or the termination of a child process.
    An explicit request means the use of a library function such as kill whose purpose is specifically to generate a signal.
    Signals can be generated synchronously or asynchronously (the latter being more common). If you try to reference an unmapped, protected or bad memory address a SIGSEV or SIGBUS can be issued, a floating point exception can generate a SIGFPE, and the execution of an illegal instruction can generate SIGILL. All the previous events, called errors if you recall, generate synchronous signals.Events such as keyboard interrupts generate signals (SIGINT) which are sent to the target process. Such events generate asynchronous signals.
    We now know how signals are generated and how about delivery? Well, when a signal is generated, it becomes pending. Normally, it remains pending for just a short period of time and then is delivered to the process that was signaled. However, if that kind of signal is currently blocked, it may remain pending indefinitely–until signals of that kind are unblocked. Once unblocked, it will be delivered immediately. Once a signal has been delivered, the target program has a choice : Ignore the signal, specify an handler function or accept the default action for that kind of signal. If the first option is selected, any signal that is generated is discarded immediately, even if blocked at the time. Building handler functions will be examined in closer detail later on. Finally, if a signal arrives which the program has neither handled nor ignored, its default action takes place. Each kind of action has its own default action : It can be to terminate the process (the most common one) or for certain “harmless” events, to do nothing.

總結

雖然這些概念在不同的語義下可能會有謝偉的不同,但是最重要的是把其中的思想掌握,機制弄清。細節搞清楚了,還要從一個更高的層次去總結,互相結合起來,才能有所收穫和進步。比如其實ECF在作業系統層還有一個很重要的應用,就是程式間的切換(Context Switch),就是通過interrupt來實現的。


  1. CSAPP,第八章 ↩︎

相關文章