TCPIP函数调用大致流程

来源:互联网 发布:女人被强奸 知乎 编辑:程序博客网 时间:2024/06/07 02:19
插口层 
系统调用 
send 
    sys_send 
        sys_sendto 
sendto 
    sys_sendto 
        sock_sendmsg 
sendmsg 
    sys_sendmsg 
        sock_sendmsg 
write 
    sys_write 
        vfs_write 
            file->f_op->write = do_sync_write 
                filp->f_op->aio_write = sock_aio_write 
                    do_sock_write 
                        __sock_sendmsg 
writev 
    sys_writev 
        vfs_writev 
            do_readv_writev 
                do_sync_readv_writev 
                    sock_aio_write 
                        do_sock_write 
                            __sock_sendmsg 
recv 
    sys_recv 
        sys_recvfrom 
recvfrom 
    sys_recvfrom 
        sock_recvmsg 
recvmsg 
    sys_recvmsg 
        sock_recvmsg 
read 
    sys_read 
        vfs_read 
            file->f_op->read= do_sync_read 
                filp->f_op->aio_read= sock_aio_read 
                    do_sock_read 
                        __sock_recvmsg 
readv 
    sys_readv 
        vfs_readv 
            do_readv_readv 
                do_sync_readv_readv 
                    sock_aio_read 
                        do_sock_read 
                            __sock_recvmsg 
socket 
listen 
connect 
bind 
select 
close 
shutdown 
ioctl 
getsockname 
getpeername 
setsockopt 
getsockopt 

内部实现函数 
sock_sendmsg 
    __sock_sendmsg 

__sock_sendmsg 
    sock->ops->sendmsg 
对于TCP就是tcp_sendmsg,否则就是inet_sendmsg。后者调用sk->sk_prot->sendmsg,会继续分用为udp_sendmsg或raw_sendmsg函数 

sock_recvmsg 
    __sock_recvmsg 

__sock_recvmsg 
    sock->ops->recvmsg = sock_common_recvmsg 
    sock_common_recvmsg对于不同协议,是tcp_recvmsg,udp_sendmsg或raw_sendmsg函数。 

运输层 

TCP 
系统调用sys_connect间接调用了tcp_v4_connect 
tcp_v4_connect 
    ip_route_connect(寻找路由) 
    __ip_route_output_key 
    ip_route_output_flow★ 
    tcp_connect(构造一个SYN并发送) 
        tcp_transmit_skb 
        inet_csk_reset_xmit_timer(启动一个超时定时器,等待SYN+ACK) 


TCP的写函数最终都调用了tcp_sendmsg 
tcp_sendmsg★ 
    __tcp_push_appending_frames 
        tcp_write_xmit 
            tcp_transmit_skb 
    tcp_push_one 
        tcp_transmit_skb    
    tcp_push 
        __tcp_push_pending_frames 
        
TCP发送数据共有三种途径__tcp_push_appending_frames,tcp_push_one,tcp_push,其中tcp_push调用了__tcp_push_pending_frames。到底调用哪个或哪些函数取决于是否有PUSH标志、NAGLE是否开启、和一些其他情况。 
__tcp_push_appending_frames是试图一次发送完缓存队列中所有的skb。tcp_push_one先计算拥塞窗口,然后只发送窗口大小的数据,如果窗口大小为0,则不发送任何数据。 


TCP实际的发送函数,tcp_transmit_skb 
/* This routine actually transmits TCP packets queued in by 
* tcp_do_sendmsg().  This is used by both the initial 
* transmission and possible later retransmissions. 
* All SKB's seen here are completely headerless.  It is our 
* job to build the TCP header, and pass the packet down to 
* IP so it can do the same plus pass the packet off to the 
* device. 

* We are working here with either a clone of the original 
* SKB, or a fresh unique copy made by the retransmit engine. 
*/ 
tcp_transmit_skb 
    build包头 
    icsk->icsk_af_ops->queue_xmit = ip_queue_xmit★ 


