首頁 > 軟體

Linux通知鏈機制及範例

2020-06-16 16:34:57

 

Linux核心中各個子系統相互依賴,當其中某個子系統狀態發生改變時,要使用一定的機制告知使用其服務的其他子系統,以便其他子系統採取相應的措施。核心實現了事件通知鏈機制(notification chain)。

 

通知鏈只能用在各個子系統之間,而不能在核心和使用者空間進行事件的通知。

 

通知鏈是一個函數列表,當給定事件發生的時候予以執行。每條通知鏈都有被通知者和擁有者。擁有者定義列表,被通知的子系統選擇要執行的函數。網路子系統有3個通知鏈,如下圖:

 

1.  資料結構   
Linux網路子系統中有3個通知鏈,表示ipv4地址傳送變化時的inetaddr_chain,表示ipv6地址發生變化的inet6addr_chain,表示裝置註冊、狀態變化的netdev_chain。

 

      鏈中都是一個個notifier_block結構。

 

任何核心子系統都可以對該鏈條註冊的一個回撥函數以接收通知資訊。

 

      通知鏈列表元素的型別是notifier_block

 

      定義在include/linux/notifier.h檔案中。

 

struct notifier_block {

 

        notifier_fn_t notifier_call;

 

        struct notifier_block __rcu *next;

 

        int priority;

 

};

 

      notifier_call是要執行的函數,由被通知方提供,next用於連結列表的元素,而priority代表的是該函數的優先順序。

 

      通用函數notifier_chain_register予以註冊,定義在kernel/notifier.c。

 

Linux核心中通知鏈,一般命名為xxx_chain或者,xxx_notifier_chian。核心有四種型別的通知鏈連結串列表頭。

 

  • 原子通知鏈( Atomic notifier chains ):通知鏈元素的回撥函數(當事件發生時要執行的函數)在中斷或原子操作上下文中執行,不允許阻塞。對應的連結串列頭結構:atomic_notifier_head
  • 可阻塞通知鏈( Blocking notifier chains ):通知鏈元素的回撥函數在進程上下文中執行,允許阻塞。對應的連結串列頭:blocking_notifier_head
  • 原始通知鏈( Raw notifierchains ):對通知鏈元素的回撥函數沒有任何限制,所有鎖和保護機制都由呼叫者維護。對應的連結串列頭:raw_notifier_head,網路子系統就是該型別
  • SRCU 通知鏈( SRCU notifier chains ):可阻塞通知鏈的一種變體。對應的連結串列頭:srcu_notifier_head.

 

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;

 

};

 

2.    註冊回撥函數
被通知一方(other_subsys_x)通過notifier_chain_register向特定的chain註冊回撥函數,一般子系統會用特定的notifier_chain_register包裝函數來註冊,如網路子系統是使用register_netdevice_notifier來註冊他的notifier_block。

 

3.    使用範例
向事件通知鏈註冊步驟如下:

 

1. 申明struct notifier_block結構

 

2. 編寫notifier_call函數

 

3. 呼叫事件通知鏈的註冊函數,將notifier_block註冊到通知鏈中

 

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

 

3.1    通知子系統
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          /* Don't care */

 

#define NOTIFY_OK              0x0001          /* Suits me */

 

#define NOTIFY_STOP_MASK        0x8000          /* Don't call further */

 

#define NOTIFY_BAD              (NOTIFY_STOP_MASK|0x0002)

 

                                                /* Bad/Veto action */

 

notifier_call_chain捕獲並返回最後一個事件處理常式的返回值, 並可能同時被不同的cpu呼叫,呼叫者須保證互斥。

 

3.2    事件連結串列
對於網路子系統而言,事件常以NETDEV_XXX命名,用於描述網路裝置狀態(dev->flags)、傳送佇列狀態(dev->state)、裝置註冊狀態(dev->reg_state),以及裝置的硬體功能特性(dev->features),位於檔案include/linux/notifier.h中:

 

#define NETDEV_UP      0x0001  /* For now you can't veto a device up/down */

 

#define NETDEV_DOWN    0x0002

 

#define NETDEV_REBOOT  0x0003  /* Tell a protocol stack a network interface

 

                                  detected a hardware crash and restarted

 

                                  - we can use this eg to kick tcp sessions

 

                                  once done */

 

