云计算网络技术分享 - Linux
type: channel
link: https://zhaozhanxu.com/tag/Linux/
items: eBPF简介
items: Linux内核报文收发-TCP
items: Linux内核报文收发-Netfilter
items: Linux内核报文收发-路由
items: Linux内核报文收发-L3
items: Linux内核报文收发-2层报文处理
items: Linux内核报文收发-网卡部分
items: Haproxy初识
items: TCMalloc简单测试

eBPF简介
type: item
date: 2018-04-01T15:30:00+08:00
description: 可以查看文章Part1和Part2eBPF(Extended Berkeley Packet Filter)的核心是驻留在kernel的高效虚拟机。最初的目的是高效网络过滤框架，前身是BPF，所以我们先了解下BPF。BPF框架上图是BPF的位置和框架，需要注意的是kernel和user使用了buffer来传输数据，避免频繁上下文切换。BPF虚拟机非常简洁，由累加器、索引寄存器、存储、隐式程序计数器组成。示例接下来我们看一下示例，过滤所有ip报文，可以使用tcpdump -d ip查看：(000) ldh [12] // 链路层第12字节的数据加载到寄存器，ethertype字段 (001) jeq #0x800 jt 2 jf 3 // 比较寄存器的ethertype字段是否为IP类型，true跳到2，false跳到3 (002) ret #65535 // 返回true (003) ret #0 // 返回0BPF只使用了4条虚拟机指令，就能提供非常有用的IP报文过滤。tcpdump -d tcp (000) ldh [12] // 链路层第12字节的数据(2字节)加载到寄存器，ethertype字段 (001) jeq #0x86dd jt 2 jf 7 // 判断是否为IPv6类型，true跳到2，false跳到7 (002) ldb [20] // 链路层第20字节的数据(1字节)加载到寄存器，IPv6的next header字段 (003) jeq #0x6 jt 10 jf 4 // 判断是否为TCP，true跳到10，false跳到4 (004) jeq #0x2c jt 5 jf 11 // 可能是IPv6分片标志，true跳到5，false跳到11 (005) ldb [54] // 我编不下去了... (006) jeq #0x6 jt 10 jf 11 // 判断是否为TCP，true跳到10，false跳到11 (007) jeq #0x800 jt 8 jf 11 // 判断是否为IP类型，true跳到8，false跳到11 (008) ldb [23] // 链路层第23字节的数据(1字节)加载到寄存器，next proto字段 (009) jeq #0x6 jt 10 jf 11 // 判断是否为TCP，true跳到10，false跳到11 (010) ret #65535 // 返回true (011) ret #0 // 返回0以上是freebsd的BPF，Linux中应该不叫这个，叫LSF，自己看吧。eBPFeBPF初识Linux kernel 3.18版本开始包含了eBPF，相对于BPF做了一些重要改进，首先是效率，这要归功于JIB编译eBPF代码；其次是应用范围，从网络报文扩展到一般事件处理；最后不再使用socket，使用map进行高效的数据存储。根据以上的改进，内核开发人员在不到两年半的事件，做出了包括网络监控、限速和系统监控。目前eBPF可以分解为三个过程：以字节码的形式创建eBPF的程序。编写C代码，将LLVM编译成驻留在ELF文件中的eBPF字节码。将程序加载到内核中，并创建必要的eBPF-maps。eBPF具有用作socket filter，kprobe处理器，流量控制调度，流量控制操作，tracepoint处理，eXpress Data Path(XDP)，性能监测，cgroup限制，轻量级tunnel的程序类型。将加载的程序attach到系统中。根据不同的程序类型attach到不同的内核系统中。程序运行的时候，启动状态并且开始过滤，分析或者捕获信息。2016年10月的NetDev 1.2大会上，Netronome的Jakub Kicinski和Nic Viljoen发表了标题为“eBPF / XDP硬件卸载到SmartNIC”。 Nic Viljoen在其中介绍了Netronome SmartNIC上每个FPC每秒达到300万个数据包，每个SmartNIC有72到120个FPC，可能最大支持eBPF吞吐量4.3 Tbps！（理论上）eBPF入口接下来我们以内核版本4.14版本为例进行查看。kernel/bpf/syscall.c bpf的系统调用include/uapi/linux/bpf.h bpf系统调用的头文件入口函数int bpf(int cmd, union bpf_attr *attr, unsigned int size);，从kernel/bpf/syscall.c中的宏定义展开。eBPF命令Linux系统的BPF系统调用有10个命令，其中man page中列出了6个：BPF_PROG_LOAD 验证并且加载eBPF程序，返回一个新的文件描述符。BPF_MA…

