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和它以下的层次中用来存放网络接收到或需要发送的数据,因此它需要设计的有足够扩展性。

为了使用套接字缓冲区,内核创建了两个后备高速缓存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程序,观察是否出现断点。

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

原文地址:https://www.cnblogs.com/xhgblog/p/12103803.html

时间: 2025-01-01 07:16:27

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

TCP/IP网络编程读书笔记-简单的套接字编程(1)

在linux和windows下都是通过套接字编程进行网络编程.不同的系统上通信有部分差别,现在刚开始学习,给自己学习的时候一个总结. 一,socket函数的套接字步骤 第一,linux网络编程中接受连接请求(服务器端)套接字的四个步骤: 1)调用socket函数创建套接字 2)调用bind函数分配IP地址和端口号 3)调用listen函数转为可接收请求状态 4)调用accept函数受理连接请求 第二,linux网络编程中请求连接(客户端)套接字的两个步骤: 1)调用socket函数创建套接字 2

ZeroMQ接口函数之 :zmq_socket – 创建ZMQ套接字

ZeroMQ API 目录 :http://www.cnblogs.com/fengbohello/p/4230135.html 翻译:风波 mail : [email protected] ————————————————————————————————————— ZeroMQ 官方地址:http://api.zeromq.org/4-0:zmq-socket zmq_socket(3)            ØMQ Manual - ØMQ/4.0 Name zmq_socket – 创建Z

socket模块(套接字模块)

socket模块(套接字模块) 一.最简单版本(互传一次就结束) # 客户端 import socket client = socket.socket() client.connect(('127.0.0.1', 8080)) # 8080是端口号 ''' 来源百度百科 '127.0.0.1'是本机回还地址,不属于任何一个有类别地址类.它代表设备的本地虚拟接口,所以默认被看作是永远不会宕掉的接口.在Windows操作系统中也有相似的定义,所以通常在安装网卡前就可以ping通这个本地回环地址.一般

TCP协议的学习(三)TCP协议三次握手及攻击

(一)三次握手 ACK : TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1 SYN(SYNchronization) : 在连接建立时用来同步序号.当SYN=1而ACK=0时,表明这是一个连接请求报文.对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1. 因此, SYN置1就表示这是一个连接请求或连接接受报文. 发送序列号:Sequence Number 确认序列号:Acknowledgment Number CLOSED: 初始状态. LISTE

TCP协议的建立与释放及TCP的有限状态基

一.TCP的链接建立(三次握手) 如下图所示:假定最初两端的TCP进程都处于关闭状态.下图中,client主动打开链接,server被动打开链接 创建过程: ①.server的TCP服务器进程先创建传输控制块TCB(存储了每一个链接中的重要信息),准备接受client进程的连接请求.然后服务器进程就处于LISTEN(收听)状态,等待客户的连接请求.如有,作出响应. ②.client的TCP客户进程也是首先创建传输控制模块TCB,然后向server发出连接请求报文段,这时首部中的同步位SYN =1

【Socket编程】套接字Windows平台C语言实现

[编译环境]:Visual Studio 2013 #include<stdio.h> #include<stdlib.h> #include<winsock2.h> #pragma comment(lib, "ws2_32.lib") const int BACK_LOG = 5; int main(int argc, char * argv[]) { //初始化套接字 WSADATA wsaData; int ret = WSAStartup(M

socket IPC(本地套接字 domain)

1.简介 socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket.虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包.计算校验和.维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程.这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的.UNIX

Socket:★★★★,套接字,通信的端点

就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输. UDP传输: 1,只要是网络传输,必须有socket . 2,数据一定要封装到数据包中,数据包中包括目的地址.端口.数据等信息. 直接操作udp不可能,对于java语言应该将udp封装成对象,易于我们的使用,这个对象就是DatagramSocket. 封装了udp传输协议的socket对象. 因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对

socket()模块和套接字对象的内建方法

一.socket()模块函数 要使用socket.socket()函数来创建套接字,其语法如下: socket(socket_family,socket_type,protocol=0) 如上所述,scoket_family不是AF_UNIX就是AF_INET,scoket_type可以是SOCK_STREAM或SOCK_DGRAM,protocol一般不填,默认值为0. 创建一个TCP/IP套接字,你要这样调用socket.socket(): tcpsock = socket.socket(s