设计模式的C语言应用-责任链模式-第三章

模式介绍

责任链将需要触发的对象组成一条链,发送者将请求发给链的第一个接收者,并且沿着这条链传递,直到有一个对象来处理它或者直到最后也没有对象处理而留在链末尾端。

image.png

图表 1责任链模式流程图

责任链在C语言里也是实现形式非常明显的模式。最典型的责任链有linux内核的中断处理机制的纯软件部分和内核网络netfiler的HOOK机制。这两者均强化了责任链机制,重点在引入了责任优先级方法和增加了通过/终结两种处理结果。

责任链模式的最重要的数据结构是handler链表。事件发生时,handler链表上的回调函数会被以此调用。优先级决定了那个handler会被先调,哪些会被后调用。在扩展特性里,每个handler可以有不处理和处理完之后继续交给下一个handler两种选择。如果该事件最后没有被消费,会有一个异常处理函数。如果责任链上任意一个handler消费了事件,那么就不传给下一个handler,直接结束。

逻辑上和责任链模式最相近的一个设计模式为观察者模式。流程图如下。观察者模式和责任链模式的最大的差别在于,事件会被通知到每一个平等的handler,而不是逐级处理。也不存在优先级的说法,也不会出现事件没有处理需要异常函数收尾。

image.png

图表 2观察者模式流程图

责任链模式实现

责任链模式事件怎么触发不要紧,关键就是handler的数据结构组织和处理逻辑。

责任链节点定义

//两类处理结果,子类可以扩展

#define CHAIN_PASS 0

#define CHAIN_STOP 1

typedef int (*chain_func)(char *buf);

struct chain_ops_node {

              struct list_head list;  //内核链表标准结构

              chain_func *handler;  //handler的回调函数

              int priority; //优先级

};

责任链和处理函数

//全局的责任链

struct list_head chain_global_list;

//具体的处理函数

int chain_handler1(char *buf)

{

    //do something

    if(/*some conditions*/)

    {

        return CHAIN_PASS;

    }

    return CHAIN_STOP;

}

int chain_handler2(char *buf)

{

    //do something

    if(/*some conditions*/)

    {

        return CHAIN_PASS;

    }

    return CHAIN_STOP;

}

//封装成节点

struct chain_ops_node node1 =

{

    .handler = chain_handler1,

    .priority = 0

}

struct chain_ops_node node2 =

{

    .handler = chain_handler2,

    .priority = 1

}

注册和反注册函数

特别注意,一般是需要信号量锁定的,因为很可能链条上的数据正在执行。内核里喜欢用rcu锁,可以避免资源互斥引起cpu浪费。

int chain_register(struct chain_ops_node *node)

{   

    //lock chain_global_list

    //add node into chain_global_list according to priority

    //unlock chain_global_list

    return 0;

}

int chain_unregister(struct chain_ops_node *node)

{

    //lock chain_global_list

    //delete node into chain_global_list

    //unlock chain_global_list

    return 0;

}

调用流程

int main()

{

    struct list_head *node;

    struct chain_ops_node *node_func;

    char buf[16];

   

    chain_register(&node1);

    chain_register(&node2);

    //something happend, should trigger responsibility chain

    //fill buf with event

    list_for_each(node, &chain_global_list)

    {

        node_func = (struct chain_ops_node *)node;

        if(node_func.handler(buf) == CHAIN_STOP)

        {break;}

    }

   

    return 0;

}

内核的责任链模式实例

内核里最典型的就是内核中断处理和和内核网络netfiler的HOOK机制。而内核网络netfiler的HOOK机制的责任链模式体现更为完整充分。所以本文以netfiler的HOOK机制为例讲解。

handler的格式

内核的hook链就是责任链模式的handler链。nf_hook_ops就是handler链的一个handler节点。

struct nf_hook_ops {

              struct list_head list;  //内核链表标准结构

              /* User fills in from here down. */

              nf_hookfn *hook;  //handler的回调函数

              struct module *owner; //模式无关,可忽略

              u_int8_t pf; //协议族,用来区分事件处理的,可以看作辅助标记。作为单链可忽略。

              unsigned int hooknum; //挂在在哪个hook链上,netfiler的hook设计支持多hook链条,不过同一类事件只是触发一个hook链条的函数。所以从设计模式上讲这里只是同时实现了4条互不相干的责任链模式的handler链。作为单链可忽略。
 /* Hooks are ordered in ascending priority. */

              int priority; //优先级

};

比如如下定义:

static struct nf_hook_ops nf_nat_ops[] __read_mostly = {

              /* Before packet filtering, change destination */

              {

                             .hook                   = nf_nat_in,

                             .owner                 = THIS_MODULE,

                             .pf                        = NFPROTO_IPV4,

                             .hooknum           = NF_INET_PRE_ROUTING,

                             .priority = NF_IP_PRI_NAT_DST,

              },

…

…

}

handler的注册

int nf_register_hook(struct nf_hook_ops *reg)

非常简单的操作,在锁的保护下,将handler节点加入到链表nf_hooks[reg->pf][reg->hooknum]上。插入链表的顺序由priority决定,升序排列。

从这里可以看出,链表是二维的,区分了协议族和hooknum(网络通路的位置)。本质上,对于一个固定handler,可以认为只是和一个handler链条发生关系。

事件触发的处理函数

以IPV4的NF_INET_PRE_ROUTING hook为例,ip_rcv函数最后会调用nf_hook_slow,遍历链表调用handler函数。Handler返回处理结果有NF_ACCEPT,NF_STOLEN,NF_DROP等好几种。nf_hook_slow会根据这些结果决定接着调用链表上下一个handler还是终止等一系列动作。

linux内核遍历的方法基本上都是list_for_each_xxx函数。

模式实现总结

1. 责任链模式在内核的实现很普遍,实现代码典型而简单,都是先定义各异handler的链表节点,包含list结构体,优先级,回调处理函数3个要素即可。更复杂的责任链模式实现只不过多条链,但是单个链的属性是没有改变的。而netfilter的链已经算比较复杂的,所以绝大部分编码学习到这个水平就足够了。

2.每个handler的处理结果根据需要定义,总体上讲都是继续和不继续两种。

来源:华为云社区  作者:lurayvis

原文地址:https://www.cnblogs.com/2020-zhy-jzoj/p/13165728.html