Linux内核报文收发-TCP
type: item
date: 2016-07-17T15:25:00+08:00
description: 版本说明Linux版本： 3.10.103网卡驱动： ixgbe协议头注意上图中的五个非常重要的东西：Sequence Number是包的序号，用来解决网络包乱序（reordering）问题。Acknowledgement Number就是ACK——用于确认收到，用来解决不丢包的问题。Window又叫Advertised-Window，也就是著名的滑动窗口（Sliding Window），用于解决流控的。TCP Flag ，也就是包的类型，主要是用于操控TCP的状态机的。TCP Options，主要是用于协商信息用的。flagsoptions数据是tlv形式，主要有kind-len-content，一些kind类型如下：KindMeaning0End of Option List1No-Operation2Maximum Segment Size3WSOPT - Window Scale4SACK Permitted5SACK6Echo (obsoleted by option 8)7Echo Reply (obsoleted by option 8)8TSOPT - Time Stamp Option9Partial Order Connection Permitted10Partial Order Service Profile11CC12CC.NEW13CC.ECHO14TCP Alternate Checksum Request15TCP Alternate Checksum Data16Skeeter17Bubba18Trailer Checksum Option19MD5 Signature Option20SCPS Capabilities21Selective Negative Acknowledgements22Record Boundaries23Corruption experienced24SNAP25Unassigned (released 12/18/00)26TCP Compression Filter状态转换图连接建立和销毁对于建链接的3次握手，主要是要初始化Sequence Number的初始值。通信的双方要互相通知对方自己的初始化的Sequence Number(缩写为ISN：Inital Sequence Number)——所以叫SYN，全称Synchronize Sequence Numbers。也就上图中的 x 和 y。这个号要作为以后的数据通信的序号，以保证应用层接收到的数据不会因为网络上的传输的问题而乱序（TCP会用这个序号来拼接数据）。对于4次挥手，其实你仔细看是2次，因为TCP是全双工的，所以，发送方和接收方都需要Fin和Ack。只不过，有一方是被动的，所以看上去就成了所谓的4次挥手。如果两边同时断连接，那就会就进入到CLOSING状态，然后到达TIME_WAIT状态。数据传输该图是我从Wireshark中截了个我在访问百度时的有数据传输的图，看下SeqNum是怎么变的。SeqNum的增加是和传输的字节数相关的。该图中，三次握手后，来了个Len:351的包，而第二个包的SeqNum就成了352。然后第一个ACK回的是352，表示第一个352收到了。重传机制TCP要保证所有的数据包都可以到达，所以，必需要有重传机制。重传机制：超时重传机制。快速重传机制。SACK(Selective Acknowledgment)。Duplicate SACK – 重复收到数据的问题。超时重传接收端给发送端的Ack确认只会确认最后一个连续的包。比如，发送端发了1,2,3,4,5一共五份数据，接收端收到了1，2，于是回ack 3，因为数据包3丢失了，所以收不到ACK，等到一定的时间超时之后就会重新发送。需要关注的问题就是我们设置超时时间多长？设长了，重发就慢，没有效率，性能差。设短了，重发的就快，会增加网络拥塞，导致更多的超时，更多的超时导致更多的重发。这时TCP也比较纠结到底设置多长时间(后续也会讲具体的超时依据，但也不是完美设置超时时间)，所以我们还需要找到一种方式来缓解超时带来的误差。快速重传TCP引入了一种叫Fast Retransmit 的算法，不以时间驱动，而以数据驱动重传。如果包没有连续到达，就ack最后那个可能被丢了的包，如果发送方连续收到3次相同的ack，就重传。Fast Retransmit的好处是不用等timeout了再重传。比如：发送方发出了1，2，3，4，5份数据，第一份先到送了，于是就ack回2，结果2因为某些原因没收到，3到达了，于是还是ack回2，后面的4和5都到了，但是还是ack回2，因为2还是没有收到，于是发送端收到了三个ack=2的确认，知道了2还没有到，于是就…

