class="markdown_views prism-atom-one-light">
嵌入式Linux 系统启动优化的那些事儿
嵌入式Linux 系统优化的那些儿事之系统启动时间的优化方法。。
Printk Times – 用于显示每个 printk 的执行时间
配置
CONFIG_PRINTK_TIME
Kernel hacking –> Show timing information on printks
结果
dmesg > boot.log
[
3.038027] Memory: 3154176k/3325940k available …
[
3.042366] SLUB: Genslabs=13, HWalign=32, Order=0-3, MinObjects=0, CPUs=32, Nodes=1
[
3.050169] Hierarchical RCU implementation.
[
3.050558] RCU-based detection of stalled CPUs is enabled.
[
3.066428] console [ttyS0] enabled, bootconsole disabled
[
3.066965] Calibrating delay loop… 166.40 BogoMIPS (lpj=332800)
分析
scripts/show_delta boot.log | cut -d’ ’ -f2- | sort -k3 -gr
< 3.179683 >] 0000:01:00.0: eth0: (PCI Express:2.5GB/s:Width x1) …
< 1.834118 >] Initializing cgroup subsys cpuset
< 1.555749 >] IP-Config: Complete:
< 0.463878 >] Memory: 3154176k/3325940k available …
< 0.119374 >] Initrd not found or empty - disabling initrd
< 0.111845 >] Serial: 8250/16550 driver, 2 ports, IRQ sharing disabled
避免丢失内核日志: CONFIG_LOG_BUF_SHIFT=21
在printk中打印时间戳:kernel/printk.c: vprintk()
if (printk_time) {
...
t = cpu_clock(printk_cpu);
nanosec_rem = do_div(t, 1000000000);
tlen = sprintf(tbuf, "[%5lu.%06lu] ",
(unsigned long) t,
nanosec_rem / 1000);
...
}
cpu_clock()调用sched_clock()
时间单位us,但有些平台精度只有10ms
默认实现:直接通过jiffies转换
如果HZ=100,jiffies单位为1/HZ, 即10ms
kernel/sched_clock.c: __weak sched_clock()
return (unsigned long long)(jiffies - INITIAL_JIFFIES) * (NSEC_PER_SEC / HZ);
高精度sched_clock():读硬件时钟计数器,如果时钟频率为400M,精度达2.5ns
arch/x86/kernel/tsc.c: native_sched_clock()
/* read the Time Stamp Counter: */
rdtscll(this_offset);
/* return the value in ns */
return __cycles_2_ns(this_offset);
潜在问题:计数器溢出?include/linux/cnt32_to_63.h
相关信息:http://elinux.org/Printk_Times
内核函数跟踪(Ftrace) – 用于报告内核中每个函数的调用时间。详细用法见Ftrace 简介
Linux 跟踪工具箱(LTT) – 用于报告确切的内核和进程事件的时间数据。
Oprofile(译注:最新替代品是 perf) – 通用的 Linux 分析器(Profile) - 详细用法见间Oprofile
Bootchart – 用于 Linux 启动过程的性能分析和数据展示。收集启动过程中的用户空间部分的资源使用情况和进程信息,然后渲染成 PNG、SVG 或者 EPS 格式的图表。
Bootprobe – 一组用于分析系统启动过程的 System Tap 脚本
当然,别忘了 cat /proc/uptime (译注:统计系统已经运行的时间)
grabserial – Tim Bird (译注:CE Linux Forum 主席)写的一个非常赞的工具用于记录控制台输出并打上时间戳
进程跟踪 –- 同样是 Tim Bird 写的一个简单补丁,用于记录 exec、fork 和 exit 系统调用。
ptx_ts – Pengutronix 的时间戳记录器(TimeStamper):一个简单的过滤器,可前置时间戳到标准输出(STDOUT)上,有点像 grabserial 但是不限于串口。
Initcall(内核初始化函数)调试 – 一个用于显示 initcalls 所花时间的内核命令行选项
也可以看下: Kernel 检测工具,里头列举了一些已知的内核检测工具,这些对于测量内核启动时间来说可能会有帮助。
禁用内核的IP地址自动配置
net/ipv4/ipconfig.c: ip_auto_config() 1.58s
/* Wait for devices to appear */
err = wait_for_devices();
if (err)
return err;
/* Setup all network devices */
err = ic_open_devs();
if (err)
return err;
/* Give drivers a chance to settle */
ssleep(CONF_POST_OPEN); /* 1s */
禁用办法
不传递ip参数给内核
禁用内核配置:CONFIG_IP_PNP*
延迟到用户态配置IP地址:/etc/init.d/rcS
将IP配置相关放在启动后交于业务去做这些事情。
思路:分开两个相互依赖的操作,消除不必要的IO等待时间
减少或禁用pseudo终端设备
drivers/tty/pty.c: pty_init() 0.65 s
pseudo终端:用于远程连接(ssh)或者X下的虚拟终端(xterm)
减少设备个数:LEGACY_PTY_COUNT=2
直接禁用:UNIX98_PTYS, LEGACY_PTYS
思路:根据实际需要减少或停用某些功能
关闭控制台输出
打印日志信息到控制台很耗时
控制台设备:VGA、Framebuffer、串口
关闭控制台输出
关闭部分输出: quiet参数, console_loglevel=4
关闭所有输出:loglevel=0,副作用:错误也不显示
禁用控制台设备: e.g. 如果产品基于X11
CONFIG_{SERIAL*,VGA*, FB}=n
完全禁用printk:副作用:不能收集错误日志
CONFIG_{EARLY_PRINTK, PRINTK}=n
e.g. #define panic(…) do { } while (0) or loop or reboot
效果:提速30 ˜ 60%
思路:考虑研发阶段和产品阶段的不同需求
计算loops_per_jiffy
{u,n}delay(): 根据loops_per_jiffy执行相应的nops
loops_per_jiffy: 每个jiffy需要执行的nop次数
1个jiffy = 1/HZ = 10ms (HZ = 100)
tick_periodic() -> do_timer(1) -> jiffies_64 += 1;
loops_per_jiffy计算:init/calibrate.c: calibrate_delay()
最慢:calibrate_delay_converge(): 250ms
最快:启动一次记录下来,下次直接传lpj参数给内核
$ dmesg | grep lpj
… calculated using timer frequency.. (lpj=7181976)
问题:下次启动时处理器主频变了怎么办?
解决办法:实现不基于loops_per_jiffy的{u,n}delay()
新办法:读取硬件计数器直到delay的时长:e.g. delay_tsc()
在delay_tsc()里头设置lpj_fine
思路:1、以静制动;2、转变思维方式寻求突破
采用更快的内核解压算法
减少内核大小
内核(未压缩的)越小,拷贝快,功能可能也少,执行快
减少内核大小的详细方法见《减少系统和程序的大小》
思路:启动速度的影响因素是多方面的
减少或者消除动态探测
基本思想:类似传lpj给内核避免计算loops_per_jiffy
思想扩展
把“不变”参数作为binary,类似DTB,传给内核
把“不变”参数定义成宏等数据,重编译内核,例
如:arch/mips/include/asm/cpu-features.h
可采用的对象
处理器:大部分属性都是固定的,比如tlbsize,
cachesize等feature
PCI:PCI的外设不变的情况下,可把各外设的配置定死
效果:减少Probe过程,如果重编译,还可减少内核大小,
减少大量分支跳转以及由此相应的分支预测失败等
思路:以静制动
统筹考虑整个启动过程
- 一般启动过程:man boot
硬件开机、重启、软件重启(reboot)
BIOS(EFI)、Boot Loader(U-boot)
OS Loader in MBR: Lilo, Grub
装载Linux: cp.b, tftp
启动Linux: boot, bootm
运行Linux: 初始化、内核线程
进入用户态
- 优化后
硬件开机、重启、软件重启(reboot)
完成必要的硬件初始化: X-loader
Kexecboot: 装载、启动、运行Linux并进入用户态
- 切换其他Linux
- 效果:减少若干秒
- 思路:从整体上考虑问题
更多内核启动加速方法
快速重启: 直接装载、启动、运行Linux并进入用户态
Kexec: Documentation/kdump/kdump.txt
reboot=soft (?)
快速分配内存:内存预留(mem, reserve_bootmem)映射(ioremap)后直接使用
优化内存拷贝:DMA方式从Flash拷贝内核到RAM
initcalls优化
串行转并行:异步API: http://lwn.net/Articles/314808/
延迟initcalls到用户态: http://elinux.org/Deferred_Initcalls
全速(或超速)启动、重启
确保处理器在启动过程中全速运行,刚初始化时就设置处理器主频为全速
在处理器启动过程中禁用变频、idle和节能模式
设备驱动特定的优化
Init 进程
SysV init
串行地启动预先配置好的服务
启动下一个时需要等前一个完成
upstart
基于事件驱动,基于系统状态的改变启用和停用相应的任务
e.g. /etc/init/cron.conf
start on runlevel [2345]
stop on runlevel [!2345]
…
exec cron
systemd
基于socket和D-Bus激活来启动服务
按需启动daemons,类似xinetd
追踪Init启动服务过程
在内核态跟踪进程执行
在系统调用kernel/exec.c: sys_exec()入口打印时间戳
可以用scripts/show_delta统计分析
bootchart
统计资源利用率和各个进程的执行情况,导出SVG结果
启动阶段资源利用率越高越好
timechart
统计更多信息,结果更详细,导出SVG结果
tools/perf/builtin-timechart.c
从休眠的映像文件启动内核
休眠接口:/sys/power/state
开发:启动内核和必须的应用,把系统休眠到磁盘或者Flash设备中,产生一个休眠映像文件
echo disk > /sys/power/state
产品:启动内核,直接从休眠映像文件恢复系统
效果:无需重新一个一个地启动程序,只需要恢复到一个早期休眠到内存的系统状态
预读:readahead与tmpfs
readahead
预先读取文件到内存中
减少iowait
用法:sys_readahead(), readahead-list
tmpfs
Documentation/filesystems/tmpfs.txt
tmpfs:内存中的文件系统+no swap
如果内存足够可以考虑把程序预先复制到tmpfs中
使用更快的文件系统
文件系统影响程序的IO操作
Squashfs v.s. CramFS
UBIFS v.s. JFFS2
Reiser4 v.s. Ext3
XFS(mount) v.s. JFS(cpu utilization)
文件系统操作属性:async,noatime,nodirtime,relatime
文件系统性能评测:Dbench, Bonnie++, IOzone, Flexible,IO Tester
使用更小的执行文件
可执行文件更小,启动更快,占用内存更少
ash v.s. bash
busybox
buildroot
编译器优化
优化程序本身
记录程序执行时间: time
$ time find /var/log/ -name “test” > /dev/null
real 0m0.006s
user 0m0.004s
sys 0m0.000s
跟踪程序函数执行开销: gprof
跟踪程序代码执行覆盖情况:gcov
-: 35:int main(int argc, char **argv)
function main called 3 returned 100% blocks executed 60%
3: 36:{
3: 37: if (argc < 2) {
#####: 38: a();
-: 39: } else {
3: 40: b();
-: 41: }
-: 42:}
跟踪Cache miss, branch miss, page fault, tlb miss等:oprofile, perf, valgrind
Cache miss: cacheline对齐。
branch miss: 消除不必要分支,通过gcov把执行多的branch调到前面或者用likely。
page fault: mlock/munlock防止swap出去。
tlb miss: 增加page大小。
优化库函数 Ltrace .
用法:e.g. ltrace -T -f -o ltrace.log ls -l
ltrace跟踪可执行文件调用的系统库文件
pre_load(LD_PRELOAD)优化过后的函数,如memcpy
$ ltrace -c -f ls -l
% time seconds usecs/call calls function
24.88 0.020998 42 491 strlen
10.64 0.008981 41 216 __ctype_get_mb_cur_max
9.02 0.007613 45 166 __overflow
8.75 0.007388 47 156 __errno_location
7.67 0.006472 41 156 memcpy
5.33 0.004497 55 81 strcoll
…
100.00 0.084397 1733 total
优化系统调用: strace .
用法:e.g. strace -T -f -o strace.log ls -l
减少fork/exec,合并程序成applet
优化shell程序:去掉不必要的pipe, pipe也使用fork/exec
减少不必要的系统调用
fast system call: e.g. MIPS syscall指令有预留的指令域,
可以用于实现快速系统调用,比如模拟rdtsc,在用户态读
取硬件时钟计数器
优化内核函数: Ftrace/Kgcov .
Ftrace: Documentation/trace/ftrace.txt
Ftrace可以跟踪内核的函数执行情况
执行程序前后开关Ftrace可追踪程序运行时的内核执行路径
优化跟程序相关的内核路径
Kgcov: Documentation/gcov.txt
内核代码覆盖率测试
lgcov:把测试结果转换成HTML格式方便浏览和分析
内核配置 .
默认关闭所有配置: make allnoconfig
开启一些必须的配置选项: lspci, lsusb…
通过CONFIG_EMBEDDED去掉某些功能: futex?
开启内核和initramfs压缩支持: lzo, lzma, gzip, bzip2, xz
采用支持压缩的文件系统: squashfs, ubifs
去掉内核调试支持: 调试功能、调试符号
去掉模块支持?
-Os: CONFIG_CC_OPTIMIZE_FOR_SIZE
strip -X: CONFIG_STRIP_ASM_SYMS
Linux-Tiny .
目标:致力于降低内核大小和内存开销
下载:http://elinux.org/Linux_Tiny
策略
让更多选项可配置
删除内核消息(printk, BUG, panic, die)
不内联inline函数:性能跟大小折中
内存分配: Slob v.s. slab
减少内存数据结果大小:性能与大小折中
相同功能的简单实现:BFS v.s. CFS
降低内存消耗 .
initramfs v.s initrd
no block
no filesystem
no duplication
strip -x: 删除non-global符号,模块的non-global符号可删除
strip -s: 删除所有符号; strip -S: 删除跟调试相关符号
sstrip(来自buildroot): 删除可执行文件的section table
objcopy -O binary: 仅保留可直接执行的二进制映像
section garbage collection patchset
-ffunction-sections -fdata-sections and -gc-sections
动态probing转静态definition
去掉更多的内核特性
系统调用: ptrace
内核和模块参数支持
让某些宏可配置:NR_IRQS, COMMAND_LINE_SIZE…
多选一:多个重复功能选其中一个,比如emulated
FPU和hardware FPU
减少长调用:-mno-long-calls, 合并内核和模块空间
Tickless Kernel(Dynamic Ticks)
配置:CONFIG_NO_HZ
HZ: 周期性的发出中断以便进行任务调度而支持多任务
NO_HZ: 时钟中断按需发出, Idle时无时钟中断
include/linux/clockchips.h: clock_event_device()
CLOCK_EVT_FEAT_ONESHOT
set_next_event
2.6.24: 支持X86, ARM, MIPS和PowerPC架构
Powertop
监测频繁唤醒系统的内核和应用
提供一些减少功耗的建议
其他节能措施
CPUFreq
处理器支持多级频率支持且可软件调节
自动调节策略:governor用户态可配置
实现驱动:配置可调节频率范围和操作底层寄存器
挂起隐藏的GUI
Suspend: kill -SIGTSTP
Resume: kill -SIGCONT
软件休眠与挂起
休眠到Disk: echo disk > /sys/power/state
挂起到内存:echo mem > /sys/power/state
设备驱动支持:dev_pm_ops: suspend/resume
视频输出控制:video_output子系统
背光控制: backlight子系统
无线射频:rfkill子系统
测试系统响应延迟
yclictest: git://git.kernel.org/pub/scm/linux/kernel/git/clrkwllms/rt-tests.git
更换调度策略
BFS v.s. CFS
低延迟桌面:PREEMPT
中断线程化
降低某些长中断处理的优先级
request_threaded_irq()
调整任务优先级:nice, chrt
绑定任务到处理器:taskset
资源分配:Session cgroup, ulimit
文章引用自吴老师的ppt ;