TCP协议的初始化及socket创建TCP套接字描述符

我们依然从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和它以下的层次中用来存放网络接收到或需要发送的数据,因此它需要设计的有足够扩展性。
TCP协议的初始化及socket创建TCP套接字描述符

为了使用套接字缓冲区,内核创建了两个后备高速缓存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中存在的数据包最大可扩展的空间范围。
以下为网络协议栈的内存管理函数
TCP协议的初始化及socket创建TCP套接字描述符

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程序,观察是否出现断点。

由于时间紧张,内容下次再补充。

上一篇:深入理解TCP协议及其源代码


下一篇:深入理解TCP协议:三次握手详解