Linux内核报文收发-Netfilter
type: item
date: 2016-07-16T15:23:00+08:00
description: 版本说明Linux版本： 3.10.103网卡驱动： ixgbe位置框架图ip_rcv调用NF_HOOK开始进入netfilter框架对数据进行处理NF_HOOK(pf, hook, skb, indev, outdev, okfn)pf ：协议族名，Netfilter架构可以用于IP层之外，ipv4、arp、bridge、ipv6等。hook ：HOOK点的名字，对于IP层，就是取上面的五个值，其他的大同小异。skb ：不解释。indev ：数据包进来的设备，以struct net_device结构表示。outdev：数据包出去的设备，以struct net_device结构表示。okfn :是个函数指针，当所有的该HOOK点的所有登记函数调用完后，转而走此流程。Hook点PRE_ROUTING ：在进入网络层之初就会经过。LOCAL_IN ：数据进入本地时经过。FORWARD ：转发数据经过。LOCAL_OUT ：本地发送数据时经过。POST_ROUTING：数据出设备时调用比如我们做数据统计的话：如果需要统计流入和流出流量的话，只需要在1和5两个点统计skb即可。如果我们要分别统计本地流量和转发，需要在2、3、4三个点统计。Hook点的选取和我们的操作息息相关，主要看我们改变网络特性的情况，比如我们要改变连接属性的话，需要考虑conntrack的影响，以IP做策略匹配的时候，需要考虑NAT。优先级协议族的引入是为了支持更多的协议优先级的引入是因为同一个hook点可以挂很多钩子，不同的Hook函数用不同的优先级表示调用的先后顺序，数值越小的优先被调用，最大值是0x7fffffff，最小值是-0x80000000实现netfilter定义struct list_head nf_hooks[PROTO][HOOKS]，建立一个二维数组的链表，分别是协议族和hook点，然后以优先级为基准进行链表的创建和排序。我们在挂Hook函数的时候会指定协议、hook点、优先级和执行函数，直接将这些信息加入相应的链表中，协议栈的数据包进入的时候会根据相应的协议和当前执行到的hook点去遍历链表，执行相应的函数。比如，我们设置协议为ipv4， hook点为pre_routing，优先级-100，函数为dnatfor{ a = get_list(nf_hooks[ipv4][prerouting]); a.func(skb); }当遍历到优先级为-100的时候，就会执行dnat函数。挂钩子Netfilter处于kernel层，推荐直接编写module，比直接修改内核代码方便调试和编译static struct nf_hook_ops my_nfhook[] = { { .hook=my_hook_func, //Hook函数 .owner=THIS_MODULE, .pf=PF_INET, //协议族 .hooknum=NF_INET_LOCAL_IN, //Hook点 .priority=NF_IP_PRI_FIRST, //优先级 },//可以写多个 };挂Hook的接口nf_register_hooks(my_nfhook, ARRAY_SIZE(my_nfhook))取消Hook的接口nf_unregister_hooks(my_nfhook, ARRAY_SIZE(my_nfhook))unsigned int my_hook_func(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff*)) { //此处skb任你处置 return NF_ACCEPT; }需要注意的是比较老的内核版本的函数参数skb是二维指针**skb, 新的是*skb，老内核我遇到过的是2.6.18之前的，2.6.32之后是新的返回值Netfilter将skb交给Hook函数进行处理，处理的结果如何，需要通过返回值的形式告知协议栈：NF_ACCEPT继续正常传输数据报。这个返回值告诉 Netfilter：到目前为止，该数据包还是被接受的并且该数据包应当被递交到网络协议栈的下一个阶段。NF_DROP丢弃该数据报，不再传输。NF_STOLEN模块接管该数据报，告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理，并且Netfilter应当放弃对该数据包做任何的处理。但是，这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效，只是回调函数从Netfilter 获取了该数据包的所有权。NF_QUEUE对该数据报…

Linux内核报文收发-路由
type: item
date: 2016-07-15T15:19:00+08:00
description: 版本说明Linux版本： 3.10.103网卡驱动： ixgbe路由路由项通过两种途径加入内核：通过用户态路由协议进程或者用户静态配置配置加入。主机自动发现的路由。自动发现的路由实际上是“发现了一个路由项和一个转发表”，其含义在主机某一个网卡启动的时候生效，比如eth0启动，那么系统生成下列路由表项/转发项：往eth0同一IP网段的包通过eth0发出。路由实现内核路由存在两种查找算法：HASH算法(Linux之前默认)。LC-trie算法(Linux现在默认，更适用在超大路由表的情况，它在这种情况提高查找效率的同时，大大地增加了算法本身的复杂性和内存的消耗)。HASH路由算法路由的性能很大程度上由路由表的设计决定，好的设计能减少系统资源的消耗，这两方面尤其体现在路由表的查找上。Linux使用了多个路由表，一个用于上传给本地上层协议，另一个则用于转发， 其他用于策略路由。不同策略的路由存放在不同的表中，有效地被免了查找庞大的路由表，在一定程度上提高了查找了效率。HASH路由表由多个结构组合而成，是一个分层的结构组合：第一层是指表头fib_table->tb_data指向fn_hash，里面包含了33个fn_zone。第二层根据子网掩码(netmask)的长度(0~32)分成33个部分(struct fn_zone)。第三层根据子网的不同(如10.1.1.0/24和10.1.2.0/24)划分 (struct fib_node)。第四层是一个路由表项(struct fib_info) ，因为不同的子网可能是对应着同一个下一跳，所以fib_node和fib_info是多对一的关系。LC-Trie树路由算法trie树本质上是一棵检索树。CheckNode{ int pos; int bits; Node children[1<<bits]; } union Node{ Leaf entry; CheckNode node; } //Pos表示位置 //Bits表示位数 //Children表示子节点RT_TABLE_LOCAL优先级 0选择器 = 匹配任何数据报动作＝察看本地路由表(routing table local)，ID为255。local表是保留路由表，包含了到本地和广播地址已及NAT地址的路由。规则0是特殊的规则，由系统自动维护，不可被删除或修改。RT_TABLE_MAIN优先级 32766选择器 = 匹配所有数据报动作 = 察看主路由表(routing table main)， ID为254。main路由表是默认的标准路由表，其包含所有非策略路由，main表是存放旧的路由命令(route命令)创建的路由。而且任何由ip route命令创建的没有明确指定路由表的路由都被加入到该路由表中。该规则不能被删除和被其他规则覆盖。RT_TABLE_DEFAULT优先级 32767选择器 = 匹配所有数据报动作 = 察看默认路由表(routing table default)，ID为253。default路由表是空的，为最后处理所预留，若前面的默认规则没有选择该数据报时保留用作最后的处理。该规则可以被删除。USER_DEFINE 策略路由优先级 1-32765选择器 = 自定义动作 = 自动以静态路由(LOCAL、MAIN、DEFAULT)匹配字段： 目的地址操作： 转发策略路由(USER_DEFINE)匹配字段： 各个字段（常用五元组：源IP、目标IP、协议、源端口、目标端口）4操作： 数据包任意操作可以实现ACL的功能，不过这些功能目前的linux中用了更高效的nf-HiPAC，另外也可以了解一下ipset和nftables

Linux内核报文收发-L3
type: item
date: 2016-07-14T14:57:00+08:00
description: 版本说明Linux版本： 3.10.103网卡驱动： ixgbe网络协议注册inet_init主要是注册各种协议注册TCP协议proto_register(&tcp_prot, 1)继续注册UDP、RAW、PINGarp_init, ip_init, tcp_init, udp_init, ping_init, icmp_initdev_add_pack(&ip_packet_type)主要是注册ip报文处理函数ip_rcv到pttype_base。arp_init-->dev_add_pack(&arp_packet_type)主要是注册arp报文处理函数arp_rcv到pttype_base。报文处理网卡调用__netif_receivve_skb_core后，会调用deliver_skb(skb, pt_prev, orig_dev)处理对应的３层协议函数。ip协议ip_rcv主要获取报文头，报文健康检查，最后进入NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,...,ip_rcv_finish)。ip_rcv_finish进行路由查找，ip_route_input_noref-->ip_route_input_slow进行慢速路由判定。ip_route_input_slow判定报文是本地的话，就给input装载ip_local_deliver函数，如果不是本地继续调用ip_mkroute_input-->__mkroute_input查找路由，并且给input装载ip_forward函数，output装载ip_output。ip_local_deliver函数直接进入NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,..., ip_local_deliver_finish)。ip_local_deliver_finish调用ipprot->handler进入4层的协议处理函数。ip_forward进入NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,..., ip_forward_finish)。ip_forward_finish调用output装载的ip_output。ip_output进入NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING,...,ip_finish_output)ip_finish_output进行分片的判断和操作，此处涉及到GSO的判定，最后调用ip_finish_output2。ip_finish_output2调用__ipv4_neigh_lookup_noref进行邻居子系统的表查找，ipv4主要是arp表，查找到arp表，则调用dst_neigh_output-->neigh_hh_output-->dev_queue_xmit进行报文发送。如果没有查找到，则调用__neigh_create-->arp_constructor进行发送等函数的装载，最后也调用dst_neigh_output，然后也调用装载的发送函数，将报文修改为request，调用dev_queue_xmit进行发送。arp协议arp_rcv健康检查后，进入NF_HOOK(NFPROTO_ARP, NF_ARP_IN, ..., arp_process)arp_process完成了所有的处理操作，包括是reply报文则更新arp表；如果是request本地的话，则更新或者创建arp表，并且调用arp_send回复arp报文；如果不是本地不支持arp proxy的话，丢弃报文；支持的话转发报文。arp_send调用arp_create创建报文，并且调用arp_xmit发送报文。arp_xmit进入NF_HOOK(NFPROTO_ARP, NF_ARP_OUT, ..., dev_queue_xmit_sk)，最后调用dev_queue_xmit发送报文。转发流程邻居子系统Generic neighbouring interface(VFT)，为上层协议提供了一个统一的输出接口neigh->output()。Generic neighbouring interface，为下层提供的也是一个统一的接口dev_queue_xmit()。ARP是为IPV4设计的地址解析协议，而ND（neighbour detect）则是为IPv6设计的。这些地址解析协议可以说是“嵌入”在邻居子系统里面，但是又可以自由灵活的拆卸，非常方便。为了加速数据包的发送速度，会将路由表和邻居缓存进行绑定，这个绑定其实就是把邻居缓存中的每个项的结构体嵌入路由表中的一个路由项中，这样报文在查找到路由以后，其实也相当于已经在邻居子系统中查到了…

