1 修改Android mksh默认的列长度
不修改这个参数,adb shell后,输入超过80个字符,就不能看到完整的命令行。
external/mksh/src/sh.h
EXTERN mksh_ari_t x_cols E_INIT(80);
EXTERN mksh_ari_t x_lins E_INIT(24);
2 Kernel优化
2.1 内核驱动模块化
将内核中尽可能多的驱动模块化,写一个负责insmod的shell脚本,开机时作为服务运行,可以大大减少内核启动的时间。同时由于init后是多任务运行,脚本服务对init启动其它服务的延时可以忽略不计。
注意insmod是阻塞调用,所以直接在init.rc脚本中调用,还是会加长启动时间,所以需要insmod的模块统一放到一个脚本服务中。
BoardConfig.mk
BOARD_VENDOR_KERNEL_MODULES
BOARD_RECOVERY_KERNEL_MODULES
2.2 提升console串口的波特率
2.3 printk
2.3.1 实现原理
printk的实现原理很简单,在有了日志消息后,首先申请控制台的信号量,如果申请到,则调用控制台写方法,写控制台。
在内核源码树的kernel/printk.c中,使用宏DECLARE_MUTEX声明了一个互斥锁console_sem,他用于保护console驱动列表console_drivers及同步对整个console驱动系统的访问。其中定义了函数acquire_console_sem来获得互斥锁console_sem,定义了release_console_sem来释放互斥锁console_sem,定义了函数try_acquire_console_sem来尽力得到互斥锁console_sem。这三个函数实际上是分别对函数down,up和down_trylock的简单包装。需要访问console_drivers驱动列表时就需要使用acquire_console_sem来保护console_drivers列表,当访问完该列表后,就调用release_console_sem释放信号量console_sem。函数console_unblank,console_device,console_stop,console_start,register_console 和unregister_console都需要访问console_drivers,因此他们都使用函数对acquire_console_sem和release_console_sem来对console_drivers进行保护。
调试console_sem时,需要打开宏CONFIG_DEBUG_SPINLOCK以跟踪owner字段。
关闭Kernel Log,通过bootchart.png可以看到启动init进程的时间明显提前,可以加快启动速度。
kernel/printk.c
int console_printk[4] = {
DEFAULT_CONSOLE_LOGLEVEL,
DEFAULT_MESSAGE_LOGLEVEL,
MINIMUM_CONSOLE_LOGLEVEL,
DEFAULT_CONSOLE_LOGLEVEL,
};
改为
int console_printk[4] = {
0, //DEFAULT_CONSOLE_LOGLEVEL,
0, //DEFAULT_MESSAGE_LOGLEVEL,
0, //MINIMUM_CONSOLE_LOGLEVEL,
0, //DEFAULT_CONSOLE_LOGLEVEL,
};
这四个值对应到路径proc/sys/kernel/printk,当printk()没有指定消息级别时,就采用DEFAULT_MESSAGE_LOGLEVEL(对应到KERN_WARNING = 4)。
echo "8 8 8 8" > /proc/sys/kernel/printk
- 第一个“8”表示内核打印函数printk的打印级别
2.3.2 pr_debug动态log
CONFIG_DEBUG_FS=y
CONFIG_DYNAMIC_DEBUG=y
echo "file my_drv.c +p" > \
/sys/kernel/debug/dynamic_debug/control
2.3.3 kernel调试时打开所有log
BOARD_KERNEL_CMDLINE += ignore_loglevel
动态修改:
echo 0 > \
/sys/module/printk/parameters/ignore_loglevel
echo 1 > \
/sys/module/printk/parameters/ignore_loglevel
2.4 调试驱动probe耗时
BoardConfig.mk
BOARD_KERNEL_CMDLINE += \
initcall_debug ignore_loglevel
3 Android init进程
3.1 init
stage1: device tree
stage2: fstab
3.2 Sections Loading Sequence
on early-init
wait_for_coldboot_done()
on init
on early-fs
on fs
on post-fs
on post-fs-data
on early-boot
on boot
3.3 init Log机制
无论init代码架构如何变化,init进程的log始终是通过/dev/kmsg输出。
android::base::InitLogging(argv,
&android::base::KernelLogger);
LOG:普通的流式
PLOG:普通的流式,但是可以打印错误,类似于Linux的perror()
3.4 自定义kmsg函数
#include <sys/stat.h>
#include <sys/types.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
static void kmsg(const char *fmt, ...)
{
char buf[512] = {0};
va_list ap;
int n, flags;
static int fd = 0;
va_start(ap,fmt);
n = vsnprintf(buf, 511, fmt, ap);
va_end(ap);
if (fd <= 0) {
fd = open("/dev/kmsg", O_RDWR);
if (fd > 0) {
// 必须加,否则init fork zygote后,
// 该描述符会被zygote继承,
// 导致zygote异常,不断重启
flags = fcntl(fd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(fd, F_SETFD, flags);
}
}
if ((fd > 0) && (n > 0)) {
write(fd, buf, n);
}
}
如果往/dev/kmsg中写log,通过dmesg几乎看不到log,加上如下的配置可以解决该问题。
BoardConfig.mk
BOARD_KERNEL_CMDLINE += \
printk.devkmsg=on
该配置用在如下的代码中:
kernel/printk/printk.c
devkmsg_write()
4 IO
4.1 CPU手动调频
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
Foreground:Tasks run all cores
Background:Tasks run little cores
System-background:Tasks run all little cores for system processes that shouldn’t run on big cores
TOP-APP:Tasks run all cores big cores
4.2 eMMC5.1速度调试
Read my blog “Flash闪存技术”。
4.3 IO调度Tunning
IO调度算法种类:cfq、deadline、noop(No Operation,电梯调度算法)
4.3.1 方法1
@ init.rc
on late-fs
# boot time fs tune for UFS, change sda for eMMC
write /sys/block/sda/queue/iostats 0
write /sys/block/sda/queue/read_ahead_kb 2048
write /sys/block/sda/queue/nr_requests 256
write /sys/block/dm-0/queue/read_ahead_kb 2048
write /sys/block/dm-1/queue/read_ahead_kb 2048
on property:sys.boot_completed=1
write /sys/block/dm-0/queue/read_ahead_kb 512
write /sys/block/dm-1/queue/read_ahead_kb 512
write /sys/block/sda/queue/read_ahead_kb 128
write /sys/block/sda/queue/nr_requests 128
更好的方法是用(与上面的方法互斥):ioprio rt <value>
添加方法如下:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
[...]
# nice的取值范围从-20到+19,-20优先级最高,+19最低
priority -20
# 范围从0到7,数字越小ioprio real-time优先级越高
ioprio rt 2
[...]
Read my blog “Linux Containers知识点”。
4.3.2 方法2
ionice可以用来调整特定进程的ioprio。
0 - none, 1 - Realtime, 2 - Best-effort, 3 - idle
SYS_ioprio_get
SYS_ioprio_set
5 Framework优化
5.1 设置log等级
/data/local.prop
setprop log.tag.<tagname> VERBOSE
setprop persist.log.tag.<tagname> VERBOSE
5.2 Android虚拟按键编码头文件
frameworks/native/include/android/keycodes.h
5.3 Zygote
5.3.1 Preface
frameworks/base/cmds/app_process
frameworks/base/core/jni/AndroidRuntime.cpp - LOG_BOOT_PROGRESS_START
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
当64位zygote启动时,它会从/data/dalvik-cache/arm64/system@framework@boot*.{art,oat,vdex}加载代码,这些被加载的jar包由环境变量BOOTCLASSPATH指定。
测试zygote c++时间:
#include<utils/SystemClock.h>
uptimeMillis()
5.3.2 preloaded-classes
frameworks/base/preloaded-classes
frameworks/base/tools/preload/WritePreloadedClassFile.java
frameworks/base/tools/preload/Compile.java
以上2个文件的修改,需要重新生成preload.jar
mmm frameworks/base/tools/preload/
产生文件out/host/linux-x86/framework/preload.jar
重新生成preloaded-classes文件
rm frameworks/base/preloaded-classes
rm out/target/product/<ppp>/system/etc/preloaded-classes
java -Xss512M -cp out/host/linux-x86/framework/preload.jar WritePreloadedClassFile frameworks/base/tools/preload/20100223.compiled
make systemimage -j8
5.3.3 framework-res.apk
on post-fs-data
# exec [ <seclabel> [ <user> [ <group> ]* ]] -- <command> [ <argument> ]*
exec u:r:init:s0 -- /system/bin/cat /system/framework/framework-res.apk > /dev/null
or
service preload_res /system/bin/cat /system/framework/framework-res.apk > /dev/null
class core
user root
# 加seclabel是为了避免写cat.te文件,可以快速测试,最终提交版本是需要写cat.te的
seclabel u:r:init:s0
oneshot
disabled
on post-fs-data
start preload_res
5.3.4 多线程做preload优化
new Thread(new Runnable() {
@Override
public void run() {
// TODO, call preload();
}
}).start();
5.4 system_server
5.4.1 PMS包扫描
scanDirLI() :scanDir Lock mInstallLock
/system/app
/system/framework
/data/app
/data/app-private
Android 8.0后存储管理类是StorageManagerService.java
5.4.2 Pre-optimization
Android第一次开机后会进行dex2oat操作。
5.4.3 enableScreenAfterBoot
@ ActivityManagerService.java
enableScreenAfterBoot()
- >
@ WindowManagerService.java
enableScreenAfterBoot()
- >
mPolicy.systemBooted() - 在PhoneWindowManager.java(PWM)中
->
performEnableScreen()
- >
checkWaitingForWindows() - 系统将检查目前所有的window是否画完,如果所有的window(包括keyguard、wallpaper和launcher等)都已经画好,系统会设置属性service.bootanim.exit值为1,退出动画。
查看当前top窗口:
findFocusedWindow()
adb shell dumpsys window windows | grep mCurrent
adb shell pm path <package name>
5.5 查看系统安装的所有APK
pm list packages
5.6 settings数据库读写
settings list system
settings put system screen_brightness 50
6 进程调试
6.1 Android性能分析工具汇总
top
free -m
procrank
vmstat 1
pidstat -w 1
mpstat -P ALL 3
iostat
logcat -b events | grep am_crash
logcat | grep died
https://github.com/zhenggaobing/pidstat
6.2 busybox
https://busybox.net/downloads/binaries/1.28.1-defconfig-multiarch/
查看进程树
busybox pstree
show threads
busybox ps -T
busybox top -H
6.3 Linux signal
查看Linux支持的signal:kill -l
6.4 Linux swap分区的使用
/dev/block/zram0
procrank
free -m
6.5 strace zygote
修改init进程
@ system/core/init/service.cpp
static void trace_zygote64(pid_t pid)
{
char pid_str[16] = {0};
if (fork() == 0) {
snprintf(pid_str, 15, "%d", pid);
execl("/system/xbin/strace",
"strace",
"-p", pid_str,
/*"-e", "trace=open,read,write,ioctl",*/
"-o", "/dev/z_tr.log",
"-s", "128",
"-tt", "-T", "-x", NULL);
}
}
将该函数放在创建zygote的地方。
z_tr.log中有具体时间,与logcat -b events | grep boot_progress抓取的log的关键事件时间戳进行对比,找出问题。
6.6 strace监视文件读写
抓取unix domain socket数据的读写 - 类似于tcpdump抓取网络数据包
strace -e read=7 -e write=7 -p 1
PID为1的进程中,dump出所有对fd = 7的读写数据
7 时间测量方法
7.1 bootchart源码下载编译
http://www.bootchart.org/download.html
在Linux桌面机器上:
apt-get install ant
解压下载的bootchart源代码,在bootchart源代码目录下执行ant,结束后,产生bootchart.jar,可以在Linux上分析,也可以将该jar包拷贝到Windows上。
7.2 如何使用bootchart
Android的bootchart时间轴是从kernel启动的时间点开始计算的,这个可以根据生成的bootchart.png和kernel msg得出结论。
echo 1 > /data/bootchart/enabled
重启手机
logs under /data/bootchart
cd /data/bootchart
busybox tar zcvf bootchart.tgz header kernel_pacct proc_diskstats.log proc_ps.log proc_stat.log
adb pull /data/bootchart/bootchart.tgz .
java -jar bootchart.jar .\bootchart.tgz
7.3 比较修改
system/core/init/compare-bootcharts.py
将2次生成的bootchart.tgz分别放到old_dir和new_dir中:
python compare-bootcharts.py old_dir new_dir
7.4 perfboot
system/core/init/perfboot.py
将system/core/init/perfboot.py和development/python-packages/adb文件夹拷贝到同一个目录下。需要注意的是,拷贝的是adb文件夹,不然perfboot.py中的import adb会报错。生成的.tsv文件使用excel打开。
python perfboot.py \
--iterations=2 \
--interval=30 -v \
--output=D:\data.tsv
等价于如下的命令:
adb logcat -b events | grep boot_progress
7.5 kernel启动时间分析
packages/services/Car/tools/bootanalyze/bootanalyze.py
packages/services/Car/tools/bootanalyze/config.yaml
7.6 获取Android各阶段的时间消耗
getprop | grep -i boottime
logcat | grep -i Timing
7.7 Android systrace使用
1) atrace.rc
打开默认关闭的trace开关
in frameworks/native/cmds/atrace/atrace.rc
- write /sys/kernel/debug/tracing/tracing_on 0
+ #write /sys/kernel/debug/tracing/tracing_on 0
2) 附加配置
in device/<OEM>/common/common.mk
PRODUCT_PROPERTY_OVERRIDES += \
debug.atrace.tags.enableflags=802922
in BoardConfig.mk
BOARD_KERNEL_CMDLINE += \
trace_buf_size=64M \
trace_event=sched_wakeup,\
sched_switch,sched_blocked_reason,\
sched_cpu_hotplug,block,ext4
3) 开机完成后结束纪录
项目的init.<PRODUCT>.rc文件加入如下修改,目的是结束trace记录。
on property:sys.boot_completed=1
write /d/tracing/tracing_on 0
write /d/tracing/events/ext4/enable 0
write /d/tracing/events/block/enable 0
4) 抓取log并分析
做完上述修改后编译烧录镜像文件,待开机结束后执行:
adb root
adb shell "cat /d/tracing/trace" > boot_trace
然后执行
python external/chromium-trace/systrace.py \
--from-file=boot_trace \
-o boot_trace.html
上述命令可以将trace log转成html文件,用浏览器打开即可。
8 Abbreviations
bzImage:big zImage
vmlinuz:virtual memory
ABS_MT_POSITION_X:Multi Touch
Android PMS LI、LIF、LPw、LPr:要想弄明白方法名中的LI、LIF、LPw、LPr的含义,需要先了解PackageManagerService内部使用的两个锁。因为LI、LIF、LPw、LPr中的L,指的是Lock,而后面跟的I和P指的是两个锁,I表示mInstallLock同步锁;P表示mPackages同步锁。LPw、LPr中的w表示writing,r表示reading。LIF中的F表示Freeze。
avb:Android Verified Boot,用dm-verify验证system分区的完整性,用在Android 8.0之后的fstab文件中
scanDirLI() :scanDir Lock mInstallLock
APUE:əˈpju,Advanced Programming in the UNIX Environment
AT_FDCWD:File Descriptor Current Working Directory
bail out:跳伞
BLCR:BerkeleyLab Checkpoint/Restart
FRP:Factory Reset Protection
Intercept:API拦截,通信拦截
Linux dd命令:if表示input file,of表示output file,bs表示block size
Linux EPROTO:表示USB bitstuff出现了错误,眼图有问题
lmkd:Low Memory Killer Daemon
lsof:list open files
MIDR:ARM Main ID Register
MPIDR:ARM MultiProcessor ID Register
PPID:Parent Process ID(Linux ps命令可以看到),MFi:Product Plan ID
PuTTY:ˈpʌti
RA:Linux blockdev read ahead
Slog.wtf:what a terrible failure
Android启动时间优化大全
最新推荐文章于 2024-04-09 17:00:06 发布