由
Linux Kernel(8)- Notification可以學會運用notifier,而這一篇會概述如何實現,基本上所謂的publish-and-subscribe pattern都是註冊callback function到某個list上去,某事件發生時,再將整個list的callback function執行過一次。
include/lunux/notifier.h
#define BLOCKING_NOTIFIER_HEAD(name) \
struct blocking_notifier_head name = \
BLOCKING_NOTIFIER_INIT(name)
struct blocking_notifier_head {
// 用於blocking機制時使用
// 可以於kernel/notifier.c看到以下註解
/*
* Blocking notifier chain routines. All access to the chain is
* synchronized by an rwsem.
*/
struct rw_semaphore rwsem;
// callback function之linking-list的頭
struct notifier_block __rcu *head;
};
// linking-list之node結構
struct notifier_block {
// callback function
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block __rcu *next;
// 用於註冊到list之優先順序, 數字越大 priority越高
int priority;
};
kernel/notifier.c
/**
* blocking_notifier_chain_register - Add notifier to a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain
* @n: New entry in notifier chain
*
* Adds a notifier to a blocking notifier chain.
* Must be called in process context.
* // 因為semaphore只能用在process context
*
* Currently always returns zero.
*/
int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
struct notifier_block *n)
{
int ret;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
*/
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n);
// 使用writer semaphore保護, 確保kernel synchronization
down_write(&nh->rwsem);
// 真正掛callback function到list的function
ret = notifier_chain_register(&nh->head, n);
up_write(&nh->rwsem);
return ret;
}
/*
* 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)
{
// nl指向list中的第一個node
while ((*nl) != NULL) {
// 比較list中的每一個node之priority,
// 如果發現新的比較大, 就break準備插到這個(*nl)的前面
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
// 將(*nl)串到新的後面
n->next = *nl;
// 將(*nl)取代成n
rcu_assign_pointer(*nl, n);
return 0;
}
/**
* blocking_notifier_chain_unregister - Remove notifier from a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain
* @n: Entry to remove from notifier chain
*
* Removes a notifier from a blocking notifier chain.
* Must be called from process context.
*
* Returns zero on success or %-ENOENT on failure.
*/
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,
struct notifier_block *n)
{
// 基本上這個function和blocking_notifier_chain_register()相同
int ret;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
*/
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_unregister(&nh->head, n);
// 使用writer semaphore保護, 確保kernel synchronization
down_write(&nh->rwsem);
// 真正移除callback function
ret = notifier_chain_unregister(&nh->head, n);
up_write(&nh->rwsem);
return ret;
}
static int notifier_chain_unregister(struct notifier_block **nl,
struct notifier_block *n)
{
while ((*nl) != NULL) {
if ((*nl) == n) {
// 找到n在list中的位置, 然後將之移除
rcu_assign_pointer(*nl, n->next);
return 0;
}
// 將nl往下一個移動
nl = &((*nl)->next);
}
return -ENOENT;
}
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v)
{
return __blocking_notifier_call_chain(nh, val, v, -1, NULL);
}
/**
* __blocking_notifier_call_chain - Call functions in a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain
* @val: Value passed unmodified to notifier function
* @v: Pointer passed unmodified to notifier function
* @nr_to_call: See comment for notifier_call_chain.
* @nr_calls: See comment for notifier_call_chain.
*
* Calls each function in a notifier chain in turn. The functions
* run in a process context, so they are allowed to block.
*
* If the return value of the notifier can be and'ed
* with %NOTIFY_STOP_MASK then blocking_notifier_call_chain()
* will return immediately, with the return value of
* the notifier function which halted execution.
* Otherwise the return value is the return value
* of the last notifier function called.
*/
int __blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v, int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
/*
* We check the head outside the lock, but if this access is
* racy then it does not matter what the result of the test
* is, we re-check the list after having taken the lock anyway:
*/
if (rcu_dereference_raw(nh->head)) {
down_read(&nh->rwsem);
// 真正執行list中所有callback function的API
ret = notifier_call_chain(&nh->head, val, v, nr_to_call,
nr_calls);
up_read(&nh->rwsem);
}
return ret;
}
/**
* notifier_call_chain - Informs the registered notifiers about an event.
* @nl: Pointer to head of the blocking notifier chain
* @val: Value passed unmodified to notifier function
* @v: Pointer passed unmodified to notifier function
* @nr_to_call: Number of notifier functions to be called. Don't care
* value of this parameter is -1.
* @nr_calls: Records the number of notifications sent. Don't care
* value of this field is NULL.
* @returns: notifier_call_chain returns the value returned by the
* last notifier function called.
*/
static int __kprobes notifier_call_chain(struct notifier_block **nl,
unsigned long val, void *v, int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb, *next_nb;
nb = rcu_dereference_raw(*nl);
// 由blocking_notifier_call_chain傳進來的nr_to_call為-1,
// 由於nr_to_call只會--, 所以nr_to_call就是always成立
// 於是停止的條件只剩下nb為NULL
while (nb && nr_to_call) {
// ??這段的用意就不是很明瞭了??
// 為啥不在後面在nb = nb->next?
next_nb = rcu_dereference_raw(nb->next);
#ifdef CONFIG_DEBUG_NOTIFIERS
if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
WARN(1, "Invalid notifier called!");
nb = next_nb;
continue;
}
#endif
// 執行callback function
ret = nb->notifier_call(nb, val, v);
if (nr_calls)
(*nr_calls)++;
// 如果帶有 STOP的bit就停止執行後面的callback function
if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
break;
nb = next_nb;
nr_to_call--;
}
return ret;
}
這篇文章還不算完成,後面在補充啦~~