44、Linux系统调优:提升启动速度的实用技巧

Linux系统调优:提升启动速度的实用技巧

在Linux系统中,优化系统启动时间是提高系统性能和用户体验的关键。本文将介绍一些实用的调优技巧,帮助你减少内核模块加载时间、测量内核启动时间以及降低根文件系统启动时间。

无内核模块

Linux内核模块是在运行时链接到内核的代码。插入模块的过程取决于其大小和内核必须链接的符号数量。对于硬件配置固定的嵌入式系统,可将所有模块直接链接到内核,这样能将加载模块的时间降为零。但对于硬件配置可能变化的系统,直接将驱动添加到内核虽可避免在启动或其他时间加载正确模块,但会增加可能永远不会运行的额外代码。

加载模块所需的时间只是一部分。在加载模块之前,会执行一些代码来决定是否首先加载该模块,这些逻辑通常存在于shell脚本中。shell脚本的执行速度较慢,因为它不仅原生执行速度慢,而且大多数shell脚本为完成任务会频繁调用其他程序。

如果目标系统必须使用模块且启动时间是个问题,可以考虑使用C程序来包含确定需要加载哪些模块的逻辑。这种方法虽不能减少将模块加载到内核所需的时间,但能减少确定应加载哪些模块所需的时间。

测量内核启动时间

以下介绍三种确定内核启动所需时间的工具,有些工具还能测量内核将控制权交给init进程后所消耗的时间。
- /proc/uptime :这是一种看似简单却有效的查看系统启动速度的方法,将 /proc/uptime 的结果输出到控制台。将此作为init脚本的第一行,可得知系统已运行的时间,从而了解启动花费的时间。示例如下:

$ cat /proc/uptime
1.20 0.00

第一列显示机器已运行的秒数,第二列显示空闲时间,即CPU因等待I/O或其他外部输入而处于休眠状态的时间。由于内核启动过程中进程统计的性质,如果作为第一个程序执行,第二个数字为零(或非常接近零)。在执行初始化脚本后打印该信息时,第二个数字能准确报告任务处于休眠状态等待硬件或其他输入的时间。
- Grabserial :该脚本会在串口输出前添加时间戳,让你能看到每个通过 printk 输出的操作所花费的时间,进而将其与特定驱动关联起来,决定是否包含该驱动或延迟其启动。使用此代码要求设备通过串口或类似串口的USB端口输出启动信息。使用步骤如下:
1. 下载必要文件:

$ wget http://elinux.org/images/b/b6/Pyserial-2.2.zip
$ wget http://elinux.org/images/b/bf/Grabserial
$ chmod +x ./Grabserial
2. 安装Python串口库:
$ unzip Pyserial-2.2.zip
$ cd ~/pyserial-2.2
$ sudo python setup.py install
3. 运行Grabserial程序:
./Grabserial -d /dev/ttyS0 -b 115200 -t -e 360

运行该程序后,它会从串口抓取数据,并开始打印带有时间戳信息的数据。例如:

[    0.003957]  
[    0.004153] Loading from NAND 64MiB 3,3V 8-bit, offset 0x60000  
[    0.011949]    Image Name:   Linux-2.6.31  
...

通过对输出结果的简要分析,可发现JFFS2文件系统在启动前扫描卷时存在明显延迟,使用挂载速度更快的文件系统可将启动时间几乎减半。

Grabserial的参数说明如下:
| 参数 | 示例 | 含义 |
| ---- | ---- | ---- |
| -d, –device | -d /dev/ttySAC0 | 指定作为程序输入的串口设备,默认是 /dev/ttyS0 |
| -b, –baudrate | -b 9600 | 指定设备使用的波特率,默认值是115200 |
| -w, –width | -b 7 | 指定每个字符的位数,默认值是8 |
| -p, –parity | -p N | 指定是否包含奇偶校验位,默认值是N |
| -s, –stopbits | -s 1 | 指定停止位的数量,默认是1 |
| -x, -xonxoff | -x | 使用xon/xoff流控制,默认不使用 |
| -r, -rtscts | -r | 使用rts/cts流控制,默认不使用 |
| -t, –time | -t | 打印每行接收到的时间,默认未设置 |
| -m, –match | -M “^Linux Version” | 用于重置时钟的模式,默认表达式为空,无效果 |
| -v, –verbose | -v | 启用详细的程序操作,遇到通信问题时可提供额外的诊断信息 |

以下是使用Grabserial的流程图:

graph TD
    A[下载Pyserial和Grabserial] --> B[安装Python串口库]
    B --> C[运行Grabserial]
    C --> D[分析输出结果]
  • Initcall_debug :在启动时间输出方面, Initcall_debug quiet 相反。通过在内核命令行中添加 initcall_debug ,可以获得类似如下的输出:
initcall init_sunrpc+0x0/0x58 returned 0 after 8145 usecs  
rootcalling  init_rpcsec_gss+0x0/0x4c @ 1  
...

