< Linux Kernel > Notification Chain

本文深入解析了通知链机制,介绍了其基本概念、不同类型的分类、结构体定义及使用方法,并通过inetaddr_chain通知链实例展示了如何实现注册与事件通知。

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

A notification chain is simply a list of functions to execute when a given event occurs. Each function lets one
other subsystem know about an event that occurred within, or was detected by, the subsystem calling the

function.


Thus, 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.


The functions executed are chosen by the notified subsystems. It is never up to the owner of the chain (the

subsystem that generates the notifications) to decide what functions to execute. The owner simply defines the

list; any kernel subsystem can register a callback function with that chain to receive the notification.


Notification Chain 首先是一个链表,类型如下:

struct notifier_block {
	int (*notifier_call)(struct notifier_block *, unsigned long, void *);
	struct notifier_block __rcu *next;
	int priority;
};

Notification Chain 的分类:

/*
 * 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.
 */

下面就 inetaddr_chain 通知链举例说明使用方法,

1) 定义

static BLOCKING_NOTIFIER_HEAD(inetaddr_chain);

#define BLOCKING_NOTIFIER_HEAD(name)				\
	struct blocking_notifier_head name =			\
		BLOCKING_NOTIFIER_INIT(name)

#define BLOCKING_NOTIFIER_INIT(name) {				\
		.rwsem = __RWSEM_INITIALIZER((name).rwsem),	\
		.head = NULL }

2)Register & Unregister API

int register_inetaddr_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&inetaddr_chain, nb);
}
EXPORT_SYMBOL(register_inetaddr_notifier);

int unregister_inetaddr_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_unregister(&inetaddr_chain, nb);
}

3)inetaddr_chain 通知链 注册


---- register_inetaddr_notifier Matches (13 in 12 files) ----
Clip.c (net\atm):    register_inetaddr_notifier(&clip_inet_notifier);
Devinet.c (net\ipv4):int register_inetaddr_notifier(struct notifier_block *nb)
Devinet.c (net\ipv4):EXPORT_SYMBOL(register_inetaddr_notifier);
Fib_frontend.c (net\ipv4):    register_inetaddr_notifier(&fib_inetaddr_notifier);
Inetdevice.h (include\linux):extern int register_inetaddr_notifier(struct notifier_block *nb);
ipt_MASQUERADE.c (net\ipv4\netfilter):        register_inetaddr_notifier(&masq_inet_notifier);
Main.c (net\mac80211):    result = register_inetaddr_notifier(&local->ifa_notifier);
Nes.c (drivers\infiniband\hw\nes):        register_inetaddr_notifier(&nes_inetaddr_notifier);
Netxen_nic_main.c (drivers\net\ethernet\qlogic\netxen):    register_inetaddr_notifier(&netxen_inetaddr_cb);
Protocol.c (net\sctp):    register_inetaddr_notifier(&sctp_inetaddr_notifier);
Qeth_l3_main.c (drivers\s390\net):    rc = register_inetaddr_notifier(&qeth_l3_ip_notifier);
Qlcnic_main.c (drivers\net\ethernet\qlogic\qlcnic):    register_inetaddr_notifier(&qlcnic_inetaddr_cb);
Via-velocity.c (drivers\net\ethernet\via):    register_inetaddr_notifier(&velocity_inetaddr_notifier);

void __init ip_fib_init(void)
{
	rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, NULL);
	rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL, NULL);
	rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib, NULL);

	register_pernet_subsys(&fib_net_ops);
	register_netdevice_notifier(&fib_netdev_notifier);
	register_inetaddr_notifier(&fib_inetaddr_notifier);

	fib_trie_init();
}

以 ip_fib_init() 为例,在此向 inetaddr_chain 通知链 注册(请求在发生event 的时候调用自己的Callback),

static struct notifier_block fib_inetaddr_notifier = {
    .notifier_call = fib_inetaddr_event,
};

