20180803

1 算法

2 操作系统收数据包流程

物理接口到FIFO

网卡从物理接口得到数据临时放到FIFO中,如果FIFO溢出,新到的帧将直接丢弃.可以通过

  1. ifconfig命令查看丢包数据
  2. netstat -iRX-OVR字段

查看丢包的数据.

如果发生丢包,基本是上层处理过慢导致的.

帧校验并转存

一个帧的数据接收完成以后,网卡控制器做CRC校验,和MAC地址教研,如果数据合法,就将借助Ring Buffer转存到系统主存.

Ring Buffer是操作系统分配的一段连续的内存,网卡通过DMA读取Ring Buffer中的元素.网卡控制器中保存Ring Buffer的首尾指针.

Ring Buffer数据溢出,也会丢弃,查看命令为:

  1. ifconfig
  2. netstat -i

无论是FIFO还是Ring Buffer溢出,都可以通过一下三个思路解决:

  1. 系统负载过高,找出占cpu高的进程
  2. 硬件扩容
  3. 被攻击,瞬间包量超过网卡吞吐能力,DDos,
  4. Ring Buffer 太小,通过ethtool增大.

Ring Buffer有多个(一个cpu对应一个),因此在数据帧选择Ring Buffer的时候,要根据数据帧的源和目标ip做哈希,选出队列.因此可能会造成中断不均匀的情况.补救办法由RPS,但是不推荐.

DMA到主存

DMA Engine通过DMA地址将数据帧复制到系统主存.然后更新Ring Buffer的首尾指针.

前四步由网卡独立完成不需要cpu参与,由于数据帧已经传输到系统主存,接下来通过终端控制器产生中断,通知cpu

CPU发现中断

cpu发现中断,调用中断处理程序.网卡驱动程序初始化的时候已经注册,因此这里最终调用的是驱动,驱动屏蔽网卡中断,调用
NAPI.

NAPI

NAPI是linux上采用的一种提高网络处理效率的奇数,他的核心概念就是不采用中断的方式处理数据.首先以中断唤醒数据接收的服务程序,然后POLL的方式来轮训数据.

也就是说,放在中断的下半部分,软中断中执行.因为硬件中断需要保持轻量.所以NAPI只是设置下软中断标志位,激活收包的软中断.将发生中断的设备加入到自己的轮训链表中.至此,这个中断处理完成.

软中断

中断执行完以后,退出中断模式,linux规定在返回用户态的时候,需要处理软中断,因此此时会检测cpu的软中断flag,遍历所有激活的软中断,包括上面NAPI设置的软中断.执行NAPI对应的函数.

NAPI遍历所有的中断设备,调用其驱动程序提供的轮询方法.由于(软硬)中断会打断当前进程,因此收包轮询的软终端不能执行的太久,不然会饿死其他进程,因此所有网卡的处理量由net.core.netdev_budget限制.也就是/proc/sys/net/core/netdev_budget文件.单个设备的处理量由net.core.dev_weight控制.如果处理不完,会唤醒当前cpu对应的ksfotirq进程,优先级和用户进程相同,公平竞争cpu.

驱动轮询完成后会重新打开中断.(通过网卡的寄存器来判断是否还有未处理的帧)

传给上层

NAPI完成包的接受,NAPI将包传递到上层.

NAPI根据协议,调用对应的方法同时检测是否有嗅探.Tcpdump等,如果由,将包传递给他们

因此Tcpdump没有抓到包,说明在网卡的时候就已经将包丢了.如果Tcpdump抓到了,但是进程没有收到,说明,链路层到传输层之间丢弃了.

3 操作系统发包流程

待补全

包从上一层流到IP层,然后交给邻居缓存.邻居缓存主要负责arp/rarp及其缓存.

将数据添加到队列,等待调度发送.QDisc负责linux下的流量控制,解决系统和外设之间速度不一致的问题:

  1. 基于优先级调度,调度算法根据ip头的TOS字段区分队列,对标识为时延敏感的数据报优先发送.
  2. 控制发送速度
  3. 控制时延和丢包率