Linux内核报文收发-2层报文处理
type: item
date: 2016-07-13T15:10:00+08:00
description: 版本说明Linux版本： 3.10.103网卡驱动： ixgbe网络协议注册br_add_if主要是注册桥处理函数br_handle_frame给skb->edv->rx_handler。netdev_create-->netdev_rx_handler_register主要是注册接收函数netdev_frame_hook，此处是给openvswitch的datapath用的。报文接收网卡调用__netif_receivve_skb_core后，会调用skb->dev->rx_handler处理２层协议函数，此处只关注桥的，暂不关注ovs的。br_handle_frame首先判定mac是否本地，是本地的报文则进入到NFPROTO_BRIDGE的NF_HOOK，即NF_BR_LOCAL_IN，进入处理链，最终完成后调用br_handle_local_finish完成操作，等着进入再上一层的协议。如果不是本地，则进入转发流程，如果直接转发状态，则调用br_should_route_hook，即ebt_route；如果是学习状态是NF_HOOK，即NF_BR_PRE_ROUTING，进入处理链，最终完成调用br_handle_frame_finish。然后调用br_fdb_update学习源MAC地址，更新MAC地址表，__br_fdb_get或者目的MAC对应的端口。如果查找到了端口，调用br_forward转发到对应的端口。找不到端口，调用br_flood_forward进行广播。br_forward继续调用NF_HOOK的NF_BR_FORWARD，完成后调用br_forward_finish，继续调用NF_HOOK的NF_BR_POST_ROUTING，最终调用br_dev_queue_push_xmit。br_dev_queue_push_xmit-->dev_queue_xmit进行L2的发送。报文发送dev_queue_xmitnetdev_pick_tx主要是选择发送设备设置的映射关系，查找当前CPU对应的队列，如果多个则计算hash，确定队列。支持入队的话，调用__dev_xmit_skb进行下一步的操作。不支持队列的话调用dev_hard_start_xmit进行数据的发送。__dev_xmit_skb如果队列的状态是非活跃的，则丢包。如果是支持bypass，则调用sch_direct_xmit直接发送数据包，没有发送完成，继续调用__qdisc_run。其他情况入队并且调用__qdisc_run。__qdisc_runwhile循环调用qdisc_restart，进行的操作为skb出队，并且调用sch_direct_xmit直接发送数据包，没有发送完成，返回值非0，继续参与循环。一直循环到数据包发送完毕，或者权重耗完或者需要重新调度，此时调用__netif_schedule-->__netif_reschedule，将当前的队列记录在CPU的softnet_data，触发软中断NET_TX_SOFTIRQ，调用net_tx_action。sch_direct_xmit直接调用dev_hard_start_xmit-->ops->ndo_start_xmit-->ixgbe_xmit_frame发送数据包。如果发送成功，则返回值为队列剩下的数据包量。如果被自己CPU锁了则报错，被其他CPU锁了则重新入队，等以后再发送。如果busy的话重新入队，等以后发送。所以其实就是发送完成则返回成功0，否则发送非0。

