מערכת Android מספקת הטמעה לדוגמה של כל הרכיבים שנדרשים להטמעה של Android Virtualization Framework. בשלב הזה, ההטמעה הזו מוגבלת ל-ARM64. בדף הזה מוסברת ארכיטקטורת המסגרת.
רקע
ארכיטקטורת Arm מאפשרת עד ארבע רמות חריגה, כאשר רמת חריגה 0 (EL0) היא הרמה עם הכי פחות הרשאות, ורמת חריגה 3 (EL3) היא הרמה עם הכי הרבה הרשאות. החלק הגדול ביותר של בסיס הקוד של Android (כל הרכיבים של סביבת המשתמש) פועל ברמה EL0. שאר מה שמכונה בדרך כלל Android הוא ליבת לינוקס, שפועלת ב-EL1.
שכבת EL2 מאפשרת להציג היפרוויזור שמאפשר לבודד זיכרון ומכשירים למכונות pVM נפרדות ברמות EL1/EL0, עם הבטחות חזקות של סודיות ויושרה.
Hypervisor
המכונה הווירטואלית המוגנת שמבוססת על ליבת מערכת (pKVM) מבוססת על ההיפר-ויז'ור Linux KVM, שהורחב עם היכולת להגביל את הגישה למטענים הייעודיים (payloads) שפועלים במכונות וירטואליות אורחות שמסומנות כ'מוגנות' בזמן היצירה.
KVM/arm64 תומך במצבי ביצוע שונים בהתאם לזמינות של תכונות מסוימות במעבד, כלומר, Virtualization Host Extensions (VHE) (ARMv8.1 ואילך). באחד מהמצבים האלה, שנקרא בדרך כלל מצב לא-VHE, קוד ההיפר-ויז'ר מפוצל מתמונת הליבה במהלך האתחול ומוגדר ב-EL2, בעוד שהליבה עצמה פועלת ב-EL1. למרות שרכיב EL2 של KVM הוא חלק מבסיס הקוד של Linux, הוא רכיב קטן שאחראי על המעבר בין כמה רכיבי EL1. רכיב ההיפר-ויזור עובר קומפילציה עם Linux, אבל הוא נמצא בקטע זיכרון נפרד וייעודי של תמונת vmlinux
. pKVM ממנף את העיצוב הזה על ידי הרחבת קוד ההיפר-ויזור עם תכונות חדשות שמאפשרות לו להטיל הגבלות על ליבת המארח של Android ועל מרחב המשתמש, ולהגביל את גישת המארח לזיכרון האורח ולהיפר-ויזור.
מודולים של ספק pKVM
מודול ספק pKVM הוא מודול ספציפי לחומרה שמכיל פונקציונליות ספציפית למכשיר, כמו מנהלי התקנים של יחידת ניהול זיכרון קלט-פלט (IOMMU). המודולים האלה מאפשרים להעביר ל-pKVM תכונות אבטחה שדורשות גישה ברמה 2 של חריגים (EL2).
כדי ללמוד איך להטמיע ולהטעין מודול ספק pKVM, אפשר לעיין במאמר בנושא הטמעה של מודול ספק pKVM.
תהליך ההפעלה
באיור הבא מוצג תהליך האתחול של pKVM:
- תוכנת האתחול נכנסת לליבה הגנרית ב-EL2.
- הליבה הגנרית מזהה שהיא פועלת ברמת הרשאה EL2 ומבטלת את ההרשאות שלה לרמה EL1, בזמן ש-pKVM והמודולים שלה ממשיכים לפעול ברמה EL2. בנוסף, מודולים של ספקים של pKVM נטענים בשלב הזה.
- הליבה הגנרית ממשיכה לאתחול כרגיל, וטוענת את כל מנהלי ההתקנים הדרושים עד שהיא מגיעה למרחב המשתמש. בשלב הזה, pKVM כבר מותקן ומטפל בטבלאות הדפים של שלב 2.
הליך האתחול מסתמך על תוכנת האתחול כדי לשמור על השלמות של תמונת הליבה רק במהלך האתחול המוקדם. כשמבטלים את ההרשאות של ליבת מערכת ההפעלה, היא כבר לא נחשבת מהימנה על ידי ההיפר-ויז'ור, שאחראי להגן על עצמו גם אם הליבה נפגעה.
הכללת ליבת Android וההיפר-ויז'ר באותו קובץ בינארי של אימג' מאפשרת ממשק תקשורת צמוד מאוד ביניהם. השילוב ההדוק הזה מבטיח עדכונים אטומיים של שני הרכיבים, כך שלא צריך לשמור על יציבות הממשק ביניהם, ומציע גמישות רבה בלי לפגוע ביכולת התחזוקה לטווח ארוך. השילוב ההדוק מאפשר גם לבצע אופטימיזציות של הביצועים, כששני הרכיבים יכולים לשתף פעולה בלי לפגוע בהבטחות האבטחה שמספק ההיפר-ויזור.
בנוסף, האימוץ של GKI במערכת האקולוגית של Android מאפשר באופן אוטומטי לפרוס את ההיפר-ויז'ר pKVM במכשירי Android באותו קובץ בינארי כמו הליבה.
הגנה על הגישה לזיכרון של ה-CPU
ארכיטקטורת Arm מציינת יחידה לניהול זיכרון (MMU) שמפוצלת לשני שלבים עצמאיים, שכל אחד מהם יכול לשמש ליישום תרגום כתובות ובקרת גישה לחלקים שונים בזיכרון. ה-MMU בשלב 1 נשלט על ידי EL1 ומאפשר רמה ראשונה של תרגום כתובות. מערכת Linux משתמשת ב-MMU בשלב 1 כדי לנהל את מרחב הכתובות הווירטואלי שמוקצה לכל תהליך במרחב המשתמשים ולמרחב הכתובות הווירטואלי שלה.
יחידת ניהול הזיכרון (MMU) בשלב 2 נשלטת על ידי EL2 ומאפשרת להחיל תרגום כתובות שני על כתובת הפלט של יחידת ניהול הזיכרון (MMU) בשלב 1, וכך מתקבלת כתובת פיזית (PA). התרגום בשלב 2 יכול לשמש היפרויזורים כדי לשלוט בגישה לזיכרון ולתרגם אותה מכל המכונות הווירטואליות של האורחים. כפי שמוצג באיור 2, כששני שלבי התרגום מופעלים, כתובת הפלט של שלב 1 נקראת כתובת פיזית ביניים (IPA). הערה: הכתובת הווירטואלית (VA) מתורגמת לכתובת IPA ואז לכתובת PA.
בעבר, KVM פעל עם תרגום של שלב 2 כשהוא מריץ אורחים, ועם תרגום של שלב 2 מושבת כשהוא מריץ את ליבת Linux של המארח. הארכיטקטורה הזו מאפשרת גישה לזיכרון מה-MMU בשלב 1 של המארח דרך ה-MMU בשלב 2, וכך מאפשרת גישה בלתי מוגבלת מהמארח לדפי הזיכרון של האורח. לעומת זאת, pKVM מאפשר הגנה בשלב 2 גם בהקשר של המארח, ומטיל על ההיפר-ויזור את האחריות להגנה על דפי הזיכרון של האורח במקום על המארח.
KVM משתמש באופן מלא בתרגום כתובות בשלב 2 כדי להטמיע מיפויים מורכבים של IPA/PA לאורחים, וכך יוצר אשליה של זיכרון רציף לאורחים למרות פיצול פיזי. עם זאת, השימוש ב-MMU בשלב 2 עבור המארח מוגבל לבקרת גישה בלבד. השלב השני של המארח הוא מיפוי זהות, שמבטיח שזיכרון רציף במרחב ה-IPA של המארח יהיה רציף במרחב ה-PA. הארכיטקטורה הזו מאפשרת שימוש במיפויים גדולים בטבלת הדפים, וכתוצאה מכך מצמצמת את העומס על מאגר הנתונים הזמני לחיפוש תרגום (TLB). מיפוי הזהויות יכול להיכלל באינדקס של PA, ולכן שלב 2 של המארח משמש גם למעקב אחרי הבעלות על הדף ישירות בטבלת הדפים.
הגנה על גישה ישירה לזיכרון (DMA)
כמו שצוין קודם, ביטול המיפוי של דפי אורחים ממארח Linux בטבלאות הדפים של ה-CPU הוא שלב הכרחי אבל לא מספיק להגנה על זיכרון האורח. בנוסף, pKVM צריך להגן מפני גישה לזיכרון שמתבצעת על ידי מכשירים עם יכולת DMA בשליטת ליבת המארח, ומפני אפשרות של התקפת DMA שמתחילה על ידי מארח זדוני. כדי למנוע ממכשיר כזה לגשת לזיכרון של האורח, pKVM דורש יחידת ניהול זיכרון של קלט/פלט (IOMMU) לכל מכשיר עם יכולת DMA במערכת, כמו שמוצג באיור 3.
לפחות, חומרת IOMMU מספקת את האמצעים להענקת גישת קריאה/כתיבה למכשיר לזיכרון פיזי ברמת גרנולריות של דף, ולביטול הגישה. עם זאת, חומרת ה-IOMMU הזו מגבילה את השימוש במכשירים במכונות pVM, כי היא מניחה ששלב 2 הוא מיפוי זהות.
כדי להבטיח בידוד בין מכונות וירטואליות, עסקאות בזיכרון שנוצרות בשם ישויות שונות צריכות להיות ניתנות להבחנה על ידי IOMMU, כדי שאפשר יהיה להשתמש בקבוצה המתאימה של טבלאות דפים לתרגום.
בנוסף, צמצום כמות הקוד הספציפי ל-SoC ב-EL2 הוא אסטרטגיה מרכזית לצמצום בסיס המחשוב המהימן (TCB) הכולל של pKVM, והוא מנוגד להכללה של מנהלי התקנים של IOMMU בהיפר-ויזור. כדי לפתור את הבעיה הזו, המארח ב-EL1 אחראי למשימות ניהול עזר של IOMMU, כמו ניהול צריכת חשמל, הפעלה וטיפול בהפסקות, לפי הצורך.
עם זאת, כדי שהמארח יוכל לשלוט במצב המכשיר, יש דרישות נוספות לממשק התכנות של חומרת ה-IOMMU. הדרישות האלה נועדו לוודא שאי אפשר לעקוף את בדיקות ההרשאות באמצעים אחרים, למשל אחרי איפוס המכשיר.
יחידת ניהול זיכרון מערכת (SMMU) של Arm היא ארכיטקטורה סטנדרטית עם תמיכה טובה ב-IOMMU למכשירי Arm, שמאפשרת גם בידוד וגם הקצאה ישירה. הארכיטקטורה הזו היא הפתרון המומלץ.
הבעלות על הזיכרון
בזמן האתחול, כל הזיכרון שאינו זיכרון של Hypervisor נחשב כזיכרון בבעלות המארח, והוא מתועד ככזה על ידי Hypervisor. כשמפעילים pVM, המארח תורם דפי זיכרון כדי לאפשר את האתחול שלו, וההיפר-ויז'ר מעביר את הבעלות על הדפים האלה מהמארח ל-pVM. לכן, ההיפרווייזר מטמיע הגבלות על בקרת גישה בטבלת הדפים של שלב 2 במארח, כדי למנוע גישה חוזרת לדפים, וכך מספק סודיות לאורח.
התקשורת בין המארח לבין האורחים מתאפשרת באמצעות שיתוף מבוקר של הזיכרון ביניהם. לאורחים מותר לשתף חלק מהדפים שלהם עם המארח באמצעות hypercall, שמורה להיפר-ויז'ר למפות מחדש את הדפים האלה בטבלת הדפים של שלב 2 במארח. באופן דומה, התקשורת של המארח עם TrustZone מתאפשרת באמצעות שיתוף זיכרון ו/או פעולות השאלה, שכולן מפוקחות ונשלטות באופן הדוק על ידי pKVM באמצעות מסגרת קושחה למפרט Arm (FF-A).
דרישות הזיכרון של pVM יכולות להשתנות לאורך זמן, ולכן יש hypercall שמאפשר להחזיר את הבעלות על דפים ספציפיים ששייכים ל-caller בחזרה למארח. בפועל, קריאת המערכת הזו משמשת עם פרוטוקול ה-balloon של virtio כדי לאפשר ל-VMM לבקש זיכרון בחזרה מ-pVM, ול-pVM להודיע ל-VMM על דפים שהוקצו, בצורה מבוקרת.
ההיפרווייזר אחראי למעקב אחרי הבעלות על כל דפי הזיכרון במערכת, ולמעקב אחרי השיתוף שלהם או ההשאלה שלהם לישויות אחרות. רוב המעקב הזה אחר המצב מתבצע באמצעות מטא-נתונים שמצורפים לטבלאות הדפים של המארח והאורחים בשלב 2, באמצעות ביטים שמורים ברשומות של טבלאות הדפים (PTE), שכשמן כן הן, שמורות לשימוש בתוכנה.
המאחסן חייב לוודא שהוא לא מנסה לגשת לדפים שהגישה אליהם נחסמה על ידי ההיפר-ויז'ור. גישה לא חוקית למארח גורמת להיפר-ויז'ר להחדיר חריגה סינכרונית למארח, שיכולה לגרום למשימת מרחב המשתמש האחראית לקבל אות SEGV, או לקריסת ליבת המארח. כדי למנוע גישה לא מכוונת, ליבת המארח לא מאפשרת למזג או להחליף דפים שמוקצים לאורחים.
טיפול בהפרעות וטיימרים
הפסקות הן חלק חיוני מהדרך שבה אורח מקיים אינטראקציה עם מכשירים, ומהתקשורת בין מעבדים, שבה הפסקות בין מעבדים (IPI) הן מנגנון התקשורת העיקרי. מודל ה-KVM הוא להעביר את כל ניהול ההפסקות הווירטואליות למארח ב-EL1, שמתנהג למטרה הזו כחלק לא מהימן של ה-hypervisor.
pKVM מציע אמולציה מלאה של Generic Interrupt Controller גרסה 3 (GICv3) שמבוססת על קוד KVM קיים. הטיימרים וכתובות ה-IPI מטופלים כחלק מקוד האמולציה הלא מהימן הזה.
תמיכה ב-GICv3
הממשק בין EL1 ל-EL2 צריך להבטיח שמצב ההפרעה המלא יהיה גלוי למארח EL1, כולל עותקים של רישום ההיפר-ויזורים שקשורים להפרעות. הנראות הזו מושגת בדרך כלל באמצעות אזורי זיכרון משותפים, אחד לכל מעבד וירטואלי (vCPU).
אפשר לפשט את קוד התמיכה בזמן ריצה של רישום המערכת כך שיתמוך רק ב-Software Generated Interrupt Register (SGIR) וב-Deactivate Interrupt Register (DIR) register trapping. הארכיטקטורה מחייבת שהרישומים האלה תמיד יופנו ל-EL2, בעוד שההפניות האחרות היו עד עכשיו שימושיות רק לצורך צמצום שגיאות. כל השאר מטופל בחומרה.
בצד של MMIO, כל הפעולות מבוצעות באמולציה ברמה EL1, תוך שימוש חוזר בכל התשתית הנוכחית ב-KVM. לבסוף, הפקודה Wait for Interrupt (WFI) מועברת תמיד אל EL1, כי זו אחת מהפרימיטיבים הבסיסיים לתזמון שבהם משתמש KVM.
תמיכה בטיימר
ערך ההשוואה של הטיימר הווירטואלי צריך להיחשף ל-EL1 בכל WFI של trapping, כדי ש-EL1 יוכל להזריק הפרעות של טיימר בזמן שה-vCPU חסום. הטיימר הפיזי מודמה לחלוטין, וכל המלכודות מועברות ל-EL1.
טיפול ב-MMIO
כדי לתקשר עם המוניטור של המכונה הווירטואלית (VMM) ולבצע הדמיה של GIC, צריך להעביר את מלכודות ה-MMIO בחזרה למארח ב-EL1 לצורך מיון נוסף. pKVM דורש את הפעולות הבאות:
- היקף הגישה וגודל הגישה
- נתונים במקרה של כתיבה
- סדר הבתים (Endianness) של המעבד בנקודת הלכידה
בנוסף, מלכודות עם רגיסטר למטרה כללית (GPR) כמקור או כיעד מועברות באמצעות רגיסטר פסאודו מופשט להעברה.
ממשקי אורחים
אורח יכול לתקשר עם אורח מוגן באמצעות שילוב של קריאות היפר וגישה לזיכרון לאזורים שנתפסו. ה-Hypercalls נחשפים בהתאם לתקן SMCCC, עם טווח ששמור להקצאת ספקים על ידי KVM. ההיפר-קולים הבאים חשובים במיוחד לאורחים ב-pKVM.
קריאות-על כלליות
- PSCI מספק מנגנון סטנדרטי לאורח לשליטה במחזור החיים של מעבדי ה-vCPU שלו, כולל הפעלה, השבתה וכיבוי המערכת.
- TRNG מספק מנגנון סטנדרטי לאורח כדי לבקש אנטרופיה מ-pKVM, שמעביר את הקריאה ל-EL3. המנגנון הזה שימושי במיוחד במקרים שבהם אי אפשר לסמוך על המארח שיבצע וירטואליזציה של מחולל מספרים אקראיים (RNG) בחומרה.
קריאות pKVM hypercall
- שיתוף הזיכרון עם המארח. בהתחלה, המארח לא יכול לגשת לזיכרון של האורח, אבל גישה של המארח נדרשת לתקשורת בזיכרון משותף ולמכשירים וירטואליים למחצה שמסתמכים על מאגרי נתונים משותפים. הפונקציה Hypercalls לשיתוף דפים עם המארח ולביטול השיתוף שלהם מאפשרת לאורח להחליט בדיוק אילו חלקים בזיכרון יהיו נגישים לשאר מערכת Android, בלי צורך בהעברת נתונים.
- העברת הזיכרון למארח. בדרך כלל, כל הזיכרון של האורח שייך לאורח עד שהוא נהרס. המצב הזה לא מתאים למכונות וירטואליות לטווח ארוך עם דרישות זיכרון שמשתנות לאורך זמן. ה-hypercall
relinquish
מאפשר למכונה וירטואלית להעביר במפורש את הבעלות על דפים בחזרה למארח בלי לדרוש את סיום הפעולה של המכונה הווירטואלית. - הפעלת מלכודת לגישה לזיכרון במארח. באופן מסורתי, אם אורח KVM ניגש לכתובת שלא תואמת לאזור זיכרון תקין, השרשור של ה-vCPU יוצא למארח והגישה משמשת בדרך כלל ל-MMIO ומודמת על ידי ה-VMM במרחב המשתמש. כדי לאפשר את הטיפול הזה, pKVM צריך לפרסם פרטים על ההוראה שגרמה לשגיאה, כמו הכתובת שלה, פרמטרים של רישום ואולי גם התוכן שלהם, בחזרה למארח. אם לא צפויים שיבושים, זה עלול לחשוף בטעות נתונים רגישים מתוך אורח מוגן. pKVM פותר את הבעיה הזו על ידי התייחסות לשגיאות האלה כאל שגיאות קריטיות, אלא אם האורח כבר הוציא היפר-קול כדי לזהות את טווח ה-IPA שגרם לשגיאה כטווח שבו מותרת גישה לשיבוש בחזרה למארח. הפתרון הזה נקרא MMIO guard.
מכשיר קלט/פלט וירטואלי (virtio)
Virtio הוא תקן פופולרי, נייד ומוכח להטמעה של מכשירים וירטואליים למחצה ולשימוש בהם. רוב המכשירים שנחשפים לאורחים מוגנים מיושמים באמצעות virtio. בנוסף, Virtio הוא הבסיס להטמעה של vsock שמשמשת לתקשורת בין אורח מוגן לבין שאר מערכת Android.
מכשירי Virtio מיושמים בדרך כלל במרחב המשתמש של המארח על ידי VMM, שתופס גישות לזיכרון שנלכדו מהאורח לממשק MMIO של מכשיר Virtio ומבצע אמולציה של ההתנהגות הצפויה. הגישה ל-MMIO יקרה יחסית כי כל גישה למכשיר דורשת הלוך ושוב ל-VMM, ולכן רוב העברת הנתונים בפועל בין המכשיר לאורח מתבצעת באמצעות קבוצה של תורים וירטואליים בזיכרון. הנחת יסוד מרכזית של virtio היא שהמארח יכול לגשת לזיכרון של האורח באופן שרירותי. ההנחה הזו ניכרת בעיצוב של התור הווירטואלי, שעשוי להכיל מצביעים למאגרי נתונים במכונה האורחת, שאמולציית המכשיר אמורה לגשת אליהם ישירות.
אפשר להשתמש בקריאות היתר (hypercalls) לשיתוף זיכרון שתוארו קודם כדי לשתף מאגרי נתונים של virtio מהמכונה האורחת למארח, אבל השיתוף הזה מתבצע ברמת הגרנולריות של הדף, ויכול לחשוף יותר נתונים מהנדרש אם גודל המאגר קטן מגודל הדף. במקום זאת, המכונה האורחת מוגדרת להקצאה של תורי ה-virtqueue ושל מאגרי הנתונים התואמים שלהם מחלון קבוע של זיכרון משותף, והנתונים מועתקים (מועברים) אל החלון וממנו לפי הצורך.
אינטראקציה עם TrustZone
למרות שאורחים לא יכולים ליצור אינטראקציה ישירה עם TrustZone, המארח עדיין צריך להיות מסוגל להוציא קריאות SMC לעולם המאובטח. בשיחות האלה אפשר לציין מאגרי זיכרון עם כתובות פיזיות שהמארח לא יכול לגשת אליהם. מכיוון שהתוכנה המאובטחת בדרך כלל לא מודעת לנגישות של המאגר, מארח זדוני יכול להשתמש במאגר הזה כדי לבצע מתקפת סוכן מבולבל (בדומה למתקפת DMA). כדי למנוע מתקפות כאלה, pKVM לוכד את כל קריאות ה-SMC של המארח ל-EL2 ופועל כפרוקסי בין המארח לבין הצג המאובטח ב-EL3.
שיחות PSCI מהמארח מועברות לקושחת EL3 עם שינויים מינימליים. באופן ספציפי, נקודת הכניסה למעבד שעולה אונליין או חוזר ממצב השהיה נכתבת מחדש כך שטבלת הדפים בשלב 2 מותקנת ב-EL2 לפני החזרה למארח ב-EL1. במהלך האתחול, ההגנה הזו נאכפת על ידי pKVM.
הארכיטקטורה הזו מסתמכת על כך שה-SoC תומך ב-PSCI, רצוי באמצעות שימוש בגרסה עדכנית של TF-A כקושחה EL3.
התקן Firmware Framework for Arm (FF-A) מתקנן אינטראקציות בין העולם הרגיל לבין העולם המאובטח, במיוחד בנוכחות היפר-ויז'ר מאובטח. חלק גדול מהמפרט מגדיר מנגנון לשיתוף זיכרון עם העולם המאובטח, באמצעות פורמט הודעה משותף ומודל הרשאות מוגדר היטב לדפים הבסיסיים. pKVM מעביר הודעות FF-A כדי לוודא שהמארח לא מנסה לשתף זיכרון עם הצד המאובטח שאין לו הרשאות מספיקות לגביו.
הארכיטקטורה הזו מסתמכת על תוכנה בעולם המאובטח שמכתיבה את מודל הגישה לזיכרון, כדי להבטיח שאפליקציות מהימנות ותוכנות אחרות שפועלות בעולם המאובטח יוכלו לגשת לזיכרון רק אם הוא בבעלות בלעדית של העולם המאובטח או אם הוא שותף איתו באופן מפורש באמצעות FF-A. במערכת עם S-EL2, אכיפת מודל הגישה לזיכרון צריכה להתבצע על ידי Secure Partition Manager Core (SPMC), כמו Hafnium, שמנהל טבלאות דפים בשלב 2 עבור העולם המאובטח. במערכת ללא S-EL2, סביבת ה-TEE יכולה לאכוף מודל של גישה לזיכרון באמצעות טבלאות הדפים שלה בשלב 1.
אם הקריאה ל-SMC אל EL2 היא לא קריאת PSCI או הודעה שמוגדרת על ידי FF-A, קריאות SMC שלא טופלו מועברות אל EL3. ההנחה היא שהקושחה המאובטחת (שחייבת להיות מהימנה) יכולה לטפל ב-SMC שלא טופלו בצורה בטוחה, כי הקושחה מבינה את אמצעי הזהירות שנדרשים כדי לשמור על הבידוד של ה-pVM.
מעקב אחרי מכונות וירטואליות
crosvm הוא מנהל מכונות וירטואליות (VMM) שמריץ מכונות וירטואליות דרך ממשק KVM של Linux. מה שמייחד את crosvm הוא הדגש על בטיחות באמצעות שימוש בשפת התכנות Rust וארגז חול סביב מכשירים וירטואליים כדי להגן על ליבת המארח. מידע נוסף על crosvm זמין במסמכי התיעוד הרשמיים.
תיאורי קבצים ו-ioctls
KVM חושף את /dev/kvm
character device למרחב המשתמש עם ioctls שמרכיבים את KVM API. פקודות ה-ioctl שייכות לקטגוריות הבאות:
- פקודות ioctl של המערכת שולחות שאילתות ומגדירות מאפיינים גלובליים שמשפיעים על כל מערכת המשנה של KVM, ויוצרות מכונות pVM.
- פקודות ioctl של מכונות וירטואליות שולפות ומגדירות מאפיינים שיוצרים מעבדים וירטואליים (vCPU) ומכשירים, ומשפיעים על מכונה וירטואלית פיזית שלמה, כמו פריסת הזיכרון ומספר המעבדים הווירטואליים (vCPU) והמכשירים.
- פקודות ioctl של vCPU שולחות שאילתות ומגדירות מאפיינים ששולטים בפעולה של מעבד וירטואלי יחיד.
- הפקודות ioctl של המכשיר שולפות ומגדירות מאפיינים ששולטים בהפעלה של מכשיר וירטואלי יחיד.
כל תהליך crosvm מפעיל בדיוק מופע אחד של מכונה וירטואלית. התהליך הזה משתמש ב-ioctl של המערכת KVM_CREATE_VM
כדי ליצור מתאר קובץ של מכונה וירטואלית שאפשר להשתמש בו כדי להנפיק ioctl של מכונה וירטואלית. ioctl KVM_CREATE_VCPU
או KVM_CREATE_DEVICE
ב-FD של מכונה וירטואלית יוצר vCPU או מכשיר ומחזיר מתאר קובץ שמצביע על המשאב החדש. אפשר להשתמש ב-ioctls ב-FD של vCPU או מכשיר כדי לשלוט במכשיר שנוצר באמצעות ioctl ב-FD של מכונה וירטואלית. לגבי vCPU, זה כולל את המשימה החשובה של הפעלת קוד אורח.
באופן פנימי, crosvm רושם את מתארי הקבצים של ה-VM בקרנל באמצעות ממשק epoll
עם הפעלה בקצה. לאחר מכן, ליבת המערכת מודיעה ל-crosvm בכל פעם שיש אירוע חדש בהמתנה באחד ממתארי הקבצים.
pKVM מוסיף יכולת חדשה, KVM_CAP_ARM_PROTECTED_VM
, שאפשר להשתמש בה כדי לקבל מידע על סביבת ה-pVM ולהגדיר מצב מוגן למכונה וירטואלית. מערכת crosvm משתמשת ביכולת הזו במהלך יצירת pVM אם מועבר הדגל --protected-vm
, כדי לשלוח שאילתה ולשריין את כמות הזיכרון המתאימה ל-firmware של pVM, ואז להפעיל מצב מוגן.
הקצאת זיכרון
אחת מהאחריות העיקריות של VMM היא להקצות את הזיכרון של המכונה הווירטואלית ולנהל את פריסת הזיכרון שלה. crosvm יוצר פריסת זיכרון קבועה שמתוארת באופן כללי בטבלה שלמטה.
FDT במצב רגיל | PHYS_MEMORY_END - 0x200000
|
פינוי מקום | ...
|
Ramdisk | ALIGN_UP(KERNEL_END, 0x1000000)
|
בועה | 0x80080000
|
תוכנת אתחול | 0x80200000
|
FDT במצב BIOS | 0x80000000
|
כתובת הבסיס של הזיכרון הפיזי | 0x80000000
|
קושחה של pVM | 0x7FE00000
|
זיכרון המכשיר | 0x10000 - 0x40000000
|
הזיכרון הפיזי מוקצה באמצעות mmap
והזיכרון מועבר למכונה הווירטואלית כדי לאכלס את אזורי הזיכרון שלה, שנקראים memslots, באמצעות ioctl KVM_SET_USER_MEMORY_REGION
. לכן, כל הזיכרון של מכונת ה-pVM של האורח משויך למופע של crosvm שמנהל אותו, ויכול להיות שהתהליך יופסק (המכונה הווירטואלית תסתיים) אם המארח יתחיל לאבד זיכרון פנוי. כשמפסיקים מכונה וירטואלית, ה-hypervisor מוחק את הזיכרון באופן אוטומטי ומחזיר אותו לליבת המארח.
ב-KVM רגיל, ל-VMM יש גישה לכל הזיכרון של האורח. ב-pKVM, הזיכרון של האורח לא ממופה ממרחב הכתובות הפיזי של המארח כשהוא מועבר לאורח. היוצא מן הכלל היחיד הוא זיכרון שהאורח משתף באופן מפורש, למשל עבור מכשירי virtio.
אזורי MMIO במרחב הכתובות של האורח נשארים לא ממופים. הגישה של האורח לאזורים האלה נחסמת וגורמת לאירוע קלט/פלט (I/O) ב-FD של מכונת ה-VM. המנגנון הזה משמש להטמעה של מכשירים וירטואליים. במצב מוגן, האורח צריך לאשר שאזור במרחב הכתובות שלו משמש ל-MMIO באמצעות hypercall, כדי לצמצם את הסיכון לדליפת מידע מקרית.
תזמון
כל CPU וירטואלי מיוצג על ידי שרשור POSIX ומתוזמן על ידי מתזמן Linux של המארח. ה-thread קורא ל-KVM_RUN
ioctl ב-vCPU FD, וכתוצאה מכך ה-hypervisor עובר להקשר של vCPU אורח. מתזמן המארח מחשב את הזמן שחלף בהקשר של האורח כזמן שבו נעשה שימוש בשרשור ה-vCPU המתאים. הפונקציה KVM_RUN
מוחזרת כשיש אירוע שצריך לטפל בו באמצעות VMM, כמו קלט/פלט, סיום הפרעה או עצירה של vCPU. ה-VMM מטפל באירוע וקורא שוב ל-KVM_RUN
.
במהלך KVM_RUN
, התהליך נשאר ניתן להפרעה על ידי מתזמן המארח, למעט ההפעלה של קוד ההיפרווייזר EL2, שלא ניתן להפרעה. ל-pVM של האורח עצמו אין מנגנון לשליטה בהתנהגות הזו.
מכיוון שכל ה-thread של ה-vCPU מתוזמנים כמו כל המשימות האחרות במרחב המשתמש, הם כפופים לכל המנגנונים הרגילים של QoS. באופן ספציפי, אפשר להקצות כל שרשור vCPU למעבדים פיזיים, למקם אותו ב-cpusets, להגביר או להגביל אותו באמצעות utilization clamping, לשנות את מדיניות התזמון או העדיפות שלו ועוד.
מכשירים וירטואליים
crosvm תומך במספר מכשירים, כולל:
- virtio-blk לתמונות דיסק מורכבות, קריאה בלבד או קריאה וכתיבה
- vhost-vsock לתקשורת עם המארח
- virtio-pci כהעברת virtio
- pl030 real time clock (RTC)
- 16550a UART לתקשורת טורית
קושחה של pVM
הקושחה של pVM (pvmfw) היא הקוד הראשון שמופעל על ידי pVM, בדומה ל-ROM של אתחול של מכשיר פיזי. המטרה העיקרית של pvmfw היא לאתחל את האתחול המאובטח ולגזור את הסוד הייחודי של pVM. השימוש ב-pvmfw לא מוגבל למערכת הפעלה ספציפית, כמו Microdroid, כל עוד מערכת ההפעלה נתמכת על ידי crosvm ונחתמה בצורה תקינה.
קובץ ה-binary של pvmfw מאוחסן במחיצת פלאש עם אותו שם, והוא מתעדכן באמצעות OTA.
אתחול המכשיר
רצף השלבים הבא מתווסף לתהליך האתחול של מכשיר עם pKVM:
- טוען את pvmfw מהמחיצה שלו לזיכרון ומאמת את התמונה.
- ABL מקבל את הסודות של Device Identifier Composition Engine (DICE) (מזהי מכשירים מורכבים (CDI) ושרשרת אישורי DICE) מ-Root of Trust.
- ה-ABL גוזר את ה-CDI הנדרשים עבור pvmfw, ומוסיף אותם לבינארי pvmfw.
- ה-ABL מוסיף צומת
linux,pkvm-guest-firmware-memory
של אזור זיכרון שמור ל-DT, שמתאר את המיקום והגודל של הקובץ הבינארי pvmfw והסודות שהוא הפיק בשלב הקודם. - ABL מעביר את השליטה ל-Linux, ו-Linux מאתחל את pKVM.
- pKVM מבטל את המיפוי של אזור הזיכרון pvmfw מטבלאות הדפים בשלב 2 של המארח ומגן עליו מפני המארח (ומפני האורחים) לאורך זמן הפעולה של המכשיר.
אחרי הפעלת המכשיר, Microdroid מופעל לפי השלבים שבקטע רצף ההפעלה במסמך Microdroid.
אתחול של מכונת pVM
כשיוצרים pVM, crosvm (או VMM אחר) צריך ליצור memslot גדול מספיק כדי שההיפר-ויזור יאכלס אותו בתמונת pvmfw. יש גם הגבלות על VMM ברשימת הרישומים שאפשר להגדיר את הערך הראשוני שלהם (x0-x14 עבור vCPU ראשי, ללא הגבלות עבור vCPU משני). האוגרים הנותרים שמורים והם חלק מממשק ה-ABI של hypervisor-pvmfw.
כשמריצים את ה-pVM, ההיפרווייזר מעביר קודם את השליטה ב-vCPU הראשי
ל-pvmfw. הקושחה מצפה ש-crosvm יטען לזיכרון ליבת AVB חתומה, שיכולה להיות טוען אתחול או תמונה אחרת, ו-FDT לא חתום בהיסטים ידועים. pvmfw מאמת את חתימת ה-AVB, ואם האימות מצליח, הוא יוצר עץ מכשירים מהימן מ-FDT שהתקבל, מוחק את הסודות שלו מהזיכרון ומסתעף לנקודת הכניסה של המטען הייעודי. אם אחד משלבי האימות נכשל, הקושחה מנפיקה קריאת מערכת (hypercall) של PSCI SYSTEM_RESET
.
בין הפעלות, מידע על מופע ה-pVM מאוחסן במחיצה (מכשיר virtio-blk) ומוצפן באמצעות הסוד של pvmfw כדי להבטיח שאחרי הפעלה מחדש, הסוד יוקצה למופע הנכון.