小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。我有一些面试题、架构、设计类资料可以说是程序员面试必备!所有资料都整理到网盘了,需要的话欢迎下载!私信我回复【666】即可免费获取
启动时间的优化,分为两大部分,分别是内核部分和用户空间两大部分。
从内核timestamp 0.000000作为内核启动起点,到free_initmem()输出"Freeing init memory"作为内核启动的终点。
借助于bootgraph.py对内核的kmsg进行分析,输出bootgraph.html和initcall耗时csv文件。
在紧接着free_initmem()下面,是init进程的启动,作为用户空间的起点。内核的终点和用户空间的起点基本上可以任务无缝衔接。
用户空间借助bootchartd抓取/proc/uptime、/proc/stat、/proc/diskstats、/proc/xxx/stat、/proc/meminfo信息,最后打包到bootlog.tgz。
pybootchart.py对bootlog.tgz进行解析,并生成关于CPU占用率、Memory使用情况、磁盘吞吐率以及各进程执行情况的图标。
基于以上内核和用户空间输出,可以发现initcall和进程启动的异常情况。
比如哪个initcall耗时异常;哪个进程启动耗时过长,可以进入进程启动函数查看是否有阻塞等情况。
1. 内核启动优化
在内核源码中自带了一个工具(scripts/bootgraph.pl)用于分析启动时间,这个工具生成output.svg。
但是bootgraph.py生成的结果可读性更好,也更加容易发现问题。
1.1 准备工作
对内核的修改包括,initcall_debug和CONFIG_LOG_BUF_SHIFT。
1.1.1 打开initcall_debug
bool initcall_debug = true;
这样做的目的是在内核kmsg中记录每个initcall的calling和initcall时间,本工具分析依赖于这些kmsg。
static int __init_or_module do_one_initcall_debug(initcall_t fn)
{
ktime_t calltime, delta, rettime;
unsigned long long duration;
int ret;
printk(KERN_DEBUG "calling %pF @ %i\n", fn, task_pid_nr(current));-----------------------initcall开始log
calltime = ktime_get();
ret = fn();
rettime = ktime_get();
delta = ktime_sub(rettime, calltime);
duration = (unsigned long long) ktime_to_ns(delta) >> 10;
printk(KERN_DEBUG "initcall %pF returned %d after %lld usecs\n", fn,
ret, duration);-----------------------------------------------------------------------initcall结束log
return ret;
}
int __init_or_module do_one_initcall(initcall_t fn)
{
int count = preempt_count();
int ret;
if (initcall_debug)
ret = do_one_initcall_debug(fn);
else
ret = fn();
...
}
1.1.2 增大log_buf空间
log_buf用于存放printk消息,他类似于RingBuffer,超出部分会覆盖头部。
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
static char __log_buf[__LOG_BUF_LEN];
static char *log_buf = __log_buf;
所以将CONFIG_LOG_BUF_SHIFT从16增加到18,即log_buf空间从64K增加到256K。
1.1.3 对bootgraph.py的改进
1.1.3.1 划分内核启动的起点终点
界定内核启动的起点很容易,从时间0开始。
用户空间的起点是init进程,所以将内核空间的终点放在启动init进程之前。
这样就可以清晰看到initcall在整个内核初始化中的位置。
static inline int free_area(unsigned long pfn, unsigned long end, char *s)
{
unsigned int pages = 0, size = (end - pfn) << (PAGE_SHIFT - 10);
...
if (size && s)
printk(KERN_INFO "Freeing %s memory: %dK\n", s, size);-------------输出“Freeing init memory:”到kmsg中。
return pages;
}
void free_initmem(void)
{
...
if (!machine_is_integrator() && !machine_is_cintegrator())
totalram_pages += free_area(__phys_to_pfn(__pa(__init_begin)),
__phys_to_pfn(__pa(__init_end)),
"init");
}
static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();------------------------------------------------------------内核空间的终点
...
run_init_process("/sbin/init");--------------------------------------------用户空间的起点
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
...
}
基于“Freeing init memory”对内核和用户空间初始化进行划分,Split kernel and userspace by free_area()。
commit 6195fa73b5522ec5f2461932c894421c30fc3cd7
Author: Arnold Lu <[email protected]>
Date: Tue Jun 19 22:49:09 2018 +0800
Split kernel and userspace by free_area()
diff --git a/bootgraph.py b/bootgraph.py
index 8ee626c..dafe359 100755
--- a/bootgraph.py
+++ b/bootgraph.py
@@ -63,6 +63,7 @@ class SystemValues(aslib.SystemValues):
timeformat = '%.6f'
bootloader = 'grub'
blexec = []
+ last_init=0
def __init__(self):
self.hostname = platform.node()
self.testtime = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
@@ -223,7 +224,7 @@ class Data(aslib.Data):
'kernel': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0,
'order': 0, 'color': 'linear-gradient(to bottom, #fff, #bcf)'},
'user': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0,
- 'order': 1, 'color': '#fff'}
+ 'order': 1, 'color': 'linear-gradient(to bottom, #456, #cde)'}
}
def deviceTopology(self):
return ''
@@ -345,17 +346,18 @@ def parseKernelLog():
m = re.match('^initcall *(?P<f>.*)\+.* returned (?P<r>.*) after (?P<t>.*) usecs', msg)
if(m):
data.valid = True
- data.end = ktime
+ sysvals.last_init = '%.0f'%(ktime*1000)
f, r, t = m.group('f', 'r', 't')
if(f in devtemp):
start, pid = devtemp[f]
data.newAction(phase, f, pid, start, ktime, int(r), int(t))
del devtemp[f]
continue
- if(re.match('^Freeing unused kernel memory.*', msg)):
+ if(re.match('^Freeing init kernel memory.*', msg)):
data.tUserMode = ktime
data.dmesg['kernel']['end'] = ktime
data.dmesg['user']['start'] = ktime
+ data.end = ktime+0.1
phase = 'user'
if tp.stamp:
@@ -531,8 +533,8 @@ def createBootGraph(data):
print('ERROR: No timeline data')
return False
user_mode = '%.0f'%(data.tUserMode*1000)
- last_init = '%.0f'%(tTotal*1000)
- devtl.html += html_timetotal.format(user_mode, last_init)
+ #last_init = '%.0f'%(tTotal*1000)
+ devtl.html += html_timetotal.format(user_mode, sysvals.last_init)
# determine the maximum number of rows we need to draw
devlist = []
1.1.3.2 将每个initcall启动记录到csv
图形化的好处就是直观,但是有时候需要更准确的数据进行排序分析。
这时候生成excel数据,进行处理就很方便了。
增加下面代码会在生成bootgraph.html的同时生成devinit.csv文件,Record data to csv file.。
commit 7bcb705ed30b1e1a0ca3385d01b412f8e6f23b4e
Author: Arnold Lu <[email protected]>
Date: Tue Jun 19 22:52:43 2018 +0800
Record data to csv file.
diff --git a/bootgraph.py b/bootgraph.py
index dafe359..7f43cb7 100755
--- a/bootgraph.py
+++ b/bootgraph.py
@@ -33,6 +33,7 @@ import shutil
from datetime import datetime, timedelta
from subprocess import call, Popen, PIPE
import sleepgraph as aslib
+import csv
# ----------------- CLASSES --------------------
@@ -48,6 +49,7 @@ class SystemValues(aslib.SystemValues):
kernel = ''
dmesgfile = ''
ftracefile = ''
+ csvfile = 'devinit.csv'
htmlfile = 'bootgraph.html'
testdir = ''
kparams = ''
@@ -300,6 +302,9 @@ def parseKernelLog():
lf = open(sysvals.dmesgfile, 'r')
else:
lf = Popen('dmesg', stdout=PIPE).stdout
+ csvfile = open(sysvals.csvfile, 'wb');
+ csvwriter = csv.writer(csvfile)
+ csvwriter.writerow(['Func', 'Start(ms)', 'End(ms)', 'Duration(ms)', 'Return'])
for line in lf:
line = line.replace('\r\n', '')
# grab the stamp and sysinfo
@@ -351,6 +356,7 @@ def parseKernelLog():
if(f in devtemp):
start, pid = devtemp[f]
data.newAction(phase, f, pid, start, ktime, int(r), int(t))
+ csvwriter.writerow([f, start*1000, ktime*1000, float(t)/1000, int(r)]);
del devtemp[f]
continue
if(re.match('^Freeing init kernel memory.*', msg)):
@@ -364,6 +370,7 @@ def parseKernelLog():
sysvals.stamp = 0
tp.parseStamp(data, sysvals)
data.dmesg['user']['end'] = data.end
+ csvfile.close()
lf.close()
return data
1.2 生成测试结果
执行如下命令生成两个文件bootgraph.html和devinit.csv。
bootgraph.py依赖于kmsg中的“calling”/“initcall”识别initcall的起点终点,依赖“Freeing init memory”作为内核启动终点。
./bootgraph.py -dmesg kmsg.txt -addlogs
PS:下面两张截图都覆盖了函数名称。
1.2.1 bootgraph.html分析
从下面的图可以看出内核的初始化持续到2672ms处,然后整个内核初始化主要部分就是initcall。
同时从上面可以看出哪几个initcall占用时间较长,点击可以看到持续多久、是否成功等信息。
1.2.2 devinit.csv分析
相对于bootgraph.html,devinit.csv更容易进行量化。
对devinit.csv按照Duration进行降序,可以看出占用靠前的initcall。
1.3 优化实例
1.3.1 串口log优化
对于115200的串口速率来说,一个字符耗时大概1/(115200/10)=0.087ms。所以100个字符大概耗时8.7ms。
在内核初始化的时候,输出很多串口log是一件恐怖的事情。
虽然不是什么高深的技巧,但是却很有效。
1.3.1.1 初始状态
在没有打开initcall_debug,console_printk采用默认配置情况下,内核启动总共耗时2881ms。
<6>[ 2.881049] Freeing init memory: 340K
1.3.1.2 打开initcall_debug
在打开initcall_debug用于调试之后,引入了额外的开销。
但又不得不借助于initcall_debug来发现问题。
内核启动共耗时3404ms,引入了523ms开销。
关于initcall耗时列表如下:
1.3.1.3 打开initcall_debug,关闭console显示
在关闭了console显示过后,串口被最大化的关闭。
内核共耗时1281ms,相较原始状态减少了1600ms。也就是说整个内核初始化的一大半时间被节省了。
在关闭串口console之后,可以看出initcall的时间大大减少了。
1.3.2 优化耗时top10的initcall
参见上图列表,进入initcall进行优化。
2. 用户空间启动优化
用户空间的优化依赖于bootchartd获取log,然后使用pybootchart.py进行分析。
下面分几部分进行分析:如何在busybox中使能bootchartd;对bootchartd进行简单分析;对pybootchart.py进行简单分析;最后对