//Linux is a monolithic kernel. Its subsystems or modules help to keep the kernel light by being flexible enough to
//load and unload at runtime. In most cases, the kernel modules are interconnected to one another. An event
//captured by a certain module might be of interest to another module.
//Typically, communication systems implement request-reply messaging, or polling. In such models, a program that
//receives a request will have to send the data available since the last transaction. Such methods sometimes
//require high bandwidth or they waste polling cycles.
//To fulfill the need for interaction, Linux uses so called notification chains. These notifier chains work in a
//Publish-Subscribe model. This model is more effective when compared to polling or the request-reply model.
//For each notification chain there is a passive side (the notified) and an active side (the notifier), as in the
//so-called publish-and-subscribe model:
// •The notified are the subsystems that ask to be notified about the event and that provide a callback function
// to invoke.
// •The notifier is the subsystem that experiences an event and calls the callback function.
//struct notifier_block
//The elements of the notification chain's list are of type notifier_block:
/*
* Notifier chains are of four types:
*
* Atomic notifier chains: Chain callbacks run in interrupt/atomic
* context. Callouts are not allowed to block.
* Blocking notifier chains: Chain callbacks run in process context.
* Callouts are allowed to block.
* Raw notifier chains: There are no restrictions on callbacks,
* registration, or unregistration. All locking and protection
* must be provided by the caller.
* SRCU notifier chains: A variant of blocking notifier chains, with
* the same restrictions.
*
* atomic_notifier_chain_register() may be called from an atomic context,
* but blocking_notifier_chain_register() and srcu_notifier_chain_register()
* must be called from a process context. Ditto for the corresponding
* _unregister() routines.
*
* atomic_notifier_chain_unregister(), blocking_notifier_chain_unregister(),
* and srcu_notifier_chain_unregister() _must not_ be called from within
* the call chain.
*
* SRCU notifier chains are an alternative form of blocking notifier chains.
* They use SRCU (Sleepable Read-Copy Update) instead of rw-semaphores for
* protection of the chain links. This means there is _very_ low overhead
* in srcu_notifier_call_chain(): no cache bounces and no memory barriers.
* As compensation, srcu_notifier_chain_unregister() is rather expensive.
* SRCU notifier chains should be used when the chain will be called very
* often but notifier_blocks will seldom be removed. Also, SRCU notifier
* chains are slightly more difficult to use because they require special
* runtime initialization.
*/
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block __rcu *next;
int priority;
};
// •notifier_call - function to execute.
// •next - used to link together the elements of the list.
// •priority - the priority of the function. Functions with higher priority are executed first. But in practice,
// almost all registrations leave the priority out of the notifier_block definition, which means it
// gets the default value of 0 and execution order ends up depending only on the registration order
// (i.e., it is a semirandom order).
/*
* The notifier_block data structure is a simple linked list of function pointers. The function pointers are
* registered with ‘functions’ that are to be called when an event occurs. Each module needs to maintain a
* notifier list. The functions are registered to this notification list. The notification module (publisher)
* maintains a list head that is used to manage and traverse the notifier block list. The function that subscribes
* to a module is added to the head of the module’s list by using the register_xxxxxx_notifier API and deletion
* from the list is done using unregister_xxxxxx_notifier.
*/
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block __rcu *head;
};
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block __rcu *head;
};
struct raw_notifier_head {
struct notifier_block __rcu *head;
};
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block __rcu *head;
};
//Registering and Un-registering with a Chain
//When a kernel component is interested in the events of a given notification chain, it can register it with the
//general function notifier_chain_register and to unregister we can use the function notifier_chain_unregister.
//NOTE: The kernel also provides a set of wrappers around notifier_chain_register. We might have to use those
//wrapper functions instead of directly using the notification_chain_register.
//For each chain, the notifier_block instances are inserted into a list, which is sorted by priority. Elements with
//the same priority are sorted based on insertion time: new ones go to the tail. Accesses to the notification
//chains are protected by the notifier_lock lock. The use of a single lock for all the notification chains is not a
//big constraint and does not affect performance, because subsystems usually register their notifier_call functions
//only at boot time or at module load time, and from that moment on access the lists in a read-only manner (that
//is, shared).
//Because the notifier_chain_register function is called to insert callbacks into all lists, it requires that the
//list be specified as an input parameter. However, this function is rarely called directly; generic wrappers are
//used instead.
//Notifier chain core routines. The exported routines below are layered on top of these,
//with appropriate locking added.
static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n)
{
while ((*nl) != NULL) {
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
rcu_assign_pointer(*nl, n);
return 0;
}
static int notifier_chain_cond_register(struct notifier_block **nl, struct notifier_block *n)
{
while ((*nl) != NULL) {
if ((*nl) == n)
return 0;
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
rcu_assign_pointer(*nl, n);
return 0;
}
static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
{
while ((*nl) != NULL) {
if ((*nl) == n) {
rcu_assign_pointer(*nl, n->next);
return 0;
}
nl = &((*nl)->next);
}
return -ENOENT;
}
//Types of Notifier Chains
//Notifier chains are broadly classified based on the context in which they are executed and the lock/protect
//mechanism of the calling chain. Based on the need of the module, the notifiers can be executed in the process
//context or interrupt/atomic context. Thus, notifier chains are classified into four types:
int atomic_notifier_chain_register (struct atomic_notifier_head *nh, struct notifier_block *n)
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *n)
int raw_notifier_chain_register (struct raw_notifier_head *nh, struct notifier_block *n)
int srcu_notifier_chain_register (struct srcu_notifier_head *nh, struct notifier_block *n)
//SRCU -- Sleepable Read Copy Update
//1. Atomic Notifier Chains
//As the name indicates, this notifier chain is executed in interrupt or atomic context. Normally, events that
//are time critical use this notifier. This also means it is a non-blockable call. Linux modules use atomic
//notifier chains to inform watchdog timers or message handlers.
int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *n)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&nh->lock, flags);
ret = notifier_chain_register(&nh->head, n);
spin_unlock_irqrestore(&nh->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(atomic_notifier_chain_register);
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
struct notifier_block *n)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&nh->lock, flags);
ret = notifier_chain_unregister(&nh->head, n);
spin_unlock_irqrestore(&nh->lock, flags);
synchronize_rcu();
return ret;
}
EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister);
//2. Blocking Notifier chains
//A blocking notifier chain runs in the process context. The calls in the notification list could be blocked as
//it runs in the process context. Notifications that are not highly time critical could use blocking notifier
//chains.
//Linux modules use blocking notifier chains to inform the modules on a change in QOS value or the addition of a
//new device.
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *n)
{
int ret;
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n);
down_write(&nh->rwsem);
ret = notifier_chain_register(&nh->head, n);
up_write(&nh->rwsem);
return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_chain_register);
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *n)
{
int ret;
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_unregister(&nh->head, n);
down_write(&nh->rwsem);
ret = notifier_chain_unregister(&nh->head, n);
up_write(&nh->rwsem);
return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_chain_unregister);
//3. Raw Notifier chains
//A raw notifier chain does not manage the locking and protection of the callers. Also, there are no restrictions
//on callbacks, registration, or de-registration. It provides flexibility to the user to have individual lock and
//protection mechanisms.
//Linux uses the raw notifier chain in low-level events.
int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *n)
{
return notifier_chain_register(&nh->head, n);
}
EXPORT_SYMBOL_GPL(raw_notifier_chain_register);
int raw_notifier_chain_unregister(struct raw_notifier_head *nh, struct notifier_block *n)
{
return notifier_chain_unregister(&nh->head, n);
}
EXPORT_SYMBOL_GPL(raw_notifier_chain_unregister);
//4. SRCU Notifier chains
//Sleepable Read Copy Update (SRCU) notifier chains are similar to the blocking notifier chain and run in the
//process context. It differs in the way it handles locking and protection. The SRCU methodology brings in less
//overhead when we notify the registered callers. On the flip side, it consumes more resource while unregistering.
//So it is advisable to choose this methodology where we use the notifier call often and where there’s very little
//requirement for removing from the chain.
int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *n)
{
int ret;
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n);
mutex_lock(&nh->mutex);
ret = notifier_chain_register(&nh->head, n);
mutex_unlock(&nh->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(srcu_notifier_chain_register);
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *n)
{
int ret;
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_unregister(&nh->head, n);
mutex_lock(&nh->mutex);
ret = notifier_chain_unregister(&nh->head, n);
mutex_unlock(&nh->mutex);
synchronize_srcu(&nh->srcu);
return ret;
}
EXPORT_SYMBOL_GPL(srcu_notifier_chain_unregister);