24、Git 版本控制与 Linux 实时性优化

Git 版本控制与 Linux 实时性优化

1. Git 文件修改与提交

在使用 Git 进行版本控制时,对文件进行修改并提交是常见操作。具体步骤如下:
1. 修改文件 :在工作目录下双击文件条目,在编辑器中打开文件进行修改。
2. 添加到暂存区 :右键点击修改后的条目,将其添加到暂存区。
3. 提交文件 :右键点击 hello-world 条目,选择 Commit ,会弹出提交对话框。此时, Author Committer 字段会自动填充,同时会打开一个编辑器用于输入提交信息。
4. 查看修改文件 :修改的文件会列在 Author Committer 条目下方,状态中的复选标记表示文件已暂存,工具栏中有全选文件的复选标记图标。
5. 完成提交 :点击 Finish ,提交操作完成并保存。

2. 创建新的 Git 仓库

将现有的 Eclipse 项目纳入 Git 版本控制,可按以下步骤创建新仓库:
1. 选择项目 :在 Project Explorer 中选择项目条目,右键点击并选择 Team -> Share Project ,会弹出配置对话框。
2. 选择仓库位置 :可以选择将仓库放在项目文件夹内,勾选 Use or create repository in parent folder of project ;也可以将其放在项目目录的父目录下,以便在一个 Git 仓库中轻松管理所有项目。
3. 创建仓库 :点击 Create ,会弹出一个对话框要求输入路径和名称,该名称将作为路径下的一个目录,此目录必须为空或不存在, .git/ 目录将存储在此处。
4. 完成项目迁移 :点击 Finish ,项目将被移动到仓库目录。此时在 Project Explorer 视图中,项目条目会显示 [my_projects NO - HEAD] ,表示项目已由 my_projects 仓库管理,但尚未进行初始提交。
5. 初始提交 :右键点击项目,选择 Team -> Commit ,在弹出的提交对话框中选择所有文件,输入提交信息,然后点击 Commit

在开发过程中,大部分工作仍可在 C/C11 视角下完成,Git 对开发过程的干扰较小。当需要进行如管理分支等“类 Git”操作时,需切换到 Git 视角。

3. 实时系统概述

实时系统是指系统计算结果的正确性不仅取决于计算的逻辑正确性,还取决于结果产生的时间。若系统的时间约束未得到满足,则认为系统发生故障。实时并不一定意味着速度极快,而是指速度足够快,更重要的是能可靠地满足速度要求。在实际应用中,这意味着可以为系统响应事件的时间设定一个上限,这个上限被称为延迟。

实时系统可分为硬实时和软实时:
| 实时类型 | 特点 | 示例 |
| ---- | ---- | ---- |
| 硬实时 | 调度截止时间必须每次都严格遵守,错过截止时间意味着系统可能会出现灾难性故障 | 电传飞行控制系统,计算机在飞行员与发动机及控制面之间进行干预,控制算法依赖于精确计时的空速、高度、爬升或下降率等样本。若这些样本延迟,算法可能会变得不稳定,导致飞机坠毁 |
| 软实时 | 偶尔错过截止时间是可以容忍的,只要能维持平均延迟。调度截止时间更像是一个目标而非绝对要求 | 自动取款机网络,当插入银行卡时,期望在几秒内得到响应。设计者设定了例如 5 秒的响应目标,但偶尔延迟几秒也不会造成严重后果,最多只是用户感到恼火 |

4. Linux 不适合实时应用的原因

