Q1: What happens if another interrupt occurs while a top half (ISR) is executing?

Answer:

  • By default, interrupts are disabled during the top half execution. When the CPU enters the ISR (via the IDT), it automatically clears the Interrupt Flag (IF) on x86 (using cli), preventing further interrupts until the ISR finishes.
  • Exception: Some architectures or configurations (e.g., nested interrupts) allow interrupts to preempt an ISR. For example:
    • IRQF_DISABLED (now deprecated): Previously controlled whether interrupts were disabled during the ISR. Modern kernels typically disable interrupts for all IRQ handlers by default.
    • Threaded interrupts (using IRQF_ONESHOT or IRQF_THREAD): The “top half” runs in a kernel thread with interrupts enabled.

Key Takeaway:

  • Most ISRs run with interrupts disabled, ensuring atomicity. If a nested interrupt occurs (rare), it will be queued and handled after the current ISR completes.

Q2: How does the kernel prevent stack overflow due to nested interrupts?

Answer:

  • Each CPU has separate stacks for different contexts:
    • Hardware Interrupt Stack: A small, fixed-size per-CPU stack (e.g., 4KB–16KB).
    • Process Stack: Used when executing in process context.
  • Nesting Depth: The kernel limits interrupt nesting (e.g., x86 allows limited depth before a double fault is triggered).

Example:
If an ISR for IRQ 1 (keyboard) is interrupted by IRQ 12 (mouse), the CPU switches to the interrupt stack. If nesting exceeds the stack size, the kernel panics with a stack overflow.


Q3: What happens if two devices share the same IRQ line?

Answer:

  • Shared IRQs: Drivers can register handlers with IRQF_SHARED in request_irq().
  • During ISR execution, the kernel calls all registered handlers for that IRQ. Each handler checks if its device triggered the interrupt (e.g., by reading a device status register).
  • The first handler that acknowledges the interrupt (returns IRQ_HANDLED) stops further propagation.

Example:

// Driver 1 (Shared IRQ 9)
request_irq(9, my_isr1, IRQF_SHARED, "dev1", &dev1);

// Driver 2 (Shared IRQ 9)
request_irq(9, my_isr2, IRQF_SHARED, "dev2", &dev2);

In my_isr1, the driver reads a hardware register to confirm the interrupt was from dev1. If not, it returns IRQ_NONE, allowing my_isr2 to run.


Q4: Can the same IRQ be handled concurrently on different CPUs?

Answer:

  • Yes, if the interrupt is not marked as per-CPU (IRQF_PERCPU). The kernel allows an IRQ to be handled on any CPU unless restricted.
  • Race Conditions: Shared data between CPUs requires synchronization (e.g., spinlocks with spin_lock_irqsave()).

Best Practice:
Use IRQF_PERCPU for interrupts that are inherently CPU-local (e.g., APIC timer interrupts).


Q5: What happens if you call a blocking function (e.g., sleep()) in the top half?

Answer:

  • Never do this! The top half runs in atomic context (interrupts disabled, no process context). Blocking functions like sleep(), kmalloc(GFP_KERNEL), or mutexes will cause a kernel panic or deadlock.

Safe Functions:

  • Use kmalloc(GFP_ATOMIC) for memory allocation.
  • Use spinlocks instead of mutexes.

Interview Tip:
If asked how to debug a hung system, suspect a blocking call in an ISR. Tools like kgdb or analyzing crash dumps can help.


Q6: How does the kernel ensure the top half is minimal?

Answer:

  • Design Principle: The top half only does time-critical work (e.g., acknowledging the interrupt, reading hardware status). All other processing is deferred to the bottom half (tasklets, softirqs, workqueues).
  • Kernel Enforcement: No explicit enforcement, but violating this principle causes performance issues (e.g., high interrupt latency).

Example:
A network driver’s top half copies a packet from the device to a buffer and schedules a softirq (NET_RX_SOFTIRQ) for further processing.


Q7: How to handle reentrancy in the top half?

Answer:

  • Reentrancy: If the same IRQ can interrupt its own handler (e.g., nested interrupts), use reentrancy-safe techniques:
    1. Disable interrupts locally: Use local_irq_save(flags) to manually disable interrupts.
    2. Per-CPU Data: Use get_cpu_var() to access CPU-local variables.

Example:

irqreturn_t my_isr(int irq, void *dev_id) {
    unsigned long flags;
    local_irq_save(flags); // Disable interrupts
    // Critical section
    local_irq_restore(flags); // Re-enable
    return IRQ_HANDLED;
}

Q8: How to measure the latency of a top half?

Answer:

  • Tools:
    • ftrace with irqsoff tracer to measure time interrupts are disabled.
    • perf to profile ISR execution time.
  • Metric: Aim for top-half execution time in microseconds.

Command:

echo irqsoff > /sys/kernel/debug/tracing/current_tracer
cat /sys/kernel/debug/tracing/trace

Q9: What is the difference between IRQF_SHARED and IRQF_PERCPU?

Answer:

  • IRQF_SHARED: Allows multiple devices to share the same IRQ line (e.g., PCI devices)
  • IRQF_PERCPU: The IRQ is mapped to a specific CPU (e.g., timer interrupts)

Use Case:

  • Use IRQF_SHARED for devices like USB controllers.
  • Use IRQF_PERCPU for CPU-local events (e.g., APIC timer).

Q10: How to safely exit a top half if the interrupt wasn’t for your device?

Answer:

  • Return IRQ_NONE in the ISR. This tells the kernel to continue probing other handlers for the shared IRQ.

Example:

irqreturn_t my_isr(int irq, void *dev_id) {
    if (!check_device_status(dev_id)) // Not our interrupt
        return IRQ_NONE;
    // Process interrupt
    return IRQ_HANDLED;
}

Key Takeaways for Interviews:

  1. Atomicity: Top halves run with interrupts disabled.
  2. No Blocking: Never call sleeping functions.
  3. Defer Work: Use bottom halves for non-urgent tasks.
  4. Concurrency: Shared IRQs require reentrancy checks.
  5. Debugging: Tools like ftrace and perf are essential.