לדלג לתוכן

בניית הקרנל ומסלול האתחול

לבחור מערכת שאפשר להסביר עד הסוף

נניח שהמחשב אינו מגיע למעטפת משתמש. ייתכן שמערכת הקבצים חסרה, שקובץ האתחול אינו ניתן להרצה או שהקרנל כלל לא הגיע לשלב שבו הוא יכול לחפש אותו. כדי להבדיל בין המצבים נבנה מערכת קטנה שאנחנו שולטים בכל שכבותיה. נבחר תג מקור קבוע, תצורה שמורה וארכיון אתחול פשוט, ונשמור את יומן המסוף מראשית הריצה.

בפרק נשתמש בתג v6.12 של עץ הקרנל כגרסת ייחוס לימודית קבועה. הבחירה מאפשרת לקשור כל דיון לקוד מסוים, ואינה המלצה להשתמש בגרסת הבסיס הזאת במערכת חשופה. את כל ניסויי הקרנל נריץ במכונה וירטואלית ייעודית, ונאתחל את הקרנל הניסיוני באמצעות אמולטור בלי להחליף את הקרנל שמפעיל את סביבת העבודה.

מקור, תצורה וכלי בנייה

נכין סביבת לינוקס עם כלי בניית קרנל, כלי ארכיון, כלי ניתוח בינאריים ואמולטור qemu-system-x86_64. הדרישות המדויקות נמצאות בקובץ Documentation/process/changes.rst של התג שבחרנו. נרשום את גרסאות המהדר, המקשר והכלים במקום להניח שחבילה בעלת שם מוכר מספקת אותה סביבה בכל הפצה.

git clone --depth 1 --branch v6.12 \
  https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-v6.12
cd linux-v6.12
git rev-parse HEAD
build="$PWD/build-lab"
mkdir "$build"
make O="$build" ARCH=x86_64 x86_64_defconfig
scripts/config --file "$build/.config" \
  -e BLK_DEV_INITRD -e DEVTMPFS -e DEVTMPFS_MOUNT \
  -e PROC_FS -e SYSFS -e TMPFS -e BINFMT_ELF -e BINFMT_SCRIPT \
  -e SERIAL_8250 -e SERIAL_8250_CONSOLE -e MODULES \
  -e DEBUG_KERNEL -e IKCONFIG -e IKCONFIG_PROC \
  -d DEBUG_INFO_NONE -e DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT
make O="$build" ARCH=x86_64 olddefconfig
export KBUILD_BUILD_USER=amit-lab
export KBUILD_BUILD_HOST=lab
export KBUILD_BUILD_TIMESTAMP="$(git show -s --format=%cI HEAD)"
make O="$build" ARCH=x86_64 -j2 bzImage modules

האפשרות O מפרידה את תוצרי הבנייה מהמקור. נשתמש בה בכל פקודת בנייה שייכת לאותה תצורה, משום שהשמטתה יכולה ליצור מצב מעורב של קבצים שנוצרו בשני מקומות. מספר העבודות המקביליות בדוגמה מוגבל כדי להתאים למעבדה קטנה; אפשר לשנותו לפי משאבי המכונה ולתעד את השינוי.

קובצי Kconfig מגדירים אפשרויות ותלויות, וקובצי הבנייה בוחרים אילו רכיבים ייכללו. ערך שהפעלנו ידנית יכול להיעלם אחרי פתרון התלויות אם תנאיו אינם מתקיימים. לכן נבדוק את קובץ התצורה הסופי ונשמור אותו עם התוצרים. כותרות שנוצרו בבנייה משקפות החלטות אלה ואינן קבצים שאפשר להעתיק מקרנל אחר כדי להשתיק שגיאת הידור.

מערכת משתמש מינימלית

נשתמש בבינארי סטטי של busybox כדי לא להוסיף כרגע טוען דינמי וספריות לארכיון האתחול. נבדוק עם file שהבינארי אכן מקושר סטטית. אם ההפצה מספקת גרסה דינמית בלבד, נתקין את הגרסה הסטטית המתאימה או נבנה אותה במפורש לפני המשך הניסוי.

ניצור תיקיית עבודה חדשה בשם rootfs-lab לצד תיקיית המקור. הפקודות הבאות רצות מתוך שורש עץ הקרנל ומשתמשות בתיקייה שאינה קיימת מראש. קובץ ההתקן שנוצר הוא חלק ממערכת האתחול של המעבדה בלבד.

root="$PWD/../rootfs-lab"
mkdir "$root"
mkdir "$root/bin" "$root/dev" "$root/proc" "$root/sys" "$root/tmp"
cp -- "$(command -v busybox)" "$root/bin/busybox"
for app in sh mount cat ls echo poweroff insmod rmmod dmesg; do
    ln -s busybox "$root/bin/$app"
done
sudo mknod "$root/dev/console" c 5 1
sudo chmod 600 "$root/dev/console"

נשמור בקובץ rootfs-lab/init את התוכן הבא ונגדיר אותו כניתן להרצה. התוכנית הראשונה במרחב המשתמש מקבלת תפקיד מיוחד, ולכן אסור שתצא בלי להשאיר מנהל חיים למערכת. כאן המעטפת מחליפה את תהליך האתחול ומשמשת ממשק מעבדה בלבד.

#!/bin/sh
export PATH=/bin
mount -t devtmpfs devtmpfs /dev
mount -t proc proc /proc
mount -t sysfs sysfs /sys
echo "Amit kernel lab reached userspace"
cat /proc/version
exec /bin/sh
chmod 755 "$root/init"
(cd "$root" && find . -print0 | sort -z | \
  cpio --null -o --format=newc --owner=0:0 --reproducible | gzip -n) \
  > "$PWD/initramfs-lab.cpio.gz"

הארכיון מכיל את הקובץ ואת מצב ההרשאות שלו. שכחת הרשאת ביצוע יכולה לגרום לכשל גם כאשר התוכן נכון. האפשרויות באריזה מצמצמות חלק ממקורות השונות, אך חותמות זמן של הקבצים עדיין צריכות להיות מנורמלות אם דורשים זהות בינארית מלאה בין בניות. נשמור גיבובים ונבדוק הבדלים במקום להכריז מראש שהכול שחזורי עד הבית האחרון.

אתחול ישיר ומסוף מוקדם

נריץ את הקרנל באמולטור עם מסוף סדרתי וללא רשת. האתחול הישיר של האמולטור עוקף חלק ממסלול הקושחה ומנהל האתחול של מחשב רגיל, ולכן הניסוי אינו בודק אותם. הוא כן מאפשר להתמקד במעבר מתמונת הקרנל לאתחול מרחב המשתמש.

qemu-system-x86_64 -m 512M -smp 2 -nic none -nographic -no-reboot \
  -kernel "$build/arch/x86/boot/bzImage" \
  -initrd "$PWD/initramfs-lab.cpio.gz" \
  -append 'console=ttyS0 earlycon=uart,io,0x3f8,115200 rdinit=/init panic=-1'

ההודעה שהגענו למרחב המשתמש היא נקודת בדיקה שלנו, ואחריה נצפה למעטפת. נצא מהמעבדה באמצעות פקודת כיבוי מתאימה או רצף הבקרה של האמולטור. סיום מעטפת שהיא התהליך הראשון יכול לגרום להפסקת הקרנל, ולכן הוא עצמו ניסוי בחיי האתחול ולא דרך רגילה לסיים שירות.

במחשב מלא הקושחה מכינה סביבה ראשונית, מנהל האתחול טוען תמונה ופרמטרים, וקוד מוקדם פורס ומכין את הקרנל לפי הארכיטקטורה. אחר כך הקרנל מאתחל תתי־מערכות, יוצר מצב לתהליך הראשון ומפעיל מרחב משתמש. המסלול המדויק תלוי בשיטת האתחול ובתצורה, ולכן נפריד בין ההסבר הכללי לבין המסלול הישיר שנמדד כאן.

לחקור כשל לפני המעטפת

ניצור עותק של ארכיון האתחול שבו הקובץ הראשון אינו ניתן להרצה, או נשנה את פרמטר ההפעלה לנתיב שאינו קיים. נשמור את הארכיון התקין כדי לחזור אליו. ביומן נבדוק מהי ההודעה האחרונה שהתקבלה ואיזה שלב כבר הוכח שהושלם. הודעת כשל בחיפוש תוכנית ראשונה שונה מכשל מוקדם לפני שהמסוף הרגיל זמין.

כאשר בינארי דינמי קיים אבל הטוען שלו חסר, שגיאת הפעלה יכולה להיראות כמו קובץ חסר אף שקובץ ההרצה עצמו נמצא שם. נבדוק את מפרש הבינארי בעזרת readelf -l ואת תוכן הארכיון. לכן בחרנו בבינארי סטטי בתחילת הפרק: צמצמנו משתנים כדי להבין את גבול הכשל, ולא מפני שקישור סטטי הוא פתרון לכל מערכת.

לשמור בסיס להשוואה

נשמור תג ומזהה התחייבות, תצורה סופית, גרסאות כלים, פרמטרי אתחול, ארכיון משתמש וגיבובי תוצרים. כאשר נגיע למודול קרנל, נבנה אותו מול אותו עץ תוצרים ולא מול כותרות של הקרנל המארח. כאשר נשנה אפשרות בדיקה, נשמור תוצר נפרד כדי שאפשר יהיה להשוות בין מערכת תקינה למערכת ניסוי.

מבנה הבנייה מתועד במדריך גרסת הייחוס, ודרישות הכלים נמצאות בתוך אותו עץ מקור. הבסיס הזה מאפשר לנו לקרוא בהמשך מסלול קריאה, הקצאה או פסיקה ולדעת שהקוד שאנו מסבירים הוא הקוד שהמערכת הניסיונית באמת נבנתה ממנו.