Linux 最初是按照 Unix 模型设计和构建的通用多用户操作系统,多用户系统的目标通常与实时操作的目标相冲突。通用操作系统为了最大化平均吞吐量,可能会牺牲延迟;而实时操作系统则试图最小化延迟并设定延迟上限,有时会牺牲平均吞吐量。标准 Linux 不适合实时应用的原因主要有以下几点:
- 粗粒度同步 :过去内核系统调用不可抢占,进程进入内核后,直到准备退出内核才能被抢占。不过现在很多内核代码已可抢占,并且抢占是内核的一个配置选项。
- 分页 :虚拟内存中页面的交换过程实际上是无界的,无法确定从磁盘驱动器获取一个页面所需的时间,因此无法为进程因页面错误而延迟的时间设定上限。
- 调度公平性 :传统的 Linux 调度器继承了 Unix 作为多用户分时系统的特性,尽力对所有进程公平。因此,调度器可能会将处理器分配给等待时间较长的低优先级进程,即使有高优先级进程准备运行。不过,多年来调度器已有显著改进,标准 Linux 可被视为软实时系统。
- 请求重排序 :Linux 会对多个进程的 I/O 请求进行重排序,以更高效地利用硬件。例如,为了最小化磁盘磁头移动或提高错误恢复的机会,低优先级进程的硬盘块读取请求可能会优先于高优先级进程的读取请求。
- 批处理 :Linux 会对操作进行批处理以更高效地利用资源。例如,当内存紧张时,Linux 不会一次释放一个页面,而是遍历页面列表,尽可能多地清除页面,这会延迟所有进程的执行。

这些特性导致无法为用户任务或进程可能遇到的延迟设定上限,因此标准 Linux 不符合实时系统的定义。

5. 测量 Linux 延迟:Cyclictest

Cyclictest 是一个用于测量 Linux 系统延迟的工具,其基本操作相对简单,算法如下:

clock_gettime((&now));
next = now + interval;
while (!shutdown)
{
    clock_nanosleep(&next);
    clock_gettime (&now);
    diff = calcdiff (now, next);
    // update stat-> min, max, total latency, cycles
    // update the histogram data
    next += interval;
}

由于 Cyclictest 二进制文件较难找到,需要从源码进行编译,步骤如下:
1. 克隆代码仓库: git clone git://git.kernel.org/pub/scm/linux/kernel/git/clrkwllms/rt-tests.git
2. 进入目录: cd rt-tests
3. 编译: make NUMA = 0
4. 切换到超级用户: su
5. 运行测试: ./cyclictest -S

默认情况下, Cyclictest 需要 libnuma - dev 包,其中 NUMA 代表非统一内存访问。大多数 PC 未实现 NUMA ,因此不需要该库, NUMA = 0 允许在不使用该库的情况下编译 Cyclictest

Cyclictest 默认会在每个处理器核心上以最低优先级运行一个线程。例如,在一个双核笔记本电脑上,只有一个处理器用于运行 CentOS 7 的 Linux 虚拟机(内核版本 3.10.0 - 327.el7.x86_64),运行结果如下:

# /dev/cpu_dma_latency set to 0us
policy: other/other: loadavg: 0.24 0.06 0.06 2/316 6204
T: 0 ( 6204) P: 0 I:1000 C: 10948 Min: 11 Act: 625 Avg: 304 Max: 18988

延迟以微秒为单位报告,该系统的最小延迟为 11 微秒,平均延迟为 304 微秒,最大延迟接近 19 毫秒。可以通过引入人工负载来观察延迟变化,在 samples/src 目录下有 load50/ 目录,其中的 load50.c 是一个简单程序,会派生 50 个自旋进程。可以先在一个 shell 窗口中启动 Cyclictest ,然后在另一个 shell 中运行 load50 ,此时最大延迟会显著增加。

6. 改善 Linux 延迟

可以通过以下方法改善标准 Linux 的延迟:
- 更改调度策略和进程优先级 :默认的调度策略 SCHED_OTHER 使用公平算法,为使用该策略的所有进程分配最低优先级 0,适用于“正常”进程。可选的调度策略有 SCHED_FIFO SCHED_RR ,适用于对时间要求严格、需要低延迟的进程。使用这些调度策略的进程优先级必须大于 0,当这些进程准备好运行时,会抢占任何正在运行的正常进程。 SCHED_FIFO 进程会一直运行,直到阻塞或主动让出处理器; SCHED_RR SCHED_FIFO 的轻微变体,增加了时间片机制,若 SCHED_RR 进程超过其时间片,会被放置到其优先级队列的末尾。 Cyclictest -m 选项用于锁定内存, -p N 选项用于为线程设置更高优先级。例如,在系统负载为 20 的情况下,将 Cyclictest 的优先级设置为 90,最大延迟可降低到约 12 毫秒。
- 锁定内存 :锁定进程的内存映像到 RAM 中,可防止其被换出。不过在某些情况下,锁定内存可能不会改善延迟,这可能意味着系统中没有进行页面交换。

