我们继续上一节最后分析到的fib4_rules_init()函数,我们知道这个函数将完成我们在http://blog.chinaunix.net/u2/64681/showart.php?id=1715717 那节中的fib_get_table()函数的对接,我们继续这个函数的分析,首先我们看到调用了fib_hash_table()函数来取得
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> ip_fib_net_init()-->fib4_rules_init()-->fib_hash_table()
struct fib_table *fib_hash_table(u32 id) { struct fib_table *tb;
tb = kmalloc(sizeof(struct fib_table) + sizeof(struct fn_hash), GFP_KERNEL); if (tb == NULL) return NULL;
tb->tb_id = id; tb->tb_default = -1; tb->tb_lookup = fn_hash_lookup; tb->tb_insert = fn_hash_insert; tb->tb_delete = fn_hash_delete; tb->tb_flush = fn_hash_flush; tb->tb_select_default = fn_hash_select_default; tb->tb_dump = fn_hash_dump; memset(tb->tb_data, 0, sizeof(struct fn_hash)); return tb; }
|
当然参数传递下来的是RT_TABLE_LOCAL,即当前使用本地路由表,我们看到首先是在内存中分配struct fib_table结构的空间,但是分配的大小我们看到要增加一个struct fn_hash结构的大小,这个结构我们还没有看过
struct fn_hash { struct fn_zone *fn_zones[33]; struct fn_zone *fn_zone_list; }; struct fn_zone { struct fn_zone *fz_next; /* Next not empty zone */ struct hlist_head *fz_hash; /* Hash table pointer */ int fz_nent; /* Number of entries */
int fz_divisor; /* Hash divisor */ u32 fz_hashmask; /* (fz_divisor - 1) */ #define FZ_HASHMASK(fz) ((fz)->fz_hashmask)
int fz_order; /* Zone order */ __be32 fz_mask; #define FZ_MASK(fz) ((fz)->fz_mask) };
|
我们看到fn_hash包括了一个fn_zone结构数组,一个fn_zone结构队列,struct fn_zone结构是表示使用同一种子网掩码的称为一个路由区(这里用区来表示一套路由集合),我们知道子网掩码在我们熟悉的XP上是这样的
子网掩码是用来判断任意两台计算机的IP地址是否属于同一子网络的根据。简单理解就是两台计算机他们各自的IP地址与子网掩码进行AND运算后,如果得出的结果是相同的,则说明这两台计算机是处于同一个子网络上的,可以进行直接的通讯,这样理解起来比较简单。对于IPV4来说他使用的是32位的子网掩码值,所以每一个路由表就会有33套路由区来表示,因此,在子网路由 10.0.1.0/24 和 10.0.2.0/24 将会在24位的路由区队列中(第25套路由区), 子网路由10.0.3.128/25 将会在25位的路由区队列中(第26套路由区)。那么我们来看fn_hash第一个数组就是指向了这个33套的路由区队列,数组与指针在C语言里是一样的,而每一套路由区队列中的路由区结构又通过fz_next链接在一起,fn_zones[0]用于默认网关。而fn_zone_list域就是将正在使用的fn_zone链成一个链表,我们看到fib_hash_table()函数中将这个路由hash区域表紧贴在路由表后一起分配的内存空间,此后对路由表结构变量tb进行了初始化设置,主要是钩子函数的挂入,包括对路由表的插入、删除、查找函数等,最后通过memset()函数将tb中的tb_data所指向的数据空间也就是fn_hash结构所在的空间初始化成0,然后返回这里创建的路由表struct fib_table结构指针tb给fib4_rules_init()函数中的local_table路由表结构指针。接着还要以同样的过程传递下来RT_TABLE_MAIN创建一个主路由表,这个路由表是默认的路由表,也就是如果没有指定所使用的路由表就会使用这里初始化创建的路由表,我们在fib_get_table()函数中看到过(http://blog.chinaunix.net/u2/64681/showart.php?id=1715717 )
if (id == 0) id = RT_TABLE_MAIN;
|
也就是说如果没有指定路由表,就会使用主路由表,同样这里让main_table结构指针指向他。最后调用hlist_add_head_rcu()函数将这里创建的二张路由表插入网络空间init_net中的ipv4的路由哈希队列中
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> ip_fib_net_init()-->fib4_rules_init()-->hlist_add_head_rcu()
static inline void hlist_add_head_rcu(struct hlist_node *n, struct hlist_head *h) { struct hlist_node *first = h->first; n->next = first; n->pprev = &h->first; smp_wmb(); if (first) first->pprev = &n->next; h->first = n; }
|
上面这个哈希队列头操作过程就是将前一个hash队列节点链入到后一个队列头中。
struct hlist_head { struct hlist_node *first; };
struct hlist_node { struct hlist_node *next, **pprev; };
|
具体的连接过程很简单了,上面的代码请朋友们自己阅读。至此fib4_rules_init()函数的使命就完成了,我们看到他只是初始化了本地路由表和主路由表,这二张路由表只是链入了必要的操作函数,并没有内容,如果我们看__inet_dev_addr_type()函数中调用fib_get_table()表是指定了RT_TABLE_LOCAL来取本地路由表的(http://blog.chinaunix.net/u2/64681/showart.php?id=1715703 ),而这里已经设置了路由表的操作函数tb_lookup(),我们看到在上边的fib_hash_table()中挂入了fn_hash_lookup()函数。但是在回到主路线之前还是让我们完成路由表的初始化过程吧,必竟这也是非常必要的,不过从上面的描述过程中,朋友们已经知道为什么我要分析路由的初始化过程了,我们上边看到fib_hash_table()函数已经完成了路由表的初始化,然后返回到其上层ip_fib_net_init()函数,再返回到fib_net_init()函数中,接下来调用了nl_fib_lookup_init()函数
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> nl_fib_lookup_init()
static int nl_fib_lookup_init(struct net *net) { struct sock *sk; sk = netlink_kernel_create(net, NETLINK_FIB_LOOKUP, 0, nl_fib_input, NULL, THIS_MODULE); if (sk == NULL) return -EAFNOSUPPORT; net->ipv4.fibnl = sk; return 0; }
|
这个函数是与我们上一节谈到的netlink的socket密切相关的,我们来看一下这个函数
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> nl_fib_lookup_init()-->netlink_kernel_create()
struct sock * netlink_kernel_create(struct net *net, int unit, unsigned int groups, void (*input)(struct sk_buff *skb), struct mutex *cb_mutex, struct module *module) { struct socket *sock; struct sock *sk; struct netlink_sock *nlk; unsigned long *listeners = NULL;
BUG_ON(!nl_table);
if (unit < 0 || unit >= MAX_LINKS) return NULL;
if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock)) return NULL;
/* * We have to just have a reference on the net from sk, but don't * get_net it. Besides, we cannot get and then put the net here. * So we create one inside init_net and the move it to net. */
if (__netlink_create(&init_net, sock, cb_mutex, unit) < 0) goto out_sock_release_nosk;
sk = sock->sk; sk_change_net(sk, net);
if (groups < 32) groups = 32;
listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL); if (!listeners) goto out_sock_release;
sk->sk_data_ready = netlink_data_ready; if (input) nlk_sk(sk)->netlink_rcv = input;
if (netlink_insert(sk, net, 0)) goto out_sock_release;
nlk = nlk_sk(sk); nlk->flags |= NETLINK_KERNEL_SOCKET;
netlink_table_grab(); if (!nl_table[unit].registered) { nl_table[unit].groups = groups; nl_table[unit].listeners = listeners; nl_table[unit].cb_mutex = cb_mutex; nl_table[unit].module = module; nl_table[unit].registered = 1; } else { kfree(listeners); nl_table[unit].registered++; } netlink_table_ungrab(); return sk;
out_sock_release: kfree(listeners); netlink_kernel_release(sk); return NULL;
out_sock_release_nosk: sock_release(sock); return NULL; }
|
这个函数前面首先是看到了数据结构struct netlink_sock出现了,我们还没有看过
struct netlink_sock { /* struct sock has to be the first member of netlink_sock */ struct sock sk; u32 pid; u32 dst_pid; u32 dst_group; u32 flags; u32 subscriptions; u32 ngroups; unsigned long *groups; unsigned long state; wait_queue_head_t wait; struct netlink_callback *cb; struct mutex *cb_mutex; struct mutex cb_def_mutex; void (*netlink_rcv)(struct sk_buff *skb); struct module *module; };
|
不言而喻,这个结构是用于netlink的socket使用,内核中有一个netlink_table结构数组nl_table,关于netlink_table的结构定义我们也贴在下边让朋友们有一个初步的印象
struct netlink_table { struct nl_pid_hash hash; struct hlist_head mc_list; unsigned long *listeners; unsigned int nl_nonroot; unsigned int groups; struct mutex *cb_mutex; struct module *module; int registered; };
|
结构中的具体作用在代码的阅读过程中就会变得清楚起来,我们看到在netlink_kernel_create()函数中先是检查做为参数unix传递下来的NETLINK_FIB_LOOKUP,这是在nl_fib_lookup_init()调用时传递过来的
#define NETLINK_FIB_LOOKUP 10
|
这个值很显然可以通过检查,接着调用sock_create_lite()函数创建一个netlink的socket,我们追踪看一下
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> nl_fib_lookup_init()-->netlink_kernel_create()-->sock_create_lite()
int sock_create_lite(int family, int type, int protocol, struct socket **res) { int err; struct socket *sock = NULL;
err = security_socket_create(family, type, protocol, 1); if (err) goto out;
sock = sock_alloc(); if (!sock) { err = -ENOMEM; goto out; }
sock->type = type; err = security_socket_post_create(sock, family, type, protocol, 1); if (err) goto out_release;
out: *res = sock; return err; out_release: sock_release(sock); sock = NULL; goto out; }
|
我们看到了这里也调用了sock_alloc(),我们已经在http://blog.chinaunix.net/u2/64681/showart.php?id=1680618 那节中详细分析了,也就是在我们的网络文件系统中分配一个socket结构,然后将socket的type类型指定为做为type参数传递下来的SOCK_DGRAM类型。然后函数返回到netlink_kernel_create()中我们继续往一看调用了__netlink_create()函数创建一个用于netlink的sock结构并与socket挂钩
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> nl_fib_lookup_init()-->netlink_kernel_create()-->__netlink_create()
static int __netlink_create(struct net *net, struct socket *sock, struct mutex *cb_mutex, int protocol) { struct sock *sk; struct netlink_sock *nlk;
sock->ops = &netlink_ops;
sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto); if (!sk) return -ENOMEM;
sock_init_data(sock, sk);
nlk = nlk_sk(sk); if (cb_mutex) nlk->cb_mutex = cb_mutex; else { nlk->cb_mutex = &nlk->cb_def_mutex; mutex_init(nlk->cb_mutex); } init_waitqueue_head(&nlk->wait);
sk->sk_destruct = netlink_sock_destruct; sk->sk_protocol = protocol; return 0; }
|
我们在http://blog.chinaunix.net/u2/64681/showart.php?id=1685662 那节中看到了sk_alloc函数,同时函数开始将socket的ops设置成了netlink_ops,而在sk_alloc()函数中也会将上面的调用时传递的netlink_proto 传递给sk_prot指针,所以上面的函数的创建过程请朋友们结合那里的创建过程自己阅读一遍,当做对朋友们阅读那节的一次练习。我们知道sock结构在netlink_sock结构的头部所以nlk_sk()函数可以根据sock的地址得到netlink_sock的地址,即指针。然后让netlink_sock结构变量nlk指向它,最后对nlk进行了一系列的初始化,这些内容我们不分析了,无非是对cb_mutex锁的设置,我们看到上面nl_fib_lookup_init()函数传递下来的是NULL,以及通过init_waitqueue_head()对其等待队列的初始化。最后我们再看netlink_kernel_create()函数中,将我们在nl_fib_lookup_init()调用时传递下来的nl_fib_input()函数指针,设置进了netlink_sock结构中的netlink_rcv中,netlink_kernel_create()函数的其余内容无非是关于init_net网络空间和nl_table这个全局的netlink_table结构的初始化,都很简单了。我们在这里不关心这些过程了。最后回到nl_fib_lookup_init()函数在上面的代码处,我们看到
将新创建的用于netlink_sock的sock与init_net中的ipv4变量的fibnl建立联系。回到fib_net_init()函数中,接下来调用fib_proc_init()在内核的proc文件系统中创建一个route节点。只不过传递给proc_net_fops_create()函数进一步传递给proc_create()来完成创建,这个proc文件系统节点的创建函数请朋友们阅读其他资料,这里要注意传递给文件节点用的file_operations指针是fib_seq_fops。
static const struct file_operations fib_seq_fops = { .owner = THIS_MODULE, .open = fib_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release_net, };
|
我们将上面的代码也列在下面以供朋友们阅读方便
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> fib_proc_init()
int __net_init fib_proc_init(struct net *net) { if (!proc_net_fops_create(net, "route", S_IRUGO, &fib_seq_fops)) return -ENOMEM; return 0; } int __net_init fib_proc_init(struct net *net) { if (!proc_net_fops_create(net, "route", S_IRUGO, &fib_seq_fops)) return -ENOMEM; return 0; } struct proc_dir_entry *proc_net_fops_create(struct net *net, const char *name, mode_t mode, const struct file_operations *fops) { return proc_create(name, mode, net->proc_net, fops); }
|
到此,fib_net_init()函数我们分析完了,返回到其上一级函数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(); }
|
我们看到了调用了register_netdevice_notifier函数,这个函数的代码我们在http://blog.chinaunix.net/u2/64681/showart.php?id=1715717 那节中看过了,这里是将fib_netdev_notifier通知链链入到内核中的netdev_chain链中
static struct notifier_block fib_netdev_notifier = { .notifier_call =fib_netdev_event, };
|
然后我们看到调用了register_inetaddr_notifier()函数
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->register_inetaddr_notifier()
int register_inetaddr_notifier(struct notifier_block *nb) { return blocking_notifier_chain_register(&inetaddr_chain, nb); }
|
其中函数中的inetaddr_chain链是通过宏来初始化成的
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 }
|
__RWSEM_INITIALIZER是对读写信号量的初始化。这里会根据我们使用的cpu的不同而调用相应的宏。我们不看这个初始化宏了。
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->register_inetaddr_notifier()-->blocking_notifier_chain_register()
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *n) { int ret;
/* * This code gets used during boot-up, when task switching is * not yet working and interrupts must remain disabled. At * such times we must not call down_write(). */ if (unlikely(system_state == SYSTEM_BOOTING)) return notifier_chain_register(&nh->head, n);
down_write(&nh->rwsem); ret = notifier_chain_register(&nh->head, n); up_write(&nh->rwsem); return ret; }
|
函数中调用notifier_chain_register()来将通知链插入到内核中的inetaddr_chain链中,只不过检查是系统初始化状态而确定对rwsem信号量的操作,信号量的作用相关书籍上都有,我们看以看到这是一种可读可写的信号量,远比自旋锁要方便、灵活。
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->register_inetaddr_notifier()-->blocking_notifier_chain_register()-->notifier_chain_register()
static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n) { while ((*nl) != NULL) { if (n->priority > (*nl)->priority) break; nl = &((*nl)->next); } n->next = *nl; rcu_assign_pointer(*nl, n); return 0; }
|
这里会根据通知链的优先级来确定插入的位置。
最后ip_fib_init()函数会调用fib_hash_init()来创建二个高速缓存slab块
inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_hash_init()
void __init fib_hash_init(void) { fn_hash_kmem = kmem_cache_create("ip_fib_hash", sizeof(struct fib_node), 0, SLAB_PANIC, NULL);
fn_alias_kmem = kmem_cache_create("ip_fib_alias", sizeof(struct fib_alias), 0, SLAB_PANIC, NULL);
}
|
有关高速缓存slab的内容,在深入理解linux内核第三版书中都有详细的介绍我们也不做说明了。我们的会根据函数前边注释的路线inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init(),向上返回到ip_rt_init()函数中,可以看到接下来在这个函数中初始化了一个rt_secret_timer定时器,定时执行rt_secret_rebuild()函数,定时处理函数rt_secret_rebuild()会重新对缓存flush并再次调整定时器。ip_rt_init()函数最后启动这个定时器,最后通过ip_rt_proc_init()函数调用register_pernet_subsys()我们看到向内核登记了一个pernet_operations结构ip_rt_proc_ops,下面是它的定义
static struct pernet_operations ip_rt_proc_ops __net_initdata = { .init = ip_rt_do_proc_init, .exit = ip_rt_do_proc_exit, };
|
我们再向上返回到ip_init()函数,函数接下来调用了inet_initpeers()
inet_init()-->ip_init()--> inet_initpeers()
void __init inet_initpeers(void) { struct sysinfo si;
/* Use the straight interface to information about memory. */ si_meminfo(&si); /* The values below were suggested by Alexey Kuznetsov * . I don't have any opinion about the values * myself. --SAW */ if (si.totalram <= (32768*1024)/PAGE_SIZE) inet_peer_threshold >>= 1; /* max pool size about 1MB on IA32 */ if (si.totalram <= (16384*1024)/PAGE_SIZE) inet_peer_threshold >>= 1; /* about 512KB */ if (si.totalram <= (8192*1024)/PAGE_SIZE) inet_peer_threshold >>= 2; /* about 128KB */
peer_cachep = kmem_cache_create("inet_peer_cache", sizeof(struct inet_peer), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
/* All the timers, started at system startup tend to synchronize. Perturb it a bit. */ peer_periodic_timer.expires = jiffies + net_random() % inet_peer_gc_maxtime + inet_peer_gc_maxtime; add_timer(&peer_periodic_timer); }
|
我们跳过对内存信息的统计函数的调用和计算,所以这个函数只是申请了一个peer_cachep高速缓存并设置启动了peer_periodic_timer定时器。到这里我们的路由初始过程分析也就结束了。接下来我们就开始前边__inet_dev_addr_type() (http://blog.chinaunix.net/u2/64681/showart.php?id=1715703 )已经讲到的路由表的操作函数tb_lookup(),也就是我们前面看到fn_hash_lookup()函数。下一篇继续。