notifier chain

本文深入探讨了Linux内核中用于实现高效交互的通知链机制,包括不同类型的通知链(原子通知链、阻塞通知链、原始通知链、SRCU通知链),以及注册和注销流程,特别强调了通知链在不同场景下的应用与优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


//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);
内容概要:本文深入探讨了DevOps流程落地中自动化测试与监控体系的构建,强调二者是保障软件质量和系统稳定性的重要支柱。自动化测试涵盖从单元测试到端到端测试的全流程自动化,而监控体系则通过实时采集和分析系统数据,及时发现并解决问题。文章介绍了测试金字塔模型的应用、监控指标的分层设计、测试与生产环境的一致性构建以及告警策略的精细化设置等核心技巧。此外,还提供了基于Python和Prometheus的具体代码案例,包括自动化接口测试脚本和监控指标暴露的实现,展示了如何在实际项目中应用这些技术和方法。 适合人群:对DevOps有一定了解,从事软件开发、运维或测试工作的技术人员,特别是那些希望提升自动化测试和监控能力的从业者。 使用场景及目标:①高并发业务系统中,模拟大规模用户请求,验证系统抗压能力和稳定性;②关键业务流程保障,确保金融交易、医疗数据处理等敏感业务的合规性和可追溯性;③微服务架构系统下,通过契约测试和分布式链路追踪,保证服务间的兼容性和故障快速定位。 阅读建议:本文不仅提供了理论指导,还有详细的代码示例,建议读者结合自身项目的实际情况,逐步实践文中提到的技术和方法,特别是在构建自动化测试框架和监控系统时,关注环境一致性、测试覆盖率和性能指标等方面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值