然后QDisc从队列中取出数据报,调度net.core.dev_weight个包后,需要让出cpu重新调度.

使用GSO或是驱动程序发送数据

4 网络部分中断调优

和收发包相关的中断模式可以选:

  1. 实时中断
  2. 中断+轮训:中断的上半部分和下半部分
  3. 纯轮训

这个可以配置,ethtool -C eth rx-usecs 数值(教程这样说,没实践过.),取值为:

  1. 0,实时中断,一个包一个中断,实时性高,延时低
  2. 1动态调整,一次中断可能收很多包
  3. 3保守动态调整,少于上一个
  4. 10~8191,多少微妙发生一次中断.

大佬配置位450,因为设置的太大,会有一定的延时.

5 链接时重定位和运行时重定位

链接是重定位比较简单,只是将函数调用的地址写死即可,因为在连接的时候,函数的实现能够找到.

如果是动态链接的,那么函数的地址并不知道,需要在运行的时候动态加载才能知道.同时,因为不能够修改代码段的内容,因此不能够在加载动态库获得了函数的地址以后,修改代码段的内容,因此代码段的调用动态库函数的地址也是写死的,只是写的地址不是真是的要调用的函数.

因此调用动态库函数的代码会去掉用一个编译器生成的函数,该函数读取动态库的函数的地址,然后jmp过去.

而编译器生成的函数,同一存放在一个表中,称为程序连接表PLT,这些函数的地址同意存放在一个表中,称为全局偏移表GOT.

6 C++11 新特性

官方:包含c++11,14,20

翻译,但是不全

alignas 指定符

c++中是关键字

指定类型或对象的对齐要求.

alignas 指定符可应用到变量或非位域类数据成员的声明,或能应用于 class/struct/union 或枚举的定义.它不能应用于函数参数或 catch 子句的异常参数.

以此声明声明的对象或类型的对齐要求将等于用于声明的所有 alignas 指定符最大的非零 expression ,除非这会削弱类型的自然对齐.

忽略在同一声明上弱于另一 alignas 的合法的非零对齐.

始终忽略 alignas(0) .

// 每个 sse_t 类型对象将对齐到 16 字节边界
struct alignas(16) sse_t
{
  float sse_data[4];
};
 
// 数组 "cacheline" 将对齐到 128字节边界
alignas(128) char cacheline[12];

C中:按 ISO C11 标准, C 语言有 _Alignas 关键词并于头文件 <stdalign.h>定义alignas为展开成该关键词的预处理器宏,但 C++ 中这是关键词,且头文件<stdalign.h><cstdalign>不定义该宏.尽管它们仍定义宏常量 __alignas_is_defined. 但是测试编译不通过

alignof 运算符

返回 std::size_t 类型值.

返回由类型标识所指示的类型的任何实例所要求的对齐字节数,该类型可以为完整类型,数组类型或者引用类型.若类型为引用类型,则运算符返回被引用类型的对齐;若类型为数组类型,则返回元素类型的对齐要求.

#include <iostream>
 
struct Foo {
    int   i;
    float f;
    char  c;
};
 
struct Empty {};
 
struct alignas(64) Empty64 {};
 
int main()
{
    std::cout << "Alignment of"  "\n"
        "- char             : " << alignof(char)    << "\n"
        "- pointer          : " << alignof(int*)    << "\n"
        "- class Foo        : " << alignof(Foo)     << "\n"
        "- empty class      : " << alignof(Empty)   << "\n"
        "- alignas(64) Empty: " << alignof(Empty64) << "\n";
}

// 输出
// Alignment of
// - char             : 1
// - pointer          : 8
// - class Foo        : 4
// - empty class      : 1
// - alignas(64) Empty: 64

std::alignment_of

模板类型,返回一个对象,类型的对齐大小.位于 <type_traits>

提供等于 T 类型对齐要求的成员常量 value ,如同用 alignof 表达式获得.若 T 是数组类型,则返回元素类型的对齐要求,若 T 是引用类型,则返回备用用类型的对齐要求.

#include <iostream>
#include <type_traits>
 
class A {};
 
