📄 nmi-rcu.txt
字号:
Using RCU to Protect Dynamic NMI HandlersAlthough RCU is usually used to protect read-mostly data structures,it is possible to use RCU to provide dynamic non-maskable interrupthandlers, as well as dynamic irq handlers. This document describeshow to do this, drawing loosely from Zwane Mwaikambo's NMI-timerwork in "arch/i386/oprofile/nmi_timer_int.c" and in"arch/i386/kernel/traps.c".The relevant pieces of code are listed below, each followed by abrief explanation. static int dummy_nmi_callback(struct pt_regs *regs, int cpu) { return 0; }The dummy_nmi_callback() function is a "dummy" NMI handler that doesnothing, but returns zero, thus saying that it did nothing, allowingthe NMI handler to take the default machine-specific action. static nmi_callback_t nmi_callback = dummy_nmi_callback;This nmi_callback variable is a global function pointer to the currentNMI handler. fastcall void do_nmi(struct pt_regs * regs, long error_code) { int cpu; nmi_enter(); cpu = smp_processor_id(); ++nmi_count(cpu); if (!rcu_dereference(nmi_callback)(regs, cpu)) default_do_nmi(regs); nmi_exit(); }The do_nmi() function processes each NMI. It first disables preemptionin the same way that a hardware irq would, then increments the per-CPUcount of NMIs. It then invokes the NMI handler stored in the nmi_callbackfunction pointer. If this handler returns zero, do_nmi() invokes thedefault_do_nmi() function to handle a machine-specific NMI. Finally,preemption is restored.Strictly speaking, rcu_dereference() is not needed, since this code runsonly on i386, which does not need rcu_dereference() anyway. However,it is a good documentation aid, particularly for anyone attempting todo something similar on Alpha.Quick Quiz: Why might the rcu_dereference() be necessary on Alpha, given that the code referenced by the pointer is read-only?Back to the discussion of NMI and RCU... void set_nmi_callback(nmi_callback_t callback) { rcu_assign_pointer(nmi_callback, callback); }The set_nmi_callback() function registers an NMI handler. Note that anydata that is to be used by the callback must be initialized up -before-the call to set_nmi_callback(). On architectures that do not orderwrites, the rcu_assign_pointer() ensures that the NMI handler sees theinitialized values. void unset_nmi_callback(void) { rcu_assign_pointer(nmi_callback, dummy_nmi_callback); }This function unregisters an NMI handler, restoring the originaldummy_nmi_handler(). However, there may well be an NMI handlercurrently executing on some other CPU. We therefore cannot freeup any data structures used by the old NMI handler until executionof it completes on all other CPUs.One way to accomplish this is via synchronize_sched(), perhaps asfollows: unset_nmi_callback(); synchronize_sched(); kfree(my_nmi_data);This works because synchronize_sched() blocks until all CPUs completeany preemption-disabled segments of code that they were executing.Since NMI handlers disable preemption, synchronize_sched() is guaranteednot to return until all ongoing NMI handlers exit. It is therefore safeto free up the handler's data as soon as synchronize_sched() returns.Answer to Quick Quiz Why might the rcu_dereference() be necessary on Alpha, given that the code referenced by the pointer is read-only? Answer: The caller to set_nmi_callback() might well have initialized some data that is to be used by the new NMI handler. In this case, the rcu_dereference() would be needed, because otherwise a CPU that received an NMI just after the new handler was set might see the pointer to the new NMI handler, but the old pre-initialized version of the handler's data. More important, the rcu_dereference() makes it clear to someone reading the code that the pointer is being protected by RCU.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -