במסמך הזה מפורטות הנחיות לשותפים לשיפור זמני האתחול במכשירי Android ספציפיים. זמן האתחול הוא רכיב חשוב בביצועי המערכת, כי המשתמשים צריכים להמתין לסיום האתחול כדי שיוכלו להשתמש במכשיר. במכשירים כמו מכוניות שבהם הפעלה מחדש מתרחשת בתדירות גבוהה יותר, חשוב מאוד שהזמן לאתחול יהיה קצר (אף אחד לא אוהב להמתין עשרות שניות רק כדי להזין יעד ניווט).
ב-Android 8.0 יש תמיכה בכמה שיפורים במגוון רכיבים, שמאפשרים לקצר את זמני האתחול. בטבלה הבאה מפורט סיכום של שיפורי הביצועים האלה (כפי שנמדדו במכשירי Google Pixel ו-Pixel XL).
רכיב | שיפור |
---|---|
תוכנת אתחול |
|
הליבה של המכשיר |
|
כוונון קלט/פלט |
|
init.*.rc |
|
אנימציית אתחול |
|
מדיניות SELinux | חיסכון של 0.2 שניות על ידי genfscon |
אופטימיזציה של תוכנת האתחול
כדי לבצע אופטימיזציה של מנהל האתחול כדי לשפר את זמני האתחול:
- ביומן:
- משביתים את כתיבת היומן ב-UART, כי זה יכול לקחת הרבה זמן אם יש הרבה רישום ביומן. (במכשירי Google Pixel, גילינו שהיא מאטה את תוכנת האתחול ב-1.5 שניות).
- כדאי לתעד ביומן רק מצבים של שגיאות, ולשקול לאחסן מידע אחר בזיכרון באמצעות מנגנון נפרד לאחזור.
- לדחיסה של הליבה, כדאי להשתמש ב-LZ4 לחומרה מודרנית במקום ב-GZIP (תיקון לדוגמה). חשוב לזכור שלאפשרויות שונות לדחיסת הליבה יכולים להיות זמני טעינה ודחיסה שונים, ואפשרויות מסוימות עשויות לפעול טוב יותר מאחרות לחומרה הספציפית שלכם.
- בודקים את זמני ההמתנה המיותרים לביטול הרטט או לכניסה למצב מיוחד ומצמצמים אותם.
- העברת זמן האתחול שחלף ב-bootloader לליבת הליבה כ-cmdline.
- כדאי לבדוק את שעון המעבד ולשקול ביצוע פעולות במקביל (נדרשת תמיכה במספר ליבות) לטעינת הליבה ולהפעלת יציאה/קלט.
אופטימיזציה של יעילות הקלט/פלט
שיפור היעילות של הקלט/פלט חיוני כדי לקצר את זמן האתחול, ויש לדחות את הקריאה של כל מה שלא הכרחי עד אחרי האתחול (ב-Google Pixel, קוראים כ-1.2GB של נתונים בזמן האתחול).
כוונון מערכת הקבצים
קריאה מראש של ליבה של Linux מתחילה לפעול כשקובץ נקרא מההתחלה או כשבלוקים נקראים ברצף, ולכן צריך לשנות את הפרמטרים של מתזמן הקלט/פלט במיוחד להפעלה (שיש לה מאפיין עומס עבודה שונה מזה של אפליקציות רגילות).
במכשירים שתומכים בעדכונים חלקים (A/B), כדאי מאוד לבצע התאמה של מערכת הקבצים בהפעלה הראשונה (למשל, 20 שניות ב-Google Pixel). לדוגמה, שינינו את הפרמטרים הבאים עבור Google Pixel:
on late-fs # boot time fs tune # boot time fs tune write /sys/block/sda/queue/iostats 0 write /sys/block/sda/queue/scheduler cfq write /sys/block/sda/queue/iosched/slice_idle 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 # end boot time fs tune write /sys/block/sda/queue/read_ahead_kb 512 ...
שונות
- מפעילים את גודל האחזור מראש של גיבוב dm-verity באמצעות הגדרת הליבה DM_VERITY_HASH_PREFETCH_MIN_SIZE (הגודל שמוגדר כברירת מחדל הוא 128).
- כדי לשפר את היציבות של מערכת הקבצים ולבטל את הבדיקה הכפויה שמתבצעת בכל הפעלה, צריך להשתמש בכלי החדש ליצירת ext4. לשם כך, מגדירים את TARGET_USES_MKE2FS בקובץ BoardConfig.mk.
ניתוח של קלט/פלט
כדי להבין את פעילויות הקלט/פלט במהלך האתחול, משתמשים בנתוני ftrace של הליבה (שמשמש גם את systrace):
trace_event=block,ext4 in BOARD_KERNEL_CMDLINE
כדי לפרט את הגישה לקובץ לכל קובץ, מבצעים את השינויים הבאים בליבה (ליבה לפיתוח בלבד, אין להשתמש בליבות ייצור):
diff --git a/fs/open.c b/fs/open.c index 1651f35..a808093 100644 --- a/fs/open.c +++ b/fs/open.c @@ -981,6 +981,25 @@ } EXPORT_SYMBOL(file_open_root); +static void _trace_do_sys_open(struct file *filp, int flags, int mode, long fd) +{ + char *buf; + char *fname; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return; + fname = d_path(&filp-<f_path, buf, PAGE_SIZE); + + if (IS_ERR(fname)) + goto out; + + trace_printk("%s: open(\"%s\", %d, %d) fd = %ld, inode = %ld\n", + current-<comm, fname, flags, mode, fd, filp-<f_inode-<i_ino); +out: + kfree(buf); +} + long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode) { struct open_flags op; @@ -1003,6 +1022,7 @@ } else { fsnotify_open(f); fd_install(fd, f); + _trace_do_sys_open(f, flags, mode, fd);
תוכלו להשתמש בסקריפטים הבאים כדי לנתח את ביצועי האתחול.
system/extras/boottime_tools/bootanalyze/bootanalyze.py
מדידת זמן האתחול עם פירוט של השלבים החשובים בתהליך האתחול.system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace
מספק את פרטי הגישה לכל קובץ.system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace
מציג פירוט ברמת המערכת.
אופטימיזציה של init.*.rc
Init הוא הגשר מהליבה עד להקמת המסגרת, ובדרך כלל התקנים נמצאים בשלבים שונים של init למשך כמה שניות.
הרצת משימות במקביל
תהליך ה-init הנוכחי של Android הוא פחות או יותר תהליך עם ליבה אחת, אבל עדיין אפשר לבצע משימות מסוימות במקביל.
- ביצוע פקודות איטיות בשירות של סקריפט מעטפת, והצטרפות אליו מאוחר יותר באמצעות המתנה למאפיין ספציפי. ב-Android 8.0 יש תמיכה בתרחיש לדוגמה הזה באמצעות הפקודה החדשה
wait_for_property
. - זיהוי פעולות איטיות ב-init. המערכת מתעדת ביומן את הפקודה init exec/wait_for_prop או כל פעולה שנמשכת זמן רב (ב-Android 8.0, כל פקודה שנמשכת יותר מ-50 אלפיות השנייה). לדוגמה:
init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms
עיון ביומן הזה עשוי להצביע על הזדמנויות לשיפורים.
- כדאי להפעיל שירותים ולהפעיל מכשירים היקפיים בנתיב קריטי מוקדם ככל האפשר. לדוגמה, חלק מ-SOCs דורשים להפעיל שירותים שקשורים לאבטחה לפני שמפעילים את SurfaceFlinger. בודקים את יומן המערכת כש-ServiceManager מחזיר את הערך 'wait for service'. בדרך כלל זה סימן שצריך להפעיל קודם שירות תלוי.
- מסירים את כל הפקודות והשירותים שלא בשימוש בקובץ init.*.rc. כל מה שלא נעשה בו שימוש בשלבים המוקדמים של ה-init צריך להידחות עד להשלמת האתחול.
הערה: שירות הנכסים הוא חלק מתהליך ה-init, כך שקריאה ל-setproperty
במהלך האתחול עלולה לגרום לעיכוב ארוך אם ה-init עסוק בפקודות מובנות.
שימוש בהתאמה של מתזמן הבקשות
שימוש בהתאמה של מתזמן לטעינה מוקדמת. דוגמה מ-Google Pixel:
on init # boottime stune write /dev/stune/schedtune.prefer_idle 1 write /dev/stune/schedtune.boost 100 on property:sys.boot_completed=1 # reset stune write /dev/stune/schedtune.prefer_idle 0 write /dev/stune/schedtune.boost 0 # or just disable EAS during boot on init write /sys/kernel/debug/sched_features NO_ENERGY_AWARE on property:sys.boot_completed=1 write /sys/kernel/debug/sched_features ENERGY_AWARE
יכול להיות שחלק מהשירותים יצטרכו לקבל עדיפות גבוהה יותר במהלך האתחול. דוגמה:
init.zygote64.rc: service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server class main priority -20 user root ...
התחלת zygote מוקדם
במכשירים עם הצפנה מבוססת-קובץ, אפשר להפעיל את zygote מוקדם יותר, בטריגר zygote-start (ברירת המחדל היא הפעלת zygote ב-class main, הרבה מאוחר יותר מ-zygote-start). כשעושים זאת, חשוב לאפשר ל-zygote לפעול בכל מעבדי ה-CPU (כי הגדרה שגויה של cpuset עשויה לאלץ את zygote לפעול במעבדי CPU ספציפיים).
השבתת החיסכון באנרגיה
במהלך האתחול של המכשיר, אפשר להשבית את ההגדרה של חיסכון בצריכת חשמל לרכיבים כמו UFS ו/או ל-CPU.
זהירות: כדי לשפר את היעילות, מומלץ להפעיל את חיסכון הסוללה במצב טעינה.
on init # Disable UFS powersaving write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 0 write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 0 write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 0 write /sys/module/lpm_levels/parameters/sleep_disabled Y on property:sys.boot_completed=1 # Enable UFS powersaving write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1 write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1 write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1 write /sys/module/lpm_levels/parameters/sleep_disabled N on charger # Enable UFS powersaving write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1 write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1 write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1 write /sys/class/typec/port0/port_type sink write /sys/module/lpm_levels/parameters/sleep_disabled N
דחיית אתחול לא קריטי
אפשר לדחות את השלבים הלא קריטיים של האתחול, כמו ZRAM, ל-boot_complete
.
on property:sys.boot_completed=1 # Enable ZRAM on boot_complete swapon_all /vendor/etc/fstab.${ro.hardware}
אופטימיזציה של אנימציית האתחול
הטיפים הבאים יעזרו לכם לבצע אופטימיזציה של אנימציית האתחול.
הגדרת התחלה מוקדמת
ב-Android 8.0 אפשר להפעיל את אנימציית האתחול מוקדם, לפני שמרכבים את המחיצה של נתוני המשתמש. עם זאת, גם כשמשתמשים בשרשרת הכלים החדשה של ext4 ב-Android 8.0, הפקודה fsck עדיין מופעלת מדי פעם מטעמי בטיחות, וכתוצאה מכך יש עיכוב בהפעלת השירות bootanimation.
כדי שהאנימציה של האתחול תתחיל מוקדם, צריך לפצל את הטעינה של fstab לשני שלבים:
- בשלב מוקדם, צריך לטעון רק את המחיצות (כמו
system/
ו-vendor/
) שלא דורשות בדיקות ריצה, ואז להפעיל את שירותי אנימציית האתחול ואת יחסי התלות שלהם (כמו servicemanager ו-surfaceflinger). - בשלב השני, מחברים מחיצות (כמו
data/
) שדורשות בדיקות ריצה.
אנימציית האתחול תתחיל מהר יותר (ובזמן קבוע) ללא קשר ל-fsck.
סיום נקי
אחרי קבלת אות היציאה, החלק האחרון של bootanimation מופעל, והאורך שלו יכול להאט את זמן האתחול. במערכת שמתחילה לפעול במהירות אין צורך באנימציות ארוכות שעלולות להסתיר את השיפורים שבוצעו. מומלץ ליצור לולאה חוזרת וסיום קצרים.
אופטימיזציה של SELinux
הטיפים הבאים יעזרו לכם לבצע אופטימיזציה של SELinux כדי לקצר את זמני האתחול.
- שימוש בביטויים רגולריים (regex) נקיים. ביטוי רגולרי שנוצר בצורה לא טובה יכול להוביל לעלויות רבות כשמתאימים את מדיניות SELinux ל-
sys/devices
ב-file_contexts
. לדוגמה, ביטוי ה-regex/sys/devices/.*abc.*(/.*)?
מאלץ בטעות סריקת כל תיקיות המשנה של/sys/devices
שמכילות את "abc", ומאפשר התאמות גם ל-/sys/devices/abc
וגם ל-/sys/devices/xyz/abc
. שיפור הביטוי הרגולרי הזה ל-/sys/devices/[^/]*abc[^/]*(/.*)?
יאפשר התאמה רק ל-/sys/devices/abc
. - העברת תוויות אל genfscon. התכונה הקיימת הזו של SELinux מעבירה ליבות תואמות לקובץ לליבה בקבצים הבינאריים של SELinux, שבהם הליבה מחילה אותן על מערכות קבצים שנוצרו על ידי הליבה. כך אפשר גם לתקן קבצים שנוצרו בליבה עם תוויות שגויות, וכך למנוע תנאי מרוץ שיכולים להתרחש בין תהליכים במרחב המשתמש שמנסים לגשת לקבצים האלה לפני שמתבצעת הוספת התווית מחדש.
כלים ושיטות
אתם יכולים להשתמש בכלים הבאים כדי לאסוף נתונים ליעדים של האופטימיזציה.
Bootchart
Bootchart מספק פירוט של עומסי המעבד (CPU) והקלט/פלט (I/O) של כל התהליכים במערכת כולה. לא צריך לבנות מחדש את קובץ האימג' של המערכת, וניתן להשתמש בו לבדיקת תקינות מהירה לפני שמתעמקים ב-systrace.
כדי להפעיל את bootchart:
adb shell 'touch /data/bootchart/enabled'
adb reboot
אחרי האתחול, מאחזרים את תרשים האתחול:
$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh
בסיום, מוחקים את /data/bootchart/enabled
כדי למנוע את איסוף הנתונים בכל פעם.
bootchart.png
לא קיים, מבצעים את הפעולות הבאות:
- מריצים את הפקודות הבאות:
sudo apt install python-is-python3
cd ~/Documents
git clone https://github.com/xrmx/bootchart.git
cd bootchart/pybootchartgui
mv main.py.in main.py
- מעדכנים את
$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh
כך שיצביע על העותק המקומי שלpybootchartgui
(שנמצא ב-~/Documents/bootchart/pybootchartgui.py
).
Systrace
Systrace מאפשר לאסוף גם מעקבים של הליבה וגם מעקבים של Android במהלך האתחול. תצוגה חזותית של systrace יכולה לעזור לנתח בעיה ספציפית במהלך ההפעלה. (עם זאת, כדי לבדוק את המספר הממוצע או את המספר המצטבר במהלך כל תהליך האתחול, קל יותר לבדוק את המעקב אחרי הליבה ישירות).
כדי להפעיל את systrace במהלך האתחול:
- בקובץ
frameworks/native/cmds/atrace/atrace.rc
, משנים את הפרטים הבאים:write /sys/kernel/debug/tracing/tracing_on 0 write /sys/kernel/tracing/tracing_on 0
אל:
# write /sys/kernel/debug/tracing/tracing_on 0 # write /sys/kernel/tracing/tracing_on 0
- בקובץ
device.mk
, מוסיפים את השורה הבאה:PRODUCT_PROPERTY_OVERRIDES += debug.atrace.tags.enableflags=802922 PRODUCT_PROPERTY_OVERRIDES += persist.traced.enable=0
- בקובץ
BoardConfig.mk
של המכשיר, מוסיפים את הפרטים הבאים:BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
- בקובץ
init.rc
הספציפי למכשיר, מוסיפים את הפרטים הבאים:on property:sys.boot_completed=1 // This stops tracing on boot complete write /d/tracing/tracing_on 0 write /d/tracing/events/ext4/enable 0 write /d/tracing/events/f2fs/enable 0 write /d/tracing/events/block/enable 0
-
אחרי ההפעלה, מאחזרים את המעקב:
adb root && adb shell atrace --async_stop -z -c -o /data/local/tmp/boot_trace
adb pull /data/local/tmp/boot_trace
$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=boot_trace
הפעולה הזו מפעילה את המעקב (שהוא מושבת כברירת מחדל).
לניתוח מפורט של קלט/פלט, מוסיפים גם את block ו-ext4 ו-f2fs.