Linux内核报文收发-网卡部分
type: item
date: 2016-07-12T15:07:00+08:00
description: 版本说明Linux版本： 3.10.103网卡驱动： ixgbe报文收发简单流程网卡驱动默认采用的是NAPI的报文处理方式。即中断+轮询的方式，网卡收到一个报文之后会产生接收中断，并且屏蔽中断，直到收够了netdev_max_backlog个报文(默认300)或者收完网卡上的所有报文之后，重新打开中断。网卡数据处理网卡初始化内核启动时会调用do_initcalls，从而调用注册的初始化接口net_dev_init，net_dev_init注册软中断的回调函数,分别为接收和发送的：NET_RX_SOFTIRQ = net_rx_action，NET_TX_SOFTIRQ = net_tx_action。网卡驱动加载时调用ixgbe_init_module注册一个PCI驱动。接着调用probe对应的ixgbe_probe做一些准备工作如下：创建ixgbe_adapter数据结构，这是网卡的一个实例，其中包含了网卡的所有数据和接口(netdev)。netdev注册了网卡的所有操作：ixgbe_netdev_ops和网卡的features。ixgbe_init_interrupt_scheme主要是设置网卡的NAPI对应的poll接口，这里主要是ixgbe_poll。网卡激活时会调用netdev上的open函数ixgbe_open，完成工作如下：设置接收和发送队列，通过DMA讲PCI网卡地址和队列建立映射。给网卡注册硬中断：ixgbe_open-->ixgbe_request_irq-->ixgbe_request_msix_irqs注册中断回调ixgbe_msix_clean_rings。(MSIX中断时)ixgbe_close函数所做的事情：关闭中断并且释放中断向量号。释放申请的相应空间。网卡收发数据当网卡接收到数据帧时，DMA将数据搬运到对应的rx_ring，然后产生一个硬中断，调用接口ixgbe_msix_clean_rings。硬中断回调函数ixgbe_msix_clean_rings会调用当前CPU对应的NET_RX_SOFTIRQ，即net_rx_action。其中的主要实现NAPI操作如下：关闭硬中断。调用该CPU的poll_list中的所有poll函数，即ixgbe_poll。开启硬中断。轮询函数ixgbe_poll主要是处理接收发送任务：循环处理每个ring中的数据，ixgbe_clean_tx_irq，此处发送tx_work_limit个报文(128)或者没有报文后完成，并且调用netif_wake_subqueue触发发送软中断NET_TX_SOFTIRQ，即net_tx_action。循环处理每个ring中的数据，ixgbe_clean_rx_irq，此处接收weight个报文(64)，总共收取netdev_budget(300)个，并且调用ixgbe_rx_skb进行下一步的处理。ixgbe_rx_skb最终会处理过GRO之后调用到netif_receive_skb，此处忽略rps。netif_receive_skb最终调用__netif_receivve_skb_core进入协议栈处理各种协议。首先根据下面介绍的rx_handler类型调用ovs或者桥处理函数进入L2的处理。然后根据pttype_base处理相关的L3协议处理。备注ixgbe拥有64个rx_ring，每个ring默认有512个buffer，最小64，最大4096。由以上可知网卡默认可以缓存32768个报文，当网卡接收报文的速度过快，接收软中断无法处理的时候，系统进入扼流(throttle)状态，所有后续的报文都被丢弃，直到rx_ring有空间了。通过DMA地址映射的方式，网卡接收数据相当于零拷贝。TUN虚拟网卡网卡驱动加载时调用tun_init注册一个MISC驱动，生成/dev/net/tun的设备文件。接着调用open对应的tun_chr_open做主要创建socket。随后调用ioctl对应的tun_chr_ioctl，完成一些需要的配置和信息获取，包括校验、连接状态、offload、mac等。通过调用write发送报文，主要是调用tun_chr_aio_write生成skb格式报文，最终通过调用netif_rx进入协议栈，通过三层转发到对应的端口发送。通过调用read接收报文，主要是调用tun_chr_aio_read，从内核对应的sock队列上拿到报文，然后将数据上传到用户层。注:因为网卡接收报文后最终经过四层的时候，会将报文根据协议几元组找到对应的sock结构，放入队列。上层调用read或者recv的时候都是从socket对应的sock结构队列上取数据。VETH虚拟网卡网卡驱动加载时调用veth_init注册一个netlink驱…