之前提到了 struct notifier_block 结构体的类型,这个结构体类型有 priority,也就是优先级,注册到通知链如果不指定优先级的话,采取先注册先调用的原则,优先级高的链表元素插入在链表的前面,这样最终在通知链调用 callback 的时候直接从链表头遍历就行了。

这里注册的callback是

static int fib_inetaddr_event(struct notifier_block *this, unsigned long event, void *ptr)


4)inetaddr_chain 通知链 事件通知

接下来当发生 ipaddr 变化相关的操作时就会调用通知链通知的函数

如,

static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh,
			     u32 pid)
{
	struct in_device *in_dev = ifa->ifa_dev;
	struct in_ifaddr *ifa1, **ifap, **last_primary;

	/* Send message first, then call notifier.
	   Notifier will trigger FIB update, so that
	   listeners of netlink will know about new ifaddr */
	rtmsg_ifa(RTM_NEWADDR, ifa, nlh, pid);
	blocking_notifier_call_chain(&inetaddr_chain, NETDEV_UP, ifa);

总结, 通知链的分析从定义,内核子系统注册,通知,都是围绕 当前通知链 链表 + 通知链链表元素 进行,因此search 通知链链表就会一目了然,分析具体的元素就知道需要做什么事情了。



<think>我们正在配置maven-surefire-plugin来添加一个JUnit监听器,但是监听器没有生效。针对JUnit4,我们需要确保配置正确。 在Maven Surefire Plugin中,配置监听器的方式有两种:一种是通过`<properties>`配置(如示例),另一种是通过`<listeners>`配置(但这是用于TestNG的,JUnit4通常用`<properties>`)。 根据您提供的配置,您使用了`<properties>`来设置`listener`属性,这是正确的方式。但是,如果监听器没有生效,可能有以下几个原因: 1. **监听器类的全限定名是否正确?** 请确认`com.huawei.listener.TestLoggerListener`这个类确实存在,并且它的包名和类名完全匹配。 2. **监听器类是否在测试类路径中?** 确保包含监听器类的jar包或类文件已经被正确添加到项目的测试类路径中。如果监听器类是在当前项目中编写的,那么它应该位于`target/test-classes`目录下(或者如果是测试代码,则位于`src/test/java`中,编译后会在`target/test-classes`)。 3. **Maven Surefire Plugin版本问题**:您使用的版本是3.0.0-M7,这是一个里程碑版本,可能存在一些不稳定。建议使用稳定版本,例如3.0.0(正式版)或者更早的2.22.2(一个广泛使用的版本)。 4. **配置是否正确嵌套?** 在`<configuration>`标签内,使用`<properties>`来设置JUnit监听器是正确的。但是注意,`<additionalClasspathElements>`的配置可能会影响类路径,请确保没有覆盖掉监听器所在的路径。 5. **监听器类是否实现了`org.junit.runner.notification.RunListener`?** 对于JUnit4,监听器必须实现`RunListener`接口(或继承`org.junit.runner.notification.RunListener`)。 6. **是否有多个surefire插件配置?** 检查是否在同一个POM中有多个surefire插件的配置,后面的配置可能会覆盖前面的。 7. **父POM覆盖配置**:如果项目继承了一个父POM,父POM中可能已经配置了surefire插件,并且子模块的配置可能被覆盖。可以使用`<plugins>`中的`<plugin>`的`<inherited>false</inherited>`来避免父POM的配置被继承。 8. **监听器类是否公开且有无参构造函数?** 确保监听器类有一个无参数的构造函数,并且是public的。 9. **调试系统属性**:在测试中打印系统属性`listener`的值,看是否被正确设置。 下面是一个修改后的配置示例,使用稳定版本的surefire插件,并添加了调试系统属性的方法: 修改pom.xml: ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0</version> <!-- 使用稳定版本 --> <configuration> <!-- 如果需要,可以添加以下配置来打印更多信息 --> <printSummary>true</printSummary> <showProgress>true</showProgress> <properties> <property> <name>listener</name> <value>com.huawei.listener.TestLoggerListener</value> </property> </properties> </configuration> </plugin> </plugins> </build> ``` 在测试中打印系统属性(用于调试): ```java @Test public void testSystemProperty() { String listenerProperty = System.getProperty("listener"); System.out.println("listener property: " + listenerProperty); } ``` 如果上述方法都没有解决问题,可能需要检查Maven构建的调试日志: ``` mvn test -X ``` 在输出中搜索`listener`,看是否被正确设置。 另外,也可以尝试使用`<listeners>`标签(虽然这是TestNG的方式,但有时也可以尝试)来配置,但JUnit4通常不支持这种方式。不过,surefire插件提供了一种统一的方式来配置,可以同时支持JUnit和TestNG。对于JUnit4,正确的配置方式还是使用`<properties>`中的`listener`属性。 如果以上都无效,请检查监听器类的代码,确保它被正确编写,并且没有异常抛出导致加载失败。 下面是一个简单的监听器示例,用于验证: ```java package com.huawei.listener; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.RunListener; public class TestLoggerListener extends RunListener { @Override public void testStarted(Description description) throws Exception { System.out.println("Test started: " + description.getMethodName()); } } ``` 当测试运行时,如果监听器生效,每个测试开始时都会打印一条消息。 最后,如果项目是多模块项目,确保配置在正确的模块中。 如果问题仍然存在,请提供更多信息,例如监听器的实现代码和Maven构建的调试日志片段。 根据以上分析和解决方案,提出相关问题。</think>### 解决 Maven Surefire 监听器未生效问题(JUnit4 环境) 您的配置问题在于 **`<properties>` 标签在 Surefire 插件中的错误使用**。正确配置如下: ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M7</version> <configuration> <!-- 关键:使用 listeners 而非 properties --> <properties> <property> <name>listener</name> <value>com.huawei.listener.TestLoggerListener</value> </property> </properties> <!-- 确保测试类路径包含监听器 --> <additionalClasspathElements> <element>${project.build.testOutputDirectory}</element> </additionalClasspathElements> </configuration> </plugin> ``` #### 完整解决方案步骤: 1. **验证监听器实现**(确保符合 JUnit4 规范): ```java package com.huawei.listener; import org.junit.runner.notification.RunListener; import org.junit.runner.Description; import org.junit.runner.Result; public class TestLoggerListener extends RunListener { @Override public void testStarted(Description description) { System.out.println("[TEST START] " + description.getMethodName()); } @Override public void testFailure(Failure failure) { System.err.println("[TEST FAIL] " + failure.getDescription().getMethodName()); } } ``` 2. **添加必要依赖**(pom.xml): ```xml <dependencies> <!-- JUnit4 核心依赖 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <!-- 确保监听器类可见 --> <dependency> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> <version>${project.version}</version> <scope>test</scope> <classifier>tests</classifier> <!-- 如果监听器在测试代码中 --> </dependency> </dependencies> ``` 3. **调试命令**(终端执行): ```bash mvn test -Dtest=YourTestClass -X | grep -i "listener" # 检查输出是否包含: # [DEBUG] Using listener: com.huawei.listener.TestLoggerListener ``` 4. **备选方案**(如果配置仍无效): ```xml <configuration> <!-- 强制指定JUnit4 --> <dependencies> <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-junit4</artifactId> <version>3.0.0-M7</version> </dependency> </dependencies> <!-- 显式声明监听器 --> <listeners> <listener>com.huawei.listener.TestLoggerListener</listener> </listeners> </configuration> ``` #### 常见问题排查表: | 现象 | 解决方案 | |------|----------| | 类找不到 | 添加 `<classpathDependencyExcludes>` 确保无冲突 | | 版本冲突 | 强制指定 `surefire-junit4` 依赖 | | 多模块项目 | 在子模块添加 `build-helper-maven-plugin` | | 监听器无输出 | 在监听器中添加 `System.out` 调试语句 | | 并行测试问题 | 添加 `<parallel>none</parallel>` 临时禁用并行 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值