Ip协议栈阅读笔记.docx
- 文档编号:17752087
- 上传时间:2023-08-03
- 格式:DOCX
- 页数:27
- 大小:27.18KB
Ip协议栈阅读笔记.docx
《Ip协议栈阅读笔记.docx》由会员分享,可在线阅读,更多相关《Ip协议栈阅读笔记.docx(27页珍藏版)》请在冰点文库上搜索。
Ip协议栈阅读笔记
协议栈阅读笔记
先从init/main.c的start_kernel函数说起。
在这个函数里面调用kernel_thread启动了init进程,这个进程对应的函数是同一个文件里面的init函数,在init函数里面调用了一个叫do_basic_setup的在同一个文件里面的函数,这个函数调用了net/socket.c里面的sock_init函数,这个函数就是TCP/IP协议栈,也包括ipx等的入口。
首先sock_init函数里面有很多ifdef这样的东西,我觉得对于一个
普通的主机来说,这些都不会配置的,它们包括:
SLAB_SKB,CONFIG_WAN_ROUTER,CONFIG_FIREWALL,CONFIG_RTNETLINK,
CONFIG_NETLINK_DEV
去掉了这些编译选项以后就剩下这样的代码:
for(i=0;i net_families=NULL; sk_init(); proto_init(); 其中net_families在include/linux/net.h里面定义,是这样的: structnet_proto_family { intfamily; int(*create)(structsocket*sock,intprotocol); /*Thesearecountersforthenumberofdifferentmethodsof eachwesupport*/ shortauthentication; shortencryption; shortencrypt_net; }; 其中有用的只有前两项,那个create的callback函数是每个协议,例如AF_INET等初始化 上层协议如TCP/ICMP协议需要的,以后还会遇到的 sk_init函数在net/core/sock.c里面,没什么说的.. structsock*sk_alloc(intfamily,intpriority,intzero_it) { structsock*sk=kmem_cache_alloc(sk_cachep,priority); if(sk){ if(zero_it) memset(sk,0,sizeof(structsock)); sk->family=family; } returnsk; } proto_init函数在同一个文件里面: void__initproto_init(void) { externstructnet_protoprotocols[]; structnet_proto*pro; pro=protocols; while(pro->name! =NULL) { (*pro->init_func)(pro); pro++; } } structnet_proto在include/linux/net.h里面是这样的: structnet_proto { constchar*name;/*Protocolname*/ void(*init_func)(structnet_proto*);/*Bootstrap*/ }; 这个protocols的数组是在net/protocols.c里面定义的,包含了一堆的协议初始化结构体,其中我只注意两个: AF_INET和AF_PACKET 它们的初始化函数分别是inet_proto_init和packet_proto_init 下面来看看IPv4协议和PACKET协议的初始化过程。 首先看PACKET协议,首先我们假定PACKET协议是编译在核心里面的,而不是一个MODULE,这样得到packet_proto_init函数在net/packet/af_packet.c里面是这样的: void__initpacket_proto_init(structnet_proto*pro) { sock_register(&packet_family_ops); register_netdevice_notifier(&packet_netdev_notifier); } 其中sock_register函数在net/socket.c里面,就是简单的设置前面说的net_families数组中间对应的值: intsock_register(structnet_proto_family*ops) { if(ops->family>=NPROTO){ printk(KERN_CRIT"protocol%d>=NPROTO(%d)\n",ops->family,NPROTO); return-ENOBUFS; } net_families[ops->family]=ops; return0; } 这里要说明的是packet_netdev_notifier是一个structnotifier_block类型,这个struct是在include/linux/notifier.h里面的: structnotifier_block { int(*notifier_call)(structnotifier_block*self,unsignedlong,void*); structnotifier_block*next; intpriority; }; 而register_netdevice_notifier函数在net/core/dev.c里面,是这样的: intregister_netdevice_notifier(structnotifier_block*nb) { returnnotifier_chain_register(&netdev_chain,nb); } 而notifier_chain_register函数在include/linux/notifier.h里面,是 这样的: extern__inline__intnotifier_chain_register( structnotifier_block**list,structnotifier_block*n) { while(*list) { if(n->priority>(*list)->priority) break; list=&((*list)->next); } n->next=*list; *list=n; return0; } 显然就是根据每个block的优先级把这个block排列在一个block的链表里面,在notifier_chain_register函数里面我们可以发现这个链表是netdev_chain。 实际上这个链表的作用就是在每个interface打开,关闭状态改变或者外界调用相应的ioctl的时候通知这个链表上面的所有相关的设备,而每一个协议都调用register_netdevice_notifier注册了一个netdev_notifier的结构体,这样就可以在interface改变的时候得到通知了(通过调用每个notifier_call函数)。 下面来看inet_proto_init函数,这个函数在net/ipv4/af_inet.c中间,里面也有很多ifdef的编译选项,假定下面几个是没有定义的: CONFIG_NET_IPIP,CONFIG_NET_IPGRE,CONFIG_IP_FIREWALL,CONFIG_IP_MASQUERADE,CONFIG_IP_MROUTE 假定下面几个是定义了的: CONFIG_INET_RARP,CONFIG_PROC_FS下面是整理后的代码: (void)sock_register(&inet_family_ops); for(p=inet_protocol_base;p! =NULL;){ structinet_protocol*tmp=(structinet_protocol*)p->next; inet_add_protocol(p); printk("%s%s",p->name,tmp? ",": "\n"); p=tmp; } arp_init(); ip_init(); tcp_v4_init(&inet_family_ops); tcp_init(); icmp_init(&inet_family_ops); rarp_ioctl_hook=rarp_ioctl; proc_net_register(&proc_net_rarp); proc_net_register(&proc_net_raw); proc_net_register(&proc_net_snmp); proc_net_register(&proc_net_netstat); proc_net_register(&proc_net_sockstat); proc_net_register(&proc_net_tcp); proc_net_register(&proc_net_udp); 其中的sock_register函数的作用已经在前面说了,现在来看看structinet_protocol和inet_add_protocol函数。 前面的结构体是在include/net/protocol.h里面: structinet_protocol { int(*handler)(structsk_buff*skb,unsignedshortlen); void(*err_handler)(structsk_buff*skb,unsignedchar*dp,intlen); structinet_protocol*next; unsignedcharprotocol; unsignedcharcopy: 1; void*data; constchar*name; }; 第一个函数是用来接收数据的callback函数,第二个是错误处理函数,其它的copy是用来协议共享的,这个以后再说,data当然就是这个结构体的私有数据了。 inet_add_protocol函数是在net/ipv4/protocol.c里面的: voidinet_add_protocol(structinet_protocol*prot) { unsignedcharhash; structinet_protocol*p2; hash=prot->protocol&(MAX_INET_PROTOS-1); prot->next=inet_protos[hash]; inet_protos[hash]=prot; prot->copy=0; p2=(structinet_protocol*)prot->next; while(p2! =NULL) { if(p2->protocol==prot->protocol) { prot->copy=1; break; } p2=(structinet_protocol*)p2->next; } } 显然这个函数就是作乐一个hash表,然后每个hash表项都是一个链表头,然后通过这个hash表加链表的方式访问每个协议结构体。 在这里你也见到了copy成员的用法了把。 arp_init函数是在net/ipv4/arp.c里面的(假定没有定义CONFIG_SYSCTL): neigh_table_init(&arp_tbl); dev_add_pack(&arp_packet_type); proc_net_register(&proc_net_arp); 不知道是不是有人眼睛一亮啊,呵呵,看到了dev_add_pack函数。 还是一步步来把。 neigh_table_init函数在net/core/neighbour.c中间: voidneigh_table_init(structneigh_table*tbl) { unsignedlongnow=jiffies; tbl->parms.reachable_time=neigh_rand_reach_time(tbl->parms.base_reachable_time); init_timer(&tbl->gc_timer); tbl->gc_timer.data=(unsignedlong)tbl; tbl->gc_timer.function=neigh_periodic_timer; tbl->gc_timer.expires=now+tbl->gc_interval+ tbl->parms.reachable_time; add_timer(&tbl->gc_timer); init_timer(&tbl->proxy_timer); tbl->proxy_timer.data=(unsignedlong)tbl; tbl->proxy_timer.function=neigh_proxy_process; skb_queue_head_init(&tbl->proxy_queue); tbl->last_flush=now; tbl->last_rand=now+tbl->parms.reachable_time*20; tbl->next=neigh_tables; neigh_tables=tbl; } jiffies是当前系统的时间,在i386系统上面好象一个jiffies代表50ms,显然这个函数就是生成两个timer将一个放在系统的timerlist里面。 那个gc_timer的意思是garbagecollecttimer,因为每过一段时间arp的cache就应该更新,所以要有一个expires时间,这段时间过了以后就要更新arp地址了,那个proxy_timer还没有看是什么,不过我假定我的机器不使用proxy也不做成proxy,所以proxy相关的都没有管. 那个timer的function显然是时钟到期的回调函数,data是这个回调函数要使用的私有数据了。 下面是dev_add_pack函数,它在net/core/dev.c里面: voiddev_add_pack(structpacket_type*pt) { inthash; #ifdefCONFIG_NET_FASTROUTE /*Hacktodetectpacketsocket*/ if(pt->data){ netdev_fastroute_obstacles++; dev_clear_fastroute(pt->dev); } #endif if(pt->type==htons(ETH_P_ALL)) { netdev_nit++; pt->next=ptype_all; ptype_all=pt; } else { hash=ntohs(pt->type)&15; pt->next=ptype_base[hash]; ptype_base[hash]=pt; } } 显然系统保留了两个表,一个是ptype_all,用来接收所有类型的包的链表,一个是一个hash数组+链表的结构,用来接收特定类型的包。 那个fastroute不知道是什么东西。 structpacket_type的定义在include/linux/netdevice.h里面,我保留原来的注释 { unsignedshorttype; /*Thisisreallyhtons(ether_type).*/ structdevice*dev; /*NULLiswildcardedhere*/ int(*func)(structsk_buff*, structdevice*,structpacket_type*); void*data; /*Privatetothepackettype*/ structpacket_type*next; }; 其中的func当然是回调函数了,举个例子来说,arp_packet_type是这样的: staticstructpacket_typearp_packet_type= { __constant_htons(ETH_P_ARP), NULL,/*Alldevices*/ arp_rcv, NULL, NULL }; arp_init函数还有最后一个proc_net_register函数,这个函数在include/linux/proc_fs.h里面: staticinlineintproc_net_register(structproc_dir_entry*x) { returnproc_register(proc_net,x); } 而proc_register在fs/proc/root.c里面,这个函数就不帖了主要就是在proc_net对应的目录下面生成每个协议的子目录。 这个函数的作用就是对每一个协议,例如TCP等在/proc目录下面生成相应的目录,用户可以通过访问/proc/net目录下面的相应目录得到每个协议的统计参数。 下面是ip_init函数,它在net/ipv4/ip_output.c里面: (下面假定定义了CONFIG_PROC_FS,CONFIG_IP_MULTICAST和CONFIG_NET_CLS_ROUTE) __initfunc(voidip_init(void)) { dev_add_pack(&ip_packet_type); ip_rt_init(); proc_net_register(&proc_net_igmp); } 前面的dev_add_pack是说过的,这里就不再说了,而且proc_net_register也是前面提过的,这里都不说了,先来看看ip_rt_init函数把,它在net/ipv4/route.c里面,函数是这样的: __initfunc(voidip_rt_init(void)) { structproc_dir_entry*ent; devinet_init(); ip_fib_init(); rt_periodic_timer.function=rt_check_expire; /*Allthetimers,startedatsystemstartuptend tosynchronize.Perturbitabit. */ rt_periodic_timer.expires=jiffies+net_random()% ip_rt_gc_interval+ip_rt_gc_interval; add_timer(&rt_periodic_timer); proc_net_register(&(structproc_dir_entry){ PROC_NET_RTCACHE,8,"rt_cache", S_IFREG|S_IRUGO,1,0,0, 0,&proc_net_inode_operations, rt_cache_get_info }); ent=create_proc_entry("net/rt_acct",0,0); ent->read_proc=ip_rt_acct_read; } 这个函数总的看来就是注册几个notifier(后面还要看的)和初始化路由表的timer,最后就在/proc目录下面创建一个目录项。 其中proc_net_register函数就不说了,而create_proc_entry函数就是在/proc/net目录下面创建一个rt_acct,就是路由参数统计(account)目录,读函数就是ip_rt_acct_read,这个函数就是从全局变量ip_rt_acct中间拷贝数据到用户缓冲中而已。 devinet_init函数是net/ipv4/devinet.c里面的函数,整理后如下: register_gifconf(PF_INET,inet_gifconf); register_netdevice_notifier(&ip_netdev_notifier); register_netdevice_notifier函数在说PACKET协议的时候提过,这里不说了, register_gifconf函数是用来注册对应SIOCGIFCONF这个系统调用的协议无关 的一个回调函数,这个函数对于PF_INET来说就是inet_gifconf函数。 其中inet_gifconf函数是net/ipv4/devinet.c里面的,我大概的看了一点,主要好象是在所有的interface里面做一个循环,得到相应的name和address然后返回的。 不过不是非常确定。 大家参谋呀: ) 而register_gifconf函数本身是在net/core/dev.c里面的,如下: staticgifconf_func_t*gifconf_list[NPROTO]; intregister_gifconf(unsignedintfamily,gifconf_func_t*gifconf) { if(family>=NPROTO) return-EINVAL; gifconf_list[family]=gifconf; return0; } 这个函数的意义一目了然,就不说了。 gifconf_list里的函数会在dev_ifconf函数中间被调用,而dev_ifconf函数被dev_ioctl函数调用,dev_ioctl函数负责所有的针对interface的I/O控制。 所以我们调用的interface的ioctl函数有一部分就会分到每个协议的gifconf函数里面来,我猜gifconf大概是generousinterfaceconfigure的意思。 就是通用接口配置的意思。 下面再看ip_fib_init函数,它在net/ipv4/fib_frontend.c中间,如下: (假定没有defineCONFIG_IP_MULTIPLE_TABLES,这个参数好象是要创建两个路由表,一个是local的,一个叫main,我想大概一般系统上面只有一个路由表的,所以认为没有定义,不知道哪位清楚这个参数的意义? 为什么要两个路由表? ) __initfunc(voidip_fib_init(void)) { proc_net_register(&(structproc_dir_entry){ PROC_NET_ROUTE,5,"route", S_IFREG|S_IRUG
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Ip 协议 阅读 笔记