image.png
- socket_create:创建一个 struct socket 结构,然后通过 sock_map_fd 和文件描述符对应起来。
- 参数:
- family:表示地址族。
- type:也即 Socket 的类型。
- protocol:是协议。
分别是 SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW。
inetsw 数组是在系统初始化的时候初始化的,type 作为下标,里面的内容是 struct inet_protosw,是协议,也即 inetsw 数组对于每个类型有一项,这一项里面是属于这个类型的协议。
因为上述结构,我们可以通过 family》type》protocol 获取到对应到inet_protosw 最后得到不同的 ops并赋值给socket到ops TCP的就是 inet_stream_ops。
- 参数:
- bind 函数:sockfd_lookup_light 会根据 fd 文件描述符,找到 struct socket 结构。然后将 sockaddr 从用户态拷贝到内核态,然后调用 struct socket 结构里面 ops 的 bind 函数,即调用 inet_bind。bind 里面会调用 sk_prot 的 get_port 函数,也即 inet_csk_get_port 来检查端口是否冲突,是否可以绑定。如果允许,则会设置 struct inet_sock 的本方的地址 inet_saddr 和本方的端口 inet_sport,对方的地址 inet_daddr 和对方的端口 inet_dport 都初始化为 0。
- listen 函数:我们还是通过 sockfd_lookup_light,根据 fd 文件描述符,找到 struct socket 结构。接着,我们调用 struct socket 结构里面 ops 的 listen 函数也即调用 inet_listen。 这里面建立了一个新的结构 inet_connection_sock,客户端和服务端都是有一个结构维护连接的状态,就是指这个结构。
在内核中,为每个 Socket 维护两个队列。一个是已经建立了连接的队列,这时候连接三次握手已经完毕,处于 established 状态;一个是还没有完全建立连接的队列,这个时候三次握手还没完成,处于 syn_rcvd 的状态。服务端调用 accept 函数,其实是在第一个队列中拿出一个已经完成的连接进行处理。如果还没有完成就阻塞等待。这里的 icsk_accept_queue 就是第一个队列。
- accept 函数:原来的 socket 是监听 socket,这里我们会找到原来的 struct socket,并基于它去创建一个新的 newsock。这才是连接 socket。除此之外,我们还会创建一个新的 struct file 和 fd,并关联到 socket。
inet_csk_accept 的实现,印证了上面我们讲的两个队列的逻辑。如果 icsk_accept_queue 为空,则调用 inet_csk_wait_for_connect 进行等待;等待的时候,调用 schedule_timeout,让出 CPU,并且将进程状态设置为 TASK_INTERRUPTIBLE。如果再次 CPU 醒来,我们会接着判断 icsk_accept_queue 是否为空,同时也会调用 signal_pending 看有没有信号可以处理。一旦 icsk_accept_queue 不为空,就从 inet_csk_wait_for_connect 中返回,在队列中取出一个 struct sock 对象赋值给 newsk。
image.png
- conect 函数:三次握手过程:
- 通过 tcp_v4_connect函数,查询路由表,选择从那个网卡出去。然后将状态置为TCP_SYN_SEND,然后初始化TCP 的 seq num,也即 write_seq,然后调用 tcp_connect 进行发送SYN 包。
- 服务端处理函数 tcp_rcv_state_process ,根据不同的状态回应请求,由于当前服务端在TCP_LISTEN状态,会调用 tcp_v4_send_synack,服务端将状态改为TCP_SYN_RECV并发送SYN_ACK保存。
- 客户端到 tcp_rcv_state_process ,由于客户端处于 TCP_SYN_SEND,会调用 tcp_send_ack,然后将客户端状态改为TCP_ESTABLISHED,然后发送ACK-ACK包。
- 服务端到tcp_rcv_state_process,由于服务端处于TCP_SYN_RECV状态,将状态转为TCP_ESTABLISHED,然后就可以将这个连接放到就绪队列然后接收请求。
网友评论