Haproxy初识
type: item
date: 2016-06-15T15:06:00+08:00
description: 了解版本为主要版本为1.5.12。主要从四个方面了解：主循环逻辑、TCP连接处理流程、HTTP连接处理流程。主循环处理流程主循环run_poll_loopHAproxy的主循环在haproxy.c中的run_poll_loop()函数，代码如下：/* Runs the polling loop */ void run_poll_loop() { int next; tv_update_date(0,1); while (1) { /* check if we caught some signals and process them */ signal_process_queue(); /* Check if we can expire some tasks */ wake_expired_tasks(&next); /* Process a few tasks */ process_runnable_tasks(&next); /* stop when there's nothing left to do */ if (jobs == 0) break; /* The poller will ensure it returns around <next> */ cur_poller.poll(&cur_poller, next); fd_process_cached_events(); } }主循环的结构比较清晰，就是循环的调用几个函数，并在适当的时候结束循环并退出：处理信号队列。超时任务。处理可运行的任务。检测是否可以结束循环。执行 poll 处理 fd 的 IO 事件。处理可能仍有 IO 事件的 fd.signal_process_queue - 处理信号队对列haproxy实现了自己的信号处理机制。接受到信号之后，将该信号放到信号队列中。在程序运行到signal_process_queue()时处理所有位于信号队列中的信号。wake_expired_tasks - 唤醒超时任务haproxy的顶层处理逻辑是task，task上存储着要处理的任务的全部信息。task的管理是采用队列方式，同时分为wait_queue和run_queue。顾名思义，wait_queue是需要等待一定时间的task的集合，而run_queue则代表需要立即执行的task的集合。该函数就是检查wait_queue中那些超时的任务，并将其放到run_queue中。haproxy在执行的过程中，会因为一些情况导致需要将当前的任务通过调用task_queue等接口放到wait_queue中。process_runnable_tasks - 处理可运行的任务处理位于run_queue中的任务。前面提到，wake_expired_tasks可能将一些超时的任务放到run_queue中。此外，haproxy执行的过程中，还有可能通过调用task_wakeup直接讲某个task放到run_queue中，这代表程序希望该任务下次尽可能快的被执行。对于TCP或者HTTP业务流量的处理，该函数最终通过调用process_session来完成，包括解析已经接收到的数据， 并执行一系列load balance的特性，但不负责从socket收发数据。jobs == 0 - 无任务可执行，结束循环haproxy中用jobs记录当前要处理的任务总数，一个listener也会被计算在内。因此， 如果jobs为0的话，通常意味着haproxy要退出了，因为连listener都要释放了。 jobs的数值通常在process_session时更新。因此，是否可以退出循环，就放在了所有任务的process_session执行之后。cur_poller.poll() - 执行poll处理fd的IO事件haproxy启动阶段，会检测当前系统可以启用那种异步处理的机制，比如select、poll、epoll、kqueue等，并注册对应poller的poll方法。epoll的相关函数接口在ev_epoll.c中。这里就是执行已经注册的poller的poll方法，主要功能就是获取所有活动的fd，并调用对应的handler，完成接受新建连接、数据收发等功能。处理可能仍有IO事件的fdpoller的poll方法执行时，程序会将某些符合条件以便再次执行IO处理的的fd放到fd_spec list[]中，fd_process_cached_events()函数会再次执行这些fd的io handler。TCP连接处理流程关键数据结构 sessionhaproxy负责处理请求的核心数据结构是struct session，本文不对该数据结构进行分析。从业务的处理的角度，简单介绍一下对session的理解：ha…