这显示了每个驱动初始化调用及其完成所需的时间。如果内核编译时包含符号信息,会打印函数的符号名称;如果符号被移除(为节省空间),则需要使用 addr2line 程序来解析符号。

在输出结果中,部分行的初始化时间较长,值得关注:

initcall init_sunrpc+0x0/0x58 returned 0 after 8145 usecs
initcall pm_qos_power_init+0x0/0xac returned 0 after 14959 usecs
initcall rtc_hctosys+0x0/0x13c returned 0 after 7436 usecs  

后两项与硬件设备配置(电源管理硬件和实时时钟)相关,由于它们是必需的且时间严格取决于底层硬件,因此无法更改。而第一项是优化的目标,因为它是用于处理NFS连接的代码,在启动时配置自身会消耗大量时间。

降低根文件系统启动时间

在核完成启动后,减少Linux启动并运行所需的时间更为直接。内核挂载根文件系统,然后运行一个负责启动系统中所有其他进程的程序。在大多数标准Linux系统(如服务器和桌面机)中,该程序称为 init ,它会运行多个其他程序来启动系统,此程序设计注重灵活性而非速度。在嵌入式系统中,主要关注点是尽快启动系统,使用户能够尽快使用设备。

以下是一些已被证明有效的技术,可帮助系统尽快启动并运行:
- 使用只读文件系统 :许多有闪存存储的设备用户会选择JFFS2文件系统,若需要对闪存设备进行写入操作,这是个实用的选择。但JFFS2挂载时间很长,因为它在挂载时会对介质的空闲块进行盘点以执行损耗均衡任务。虽有更新来减少此时间,但对于1GB左右的介质,仍需几秒。解决方法是将只读文件系统(如cramfs)用作根文件系统,并在用户程序运行后再挂载JFFS2文件系统。

系统需要一些读写文件系统,如 /tmp /var 。在使用只读文件系统的系统中,这些目录会被创建并用作读写文件系统的挂载点,通常是内存中的文件系统,如tmpfs。系统启动时,初始化脚本会挂载在只读目录上。这样做的代价是设备的部分RAM会被用于临时文件,可供应用程序使用的内存会减少。

创建临时文件系统时,关键是限制其大小,以免占用系统的所有可用内存。推荐使用tmpfs文件系统作为基于RAM的临时驱动器,因为可以限制其占用的空间。例如,在 /etc/fstab 文件中添加以下行,可在 /tmp 挂载一个大小限制为512KB的tmpfs文件系统:

tmpfs           /tmp           tmpfs    defaults,size=512k 0      0

对于大多数嵌入式系统,用户可用于写入数据的空间相对于根文件系统较小,根文件系统实际上是只读的,因为用户不应覆盖运行设备的代码。由于JFFS2的挂载时间与介质大小成正比,挂载较小的读写区域也意味着更快的挂载时间。

可以通过以下命令将根文件系统更改为只读:

# mount -n -o remount,ro /

运行应用程序,查看哪些部分会出现问题。需要注意的是,创建只读文件系统比读写文件系统需要更多的规划,因为习惯了可以在任何地方写入数据。此外,更新只读根文件系统需要重建和重新刷新整个映像,这是一个耗时的过程,会使实验变得困难。在之前的启动代码中,根文件系统的挂载时间接近六秒,而使用只读文件系统,相同的卷在不到半秒内即可挂载并准备好使用。
- 替换初始化脚本 :系统初始化不一定需要脚本。为实现最快的启动时间,可以编写一个程序,通过 fork()/exec() 执行启动服务所需的命令,而不是依赖脚本来完成此任务。这是配置和启动系统最快的方法,但需要最多的工程工作。

如果必须使用脚本,可以将系统中的所有初始化脚本合并为一个大脚本,并去除冗余代码。这样可以避免每次执行脚本时都加载shell。在脚本开始时进行检查,可避免每个脚本加载时都重复进行这些检查。

以下是一个包含在buildroot中的初始化脚本示例:

for i in /etc/init.d/S??* ;do
    # 忽略悬空符号链接(如果有)。
    [ ! -f "$i" ] && continue
    case "$i" in
       *.sh)
            # 为了速度,源化shell脚本。
            (
                trap - INT QUIT TSTP
                set start
               . $i
            )
            ;;
        *)
            # 没有.sh扩展名,所以派生子进程。
            $i start
            ;;
    esac
done

