• Linux-2.6.25 TCPIP函数调用大致流程


    插口层
    系统调用
    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)


    from:http://blog.csdn.net/wan706364166/article/details/6693698

  • 相关阅读:
    Rhino 是一个完全使用Java语言编写的开源JavaScript实现。Rhino通常用于在Java程序中,为最终用户提供脚本化能力。它被作为J2SE 6上的默认Java脚本化引擎。
    VS的快捷键F12改成和ECLIPSE一样用ctrl+点击下载线
    到底要不要拆分函数
    “DllRegisterServer的调用失败”问题解决办法(转)
    select into的缺点
    win8 下脚本安装IIS
    快速打开IIS的方法
    windows下硬盘的逻辑结构
    sql server 2005/2008R2 报“红叉”错,即“不允许所请求的注册表访问权”的错误
    rundll32.exe的相关使用语句
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6332319.html
Copyright © 2020-2023  润新知