7. 使 Linux 具备确定性的两种方法
7.1 改进内核抢占

一种方法是修改 Linux 内核本身,使其更具响应性,主要是在内核中引入更多的抢占点以减少延迟。可以利用内核中已有的“自旋锁”宏来支持对称多处理(SMP),在 SMP 环境中,自旋锁可防止多个处理器同时在共享内存上执行关键代码段;在单处理器环境中,自旋锁可锁定调度器。

改进内核抢占的策略是将自旋锁转换为优先级继承互斥锁。自 2.4 系列开发后期以来,在内核中引入抢占点的工作一直在进行。如今的主线内核实现了三种不同的抢占模型,可在编译时作为配置参数选择:
| 抢占模型 | 特点 | 适用场景 |
| ---- | ---- | ---- |
| 无强制抢占(服务器) | 传统的 Linux 抢占模型,旨在最大化吞吐量。系统调用返回和中断是唯一的抢占点 | 服务器应用程序 |
| 自愿内核抢占(桌面) | 通过在内核代码中添加更多“显式抢占点”来降低内核延迟,但会略微降低吞吐量。除显式抢占点外,系统调用返回和中断返回仍是隐式抢占点 | 桌面应用程序 |
| 可抢占内核(低延迟桌面) | 通过使不在关键部分执行的所有内核代码可抢占,进一步降低内核延迟。每个禁用抢占的部分之后都有一个隐式抢占点 | 对延迟要求较高的桌面应用程序 |

需要注意的是,这里所说的内核抢占是指进程延迟,而非中断延迟。在标准的单处理器 Linux 内核中,中断延迟约为 10 微秒或更短,具体取决于处理器速度。标准内核的最大进程延迟在几十毫秒,而改进内核抢占策略可将其降低到 1 - 2 毫秒。

这种方法的优点是实时应用程序可以像普通 Linux 应用程序一样在用户空间运行,使用熟悉的 Linux/POSIX API,实时进程与普通进程遵循相同的内存保护规则。但它仍不是硬实时系统,虽然延迟有所降低,但内核中有太多执行路径,无法对确定性进行全面分析。

随着内核抢占的改进,进程调度器多年来也经历了多次重大改进。最初的调度器在 2.4 系列中使用,具有 O(n) 行为,即调度下一个进程所需的时间与就绪队列中的进程数量成正比。2.6 系列引入的调度器具有 O(1) 行为,调度时间与就绪进程的数量无关。内核版本 2.6.23 引入了完全公平调度器(CFS),使用红黑树代替运行队列,是一种 O(log n) 算法,选择下一个要运行的任务是常数时间,但将被抢占的任务重新插入树中需要 log n 时间,CFS 还具有纳秒级的计时粒度。

此外,2.6 内核中还出现了一个配置选项,可关闭虚拟内存,从而避免分页问题。关闭虚拟内存的前提是能够预估系统所需的最大内存量,在嵌入式环境中这通常是可行的。该选项名为“Support for paging of anonymous memory”,在配置菜单的“General setup”中可以找到。

与内核抢占和调度器改进并行进行的还有 PREEMPT_RT 补丁项目,其目标是在 Linux 内核中实现真正的硬实时行为。该补丁的关键在于最小化不可抢占的内核代码量,同时最小化为提供额外可抢占性而需要更改的代码量。 PREEMPT_RT 补丁处理中断的方式也很独特,在标准内核中,中断发生时,中断处理程序会抢占当前运行的任务;而在 PREEMPT_RT 内核中,中断处理程序仅唤醒相应的中断内核线程,该线程由调度器调度,当前运行的任务可能具有比中断线程更高的优先级,因此在中断处理程序执行后可能会继续运行,从原理上提供了更好的调度控制。

综上所述,通过对 Git 版本控制的操作以及对 Linux 实时性的分析和优化,可以更好地进行软件开发和系统性能提升。在实际应用中,可根据具体需求选择合适的方法和工具,以满足不同场景下的要求。

Git 版本控制与 Linux 实时性优化

8. PREEMPT_RT 补丁详解

PREEMPT_RT 补丁致力于在 Linux 内核中实现真正的硬实时行为,其在多个关键方面进行了优化。

8.1 中断处理机制

