Linux內核基礎--事件通知鏈(notifier chain)

原文

1.1. 概述

Linux內核中各個子系統相互依賴,當其中某個子系統狀態發生改變時,就必須使用一定的機制告知使用其服務的其他子系統,以便其他子系統採取相應的措施。爲滿足這樣的需求,內核實現了事件通知鏈機制(notificationchain)。

通知鏈只能用在各個子系統之間,而不能在內核和用戶空間進行事件的通知。組成內核的核心繫統代碼均位於kernel目錄下,通知鏈表位於kernel/notifier.c中,對應的頭文件爲include/linux/notifier.h。通知鏈表機制並不複雜,實現它的代碼只有區區幾百行。

事件通知鏈表是一個事件處理函數的列表,每個通知鏈都與某個或某些事件有關,當特定的事件發生時,就調用相應的事件通知鏈中的回調函數,進行相應的處理。

推薦閱讀:

Linux內核驅動開發之KGDB原理介紹及kgdboe方式配置 http://www.linuxidc.com/Linux/2013-06/86406.htm

Linux內核中memcpy和memmove函數的區別和實現 http://www.linuxidc.com/Linux/2013-06/85344.htm

 

圖 1 內核通知鏈

1.2.數據結構

如圖 1中所示,Linux的網絡子系統一共有3個通知鏈:表示ipv4地址發生變化時的inetaddr_chain;表示ipv6地址發生變化的inet6addr_chain;還有表示設備註冊、狀態變化的netdev_chain。

在這些鏈中都是一個個notifier_block結構:

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


其中,

1. notifier_call:當相應事件發生時應該調用的函數,由被通知方提供,如other_subsys_1;

2. notifier_block *next:用於鏈接成鏈表的指針;

3. priority:回調函數的優先級,一般默認爲0。

內核代碼中一般把通知鏈命名爲xxx_chain, xxx_nofitier_chain這種形式的變量名。圍繞核心數據結構notifier_block,內核定義了四種通知鏈類型:

1. 原子通知鏈( Atomic notifier chains ):通知鏈元素的回調函數(當事件發生時要執行的函數)在中斷或原子操作上下文中運行,不允許阻塞。對應的鏈表頭結構:

struct atomic_notifier_head {
        spinlock_t  lock;
        struct  notifier_block *head;
};

2. 可阻塞通知鏈( Blocking notifier chains ):通知鏈元素的回調函數在進程上下文中運行,允許阻塞。對應的鏈表頭:

struct  blocking_notifier_head {
        struct  rw_semaphore  rwsem;
        struct  notifier_block  *head;
};

3. 原始通知鏈( Raw notifierchains ):對通知鏈元素的回調函數沒有任何限制,所有鎖和保護機制都由調用者維護。對應的鏈表頭:

 

網絡子系統就是該類型,通過以下宏實現head的初始化

static RAW_NOTIFIER_HEAD(netdev_chain);
#define RAW_NOTIFIER_INIT(name)    {      \
              .head= NULL }
#define RAW_NOTIFIER_HEAD(name)        \      //調用他就好了
struct raw_notifier_head name =        \
                    RAW_NOTIFIER_INIT(name)  
即:
struct raw_notifier_head netdev_chain = {
          .head = NULL;
}

而其回調函數的註冊,比如向netdev_chain的註冊函數:register_netdevice_notifier。

struct  raw_notifier_head {
        struct  notifier_block  *head;
};

4. SRCU 通知鏈( SRCU notifier chains ):可阻塞通知鏈的一種變體。對應的鏈表頭:

struct  srcu_notifier_head {
        struct  mutex mutex;
        struct  srcu_struct  srcu;
        struct  notifier_block  *head;
};

1.3. 運行機理

 

被通知一方(other_subsys_x)通過notifier_chain_register向特定的chain註冊回調函數,並且一般而言特定的子系統會用特定的notifier_chain_register包裝函數來註冊,比如路由子系統使用的是網絡子系統的:register_netdevice_notifier來註冊他的notifier_block。

1.3.1. 向事件通知鏈註冊的步驟

1. 申明struct notifier_block結構

2. 編寫notifier_call函數

3. 調用特定的事件通知鏈的註冊函數,將notifier_block註冊到通知鏈中

如果內核組件需要處理夠某個事件通知鏈上發出的事件通知,其就該在初始化時在該通知鏈上註冊回調函數。

1.3.2. 通知子系統有事件發生

inet_subsys是通過notifier_call_chain來通知其他的子系統(other_subsys_x)的。

notifier_call_chain會按照通知鏈上各成員的優先級順序執行回調函數(notifier_call_x);回調函數的執行現場在notifier_call_chain進程地址空間;其返回值是NOTIFY_XXX的形式,在include/linux/notifier.h中:

#define NOTIFY_DONE            0x0000        /* 對事件視而不見 */
#define NOTIFY_OK          0x0001        /* 事件正確處理 */
#define NOTIFY_STOP_MASK  0x8000        /*由notifier_call_chain檢查,看繼續調用回調函數,還是停止,_BAD和_STOP中包含該標誌 */
#define NOTIFY_BAD        (NOTIFY_STOP_MASK|0x0002)      /*事件處理出錯,不再繼續調用回調函數 */
/*
 *Clean way to return from the notifier and stop further calls.
 */
#define NOTIFY_STOP            (NOTIFY_OK|NOTIFY_STOP_MASK)    /*  回調出錯,不再繼續調用該事件回調函數 */

notifier_call_chain捕獲並返回最後一個事件處理函數的返回值;注意:notifier_call_chain可能同時被不同的cpu調用,故而調用者必須保證互斥。

1.3.3. 事件列表