硬件->IP层->运输层收到数据,添加到对应的SOCKET缓冲区中,回复ACK 
由ip_rcv间接调用 
tcp_v4_rcv 
    __inet_lookup(根据一些参数,查找sock结构) 
        __inet_lookup_established(在已经建立的连接中找,通过inet_lhashfn在哈希表中查找) 
        __inet_lookup_listener(在监听中的Socket中找,通过inet_lhashfn在哈希表中查找) 
    tcp_v4_do_rcv 
        tcp_rcv_established(ESTABLISHED)★ 
        tcp_child_process 
            tcp_rcv_state_process 
        tcp_rcv_state_process(除ESTABLISHED和TIME_WAIT之外)★ 
    tcp_prequeue(见后面详细解释) 
        sk->sk_backlog_rcv = tcp_v4_do_rcv(又回到开头) 
    sk_add_backlog(见后面详细解释) 
    tcp_timewait_state_process(TIME_WAIT) 
    tcp_v4_timewait_ack(TIME_WAIT) 
        tcp_v4_send_ack(发送ACK) 

sock结构被初始化的时候,发送和接收数据的缓冲队列也被初始化完成,接收数据用到以下三个队列: 
sk->receive_queue 
sk->prequeue 
sk->sk_backlog 
sk->prequeue:如果sk没有被用户态程序锁定,则先进入prequeue 
sk->receive_queue:接收到数据包的sk_buff链表队列,如果数据包过多,造成receive_queue满,或者sock被用户程序锁定,将转入sk_backlog 
sk->sk_backlog:当sock_owned_by_user函数返回真时候,(sk)->sk_lock.owner被锁定,使用sk_add_backlog()函数(该函数实现非常简单,只是一个为链表添加节点的动作)将SKB加入这个后备队列。 


tcp_rcv_established 
TCP接受里面最主要的就是tcp_rcv_established和tcp_rcv_state_process了 
tcp_rcv_established★ 
    if(fast path) 
        检查包头各字段 
        tcp_ack(处理CK) 
        tcp_data_snd_check(发送ACK) 
        __skb_pull(腾出空间) 
        __skb_queue_tail(把数据追加到接受缓冲区) 
    else(slow path) 
        tcp_data_queue 
            对滑动窗口、序号做出处理 
            __skb_pull 
            __skb_queue_tail 
            tcp_event_data_recv(更新状态) 


tcp_rcv_state_process 
TCP协议的状态机,状态转移函数。ESTABLISHED和TIME_WAIT状态之外的其他状态都会调用此函数 
tcp_rcv_state_process★ 
    icsk->icsk_af_ops->conn_request(是tcp_v4_conn_request,LISTEN状态) 
        tcp_v4_send_synack(发送SYN+ACK) 
            ip_build_and_send_pkt 
                ip_local_out 
                    __ip_local_out 
                        nf_hook(dst_output) 
                    dst_output 
    tcp_rcv_synsent_state_process(SYN_SENT) 
    tcp_reset 
    tcp_ack(收到ACK) 
    tcp_set_state(SYN_RECV->ESTABLISHED或者FIN_WAIT1->FIN_WAIT2) 
    tcp_time_wait(CLOSING->TIME_WAIT) 
    tcp_update_metrics(LAST_ACK) 
    ...(都是和TCP协议状态转移相关的东西,这里目的是打通上下,以后慢慢分析) 


还有两个出镜率较高的函数tcp_v4_send_reset和tcp_v4_send_ack 
tcp_v4_send_reset(发送RST) 
    ip_send_reply 
        ip_route_output_key 
        ip_push_pending_frames 


tcp_v4_send_ack(发送ACK) 
    ip_send_reply 
        ip_route_output_key 
        ip_push_pending_frames 


用户子上而下的读函数都间接的调用了tcp_recvmsg 
tcp_recvmsg★ 
skb_copy_datagram_iovec 
tcp_recv_urg(接受一个字节的URG数据) 