#define NETDEV_CHANGE  0x0004  /* Notify device state change */

 

#define NETDEV_REGISTER 0x0005

 

#define NETDEV_UNREGISTER      0x0006

 

#define NETDEV_CHANGEMTU        0x0007 /* notify after mtu change happened */

 

#define NETDEV_CHANGEADDR      0x0008

 

#define NETDEV_GOING_DOWN      0x0009

 

#define NETDEV_CHANGENAME      0x000A

 

#define NETDEV_FEAT_CHANGE      0x000B

 

#define NETDEV_BONDING_FAILOVER 0x000C

 

#define NETDEV_PRE_UP          0x000D

 

#define NETDEV_PRE_TYPE_CHANGE  0x000E

 

#define NETDEV_POST_TYPE_CHANGE 0x000F

 

#define NETDEV_POST_INIT        0x0010

 

#define NETDEV_UNREGISTER_FINAL 0x0011

 

#define NETDEV_RELEASE          0x0012

 

#define NETDEV_NOTIFY_PEERS    0x0013

 

#define NETDEV_JOIN            0x0014

 

#define NETDEV_CHANGEUPPER      0x0015

 

#define NETDEV_RESEND_IGMP      0x0016

 

#define NETDEV_PRECHANGEMTU    0x0017 /* notify before mtu change happened */

 

#define NETDEV_CHANGEINFODATA  0x0018

 

#define NETDEV_BONDING_INFO    0x0019

 

#define NETDEV_PRECHANGEUPPER  0x001A

 

#define NETDEV_CHANGELOWERSTATE 0x001B

 

#define NETDEV_UDP_TUNNEL_PUSH_INFO    0x001C

 

#define NETDEV_UDP_TUNNEL_DROP_INFO    0x001D

 

#define NETDEV_CHANGE_TX_QUEUE_LEN      0x001E

 

    範例程式碼如下,來自網路,並整理。

 

3.3    模組0-chain0.c
定義兩個函數,一個是註冊函數register_test_notifier,一個傳送事件函數call_test_notifiers

 

#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_0n");

 

 

 

                return 0;

 

}

 

static void __exit test_chain_0_exit(void)

 

{

 

            printk(KERN_DEBUG "Goodbye to test_chain_0n");

 

}

 

 

 

MODULE_LICENSE("GPL");

 

module_init(test_chain_0_init);

 

module_exit(test_chain_0_exit);

 

 

 

3.4    模組1-chain1.c
定義notifier_block的test_init_notifier,其回撥函數為test_init_event。

 

然後呼叫模組0中的事件註冊函數register_test_notifier,向模組進行事件訂閱。當事件發生時會後呼叫函數test_init_event.

 

#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 initn");

 

      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_1n");

 

  register_test_notifier (&test_init_notifier);

 

  return 0;

 

}

 

static void __exit

 

test_chain_1_exit (void)

 

{

 

  printk (KERN_DEBUG "Goodbye to test_clain_ln");

 

}

 

 

 

MODULE_LICENSE ("GPL");

 

 

 

module_init (test_chain_1_init);

 

module_exit (test_chain_1_exit);

 

 

 

3.5    模組2-chain2.c
呼叫模組0的事件傳送函數call_test_notifiers,事件傳送後,訂閱時間的模組1會呼叫其自己的函數test_init_event,輸出字串。

 

#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_2n");

 

                call_test_notifiers(TESTCHAIN_INIT, "no_use");

 

 

 

                    return 0;

 

}

 

 

 

static void __exit test_chain_2_exit(void)

 

{

 

            printk(KERN_DEBUG "Goodbye to test_chain_2n");

 

}

 

 

 

MODULE_LICENSE("GPL");

 

module_init(test_chain_2_init);

 

module_exit(test_chain_2_exit);

 

      然後可以依次插入模組chain0.ko,chain1.ko,chain2.ko。

 

輸出如下:

 

[38086.518853] I'm in test_chain_0

 

[40723.535358] I'm in test_chain_1

 

[40731.758722] I'm in test_chain_2

 

[40731.758724] I got the chain event: test_chain_2 is on the way of init


IT145.com E-mail:sddin#qq.com