TCMalloc简单测试
type: item
date: 2016-05-06T15:04:00+08:00
description: 测试方式如下：20个线程同时进行malloc和free的操作，每个线程进行malloc和free的操作1M次，每次申请的大小是操作次数的大小(比如第10次操作，就malloc(10))。测试每个线程操作1M次malloc和free的时间。glibc malloc的结果如下：time use 20118516 second time use 20159695 second time use 20209392 second time use 20243706 second time use 20314329 second time use 20409412 second time use 20497020 second time use 20485955 second time use 20536799 second time use 20530233 second time use 20615596 second time use 20697397 second time use 20783369 second time use 20798414 second time use 20832314 second time use 20844732 second time use 20847341 second time use 20877054 second time use 20919291 second time use 20924166 secondtcmalloc的结果如下：time use 6547655 second time use 6586277 second time use 6595014 second time use 6602379 second time use 6603297 second time use 6617556 second time use 6606473 second time use 6594718 second time use 6625851 second time use 6610081 second time use 6627009 second time use 6632563 second time use 6632408 second time use 6614826 second time use 6616527 second time use 6639291 second time use 6640707 second time use 6641525 second time use 6616567 second time use 6643258 second代码如下：#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <time.h> #include <pthread.h> #define LOOP_NUM (1024 * 1024) #define THREAD_NUM 20 static void *fun_thread(void *argv) { struct timeval start, end; gettimeofday(&start, NULL); uint32_t i; for(i = 0; i < LOOP_NUM; i++) { uint8_t *a = malloc(i); free(a); } gettimeofday(&end, NULL); printf("time use %ld second\n", (end.tv_sec - start.tv_sec) * 1000000 + end.tv_usec - start.tv_usec); return NULL; } void main(void) { pthread_t a[THREAD_NUM]; uint8_t i; for(i = 0; i < THREAD_NUM; i++) pthread_create(&a[i], NULL, fun_thread, NULL); for(i = 0; i < THREAD_NUM; i++) pthread_join(a[i], NULL); }编译如下：@gcc $< -o $@ -lpthread @gcc $< -o $@ -lpthread -ltcmalloc_minimal