UDP 
UDP的写函数都调用了udp_sendmsg 
udp_sendmsg★ 
    ip_route_output_flow 
    ip_append_data 
    udp_flush_pending_frames 
        ip_flush_pending_frames 
    udp_push_pending_frames 
        ip_push_pending_frames 

硬件->IP层->运输层收到数据,添加到对应的SOCKET缓冲区中 
由ip_rcv间接调用 
udp_rcv 
    __udp4_lib_rcv 
        if(是多播或广播) 
            __udp4_lib_mcast_deliver 
                udp_queue_rcv_skb(对每个需要接受的UDP SOCKET缓冲调用) 
        __udp4_lib_lookup 
        udp_queue_rcv_skb 


把数据块sk_buff放到一个sock结构的接受缓存的末尾中 
udp_queue_rcv_skb 
    sock_queue_rcv_skb 
        skb_queue_tail 

用户子上而下的读函数都间接的调用了udp_recvmsg 
udp_recvmsg★ 
__skb_recv_datagram 
skb_copy_datagram_iovec 
skb_copy_and_csum_datagram_iovec 

原始套接字 
RAW Socket的写函数都调用了raw_sendmsg 
raw_sendmsg★ 
    ip_route_output_flow 
    if(设置了IP_HDRINCL选项,即自己构造ip头部) 
        raw_send_hdrinc★ 
    else 
        ip_append_data 
        ip_flush_pending_frames或 
        ip_push_pending_frames 


自底向上的收包 
raw_rcv 
由ip_forward调用ip_call_ra_chain,然后再调用的raw_rcv 
raw_rcv 
sock_queue_rcv_skb 
skb_queue_tail 
sk->sk_data_ready = sock_def_readable 
waitqueue_active 
sk_wake_async 


用户子上而下的读函数都间接的调用了raw_recvmsg 
raw_recvmsg★ 
skb_recv_datagram 
__skb_recv_datagram 
wait_for_packet(如果没有数据,则调用此函数等待数据) 


ICMP 
在任何需要发送ICMP报文的时候都会调用此函数 
icmp_send 
    __ip_route_output_key 
        ip_route_output_slow 
    ip_route_output_key 
        ip_route_output_flow 
    icmp_push_reply    
        ip_append_data 
        ip_flush_pending_frames或 
        ip_push_pending_frames 


硬件->IP层->运输层收到ICMP数据,作出处理逻辑 
由ip_rcv间接调用 
icmp_rcv 
    完全就是icmp协议的处理逻辑,通过函数指针icmp_pointers[icmph->type].handler调用了一下函数中的某一个 
    icmp_discard 
    icmp_unreach 
    icmp_redirect 
    icmp_timestamp 
    icmp_address 
    icmp_address_reply 
    icmp_echo 


网络层 
IP发送 
网络层中主要的发送函数有以下三个:ip_push_pending_frames,ip_queue_xmit,raw_send_hdrinc 
ip_push_pending_frames★ 
将所有pending状态的IP分组组合成一个IP分组,并发送 
    ip_local_out 


ip_queue_xmit★ 
    ip_route_output_flow(找路由) 
    ip_local_out 


raw_send_hdrinc★ 
    NF_HOOK(dst_output) 


ip_local_out★ 
    __ip_local_out 
        nf_hook(dst_output) 
    dst_output 

路由选择 
ip_route_output_flow★ 
    __ip_route_output_key 
        ip_route_output_slow 

路由选择 
ip_route_output_slow★ 
    fib_lookup 
    ip_mkroute_output 
        __mkroute_output 
        rt_hash 
        rt_intern_hash 
            arp_bind_neighbour 
                __neigh_lookup_errno 
                    neigh_lookup 
                    neigh_create 


