Merge remote-tracking branch 'LCTT/master'

This commit is contained in:
Xingyu Wang 2020-10-12 18:04:48 +08:00
commit 761db84a3a
5 changed files with 617 additions and 616 deletions

View File

@ -0,0 +1,304 @@
[#]: collector: (lujun9972)
[#]: translator: (gxlct008)
[#]: reviewer: (wxy)
[#]: publisher: (wxy)
[#]: url: (https://linux.cn/article-12710-1.html)
[#]: subject: (TCP window scaling, timestamps and SACK)
[#]: via: (https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/)
[#]: author: (Florian Westphal https://fedoramagazine.org/author/strlen/)
TCP 窗口缩放、时间戳和 SACK
======
![](https://img.linux.net.cn/data/attachment/album/202010/12/114050up4695djpw6n4tu9.jpg)
Linux TCP 协议栈具有无数个可以更改其行为的 `sysctl` 旋钮。 这包括可用于接收或发送操作的内存量、套接字的最大数量、可选的特性和协议扩展。
有很多文章出于各种“性能调优”或“安全性”原因,建议禁用 TCP 扩展,比如时间戳或<ruby>选择性确认<rt>Selective ACKnowledgments</rt></ruby>SACK
本文提供了这些扩展功能的背景,为什么会默认启用,它们之间是如何关联的,以及为什么通常情况下将它们关闭是个坏主意。
### TCP 窗口缩放
TCP 可以承受的数据传输速率受到几个因素的限制。其中包括:
* <ruby>往返时间<rt>Round trip time</rt></ruby>RTT
这是数据包到达目的地并返回回复所花费的时间。越低越好。
* 所涉及的网络路径的最低链路速度。
* 丢包频率。
* 新数据可用于传输的速度。
例如CPU 需要能够以足够快的速度将数据传递到网络适配器。如果 CPU 需要首先加密数据,则适配器可能必须等待新数据。同样地,如果磁盘存储不能足够快地读取数据,则磁盘存储可能会成为瓶颈。
* TCP 接收窗口的最大可能大小。
接收窗口决定了 TCP 在必须等待接收方报告接收到该数据之前可以传输多少数据(以字节为单位)。这是由接收方宣布的。接收方将在读取并确认接收到传入数据时不断更新此值。接收窗口的当前值包含在 [TCP 报头][2] 中,它是 TCP 发送的每个数据段的一部分。因此只要发送方接收到来自对等方的确认它就知道当前的接收窗口。这意味着往返时间RTT越长发送方获得接收窗口更新所需的时间就越长。
TCP 的未确认(正在传输)数据被限制为最多 64KB。在大多数网络场景中这甚至还不足以维持一个像样的数据速率。让我们看看一些例子。
**理论数据速率**
在往返时间RTT为 100 毫秒的情况下TCP 每秒最多可以传输 640KB。在延迟为 1 秒的情况下,最大理论数据速率降至只有 64KB/s。
这是因为接收窗口的原因。一旦发送了 64KB 的数据,接收窗口就已经满了。发送方必须等待,直到对等方通知它应用程序已经读取了至少一部分数据。
发送的第一个段会把 TCP 窗口缩减去该段的大小。在接收窗口值的更新信息可用之前,需要往返一次。当更新以 1 秒的延迟到达时,即使链路有足够的可用带宽,也会导致 64KB 的限制。
为了充分利用一个具有几毫秒延迟的快速网络,必须有一个比传统 TCP 支持的窗口更大的窗口。“64KB 限制”是协议规范的产物TCP 头只为接收窗口大小保留了 16 个位。这允许接收窗口最大为 64KB。在 TCP 协议最初设计时,这个大小并没有被视为一个限制。
不幸的是,想通过仅仅更改 TCP 头来支持更大的最大窗口值是不可能的。如果这样做就意味着 TCP 的所有实现都必须同时更新,否则它们将无法相互理解。为了解决这个问题,我们改变了对接收窗口值的解释。
“窗口缩放选项”允许你改变这个解释,同时保持与现有实现的兼容性。
#### TCP 选项:向后兼容的协议扩展
TCP 支持可选扩展。这允许使用新特性增强协议,而无需立即更新所有实现。当 TCP 发起方连接到对等方时,它还会发送一个支持的扩展列表。所有扩展都遵循相同的格式:一个唯一的选项号,后跟选项的长度以及选项数据本身。
TCP 响应方检查连接请求中包含的所有选项号。如果它遇到一个不能理解的选项号,则会跳过
该选项号附带的“长度”字节的数据,并检查下一个选项号。响应方忽略了从答复中无法理解的内容。这使发送方和接收方都够理解所支持的公共选项集。
使用窗口缩放时,选项数据总是由单个数字组成。
### 窗口缩放选项
```
Window Scale option (WSopt): Kind: 3, Length: 3
    +---------+---------+---------+
    | Kind=3  |Length=3 |shift.cnt|
    +---------+---------+---------+
         1         1         1
```
[窗口缩放][3] 选项告诉对等方,应该使用给定的数字缩放 TCP 标头中的接收窗口值,以获取实际大小。
例如,一个宣告窗口缩放因子为 7 的 TCP 发起方试图指示响应方,任何将来携带接收窗口值为 512 的数据包实际上都会宣告 65536 字节的窗口。增加了 128 倍2^7。这将允许最大为 8MB 的 TCP 窗口。
不能理解此选项的 TCP 响应方将会忽略它,为响应连接请求而发送的 TCP 数据包SYN-ACK不会包含该窗口缩放选项。在这种情况下双方只能使用 64k 的窗口大小。幸运的是,默认情况下,几乎每个 TCP 栈都支持并默认启用了此选项,包括 Linux。
响应方包括了它自己所需的缩放因子。两个对等方可以使用不同的因子。宣布缩放因子为 0 也是合法的。这意味着对等方应该如实处理它接收到的接收窗口值,但它允许应答方向上的缩放值,然后接收方可以使用更大的接收窗口。
与 SACK 或 TCP 时间戳不同,窗口缩放选项仅出现在 TCP 连接的前两个数据包中,之后无法更改。也不可能通过查看不包含初始连接三次握手的连接的数据包捕获来确定缩放因子。
支持的最大缩放因子为 14。这将允许 TCP 窗口的大小高达 1GB。
**窗口缩放的缺点**
在非常特殊的情况下,它可能导致数据损坏。但在你禁用该选项之前,要知道通常情况下是不可能损坏的。还有一种解决方案可以防止这种情况。不幸的是,有些人在没有意识到它与窗口缩放的关系的情况下禁用了该解决方案。首先,让我们看一下需要解决的实际问题。想象以下事件序列:
1. 发送方发送段s_1、s_2、s_3、... s_n。
2. 接收方看到s_1、s_3、... s_n并发送对 s_1 的确认。
3. 发送方认为 s_2 丢失,然后再次发送。它还发送了段 s_n+1 中包含的新数据。
4. 接收方然后看到s_2、s_n+1s_2数据包 s_2 被接收两次。
当发送方过早触发重新传输时,可能会发生这种情况。在正常情况下,即使使用窗口缩放,这种错误的重传也绝不会成为问题。接收方将只丢弃重复项。
#### 从旧数据到新数据
TCP 序列号最多可以为 4GB。如果它变得大于此值则该序列会回绕到 0然后再次增加。这本身不是问题但是如果这种问题发生得足够快则上述情况可能会造成歧义。
如果在正确的时刻发生回绕,则序列号 s_2重新发送的数据包可能已经大于 s_n+1。因此在最后的步骤4接收方可以将其解释为s_2、s_n+1、s_n+m即它可以将 “旧” 数据包 s_2 视为包含新数据。
通常,这不会发生,因为即使在高带宽链接上,“回绕”也只会每隔几秒钟或几分钟发生一次。原始数据包和不需要的重传的数据包之间的间隔将小得多。
例如,对于 50MB/s 的传输速度,重复项要迟到一分钟以上才会成为问题。序列号的回绕速度没有快到让小的延迟会导致这个问题。
一旦 TCP 达到 “GB/s” 的吞吐率,序列号的回绕速度就会非常快,以至于即使只有几毫秒的延迟也可能会造成 TCP 无法检测出的重复项。通过解决接收窗口太小的问题TCP 现在可以用于以前无法实现的网络速度,这会产生一个新的,尽管很少见的问题。为了在 RTT 非常低的环境中安全使用 GB/s 的速度,接收方必须能够检测到这些旧的重复项,而不必仅依赖序列号。
### TCP 时间戳
#### 最佳截止日期
用最简单的术语来说,[TCP 时间戳][3]只是在数据包上添加时间戳,以解决由非常快速的序列号回绕引起的歧义。如果一个段看起来包含新数据,但其时间戳早于上一个在接收窗口内的数据包,则该序列号已被重新回绕,而“新”数据包实际上是一个较旧的重复项。这解决了即使在极端情况下重传的歧义。
但是该扩展不仅仅是检测旧数据包。TCP 时间戳的另一个主要功能是更精确的往返时间测量RTTm
#### 需要准确的 RTT 估算
当两个对等方都支持时间戳时,每个 TCP 段都携带两个附加数字:时间戳值和回显时间戳。
```
TCP Timestamp option (TSopt): Kind: 8, Length: 10
+-------+----+----------------+-----------------+
|Kind=8 | 10 |TS Value (TSval)|EchoReply (TSecr)|
+-------+----+----------------+-----------------+
    1      1         4                4
```
准确的 RTT 估算对于 TCP 性能至关重要。TCP 会自动重新发送未确认的数据。重传由计时器触发:如果超时,则 TCP 会将尚未收到确认的一个或多个数据包视为丢失。然后再发送一次。
但是,“尚未得到确认” 并不意味着该段已丢失。也有可能是接收方到目前为止没有发送确认或者确认仍在传输中。这就造成了一个两难的困境TCP 必须等待足够长的时间,才能让这种轻微的延迟变得无关紧要,但它也不能等待太久。
**低网络延迟 VS 高网络延迟**
在延迟较高的网络中如果计时器触发过快TCP 经常会将时间和带宽浪费在不必要的重发上。
然而在延迟较低的网络中等待太长时间会导致真正发生数据包丢失时吞吐量降低。因此在低延迟网络中计时器应该比高延迟网络中更早到期。所以TCP 重传超时不能使用固定常量值作为超时。它需要根据其在网络中所经历的延迟来调整该值。
**往返时间的测量**
TCP 选择基于预期的往返时间RTT的重传超时。RTT 事先是未知的。它是通过测量发送的段与 TCP 接收到该段所承载数据的确认之间的增量来估算的。
由于多种因素使其而变得复杂。
* 出于性能原因TCP 不会为收到的每个数据包生成新的确认。它等待的时间非常短:如果有更多的数据段到达,则可以通过单个 ACK 数据包确认其接收。这称为<ruby>“累积确认”<rt>cumulative ACK</rt></ruby>
* 往返时间并不恒定。这是有多种因素造成的。例如,客户端可能是一部移动电话,随其移动而切换到不同的基站。也可能是当链路或 CPU 的利用率提高时,数据包交换花费了更长的时间。
* 必须重新发送的数据包在计算过程中必须被忽略。这是因为发送方无法判断重传数据段的 ACK 是在确认原来的传输数据(毕竟已到达)还是在确认重传数据。
最后一点很重要:当 TCP 忙于从丢失中恢复时,它可能仅接收到重传段的 ACK。这样它就无法在此恢复阶段测量更新RTT。所以它无法调整重传超时然后超时将以指数级增长。那是一种非常具体的情况它假设其他机制如快速重传或 SACK 不起作用)。但是,使用 TCP 时间戳,即使在这种情况下也会进行 RTT 评估。
如果使用了扩展,则对等方将从 TCP 段的扩展空间中读取时间戳值并将其存储在本地。然后,它将该值作为 “回显时间戳” 放入发回的所有数据段中。
因此,该选项带有两个时间戳:它的发送方自己的时间戳和它从对等方收到的最新时间戳。原始发送方使用 “回显时间戳” 来计算 RTT。它是当前时间戳时钟与 “回显时间戳” 中所反映的值之间的增量。
**时间戳的其他用途**
TCP 时间戳甚至还有除 PAWS<ruby>防止序列号回绕<rt>Protection Against Wrapped Sequences</rt></ruby> 和 RTT 测量以外的其他用途。例如,可以检测是否不需要重发。如果该确认携带较旧的回显时间戳,则该确认针对的是初始数据包,而不是重新发送的数据包。
TCP 时间戳的另一个更晦涩的用例与 TCP [syn cookie][4] 功能有关。
**在服务器端建立 TCP 连接**
当连接请求到达的速度快于服务器应用程序可以接受新的传入连接的速度时,连接积压最终将达到其极限。这可能是由于系统配置错误或应用程序中的错误引起的。当一个或多个客户端发送连接请求而不对 “SYN ACK” 响应做出反应时,也会发生这种情况。这将用不完整的连接填充连接队列。这些条目需要几秒钟才会超时。这被称为<ruby>“同步泛洪攻击”<rt>syn flood attack</rt></ruby>
**TCP 时间戳和 TCP Syn Cookie**
即使队列已满,某些 TCP 协议栈也允许继续接受新连接。发生这种情况时Linux 内核将在系统日志中打印一条突出的消息:
> 端口 P 上可能发生 SYN 泛洪。正在发送 Cookie。检查 SNMP 计数器。
此机制将完全绕过连接队列。通常存储在连接队列中的信息被编码到 SYN/ACK 响应 TCP 序列号中。当 ACK 返回时,可以根据序列号重建队列条目。
序列号只有有限的空间来存储信息。因此,使用 “TCP Syn Cookie” 机制建立的连接不能支持 TCP 选项。
但是,对两个对等点都通用的 TCP 选项可以存储在时间戳中。ACK 数据包在回显时间戳字段中反映了该值,这也允许恢复已达成共识的 TCP 选项。否则cookie 连接受标准的 64KB 接收窗口限制。
**常见误区 —— 时间戳不利于性能**
不幸的是,一些指南建议禁用 TCP 时间戳以减少内核访问时间戳时钟来获取当前时间所需的次数。这是不正确的。如前所述RTT 估算是 TCP 的必要部分。因此,内核在接收/发送数据包时总是采用微秒级的时间戳。
在包处理步骤的其余部分中Linux 会重用 RTT 估算所需的时钟时间戳。这还避免了将时间戳添加到传出 TCP 数据包的额外时钟访问。
整个时间戳选项在每个数据包中仅需要 10 个字节的 TCP 选项空间,这不会显著减少可用于数据包有效负载的空间。
**常见误区 —— 时间戳是个安全问题**
一些安全审计工具和(较旧的)博客文章建议禁用 TCP 时间戳,因为据称它们泄露了系统正常运行时间:这样一来,便可以估算系统/内核的补丁级别。这在过去是正确的:时间戳时钟基于不断增加的值,该值在每次系统引导时都以固定值开始。时间戳值可以估计机器已经运行了多长时间(正常运行时间 `uptime`)。
从 Linux 4.12 开始TCP 时间戳不再显示正常运行时间。发送的所有时间戳值都使用对等设备特定的偏移量。时间戳值也每 49 天回绕一次。
换句话说,从地址 “A” 出发,或者终到地址 “A” 的连接看到的时间戳与到远程地址 “B” 的连接看到的时间戳不同。
运行 `sysctl net.ipv4.tcp_timeamp=2` 以禁用随机化偏移。这使得分析由诸如 `wireshark``tcpdump` 之类的工具记录的数据包跟踪变得更容易 —— 从主机发送的数据包在其 TCP 选项时间戳中都具有相同的时钟基准。因此,对于正常操作,默认设置应保持不变。
### 选择性确认
如果同一数据窗口中的多个数据包丢失了TCP 将会出现问题。这是因为 TCP 确认是累积的,但仅适用于按顺序到达的数据包。例如:
* 发送方发送段 s_1、s_2、s_3、... s_n
* 发送方收到 s_2 的 ACK
* 这意味着 s_1 和 s_2 都已收到,并且发送方不再需要保留这些段。
* s_3 是否应该重新发送? s_4 呢? s_n
发送方等待 “重传超时” 或 “重复 ACK” 以使 s_2 到达。如果发生重传超时或到达了 s_2 的多个重复 ACK则发送方再次发送 s_3。
如果发送方收到对 s_n 的确认,则 s_3 是唯一丢失的数据包。这是理想的情况。仅发送单个丢失的数据包。
如果发送方收到的确认段小于 s_n例如 s_4则意味着丢失了多个数据包。发送方也需要重传下一个数据段。
**重传策略**
可能只是重复相同的序列:重新发送下一个数据包,直到接收方指示它已处理了直至 s_n 的所有数据包为止。这种方法的问题在于,它需要一个 RTT直到发送方知道接下来必须重新发送的数据包为止。尽管这种策略可以避免不必要的重传但要等到 TCP 重新发送整个数据窗口后,它可能要花几秒钟甚至更长的时间。
另一种方法是一次重新发送几个数据包。当丢失了几个数据包时,此方法可使 TCP 恢复更快。在上面的示例中TCP 重新发送了 s_3、s_4、s_5、...,但是只能确保已丢失 s_3。
从延迟的角度来看,这两种策略都不是最佳的。如果只有一个数据包需要重新发送,第一种策略是快速的,但是当多个数据包丢失时,它花费的时间太长。
即使必须重新发送多个数据包,第二个也是快速的,但是以浪费带宽为代价。此外,这样的 TCP 发送方在进行不必要的重传时可能已经发送了新数据。
通过可用信息TCP 无法知道丢失了哪些数据包。这就是 TCP [选择性确认][5]SACK的用武之地了。就像窗口缩放和时间戳一样它是另一个可选的但非常有用的 TCP 特性。
**SACK 选项**
```
   TCP Sack-Permitted Option: Kind: 4, Length 2
   +---------+---------+
   | Kind=4  | Length=2|
   +---------+---------+
```
支持此扩展的发送方在连接请求中包括 “允许 SACK” 选项。如果两个端点都支持该扩展,则检测到数据流中丢失数据包的对等方可以将此信息通知发送方。
```
   TCP SACK Option: Kind: 5, Length: Variable
                     +--------+--------+
                     | Kind=5 | Length |
   +--------+--------+--------+--------+
   |      Left Edge of 1st Block       |
   +--------+--------+--------+--------+
   |      Right Edge of 1st Block      |
   +--------+--------+--------+--------+
   |                                   |
   /            . . .                  /
   |                                   |
   +--------+--------+--------+--------+
   |      Left Edge of nth Block       |
   +--------+--------+--------+--------+
   |      Right Edge of nth Block      |
   +--------+--------+--------+--------+
```
接收方遇到 s_2 后跟 s_5 ... s_n则在发送对 s_2 的确认时将包括一个 SACK 块:
```
                +--------+-------+
                | Kind=5 |   10  |
+--------+------+--------+-------+
| Left edge: s_5                 |
+--------+--------+-------+------+
| Right edge: s_n                |
+--------+-------+-------+-------+
```
这告诉发送方到 s_2 的段都是按顺序到达的,但也让发送方知道段 s_5 至 s_n 也已收到。然后发送方可以重新发送那两个数据包s_3、s_4并继续发送新数据。
**神话般的无损网络**
从理论上讲,如果连接不会丢包,那么 SACK 就没有任何优势。或者连接具有如此低的延迟,甚至等待一个完整的 RTT 都无关紧要。
在实践中,无损行为几乎是不可能保证的。即使网络及其所有交换机和路由器具有足够的带宽和缓冲区空间,数据包仍然可能丢失:
* 主机操作系统可能面临内存压力并丢弃数据包。请记住,一台主机可能同时处理数万个数据包流。
* CPU 可能无法足够快地消耗掉来自网络接口的传入数据包。这会导致网络适配器本身中的数据包丢失。
* 如果 TCP 时间戳不可用,即使一个非常小的 RTT 的连接也可能在丢失恢复期间暂时停止。
使用 SACK 不会增加 TCP 数据包的大小,除非连接遇到数据包丢失。因此,几乎没有理由禁用此功能。几乎所有的 TCP 协议栈都支持 SACK —— 它通常只在不进行 TCP 批量数据传输的低功耗 IOT 类的设备上才不存在。
当 Linux 系统接受来自此类设备的连接时TCP 会自动为受影响的连接禁用 SACK。
### 总结
本文中研究的三个 TCP 扩展都与 TCP 性能有关,最好都保留其默认设置:启用。
TCP 握手可确保仅使用双方都可以理解的扩展,因此,永远不需因为对等方可能不支持而全局禁用该扩展。
关闭这些扩展会导致严重的性能损失,尤其是 TCP 窗口缩放和 SACK。可以禁用 TCP 时间戳而不会立即造成不利影响,但是现在没有令人信服的理由这样做了。启用它们还可以支持 TCP 选项,即使在 SYN cookie 生效时也是如此。
--------------------------------------------------------------------------------
via: https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/
作者:[Florian Westphal][a]
选题:[lujun9972][b]
译者:[gxlct008](https://github.com/gxlct008)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://fedoramagazine.org/author/strlen/
[b]: https://github.com/lujun9972
[1]: https://fedoramagazine.org/wp-content/uploads/2020/08/tcp-window-scaling-816x346.png
[2]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure
[3]: https://www.rfc-editor.org/info/rfc7323
[4]: https://en.wikipedia.org/wiki/SYN_cookies
[5]: https://www.rfc-editor.org/info/rfc2018

View File

@ -1,284 +0,0 @@
[#]: collector: (lujun9972)
[#]: translator: (robsean)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Learn the basics of programming with C)
[#]: via: (https://opensource.com/article/20/8/c-programming-cheat-sheet)
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
Learn the basics of programming with C
======
Our new cheat sheet puts all the essentials of C syntax on an
easy-to-read handout.
![Cheat Sheet cover image][1]
In 1972, Dennis Ritchie was at Bell Labs, where a few years earlier, he and his fellow team members invented Unix. After creating an enduring OS (still in use today), he needed a good way to program those Unix computers so that they could perform new tasks. It seems strange now, but at the time, there were relatively few programming languages; Fortran, Lisp, [Algol][2], and B were popular but insufficient for what the Bell Labs researchers wanted to do. Demonstrating a trait that would become known as a primary characteristic of programmers, Dennis Ritchie created his own solution. He called it C, and nearly 50 years later, it's still in widespread use.
### Why you should learn C
Today, there are many languages that provide programmers more features than C. The most obvious one is C++, a rather blatantly named language that built upon C to create a nice object-oriented language. There are many others, though, and there's a good reason they exist. Computers are good at consistent repetition, so anything predictable enough to be built into a language means less work for programmers. Why spend two lines recasting an `int` to a `long` in C when one line of C++ (`long x = long(n);`) can do the same?
And yet C is still useful today.
First of all, C is a fairly minimal and straightforward language. There aren't very advanced concepts beyond the basics of programming, largely because C is literally one of the foundations of modern programming languages. For instance, C features arrays, but it doesn't offer a dictionary (unless you write it yourself). When you learn C, you learn the building blocks of programming that can help you recognize the improved and elaborate designs of recent languages.
Because C is a minimal language, your applications are likely to get a boost in performance that they wouldn't see with many other languages. It's easy to get caught up in the race to the bottom when you're thinking about how fast your code executes, so it's important to ask whether you _need_ more speed for a specific task. And with C, you have less to obsess over in each line of code, compared to, say, Python or Java. C is fast. There's a good reason the Linux kernel is written in C.
Finally, C is easy to get started with, especially if you're running Linux. You can already run C code because Linux systems include the GNU C library (`glibc`). To write and build it, all you need to do is install a compiler, open a text editor, and start coding.
### Getting started with C
If you're running Linux, you can install a C compiler using your package manager. On Fedora or RHEL:
```
`$ sudo dnf install gcc`
```
On Debian and similar:
```
`$ sudo apt install build-essential`
```
On macOS, you can [install Homebrew][3] and use it to install [GCC][4]:
```
`$ brew install gcc`
```
On Windows, you can install a minimal set of GNU utilities, GCC included, with [MinGW][5].
Verify you've installed GCC on Linux or macOS:
```
$ gcc --version
gcc (GCC) x.y.z
Copyright (C) 20XX Free Software Foundation, Inc.
```
On Windows, provide the full path to the EXE file:
```
PS&gt; C:\MinGW\bin\gcc.exe --version
gcc.exe (MinGW.org GCC Build-2) x.y.z
Copyright (C) 20XX Free Software Foundation, Inc.
```
### C syntax
C isn't a scripting language. It's compiled, meaning that it gets processed by a C compiler to produce a binary executable file. This is different from a scripting language like [Bash][6] or a hybrid language like [Python][7].
In C, you create _functions_ to carry out your desired task. A function named `main` is executed by default.
Here's a simple "hello world" program written in C:
```
#include &lt;stdio.h&gt;
int main() {
  [printf][8]("Hello world");
  return 0;
}
```
The first line includes a _header file_, essentially free and very low-level C code that you can reuse in your own programs, called `stdio.h` (standard input and output). A function called `main` is created and populated with a rudimentary print statement. Save this text to a file called `hello.c`, then compile it with GCC:
```
`$ gcc hello.c --output hello`
```
Try running your C program:
```
$ ./hello
Hello world$
```
#### Return values
It's part of the Unix philosophy that a function "returns" something to you after it executes: nothing upon success and something else (an error message, for example) upon failure. These return codes are often represented with numbers (integers, to be precise): 0 represents nothing, and any number higher than 0 represents some non-successful state.
There's a good reason Unix and Linux are designed to expect silence upon success. It's so that you can always plan for success by assuming no errors nor warnings will get in your way when executing a series of commands. Similarly, functions in C expect no errors by design.
You can see this for yourself with one small modification to make your program appear to fail:
```
include &lt;stdio.h&gt;
int main() {
  [printf][8]("Hello world");
  return 1;
}
```
Compile it:
```
`$ gcc hello.c --output failer`
```
Now run it using a built-in Linux test for success. The `&&` operator executes the second half of a command only upon success. For example:
```
$ echo "success" &amp;&amp; echo "it worked"
success
it worked
```
The `||` test executes the second half of a command upon _failure_.
```
$ ls blah || echo "it did not work"
ls: cannot access 'blah': No such file or directory
it did not work
```
Now try your program, which does _not_ return 0 upon success; it returns 1 instead:
```
$ ./failer &amp;&amp; echo "it worked"
String is: hello
```
The program executed successfully, yet did not trigger the second command.
#### Variables and types
In some languages, you can create variables without specifying what _type_ of data they contain. Those languages have been designed such that the interpreter runs some tests against a variable in an attempt to discover what kind of data it contains. For instance, Python knows that `var=1` defines an integer when you create an expression that adds `var` to something that is obviously an integer. It similarly knows that the word `world` is a string when you concatenate `hello` and `world`.
C doesn't do any of these investigations for you; you must define your variable type. There are several types of variables, including integers (int), characters (char), float, and Boolean.
You may also notice there's no string type. Unlike Python and Java and Lua and many others, C doesn't have a string type and instead sees strings as an array of characters.
Here's some simple code that establishes a `char` array variable, and then prints it to your screen using [printf][9] along with a short message:
```
#include &lt;stdio.h&gt;
int main() {
   char var[6] = "hello";
   [printf][8]("Your string is: %s\r\n",var);
```
You may notice that this code sample allows six characters for a five-letter word. This is because there's a hidden terminator at the end of the string, which takes up one byte in the array. You can run the code by compiling and executing it:
```
$ gcc hello.c --output hello
$ ./hello
hello
```
### Functions
As with other languages, C functions take optional parameters. You can pass parameters from one function to another by defining the type of data you want a function to accept:
```
#include &lt;stdio.h&gt;
int printmsg(char a[]) {
   [printf][8]("String is: %s\r\n",a);
}
int main() {
   char a[6] = "hello";
   printmsg(a);
   return 0;
}
```
The way this code sample breaks one function into two isn't very useful, but it demonstrates that `main` runs by default and how to pass data between functions.
### Conditionals
In real-world programming, you usually want your code to make decisions based on data. This is done with _conditional_ statements, and the `if` statement is one of the most basic of them.
To make this example program more dynamic, you can include the `string.h` header file, which contains code to examine (as the name implies) strings. Try testing whether the string passed to the `printmsg` function is greater than 0 by using the `strlen` function from the `string.h` file:
```
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
int printmsg(char a[]) {
  size_t len = [strlen][10](a);
  if ( len &gt; 0) {
    [printf][8]("String is: %s\r\n",a);
  }
}
int main() {
   char a[6] = "hello";
   printmsg(a);
   return 1;
}
```
As implemented in this example, the sample condition will never be untrue because the string provided is always "hello," the length of which is always greater than 0. The final touch to this humble re-implementation of the `echo` command is to accept input from the user.
### Command arguments
The `stdio.h` file contains code that provides two arguments each time a program is launched: a count of how many items are contained in the command (`argc`) and an array containing each item (`argv`). For example, suppose you issue this imaginary command:
```
`$ foo -i bar`
```
The `argc` is three, and the contents of `argv` are:
* `argv[0] = foo`
* `argv[1] = -i`
* `argv[2] = bar`
Can you modify the example C program to accept `argv[2]` as the string instead of defaulting to `hello`?
### Imperative programming
C is an imperative programming language. It isn't object-oriented, and it has no class structure. Using C can teach you a lot about how data is processed and how to better manage the data you generate as your code runs. Use C enough, and you'll eventually be able to write libraries that other languages, such as Python and Lua, can use.
To learn more about C, you need to use it. Look in `/usr/include/` for useful C header files, and see what small tasks you can do to make C useful to you. As you learn, use our [C cheat sheet][11] by [Jim Hall][12] of FreeDOS. It's got all the basics on one double-sided sheet, so you can immediately access all the essentials of C syntax while you practice.
--------------------------------------------------------------------------------
via: https://opensource.com/article/20/8/c-programming-cheat-sheet
作者:[Seth Kenlon][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/seth
[b]: https://github.com/lujun9972
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/coverimage_cheat_sheet.png?itok=lYkNKieP (Cheat Sheet cover image)
[2]: https://opensource.com/article/20/6/algol68
[3]: https://opensource.com/article/20/6/homebrew-mac
[4]: https://gcc.gnu.org/
[5]: https://opensource.com/article/20/8/gnu-windows-mingw
[6]: https://opensource.com/resources/what-bash
[7]: https://opensource.com/resources/python
[8]: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
[9]: https://opensource.com/article/20/8/printf
[10]: http://www.opengroup.org/onlinepubs/009695399/functions/strlen.html
[11]: https://opensource.com/downloads/c-programming-cheat-sheet
[12]: https://opensource.com/users/jim-hall

View File

@ -1,303 +0,0 @@
[#]: collector: (lujun9972)
[#]: translator: (gxlct008)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (TCP window scaling, timestamps and SACK)
[#]: via: (https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/)
[#]: author: (Florian Westphal https://fedoramagazine.org/author/strlen/)
TCP 窗口缩放,时间戳和 SACK
======
![][1]
Linux TCP 协议栈具有无数个 _sysctl_ 旋钮,允许更改其行为。 这包括可用于接收或发送操作的内存量,套接字的最大数量、可选特性和协议扩展。
有很多文章出于各种“性能调优”或“安全性”原因,建议禁用 TCP 扩展,比如时间戳或<ruby>选择性确认<rt>selective acknowledgments</rt></ruby> SACK
本文提供了这些扩展的功能背景,默认情况下处于启用状态的原因,它们之间是如何关联的,以及为什么通常情况下将它们关闭是个坏主意。
### TCP 窗口缩放
TCP 可以维持的数据传输速率受到几个因素的限制。其中包括:
* 往返时间RTT。这是数据包到达目的地并返回回复所花费的时间。越低越好。
* 所涉及的网络路径的最低链路速度
* 丢包频率
* 新数据可用于传输的速度。 例如CPU 需要能够以足够快的速度将数据传递到网络适配器。如果 CPU 需要首先加密数据,则适配器可能必须等待新数据。同样地,如果磁盘存储不能足够快地读取数据,则磁盘存储可能会成为瓶颈。
* TCP 接收窗口的最大可能大小。接收窗口决定 TCP 在必须等待接收方报告接收到该数据之前可以传输多少数据 (以字节为单位)。这是由接收方宣布的。接收方将在读取并确认接收到传入数据时不断更新此值。接收窗口当前值包含在 [TCP 报头][2] 中,它是 TCP 发送的每个数据段的一部分。因此只要发送方接收到来自对等方的确认它就知道当前的接收窗口。这意味着往返时间RTT越长发送方获得接收窗口更新所需的时间就越长。
TCP 被限制为最多 64KB 的未确认(正在传输)数据。在大多数网络场景中,这甚至还不足以维持一个像样的数据速率。让我们看看一些例子。
##### 理论数据速率
由于往返时间 (RTT) 为 100 毫秒TCP 每秒最多可以传输 640KB。在延迟 1 秒的情况下,最大理论数据速率降至 64KB/s。
这是因为接收窗口的原因。一旦发送了 64KB 的数据,接收窗口就已经满了。发送方必须等待,直到对等方通知它应用程序已经读取了至少一部分数据。
发送的第一个段会把 TCP 窗口缩减一个自身的大小。在接收窗口值的更新可用之前,需要往返一次。当更新以 1 秒的延迟到达时,即使链路有足够的可用带宽,也会导致 64KB 的限制。
为了充分利用一个具有几毫秒延迟的快速网络,必须有一个比传统 TCP 支持的窗口大的窗口。“64KB 限制”是协议规范的产物TCP 头只为接收窗口大小保留 16 位。这允许接收窗口高达 64KB。在 TCP 协议最初设计时,这个大小并没有被视为一个限制。
不幸的是,想通过仅仅更改 TCP 头来支持更大的最大窗口值是不可能的。如果这样做就意味着 TCP 的所有实现都必须同时更新,否则它们将无法相互理解。为了解决这个问题,需要改变接收窗口值的解释。
“窗口缩放选项”允许这样做,同时保持与现有实现的兼容性。
#### TCP 选项:向后兼容的协议扩展
TCP 支持可选扩展。 这允许使用新特性增强协议,而无需立即更新所有实现。 当 TCP 启动器连接到对等方时,它还会发送一个支持的扩展列表。 所有扩展名都遵循相同的格式:一个唯一的选项号,后跟选项的长度以及选项数据本身。
TCP 响应程序检查连接请求中包含的所有选项号。 如果它遇到一个不能理解的选项号,则会跳过
该选项号附带的“长度”字节的数据,并检查下一个选项号。 响应者忽略了从答复中无法理解的内容。 这使发送方和接收方都够了解所支持的通用选项集。
使用窗口缩放时,选项数据总是由单个数字组成。
### 窗口缩放选项
```
窗口缩放选项 (WSopt): Kind: 3, Length: 3
    +---------+---------+---------+
    | Kind=3  |Length=3 |shift.cnt|
    +---------+---------+---------+
         1         1         1
```
[窗口缩放][3] 选项告诉对等点,应该使用给定的数字缩放 TCP 标头中的接收窗口值,以获取实际大小。
例如,一个宣告窗口缩放比例因子为 7 的 TCP 启动器试图指示响应程序,任何将来携带接收窗口值为 512 的数据包实际上都会宣告 65536 字节的窗口。 增加了 128 倍。这将允许最大为 8MB 的 TCP 窗口。
不能理解此选项的 TCP 响应程序将会忽略它。 为响应连接请求而发送的 TCP 数据包SYN-ACK不包含窗口缩放选项。在这种情况下双方只能使用 64k 的窗口大小。幸运的是,默认情况下,几乎每个 TCP 堆栈都支持并启用此选项,包括 Linux。
响应程序包括它自己所需的比例因子。两个对等点可以使用不同的号码。宣布比例因子为 0 也是合法的。这意味着对等点应该逐字处理它接收到的接收窗口值,但它允许应答方向上的缩放值,然后接收方可以使用更大的接收窗口。
与 SACK 或 TCP 时间戳不同,窗口缩放选项仅出现在 TCP 连接的前两个数据包中,之后无法更改。也不可能通过查看不包含初始连接三次握手的连接的数据包捕获来确定比例因子。
支持的最大比例因子为 14。这将允许 TCP 窗口的大小高达 1GB。
##### 窗口缩放的缺点
在非常特殊的情况下,它可能导致数据损坏。 在禁用该选项之前——通常情况下是不可能的。 还有一种解决方案可以防止这种情况。不幸的是,有些人在没有意识到与窗口缩放的关系的情况下禁用了该解决方案。 首先,让我们看一下需要解决的实际问题。 想象以下事件序列:
1. 发送方发送段s_1s_2s_3... s_n
2. 接收方看到s_1s_3.. s_n并发送对 s_1 的确认。
3. 发送方认为 s_2 丢失,然后再次发送。 它还发送段 s_n+1 中包含的新数据。
4. 接收方然后看到s_2s_n+1s_2数据包 s_2 被接收两次。
例如,当发送方过早触发重新传输时,可能会发生这种情况。 在正常情况下,即使使用窗口缩放,这种错误的重传也绝不会成为问题。 接收方将只丢弃重复项。
#### 从旧数据到新数据
TCP 序列号最多可以为 4GB。如果它变得大于此值则序列会回绕到 0然后再次增加。这本身不是问题但是如果这种问题发生得足够快则上述情况可能会造成歧义。
如果在正确的时刻发生回绕,则序列号 s_2重新发送的数据包可能已经大于 s_n+1。 因此在最后的步骤4接收器可以将其解释为s_2s_n+1s_n+m即它可以将 **“旧”** 数据包 s_2 视为包含新数据。
通常,这不会发生,因为即使在高带宽链接上,“回绕”也只会每隔几秒钟或几分钟发生一次。原始和不需要的重传之间的间隔将小得多。
例如,对于 50MB/s 的传输速度,副本要延迟到一分钟以上才会成为问题。序列号的包装速度不够快,小的延迟才会导致这个问题。
一旦 TCP 达到 “GB/s” 的吞吐率,序列号的包装速度就会非常快,以至于即使只有几毫秒的延迟也可能会造成 TCP 无法再检测到的重复项。通过解决接收窗口太小的问题TCP 现在可以用于以前无法实现的网络速度,这会产生一个新的,尽管很少见的问题。为了在 RTT 非常低的环境中安全使用 GB/s 的速度,接收方必须能够检测到这些旧副本,而不必仅依赖序列号。
### TCP 时间戳
#### 最佳使用日期。
用最简单的术语来说,[TCP 时间戳][3]只是在数据包上添加时间戳,以解决由非常快速的序列号回绕引起的歧义。 如果一个段看起来包含新数据,但其时间戳早于最后一个在窗口内的数据包,则该序列号已被重新包装,而“新”数据包实际上是一个较旧的副本。 这解决了即使在极端情况下重传的歧义。
但是,该扩展不仅仅是检测旧数据包。 TCP 时间戳的另一个主要功能是更精确的往返时间测量RTTm
#### 需要准确的 RTT 估算
当两个对等方都支持时间戳时,每个 TCP 段都携带两个附加数字:时间戳值和时间戳回显。
```
TCP 时间戳选项 (TSopt): Kind: 8, Length: 10
+-------+----+----------------+-----------------+
|Kind=8 | 10 |TS Value (TSval)|EchoReply (TSecr)|
+-------+----+----------------+-----------------+
    1      1         4                4
```
准确的 RTT 估算对于 TCP 性能至关重要。 TCP 自动重新发送未确认的数据。 重传由计时器触发:如果超时,则 TCP 会将尚未收到确认的一个或多个数据包视为丢失。 然后再发送一次。
但是,“尚未得到确认” 并不意味着该段已丢失。 也有可能是接收方到目前为止没有发送确认,或者确认仍在传输中。 这就造成了一个两难的困境TCP 必须等待足够长的时间,才能让这种轻微的延迟变得无关紧要,但它也不能等待太久。
##### 低网络延迟 VS 高网络延迟
在延迟较高的网络中如果计时器触发过快TCP 经常会将时间和带宽浪费在不必要的重发上。
然而,在延迟较低的网络中,等待太长时间会导致真正发生数据包丢失时吞吐量降低。因此,在低延迟网络中,计时器应该比高延迟网络中更早到期。 所以TCP 重传超时不能使用固定常量值作为超时。它需要根据其在网络中所经历的延迟来调整该值。
##### RTT往返时间的测量
TCP 选择基于预期往返时间RTT的重传超时。 RTT 事先是未知的。它是通过测量发送段与 TCP 接收到该段所承载数据的确认之间的增量来估算的。
由于多种因素使其而变得复杂。
* 出于性能原因TCP 不会为收到的每个数据包生成新的确认。它等待的时间非常短:如果有更多的数据段到达,则可以通过单个 ACK 数据包确认其接收。这称为<ruby>“累积确认”<rt>cumulative ACK</rt></ruby>
* 往返时间并不恒定。 这是有多种因素造成的。例如,客户端可能是一部移动电话,随其移动而切换到不同的基站。也可能是当链路或 CPU 利用率提高时,数据包交换花费了更长的时间。
* 必须重新发送的数据包在计算过程中必须被忽略。
这是因为发送方无法判断重传数据段的 ACK 是在确认原始传输 (毕竟已到达) 还是在确认重传。
最后一点很重要:当 TCP 忙于从丢失中恢复时,它可能仅接收到重传段的 ACK。这样它就无法在此恢复阶段测量更新RTT。所以它无法调整重传超时然后超时将以指数级增长。那是一种非常具体的情况它假设其他机制如快速重传或 SACK 不起作用)。但是,使用 TCP 时间戳,即使在这种情况下也会进行 RTT 评估。
如果使用了扩展,则对等方将从 TCP 段扩展空间中读取时间戳值并将其存储在本地。然后,它将该值放入作为 “时间戳回显” 发回的所有数据段中。
因此,该选项带有两个时间戳:它的发送方自己的时间戳和它从对等方收到的最新时间戳。原始发送方使用“回显时间戳”来计算 RTT。它是当前时间戳时钟与“时间戳回显”中所反映的值之间的增量。
##### 时间戳的其他用用途
TCP 时间戳甚至还有除 PAWS 和 RTT 测量以外的其他用途。例如,可以检测是否不需要重发。如果该确认携带较旧的时间戳回显,则该确认针对的是初始数据包,而不是重新发送的数据包。
TCP 时间戳的另一个更晦涩的用例与 TCP [syn cookie][4] 功能有关。
##### 在服务器端建立 TCP 连接
当连接请求到达的速度快于服务器应用程序可以接受新的传入连接的速度时,连接积压最终将达到其极限。这可能是由于系统配置错误或应用程序中的错误引起的。当一个或多个客户端发送连接请求而不对 “SYN ACK” 响应做出反应时,也会发生这种情况。这将用不完整的连接填充连接队列。这些条目需要几秒钟才会超时。这被称为<ruby>“同步洪水攻击”<rt>syn flood attack</rt></ruby>
##### TCP 时间戳和 TCP Syn Cookie
即使队列已满,某些 TCP 协议栈也允许继续接受新连接。发生这种情况时Linux 内核将在系统日志中打印一条突出的消息:
> P 端口上可能发生 SYN 泛洪。正在发送 Cookie。检查 SNMP 计数器。
此机制将完全绕过连接队列。通常存储在连接队列中的信息被编码到 SYN/ACK 响应 TCP 序列号中。当 ACK 返回时,可以根据序列号重建队列条目。
序列号只有有限的空间来存储信息。 因此,使用 “TCP Syn Cookie” 机制建立的连接不能支持 TCP 选项。
但是,对两个对等点都通用的 TCP 选项可以存储在时间戳中。 ACK 数据包在时间戳回显字段中反映了该值,这也允许恢复已达成共识的 TCP 选项。否则cookie 连接受标准的 64KB 接收窗口限制。
##### 常见误区 —— 时间戳不利于性能
不幸的是,一些指南建议禁用 TCP 时间戳以减少内核访问时间戳时钟来获取当前时间所需的次数。这是不正确的。如前所述RTT 估算是 TCP 的必要部分。因此,内核在接收/发送数据包时总是采用微秒级的时间戳。
在包处理步骤的其余部分中Linux 会重用 RTT 估算所需的时钟时间戳。这还避免了将时间戳添加到传出 TCP 数据包的额外时钟访问。
整个时间戳选项在每个数据包中仅需要 10 个字节的 TCP 选项空间,这并没有显著减少可用于数据包有效负载的空间。
##### 常见误区 —— 时间戳是个安全问题
一些安全审计工具和 (较旧的) 博客文章建议禁用 TCP 时间戳,因为据称它们泄露了系统正常运行时间:这样一来,便可以估算系统/内核的补丁级别。这在过去是正确的:时间戳时钟基于不断增加的值,该值在每次系统引导时都以固定值开始。时间戳值可以估计机器已经运行了多长时间 (正常运行时间)。
从 Linux 4.12 开始TCP 时间戳不再显示正常运行时间。发送的所有时间戳值都使用对等设备特定的偏移量。时间戳值也每 49 天换行一次。
换句话说,从地址 “A” 出发,或者终到地址 “A” 的连接看到的时间戳与到远程地址 “B” 的连接看到的时间戳不同。
运行 _sysctl net.ipv4.tcp_timeamp=2_ 以禁用随机化偏移。这使得分析由诸如 _Wireshark__tcpdump_ 之类的工具记录的数据包跟踪变得更容易 —— 从主机发送的数据包在其 TCP 选项时间戳中都具有相同的时钟基准。因此,对于正常操作,默认设置应保持不变。
### 选择性确认
如果丢失同一数据窗口中的多个数据包TCP 将会出现问题。 这是因为 TCP 确认是累积的,但仅适用于按顺序到达的数据包。例如:
* 发送方发送段 s_1s_2s_3... s_n
* 发送方收到 s_2 的 ACK
* 这意味着 s_1 和 s_2 都已收到,并且发送方不再需要保留这些段。
* s_3 是否应该重新发送? s_4呢 s_n
发送方等待 “重传超时” 或 “重复 ACK” 以使 s_2 到达。如果发生重传超时或到达 s_2 的多个重复 ACK则发送方再次发送 s_3。
如果发送方收到对 s_n 的确认,则 s_3 是唯一丢失的数据包。这是理想的情况。仅发送单个丢失的数据包。
如果发送方收到的确认段小于 s_n例如 s_4则意味着丢失了多个数据包。
发送方也需要重传下一个数据段。
##### 重传策略
可能只是重复相同的序列:重新发送下一个数据包,直到接收方指示它已处理了直至 s_n 的所有数据包为止。这种方法的问题在于,它需要一个 RTT直到发送方知道接下来必须重新发送的数据包为止。尽管这种策略可以避免不必要的重传但要等到 TCP 重新发送整个数据窗口后,它可能要花几秒钟甚至更长的时间。
另一种方法是一次重新发送几个数据包。当丢失了几个数据包时,此方法可使 TCP 恢复更快。在上面的示例中TCP 重新发送了 s_3s_4s_5...,但是只能确保已丢失 s_3。
从延迟的角度来看,这两种策略都不是最佳的。如果只有一个数据包需要重新发送,第一种策略是快速的,但是当多个数据包丢失时,它花费的时间太长。
即使必须重新发送多个数据包,第二个也是快速的,但是以浪费带宽为代价。此外,这样的 TCP 发送方在进行不必要的重传时可能已经发送了新数据。
通过可用信息TCP 无法知道丢失了哪些数据包。这就是 TCP [选择性确认][5]SACK的用武之地了。就像窗口缩放和时间戳一样它是另一个可选的但非常有用的 TCP 特性。
##### SACK 选项
```
   TCP Sack-Permitted Option: Kind: 4, Length 2
   +---------+---------+
   | Kind=4  | Length=2|
   +---------+---------+
```
支持此扩展的发送方在连接请求中包括 “允许 SACK” 选项。如果两个端点都支持扩展,则检测到数据流中丢失数据包的对等点可以将此信息通知发送方。
```
   TCP SACK Option: Kind: 5, Length: Variable
                     +--------+--------+
                     | Kind=5 | Length |
   +--------+--------+--------+--------+
   |      Left Edge of 1st Block       |
   +--------+--------+--------+--------+
   |      Right Edge of 1st Block      |
   +--------+--------+--------+--------+
   |                                   |
   /            . . .                  /
   |                                   |
   +--------+--------+--------+--------+
   |      Left Edge of nth Block       |
   +--------+--------+--------+--------+
   |      Right Edge of nth Block      |
   +--------+--------+--------+--------+
```
接收方遇到 segment_s2 后跟 s_5 ... s_n则在发送对 s_2 的确认时将包括一个 SACK 块:
```
                +--------+-------+
                | Kind=5 |   10  |
+--------+------+--------+-------+
| Left edge: s_5                 |
+--------+--------+-------+------+
| Right edge: s_n                |
+--------+-------+-------+-------+
```
这告诉发送方到 s_2 的段都是按顺序到达的,但也让发送方知道段 s_5 至 s_n 也已收到。 然后,发送方可以重新发送这两个数据包,并继续发送新数据。
##### 神话般的无损网络
从理论上讲,如果连接不会丢包,那么 SACK 就没有任何优势。或者连接具有如此低的延迟,甚至等待一个完整的 RTT 都无关紧要。
在实践中,无损行为几乎是不可能保证的。
即使网络及其所有交换机和路由器具有足够的带宽和缓冲区空间,数据包仍然可能丢失:
* 主机操作系统可能面临内存压力并丢弃数据包。请记住,一台主机可能同时处理数万个数据包流。
* CPU 可能无法足够快地消耗掉来自网络接口的传入数据包。这会导致网络适配器本身中的数据包丢失。
* 如果 TCP 时间戳不可用,即使一个非常小的 RTT 的连接也可能在丢失恢复期间暂时停止。
使用 SACK 不会增加 TCP 数据包的大小,除非连接遇到数据包丢失。因此,几乎没有理由禁用此功能。几乎所有的 TCP 协议栈都支持 SACK —— 它通常只在不进行 TCP 批量数据传输的低功耗 IOT 类似设备上才不存在。
当 Linux 系统接受来自此类设备的连接时TCP 会自动为受影响的连接禁用 SACK。
### 总结
本文中研究的三个 TCP 扩展都与 TCP 性能有关最好都保留其默认设置enabled。
TCP 握手可确保仅使用双方都可以理解的扩展,因此,永远不需因为对等方可能不支持而全局禁用扩展。
关闭这些扩展会导致严重的性能损失,尤其是在 TCP 窗口缩放和 SACK 的情况下。 可以禁用 TCP 时间戳而不会立即造成不利影响,但是现在没有令人信服的理由这样做了。
启用它们还可以支持 TCP 选项,即使在 SYN cookie 生效时也是如此。
--------------------------------------------------------------------------------
via: https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/
作者:[Florian Westphal][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/gxlct008)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://fedoramagazine.org/author/strlen/
[b]: https://github.com/lujun9972
[1]: https://fedoramagazine.org/wp-content/uploads/2020/08/tcp-window-scaling-816x346.png
[2]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure
[3]: https://www.rfc-editor.org/info/rfc7323
[4]: https://en.wikipedia.org/wiki/SYN_cookies
[5]: https://www.rfc-editor.org/info/rfc2018

View File

@ -0,0 +1,284 @@
[#]: collector: (lujun9972)
[#]: translator: (robsean)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Learn the basics of programming with C)
[#]: via: (https://opensource.com/article/20/8/c-programming-cheat-sheet)
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
使用 C 语言学习基本的编程
======
我们将所有的 C 语言要素放置到一份易读的备忘录上。
![备忘录封面图片][1]
在 1972 年,丹尼斯·里奇在贝尔实验室,在几年前,他和他的团队成员发明了 Unix 。在创建了一个经久不衰的操作系统(至今仍在使用)之后,他需要一种好的方法来编程这些 Unix 计算机以便它们可用执行新的任务。在现在看来这很奇怪但在当时编程语言相对较少FortranLisp[Algol][2] 以及 B 语言都很流行,但是,对于贝尔实验室的研究员们想要做的事情来说,它们还是远远不够的。丹尼斯·里奇创造了他自己的解决方案,表现出一种以程序员的主要特征而闻名的特质,他称之为 C 语言,并且在近 50 年后,它仍在广泛的使用。
### 为什么你应该学习 C 语言
今天,这里有很多语言为程序员提供比 C 语言更多是特性。最明显的一种语言是 C++ 语言,一种相当明显的命名方式的语言,它在 C 语言之上构建,创建了一种很好的面向对象的语言。不过,这里有很多其它的语言,它们都很好的存在理由。计算机擅长始终如一的重复,因此任何可预见的东西都足以构建到一种语言中,对程序员来说这意味着更少的工作量。为什么要在 C++ 语言中一行 (`long x = long(n);`) 可以做到的相同的事时,而要花费两行来改写 C 语言中的一个 `int` 为一个 `long` ?
然而C 语言在今天仍然有用。
首先C 语言是一种最小和简单的的语言。这里没有超出编程基础的非常高级的概念,很大程度上是因为 C 语言简直是现代编程语言的基础之一。例如C 语言的特性数组,但是它不提供字典(除非你自己写)。当你学习 C 语言时,你将学习编程的构建语句块,它可以帮助你辨别出当前语言的改善和精心制作的设计构思。
因为 C 语言是一种最小的编程语言,你的应用程序很可能会获得性能上的提升,这在很多其它编程语言中是看不到的。当你考虑你的代码可以执行多快的时候,它很容易被卷入到下面的速度竞赛,因此,询问你是否 _需要_ 更快的速度来完成一项特定的任务是很重要的。与 Python 或 Java 相比,使用 C 语言你没有必要为每一行的代码所困扰。C 语言程序运行很快。这是 Linux 内核使用 C 语言编写的一个很好的理由。
最后C 语言很容易入门,特别是,如果你正在运行 Linux ,你可能已经在运行 C 语言代码,因为 Linux 系统包含 GNU C 库(`glibc`)。为了编写和构建 C 语言程序, 你需要做的全部工作就是安装一个编译器,打开一个文本编辑器,开始编码。
### 开始学习 C 语言
如果你正在运行 Linux ,你可以使用你的软件包管理器安装一个 C 编译器。在 Fedora 或 RHEL 上:
```
$ sudo dnf install gcc
```
在 Debian 及其衍生系统上:
```
$ sudo apt install build-essential
```
在 macOS 上,你可以 [安装 Homebrew][3] ,并使用它来安装 [GCC][4]:
```
`$ brew install gcc`
```
在 Windows 上, 你可以使用 [MinGW][5] 安装一套最小的包含 GCC的 GNU 实用程序集。
在 Linux 或 macOS 上验证你已经安装的 GCC:
```
$ gcc --version
gcc (GCC) x.y.z
Copyright (C) 20XX Free Software Foundation, Inc.
```
在 Windows 上, 提供 EXE 文件的完整路径:
```
PS> C:\MinGW\bin\gcc.exe --version
gcc.exe (MinGW.org GCC Build-2) x.y.z
Copyright (C) 20XX Free Software Foundation, Inc.
```
### C 语法
C 语言不是一种脚本型的语言。它是一种编译型的语言,这意味着它由 C 编译器处理来产生一个二进制可执行文件。这不同于一种脚本型语言(像:[Bash][6] )或一种混合型语言(像:[Python][7] )。
在 C 语言中,你创建 _函数_ 来实施你渴望做到的任务。默认情况下,一个名称为 `main` 的函数将被执行。
这里是一个使用 C 语言写的简单的 "hello world" 程序:
```
#include <stdio.h>
int main() {
printf("Hello world");
return 0;
}
```
第一行包含一个被称为 `stdio.h` (标准输入和输出) 的 _头文件_ ,它基本上是自由的、非常初级的 C 语言代码,你可以在你自己的程序中重复使用。创建一个由一个基本的输出语句构成的名称为 `main` 的函数。保存这些文本到一个被称为 `hello.c` 的文件中,然后使用 GCC 编译它:
```
`$ gcc hello.c --output hello`
```
尝试运行你的 C 语言程序:
```
$ ./hello
Hello world$
```
#### 返回值
一个函数在执行后“返回”一些东西是 Unix 哲学的一部分:在成功时不返回任何东西,在失败使返回其它的一些东西(例如,一个错误信息)。这些返回代码通常使用数字(确切地说是整数)表示: 0 表示没有东西,任何大于 0 的数字都表示一些不成功的状态。
Unix 和 Linux 被设计成在运行成功时要求沉默的是很明智的。这样,你可以总是通过假设在执行一系列命令后没有获得任何错误或警告来期待成功。类似地,在 C 语言中的函数在设计上也不希望出现错误。
你可以使用一小处使你的出现出现错误的修改来自己看到这些设计:
```
include <stdio.h>
int main() {
printf("Hello world");
return 1;
}
```
编译它:
```
$ gcc hello.c --output failer
```
现在使用一个内置的 Linux 测试来运行它。仅在成功时,`&&` 操作符执行一个命令的第二部分。例如:
```
$ echo "success" && echo "it worked"
success
it worked
```
_失败_ 时,`||`测试执行一个命令的第二部分。
```
$ ls blah || echo "it did not work"
ls: cannot access 'blah': No such file or directory
it did not work
```
现在,尝试你的程序,在成功时,它 _不_ 返回 0 ;而是返回 1 :
```
$ ./failer &&; echo "it worked"
String is: hello
```
这个程序成功地执行,但是没有触发第二个命令。
#### 变量和类型
在一些语言中,你可以在不具体指定变量所包含的数据的 _类型_ 的情况下创建变量。这些语言如此设计使得解释器对一个变量运行一些测试来企图发现变量什么样的数据类型。例如Python 知道当你创建一个表达式时,`var=1` 定义了一个整型数,将 `var` 添加一些东西上,这显然是一个整型数。它同样知道当你连接 `hello``world` 时,单词 `world` 是一个字符串。
C 语言不会为你做任何这些识别和调查;你必需定义你自己的变量类型。这里有几种变量类型,包括整型(int),字符型(char),浮点型(float),布尔型(Boolean)。
你可能也注意到这里没有字符串类型。与 Python 和 Java 和 Lua 以及其它的编程语言不同C 语言没有字符串类型,而是将字符串看作一个字符数组。
这里是一些简单的代码,它建立了一个 `char` 数组变量,然后使用 [printf][9] 将数组变量和一段简单的信息打印到你的屏幕上:
```
#include <stdio.h>
int main() {
char var[6] = "hello";
printf("Your string is: %s\r\n",var);
}
```
你可能会注意到,这个代码示例向一个由五个字母组成的单词提供六个字符的空间。这是因为在字符串的结尾有处一个隐藏的终止符,终止符占用数组中的一个字节。你可以通过编译和执行代码来运行它:
```
$ gcc hello.c --output hello
$ ./hello
hello
```
### 函数
和其它的编程语言一样C 函数采取可选函数。你可以通过定义你希望一个函数接受的数据类型来将参数从一个函数传递到另一个函数:
```
#include <stdio.h>
int printmsg(char a[]) {
printf("String is: %s\r\n",a);
}
int main() {
char a[6] = "hello";
printmsg(a);
return 0;
}
```
使用这种方法简单将一个函数分解为两个函数的方法并不是非常有用,但是它证明 `main` 默认运行以及如何在函数之间传递数据。
### 条件语句
在真实的编程中你通常希望你的代码根据数据做出判断。这是使用This is done with _条件_ 语句完成的,并且 `if` 语句是它们中最基础的一个语句。
为了使这个示例程序更具动态性,你可以包含 `string.h` 头文件,它包含用于检查(顾名思义)字符串的代码。尝试使用来自 `string.h` 文件中的 `strlen` 函数测试传递给 `printmsg` 函数的字符串是否大于 0 :
```
#include <stdio.h>
#include <string.h>
int printmsg(char a[]) {
size_t len = strlen(a);
if ( len > 0) {
printf("String is: %s\r\n",a);
}
}
int main() {
char a[6] = "hello";
printmsg(a);
return 1;
}
```
正如在这个示例中所实现的,示例条件永远都不会是真实的,因为所提供的字符串总是 "hello" ,它的长度总是大于 0 。`echo` 命令的这个不够认真的重新实施的最后接触是接受来自用户的输入。
### 命令参数
`stdio.h` 文件包含的代码在每次程序启动时提供两个参数: 一个是包含在命令 (`argc`) 中的项目总数,一个是包含每个项目 (`argv`) 的数组。例如, 假设你发出这个虚构的命令:
```
$ foo -i bar
```
`argc` 是 3 个,`argv` 的内容是:
* `argv[0] = foo`
* `argv[1] = -i`
* `argv[2] = bar`
你可以修改示例 C 语言程序来接受 `argv[2]` 作为字符串,而不是默认来接受`hello` 吗?
### 命令式编程语言
C 语言是一种命令式编程语言。它不是面向对象的,它没有类结构。使用 C 语言的经验可以教你很多关于如何处理数据和如何更好地管理你的代码运行时生成的数据。充分地使用 C 语言,你最后能够编写其它语言 (例如 Python 和 Lua) 能够使用的库。
为了学习更多关于 C 的知识,你需要使用它。在 `/usr/include/` 中查找有用的 C 语言头文件,并且看看你可以做什么小任务来使其对你学习 C 语言有用。 正如你学习时,使用 [Jim Hall][12] 的 FreeDOS 的 [C 语言忘备录][11]。它在一张双面纸忘备录上放置所有的基本要素,因此在你实施练习时,你可以立即访问 C 语言语法的所有要素。
--------------------------------------------------------------------------------
via: https://opensource.com/article/20/8/c-programming-cheat-sheet
作者:[Seth Kenlon][a]
选题:[lujun9972][b]
译者:[robsean](https://github.com/robsean)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/seth
[b]: https://github.com/lujun9972
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/coverimage_cheat_sheet.png?itok=lYkNKieP (Cheat Sheet cover image)
[2]: https://opensource.com/article/20/6/algol68
[3]: https://opensource.com/article/20/6/homebrew-mac
[4]: https://gcc.gnu.org/
[5]: https://opensource.com/article/20/8/gnu-windows-mingw
[6]: https://opensource.com/resources/what-bash
[7]: https://opensource.com/resources/python
[8]: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
[9]: https://opensource.com/article/20/8/printf
[10]: http://www.opengroup.org/onlinepubs/009695399/functions/strlen.html
[11]: https://opensource.com/downloads/c-programming-cheat-sheet
[12]: https://opensource.com/users/jim-hall

View File

@ -1,5 +1,5 @@
[#]: collector: (lujun9972)
[#]: translator: ( )
[#]: translator: (geekpi)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
@ -7,32 +7,32 @@
[#]: via: (https://www.2daygeek.com/linux-remove-delete-physical-volume-pv-from-volume-group-vg-in-lvm/)
[#]: author: (Magesh Maruthamuthu https://www.2daygeek.com/author/magesh/)
How to Remove Physical Volume from a Volume Group in LVM
如何从 LVM 的卷组中删除物理卷?
======
If a device is no longer need for use by LVM, you can use the vgreduce command to remove physical volumes from a volume group.
如果 LVM 不再需要使用某个设备,你可以使用 vgreduce 命令从卷组中删除物理卷。
The Vgreduce command shrinks the capacity of a volume group by removing a physical volume.
vgreduce 命令通过删除物理卷来缩小卷组的容量。
But make sure that the physical volume is not used by any logical volumes using the pvdisplay command.
但要确保物理卷没有被任何逻辑卷使用,请使用 pvdisplay 命令。
If the physical volume is still being used, you must transfer the data to another physical volume using the pvmove command.
如果物理卷仍在使用,你必须使用 pvmove 命令将数据转移到另一个物理卷。
Once the data is moved, it can be removed from the volume group.
数据转移后,它就可以从卷组中删除。
Finally use the pvremove command to remove the LVM label and LVM metadata on the empty physical volume.
最后使用 pvremove 命令删除空物理卷上的 LVM 标签和 LVM 元数据。
* **Part-1: [How to Create/Configure LVM (Logical Volume Management) in Linux][1]**
* **Part-2: [How to Extend/Increase LVMs (Logical Volume Resize) in Linux][2]**
* **Part-3: [How to Reduce/Shrink LVMs (Logical Volume Resize) in Linux][3]**
* **第一部分:[如何在 Linux 中创建/配置 LVM逻辑卷管理][1]**。
* **第二部分:[如何在 Linux 中扩展/增加 LVM 大小(逻辑卷调整)][2]**。
* **第三部分:[如何在 Linux 中减少/缩小 LVM 大小(逻辑卷调整)][3]**。
![][4]
### 1) Moving Extents to Existing Physical Volumes
### 1) 将扩展移动到现有物理卷上
Use the pvs command to check if the desired physical volume (we plan to remove the **“/dev/sdb1”** disk in LVM) is used or not.
使用 pvs 命令检查是否使用了所需的物理卷(我们计划删除 LVM 中的 **“/dev/sdb1”** 磁盘)。
```
# pvs -o+pv_used
@ -43,9 +43,9 @@ PV VG Fmt Attr PSize PFree Used
/dev/sdc1 myvg lvm2 a- 17.15G 12.15G 5.00G
```
If this is used, check to see if there are enough free extents on the other physics volumes in the volume group.
如果使用了,请检查卷组中的其他物理卷是否有足够的空闲空间。
If so, you can run the pvmove command on the device you want to remove. Extents will be distributed to other devices.
如果有的话,你可以在需要删除的设备上运行 pvmove 命令。扩展将被分配到其他设备上。
```
# pvmove /dev/sdb1
@ -57,7 +57,7 @@ If so, you can run the pvmove command on the device you want to remove. Extents
/dev/sdb1: Moved: 100.0%
```
When the pvmove command is complete. Re-use the pvs command to check whether the physics volume is free or not.
当 pvmove 命令完成后。再次使用 pvs 命令检查物理卷是否有空闲。
```
# pvs -o+pv_used
@ -68,25 +68,25 @@ PV VG Fmt Attr PSize PFree Used
/dev/sdc1 myvg lvm2 a- 17.15G 12.15G 5.00G
```
If its free, use the vgreduce command to remove the physical volume /dev/sdb1 from the volume group.
如果它是空闲的,使用 vgreduce 命令从卷组中删除物理卷 /dev/sdb1。
```
# vgreduce myvg /dev/sdb1
Removed "/dev/sdb1" from volume group "myvg"
```
Finally, run the pvremove command to remove the disk from the LVM configuration. Now, the disk is completely removed from the LVM and can be used for other purposes.
最后,运行 pvremove 命令从 LVM 配置中删除磁盘。现在,磁盘已经完全从 LVM 中移除,可以用于其他用途。
```
# pvremove /dev/sdb1
Labels on physical volume "/dev/sdb1" successfully wiped.
```
### 2) Moving Extents to a New Disk
### 2) 移动扩展到新磁盘
If you dont have enough free extents on the other physics volumes in the volume group. Add new physical volume using the steps below.
如果你在卷组中的其他物理卷上没有足够的可用扩展。使用以下步骤添加新的物理卷。
Request new LUNs from the storage team. Once this is allocated, run the following commands to **[discover newly added LUNs or disks in Linux][5]**.
向存储组申请新的 LUN。分配完毕后运行以下命令来**[在 Linux 中发现新添加的 LUN 或磁盘][5]**。
```
# ls /sys/class/scsi_host
@ -101,21 +101,21 @@ host0
# fdisk -l
```
Once the disk is detected in the OS, use the pvcreate command to create the physical volume.
操作系统中检测到磁盘后,使用 pvcreate 命令创建物理卷。
```
# pvcreate /dev/sdd1
Physical volume "/dev/sdd1" successfully created
```
Use the following command to add new physical volume /dev/sdd1 to the existing volume group vg01.
使用以下命令将新的物理卷 /dev/sdd1 添加到现有卷组 vg01 中。
```
# vgextend vg01 /dev/sdd1
Volume group "vg01" successfully extended
```
Now, use the pvs command to see the new disk **“/dev/sdd1”** that you have added.
现在,使用 pvs 命令查看你添加的新磁盘 **“/dev/sdd1”**。
```
# pvs -o+pv_used
@ -127,7 +127,7 @@ PV VG Fmt Attr PSize PFree Used
/dev/sdd1 myvg lvm2 a- 60.00G 60.00G 0
```
Use the pvmove command to move the data from /dev/sdb1 to /dev/sdd1.
使用 pvmove 命令将数据从 /dev/sdb1 移动到 /dev/sdd1。
```
# pvmove /dev/sdb1 /dev/sdd1
@ -139,7 +139,7 @@ Use the pvmove command to move the data from /dev/sdb1 to /dev/sdd1.
/dev/sdb1: Moved: 100.0%
```
After the data is moved to the new disk. Re-use the pvs command to check whether the physics volume is free.
数据移动到新磁盘后。再次使用 pvs 命令检查物理卷是否空闲。
```
# pvs -o+pv_used
@ -151,14 +151,14 @@ PV VG Fmt Attr PSize PFree Used
/dev/sdd1 myvg lvm2 a- 60.00G 10.00G 50.00G
```
If its free, use the vgreduce command to remove the physical volume /dev/sdb1 from the volume group.
如果空闲,使用 vgreduce 命令从卷组中删除物理卷 /dev/sdb1。
```
# vgreduce myvg /dev/sdb1
Removed "/dev/sdb1" from volume group "myvg"
```
Finally, run the pvremove command to remove the disk from the LVM configuration. Now, the disk is completely removed from the LVM and can be used for other purposes.
最后,运行 pvremove 命令从 LVM 配置中删除磁盘。现在,磁盘已经完全从 LVM 中移除,可以用于其他用途。
```
# pvremove /dev/sdb1
@ -171,7 +171,7 @@ via: https://www.2daygeek.com/linux-remove-delete-physical-volume-pv-from-volume
作者:[Magesh Maruthamuthu][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID)
译者:[geekpi](https://github.com/geekpi)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出