接着上一篇,我们继续看
sys_socketcall()-->sys_bind()
-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()-->fib_get_table()
struct fib_table *fib_get_table(struct net *net, u32 id) { struct fib_table *tb; struct hlist_node *node; struct hlist_head *head; unsigned int h;
if (id == 0) id = RT_TABLE_MAIN; h = id & (FIB_TABLE_HASHSZ - 1);
rcu_read_lock(); head = &net->ipv4.fib_table_hash[h]; hlist_for_each_entry_rcu(tb, node, head, tb_hlist) { if (tb->tb_id == id) { rcu_read_unlock(); return tb; } } rcu_read_unlock(); return NULL; }
|
我们已经知道这里的参数net就是init_net这个全局结构变量,很显然上面的函数要根据其已经初始化的fib_table_hash来他的杂凑队列查找td_id为RT_TABLE_LOCAL(路由标识符为255)的路由表。已经初始化进fib_table_hash这个hash数组中的路由表,他们都是通过其内部结构中的tb_hlist头链入数组队列的,关于struct fib_table这个数据结构是代表着一个路由表,内核中还有一个缓存的路由表结构与这个作用不同,将来我们会看到,struct fib_table结构的内容我们就先不看了,里面大多是一些函数指针。找到路由表后就返回到__inet_dev_addr_type()函数中调用路由表结构中的tb_lookup()函数根据我们上面初始化的键值找到struct fib_result结构,这个结构中的type记录着地址类型。那么这个路由表是什么时候初始化进内核的呢?我们先从inet_init()函数中看起,在其内部调用了ip_init(),而进一步通过它调用了ip_rt_init()函数,我们跟进这个函数看一下是怎么样对路由表进行的初始化
inet_init()-->ip_init()--> ip_rt_init()
int __init ip_rt_init(void) { int rc = 0;
atomic_set(&rt_genid, (int) ((num_physpages ^ (num_physpages>>8)) ^ (jiffies ^ (jiffies >> 7))));
#ifdef CONFIG_NET_CLS_ROUTE ip_rt_acct = __alloc_percpu(256 * sizeof(struct ip_rt_acct)); if (!ip_rt_acct) panic("IP: failed to allocate ip_rt_acct\n"); #endif
ipv4_dst_ops.kmem_cachep = kmem_cache_create("ip_dst_cache", sizeof(struct rtable), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
ipv4_dst_blackhole_ops.kmem_cachep = ipv4_dst_ops.kmem_cachep;
rt_hash_table = (struct rt_hash_bucket *) alloc_large_system_hash("IP route cache", sizeof(struct rt_hash_bucket), rhash_entries, (num_physpages >= 128 * 1024) ? 15 : 17, 0, &rt_hash_log, &rt_hash_mask, 0); memset(rt_hash_table, 0, (rt_hash_mask + 1) * sizeof(struct rt_hash_bucket)); rt_hash_lock_init();
ipv4_dst_ops.gc_thresh = (rt_hash_mask + 1); ip_rt_max_size = (rt_hash_mask + 1) * 16;
devinet_init(); ip_fib_init();
rt_secret_timer.function = rt_secret_rebuild; rt_secret_timer.data = 0; init_timer_deferrable(&rt_secret_timer);
/* All the timers, started at system startup tend to synchronize. Perturb it a bit. */ schedule_delayed_work(&expires_work, net_random() % ip_rt_gc_interval + ip_rt_gc_interval);
rt_secret_timer.expires = jiffies + net_random() % ip_rt_secret_interval + ip_rt_secret_interval; add_timer(&rt_secret_timer);
if (ip_rt_proc_init()) printk(KERN_ERR "Unable to create route proc files\n"); #ifdef CONFIG_XFRM xfrm_init(); xfrm4_init(); #endif rtnl_register(PF_INET, RTM_GETROUTE, inet_rtm_getroute, NULL);
return rc; }
|
函数前边调用了atomic_set宏,这个宏在include/asm-x86/atomic_32.h中
#define atomic_set(v, i) (((v)->counter) = (i))
|
这个宏的主要作用是声明一个原子类型的变量V他的值是i,那这个原子类型是了保证该变量V能够实现原子操作,这是什么意思呢,学会操作系统理论的朋友可能知道多进程或者中断可能会影响当前进程的操作过程,尤其是对一个变量的值进行修改或者读取时,一个进程正在读取变量值,然后另一个进程抢占进来或者中断进程将这个变量值修改了,但是前一个进程如果再进一步操作的话,就会导致变量值的“不同步”,所以为了保证在对这样的变量值同步操作,所以出现了原子操作的方法,即很多资料上称的避免被其他进程和中断打断的操作就做为原子操作,实际上这只是表面现象,更主要是的对这个样的变量操作采用一条汇编语句实现的,因为在linux里可以内嵌汇编语句,当然我们这里就不准备讲解这些内容了,这样的通过一条汇编指令,完成“读,修改,写回内存”,不能被中断,如果是多cpu还会有lock锁住总线。所以原子操作的主要作用就是为了保持对数值的同步。
我们看到
static atomic_t rt_genid __read_mostly;
|
注意上面的__read_mostly是保证这个原子变量能够在smp系统中能够保证安全的访问,也就是能够保证多个处理器的缓存中保持对这个变量的同步,这是假定大多数是读的情况下的,如果一个处理器写的话这将会给其他多处理器带来更多的额外开销。至于atomic_t结构,请朋友们参考深入内核第三版的讲解吧。很明显函数开头声明了一个rt_genid原子量。接着下面调用的__alloc_percpu()函数也类型上面的smp的作用,是分配了一个percpu数据结构,关于percpu变量或者数据结构,我们将会在后边的文章中详细为大家讲解,这里struct ip_rt_acct是用于路由参数统计使用的
struct ip_rt_acct { __u32 o_bytes; __u32 o_packets; __u32 i_bytes; __u32 i_packets; };
|
很显然前边的二个是发出的字节和包数,而后二个是进入的字节和包数。我们继续看代码接着对ipv4_dst_ops这个全局的struct dst_ops结构中的路由表rable的高速slab缓存的创建,其高速缓存原名称为ip_dst_cache。关于struct dst_ops结构主要是用来封装路由的操作函数指针,它实际上是用来我们指定事件的协议通知,例如链接出错情况,这个结构主要用在第三层网络层。所以它和具体的协议相关。我们这里的IPV4定义了它自己的路由操作函数表ipv4_dst_ops,关于slab的缓存的创建函数kmem_cache_create()参考深入内核书中的高速缓存部分,这里不做重点。代码中将ipv4_dst_blackhole_ops也同时指向ipv4_dst_ops中的这个路由表高速缓存区。
然后我们看到rt_hash_table变量的初始化
static struct rt_hash_bucket *rt_hash_table __read_mostly;
|
关于rt_hash_bucket结构是用专门用于路由的缓存使用的结构,它内部只包括一个路由表结构
struct rt_hash_bucket { struct rtable *chain; };
|
Rtable是路由缓存所使用的数据结构,我们在后边的章节结构代码进行分析,注意我们之前看到过另一个路由表结构fib_table,注意二者的不同。我们这里将路由缓存的图片先展示在这里以供大家对整体有个印象
代码中我们看到调用alloc_large_system_hash()来分配一个rt_hash_bucket的路由缓存空间,并让rt_hash_table指向这个路由缓存空间。alloc_large_system_hash()函数与内存分配有关,我们不涉及其中了,它是从系统的bootmem空间中分配大量的专用于路由的缓存。Bootmem是在内核启动时的内存管理策略主要是指内核所使用的页面,意图是从内核的核心内存部分按面进行分配,我们知道这部分内存页面是不会被linux的内存管理执行页面交换或者回收的,也就是说这部分低端的内存一经分配就不再改变了。可见路由缓存结构的重要性。我们继续看代码,分配了路由的缓存结构空间后,还要对这些结构进行初始化0设置,这是在memset()函数完成的。注意rt_hash_mask代表分配的rt_hash_bucket的数,同样既然分配了就要在ipv4_dst_ops记录下可以回收的碎片数gc_thresh中。接着下面我们看到调用了
inet_init()-->ip_init()--> ip_rt_init()-->devinet_init()
void __init devinet_init(void) { register_pernet_subsys(&devinet_ops);
register_gifconf(PF_INET, inet_gifconf); register_netdevice_notifier(&ip_netdev_notifier);
rtnl_register(PF_INET, RTM_NEWADDR, inet_rtm_newaddr, NULL); rtnl_register(PF_INET, RTM_DELADDR, inet_rtm_deladdr, NULL); rtnl_register(PF_INET, RTM_GETADDR, NULL, inet_dump_ifaddr); }
|
我们看到向内核登记了一个pernet_operations结构devinet_ops
static __net_initdata struct pernet_operations devinet_ops = { .init = devinet_init_net, .exit = devinet_exit_net, };
|
register_pernet_subsys我们在前面看过了,上面的devinet_init()函数中注册了一个ip_netdev_notifier通知链
static struct notifier_block ip_netdev_notifier = { .notifier_call =inetdev_event, };
|
它是一个通知链结构
struct notifier_block { int (*notifier_call)(struct notifier_block *, unsigned long, void *); struct notifier_block *next; int priority; };
|
notifier_call函数指针表示对应节点要运行的函数。函数通过next指针形成“链队”。priority代表优先级别,它保证了通知链按指定的顺序来执行,我们还不打算详细的解释通知链的概念,还是随着我们的代码来理解吧,“用时理解”,我们看到这是经过register_netdevice_notifier()函数将我们的ip_netdev_notifier通知链链入内核中的netdev_chain链中。
inet_init()-->ip_init()--> ip_rt_init()-->devinet_init()-->register_netdevice_notifier()
int register_netdevice_notifier(struct notifier_block *nb) { struct net_device *dev; struct net_device *last; struct net *net; int err;
rtnl_lock(); err = raw_notifier_chain_register(&netdev_chain, nb); if (err) goto unlock; if (dev_boot_phase) goto unlock; for_each_net(net) { for_each_netdev(net, dev) { err = nb->notifier_call(nb, NETDEV_REGISTER, dev); err = notifier_to_errno(err); if (err) goto rollback;
if (!(dev->flags & IFF_UP)) continue;
nb->notifier_call(nb, NETDEV_UP, dev); } }
unlock: rtnl_unlock(); return err;
rollback: last = dev; for_each_net(net) { for_each_netdev(net, dev) { if (dev == last) break;
if (dev->flags & IFF_UP) { nb->notifier_call(nb, NETDEV_GOING_DOWN, dev); nb->notifier_call(nb, NETDEV_DOWN, dev); } nb->notifier_call(nb, NETDEV_UNREGISTER, dev); } }
raw_notifier_chain_unregister(&netdev_chain, nb); goto unlock; }
|
这个函数将我们的通知链注册后,接着看是否在内核启动阶段,如果是启动阶段就开锁后返回了,如果是在内核运行过程中的话,就会在我们的init_net网络空间中对所有已经注册的网络设备的通知链结构,执行他们的挂入到notifier_call()的钩子函数。当然结合上面我们登记的结构,很明显是inetdev_event()
inet_init()-->ip_init()--> ip_rt_init()-->devinet_init()-->register_netdevice_notifier()-->inetdev_event()
static int inetdev_event(struct notifier_block *this, unsigned long event, void *ptr) { struct net_device *dev = ptr; struct in_device *in_dev = __in_dev_get_rtnl(dev);
ASSERT_RTNL();
if (!in_dev) { if (event == NETDEV_REGISTER) { in_dev = inetdev_init(dev); if (!in_dev) return notifier_from_errno(-ENOMEM); if (dev->flags & IFF_LOOPBACK) { IN_DEV_CONF_SET(in_dev, NOXFRM, 1); IN_DEV_CONF_SET(in_dev, NOPOLICY, 1); } } goto out; }
switch (event) { case NETDEV_REGISTER: printk(KERN_DEBUG "inetdev_event: bug\n"); dev->ip_ptr = NULL; break; case NETDEV_UP: if (dev->mtu < 68) break; if (dev->flags & IFF_LOOPBACK) { struct in_ifaddr *ifa; if ((ifa = inet_alloc_ifa()) != NULL) { ifa->ifa_local = ifa->ifa_address = htonl(INADDR_LOOPBACK); ifa->ifa_prefixlen = 8; ifa->ifa_mask = inet_make_mask(8); in_dev_hold(in_dev); ifa->ifa_dev = in_dev; ifa->ifa_scope = RT_SCOPE_HOST; memcpy(ifa->ifa_label, dev->name, IFNAMSIZ); inet_insert_ifa(ifa); } } ip_mc_up(in_dev); break; case NETDEV_DOWN: ip_mc_down(in_dev); break; case NETDEV_CHANGEMTU: if (dev->mtu >= 68) break; /* MTU falled under 68, disable IP */ case NETDEV_UNREGISTER: inetdev_destroy(in_dev); break; case NETDEV_CHANGENAME: /* Do not notify about label change, this event is * not interesting to applications using netlink. */ inetdev_changename(dev, in_dev);
devinet_sysctl_unregister(in_dev); devinet_sysctl_register(in_dev); break; } out: return NOTIFY_DONE; }
|
我们要注意传递下来的event是NETDEV_REGISTER事件,函数首先根据传递过来的网卡结构体struct net_device得到struct in_device结构指针,这个in_device结构包含了所有的关于IPV4的一些网卡相关的参数信息
struct in_device { struct net_device *dev; atomic_t refcnt; int dead; struct in_ifaddr *ifa_list; /* IP ifaddr chain */ rwlock_t mc_list_lock; struct ip_mc_list *mc_list; /* IP multicast filter chain */ spinlock_t mc_tomb_lock; struct ip_mc_list *mc_tomb; unsigned long mr_v1_seen; unsigned long mr_v2_seen; unsigned long mr_maxdelay; unsigned char mr_qrv; unsigned char mr_gq_running; unsigned char mr_ifc_count; struct timer_list mr_gq_timer; /* general query timer */ struct timer_list mr_ifc_timer; /* interface change timer */
struct neigh_parms *arp_parms; struct ipv4_devconf cnf; struct rcu_head rcu_head; };
|
我们可以通过ifconfig命令或者ip命令来改变结构内容,这个结构通过其dev指针和网卡的net_device->ip_ptr来与网络设备的结构体net_device建立联系,结构中的refcnt代表引用计数可以用来被参考是否当前结构可以被内存管理释放和回收,只要为0就可以执行回收。其他的内容我们随着代码会逐渐展开,朋友们也可以参阅其他内容看一下其含义。函数中如果还没有建立网卡的这个结构就会执行inetdev_init()函数,这个函数创建一个新的in_device结构并初始化后与网卡的net_device结构体建立上述的连接关系。然后函数层层返回到devinet_init()中了devinet_init()函数中。(明天继续)