- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在阅读TCP源码的时候,我发现了一个迷茫的地方:
我知道 TCP 在 3 次握手中有两个队列:
SYN
并发送回ACK + SYN
的连接,我们称之为syn队列。但是在阅读代码时,我发现 listen()
会调用 inet_csk_listen_start()
,这会调用 reqsk_queue_alloc()
来创建 icsk_accept_queue
。而那个队列在accept()
中使用,当我们发现队列不为空时,我们会从中获取一个连接并返回。
此外,跟踪接收过程后,调用堆栈是这样的
tcp_v4_rcv()->tcp_v4_do_rcv()->tcp_rcv_state_process()
第一次握手时服务器状态为LISTEN。所以它会调用
`tcp_v4_conn_request()->tcp_conn_request()`
在tcp_conn_request()
if (!want_cookie)
// Add the req into the queue
inet_csk_reqsk_queue_hash_add(sk, req, tcp_timeout_init((struct sock *)req));
但这里的队列正是 icsk_accept_queue
,而不是 syn 队列。
void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
unsigned long timeout)
{
reqsk_queue_hash_req(req, timeout);
inet_csk_reqsk_queue_added(sk);
}
static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}
accept()
会返回建立的连接,也就是说icsk_accept_queue
是第二个队列,但是第一个队列在哪里呢?
从第一个队列到第二个队列的连接在哪里变化?
为什么 Linux 将新请求添加到 icsk_accept_queue
中?
最佳答案
在下文中,我们将遵循最典型的代码路径,并忽略由丢包、重传和使用 TCP 快速打开(代码注释中的 TFO)等非典型特性引起的问题。
accept 调用由 intet_csk_accept
处理,它调用 reqsk_queue_remove
从接受队列中获取套接字 &icsk->icsk_accept_queue
来自监听套接字:
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
struct request_sock *req;
struct sock *newsk;
int error;
lock_sock(sk);
[...]
req = reqsk_queue_remove(queue, sk);
newsk = req->sk;
[...]
return newsk;
[...]
}
在reqsk_queue_remove
, 它使用 rskq_accept_head
和 rskq_accept_tail
从队列中拉出套接字并调用 sk_acceptq_removed
:
static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue,
struct sock *parent)
{
struct request_sock *req;
spin_lock_bh(&queue->rskq_lock);
req = queue->rskq_accept_head;
if (req) {
sk_acceptq_removed(parent);
WRITE_ONCE(queue->rskq_accept_head, req->dl_next);
if (queue->rskq_accept_head == NULL)
queue->rskq_accept_tail = NULL;
}
spin_unlock_bh(&queue->rskq_lock);
return req;
}
和sk_acceptq_removed
减少等待在 sk_ack_backlog
中接受的套接字队列的长度:
static inline void sk_acceptq_removed(struct sock *sk)
{
WRITE_ONCE(sk->sk_ack_backlog, sk->sk_ack_backlog - 1);
}
我认为,提问者完全理解这一点。现在让我们看看收到 SYN 以及 3WH 的最终 ACK 到达时会发生什么。
首先收到SYN。同样,我们假设 TFO 和 SYN cookie 没有发挥作用,并查看最常见的路径(至少在 SYN 泛滥时不会)。
SYN 在tcp_conn_request
中处理通过调用 inet_csk_reqsk_queue_hash_add
存储连接请求(不是完整的套接字)的位置(我们很快就会看到)然后调用send_synack
响应 SYN:
int tcp_conn_request(struct request_sock_ops *rsk_ops,
const struct tcp_request_sock_ops *af_ops,
struct sock *sk, struct sk_buff *skb)
{
[...]
if (!want_cookie)
inet_csk_reqsk_queue_hash_add(sk, req,
tcp_timeout_init((struct sock *)req));
af_ops->send_synack(sk, dst, &fl, req, &foc,
!want_cookie ? TCP_SYNACK_NORMAL :
TCP_SYNACK_COOKIE);
[...]
return 0;
[...]
}
inet_csk_reqsk_queue_hash_add
电话 reqsk_queue_hash_req
和 inet_csk_reqsk_queue_added
存储请求。
void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
unsigned long timeout)
{
reqsk_queue_hash_req(req, timeout);
inet_csk_reqsk_queue_added(sk);
}
reqsk_queue_hash_req
将请求放入哈希。
static void reqsk_queue_hash_req(struct request_sock *req,
unsigned long timeout)
{
[...]
inet_ehash_insert(req_to_sk(req), NULL);
[...]
}
然后 inet_csk_reqsk_queue_added
电话 reqsk_queue_added
与 icsk_accept_queue
:
static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}
这会增加 qlen
(不是 sk_ack_backlog
):
static inline void reqsk_queue_added(struct request_sock_queue *queue)
{
atomic_inc(&queue->young);
atomic_inc(&queue->qlen);
}
ehash 是存储所有 ESTABLISHED 和 TIMEWAIT 套接字的地方,最近也是存储 SYN“队列”的地方。
请注意,将到达的连接请求存储在适当的队列中实际上没有任何意义。它们的顺序无关紧要(最终的 ACK 可以以任何顺序到达)并且通过将它们移出监听套接字,无需锁定监听套接字来处理最终的 ACK。
参见 this commit对于影响此更改的代码。
最后,我们可以看到请求从 ehash 中删除并作为完整套接字添加到接受队列中。
3WH的最终ACK由tcp_check_req
处理它创建一个完整的子套接字然后调用 inet_csk_complete_hashdance
:
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
bool fastopen, bool *req_stolen)
{
[...]
/* OK, ACK is valid, create big socket and
* feed this segment to it. It will repeat all
* the tests. THIS SEGMENT MUST MOVE SOCKET TO
* ESTABLISHED STATE. If it will be dropped after
* socket is created, wait for troubles.
*/
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL,
req, &own_req);
[...]
return inet_csk_complete_hashdance(sk, child, req, own_req);
[...]
}
然后inet_csk_complete_hashdance
电话 inet_csk_reqsk_queue_drop
和 reqsk_queue_removed
根据要求,和inet_csk_reqsk_queue_add
关于 child :
struct sock *inet_csk_complete_hashdance(struct sock *sk, struct sock *child,
struct request_sock *req, bool own_req)
{
if (own_req) {
inet_csk_reqsk_queue_drop(sk, req);
reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req);
if (inet_csk_reqsk_queue_add(sk, req, child))
return child;
}
[...]
}
inet_csk_reqsk_queue_drop
电话 reqsk_queue_unlink
,它从 ehash 中删除了请求,并且 reqsk_queue_removed
减少 qlen:
void inet_csk_reqsk_queue_drop(struct sock *sk, struct request_sock *req)
{
if (reqsk_queue_unlink(req)) {
reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req);
reqsk_put(req);
}
}
最后,inet_csk_reqsk_queue_add
将完整套接字添加到接受队列。
struct sock *inet_csk_reqsk_queue_add(struct sock *sk,
struct request_sock *req,
struct sock *child)
{
struct request_sock_queue *queue = &inet_csk(sk)->icsk_accept_queue;
spin_lock(&queue->rskq_lock);
if (unlikely(sk->sk_state != TCP_LISTEN)) {
inet_child_forget(sk, req, child);
child = NULL;
} else {
req->sk = child;
req->dl_next = NULL;
if (queue->rskq_accept_head == NULL)
WRITE_ONCE(queue->rskq_accept_head, req);
else
queue->rskq_accept_tail->dl_next = req;
queue->rskq_accept_tail = req;
sk_acceptq_added(sk);
}
spin_unlock(&queue->rskq_lock);
return child;
}
TL;DR 在ehash中,这样的SYN数量是qlen
(而不是 sk_ack_backlog
,它保存接受队列中的套接字数量)。
关于linux - syn队列和accept队列的混淆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63232891/
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,
Linux 管道可以缓冲多少数据?这是可配置的吗? 如果管道的两端在同一个进程中,但线程不同,这会有什么不同吗? 请注意:这个“同一个进程,两个线程”的问题是理论上的边栏,真正的问题是关于缓冲的。 最
我找到了here [最后一页] 一种有趣的通过 Linux 启动 Linux 的方法。不幸的是,它只是被提及,我在网上找不到任何有用的链接。那么有人听说过一种避免引导加载程序而使用 Linux 的方法
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
我试图了解 ld-linux.so 如何在 Linux 上解析对版本化符号的引用。我有以下文件: 测试.c: void f(); int main() { f(); } a.c 和 b.c:
与 RetroPie 的工作原理类似,我可以使用 Linux 应用程序作为我的桌面环境吗?我实际上并不需要像实际桌面和安装应用程序这样的东西。我只需要一种干净简单的方法来在 RaspberryPi 上
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 这个问题似乎不是关于 a specific programming problem, a softwar
关闭。这个问题是off-topic .它目前不接受答案。 想改进这个问题吗? Update the question所以它是on-topic用于堆栈溢出。 关闭 10 年前。 Improve thi
有什么方法可以覆盖现有的源代码,我应该用 PyQt、PyGTK、Java 等从头开始构建吗? 最佳答案 如果您指的是软件本身而不是它所连接的存储库,那么自定义应用程序的方法就是 fork 项目。据我所
我的情况是:我在一个磁盘上安装了两个 linux。我将第一个安装在/dev/sda1 中,然后在/dev/sda2 中安装第二个然后我运行第一个系统,我写了一个脚本来在第一个系统运行时更新它。
我在 i2c-0 总线上使用地址为 0x3f 的系统监视器设备。该设备在设备树中配置有 pmbus 驱动程序。 问题是,加载 linux 内核时,这个“Sysmon”设备没有供电。因此,当我在总线 0
关闭。这个问题是off-topic .它目前不接受答案。 想改进这个问题吗? Update the question所以它是on-topic用于堆栈溢出。 关闭 11 年前。 Improve thi
我正试图在 linux 模块中分配一大块内存,而 kalloc 做不到。 我知道唯一的方法是使用 alloc_bootmem(unsigned long size) 但我只能从 linux 内核而不是
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 这个问题似乎不是关于 a specific programming problem, a softwar
我有 .sh 文件来运行应用程序。在该文件中,我想动态设置服务器名称,而不是每次都配置。 我尝试了以下方法,它在 CentOS 中运行良好。 nohup /voip/java/jdk1.8.0_71/
我是在 Linux 上开发嵌入式 C++ 程序的新手。我有我的 Debian 操作系统,我在其中开发和编译了我的 C++ 项目(一个简单的控制台进程)。 我想将我的应用程序放到另一个 Debian 操
关闭。这个问题需要多问focused 。目前不接受答案。 想要改进此问题吗?更新问题,使其仅关注一个问题 editing this post . 已关闭 4 年前。 Improve this ques
我使用4.19.78版本的稳定内核,我想找到带有企鹅二进制数据的C数组。系统启动时显示。我需要在哪里搜索该内容? 我在 include/linux/linux_logo.h 文件中只找到了一些 Log
我知道可以使用 gdb 的服务器模式远程调试代码,我知道可以调试针对另一种架构交叉编译的代码,但是是否可以更进一步,从远程调试 Linux 应用程序OS X 使用 gdbserver? 最佳答案 当然
是否有任何可能的方法来运行在另一个 Linux 上编译的二进制文件?我知道当然最简单的是在另一台机器上重建它,但假设我们唯一能得到的是一个二进制文件,那么这可能与否? (我知道这可能并不容易,但我只是
我是一名优秀的程序员,十分优秀!