zoukankan      html  css  js  c++  java
  • Linux网络子系统

    再Linux的世界里,万物皆文件,通过虚拟文件系统VFS,程序可以用标准的Linux系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。下面我们揭示Linux网络子系统的秘密

    sockfs

    在Linux上,和读写文件保持同一套接口是通过套接口伪文件系统sockfs来实现的。

    sockfs实现了VFS中的4种主要对象:超级块super block、索引节点inode、目录项对象dentry和文件对象file,当执行文件IO系统调用时,VFS就将请求转发给sockfs,而sockfs就调用特定的协议实现,层次结构如下图:

    image

    sockfs的装载

    首先初始化:

    static int __init sock_init(void)
    {
        //创建inode缓存
        init_inodecache();
        //创建socket的file_system
        register_filesystem(&sock_fs_type);
        //装载套接字文件系统
        sock_mnt = kern_mount(&sock_fs_type);
    }
    

    Socket创建

    系统调用socket、accept和socketpair(域套接字)是用户空间创建socket的几种方法,其核心调用链如下图:

    image

    1. 先构造inode
    2. 再构造对应的file
    3. 最后安装file到当前进程中(即关联映射到一个未用的文件描述符)

    这里很有意思,我们可以分析一下源码

    构造inode

    static struct socket *sock_alloc(void)
    {
        struct inode *inode;
        struct socket *sock;
    
        inode = new_inode(sock_mnt->mnt_sb);
            
        sock = SOCKET_I(inode);
                
        inode->i_mode = S_IFSOCK | S_IRWXUGO;
        inode->i_uid = current_fsuid();
        inode->i_gid = current_fsgid();
        return sock;
    }
    

    构造file

    有了inode对象后,接下来就要构造对应的file对象了,然后file对象和sock对象关联起来

    安装file

    void fd_install(unsigned int fd, struct file *file)
    {
        struct files_struct *files = current->files;
        struct fdtable *fdt;
        spin_lock(&files->file_lock);
        fdt = files_fdtable(files);
        BUG_ON(fdt->fd[fd] != NULL);
        rcu_assign_pointer(fdt->fd[fd], file);
        spin_unlock(&files->file_lock);
    }
    

    fd和file分别为上一过程返回的空闲文件描述符和文件对象,使RCU(Read-Copy Update)技术来设置file到当前进程的fd数组中。

    image

    socket操作

    读套接字有两种实现,read和recv实现不同。
    image
    read的实现,调用的是vfs_readv,后面的过程和sys_read相同

    image
    recv的实现没有经过vfs,而是先调用sock_lookup_light从fd得到socket,然后后面的流程和read一样

    Socket销毁

    系统调用close是用户空间销毁socket的唯一方法
    image
    filp_close先递减引用计数,若为0则调用__fput释放file。

    我们关闭一条TCP连接,还可以调用shutdown。该函数有三种关闭方式:单独关闭读(写)、同时关闭读写。shutdown处理过程调用序列见。shutdown不管引用计数,会直接关闭(不是析构)套接口。

    Linux 网络协议栈

    明白了上面的sockfs后,上层应用开发似乎已经完全足够了。下面我们以TCP和UDP为例子,继续深入一点点,去探究一下Linux内核的网络协议栈

    image

    Linux sk_buff struct 数据结构和队列

    sk_buffer

    • sk_buffer是Linux内核网络栈(L2到L4)处理网络包(packets)所使用的buffer。一个skb 表示 Linux 网络栈中的一个packet

    socket与inode绑定,对于不同的协议,Linux又抽象出了不同的struct sock

    struct sock 有三个 skb 队列(sk_buffer queue),分别是receive_queue , write_queue 和 error_queue(好像没什么用)。在 sock 结构被初始化的时候,这些缓冲队列也被初始化完成;在收据收发过程中,每个 queue 中保存要发送或者接受的每个 packet 对应的 Linux 网络栈 sk_buffer 数据结构的实例 skb
    image

    skb 的操作示例

    TCP操作:

    image

    协议栈发送

    应用层

    _sock_sendmsg 被调用,根据 socket 的协议类型,调用相应协议的发送函数。

    1. 对于 TCP ,调用 tcp_sendmsg 函数
    2. 对于 UDP来说,调用udp_sendmsg函数。

    传输层

    TCP 栈简要过程

    1. tcp_sendmsg 函数会首先检查已经建立的 TCP connection 的状态,然后获取该连接的 MSS,开始 segement 发送流程
    2. 构造 TCP 段的 playload:它在内核空间中创建该 packet 的 sk_buffer 数据结构的实例 skb,从 userspace buffer 中拷贝 packet 的数据到 skb 的 buffer
    3. 构造 TCP header
    4. 计算 TCP 校验和(checksum)和 顺序号 (sequence number)
      1. TCP 校验和是一个端到端的校验和,由发送端计算,然后由接收端验证。
    5. 发到 IP 层处理:调用 IP handler 句柄 ip_queue_xmit,将 skb 传入 IP 处理流程。

    UDP 栈简要过程

    1. UDP 将 message 封装成 UDP 数据报
    2. 调用 ip_append_data() 方法将 packet 送到 IP 层进行处理。

    IP 网络层

    网络层任务:

    1. 路由处理,即选择下一跳
    2. 添加 IP header
    3. 计算 IP header checksum,用于检测 IP 报文头部在传播过程中是否出错
    4. 可能的话,进行 IP 分片
    5. 处理完毕,获取下一跳的 MAC 地址,设置链路层报文头,然后转入链路层处理。

    数据链路层

    数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。

    物理层

    一旦网卡完成报文发送,将产生中断通知CPU,然后驱动层中的中断处理程序就可以删除保存的 skb(TCP得收到ACK)

    报文发送过程简要总结

    image

    协议栈接收

    物理层和数据链路层

    我们没必要了解那么多吧,简要描述:

    1. 网卡收到一个package,通过网卡中断创建一个sk_buff。
    2. 发出软中断(NET_RX_SOFTIRQ),通知内核处理。以后就可以愉快地把sk_buff交给网络层了。

    网络层

    1. 校验,合包
    2. 转发或者递交给上层

    传输层 TCP

    1. 它会做 TCP header 检查等处理
    2. 调用 _tcp_v4_lookup,查找该 package 的 open socket。如果找不到,该 package 会被丢弃。接下来检查 socket 和 connection 的状态。
    3. 如果socket 和 connection 一切正常,调用 tcp_prequeue 使 package 从内核进入 user space,放进 socket 的 receive queue(struct sk_buff队列)。然后 socket 会被唤醒,调用 system call,并最终调用 tcp_recvmsg 函数去从 socket recieve queue 中获取 segment。

    报文接收过程简单总结

    image

    关于TCP发送/接收缓冲区

    从struct sock中摘抄一点内容来解释发送/接收缓冲区

    struct sock {
    
      volatile unsigned long   wmem_alloc;/*当前写缓冲区大小,该值不可大于系统规定的最大值*/
    
      volatile unsigned long   rmem_alloc;/*当前读缓冲区大小,该值不可大于系统规定最大值*/
    
      struct sk_buff      * volatile send_head;
    
      struct sk_buff      * volatile send_tail;
    
    /* send_head, send_tail 用于 TCP协议重发队列。*/
    
      struct sk_buff      *partial;/*创建最大长度的待发送数据包。*/
    
    /*
    write_queue 指向待发送数据包,其与 send_head,send_tail
    队列的不同之处在于send_head,send_tail
    队列中数据包均已经发送出去,但尚未接收到应答。而 write_queue
    中数据包尚未发送。 receive-queue为读队列,其不同于 back_log 队列之处在于
    back_log 队列缓存从网络层传 上来的数据包,在用户进行读取操作时,不可操作
    back_log 队列,而是从 receive_queue
    队列中去数据包读取其中的数据,即数据包首先缓存在 back_log 队列中,然后从
    back_log 队列中移动到
    receive_queue队列中方可被应用程序读取。而并非所有back_log 队列中缓
    存的数据包都可以成功的被移动到
    receive_queue队列中,如果此刻读缓存区太小,则当 前从back_log
    队列中被取下的被处理的数据包将被直接丢弃,而不会被缓存到receive_queue
    队列中。如果从应答的角度看,在back_log队列中的数据包由于有可能被
    丢弃,故尚未应答,而将一个数据包从 back_log 移动到
    receive_queue时,表示该数据包
    已被正式接收,即会发送对该数据包的应答给远端表示本地已经成功接收该数据包。 */
    
      struct sk_buff_head       write_queue,
    
                          receive_queue;
    
        int rcvbuf; // 接受缓冲区的大小(按字节) 
        int sndbuf; // 发送缓冲区的大小(按字节) 
        atomic_t rmem_alloc; // 接受队列中存放的数据的字节数 
        atomic_t wmem_alloc; // 发送队列中存放的数据的字节数 
        int wmem_queued; // 所有已经发送的数据的总字节数 
        int forward_alloc; // 预分配剩余字节数 
    
      struct socket             *socket;/*对应的socket结构体*/
    
    };
    

    可以看出,sock结构里面并没有什么发送/接收缓冲区,只有由struct sk_buff构成的接收/发送队列。

    sock的收发都是要占用内存的,即发送缓冲区和接收缓冲区。 系统对这些内存的使用是有限制的。 通常,每个sock都会从配额里
    预先分配一些,这就是forward_alloc, 具体分配时:

    1. 比如收到一个skb,则要计算到rmem_alloc中,并从forward_alloc中扣除。接收处理完成后(如用户态读取),则释放skb,并利用tcp_rfree()把该skb的内存反还给forward_alloc。
    2. 发送一个skb,也要暂时放到发送缓冲区,这也要计算到wmem_queued中,并从forward_alloc中扣除。真正发送完成后,也释放
      skb,并反还forward_alloc。
  • 相关阅读:
    HDU4474 Yet Another Multiple Problem BFS搜索
    HDU4473 Exam 数学分析
    2013ACM多校联合(4)
    POJ1273 网络流...
    HDU4472 Count 递推
    POJ1149 PIGS 网络流
    UVA10881 Piotr's Ants 想法题
    javascript js string.Format()收集
    修改 设置 vs.net 网站 调试 设为 起始页
    【转】HTML5杂谈 概念与现行游戏 割绳子 宝石迷阵
  • 原文地址:https://www.cnblogs.com/biterror/p/6909860.html
Copyright © 2011-2022 走看看