该脚本的伪代码如下:
1. 找到 /etc/init.d 中所有以S开头且至少有两个字符的目录项。 for 语句根据掩码 S?? 获取要执行的文件列表,shell按排序顺序返回文件列表,因此 S01foo 会在 S02bar 之前处理。
2. 如果目录项对应的文件不存在,则跳过处理,继续处理下一个文件。可能目录项是一个不指向文件系统中文件的符号链接,检查 [ ! -f "$i" ] && continue 可确保文件存在,若不存在则处理列表中的下一个文件。在生产系统中,由于创建文件系统时工程师已确保 /etc/init.d 目录中的所有文件都存在,因此可以跳过此步骤。
3. 如果文件以 .sh 结尾,则假设它是一个脚本并运行它。以 .sh 结尾的文件被视为shell脚本,使用 . 命令( source 关键字的快捷方式)将其读入当前脚本并执行。通过将代码读入当前脚本,系统无需运行另一个shell实例来执行脚本,从而节省时间和内存。
4. 否则,使用参数 start 执行该文件。此时,脚本认为目录中的程序是可执行文件,因此使用参数 start 运行它。
5. 移动到下一个要处理的文件,返回步骤2。当前程序运行完成后,代码返回到 for 循环的顶部,处理列表中的下一个文件,此循环将继续,直到没有更多文件需要处理。

这是一个非常精简的启动脚本,比桌面系统上的标准初始化脚本更高效,但比将所有脚本合并为一个文件并一次性执行的效率要低。在总是运行相同脚本的系统中,无需扫描目录以获取脚本列表,因为该列表始终相同。此操作还会促使你逐个检查脚本,以消除不必要的操作。

以启动Dropbear ssh服务器的脚本为例,该脚本有56行,主要完成以下操作:
1. 检查 /etc/dropbear 目录是否存在。
2. 检查RSA和DSA密钥是否存在,若不存在则生成它们。
3. 输出几个状态消息。
4. 启动Dropbear。

通过上述方法,可以有效优化Linux系统的启动时间,提升系统性能和用户体验。希望这些技巧能帮助你更好地管理和优化系统。

Linux系统调优:提升启动速度的实用技巧

优化总结与建议

为了更清晰地展示上述各项优化措施,我们将其总结成如下表格,方便对比和参考:
| 优化方向 | 具体方法 | 优点 | 缺点 |
| ---- | ---- | ---- | ---- |
| 内核模块 | 对于固定硬件配置系统,将所有模块直接链接到内核;使用C程序确定加载模块逻辑 | 减少模块加载和判断时间 | 增加可能不运行的代码;需编写C程序 |
| 内核启动时间测量 | 使用 /proc/uptime Grabserial Initcall_debug 工具 | 了解启动耗时分布,定位慢的环节 | 部分工具使用有条件限制 |
| 根文件系统启动时间 | 使用只读文件系统(如cramfs),使用tmpfs作为读写挂载点;替换或合并初始化脚本 | 加快挂载速度,减少脚本执行开销 | 创建只读系统需更多规划,更新困难;合并脚本需检查代码 |

根据上述总结,以下是一些综合的优化建议流程图:

graph TD
    A[开始优化] --> B{是否固定硬件配置}
    B -- 是 --> C[将模块直接链接到内核]
    B -- 否 --> D[用C程序确定加载模块逻辑]
    C --> E[测量内核启动时间]
    D --> E
    E --> F{是否有慢的环节}
    F -- 是 --> G[针对性优化慢环节]
    F -- 否 --> H[优化根文件系统启动时间]
    G --> H
    H --> I{是否使用JFFS2文件系统}
    I -- 是 --> J[使用只读文件系统和tmpfs]
    I -- 否 --> K{是否使用多个初始化脚本}
    J --> K
    K -- 是 --> L[合并初始化脚本]
    K -- 否 --> M[完成优化]
    L --> M
实际案例分析

假设我们有一个嵌入式Linux设备,使用JFFS2作为根文件系统,启动时间较长。我们按照上述优化步骤进行操作:
1. 内核模块优化 :由于该设备硬件配置固定,我们将所有内核模块直接链接到内核,减少了模块加载的时间开销。
2. 内核启动时间测量 :使用 Grabserial 工具,发现JFFS2文件系统挂载时间过长,接近6秒。
3. 根文件系统启动时间优化
- 将根文件系统更换为只读的cramfs,同时在 /etc/fstab 中添加如下配置,使用tmpfs作为 /tmp 的挂载点:

tmpfs           /tmp           tmpfs    defaults,size=512k 0      0
- 合并系统中的初始化脚本,去除冗余代码,避免每次执行脚本时都加载shell。

经过上述优化后,再次使用 Grabserial 测量启动时间,发现根文件系统的挂载时间从原来的接近6秒缩短到不到半秒,整个系统的启动时间也有了显著的提升。

注意事项

在进行系统调优时,还需要注意以下几点:
- 数据备份 :在对根文件系统等进行修改之前,务必备份重要的数据,以免因操作失误导致数据丢失。
- 兼容性 :更换文件系统或修改内核配置时,要确保新的设置与系统中的硬件和软件兼容,避免出现系统无法正常启动的问题。
- 测试验证 :每次进行优化操作后,都要进行充分的测试,确保系统的各项功能正常运行,同时验证优化效果是否符合预期。

通过合理运用上述优化技巧,结合实际情况进行调整和测试,你可以有效地提升Linux系统的启动速度,让系统更加高效地运行。希望这些内容能对你的系统调优工作有所帮助。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值