Linux 内核的通知链
Introduction
The Linux kernel is huge piece of C code which consists from many different subsystems. Each subsystem has its own purpose which is independent of other subsystems. But often one subsystem wants to know something from other subsystem(s). There is special mechanism in the Linux kernel which allows to solve this problem partly. The name of this mechanism is - notification chains
and its main purpose to provide a way for different subsystems to subscribe on asynchronous events from other subsystems. Note that this mechanism is only for communication inside kernel, but there are other mechanisms for communication between kernel and userspace.
Before we consider notification chains
API and implementation of this API, let's look at Notification chains
mechanism from theoretical side as we did it in other parts of this book. Everything which is related to notification chains
mechanism is located in the include/linux/notifier.h header file and kernel/notifier.c source code file. So let's open them and start to dive.
Notification Chains related data structures
Let's start to consider notification chains
mechanism from related data structures. As I wrote above, main data structures should be located in the include/linux/notifier.h header file, so the Linux kernel provides generic API which does not depend on certain architecture. In general, the notification chains
mechanism represents a list (that's why it's named chains
) of callback functions which are will be executed when an event will be occurred.
All of these callback functions are represented as notifier_fn_t
type in the Linux kernel:
So we may see that it takes three following arguments:
nb
- is linked list of function pointers (will see it now);action
- is type of an event. A notification chain may support multiple events, so we need this parameter to distinguish an event from other events;data
- is storage for private information. Actually it allows to provide additional data information about an event.
Additionally we may see that notifier_fn_t
returns an integer value. This integer value maybe one of:
NOTIFY_DONE
- subscriber does not interested in notification;NOTIFY_OK
- notification was processed correctly;NOTIFY_BAD
- something went wrong;NOTIFY_STOP
- notification is done, but no further callbacks should be called for this event.
All of these results defined as macros in the include/linux/notifier.h header file:
Where NOTIFY_STOP_MASK
represented by the:
macro and means that callbacks will not be called during next notifications.
Each part of the Linux kernel which wants to be notified on a certain event will should provide own notifier_fn_t
callback function. Main role of the notification chains
mechanism is to call certain callbacks when an asynchronous event occurred.
The main building block of the notification chains
mechanism is the notifier_block
structure:
which is defined in the include/linux/notifier.h file. This struct contains pointer to callback function - notifier_call
, link to the next notification callback and priority
of a callback function as functions with higher priority are executed first.
The Linux kernel provides notification chains of four following types:
Blocking notifier chains;
SRCU notifier chains;
Atomic notifier chains;
Raw notifier chains.
Let's consider all of these types of notification chains by order:
In the first case for the blocking notifier chains
, callbacks will be called/executed in process context. This means that the calls in a notification chain may be blocked.
The second SRCU notifier chains
represent alternative form of blocking notifier chains
. In the first case, blocking notifier chains uses rw_semaphore
synchronization primitive to protect chain links. SRCU
notifier chains run in process context too, but uses special form of RCU mechanism which is permissible to block in an read-side critical section.
In the third case for the atomic notifier chains
runs in interrupt or atomic context and protected by spinlock synchronization primitive. The last raw notifier chains
provides special type of notifier chains without any locking restrictions on callbacks. This means that protection rests on the shoulders of caller side. It is very useful when we want to protect our chain with very specific locking mechanism.
If we will look at the implementation of the notifier_block
structure, we will see that it contains pointer to the next
element from a notification chain list, but we have no head. Actually a head of such list is in separate structure depends on type of a notification chain. For example for the blocking notifier chains
:
or for atomic notification chains
:
Now as we know a little about notification chains
mechanism let's consider implementation of its API.
Notification Chains
Usually there are two sides in a publish/subscriber mechanisms. One side who wants to get notifications and other side(s) who generates these notifications. We will consider notification chains mechanism from both sides. We will consider blocking notification chains
in this part, because of other types of notification chains are similar to it and differ mostly in protection mechanisms.
Before a notification producer is able to produce notification, first of all it should initialize head of a notification chain. For example let's consider notification chains related to kernel loadable modules. If we will look in the kernel/module.c source code file, we will see following definition:
which defines head for loadable modules blocking notifier chain. The BLOCKING_NOTIFIER_HEAD
macro is defined in the include/linux/notifier.h header file and expands to the following code:
So we may see that it takes name of a name of a head of a blocking notifier chain and initializes read/write semaphore and set head to NULL
. Besides the BLOCKING_INIT_NOTIFIER_HEAD
macro, the Linux kernel additionally provides ATOMIC_INIT_NOTIFIER_HEAD
, RAW_INIT_NOTIFIER_HEAD
macros and srcu_init_notifier
function for initialization atomic and other types of notification chains.
After initialization of a head of a notification chain, a subsystem which wants to receive notification from the given notification chain should register with certain function which depends on the type of notification. If you will look in the include/linux/notifier.h header file, you will see following four function for this:
As I already wrote above, we will cover only blocking notification chains in the part, so let's consider implementation of the blocking_notifier_chain_register
function. Implementation of this function is located in the kernel/notifier.c source code file and as we may see the blocking_notifier_chain_register
takes two parameters:
nh
- head of a notification chain;nb
- notification descriptor.
Now let's look at the implementation of the blocking_notifier_chain_register
function:
As we may see it just returns result of the notifier_chain_register
function from the same source code file and as we may understand this function does all job for us. Definition of the notifier_chain_register
function looks:
As we may see implementation of the blocking_notifier_chain_register
is pretty simple. First of all there is check which check current system state and if a system in rebooting state we just call the notifier_chain_register
. In other way we do the same call of the notifier_chain_register
but as you may see this call is protected with read/write semaphores. Now let's look at the implementation of the notifier_chain_register
function:
This function just inserts new notifier_block
(given by a subsystem which wants to get notifications) to the notification chain list. Besides subscribing on an event, subscriber may unsubscribe from a certain events with the set of unsubscribe
functions:
When a producer of notifications wants to notify subscribers about an event, the *.notifier_call_chain
function will be called. As you already may guess each type of notification chains provides own function to produce notification:
Let's consider implementation of the blocking_notifier_call_chain
function. This function is defined in the kernel/notifier.c source code file:
and as we may see it just returns result of the __blocking_notifier_call_chain
function. As we may see, the blocking_notifer_call_chain
takes three parameters:
nh
- head of notification chain list;val
- type of a notification;v
- input parameter which may be used by handlers.
But the __blocking_notifier_call_chain
function takes five parameters:
Where nr_to_call
and nr_calls
are number of notifier functions to be called and number of sent notifications. As you may guess the main goal of the __blocking_notifer_call_chain
function and other functions for other notification types is to call callback function when an event occurs. Implementation of the __blocking_notifier_call_chain
is pretty simple, it just calls the notifier_call_chain
function from the same source code file protected with read/write semaphore:
and returns its result. In this case all job is done by the notifier_call_chain
function. Main purpose of this function is to inform registered notifiers about an asynchronous event:
That's all. In general all looks pretty simple.
Now let's consider on a simple example related to loadable modules. If we will look in the kernel/module.c. As we already saw in this part, there is:
definition of the module_notify_list
in the kernel/module.c source code file. This definition determines head of list of blocking notifier chains related to kernel modules. There are at least three following events:
MODULE_STATE_LIVE
MODULE_STATE_COMING
MODULE_STATE_GOING
in which maybe interested some subsystems of the Linux kernel. For example tracing of kernel modules states. Instead of direct call of the atomic_notifier_chain_register
, blocking_notifier_chain_register
and etc., most notification chains come with a set of wrappers used to register to them. Registration on these modules events is going with the help of such wrapper:
If we will look in the kernel/tracepoint.c source code file, we will see such registration during initialization of tracepoints:
Where tracepoint_module_nb
provides callback function:
When one of the MODULE_STATE_LIVE
, MODULE_STATE_COMING
or MODULE_STATE_GOING
events occurred. For example the MODULE_STATE_LIVE
the MODULE_STATE_COMING
notifications will be sent during execution of the init_module system call. Or for example MODULE_STATE_GOING
will be sent during execution of the delete_module system call
:
Thus when one of these system call will be called from userspace, the Linux kernel will send certain notification depending on a system call and the tracepoint_module_notify
callback function will be called.
That's all.
Links
最后更新于