bashinterruptpollingnmi

How to wait for an NMI interrupt without polling?


To wait for an NMI interrupt, currently I do:

while true; do

  RES=$(cat /proc/interrupts | grep NMI)
  RES=$(echo "$RES" | sed 's/[^0-9]*//g')
  RES=$(echo "$RES" | sed 's/^0*//')

  if [ "$RES" != "" ]; then

    echo "DONE"
    exit 0
  
  fi

  sleep 1

done

Is there a nicer way to do this without polling or sleeping?


Solution

  • Waiting for an NMI interrupt without polling can be achieved by using an event-driven approach. Instead of constantly checking the /proc/interrupts file, you can use a signal handler to catch the NMI signal and execute the required actions when the signal is received. Sad but, you cannot directly catch an NMI interrupt in a user-space script, as NMI interrupts are reserved for kernel-level code. However, you can write a kernel module to handle NMI interrupts and pass a signal to your user-space script when an NMI occurs.

    Here is what you can do.. First: Write a kernel module to handle NMI interrupts. then when an NMI occurs, let the kernel module send a signal (e.g., SIGUSR1) to your user-space script. At last in your script, set up a signal handler to catch the signal sent by the kernel module and execute the required action. And check example below:

    handle_signal() {
    echo "NMI interrupt received"
    echo "DONE"
    exit 0
    }
    
    trap 'handle_signal' SIGUSR1
    start_nmi_module
    while true; do
        sleep 1
    done
    

    Answering the comment:

    hmm, if you are unable to modify the kernel or add modules to the machines, then yes, your current method is likely the best option. But, you can make a few optimizations to reduce the load because of polling:

    Example using awk:

    while true; do
      RES=$(awk '/NMI/ {for (i=2; i<=NF; i++) if ($i ~ /^[0-9]+$/) {sum+=$i}} END {print sum}' /proc/interrupts)
    
      if [ "$RES" != "" ] && [ "$RES" -ne "0" ]; then
        echo "DONE"
        exit 0
      fi
    
      sleep 5
    done
    

    This script uses awk to read the /proc/interrupts file and sum the NMI interrupt counts for all the CPU. If the total count is not zero, the script exits with "DONE". The sleep interval has been increased to 5 seconds, because it will reduce the frequency and system load.

    Answering second question:

    So sad but, without kernel modifications or access to kernel modules, your options for handling NMI interrupts in real-time are limited. But, you can try to strike a balance between delay and load by changing the sleep interval according. Example, use a smaller sleep interval such as 0.1 or something like that or maybe 0.5 seconds. This would decrease the delay handling, the interrupt while not excessively increasing the system load.

    while true; do
    
    
    RES=$(awk '/NMI/ {for (i=2; i<=NF; i++) if ($i ~ /^[0-9]+$/) {sum+=$i}} END {print sum}' /proc/interrupts)
    
      if [ "$RES" != "" ] && [ "$RES" -ne "0" ]; then
        echo "DONE"
        exit 0
      fi
    
      sleep 0.1
    done
    

    Remember that this is still a polling and there will always be a trade between response time and load. Choosing carefully the sleep interval you can find the balance for your specific case.