精华内容
下载资源
问答
  • 用户态协议栈

    2021-01-05 17:53:36
    用户态协议栈 dpdk/netmap c10M的问题,千万并发 客户端发送数据到服务器,服务器接收数据的步骤: 1. 从网卡copy到内核协议栈 2. 从内核协议栈copy到应用程序 提升系统的性能,降低瓶颈,我们可以设计用户态协议栈...

    章节

    为什么要实现用户态协议栈?

    • 深入理解网络协议栈
    • 走进Linux内核开发的第一步
    • 加入开源组织,提升技术实力
    1. 用户态协议栈
    2. dpdk/netmap
    3. c10M的问题,千万并发

    客户端发送数据到服务器,服务器接收数据的步骤:
    1. 从网卡copy到内核协议栈
    2. 从内核协议栈copy到应用程序

    提升系统的性能,降低瓶颈,我们可以设计用户态协议栈,实现零拷贝(直接从网卡copy到应用程序,减少了一次拷贝操作)

    为了使网卡的数据,直接到达我们应用程序的方案:

    1. mmap
    2. pf_ring, libcap, raw_socket.

    既然原生的socket可以实现抓取到链路层的数据,是因为网卡抓取到链路层的数据不代表是抓取到网卡中的数据,使用原生的soket也会经过两次拷贝,从网卡copy到内核协议栈,从内核协议栈copy到应用程序(只是此时不会经过tcp协议栈)

    mmap把外设中的数据直接映射到内存中,有了mmap的映射,就有了实现直接抓取网卡数据,netmap就是这样实现的。

    netmap与dpdk对比

    netmap只是纯软件方式的开源框架,dpdk的使用场景,稳定性比netmap广

    C10K->C10M

    c10K的解决方案:
    (1) 在没有epoll出现时:

    • select/poll
    • 多线程/多进程
      select/poll存在的问题:
      • 数量(1. 所有io集合的数量; 2. 设置有少量的io会被触发,成为就绪状态):一个select监控1024个fd
      • 拷贝():
        多线程/多进程存在的问题:
      • 内存:
        (2) epoll的出现解决了的问题:
        • 数量:所有io集合的数量,把所有需要的网络io放在一起,少量io会被触发程序就绪状态
        • 拷贝: 一次一次添加到整个集合

    数据结构:
    把所有需要的网络io放在一起
    少量io会就绪

    C10M的解决方案:
    需要从以下几个方面来考虑

    1. 内存
    2. CPU
    3. 磁盘
    4. 网卡
    5. 应用程序
    6. 操作系统

    网卡, NIC(network interface card),netmap

    nic是对网卡的一层软件级封装;
    etho是nic针对物理网卡的实例化对象;

    netmap由两部分组成:

    1. 内核模块—>对nic子系统进行了扩展
    2. 应用程序接口

    用户态协议栈之TCPIP设计

    首先抛出疑问:

    1. 滑动窗口如何实现?
    • 滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。
    • 简单的说就是发送端发送缓冲区中允许发送,但尚未发送的数据所组成的窗口;接收端中接收缓冲区允许接受数据组成的窗口;
    1. sk_buff是什么?
    • sk_buff(socket buffer)结构是linux网络代码中重要的数据结构,它管理和控制接收或发送数据包的信息
    • 参照https://www.cnblogs.com/tzh36/p/5424564.html
    1. TCP_NODELAY设置,抓包后是N个包?
    • 启动TCP_NODELAY,就意味着禁用了Nagle算法,允许小包的发送。
    • 对于关闭TCP_NODELAY,则是应用了Nagle算法。数据只有在写缓存中累积到一定量之后,才会被发送出去,这样明显提高了网络利用率(实际传输数据payload与协议头的比例大大提高)。但是这又不可避免地增加了延时;与TCP delayed ack(延迟确认)这个特性结合,这个问题会更加显著,延时基本在40ms左右。当然这个问题只有在连续进行两次写操作的时候,才会暴露出来。
      连续进行多次对小数据包的写操作,然后进行读操作
    • Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS(max segment size)尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
      参照:https://blog.csdn.net/lclwjl/article/details/80154565
    1. Epoll 检测网络IO,水平触发与边沿触发如何判断?
    • 边缘触发:当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你
    • 水平触发:当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你
    1. 出现大量的close_wait如何解决?
    • close_wait产生的原因:接收到发送端发来的close,而迟迟未调用close向发送端发送fin包
    • 解决办法:审查代码,在接收到发送端发来的close后合理的调用close
    • 这也是发送端长时间处于fin_wait_2状态的根源
    1. DDOS?
    • 全称:Distributed Denial of Service(分布式拒绝服务攻击)
    • 可以对源IP地址进行伪造,使正常的应用无法连接服务器
    1. UDP广播?
    • udp单播: 点对点发送信息
    • udp广播: 一个点对所处局域网内的点发送消息,与单播不同点在于,地址是使用255.255.255.255,广播也需要指定端口号,本地广播信息是不会被路由器转发
    • udp多播:多播,也称为“组播”,将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据

    背景

    1. 服务器做到百万级已经不是什么难点
    2. 大连数据的介入,C10M的问题
    3. 微内核概念的兴起

    MAC地址是以太网产物
    IP地址是网络层产物
    端口是传输层产物

    TCP状态迁移图

    在这里插入图片描述

    数据状态

    发送端

    1. 已发送并收到确认数据
    2. 已发送但未收到确认数据
    3. 允许发送但尚未发送的数据
    4. 暂不被允许发送的数据

    接收端

    1. 已确认消息
    2. 允许接收
    3. 不允许接收
    4. 接收未发送确认消息

    用户态协议栈之协议栈的实现

    物理层传输的是:光电信号
    数据链路层传输的是:数字信号
    网卡的作用:接收时将光电信号转换为数字信号,发送是吧数字信号转换为光电信号(a->d, d->a)

    mac地址只在局域网中有效,出了子网,mac地址就会更改

    以太网的头部: 6字节目的地址+6字节源地址+2字节类型
    在这里插入图片描述

    struct ethhdr {
    	unsigned char h_dest[ETH_ALEN];
    	unsigned char h_source[ETH_ALEN];
    	unsigned short h_proto;
    };
    

    ip的头部:
    在这里插入图片描述

    struct iphdr {
    	unsigned char version;
    	unsigned char tos;
    	unsigned short tot_len;
    	unsigned short id;
    	unsigned short flag_off;
    	unsigned char ttl;
    	unsigned char protocol;
    	unsigned short check;
    	unsigned int saddr;
    	unsigned int daddr;
    };
    

    udp的头部:

    struct udphdr {
    	unsigned short source;
    	unsigned short dest;
    	unsigned short len;
    	unsigned short check;
    };
    

    udp数据包:

    	struct udppkt {
    	struct ethhdr eh;
    	struct iphdr ip;
    	struct udphdr udp;
    	unsigned char body[0];			//柔性数组,sizeof(body)=0
    };
    

    MTU 最大传输单元

    TTL(time to live) 生存时间,默认值是64,每经过一个路由器-1

    柔性数组?及用在哪里?及怎么用?sizeof(柔性数组)= 0
    柔性数组用在哪里?

    1. 当长度不确定时
    2. 长度可以通过外界计算出来,不会出现越界现象
    3. 已经提前分配好

    netmap.ko实现了哪些功能?

    什么时候选择select/poll/epoll?

    可以使用网络调试助手调试

    用户态协议栈之tcpip滑动窗口 拥塞慢启动

    为什么TCP能够被这么多人使用?

    1. 数据可靠,保证必达
    2. 传输效率不低
    3. 顺序
    

    TCP头部:
    TCP头部
    三次握手中的syn包中的seq就是TCP头部中的Sequence Number
    ack中的ack包中的seq就是acknowledgment Number;
    重要:
    urg:
    ack:
    syn:
    fin:
    tcp头部的定义:
    tcp头部定义

    tcp如何保证必达,保证顺序?
    1. 三次握手
    (1)如何做到一对一
    (2)状态机
    在这里插入图片描述
    在三次握手第一次握手的时候windows size无意义

    自己实现三次握手需要在服务器端准备两个队列
    在这里插入图片描述
    accept里面实现的步骤如下:
    在这里插入图片描述
    结点的生命周期(从listen到time_wait一直存在):
    在这里插入图片描述

    fd 最大只有65535,那服务器怎么做到百万并发,这是因为fd是通过五元组来唯一标识(源ip地址,源端口号,目的ip地址,目的端口号,协议)

    listen(fd, backlog)中的backlog是指

    1. 在unix,mac系统中是syn队列的长度
    2. linux上是syn+accept队列长度之和

    三次握手的状态图:
    在这里插入图片描述
    TCP保证高效:
    1. 滑动窗口机制
    在这里插入图片描述
    rtt(round trip time)一次往返的时间
    如何确定滑动窗口的大小?
    通过rtt来确认,rtt增加,滑动窗口减小,rtt减小,滑动窗口增加
    rtt的计算公式: rtt = 0.9 *old_rtt + 0.1 * new_rtt
    滑动窗口开始的默认值是1

    在这里插入图片描述

    保证顺序的机制:
    延迟确认

    既然有tcp的可靠传输,为什么会用udp做可靠传输?
    udp可靠传输的场景:
    在这里插入图片描述
    tcp的四次挥手:
    在这里插入图片描述

    双方同时调用关闭?
    相当于ack丢失,收到fin后会进入CLOSING

    面试中被问到的问题:
    	1. time_wait过多,time_wait如何设置时长
    time_wait的作用是避免最后一次ack丢失,避免服务器再次发送fin包时变成僵尸包
    time_wait默认时长是2msl
    原因:
    		time_wait设置的时间过长
    	2. close_wait,通过netstat查看,对应的close_wait过多,该怎么处理?是什么原因造成的
    

    是没有调用close造成的
    3. 处于fin_wait_2,服务器一直不调用close时,改如何终止?只能通过Kill进程

    2.4用户态协议栈之Epoll的实现

    协议栈回调epoll的参数:

    1. eventpoll->epoll底层的数据结构,标识属于哪个epoll
    2. fd
    3. status,可读,可写,异常

    操作

    • list的添加
      list用来存储就绪的IO,当内核IO准备就绪的时候,就会执行回调函数epoll_event_callback,将epitem添加到list中
    • list的删除
      当 epoll_wait 激活重新运行的时候,将 list 的 epitem 主意从list删除并且copy到 events 参数中
    • rbtree的添加
      当执行epoll_ctl EPOLL_CTL_ADD操作时,将epitem添加到rbtree中
    • rbtree的删除
      当执行epoll_ctl EPOLL_CTL_DEL操作时,将epitem从rbtree中删除

    ET与LT是怎么实现的?
    LT :只要recvbuf有数据,就一直回调,或者只要sendbuf有剩余空间,就一直回调
    ET:buf中从有到无或者从无到有才会触发回调

    nodelay?
    SO_LINGER
    自旋锁

    什么时候使用spinlock,什么时候使用mutex?

    作业题:
    用tcp传输2m的数据流程是怎样的?

    学习方法:
    站在一个设计者的角度,来思考TCP

    展开全文
  • B树的增删查改,速度低于红黑树

     

     

    B树的增删查改,速度低于红黑树

     

     

     

    展开全文
  • 产生原因:应用程序多次调用send后,只是将数据从用户空间拷贝到内核协议栈。而对于协议栈很容将两次的send包一起发送。 解决方案:a): tcp应用层协议添加长度域 b)每个包加上分割符 c)定长包(比较low,不推荐使用)...

    1 epoll编程问题

    对于数据传输send/recv这些接口的而言;如何判断send数据,对方有没有接收成功?
    采用send返回值无意义。,send发送数据只是将数据拷贝到协议栈,而数据的发送是协议栈自动处理的(何时发送也是由自己组装的).
    2 粘包分包问题
    产生原因:应用程序多次调用send后,只是将数据从用户空间拷贝到内核协议栈。而对于协议栈很容将两次的send包一起发送。
    解决方案:a): tcp应用层协议添加长度域
    b)每个包加上分割符
    c)定长包(比较low,不推荐使用)
    3 多个线程,并发的给fd发送数据等问题
    有可能会产生这种问题。解决方案:在调用send()前,把fd在epoll的epoll_out事件取消掉,发送完后再将fd添加回来到epoll。
    4 send返回-1,打开error值是eagain???
    产生原因:sendbuffer是满的
    解决方案:检测是否准备就绪(可采用epoll,poll,select等)
    5 recv的时候,也返回-1
    这是正常现象,recvbuffer无data。
    6 常见的误区
    epoll性能很高,是因为有内存映射mmap。
    epoll性能一定比select,poll更高。当fd比较少时候,epoll效率可能没有select和poll高。

    2 epoll如何实现

    主要分为四部分:
    1 如何管理所有的fd(数据结构)
    2 如何实现线程安全
    3 协议栈如何与epoll关联
    4 ET和LT如何实现

    1 数据结构
    对于fd分为两种状态:就绪(有data)和空闲(无data);注意fd:0,1,2是系统提供的,增加是从3开始增加的。
    对于一款服务器来说,一般情况下就绪的比例一般情况下都比较少,空闲的较多。
    空闲集合:采用红黑树存储
    就绪集合:采用队列存储
    空闲集合需要查找所以采用索引的数据结构:hash,红黑树,b树,跳表等;对于就绪集合为何采用队列呢不采用栈:主要是因为就绪集合数据量相对小,而且无查找,无修改,只需要将数据从用户空间搬到内核空间。epoll_wait一次性也拿不完所以采用先进先出的队列来实现。

    主要是由两个数据结构epitem和eventpoll。
    epitem是每一个io所对应的事件,epoll_ctl添加EPOLL_CTL_ADD时候就会创建一个;
    eventpoll是每个epoll所对应的,epoll_create创建的时候就会创建一个eventpoll;
    在这里插入图片描述
    在这里插入图片描述
    具体的组织结构如下:在这里插入图片描述
    2 线程安全
    有些情况下,可能会有多个线程同时操作epoll的时候。
    解决方案:加锁
    list:存储准备就绪IO
    红黑树:用来存储所有的IO。
    对于数据结构主要是两个方面insert,remove。
    当内核数据准备就绪时候,则会执行epoll_event_callback回调函数,将epitem添加到list中;对于删除,当epoll_wait激活重新运行的时候,将list中的epitem一一拷贝copy到events参数中。
    红黑树而言,当应用程序执行epoll_ctl添加EPOLL_CTL_ADD操作时候,将epitem添加到rbtree中;对于删除当epoll_ctl删除EPOLL_CTL_DEL操作的时候,将epitem添加到rbtree中。
    线程安全的问题转换成了,对红黑树和就绪队列加锁的问题
    红黑树加锁主要两种方式:锁整颗树;锁子树。(两者锁子树相对性能高一点,实现起来就很复杂。)
    队列加锁:可以采用自旋锁spinlock或者CAS也行。

    List:添加:

    pthread_spin_lock(&ep->lock);  // 获取spinlock
    epi->rdy = 1; // epitem的rdy设置为1,代表epitem已经在就绪队列中,后续在触发相同时间就只需更改event
    LIST_INSERT_HEAD(&ep->rdlist, epi, rdlink); // 添加到list中
    ep->rdnum++; // 将eventpoll的rdnum域加1
    pthread_spin_unlock(&ep->lock);
    

    删除:

    pthread_spin_lock(&ep->lock); // 获取spinlock
    
    int cnt = 0;
    int num = (ep->rdnum > maxevents ?maxevents:ep->rdnum); // 判断就绪数量与maxevents大小避免event溢出
    int i = 0;
    
    while(num != 0 &&!LIST_EMPTY(&ep->rdlist)){ // EPOLLET 循环遍历list,判断添加list不能为空
    	struct epitem *epi = LIST_FIRST(&ep->rdlist): // 获取首个结点
    	LIST_REMOVE(epi, rdlink);	//删除首个结点
    	epi->rdy = 0; //将epitem的rdy设置为0,标识epitem不在就绪队列中。
    
    	memcpy(&events[i++], &epi->event, sizeof(struct epoll_event)); // 拷贝epitem的event到用户空间的events
    
    	num--; //copy数量加1
    	cnt++;
    	ep->rdnum--// eventpoll中的rdnum减1
    }
    
    

    避免SMP体系下,多核竞争,此处采用自旋锁,不适合采用睡眠锁。

    红黑树添加:

    pthread_mutex_lock(&ep->mtx); //获取互斥锁
    
    struct epitem tmp;
    tmp.sockfd = sockid;
    struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp); // 查找sockid的epitem是否存在。存在则不能添加,不存在则可以添加。
    if (epi){
    	nty_trace_epoll("rbtree is exist\n");
    	pthread_mutex_unlock(&ep->mtx);
    	return -1;
    }
    
    // 分配epitem
    epi = (struct epitem*)calloc(1, sizeof(struct epitem));
    if (!epi){
    	pthread_mutex_unlock(&ep->mtx);
    	errno = -ENOMEM;
    	return -1;
    }
    
    epi->sockfd = sockid; // sockid赋值
    memcpy(&epi->event, event, sizeof(struct epoll_event)); //将设置的event添加到epitem的event域
    
    epi = RB_INSERT(_epoll_rb_socket, &ep->rbr, api); //将epitem添加到rbrtree中。
    assert(epi == NULL);
    
    pthread_mutex_unlock(&ep->mtx); //释放互斥锁。 
    

    红黑树删除:

    pthread_mutex_lock(&ep->mxt); // 获取互斥锁
    
    struct epitem tmp;
    tmp.sockid = sockid;
    struct epitem *epi = RB_REMOVE(_epoll_rb_socket, &ep->rbr, &tmp); // 删除sockid的结点,如果不存在,则rbtree返回-1
    if (!epi){
    	nty_trace_epoll("rbtree is no exist\n");
    	pthread_mutex_unlock(&ep->mtx);
    	return -1;
    }
    
    free(epi); // 释放epitem
    
    pthread_mutex_unlock(&ep->mtx);
    
    

    3 epoll回调(协议栈如何与epoll关联
    Epoll 的回调函数何时执行,此部分需要与Tcp的协议栈一起来阐述。Tcp协议栈的时序图如下图所示,epoll从协议栈回调的部分从下图的编号1,2,3,4。具体Tcp协议栈的实现,后续从另外的文章中表述出来。下面分别对四个步骤详细描述
    编号1:是tcp三次握手,对端反馈ack后,socket进入rcvd状态。需要将监听socket的event置为EPOLLIN,此时标识可以进入到accept读取socket数据。

    编号2:在established状态,收到数据以后,需要将socket的event置为EPOLLIN状态。

    编号3:在established状态,收到fin时,此时socket进入到close_wait。需要socket的event置为EPOLLIN。读取断开信息。

    编号4:检测socket的send状态,如果对端cwnd>0是可以,发送的数据。故需要将socket置为EPOLLOUT。 所以在此四处添加EPOLL的回调函数,即可使得epoll正常接收到io事件。

    如下图:
    在这里插入图片描述

    4 ET与LT
    LT(水平触发)与ET(边沿触发)是电子信号里面的概念。不清楚可以man epoll查看的。如下图所示:
    在这里插入图片描述
    比如:event = EPOLLIN | EPOLLLT,将event设置为EPOLLIN与水平触发。只要event为EPOLLIN时就能不断调用epoll回调函数。 比如: event = EPOLLIN | EPOLLET,event如果从EPOLLOUT变化为EPOLLIN的时候,就会触发。在此情形下,变化只发生一次,故只调用一次epoll回调函数。关于水平触发与边沿触发放在epoll回调函数执行的时候,如果为EPOLLET(边沿触发),与之前的event对比,如果发生改变则调用epoll回调函数,如果为EPOLLLT(水平触发),则查看event是否为EPOLLIN,即可调用epoll回调函数。

    展开全文
  •   不管是mtcp还是腾讯的F-Stack这种用户态协议栈实现,都代表了一种新势力,它们均是某种完全可用的协议栈实现,并且也都是开源的。但我想表达的不是为这些概念或者说产品做宣传,我想表达的是它们下层的东西,即...

    在进入这个话题之前先说说通用专业之间的区别。

      举个很好的例子,好比我们个人,绝大部分的人都是“通用”的,而只有极少部分的人是“专业”的。通用的人主要目标是活下去,即在最坏的条件下如何活下去,而专业的人目标在于在特定领域内将能量发挥到极致,这时考虑的是最好的条件,简单点说,通用的人什么都做,衣食住行必须一样不落下,而专业的人只需要做好一件事,其它的事他未必搞得定。

      本来就是闲聊,所以也就没有列什么提纲,这里再来说说TCP拥塞控制。已经跑了30多年的基于AIMD的Reno及其变体CUBIC被证明是优秀的,很多人搞不清楚为什么它是优秀的,难道BBR不是更好吗?不!Reno/CUBIC之优秀说的是,即使在最坏的网络环境中也能保证拥塞控制起作用,维持公平性,这背后的哲学是,资源匮乏时,不患寡而患不均,因为幂律无法维持能量总量的稳定性,而在资源充盈时,则放开让幂律起作用,所谓让一部分人先富起来。这在数学上,AIMD的收敛模型可以证明这个问题,这里就不再展开,展开的话就会涉及到控制论的问题了。

      那么Reno/CUBIC作为一个通用的拥塞控制算法当然是绝佳的了,至于说BBR,姑且认为它的1.0版本是B4网络上专业的拥塞控制算法吧,然而直到目前,也没能从数学上证明BBR在最坏的网络环境下能维持公平,甚至都不能证明它和其它算法配合得有多好,所以说虽然在性能上BBR可以说在绝大多数场景下表现不错,但由于其没有通用性的特征,故而CUBIC还是有一定市场。

      刚刚扯到了人和TCP,现在来看看操作系统。这里主要来看看宏内核的Linux。首先Linux就是一个通用的操作系统,它保证的是,即使CPU再垃圾,内存再少,它也能做一个名副其实的多任务现代操作系统跑多个进程。所谓的现代操作系统其含义包含了两种善意的欺骗,在空间维度上,操作系统的虚拟地址空间机制让每一个进程都认为自己独占了内存,在时间维度上,操作系统的时间片调度机制让每一个进程都认为自己独享了CPU时间,本质上,Linux把这两件事做好就OK了,至于说别的,像I/O啦,像TCP/IP啦,这些都是进程的事情。

      但是貌似Linux内核代理了这些本不该由它管的事情,比如磁盘I/O是内核处理的,文件系统也是内核的一个子系统,TCP/IP协议栈就别说,也是内核的一个子系统,这是为什么?

      答案在于外围硬件与CPU架构之间的不兼容,所以必须由操作系统内核来提供软件兼容层适配二者,此话怎讲?前面我提到,现代操作系统在时间和空间维度上提供了两类假象,作为一个可用的系统,除了CPU,内存以及操作系统之外,外设是必不可少的,不然如何接收输入和寄送输出,问题就在这里,输入和输出外设并无法提供上述两类假象,以磁盘为例,它永远就是那么个磁盘,进程1为它设置了一个status,然后系统切换进程2运行,进程2看到的磁盘状态就是进程1刚刚给它设置的,进程之间互相闯地方这肯定会乱套,并且违背了现代操作系统提供的隔离虚拟机模型,因此必须由操作系统出面协调干涉。这最终形成了内核文件系统内核协议栈这种架构。从初衷上看,操作系统着实只是代理实现这些,这显然并非它的本职,然而,直到目前,虽然IOMMU机制可以提供I/O的空间假象,但是对于时间维度,即便是有了一些硬件指令有实现I/O时间片的意思,但大体上依然是操作系统的调度子系统在代理。

      无论如何,现实就是这个样子,几十年来,这种机制工作的非常好,作为通用操作系统,最关键的是这种机制在以往条件艰苦恶劣的情况依然可以发挥作用。不过正如穷惯了的人就算发财也依然会存钱而不是投资一样,这种机制在当前高性能服务器设计领域,会不会已经成为阻碍可扩展性阻碍性能的掣肘之制呢?如果是,有没有什么办法可以改变这个现实。

      解决方案呼之即来,即不能把单独任务包装成独立的操作系统进程任其去调度,而应该让独立的进程主动去处理每一个任务

      我们来看一个案例,即Nginx之于Apache。

      我们知道Apache的prefork这种mpm,其它的也差不多。这是典型的甩锅给操作系统的行为。对于实现者,只需要去实现一个进程,然后处理单个HTTP Request即可,收到一个新连接就跑这么一个进程,同时处理两个连接就跑两个这样的进程,至于别的,管它呢,让调度器去管理吧,如果说一个连接的优先级大于另一个连接,Apache的做法显然是为高优先级连接的处理进程设定一个高的调度优先级,依然是交给调度器去处理…这种方案显然是不可扩展的,因为Apache的可扩展性受制于调度器的可扩展性。

      再看Nginx。和Apache不同,Nginx使用固定的进程处理所有这些事,自己处理连接调度,自己处理每一个Request。具体的描述,可以看一下我写的这篇:
    网络服务的两种处理模型(Nginx为什么比Apache好)https://blog.csdn.net/dog250/article/details/78994710

      再来看另一个案例,即TCP/IP协议栈。

      看看现状是什么。刚才说了,操作系统本不该实现TCP/IP协议栈的,只是不得已而代理实现,现在我可以再进一步说这事,不得已确实不得已,在硬件网卡层面确实需要操作系统来代理,但是难道不是把数据包扔到进程隔离的内存(BufferRing?嗯,是的!)中就OK了吗,接下来的事情就是数据包的协议栈处理了,这完全可以交给用户态进程啊。这个问法问Apache的时候,就有了Nginx,我们试着问下Apache:难道不是把Connection放到一个队列里就OK了吗?接下来的事情就是让一个进程去处理这个队列,干嘛还要搞那么多个进程…这意味着对于TCP/IP协议栈,有着某种用户态的解决方案。确实,不卖关子,netmap,DPDK这些都是。

      我们来看下操作系统内核实现的TCP/IP协议栈为什么不好。微观上说,Linux内核协议栈扩展性不好,多核处理数据包特别是小包时pps/核数的曲线上凸跌落,大致阻碍线性扩展的因素无非中断,锁,软中断唤醒用户进程/切换,以及这些导致的Cache污染。但这些说再多也都是各个点,即便你各个击破了也未必能有什么质的飞跃。如果我们把这种传统实现的内核态TCP/IP协议栈看作是古老的方法,那么用古老的方法去处理当代的高性能高扩展时尚的话,实际上已经成一场高潮迭起的杂技表演,不管是网卡厂商,还是Linux社区,均为这场杂技表演增加了很多看点,随便举几个例子:

    • Intel网卡RSS
    • Intel网卡Interrupt Mode
    • Intel网卡Interrupt Delay
    • Intel网卡DCA
    • 网卡各种Offload
    • Linux NAPI
    • Linux Busy Polling
    • Linux RPS/RFS
    • Linux中断线程化

    有了这些,说实话Linux内核协议栈已经工作地相当不错,但这些都属于技艺展示。肖像画家失业是因为照相机出现了,所以在新的东西面前,技艺是不堪一击的,如果没有照相机,也就不会出现立体主义,便不会有格尔尼卡这种作品。所以说,当这些优化技巧,优化组件越来越多的时候,应该从架构上颠覆旧方法的时刻就不远了。

      那么内核协议栈的问题到底在哪里?很简单,和Apache的问题一样,只是不在一个层次而已。那就是中断!归根结底内核协议栈对数据包的处理还是依赖了操作系统的调度器。

      一次中断来到,协议栈收包软中断便可能在任意上下文运行,我们宏观上讲,中断本身就是一次任务切换,即便是使能了单独的中断stack,即便是中断线程化,即便收包软中断全部在softirqd上下文执行,所有这一切都免不了一次任务切换,我们知道切换意味着什么,我也也知道在softirq的最上层,还会触发另一次调度,即wakeup用户态的处理进程,总而言之,涉及到数据包处理时,操作系统内核的调度子系统完全投入,最终的目标无非就是为了处理一个个单独的数据包!进程切换的代价是高昂的,现场保存,负载均衡,cache污染…由于中断的到来是不可控的,因此由这个中断所引发的一系列调度和切换就是不可控的,之所以敢这么折腾调度器,就是因为现代操作系统内核在设计上是闭环的,你怎么折腾它也是金刚不坏,它确实不会崩溃,它能工作,但也仅此而已。再次重申,通用操作系统大部分时间工作在恶劣环境下而不崩溃的just fine状态,而不是工作在精益求精的满血状态。

      中断的问题在于,实时优先于吞吐,想要大吞吐,必然要轮询。我前面的文章写过,对于个人实时比吞吐重要,只有商人才会在乎吞吐,但又能如何,我们服务的就是商人,毕竟服务器是要卖钱获取收益的。

      现在让我们看看正确的做法是什么。

      不管是mtcp还是腾讯的F-Stack这种用户态协议栈实现,都代表了一种新势力,它们均是某种完全可用的协议栈实现,并且也都是开源的。但我想表达的不是为这些概念或者说产品做宣传,我想表达的是它们下层的东西,即一种新的架构。这种架构在概念上非常简单。以下步骤即可:

    1. 网卡的BufferRing被mmap到用户态进程;
    2. 网卡只管把数据包放入一个BufferRing,操作写标;
    3. 用户态进程循环读取BufferRing,操作读标;
    4. 用户态进程处理读取到的每一个packet。

    实际的实现可能会有比较复杂,但大致如此。我们发现这是似曾相识的,没错,Nginx就是这样的架构,只是多了一个epoll的通知。看看Nginx如何在并发连接数上秒掉Apache,就知道用户态协议栈如何在pps上秒掉内核协议栈了。总之,两种情况下,罪魁祸首都是调度,而解决方案均是不能把单独任务包装成独立的操作系统进程任其去调度,而应该让独立的进程主动去处理每一个任务


    要去买小龙虾了,最后,简单说一下DPDK。

      嗯,很多人都是DPDK粉,所以不能得罪。但DPDK毕竟是一个产品而不是一个作品,相对而言,我更加喜欢netmap,我自己玩的mtcp就是使能了netmap而不是DPDK。

      DPDK的问题在于,为了把性能发挥到极致,采用了一种走火入魔的方法,这并不是一个大厂的风范。它会把处理进程和CPU做强绑定,然后Busy polling,这个CPU基本做不了别的什么事,这是一种粗狂型的优化方案,此外,大部分时候你之所以看到DPDK比netmap表现好,请不要忽略Intel自家的DCA,即直接缓存访问。DCA猛的很呐,你要是知道Cache miss的代价基本也就知道DCA的收益了。我们知道协议栈处理中最频繁的处理就是包头的处理,而我们知道,经过精心的设计,一直到TCP层,包头基本不占什么空间,因此DCA便可以通过旁路把包头提前送入Cache,这样在CPU处理时,就会Cache hit!厉害吧,软硬结合!


    无关的内容,想到了,就说下。

    • Reno:并行连接数量无关,冲突就腰斩降窗
    • CUBIC:RTT无关,冲突就缩放
    • BBR:缓存无关,…

    Reno/CUBIC无限可扩展,BBR则不行。

    展开全文
  • 本文来源:http://chinaunix.net/uid-28541347-id-5785780.htmlf-stack是腾讯基于dpdk开发的一套用户态协议栈,目前已经开源,相关介绍...
  • 【技术篇】手写用户态协议栈,udp/ip/eth数据包的封装,零拷贝的实现,柔性数组|用户态协议栈 | udp/ip/eth数据包的封装| 零拷贝的实现 C/C++Linux服务器开发精彩内容包括:C/C++,Linux,Nginx
  • 针对上面的两个流程,涉及到两次拷贝(网卡拷贝到协议栈,协议栈拷贝到应用程序),所以就产生了用户态协议栈,将协议栈网络解析作为应用程序的一部分。 2 用户态协议栈原理 1 定义组成图 用户态协议栈主要是用来减少...
  • 通过FD.io VSAP构建用户态协议栈 VSAP https://wiki.fd.io/view/VSAP VSAP(VPP Stack Acceleration Project) aims to establish an industry user space application ecosystem based on VPP host stack. V
  • 用户态TCP协议栈

    2020-05-11 14:35:31
    写一篇关于用户态协议栈相关的知识。
  • 用户态协议栈,可以让Linux内核更专注系统的控制调度;将复杂的协议处理放到用户态,使用更多的系统资源,提供给开发者更自由的环境,做更多更酷的事~ 2.  关于mTCP 关于 用户态协议栈 已经有很多开源的...
  • 用户态协议栈 三部曲 uio,数据帧,协议栈| igb 专注于服务器后台开发,包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等 直播...
  • TCP/IP协议栈到底是内核态的好还是用户态的好?

    万次阅读 多人点赞 2018-06-02 08:10:51
    “TCP/IP协议栈到底是内核态的好还是用户态的好?”这根本就是一个错误的问题,问题的根源在于,干嘛非要这么刻意地去区分什么内核态和用户态。 引子 为了不让本人成为干巴巴的说教,在文章开头,我以一个实例...
  • 关于用户态协议栈的思考
  • 基于go 实现链路层、网络层、传输层、应用层 网络协议栈 ,使用虚拟网卡实现 docs: @demo 相关demo以及协议测试在cmd目录下 cd ./cmd/* @application 应用层 @transport 传输层 端口机制 @network 网络层 icmp ...
  • 用户态TCP协议栈的调研

    万次阅读 2016-10-31 10:42:46
    韩国高校的一个科研项目,在DPDK的2016年的技术开发者大会上有讲,所以intel将这个也放到了官方上,所以一般搜索DPDK的用户态协议栈的时候就能够搜索到了这个; 特点: 有准确的测试数据,我们本地也测试了其性能...
  • [tldk][dpdk][dev] TLDK--基于dpdk的用户态协议栈传输层组件简单调研 如题,以下是一份简单的快速调研。   TLDK: Transport Layer Development Kit 一 什么是TLDK transport layer development kit 处理tcp/...
  • 本周,在SDNLAB直播活动【 一期一会】中,英特尔资深软件工程师——虞平与大家进行了在线交流,分享有关通过FD.io VSAP构建用户态协议栈的主题,介绍FD.io社区中VSAP的架构,应用模型和性能数据,并提出用户态协议...
  • 文章目录目录前文列表用户态网络协议栈简述内核协议栈存在的问题总结参考文档 前文列表 《Linux 内核网络协议栈》 《DPDK 网络加速在 NFV 中的应用》 用户态网络协议栈简述 用户态网络协议栈的底层支撑技术称为「...
  • 看的很多的论文中都会对用户态网络协议栈的缺陷进行提及,提及部分均为其论文中可以优化的部分,所以把类似的论文的看个大概才能总结出用户态网络协议栈经常采用的技术的大部分缺陷,接下来根据:缺陷 + 论文【简称...
  • 1.用户态协议栈 2.udp/ip/eth数据包的封装 3.零拷贝的实现 4.零长数组(柔性数组) 【Linux服务器开发系列】手写用户态协议栈,udpipeth数据包的封装,零拷贝的实现,柔性数组 更多Linux服务器开发高阶完整视频...
  • DPDK技术峰会PPT讲稿 DPDK开发者大会讲稿 文档讨论了腾讯的开源协议栈F-Stack,设计原则、架构、主要组件、性能及其在腾讯公司内的发展历史,F-Stack, a Full User Space Network Service on DPDK – Haigong Wang @...
  • TCP/IP协议栈到底是内核态的好还是用户态的好
  • 用户态协议栈之协议栈的实现
  • 用户态协议栈之Epoll的实现
  • 用户态协议栈之TCPIP的设计

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 23,345
精华内容 9,338
关键字:

用户态协议栈