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