分享

我是如何学习软件工程的

 技术的游戏 2023-05-24 发布于广东

软件工程是一个庞大且不断演化的领域,涉及许多创新。虽然如此,大多数技术——如果不是全部——在软件工程中往往最终汇聚为几个基本原则。工程师更好地学习软件工程的方法是学习软件工程的基础知识,而不是那些不断变化的框架、语言或平台

即使学习基础知识也需要时间和努力。人们经常问我如何学习软件工程,起初我无法客观地回答这个问题。但最近我开始观察自己的学习过程,我对这个过程有了更清晰的理解。

事实上,我从不强迫自己学习任何东西。如果你一直关注我的工作,你会注意到我很少谈论新的潮流技术,相反,我经常谈论那些已经过时、没有人再讨论的软件工程主题。这不是因为新的东西不好,而是因为我觉得还有更多我不了解的东西,我宁愿追求那些东西。

在这篇文章中,我将带你走过我最近的学习经历,因为这些经历还新鲜在我的脑海中。

Q&Q — 问题和问题

对我来说,学习始于问题。我提出真正的问题,让自己进入探索的道路,在这个过程中,我会遇到一个又一个的障碍,逐个克服。每个问题都会引发另一个问题,直到我达到一个能够统一一切的基础知识。就像一个递归函数达到了其基本条件。

引发问题的原因各不相同,可能是一个学生,或者是工作中的一个随机 bug。不想陷入哲学讨论,但我发现承认自己不知道某个东西对我在学习过程中帮助很大。当一个学生问一个老师不知道的问题时,往往会让老师感到不安。我一直在努力克服这一点,真诚地探索问题,而不是向学生展示我有多么了解。

我还有很多不知道的东西。

接下来的文章将涉及一些技术细节。让我们深入了解。

我所不了解的内容

当后端应用程序监听一个地址和端口时,它可以开始接收来自客户端的请求。请求(无论是 HTTP 还是其他协议)以数据包的形式传输。这就是应用程序如何接收网络数据包的方式。

为简单起见,我在这里省略了用于连接创建/接受的其他队列。这假设连接已经创建好。

以下是流程:

  1. 内核在内核内存中为应用程序创建接收缓冲区。

  2. 内核将传入的网络数据包放入接收队列。

  3. 应用程序从接收缓冲区读取数据包,并将其复制到自己的进程内存中。

  4. 应用程序处理数据(解密、解析协议、触发事件)。

然而,我发现自己对以下两个问题还不太了解:

  1. 网络接口控制器(NIC)是如何将数据传输到内核内存的?

  2. 为什么数据必须先经过内核而不是直接进入进程内存?

接下来的段落中,我将尝试回答这两个问题。

Q1 — NIC 如何将数据包传输到内核

为了回答第一个问题,我必须了解 CPU 的工作原理,在这个过程中我发现了很多东西,这里与我们相关的是中断(interrupt)的概念。为了从任何外部设备(鼠标、键盘、硬盘或 NIC)读取数据,CPU 必须被中断并告知在哪里读取或写入数据。

因此,我将这个知识应用到我的问题上,得出了以下结论:

当 NIC 接收到电信号、光信号或无线电信号(无论是以太网、光纤还是 WIFI/5G),并将其转换为二进制数据存储在其本地缓冲区中时,它会向 CPU 发送中断请求,要求 CPU 停止当前工作并将数据传输到主存中。

CPU 从 NIC 中读取数据,并将其放入缓存行中,然后将缓存行刷新到内存中。但是数据确切地放在内存的哪个位置呢?这就是 NIC 驱动程序(在内核空间运行的软件)告诉 CPU 地址位置的地方。CPU 最终将数据刷新到提供的内存地址中。这个循环一直重复,直到 NIC 中没有数据。然后内核接管处理。

这里花费了很多时间来试图回答更具体的问题,比如 CPU 如何将缓存行刷新到内存?如果其他核心也在读取相同的内存位置会发生什么?但是我暂时跳过这些问题。

这一切对我来说都是有道理的,只有一件事让我感到困惑,这听起来非常冗长。如果我在软件工程中了解了什么,那就是我们尽量避免过度冗长。

虽然中断在小型 I/O(如鼠标移动或键盘按键)中有效,但对于大数据传输,如网络传输、磁盘读取或写入,对 CPU 来说非常耗时。因此,我认为不能仅仅这样处理,肯定还有其他方式。将大量数据放入微小的 CPU 寄存器和缓存中并进行刷新将需要很长时间。于是我进行了更多的搜索,发现了 DMA。

原来这正是为什么发明了 DMA(直接内存访问)的原因。DMA 允许 NIC 直接访问主内存,以便设备可以自行读取和写入数据,从而使 CPU 解放出来。CPU 通过在 DMA 上设置目标内存地址以及应从哪个设备(即 NIC)读取数据来启动传输,所有这些都是根据设备驱动程序的指令。在 NIC 开始将数据直接传输到内存之后,内核/驱动程序就可以在内存中正常处理一次数据。

问题2 — 为什么数据首先进入内核?

但实际上,为什么不直接将数据从 NIC 移动到进程内存呢?从 NIC 直接复制数据到内核,然后再从内核复制到应用程序,这样做的成本肯定是累积的。我发现设备驱动程序在内核中运行,而内核是与 NIC 进行通信的实体,所以数据包的存在于内核中。此外,内核(目前)还不知道将数据放在进程内存的哪个位置,而且它也不知道进程是否准备好读取数据。老实说,我不明白为什么不能通过重新设计 API 来实现这一点。我认为使用 io_uring 可能可以做到这一点,但我还没有探索过。

即使使用 DMA,每个数据包的传输对 DMA 控制器来说也是一种负担。因此,我发现 Intel 提出了一种称为 DMA 合并的想法,其中 NIC 在本地缓冲数据包,然后延迟接收数据包的 DMA 传输。这样可以最小化传输次数,以节省能源,但代价是增加延迟。

在这个探索过程中,我还学到了虚拟内存、转换后备缓冲区、NUMA 架构、上下文切换等等知识。

总结

我刚才所做的被称为“附带知识”。在学习的过程中,除了目标知识,还发现了其他事物。

也许你会说,真的吗?你不知道中断是什么,甚至不知道 DMA 是什么吗?事实上,我确实听说过这些概念,但从来没有在能够真正理解或引起我的兴趣的上下文中接触过它们。

我不知道该如何形容,但是自己发现事物与别人把事物放在盘子里递给你是不同的感觉。

如果你喜欢我的文章,点赞,关注,转发!

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多