接着昨天的继续分析,我们接着从上一节最后部分继续开始今天的内容 :
我是无名小卒,转载的朋友请注明出处,请不要抄袭做为它用,谢谢!
在内核中CONFIG_NET_NS配置选项是为了让用户自定义自己的网络空间结构,即上面的net结构,可以看出2.6.26内核的灵活性,但是我们一般在内核中不会配置该项,所以这里应该是取得init_net,这个结构是在前一节分析的那样在do_one_initcall()机制中调用了从pure_initcall(net_ns_init)注册的net_ns_init()初始化的,这个net_ns_init函数进一步调用setup_net()来对init_net结构进行详细的设置。我们这里不细细分析了他的过程类似于我们前边那节socket的初始化的流程,只不过在setup_net对init_net初始化中有一个非常重要的循环
list_for_each_entry(ops, &pernet_list, list) { if (ops->init) { error = ops->init(net); if (error < 0) goto out_undo; } } |
在这个循环中会从已经注册到pernet_list队列中依次通过其钩子结构pernet_operations,调用结构中的钩子函数init对这个net网络命名空间结构进行初始设置。而这里的pernet_list是一个队列头,它在/net/core/Net_namespace.c文件中的16行处这个队列专门用于person net 用户自定义的pernet_operations结构的登记,也就是我们上面提过的CONFIG_NET_NS情形时才可以将用户自定义的pernet_operations结构链入队列。
static LIST_HEAD(pernet_list); |
而这里的LIST_HEAD宏在include/linux/list.h
#define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) #define LIST_HEAD_INIT(name) { &(name), &(name) } struct list_head { struct list_head *next, *prev; }; |
朋友们可能注意到我经常将调用在前,说明在后的排列数据结构说明,这种习惯个人感觉有助于理解和记忆。“带着问题学习”远比摸黑效率高。那么这个结构是什么时候被初始化的呢?我们结合本类的第一节关于socket的初始化中提到的initcall机制会看到在/net/core/dev.c中有一句
subsys_initcall(net_dev_init);
他引用了include/linux/init.h中的
#define subsys_initcall(fn) __define_initcall("4",fn,4)
所以按照第一节中提到的initcall机制那部分内容,net_dev_init函数将会在开机时得到执行,在这个函数内部有这样的代码:
if (register_pernet_subsys(&netdev_net_ops)) goto out; if (register_pernet_device(&default_device_ops)) goto out; |
上面代码中调用的二个函数都会调用register_pernet_operations函数
int register_pernet_subsys(struct pernet_operations *ops) { int error; mutex_lock(&net_mutex); error = register_pernet_operations(first_device, ops); mutex_unlock(&net_mutex); return error; } int register_pernet_device(struct pernet_operations *ops) { int error; mutex_lock(&net_mutex); error = register_pernet_operations(&pernet_list, ops); if (!error && (first_device == &pernet_list)) first_device = &ops->list; mutex_unlock(&net_mutex); return error; } |
我们在上面第一个函数register_pernet_subsys中看到了first_device这是在Net_namespace.c中17行处声明的
static struct list_head *first_device = &pernet_list; 进入register_pernet_operations我们看一下 static int register_pernet_operations(struct list_head *list, struct pernet_operations *ops) { if (ops->init == NULL) return 0; return ops->init(&init_net); } |
在Net_namespace.c中还有另一个同名的函数但是那必须在支持NET_NS用户自定义网络的选择项打开情况下,我们上面提到过了,所以这里会进入
net_dev_init函数中层层传递下来的
netdev_net_ops钩子结构,执行这个结构中的钩子函数init,我们看一下这个结构
static struct pernet_operations __net_initdata netdev_net_ops = { .init = netdev_init, .exit = netdev_exit, }; |
显然是进入了netdev_init函数,在进入函数之前我们看到它是对系统默认的网络空间结构变量init_net进行的操作
/* Initialize per network namespace state */ static int __net_init netdev_init(struct net *net) { INIT_LIST_HEAD(&net->dev_base_head); net->dev_name_head = netdev_create_hash(); if (net->dev_name_head == NULL) goto err_name; net->dev_index_head = netdev_create_hash(); if (net->dev_index_head == NULL) goto err_idx; return 0; err_idx: kfree(net->dev_name_head); err_name: return -ENOMEM; } |
我是无名小卒本文是我原创完成,尽管
08
年
3
月份才开始写文章但是已经得到了朋友们认可,文章也得到了转载,所以请转载的朋友注明出处
http://qinjiana07876.cublog.cn
,我们在上面的函数中看到初始化了
init_net的几个队列头,其中有二个是哈希队列。
static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } static struct hlist_head *netdev_create_hash(void) { int i; struct hlist_head *hash; hash = kmalloc(sizeof(*hash) * NETDEV_HASHENTRIES, GFP_KERNEL); if (hash != NULL) for (i = 0; i < NETDEV_HASHENTRIES; i++) INIT_HLIST_HEAD(&hash[i]); return hash; } |
这二个函数对队列头进行了初始化,上面函数是对初始化普通的链表头,下面函数是初始化hash队列头。函数很简单。这二个函数分别在include/linux/list.h和/net/core/dev.c中。接下来我们再看上面已经贴出的
register_pernet_device函数调用register_pernet_operations时传递的是default_device_ops钩子结构,会执行这个结构中的init钩子函数
static struct pernet_operations __net_initdata default_device_ops = { .exit = default_device_exit, }; |
但是我们上面看到没有设置init钩子函数,那么看一下上面的register_pernet_operations函数,它在直接返回0了,然后在register_pernet_device函数中就会执行
if (!error && (first_device == &pernet_list)) first_device = &ops->list; |
但是我们看到first_device在这里将会指向default_device_ops中的队列头list
。到这里我们只是看到了对
init_net的几个队列头的初始化,那么还有没有其他重要的初始化呢,如何对
init_net
的网络结构进行的设置的。我们后边随着代码的逐渐深入,将会看到这个重要的数据结构在多个地方进行了设置和调整,我是无名小卒,请转载的朋友注明出处,
http://qinjiana0786.cublog.cn
,我们如果联合想一下内核的设置就能明白了,当你在
make
内核之前,在对内核进行设置选择使用何种网络时根据你对内核的网络选项,内核会有选择的使用
ipv4
还是使用
ipv6
的互联网协议。显然我们练习中使用的是
ipv4
,所以我们在内核编译时会执行下面几个初始化过程,这些过程是根据你对内核的配置而定。
1
、首先是
IP
协议的初始化安装
ipip_init
()函数,这个函数在
/net/ipv4/ipip.c
的
839
行处,其次是
GRE
通用路由协议的初始化安装
ipgre_init
()函数(
/net/ipv4/ip_gre.c
),以及
IEEE的802.1Q VLAN协议的初始化安装vlan_proto_init()(/net/8021q/vlan.c)还有通用的TUN/TAP设备驱动程序中的tun_init()初始化函数(/driversnet/tun.c)。
2、如果朋友们使用的是ipv6的话就会执行ipv6的通道设备初始化ip6_tunnel_init()函数(/net/ipv6/ip6_tunnel.c)、SIT互联网协议的sit_init()(同上目录中的sit.c)、以及上面与ipv4一样的通用的TUN/TAP设备驱动程序中的tun_init()初始化函数(目录同上)和IEEE的802.1Q VLAN协议的初始化安装vlan_proto_init()初始化函数(目录同上)。
我们这里只针对ipv4所以,只看第一项列出的几个初始化函数,那些初始化函数都非常重要,他们都会在函数内部调用register_pernet_gen_device()函数来初始化全局的init_net网络空间结构。我们先看ipip_init
()我们只关心这里重要的对其函数其他部分暂且不做分析
static int __init ipip_init(void) { 。。。。。。 err = register_pernet_gen_device(&ipip_net_id, &ipip_net_ops); 。。。。。。 } |
可以看到他向
register_pernet_gen_device
传递了二个参数结构
ipip_net_id
和
ipip_net_ops
static int ipip_net_id; static struct pernet_operations ipip_net_ops = { .init = ipip_init_net, .exit = ipip_exit_net, }; |
我们进入
register_pernet_gen_device
函数中看是如何初始化的
int register_pernet_gen_device(int *id, struct pernet_operations *ops) { int error; mutex_lock(&net_mutex); again: error = ida_get_new_above(&net_generic_ids, 1, id); if (error) { if (error == -EAGAIN) { ida_pre_get(&net_generic_ids, GFP_KERNEL); goto again; } goto out; } error = register_pernet_operations(&pernet_list, ops); if (error) ida_remove(&net_generic_ids, *id); else if (first_device == &pernet_list) first_device = &ops->list; out: mutex_unlock(&net_mutex); return error; } |
我们暂时只关心与
init_net
相关的函数,上面代码中也是调用了
register_pernet_operations
函数只不过这次传递进去的
ops
是
ipip_net_ops
,我们在上面看过了
register_pernet_operations
,他是首先判断
ipip_net_ops
的
init
钩子是否链入了,我们看到上面他的结构中是
ipip_init_net
函数,那么就要进一步调用这个函数初始化我们这里的
init_net
网络命名空间。
static int ipip_init_net(struct net *net) { int err; struct ipip_net *ipn; err = -ENOMEM; ipn = kzalloc(sizeof(struct ipip_net), GFP_KERNEL); if (ipn == NULL) goto err_alloc; err = net_assign_generic(net, ipip_net_id, ipn); if (err < 0) goto err_assign; ipn->tunnels[0] = ipn->tunnels_wc; ipn->tunnels[1] = ipn->tunnels_l; ipn->tunnels[2] = ipn->tunnels_r; ipn->tunnels[3] = ipn->tunnels_r_l; ipn->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), "tunl0", ipip_tunnel_setup); if (!ipn->fb_tunnel_dev) { err = -ENOMEM; goto err_alloc_dev; } ipn->fb_tunnel_dev->init = ipip_fb_tunnel_init; dev_net_set(ipn->fb_tunnel_dev, net); if ((err = register_netdev(ipn->fb_tunnel_dev))) goto err_reg_dev; return 0; err_reg_dev: free_netdev(ipn->fb_tunnel_dev); err_alloc_dev: /* nothing */ err_assign: kfree(ipn); err_alloc: return err; } |
这里有了一个新的结构
struct ipip_net
,是专门用于
ip
协议专用的数据结构
struct ipip_net { struct ip_tunnel *tunnels_r_l[HASH_SIZE]; struct ip_tunnel *tunnels_r[HASH_SIZE]; struct ip_tunnel *tunnels_l[HASH_SIZE]; struct ip_tunnel *tunnels_wc[1]; struct ip_tunnel **tunnels[4]; struct net_device *fb_tunnel_dev; }; |
其结构内部还包含
IP
通道的结构变量
struct ip_tunnel { struct ip_tunnel *next; struct net_device *dev; struct net_device_stats stat; int recursion; /* Depth of hard_start_xmit recursion */ int err_count; /* Number of arrived ICMP errors */ unsigned long err_time; /* Time when the last ICMP error arrived */ /* These four fields used only by GRE */ __u32 i_seqno; /* The last seen seqno */ __u32 o_seqno; /* The last output seqno */ int hlen; /* Precalculated GRE header length */ int mlink; struct ip_tunnel_parm parms; struct ip_tunnel_prl_entry *prl; /* potential router list */ unsigned int prl_count; /* # of entries in PRL */ }; |
我们先不解释他的作用了,具体的作用会在使用过程中了解的,接着为进入
net_assign_generic
函数为
init_net->gen
也就是
struct net_generic
结构变量分配空间,并将参数
ipip_net_id
和参数结构
ipn
赋值到
gen
中,注意
ipn
是我们刚刚上面分配的
struct ipip_net
,我们可以从
init_net->gen->ptr
数组的以
ipip_net_id
为下标找到这里分配的
IP
协议结构
struct ipip_net
。所以
struct net_generic
结构是一个通用的数据结构
struct net_generic { unsigned int len; struct rcu_head rcu; void *ptr[0]; }; |
这个结构的定义在
/net/netns/generic.h
中,正象注释说的那样他的作用是减少了一些专用数据结构的声明。很多数据可以共用这个能用的数据结构。我是无名小卒,http://qinjiana0786.cublog.cn,转载请注明此出处。这也是他的结构名称的由来。
回到
ipip_init_net
函数中我们继续往下看
ipn->tunnels[0] = ipn->tunnels_wc; ipn->tunnels[1] = ipn->tunnels_l; ipn->tunnels[2] = ipn->tunnels_r; ipn->tunnels[3] = ipn->tunnels_r_l; |
这段代码是对
ipip_net
结构中的
ip
通道数组进行设置,使双重指针数组中的元素,也就是队列头指针分别与
ipip_net
中其他四个
ip
通道的队列数组挂上钩。我们上面看到了可以通过
init_net
全局的网络命名空间结构找到这个
ipip_net
结构变量
ipn
,请朋友们注意这里。在
struct ipip_net
结构中还有一个网络设备的结构体变量
struct net_device
*fb_tunnel_dev
,函数中,
struct net_device
结构体很大,我们不贴了,随着代码的阅读会越来越清楚他的作用。在这里调用了
alloc_netdev
()函数
这是个宏声明在
/incluce/linux/netdevice.h
中
#define alloc_netdev(sizeof_priv, name, setup) \ alloc_netdev_mq(sizeof_priv, name, setup, 1) |
然后他转向了
/net/core/dev.c
中的函数
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name, void (*setup)(struct net_device *), unsigned int queue_count) { void *p; struct net_device *dev; int alloc_size; BUG_ON(strlen(name) >= sizeof(dev->name)); alloc_size = sizeof(struct net_device) + sizeof(struct net_device_subqueue) * (queue_count - 1); if (sizeof_priv) { /* ensure 32-byte alignment of private area */ alloc_size = (alloc_size + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST; alloc_size += sizeof_priv; } /* ensure 32-byte alignment of whole construct */ alloc_size += NETDEV_ALIGN_CONST; p = kzalloc(alloc_size, GFP_KERNEL); if (!p) { printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n"); return NULL; } dev = (struct net_device *) (((long)p + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST); dev->padded = (char *)dev - (char *)p; dev_net_set(dev, &init_net); if (sizeof_priv) { dev->priv = ((char *)dev + ((sizeof(struct net_device) + (sizeof(struct net_device_subqueue) * (queue_count - 1)) + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST)); } dev->egress_subqueue_count = queue_count; dev->gso_max_size = GSO_MAX_SIZE; dev->get_stats = internal_stats; netpoll_netdev_init(dev); setup(dev); strcpy(dev->name, name); return dev; } |
这个函数的主要使用就是分配网络设备结构,并初始化它,函数首先确定要分配一块内存的大小,然后分配内存用于
struct net_device
结构变量
dev
使用,我们看到他调用了一个与
init_net
网络空间结构相关的操作
dev_net_set
(),但是我们追踪进去发现他是在
CONFIG_NET_NS
配置情况下才会得到执行,在这里也只是一个空操作,函数最后调用
ipip_tunnel_setup
()对
dev
进一步的初始化。并且为
dev
复制了一个名称“
tunl0
”。函数返回后,我们在
ipip_init_net
()函数中的
ipn
结构变量其中的
fb_tunnel_dev
也就自此有了具体的指向。最后再进一步对这个网络设备结构初始化和“登记”工作。
ipn->fb_tunnel_dev->init = ipip_fb_tunnel_init; dev_net_set(ipn->fb_tunnel_dev, net); if ((err = register_netdev(ipn->fb_tunnel_dev))) goto err_reg_dev; |
注意上面的
init
钩子函数的挂入了
ipip_fb_tunnel_init
,一会我们会用到。我们进入
register_netdev()
函数
int register_netdev(struct net_device *dev) { int err; rtnl_lock(); /* * If the name is a format string the caller wants us to do a * name allocation. */ if (strchr(dev->name, '%')) { err = dev_alloc_name(dev, dev->name); if (err < 0) goto out; } err = register_netdevice(dev); out: rtnl_unlock(); return err; } |
这个函数的主要做用就是注册网络设备到内核,我们先跳过锁的操作,如果设备名称中含有“
%
”号就代表着让内核自动分配一个名称。我们知道上面已经有一个名称“
tunl0”
了。所以不用分配,接着函数进入了
register_netdevice
(),这是个比较大的函数,我们分段来看
int register_netdevice(struct net_device *dev) { struct hlist_head *head; struct hlist_node *p; int ret; struct net *net; BUG_ON(dev_boot_phase); ASSERT_RTNL(); might_sleep(); /* When net_device's are persistent, this will be fatal. */ BUG_ON(dev->reg_state != NETREG_UNINITIALIZED); BUG_ON(!dev_net(dev)); net = dev_net(dev); spin_lock_init(&dev->queue_lock); spin_lock_init(&dev->_xmit_lock); netdev_set_lockdep_class(&dev->_xmit_lock, dev->type); dev->xmit_lock_owner = -1; spin_lock_init(&dev->ingress_lock); dev->iflink = -1; /* Init, if this function is available */ if (dev->init) { ret = dev->init(dev); if (ret) { if (ret > 0) ret = -EIO; goto out; } } if (!dev_valid_name(dev->name)) { ret = -EINVAL; goto err_uninit; } dev->ifindex = dev_new_index(net); if (dev->iflink == -1) dev->iflink = dev->ifindex; 上面代码中,首先取得了init_net网络空间的结构指针 static inline struct net *dev_net(const struct net_device *dev) { #ifdef CONFIG_NET_NS return dev->nd_net; #else return &init_net; #endif } |
接着要调用
dev
中设置初始化函数,我们在前面提醒过朋友们,在那里间接设置了
ipip_fb_tunnel_init
(),所以会进入这个函数,它在
/net/ipv4/ipip.c
中的
736
行
static int ipip_fb_tunnel_init(struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); struct iphdr *iph = &tunnel->parms.iph; struct ipip_net *ipn = net_generic(dev_net(dev), ipip_net_id); tunnel->dev = dev; strcpy(tunnel->parms.name, dev->name); iph->version = 4; iph->protocol = IPPROTO_IPIP; iph->ihl = 5; dev_hold(dev); ipn->tunnels_wc[0] = tunnel; return 0; } |
这个函数首先是取得IP通道 static inline void *netdev_priv(const struct net_device *dev) { return dev->priv; } |
对照一下之前的设置,在前面分配
struct net_device
dev
调用
alloc_netdev_mq
()函数时,我们再贴一下代码
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name, void (*setup)(struct net_device *), unsigned int queue_count) { 。。。。。。 if (sizeof_priv) { dev->priv = ((char *)dev + ((sizeof(struct net_device) + (sizeof(struct net_device_subqueue) * (queue_count - 1)) + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST)); } 。。。。。。 } |
我们看到一个结构
struct net_device_subqueue
struct net_device_subqueue { /* Give a control state for each queue. This struct may contain * per-queue locks in the future. */ unsigned long state; }; |
他只包含一变量,所以这个结构实际是网络设备结构的一个辅助结构,从字面上看是设备队列使用的,但是没有看到其包含任何的队列头。
queue_count
在参数传递下为是
1
,而
NETDEV_ALIGN_CONST
是个宏定义
#define NETDEV_ALIGN 32
#define NETDEV_ALIGN_CONST (NETDEV_ALIGN - 1)
所以上面的宏是为了保持地址对齐使用的,这样就能看懂了
dev->priv
所指向的位置是与
dev
地址相差一个
struct net_device
结构大小的地址处。所以回到上面
ipip_fb_tunnel_init
()函数中调用
netdev_priv
()就取得了这个地址,然后将这段地址做
struct ip_tunnel
的
IP
通道结构起始地址,在
ip
通道结构中有一个代表其参数的数据结构
struct ip_tunnel_parm { char name[IFNAMSIZ]; int link; __be16 i_flags; __be16 o_flags; __be32 i_key; __be32 o_key; struct iphdr iph; }; 参数中最后一个是IP头部结构变量。 struct iphdr { #if defined(__LITTLE_ENDIAN_BITFIELD) __u8 ihl:4, version:4; #elif defined (__BIG_ENDIAN_BITFIELD) __u8 version:4, ihl:4; #else #error "Please fix <asm/byteorder.h>" #endif __u8 tos; __be16 tot_len; __be16 id; __be16 frag_off; __u8 ttl; __u8 protocol; __sum16 check; __be32 saddr; __be32 daddr; /*The options start here. */ }; |
上面
IP
头部结构中变量根据应用我们自然会明白其作用和含义。
所以函数中的
struct iphdr *iph
指了
IP
头部。接着我们看到
struct ipip_net *ipn = net_generic(dev_net(dev), ipip_net_id);
这是取得我们在
ipip_init_net
()函数中已经初始化的
ipn
结构变量。函数首先是让靠在
dev
后边的
ip
通道与
dev
挂上关系,紧接着复制网络设备名称到
ip
通道的参数结构
ip_tunnel
中。接着对
ip
头部结构进行了设置
iph->version = 4;//
表示
ip
版本是
ipv4
iph->protocol = IPPROTO_IPIP;//
使用
IPIP
通道
iph->ihl = 5;//ip
头部的长度是
5
然后让
ipn
的
ip
结构中的通道指针数组指向这里的
ip
通道
ipn->tunnels_wc[0] = tunnel;
初始化
dev
结构以后,我们回到
register_netdevice
()函数代码中,下面是检查
init_net
中的
hash
队列头是否已经存在该网络设备。
其具体过程请朋友们自已追踪了。我们为了不离了主题,层层返回到
ipip_init
()函数中,也就完成了
IP
协议的初始化过程。我们已经看到了其对
init_net
的初始化部分。很明显
init_net
的重要性不言而喻。
转自:http://blog.chinaunix.net/uid-7960587-id-2035550.html