對於網絡子系統而言,其事件常以NETDEV_XXX命名;描述了網絡設備狀態(dev->flags)、傳送隊列狀態(dev->state)、設備註冊狀態(dev->reg_state),以及設備的硬件功能特性(dev->features):

include/linux/notifier.h中

/* netdevice notifier chain */
#define NETDEV_UP  0x0001  /* 激活一個網絡設備 */
#define NETDEV_DOWN  0x0002f /* 停止一個網絡設備,所有對該設備的引用都應釋放 */
#define NETDEV_REBOOT      0x0003      /* 檢查到網絡設備接口硬件崩潰,硬件重啓 */
#define NETDEV_CHANGE      0x0004  /* 網絡設備的數據包隊列狀態發生改變 */
#define NETDEV_REGISTER  0x0005  /*一個網絡設備事例註冊到系統中,但尚未激活 */
#define NETDEV_UNREGISTER      0x0006      /*網絡設備驅動已卸載 */
#define NETDEV_CHANGEMTU      0x0007  /*MTU發生了改變 */
#define NETDEV_CHANGEADDR    0x0008  /*硬件地址發生了改變 */
#define NETDEV_GOING_DOWN  0x0009  /*網絡設備即將註銷,有dev->close報告,通知相關子系統處理 */
#define NETDEV_CHANGENAME  0x000A  /*網絡設備名改變 */
#define NETDEV_FEAT_CHANGE    0x000B  /*feature網絡硬件功能改變 */
#define NETDEV_BONDING_FAILOVER 0x000C  /*    */
#define NETDEV_PRE_UP        0x000D  /*    */
#define NETDEV_BONDING_OLDTYPE  0x000E              /*    */
#define NETDEV_BONDING_NEWTYPE  0x000F      /*    */

1.4. 簡單一例:

通過上面所述,notifier_chain機制只能在內核個子系統間使用,因此,這裏使用3個模塊:test_notifier_chain_0、test_notifier_chain_1、test_notifier_chain_2;當 test_notifier_chain_2通過module_init初始化模塊時發出事件TESTCHAIN_2_INIT;然後 test_notifier_chain_1作出相應的處理:打印 test_notifier_chain_2正在初始化。

/* test_chain_0.c :0. 申明一個通知鏈;1. 向內核註冊通知鏈;2. 定義事件; 3. 導出符號,因而必需最後退出*/

#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */

#define TESTCHAIN_INIT 0x52U
static RAW_NOTIFIER_HEAD(test_chain);

/* define our own notifier_call_chain */
static int call_test_notifiers(unsigned long val, void *v)
{
return raw_notifier_call_chain(&test_chain, val, v);
}
EXPORT_SYMBOL(call_test_notifiers);

/* define our own notifier_chain_register func */
static int register_test_notifier(struct notifier_block *nb)
{
int err;
err = raw_notifier_chain_register(&test_chain, nb);

if(err)
goto out;

out:
return err;
}

EXPORT_SYMBOL(register_test_notifier);

static int __init test_chain_0_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_0\n");

return 0;
}

static void __exit test_chain_0_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_chain_0\n");
// call_test_notifiers(TESTCHAIN_EXIT, (int *)NULL);
}

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("fishOnFly");

module_init(test_chain_0_init);
module_exit(test_chain_0_exit);

/* test_chain_1.c :1. 定義回調函數;2. 定義notifier_block;3. 向chain_0註冊notifier_block;*/
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>

#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */

extern int register_test_notifier(struct notifier_block *nb);
#define TESTCHAIN_INIT 0x52U

/* realize the notifier_call func */
int test_init_event(struct notifier_block *nb, unsigned long event,
void *v)
{
switch(event){
case TESTCHAIN_INIT:
printk(KERN_DEBUG "I got the chain event: test_chain_2 is on the way of init\n");
break;

default:
break;
}

return NOTIFY_DONE;
}
/* define a notifier_block */
static struct notifier_block test_init_notifier = {
.notifier_call = test_init_event,
};
static int __init test_chain_1_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_1\n");
register_test_notifier(&test_init_notifier);<SPAN style="WHITE-SPACE: pre"> </SPAN>// 由chain_0提供的設施
return 0;
}

static void __exit test_chain_1_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_clain_l\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fishOnFly");

module_init(test_chain_1_init);
module_exit(test_chain_1_exit);

/* test_chain_2.c:發出通知鏈事件*/

#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */

extern int call_test_notifiers(unsigned long val, void *v);
#define TESTCHAIN_INIT 0x52U

static int __init test_chain_2_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_2\n");
call_test_notifiers(TESTCHAIN_INIT, "no_use");

return 0;
}

static void __exit test_chain_2_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_chain_2\n");
}

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("fishOnFly");

module_init(test_chain_2_init);
module_exit(test_chain_2_exit);

# Makefile

# Comment/uncomment the following line to disable/enable debugging
# DEBUG = y


# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
else
DEBFLAGS = -O2
endif


ifneq ($(KERNELRELEASE),)
# call from kernel build system

obj-m := test_chain_0.o test_chain_1.o test_chain_2.o

else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

 

clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

depend .depend dep:
$(CC) $(CFLAGS) -M *.c > .depend


ifeq (.depend,$(wildcard .depend))
include .depend
endif

[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_0.ko
[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_1.ko
[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_2.ko

[wang2@iwooing: notifier_chian]$ dmesg

[ 5950.112649] I'm in test_chain_0
[ 5956.766610] I'm in test_chain_1
[ 5962.570003] I'm in test_chain_2
[ 5962.570008] I got the chain event: test_chain_2 is on the way of init

[ 6464.042975] Goodbye to test_chain_2
[ 6466.368030] Goodbye to test_clain_l
[ 6468.371479] Goodbye to test_chain_0


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章