代码是基于2.6.18,挑了几个比较重要的函数和2.6.32做了对比,发现核在网络上代码并没有太的变化。
网络的初始化
网络的初始化分4个阶段:socket_init,inet_init,net_dev_init,驱动的初始化。
依次解读这个4个阶段的初始化工作,在这之前要先找到这4个函数的调用入口。
这4个阶段的调用发生在:
start_kernel() -> rest_init() -> kernel_thread:init() -> do_basic_setup() -> do_initcalls()
在rest_init里面会启动一个内核线程,这个线程进入init函数,最终在do_initcalls里面调用到了所有被__init修饰的函数。
do_initcalls
static void __init do_initcalls(void) { initcall_t *call; int count = preempt_count(); for (call = __initcall_start; call < __initcall_end; call++) { result = (*call)(); } }
__initcall_start和__initcall_end看起来是函数指针的数组,他们是在哪里设置的呢?
这两个符号是链接器在链接阶段动态产生的,并非是写在代码里的。这个过程是链接脚本控制的。
内核的链接脚本
ld -verbose
显示系统默认的链接脚本。
而,内核有自己的链接脚本,在源码的arch/x86_64/kernel/vmlinux.lds.S
__initcall_start = .; .initcall.init : { *(.initcall1.init) *(.initcall2.init) *(.initcall3.init) *(.initcall4.init) *(.initcall5.init) *(.initcall6.init) *(.initcall7.init) } __initcall_end = .;
其中initcall1.init第1优先级得到执行,initcall7为第7优先级得到执行,其定义如下:
#define core_initcall(fn)__define_initcall("1",fn) #define postcore_initcall(fn)__define_initcall("2",fn) #define arch_initcall(fn)__define_initcall("3",fn) #define subsys_initcall(fn)__define_initcall("4",fn) #define fs_initcall(fn)__define_initcall("5",fn) #define device_initcall(fn)__define_initcall("6",fn) #define late_initcall(fn)__define_initcall("7",fn) #define __define_initcall(level,fn) \ static initcall_t __initcall_##fn __attribute_used__ \ __attribute__((__section__(".initcall" level ".init"))) = fn
可以看到,core_initcall等修饰的函数,其实就是给这个函数添加__section__属性。
而链接器脚本把所有__initcallN修饰的函数放在_initcall_start开始, initcall_end结束的数组里。
通过objump可以查看编译后的vmlinux中的符号。
objdump -t vmlinux |grep initcall ffffffff817aedd0 g .init.data 0000000000000000 __initcall_start ffffffff817af8c0 g .init.data 0000000000000000 __initcall_end
第一阶段:socket_init 基础设施的初始化
代码文件:net/socket.c
static int __init sock_init(void) { sk_init(); skb_init(); init_inodecache(); register_filesystem(&sock_fs_type); sock_mnt = kern_mount(&sock_fs_type); netfilter_init(); return 0; }
1) sk_init
初始化socket的SLABcache。
2) skb_init
初始化skbuff的SLABcache。
3) init_inodecache
初始化套接字和文件系统的接口。
static int init_inodecache(void) { sock_inode_cachep = kmem_cache_create("sock_inode_cache", sizeof(struct socket_alloc), 0, (SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT| SLAB_MEM_SPREAD), init_once, NULL); }
sock_inode_cache在内核中存储socket的内核结构。创建对象池sock_inode_cachep用来分配socket_alloc。
4) register_filesystem(&sock_fs_type);
sock_mnt = kern_mount(&sock_fs_type);
在全局变量file_systems等级scok类型的文件系统。
其中
static struct file_system_type sock_fs_type = { .name ="sockfs", .get_sb =sockfs_get_sb, .kill_sb =kill_anon_super, };
经过这两个函数调用,最终把sockfs挂入了VFS中。
sock_fs_type 挂入了全局变量 file_systems中;
通过sockfs_get_sb()生成的super_block 挂入了全局变量 super_block中。
形成了三元组:<super_block, sock_fs_type, sockfs_ops>
skbuff
网络协议栈最重要的数据结构,skbuff承载要发送,接收的数据,经过tcp层,ip层,Ethernet层。 所以,skbuff结构体中有各层协议头部的结构体:
struct sk_buff { /* These two members must be first. */ struct sk_buff*next; struct sk_buff*prev; struct sock*sk; struct skb_timevaltstamp; struct net_device*dev; struct net_device*input_dev; union { struct tcphdr*th; struct udphdr*uh; struct icmphdr*icmph; struct igmphdr*igmph; struct iphdr*ipiph; struct ipv6hdr*ipv6h; unsigned char*raw; } h; union { struct iphdr*iph; struct ipv6hdr*ipv6h; struct arphdr*arph; unsigned char*raw; } nh; union { unsigned char *raw; } mac; struct dst_entry*dst; structsec_path*sp; /* These elements must be at the end, see alloc_skb() for details. */ unsigned inttruesize; atomic_tusers; unsigned char*head, *data, *tail, *end; };
为了加速skbuff的分配,在SLAB系统有两个cachep:skbuff_head_cache和skbuff_fclone_cache。
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, int fclone) { kmem_cache_t *cache; struct skb_shared_info *shinfo; struct sk_buff *skb; u8 *data; cache = fclone ? skbuff_fclone_cache : skbuff_head_cache; // 分配sk_buff结构体 skb = kmem_cache_alloc(cache, gfp_mask & ~__GFP_DMA); if (!skb) goto out; size = SKB_DATA_ALIGN(size); // kmalloc分配数据体 data = ____kmalloc(size + sizeof(struct skb_shared_info), gfp_mask); if (!data) goto nodata; memset(skb, 0, offsetof(struct sk_buff, truesize)); skb->truesize = size + sizeof(struct sk_buff); atomic_set(&skb->users, 1); // 设置head, data, tail, end指向数据体的开始 skb->head = data; skb->data = data; skb->tail = data; skb->end = data + size; // 在sk_buff尾部还有一个结构体描述聚合IO信息 shinfo = skb_shinfo(skb); atomic_set(&shinfo->dataref, 1); shinfo->nr_frags = 0; shinfo->gso_size = 0; shinfo->gso_segs = 0; shinfo->gso_type = 0; shinfo->ip6_frag_id = 0; shinfo->frag_list = NULL; // 如果是clone, 则设置相邻的sk_buff if (fclone) { struct sk_buff *child = skb + 1; atomic_t *fclone_ref = (atomic_t *) (child + 1); skb->fclone = SKB_FCLONE_ORIG; atomic_set(fclone_ref, 1); child->fclone = SKB_FCLONE_UNAVAILABLE; }
各层协议会把该层协议的头部信息append到head, data, tail, end的指针指向的内存。
第二阶段:inet_init 网络协议的初始化
inet_init()的初始化非常重要,主要初始化了tcp/ip协议栈中各个层支持的协议族,协议类型,协议对应的操作。
用户态创建套接字的过程就是根据套接字的参数,从这些基础设施中选择出对应的协议。
初始化3个全局的数据结构:
1) net_families[];
2) inetsw_array;
3) inet_protos.
地址族和套接字类型
地址族
地址族的结构体:
struct net_proto_family { int family; int(*create)(struct socket *sock, int protocol); short authentication; short encryption; short encrypt_net; struct module*owner; }; }
系统预留了32种地址族。已经使用了PF_UNIX, PF_INET, PF_NETLINK, PF_PACKET。
static struct net_proto_family *net_families[NPROTO];
inet_init
static int __init inet_init(void) { rc = proto_register(&tcp_prot, 1); rc = proto_register(&udp_prot, 1); rc = proto_register(&raw_prot, 1); (void)sock_register(&inet_family_ops); inet_add_protocol(&icmp_protocol, IPPROTO_ICMP); inet_add_protocol(&udp_protocol, IPPROTO_UDP); inet_add_protocol(&tcp_protocol, IPPROTO_TCP); for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q) inet_register_protosw(q); arp_init(); ip_init(); tcp_init(); icmp_init(&inet_family_ops); dev_add_pack(&ip_packet_type); }
1) 调用3次proto_register,将tcp_prot, udp_prot, raw_prot挂入到全局变量prot_list上.
struct proto { void(*close)(struct sock *sk, long timeout); int(*connect)(struct sock *sk, struct sockaddr *uaddr, int addr_len); int(*ioctl)(struct sock *sk, int cmd, unsigned long arg); int(*sendmsg)(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len); int(*setsockopt)(struct sock *sk, int level, int optname, char __user *optval, int optlen); int(*bind)(struct sock *sk, struct sockaddr *uaddr, int addr_len); ... int *memory_pressure; int *sysctl_mem; int *sysctl_wmem; int *sysctl_rmem; kmem_cache_t *slab; unsigned int obj_size; }
2) sock_register
把inet_family_ops添加到net_families数组里。
unix_family_ops和netlink_family_ops也是通过这个函数添加进来的。
static struct net_proto_family inet_family_ops = { .family = PF_INET, .create = inet_create, .owner= THIS_MODULE, };
3) inet_add_protocol
注册接收函数到全局数组inet_protos
static struct net_protocol tcp_protocol = { .handler =tcp_v4_rcv, .err_handler =tcp_v4_err, .gso_send_check = tcp_v4_gso_send_check, .gso_segment =tcp_tso_segment, .no_policy =1, }; static struct net_protocol udp_protocol = { .handler =udp_rcv, .err_handler =udp_err, .no_policy =1, }; static struct net_protocol icmp_protocol = { .handler =icmp_rcv, };
4) inet_register_protosw(q);
注册协议切换表到全局变量inetsw中
static struct inet_protosw inetsw_array[] = { { .type = SOCK_STREAM, .protocol = IPPROTO_TCP, .prot = &tcp_prot, .ops = &inet_stream_ops, .capability = -1, .no_check = 0, .flags = INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK, }, { .type = SOCK_DGRAM, .protocol = IPPROTO_UDP, .prot = &udp_prot, .ops = &inet_dgram_ops, .capability = -1, .no_check = UDP_CSUM_DEFAULT, .flags = INET_PROTOSW_PERMANENT, }, { .type = SOCK_RAW, .protocol = IPPROTO_IP,/* wild card * .prot = &raw_prot, .ops = &inet_sockraw_ops, .capability = CAP_NET_RAW, .no_check = UDP_CSUM_DEFAULT, .flags = INET_PROTOSW_REUSE, } }
5) arp_init();
ip_init();
tcp层以下的协议层,需要一个接收函数用来解封装报文。
对底层协议感兴趣的协议有两个:arp和ip。
报文从设备送往上层之前必须区分是arp报文还是ip报文。
这个过程由packet_type中的type来完成。
struct packet_type { __be16 type;/* This is really htons(ether_type). */ struct net_device* dev;/* NULL is wildcarded here */ int(*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *); ... };
6) dev_add_pack(&ip_packet_type);
添加ip的handler到协议栈ptype_base中。
ip_packet_type的定义如下:
static struct packet_type ip_packet_type = { .type = __constant_htons(ETH_P_IP), .func = ip_rcv, .gso_send_check = inet_gso_send_check, .gso_segment = inet_gso_segment, };
第三阶段:net_dev_init 抽象网络设备的初始化
static int __init net_dev_init(void) { net_random_init(); dev_proc_init(); netdev_sysfs_init(); for_each_possible_cpu(i) { struct softnet_data *queue; queue = &per_cpu(softnet_data, i); skb_queue_head_init(&queue->input_pkt_queue); queue->completion_queue = NULL; INIT_LIST_HEAD(&queue->poll_list); set_bit(__LINK_STATE_START, &queue->backlog_dev.state); queue->backlog_dev.weight = weight_p; queue->backlog_dev.poll = process_backlog; atomic_set(&queue->backlog_dev.refcnt, 1); } open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL); }
1) for_eache_possible_cpu
每个cpu都有一个队列用来收发数据包,因此不同cpu之间无需上锁。
全局变量 per_cpu_softnet_data[NCPU]就是这个数组。
struct softnet_data { struct net_device*output_queue; struct sk_buff_headinput_pkt_queue; struct list_headpoll_list; struct sk_buff*completion_queue; struct net_devicebacklog_dev;/* Sorry. 8) */ #ifdef CONFIG_NET_DMA struct dma_chan*net_dma; #endif };
1) open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
设置软中断!
第四阶段:设备驱动的初始化
PCI总线会调用pci_module_init 初始化驱动,
__driver_attach() -> driver_probe_device() -> ret = drv->probe(dev);
void driver_attach(struct device_driver * drv) { bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); } int driver_probe_device(struct device_driver * drv, struct device * dev) { int ret = 0; if (drv->bus->match && !drv->bus->match(dev, drv)) goto Done; dev->driver = drv; if (dev->bus->probe) { ret = dev->bus->probe(dev); if (ret) { dev->driver = NULL; goto ProbeFailed; } } else if (drv->probe) { ret = drv->probe(dev); if (ret) { dev->driver = NULL; goto ProbeFailed; } } device_bind_driver(dev); ret = 1; }
遍历所有的device,如果drv->bus->match(dev, drv),当前的驱动和设备match了,
会通过drv->probe()最终进入具体的驱动程序初始化。
int register_netdevice(struct net_device *dev) { dev->ifindex = dev_new_index(); head = dev_name_hash(dev->name); ret = netdev_register_sysfs(dev); *dev_tail = dev; dev_tail = &dev->next; hlist_add_head(&dev->name_hlist, head); hlist_add_head(&dev->index_hlist, dev_index_hash(dev->ifindex)); }
1) *dev_tail = dev;
dev_tail = &dev->next;
把dev添加到dev_base列表中。
2) hlist_add_head(&dev->name_hlist, head)
根据dev->name,把dev添加到dev_name_head哈希表中。
3) hlist_add_head(&dev->index_hlist, dev_index_hash(dev->ifindex));
根据dev->ifindex,把dev添加到dev_index_head哈希表中。
4) net_device的结构体很庞大,只列出比较重要的。
有几个网卡接口就会有几个这样的数据结构。
struct net_device { unsigned long base_addr;/* device I/O address*/ unsigned int irq;/* device IRQ number*/ void *ip_ptr; void *dev; // Qos相关 struct Qdisc *qdisc; struct Qdisc* qdisc_sleeping; struct list_headqdisc_list; Unsigned long tx_queue_len; struct hlist_nodeindex_hlist; }
in_device和net_device之间的关系:
1) in_device{}: 设备无关层,保存IP地址信息,neighbour信息;
2) net_device{}: 保存设备的名字,地址,等共性;
3) 特定的网络层驱动,有硬件发送缓冲,接收缓冲,芯片的寄存器。这片内存一般是紧紧的跟在net_device之后。
loopback的初始化
文件:drivers/net/loopback.c
首先是loopback的net_device结构体
struct net_device loopback_dev = { .name = "lo", .mtu= (16 * 1024) + 20 + 20 + 12, .hard_start_xmit= loopback_xmit, .hard_header= eth_header, .hard_header_cache= eth_header_cache, .header_cache_update= eth_header_cache_update, .hard_header_len= ETH_HLEN,/* 14*/ .addr_len= ETH_ALEN,/* 6*/ .tx_queue_len= 0, .type= ARPHRD_LOOPBACK,/* 0x0001*/ .rebuild_header= eth_rebuild_header, .flags= IFF_LOOPBACK, .features = NETIF_F_SG | NETIF_F_FRAGLIST .ethtool_ops= &loopback_ethtool_ops, }
loopback的初始化并非是有PCI调用的,而是在系统初始化时主动把自己初始化的:
int __init loopback_init(void) { struct net_device_stats *stats; stats = kmalloc(sizeof(struct net_device_stats), GFP_KERNEL); memset(stats, 0, sizeof(struct net_device_stats)); loopback_dev.priv = stats; loopback_dev.get_stats = &get_stats; return register_netdev(&loopback_dev); }