int main() 
{
    std::cout << std::alignment_of<A>::value << '\n';
    std::cout << std::alignment_of<int>() << '\n'; // 另一种语法
    // std::cout << std::alignment_of_v<double> << '\n'; // c++17 另一种语法
}

// 输出
// 1
// 4
// 8      需要c++17

原子操作库

原子库为细粒度的原子操作提供组件,允许无锁并发编程.涉及同一对象的每个原子操作,相对于任何其他原子操作是不可分的.原子对象不具有数据竞争.定义于头文件 <atomic>

类型为:atomic

如果相对一个非原子的数值对象进行操作,在c++20中提供了,模板类型atomic_ref

重载了运算符,例如+=等,但是不提供-这种二元操作符

std::atomic_flag

定义于头文件 <atomic>

它保证是免锁的

提供clear(),=,test_and_set

auto

对于变量,指定其类型将从其初始化器自动推导而出.

constexpr 指定符

constexpr 指定符声明可以在编译时求得函数或变量的值.然后这些变量和函数(若给定了合适的函数参数)可用于仅允许编译时常量表达式之处.

满足:

  1. 其类型必须是字面类型(LiteralType) .
  2. 它必须被立即初始化
  3. 其初始化的完整表达式,包括所有隐式转换,构造函数调用等,都必须是常量表达式

constexpr 构造函数必须满足下列要求

  1. 其每个参数都必须是字面类型(LiteralType) .
  2. 该类不能有虚基类
  3. 该构造函数不可有函数 try 块

委托构造函数

若类名自身于初始化器列表作为 class-or-identifier 出现,则列表自身必须仅有那一个成员初始化器组成;这种构造函数被称为委托构造函数,而构造函数列表的仅有成员所选择的构造函数是目标构造函数

此情况下,目标构造函数为重载决议所选择,并首先执行,然后控制转移到委托构造函书并执行其体.

委托构造函数不能递归.

显式转换运算符

四种显示类型转换函数

扩展的friend语法

在C++ 11中,声明一个类为另外一个类的友元时,不再需要使用class关键字,也可以使用typedef(或者using)定义的别名.

extern template

c++98 的 template 有些问题,代码会膨胀,而且不容易定位到底是实例化了那一个实例.

编译选项-DEXTERN_TMPL

enum前置声明

标准C++的规范并没有定义enum结构的大小.故即使在头文件对enum进行前置声明,在引用enum的时候,还是无法确定enum的大小,故无法对enum进行前置声明.

c++11支持了,C++11 域化枚举(scoped enum),关键字为 enum class,可视为一个 "class",故能防止“命名空间污染”.

继承的构造函数

c++11允许,除了使用using Base::func将积累的成员函数在派生类中可见外,还允许使用using Base::Base将构造函数可见

初始化器列表

冒号初始化

Lambda

还是挺复杂的

[ captures ]( params ) -> retType { body }
[ captures ]( params ) { body }
[ captures ] { body }

captures,零或更多捕获的逗号分隔列表:

若变量满足下列条件,则 lambda 表达式能使用而不捕获它

  1. 为非局部变量,或拥有静态或线程局域存储期(该情况下不能捕获该变量)
  2. 为以常量表达式初始化的引用.

若变量满足下列条件,则 lambda 表达式能读取其值而不捕获它

  1. 拥有 const 而非 volatile 的整数或枚举类型,并已用常量表达式初始化,
  2. 为 constexpr 且为可平凡复制构造.

ret :返回类型.若缺失,则由函数的 return 语句所隐含(或若函数不返回任何值则为 void )

Lambda 表达式是纯右值表达式,其类型是唯一的无名非联合非聚合类类型,被称为闭包类型,它声明于含有该 lambda 表达式的最小块作用域,类作用域或命名空间作用域.Lambda中有this指针,可以访问捕获的变量.

若 lambda 捕获外围对象(用 this 或 *this ),则最接近的外围函数必须是非静态成员函数,或者该 lambda 必须在默认成员初始化器中,也就是说Lambda可以捕获this指针.

Lambda嵌套的时候,外层的不能够根据内层Lambda捕获变量而,自动捕获.

