Chinaunix首页 | 论坛 | 博客
  • 博客访问: 286926
  • 博文数量: 134
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 118
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-01 14:02
文章分类

全部博文(134)

文章存档

2015年(2)

2014年(4)

2013年(128)

分类: LINUX

2013-12-06 14:22:10

我们接着上一篇的文章继续分析,上一节最后我们又回到了devinet_init()函数,我们接着看下边的调用了rtnl_register()函数

 

inet_init()-->ip_init()--> ip_rt_init()-->devinet_init()-->rtnl_register()

void rtnl_register(int protocol, int msgtype,
         rtnl_doit_func doit, rtnl_dumpit_func dumpit)
{
    if (__rtnl_register(protocol, msgtype, doit, dumpit) < 0)
        panic("Unable to register rtnetlink message handler, "
         "protocol = %d, message type = %d\n",
         protocol, msgtype);
}

在这里我们需要了解netlink的概念,它代表一种特殊的socket通讯方式,它是linux特有的,它是linux内核与用户空间进行双向数据传输非常好的方式,我们知道INET协议是tcp/ip协议在linux系统中的一套执行协议,INET是使用BSDsocket与用户空间进行通讯的,而rtnetlink代表的是netlink socket的路由接口,它与协议无关,还是让我们随着代码来理解吧,很明显上面的函数调用了__rtnl_register(),我们跟进看一下

 

inet_init()-->ip_init()--> ip_rt_init()-->devinet_init()-->rtnl_register()-->__rtnl_register()

int __rtnl_register(int protocol, int msgtype,
         rtnl_doit_func doit, rtnl_dumpit_func dumpit)
{
    struct rtnl_link *tab;
    int msgindex;

    BUG_ON(protocol < 0 || protocol >= NPROTO);
    msgindex = rtm_msgindex(msgtype);

    tab = rtnl_msg_handlers[protocol];
    if (tab == NULL) {
        tab = kcalloc(RTM_NR_MSGTYPES, sizeof(*tab), GFP_KERNEL);
        if (tab == NULL)
            return -ENOBUFS;

        rtnl_msg_handlers[protocol] = tab;
    }

    if (doit)
        tab[msgindex].doit = doit;

    if (dumpit)
        tab[msgindex].dumpit = dumpit;

    return 0;
}

我们看到代码中准备了一个用于上述目的struct rtnl_link结构,这个结构中封装着二个函数指针

struct rtnl_link
{
    rtnl_doit_func        doit;
    rtnl_dumpit_func    dumpit;
};
typedef int (*rtnl_doit_func)(struct sk_buff *, struct nlmsghdr *, void *);
typedef int (*rtnl_dumpit_func)(struct sk_buff *, struct netlink_callback *);

doit函数指针是表示请求操作信息时使用的,而dumpit函数指针而是在释放操作时使用的。

我们看到在devinet_init()函数调用三次rtnl_register()函数时,前二次调用分别传递了inet_rtm_newaddrinet_rtm_deladdr(这些是关于路由的ip设备地址处理函数将会被IPROUTE2使用)做为doit参数传递给rtnl_link结构而dumpitNULL,最后一次传递了inet_dump_ifaddr做为dumpi参数,而doit而为NULL,而protocol而指定为PF_INETmsgtype(路由信息的类型)则分别使用了三种。我们来看__rtnl_register()函数,在内核中有一个总的rtnl_msg_handlers数组,这是一个rtnl_link的结构数组,这里先根据我们参数protocolPF_INET,以此为数组的下标查看是否已经存在rtnl_link结构,如果没有存在则通过kcalloc在内存中分配一个rtnl_link数组空间,kcalloc是关于分配数组空间所使用的,它分配的数组个数是RTM_NR_MSGTYPES,而最终这个函数也是调用__kmalloc()内存分配函数来完成分配的,这个过程我们不看了请朋友们参阅资料上的内存分配部分介绍,分配tab所使用的空间后然后将数组rtnl_msg_handlers相应位置指向这里的tab,但是我们应该注意tab也是一个数组,即队列,也就是说rtnl_msg_handlers数组其实是队列的队列,虽然有点绕嘴,但是我们还是应该知道他必竟不是简单的结构数组,他的每个元素又构成一个数组,也就是队列,这显然接着要对其中的tab数组队列进行设置,而下标的位置则是根据参数msgtype来确定了,当然它已经通过rtm_msgindex()计算成所需要的下标msgindex,以下为下标将我们传递下来的doitdumpit函数指针设置进tab数组中的元素。至此__rtnl_register()函数的作用完成了。我们的devinet_init()函数的过程也就执行完了,我们继续向上返回到上一层即ip_rt_init()函数中,接着往下http://blog.chinaunix.net/u2/64681/showart.php?id=1715717 那节的内容继续往下看,下边接下来调用了ip_fib_init()函数

 

inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()

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

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

    fib_hash_init();
}

这个函数中我们同样看到调用rtnl_registerrtnl_msg_handlers数组中登记了新的路由信息类型,并设置了相关的doit使他们分别指向了inet_rtm_newrouteinet_rtm_delroute, 设置了dumpit函数指针,使他指向了inet_dump_fib。我们前边已经分析过了。我们在http://blog.chinaunix.net/u2/64681/showart.php?id=1715703 那节中看到了register_pernet_subsys()函数的代码,这里是将struct pernet_operations fib_net_ops挂入到了first_device队列中,并执行结构中的init()钩子函数,我们先来看一下fib_net_ops结构的定义

static struct pernet_operations fib_net_ops = {
    .init = fib_net_init,
    .exit = fib_net_exit,
};

很明显挂入的是fib_net_init()函数

 

inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()

static int __net_init fib_net_init(struct net *net)
{
    int error;

    error = ip_fib_net_init(net);
    if (error < 0)
        goto out;
    error = nl_fib_lookup_init(net);
    if (error < 0)
        goto out_nlfl;
    error = fib_proc_init(net);
    if (error < 0)
        goto out_proc;
out:
    return error;

out_proc:
    nl_fib_lookup_exit(net);
out_nlfl:
    ip_fib_net_exit(net);
    goto out;
}

函数中首先调用了ip_fib_net_init()来初始化我们init_net网络空间中的ipv4fib_table_hash路由哈希表队列

 

inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()-->ip_fib_net_init()

static int __net_init ip_fib_net_init(struct net *net)
{
    int err;
    unsigned int i;

    net->ipv4.fib_table_hash = kzalloc(
            sizeof(struct hlist_head)*FIB_TABLE_HASHSZ, GFP_KERNEL);
    if (net->ipv4.fib_table_hash == NULL)
        return -ENOMEM;

    for (i = 0; i < FIB_TABLE_HASHSZ; i++)
        INIT_HLIST_HEAD(&net->ipv4.fib_table_hash[i]);

    err = fib4_rules_init(net);
    if (err < 0)
        goto fail;
    return 0;

fail:
    kfree(net->ipv4.fib_table_hash);
    return err;
}

FIB_TABLE_HASHSZ定义为256,所以最多使用256个路由表队列头,具体的分配函数kzalloc()来完成的,这个函数也是调用的kmalloc()内存分配函数来完成的,只不过增加了一下分配标志__GFP_ZERO这是个清零标志,表示内核分配的内存被清零。分配成功后要使用INIT_HLIST_HEAD宏来初始化这些队列头

#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)

接着我们要调用fib4_rules_init()函数

 

inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> ip_fib_net_init()-->fib4_rules_init()

static int __net_init fib4_rules_init(struct net *net)
{
    struct fib_table *local_table, *main_table;

    local_table = fib_hash_table(RT_TABLE_LOCAL);
    if (local_table == NULL)
        return -ENOMEM;

    main_table = fib_hash_table(RT_TABLE_MAIN);
    if (main_table == NULL)
        goto fail;

    hlist_add_head_rcu(&local_table->tb_hlist,
                &net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX]);
    hlist_add_head_rcu(&main_table->tb_hlist,
                &net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]);
    return 0;

fail:
    kfree(local_table);
    return -ENOMEM;
}

我们在http://blog.chinaunix.net/u2/64681/showart.php?id=1715717 那节中说过,在sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()-->fib_get_table(),从绑定函数一直调用到检查地址类型再到查找路由表时,我们因为不知道什么时候初始化的idRT_TABLE_LOCAL的路由表而从那里转向路由表的初始化分析过程的,请朋友一定要注意并不是我们脱离了最初的分析路线,而在分析路线的道路上必需得到相关的设置才能够完整的让我们的流程继续下去,这里的fib4_rules_init()函数正是对我们那里的路由表的对接。所以看完上面的函数也就完成了那里我们留下的疑问,也就能继续我们的主线了,有的朋友会提出疑问“为什么不从初始化过程开始,而直接从应用程序这样开始,还是要结合初始化的”,之所以我们把初始化没有单独罗列出来,一是因为初始化的过程如果站在程序设计角度讲,是先有了应用后有了具体的初始化,有些工作必须在内核启动时完成就统统放在初始化函数中去进行了,程序的设计者不知道到底应该初始化哪些协议函数,我们走的路线恰恰好是程序员的思考路线,这些核客们,他们在构建代码的过程中,会经过详细的分析总结确定好具体的执行路线方案。我们应该庆幸我们采取的分析方法和路线是正确的。这样的分析方法要远比书本上的理论按章节按分类学习的效果要直观的多,好了时间关系,下一节继续这个极其重要的函数分析。

阅读(1675) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~