在标准 Linux 内核中,当中断发生时,中断处理程序会立即抢占当前正在运行的任务,被中断的任务会被暂停,直到中断处理完成。而在 PREEMPT_RT 内核中,中断处理方式有了显著改变。当中断发生时,中断处理程序仅仅是唤醒一个对应的中断内核线程。这个线程由内核的调度器进行调度,这就意味着当前运行的任务如果具有比中断线程更高的优先级,那么在中断处理程序执行完后,该任务可以继续运行,而不会被中断线程立即抢占。这种机制从理论上为调度提供了更好的控制,有助于减少任务被中断的不确定性,从而更接近硬实时系统的要求。

下面通过一个 mermaid 流程图来直观展示这两种中断处理方式的差异:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([中断发生]):::startend --> B{标准内核}:::decision
    B -->|是| C(中断处理程序抢占任务):::process
    C --> D(处理中断):::process
    D --> E(恢复被中断任务):::process
    B -->|否| F{PREEMPT_RT 内核}:::decision
    F -->|是| G(中断处理程序唤醒线程):::process
    G --> H(调度器调度线程):::process
    H --> I{任务优先级高?}:::decision
    I -->|是| J(任务继续运行):::process
    I -->|否| K(线程处理中断):::process
    K --> L(恢复任务):::process
8.2 减少不可抢占代码量

PREEMPT_RT 补丁的一个重要目标是最小化内核中不可抢占的代码量。通过对内核代码的精细调整,使得更多原本不可抢占的部分变得可抢占,从而减少了任务被长时间阻塞的可能性。同时,该补丁还尽量减少了为实现额外可抢占性而需要修改的代码量,这有助于保持内核的稳定性和兼容性。

9. 不同实时场景下的选择策略

在实际应用中,需要根据不同的实时场景来选择合适的方法和工具,以确保系统能够满足特定的实时需求。

9.1 硬实时场景

对于硬实时场景,如电传飞行控制系统,对系统的实时性要求极高,任何延迟都可能导致灾难性的后果。在这种情况下, PREEMPT_RT 补丁是一个比较合适的选择。它通过优化中断处理和减少不可抢占代码量,能够最大程度地降低系统的延迟,提高系统的确定性,从而满足硬实时系统对调度截止时间的严格要求。

9.2 软实时场景

对于软实时场景,如自动取款机网络,偶尔错过调度截止时间是可以接受的,只要能维持平均延迟。在这种情况下,可以考虑使用改进内核抢占的方法。通过选择合适的内核抢占模型,如自愿内核抢占或可抢占内核,能够在一定程度上降低系统的延迟,同时又不会对系统的吞吐量造成太大的影响。此外,还可以通过调整进程的调度策略和优先级,以及锁定内存等方法来进一步优化系统的实时性能。

以下是一个不同实时场景下选择策略的总结表格:
| 实时场景 | 特点 | 推荐方法 |
| ---- | ---- | ---- |
| 硬实时 | 调度截止时间必须严格遵守,错过可能导致灾难性后果 | 使用 PREEMPT_RT 补丁 |
| 软实时 | 偶尔错过截止时间可容忍,需维持平均延迟 | 改进内核抢占,调整调度策略和优先级,锁定内存 |

10. 总结与展望

通过对 Git 版本控制和 Linux 实时性优化的深入探讨,我们了解到了如何有效地管理代码版本以及如何提升 Linux 系统的实时性能。在代码管理方面,Git 提供了强大而灵活的工具,能够帮助开发者更好地协作和管理项目。而在 Linux 实时性优化方面,我们介绍了多种方法,包括改进内核抢占、使用 PREEMPT_RT 补丁、调整调度策略和优先级等,这些方法可以根据不同的实时场景进行选择和组合。

未来,随着技术的不断发展,实时系统的应用场景将越来越广泛,对系统实时性能的要求也将越来越高。在 Linux 内核的开发中,可能会有更多的优化措施和技术出现,以进一步提升系统的实时性和确定性。同时,Git 等版本控制工具也将不断完善,为开发者提供更加便捷和高效的代码管理体验。

总之,掌握 Git 版本控制和 Linux 实时性优化的技术,对于开发者在软件开发和系统性能提升方面具有重要的意义。在实际应用中,我们应该根据具体的需求和场景,灵活运用这些技术,以实现系统的最佳性能和稳定性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值