This might be a very trivial question, but I was unable to find a concise answer to it. For non maskable interrupts, are the interrupts handled immediately while the current instruction is getting executed? Or it waits for the current instruction to get executed and then handles interrupts. Also for maskable interrupts, when the interrupt flag is set to 1, does it replicate the non-maskable interrupt situation?
The precise details of how exactly interrupts are handled varies from processor to processor. Nevertheless we can simplify the operations for ease of understanding.
At its simplest, a processor basically:
#fetch next instruction
#execute an instruction
rinse and repeat.
For interrupts, we can evaluate these before fetching an instruction. So the operation would be something like this:
if interrupt pending, do interrupt service (which is basically a function call with a little bit extra)
#fetch next instruction
rinse and repeat.
Where you have multiple interrupt sources (including non-maskable) you need a means of resolving priority. Non-maskable has the highest priority.
Some architectures implement interruptible instructions - 68k comes to mind. This can add an extra layer of complexity as you also need to save the processor micro state as well as the cpu state. If a potential write is involved, then that needs to be specially managed - was the instruction interrupted before the write or after?
I'd suggest you investigate cpu architectures like the ARM Cortex and RISC-V to see how they handle interrupts in a modern context.
In summary, you could simply describe an interrupt as a hardware conditional function call.
Interrupts, maskable or not, generally need to be processed fast. Processing them fast means that no significant amount of time should be used saving and restoring state for resuming normal operation. That generally makes interrupts be implemented as function calls that only save the instruction pointer, relying on the interrupt routine itself to save anything else it may need. Some processors have a separate slate of registers (or memory-mapped registers where just relocating the register area will work) for this, but typically interrupts may be prioritised and nested so that it is usual to reenable interrupts before executing the bulk of the interrupt processing routine, and in this case there is no definite limit of extra registers you may need to switch in and out.
So usually state is saved externally to the processor and costs time to transfer there.
For things like page faults in processors implementing demand-paged virtual memory, saving the whole processor state is necessary.
So the interrupt latency generally is a whole instruction. For CPUs that have instructions with indefinite length, like the memory/string/IO repeat instructions of the x86 architecture, the instructions tend to be designed in a manner that an interrupt can return to the start of such an instruction interrupted during unfinished business and operation will resume there properly. So while the instruction-sized latency usually leads to the next instruction's address getting pushed to the return stack, those special instructions can be interrupted while not finished and then the current instruction's address is getting pushed, leading to an additional opcode fetch upon returning from the interrupt that would not have been necessary otherwise.
So the instruction granularity of interrupt routines can come with modifications, but it is generally what to reckon with.
The best way to think about interrupts, is in terms of where the next instruction comes from. Most architectures have an Instruction Address multiplexer, which chooses one from a number of sources.
For much of the time, the processor steps linearly through the code, executing instructions sequentially. The register that holds the address of the currently executing instruction is actually called the Program Counter (abbreviated to PC) in many architectures, to reflect the fact that it just counts linearly through the code.
The PC provides a PC+1 input to the Instruction Address multiplexer.
From time to time, this linear operation is changed by a Branch instruction, which modifies the normal program flow. This could be a Jump, whether conditional or unconditional, a Call, or a Return instruction. The branch destination addresses come from the branch instruction itself, or are popped off of a Return Address stack.
The call instruction does additional work, putting the PC+1 onto a the return address stack, matched by the return which pops it. These are typically used in pairs, around a function call.
These software branch instructions provide another set of inputs to the Instruction Address multiplexer.
An interrupt is a hardware branch. The destination address usually comes from an Interrupt Vector table, though the non-maskable one is often hardwired.
The interrupt logic handles whether any interrupts are masked, and chooses the highest priority one if there are multiple interrupts. This is totally dependent on the design of the processor, look at the data sheet for the one you are using to see the detail of what individual flags do.
The interrupt instruction puts the PC+1 onto a return address stack just like the software call, though often a different one to the software call. The interrupt function is often required to end with a Return from Interrupt instruction, which pops this separate stack.
It can be seen then that an interrupt function is practically equivalent to an ordinary software function, but called by a hardware event instead of a software instruction.
Details may vary depending on architecture, but the general ideas are as follows. Note that compared to a general purpose CPU:
a microcontroller may be severely restricted due to lack of stack space and
a specialized processor could be different and/or more capable in certain respects; but let's keep the discussion simple.
... are the interrupts handled immediately while the current instruction is getting executed?
Think about it logically. If an instruction has started executing, then the IP points to the next instruction. If there is no interrupt, that would be the next instruction. If "any" kind of interrupt is handled immediately, then the data being processed by the unfinished instruction would be lost and the instruction pointer won't be pointing to it anymore either. So the answer to this question is: Never. The current instruction is always completed and then the interrupt status is checked.
Also for maskable interrupts, when the interrupt flag is set to 1, does it replicate the non-maskable interrupt situation?
The interrupt flag set to 1 just means that the interrupt will be serviced when it occurs ... but if other higher priority interrupts are being serviced or are present in the queue, then lower priority interrupts will have to wait. A 0 in the register means, that an interrupt has been masked and will not be recognized by the CPU. e.g. a timer may be overflowing but if the timer interrupt has been masked, nothing will happen.
Important point A CPU doesn't know that it is executing an instruction that belongs to an interrupt service routine. All instructions are equal.
If a non-maskable interrupt (always the highest priority) occurs, the current instruction is completed and the control is transferred to a known address, where there is supposed to be a jump instruction leading to the ISR. Upon return, the control is transferred back to the where ever the CPU happened to be when the interrupt was called.
If an interrupt is maskable (and there are many of these), a priority verification mechanism is engaged. If a higher priority interrupt is already being serviced, then the new interrupt is kept in a queue. When the high priority interrupt routine is completed, the control switches back to the location from where the ISR was called. Before the next instruction at that location can be executed, the interrupt status is checked and so it goes on.
- The main program has the lowest priority.
- The current instruction is always completed before branching off to the ISR. Control comes back to the point where it branched off. The interrupt status is checked again at the branching point.
- NMI has the highest priority. (reserved for power failure or other critical situations only)
- For other interrupts, a priority mechanism exists. Interrupts block other interrupts at lower priority and are overridden by higher priority interrupts.