Long Long

添加了个类型.这是一个至少为64 bit的整数类型

内联命名空间

类似于子命名空间中可以使用一级命名空间中的变量或是函数.子命名空间中的标识符被自动导入到父类中.

尾随的函数返回类型

nullptr

原始字符串字面量

不进行转义的完整字符串.使用R前缀:R “xxx(raw string)xxx” 

用户定义字面量

C++11增加了用户自行定义新的字面量后缀并由此用字面量构造对象的能力.这是通过定义字面量运算符(literal operator)函数或函数模板实现.

该运算符名字由一对相邻双引号前导:

#include<iostream>
struct S{
    int value;
};
S operator ""_mysuffix(unsigned long long v)  //用户定义字面量运算符的实现
{
     S s_;
     s_.value=(int)v;
     return s_;
}

int main()
{
   S sv;
   sv=101_mysuffix;  //这里的101是类型S的字面量
   std::cout<<sv.value<<std::endl;
   return 0;
}

右角括号不空格

例如vector<vector<int>>

static_assert

进行编译时断言检查.若 bool_constexpr 返回 true ,则此声明没有效果.否则发布一个编译时错误.

static_assert( bool_constexpr , message )   ;
static_assert( false , "message" )  ;

class enum

模板别名

using =

线程局域存储

线程局域存储

在标准 C++ 中,并非任意的类型都能做为 union 的成员.比方说,带有 non-trivial 构造函数的类型就不能是 union 的成员.在新的标准里,移除了所有对 union 的使用限制,除了其成员仍然不能是引用类型. 这一改变使得 union 更强大,更有用,也易于使用.

struct point
{
  point() {}
  point(int x, int y): x_(x), y_(y) {}
  int x_, y_;
};
union
{
  int z;
  double w;
  point p;  // 不合法的 C++; point 有一 non-trivial 建構式
            // 合法的 C++11
};

类型特性

添加了很多类型.

pdf下载

变长模板

范围 for 循环

override 与 final

修饰符

属性

和编译器相关的一些__attribute__(( ))

引用限定符

“引用限定符”(reference qualifier),它必须同时在 类内非静态的函数声明和定义这两个地方里放置,只带有一个&的类内函数代表这个函数是一个左值引用成员函数,也就是说,这个函数被限定了,而这个限定是对this指针的限定.左值引用成员函数的意思就是仅在this指针指向一个左值时,调用函数是正确的.事实上,写在成员函数参数列表的小括号后面,分号前面的东西都是用来修饰this指针的.比如我们假设有一个成员函数是void ACLASS::fun() cosnt;这里小括号后面分号前面的修饰词是const.这个const就是用来修饰this指针的,这样就意味着你不能通过this指针改变类内元素的值.const和&(或者&&)可以在成员函数后面混用.比如void ACLASS::fun() cosnt &;这样我们的要求就是既不能通过this指针改变任何值,也不能当this指向一个非左值时,这个函数被调用.

非静态数据成员初始化器

有并发的动态初始化及析构

静态局部变量,声明于块作用域,带指定符 static 的变量拥有静态存储期,但在控制首次经过其声明时才得到初始化(除非其初始化是零或常量初始化,这可以在首次进入块前进行).在所有进一步调用上,声明被跳过. 若初始化抛异常,则不认为变量被初始化,且控制下次经过该声明时,将再次尝试初始化. 若初始化递归地进入正在初始化的变量的块,则行为未定义.若多个线程试图初始化同一静态局部变量,则初始化准确出现一次(可对任意函数以 std::call_once 取得类似行为). 注意:此特性的通常实现使用双检查锁定模式的变体,这将已初始化的局部静态变量的运行时开销减少到单次非原子布尔比较.

noexcept

同 noexcept( true )

如果一个函数不能抛出异常,或者一个程序并没有接获某个函数所抛出的异常并进行处理,那么这个函数可以用新的noexcept关键字对其进行修饰,表示这个函数不会抛出异常或者抛出的异常不会被接获并处理.

差几个.没写完

猜你喜欢

转载自www.cnblogs.com/perfy576/p/9415731.html