dst_output★ 
    dst->output = ip_output 
    NF_HOOK_COND(ip_finish_output) 
        dst_output 
        ip_fragment 
        ip_finish_output2 
            neigh_hh_output 
                hh->hh_output = dev_queue_xmit★ 
            dst->neighbour->output = neigh_resolve_output 
                neigh->ops->queue_xmit = dev_queue_xmit★ 

IP接受 
接收IPv4包,由netif_rx间接调用 
ip_rcv★ 
    NF_HOOK 
    ip_rcv_finish 
        ip_route_input 
        dst_input 
            dst->input(可能是ip_local_deliver或ip_forward) 
            if(是发给本地的包) 
                dst->input是ip_local_deliver 
                    NF_HOOK 
                    ip_local_deliver_finish 
                    ipprot->handler(可能是tcp_v4_rcv,udp_rcv,icmp_rcv,igmp_rcv) 
            else 
                dst->input是ip_forward 

更新路由 
ip_route_input★ 
    ip_route_input_mc(多播) 
        rt_hash 
        rt_intern_hash 
    ip_route_input_slow(其它) 
        ip_mkroute_input 
            __mkroute_input 
            rt_hash 
            rt_intern_hash 
每收到一个IP报文都会调用此函数更新路由表。ip_route_input函数的上半部分是在hash table寻找路由项,如果找到就返回。找不到才会调用后面的ip_route_input_mc或ip_route_input_slow来更新路由表。 


转发 
ip_forward★ 
ip_call_ra_chain 
raw_rcv★ 
    xfrm4_route_forward(处理路由) 
        xfrm_route_forward 
            __xfrm_route_forward 
                xfrm_lookup 
                    __xfrm_lookup 
                        xfrm_find_bundle 
                            afinfo->find_bundle = __xfrm4_find_bundle 
                        xfrm_bundle_create 
                            xfrm_dst_lookup 
                                afinfo->dst_lookup = xfrm4_dst_lookup 
                                    __ip_route_output_key 
                                        ip_route_output_slow★ 
    处理各个参数(在一定条件下发送ICMP) 
    ip_decrease_ttl(减少TTL) 
    NF_HOOK(ip_forward_finish) 
        dst_output 


链路层 
接收帧 
由硬件驱动在中断处理程序中直接调用netif_rx 
netif_rx★ 
    if(netpoll_rx函数与把数据拿走) 
        return 
    __skb_queue_tail(把所有收到的数据保存起来) 
    napi_schedule 
        __napi_schedule 
            __raise_softirq_irqoff(NET_RX_SOFTIRQ); 

在net_dev_init函数中初始化了软中断: 
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); 
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL); 
所以NET_RX_SOFTIRQ中断的处理函数是net_rx_action,NET_TX_SOFTIRQ中断的处理函数是net_tx_action。需要让上层接收数据时,只要触发相应的软中断,如__raise_softirq_irqoff(NET_RX_SOFTIRQ)。内核就会在适当时机执行do_softirq来处理pending的软中断。 


net_rx_action★ 
    n->poll = process_backlog 
        netif_receive_skb 
            pt_prev->func = ip_rcv★(在这里完成了交接) 
    __raise_softirq_irqoff(NET_RX_SOFTIRQ) 


发送帧 
dev_queue_xmit★ 
    rcu_read_lock_bh 
    if(设备有发送队列) 
        q->enqueue(将数据追加到发送队列,软中断处理函数net_tx_action会执行真正的发送工作) 
    else 
        dev_hard_start_xmit 
            dev->hard_start_xmit = el_start_xmit★ 
                调用outw汇编指令发送数据,够底层了 
    rcu_read_unlock_bh 


net_tx_action★ 
    __kfree_skb(释放已发送的,此时中断由dev_kfree_skb_irq函数发起) 
    qdisc_run 
        __qdisc_run 
qdisc_restart 
                dev_hard_start_xmit★ 
            netif_schedule 
    netif_schedule 


netif_schedule★ 
    __netif_schedule 
        raise_softirq_irqoff(NET_TX_SOFTIRQ)