我们依然从start_kernel说起,它最后会执行:
arch_call_rest_init() --> rest_init() --> Kernel_init() --> Kernei_init_freeable() --> do_basic_setup() --> do_initcalls() --> do_initcall_level(level)
do_initcall_level(level)会根据level从0级开始以次执行相应先后等级的初始化函数。
第一步:core_initcall:socket_init
而在socket.c文件里面有如下代码:
core_initcall(sock_init); /* early initcall */
使用core_initcall初始化宏修饰sock_init函数,这个宏指定了sock_init函数放在级别为1的代码中,也就是说它的执行时最先进的一部分,此函数只是分配一些内存空间,以及创建了一个sock_fs_type的文件系统。在do_basic_setup中调用sock_init先于internet协议注册被调用,因此基本的socket初始化必须在每一个TCP/IP成员协议能注册到socket层之前完成。socket_init代码如下:
static int __init sock_init(void) { /* * Initialize sock SLAB cache. */ sk_init(); /* * Initialize skbuff SLAB cache */ skb_init(); /* * Initialize the protocols module. */ init_inodecache(); register_filesystem(&sock_fs_type); sock_mnt = kern_mount(&sock_fs_type); /* The real protocol initialization is performed in later initcalls. */ #ifdef CONFIG_NETFILTER netfilter_init(); #endif #ifdef CONFIG_NETWORK_PHY_TIMESTAMPING skb_timestamping_init(); #endif return 0; }
sock_init函数看上去比较简单,其实里面完成了相当重要的工作。第一句调用sk_init(),其实不做什么实质性的事,只是对一些变量进行赋值。而skb_init()函数作用就是创建了两个缓存,skbuff_head_cache和skbuff_fclone_cache。协议中相关的数据都在这两个缓存中创建。所以这是在初始化相关数据结构。
sk_buff结构 如下:
数据包在应用层成为data,在TCP层成为segment,在IP层成为packet,在数据链路层成为frame。linux内核中sk_buff{}结构来存放数组,在INET socket和它以下的层次中用来存放网络接收到或需要发送的数据,因此它需要设计的有足够扩展性。
为了使用套接字缓冲区,内核创建了两个后备高速缓存lookaside cache,他们分别是skbuff_head_cache和skbuff_fclone_cache,协议栈中所使用到的所有sk_buff结构都是从这两个后备高速缓存中分配出来的。两者的区别在于skbuff_head_cache在创建时指定的单位内存区域的大小是sizeof(struct sk_buff),可以容纳任意数目的struct sk_buff,而skbuff_fclone_cache在创建时指定的单位内存区域大小是2*sizeof(struct sk_buff)+sizeof(atomic_t),它的最小区域单位是一对struct sk_buff和一个引用计数,这一对sk_buff是克隆的,即它们指向同一数据缓冲区,引用计数值是0,1,或2,表示这一对中有几个sk_buff已被调用。
内存管理函数
在sk_buff{}中4个指针data、head、tail、end初始化的时候,data,head,tail都是指向申请到数据区的头部,end指向数据区的尾部。在以后的操作中,一般都是通过data和tail来获得在sk_buff中可用的数据区的开始和结尾。而head和end就表示sk_buff中存在的数据包最大可扩展的空间范围。
以下为网络协议栈的内存管理函数
sock_init()函数最后调用netfilter_init(),它会根据配置来对相应的协议模块进行初始化,而TCP/IP协议栈的初始化的函数入口 inet_init() 函数。
第二步:fs_initcall : inet_init()
该函数定义在linux/net/ipv4/af_inet.c文件中,源码如下:
static int __init inet_init(void) { ... rc = proto_register(&tcp_prot, 1); if (rc) goto out; ... /* * Tell SOCKET that we are alive... */ (void)sock_register(&inet_family_ops); ... /* * Add all the base protocols. */ ... if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) pr_crit("%s: Cannot add TCP protocol\n", __func__); ... /* Register the socket-side information for inet_create. */ for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r) INIT_LIST_HEAD(r); for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q) inet_register_protosw(q); ... /* Setup TCP slab cache for open requests. */ tcp_init(); ... } fs_initcall(inet_init);
tcp_init()函数将初始化tcp协议,其定义如下:
void __init tcp_init(void) { ... tcp_v4_init(); tcp_metrics_init(); BUG_ON(tcp_register_congestion_control(&tcp_reno) != 0); tcp_tasklet_init(); }
可以看到inet_init()中先inet_add_protocol()注册TCP协议再对TCP相关数据结构初始化。
struct proto tcp_prot = { .name = "TCP", .owner = THIS_MODULE, .close = tcp_close, .pre_connect = tcp_v4_pre_connect, .connect = tcp_v4_connect, .disconnect = tcp_disconnect, .accept = inet_csk_accept, .ioctl = tcp_ioctl, .init = tcp_v4_init_sock, .destroy = tcp_v4_destroy_sock, .shutdown = tcp_shutdown, .setsockopt = tcp_setsockopt, .getsockopt = tcp_getsockopt, .keepalive = tcp_set_keepalive, .recvmsg = tcp_recvmsg, .sendmsg = tcp_sendmsg, .sendpage = tcp_sendpage, .backlog_rcv = tcp_v4_do_rcv, .release_cb = tcp_release_cb, ... }; EXPORT_SYMBOL(tcp_prot);
/* thinking of making this const? Don't. * early_demux can change based on sysctl. */ static struct net_protocol tcp_protocol = { .early_demux = tcp_v4_early_demux, .early_demux_handler = tcp_v4_early_demux, .handler = tcp_v4_rcv, .err_handler = tcp_v4_err, .no_policy = 1, .netns_ok = 1, .icmp_strict_tag_validation = 1, };
创建TCP套接字描述符
一个套接字描述符在使用socket()后,就对应了一个特定的套接口结构,而在使用bind()之后,该套接口结构也就工作在特定的端口上了,等到connect()完成,对方的端点就也确定了。可是内核是如何为用户创建套接字的呢?可以参考如下:
https://www.cnblogs.com/codestack/p/10849706.html
关于DBG跟踪验证,思路如下:
按顺序对
arch_call_rest_init() --> rest_init() --> Kernel_init() --> Kernei_init_freeable() --> do_basic_setup() --> do_initcalls() --> do_initcall_level(level)
do_initcall_level(level)
打上断点,然后对创建套接字描述符的内核函数打断点,启动内核,运行replyhi和hello程序,观察是否出现断点。
由于时间紧张,内容下次再补充。