מהי מערכת הפעלה?

מדריך מעשי למה שקורה מתחת לאפליקציות: kernel, system calls, processes, threads, scheduler, זיכרון וירטואלי, file descriptors, page cache והרצת Process ב-Java.

פורסם 2026-07-14. תגיות: Operating Systems, Java, Systems.

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

כולן רוצות להשתמש באותם משאבים מוגבלים: המעבד, זיכרון ה-RAM, הכונן, כרטיס הרשת, המסך, המקלדת והרמקולים.

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

מי שמאפשר לכל זה לעבוד יחד הוא מערכת ההפעלה.

מהי מערכת הפעלה?

מערכת הפעלה היא שכבת התוכנה שמתווכת בין האפליקציות לבין החומרה.

אפליקציות
↓
מערכת ההפעלה
↓
CPU, RAM, כונן והתקנים

אבל המילה "מתווכת" אינה מספיקה. למערכת ההפעלה יש שלושה תפקידים מרכזיים.

הפשטה

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

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

ניהול משאבים

מערכת ההפעלה מחליטה:

הגנה ובידוד

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

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

הבעיה שמערכת ההפעלה פותרת

נניח שתוכנית Java רוצה לקרוא קובץ:

String text = Files.readString(
        Path.of("message.txt")
);

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

בפועל, הקוד עובר דרך כמה שכבות:

קוד Java
↓
ספריות Java
↓
בקשה למערכת ההפעלה
↓
מערכת הקבצים
↓
Driver
↓
בקר הכונן
↓
SSD

האפליקציה עובדת עם המושג "קובץ". מערכת ההפעלה מטפלת בפרטים שמתחתיו.

מערכת ההפעלה אינה רק מה שרואים על המסך

קל לחשוב ש-Windows, macOS או Linux הם שולחן העבודה, החלונות, הכפתורים והתפריטים.

אבל הממשק הגרפי הוא רק חלק מהמערכת. מחשב יכול לפעול גם ללא שולחן עבודה גרפי. שרתי Linux רבים, לדוגמה, פועלים באמצעות terminal ושירותי רקע בלבד.

אפשר לתאר מערכת הפעלה מודרנית כך:

אפליקציות
↓
ממשק גרפי, Shell ושירותי מערכת
↓
ספריות מערכת
↓
Kernel
↓
Drivers
↓
חומרה

חלק מהרכיבים פועלים ב-user space, ואילו הליבה המיוחסת פועלת ב-kernel space.

מהו Kernel?

ה-kernel הוא הליבה המרכזית והמיוחסת של מערכת ההפעלה.

הוא אחראי בין השאר על תזמון processes ו-threads, ניהול זיכרון, טיפול ב-system calls, מערכות קבצים, הרשאות ובידוד, תקשורת רשת, עבודה עם drivers וטיפול ב-interrupts.

ה-kernel אינו process משתמש רגיל שקיבל יותר הרשאות. הוא החלק של המערכת שפועל במצב המעבד המיוחס ומנהל את שאר המערכת.

מערכת ההפעלה הרחבה כוללת גם רכיבים שאינם בתוך ה-kernel, כגון shell, שירות שמע, שירותי רשת, compositor וממשק גרפי.

User mode לעומת Kernel mode

המעבד תומך במצבי הרשאה שונים. בפישוט, אפשר לדבר על שניים:

User mode
אפליקציות רגילות
הרשאות מוגבלות

Kernel mode
ליבת מערכת ההפעלה
פעולות מיוחסות

אפליקציה ב-user mode אינה יכולה פשוט לשנות את טבלאות הזיכרון, לקרוא זיכרון של process אחר, לכתוב ישירות לכל התקן, לכבות interrupts או לשנות מבנים פנימיים של מערכת ההפעלה.

כאשר היא זקוקה לפעולה כזאת, היא מבקשת שירות מה-kernel.

System call: השער אל ה-Kernel

`System call` הוא מעבר מבוקר שבאמצעותו תוכנה מבקשת שירות ממערכת ההפעלה.

דוגמאות לבקשות כאלה:

המסלול נראה כך:

האפליקציה פועלת ב-user mode
↓
מבצעת system call
↓
המעבד עובר ל-kernel mode
↓
ה-kernel בודק את הבקשה וההרשאות
↓
מבצע או דוחה את הפעולה
↓
מחזיר תוצאה לאפליקציה

System call אינו בהכרח פנייה לחומרה. בקשה לקבל את מזהה התהליך, לשנות הרשאה או ליצור process יכולה להתבצע בתוך ה-kernel בלי לדבר עם התקן פיזי.

Program, Process ו-Thread

שלושת המושגים האלה קשורים, אבל אינם זהים.

Program

תוכנית היא קובץ פסיבי שנמצא בכונן:

java
chrome.exe
MyApplication.jar

הקובץ עצמו אינו מבצע דבר. הוא רק מכיל קוד ונתונים.

Process

כאשר מערכת ההפעלה מפעילה תוכנית, היא יוצרת עבורה process.

ה-process כולל בין השאר מרחב זיכרון וירטואלי, הרשאות וזהות משתמש, קבצים ו-sockets פתוחים, מידע על מצב התוכנית ולפחות thread אחד.

אפשר להפעיל את אותה תוכנית כמה פעמים. כל הפעלה תהיה process נפרד עם PID, זיכרון ומצב משלו.

Thread

Thread הוא רצף ביצוע בתוך process.

לכל thread יש מצב ביצוע נפרד: registers, program counter, stack, מצב תזמון ונתוני thread-local.

Threads באותו process משתפים בדרך כלל את קוד התוכנית, ה-Heap והאובייקטים, קבצים פתוחים, sockets ומשאבי process נוספים.

השיתוף הזה יעיל, אך הוא גם מקור לבעיות כמו race conditions.

מי מחלק את זמן המעבד?

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

במחשב עם שתי יחידות עיבוד לוגיות, רק שני threads יכולים לבצע הוראות ממש באותו רגע. שאר ה-threads נשארים במצב `runnable` וממתינים שה-scheduler יבחר בהם.

ה-scheduler הוא הרכיב שמחליט איזה thread ירוץ, על איזו ליבה, לכמה זמן, מתי לעצור אותו ומי יקבל עדיפות.

Concurrency לעומת Parallelism

`Concurrency` פירושה שכמה משימות מתקדמות באותה תקופה.

גם על ליבה אחת אפשר לבצע concurrency:

זמן 1: Thread A
זמן 2: Thread B
זמן 3: Thread A
זמן 4: Thread C

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

`Parallelism` פירושו שכמה משימות מבצעות הוראות ממש באותו רגע:

Core 1 → Thread A
Core 2 → Thread B

אפשר לזכור זאת כך:

Concurrency = טיפול בכמה משימות באותה תקופה
Parallelism = ביצוע כמה משימות באותו רגע

Context switch

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

באופן מפושט:

שמירת registers של Thread A
שמירת program counter ו-stack pointer
בחירת Thread B
טעינת המצב של Thread B
המשך הביצוע של B

זו החלפת הקשר, או `context switch`.

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

מה קורה כאשר Thread מחכה?

נניח ש-thread ביקש מידע מהרשת, אך המידע עדיין לא הגיע.

אין טעם לתת לו להמשיך לצרוך CPU. מערכת ההפעלה מעבירה אותו למצב `blocked`.

Running
↓
בקשת קריאה מהרשת
↓
אין מידע
↓
Blocked

בינתיים ה-scheduler מריץ thread אחר.

כאשר המידע מגיע, ה-kernel מעביר את ה-thread ל-`runnable`. הוא עדיין אינו רץ מיד; עליו להמתין שה-scheduler יבחר בו.

Preemption ולולאות אינסופיות

מה קורה אם תוכנית אינה מוותרת מרצונה על המעבד?

while (true) {
}

מערכת הפעלה מודרנית אינה חייבת לחכות שהתוכנית תעצור.

Timer חומרתי מייצר interrupts במרווחים קבועים. כאשר מגיע timer interrupt, ה-kernel מקבל את השליטה ויכול להחליט לעצור זמנית את ה-thread ולהריץ thread אחר.

לכן לולאה אינסופית ב-user mode יכולה לצרוך ליבת CPU, אך אינה בהכרח מקפיאה את כל המחשב.

Race condition

Threads באותו process משתפים זיכרון, ולכן שניים מהם יכולים לגשת לאותו משתנה.

private static int counter = 0;

private static void increment() {
    counter++;
}

`counter++` נראה כמו פעולה אחת, אבל ברמה נמוכה הוא כולל כמה שלבים:

קרא את הערך
הוסף 1
כתוב את הערך החדש

אם שני threads קוראים את אותו ערך לפני שאחד מהם כתב בחזרה:

Thread A קורא 10
Thread B קורא 10
Thread A כותב 11
Thread B כותב 11

בוצעו שתי הגדלות, אך הערך עלה רק באחת.

זוהי `race condition`: התוצאה תלויה בתזמון.

מערכת ההפעלה מספקת threads, אבל אינה מתקנת את הלוגיקה של התוכנית. המתכנת צריך להשתמש בכלים כמו `synchronized`, נעילות או `AtomicInteger`.

זיכרון וירטואלי

כל process מרגיש כאילו יש לו מרחב זיכרון פרטי ורציף.

שני processes יכולים להשתמש באותה כתובת וירטואלית:

0x5000

ועדיין לקרוא מידע שונה לחלוטין.

Process A: 0x5000 → מקום פיזי אחד
Process B: 0x5000 → מקום פיזי אחר

הכתובת שהתוכנית רואה היא כתובת וירטואלית. המיקום האמיתי ב-RAM הוא כתובת פיזית.

Pages ו-Frames

הזיכרון מחולק ליחידות קבועות.

בצד הווירטואלי הן נקראות `pages`, ובצד הפיזי `frames`.

Virtual page 0 → Physical frame 81
Virtual page 1 → Physical frame 12
Virtual page 2 → Physical frame 400

מבחינת התוכנית, הדפים יכולים להיראות רציפים, אף שב-RAM הם מפוזרים.

Page table ו-MMU

לכל process יש טבלאות שמגדירות כיצד הכתובות הווירטואליות שלו ממופות לזיכרון הפיזי.

טבלאות אלה נקראות `page tables`. הן מכילות גם הרשאות: האם מותר לקרוא, האם מותר לכתוב, האם מותר לבצע קוד, האם ה-page נגיש מ-user mode והאם הוא נמצא כרגע ב-RAM.

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

Page fault

כאשר התוכנית ניגשת ל-page שאינו ממופה כעת, המעבד מייצר `page fault`.

Page fault אינו בהכרח באג.

אם זהו page חוקי שטרם קיבל RAM, ה-kernel יכול להקצות frame, לעדכן את המיפוי ולהפעיל מחדש את ההוראה.

אם ה-page נמצא ב-swap, התהליך נחסם בזמן שמערכת ההפעלה קוראת אותו מהאחסון בחזרה ל-RAM.

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

לא כל page fault הוא שגיאה
אבל גישה לא חוקית לזיכרון עשויה להתגלות באמצעות page fault

שיתוף זיכרון בין Processes

בדרך כלל לכל process מרחב כתובות מבודד. אבל ה-kernel יכול למפות את אותו physical frame לשני processes.

Process A: virtual page 10 ─┐
                             ├→ Physical frame 500
Process B: virtual page 73 ─┘

הכתובות הווירטואליות אינן חייבות להיות זהות.

כך פועלים מנגנונים כמו shared memory, memory-mapped files וספריות משותפות.

השיתוף מהיר, אבל מחזיר בעיות של סנכרון, race conditions ונעילות.

מערכת הקבצים: מ-Blocks לשמות ותיקיות

ה-SSD אינו מכיר מושגים כמו `report.pdf`, `Desktop` או "תיקיית פרויקט". מבחינתו יש אזורים ממוספרים של מידע.

מערכת הקבצים הופכת את האחסון הגולמי למבנה של קבצים, תיקיות, שמות, נתיבים, בעלים, הרשאות ו-metadata.

שם הקובץ, ה-metadata והתוכן אינם אותו דבר.

השם הוא רשומה בתיקייה. ה-metadata כולל מידע כמו גודל, בעלים, הרשאות ומיקומי blocks. התוכן הוא הבתים עצמם.

File descriptor

כאשר תוכנה פותחת קובץ, היא מקבלת בדרך כלל מזהה קטן הנקרא `file descriptor`.

3 → data.txt
4 → חיבור רשת
5 → קובץ log

המספר אינו block בכונן ואינו כתובת RAM. הוא אינדקס בטבלת המשאבים הפתוחים של התהליך.

אותו רעיון מופיע גם ב-sockets, pipes, terminals והתקנים. התוכנה מקבלת handle קטן, ומערכת ההפעלה יודעת לאיזה משאב הוא מתייחס.

Page cache: למה קריאה שנייה מהירה יותר?

כאשר קוראים קובץ, מערכת ההפעלה עשויה לשמור את המידע שנקרא בזיכרון RAM שנקרא `page cache`.

בקריאה הבאה של אותו מידע, ייתכן שלא צריך לגשת שוב לכונן. המידע כבר נמצא ב-RAM.

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

לכן חשוב להבדיל:

write() הצליחה
↓
המידע התקבל על ידי מערכת ההפעלה
↓
לא בהכרח נכתב פיזית למדיה

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

מחיקת קובץ פתוח

במערכות Unix-like, מחיקת קובץ בדרך כלל מסירה את השם שלו מהתיקייה. אם process עדיין מחזיק file descriptor פתוח לקובץ, התוכן יכול להמשיך להתקיים עד שה-descriptor האחרון נסגר.

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

השם, ה-handle והתוכן אינם אותו דבר.

משתמשים, הרשאות וזהות

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

הרשאות קבצים בסיסיות ב-Unix כוללות:

r = read
w = write
x = execute

אבל המשמעות תלויה בהקשר. על קובץ, `x` מאפשר להריץ אותו כתוכנית. על תיקייה, `x` מאפשר לעבור דרכה ולגשת לפריטים בתוכה אם יודעים את שמם.

העיקרון החשוב הוא `least privilege`: לתת לכל process רק את ההרשאות שהוא באמת צריך.

System call, Exception ו-Interrupt

שלושת המושגים האלה גורמים למעבר אל הקוד של מערכת ההפעלה, אבל מסיבות שונות:

System call
בקשה יזומה של תוכנה לשירות מה-kernel

Exception
אירוע שנוצר בעקבות ביצוע הוראה, למשל חלוקה באפס או page fault

Interrupt
אירוע חיצוני יחסית לרצף התוכנית, למשל timer או התקן I/O

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

מה באמת קורה כשקוראים קובץ?

נחזור לשורת Java:

String text = Files.readString(
        Path.of("message.txt")
);

מאחורי הקלעים, המסלול יכול להיראות כך:

Java מבקשת לפתוח ולקרוא קובץ
↓
ספריית Java מבצעת קריאות למערכת ההפעלה
↓
ה-kernel בודק הרשאות ומתרגם path לאובייקט במערכת הקבצים
↓
אם המידע נמצא ב-page cache, הוא מועתק משם
↓
אם לא, נשלחת בקשה ל-driver ולבקר האחסון
↓
ייתכן שה-thread נחסם בזמן ההמתנה
↓
ההתקן מסיים ומייצר interrupt
↓
ה-kernel מעדכן structures פנימיים ומעיר את ה-thread
↓
המידע חוזר אל Java
↓
Java מפענחת bytes לטקסט לפי charset

שורת קוד אחת מסתירה הרבה עבודה.

דוגמה מעשית ב-Java: להריץ Process

ב-Java אפשר לבקש ממערכת ההפעלה להריץ תוכנית אחרת באמצעות `ProcessBuilder`.

Process process = new ProcessBuilder("java", "-version")
        .redirectErrorStream(true)
        .start();

int exitCode = process.waitFor();

הקוד אינו "יוצר מערכת הפעלה קטנה". הוא מבקש ממערכת ההפעלה ליצור process חדש.

ה-process החדש מקבל PID, סביבה, handles או descriptors לקלט ופלט, והרשאות לפי התהליך שהפעיל אותו.

כאשר מריצים process חיצוני, חשוב לקרוא את `stdout` ואת `stderr`. אם הילד כותב הרבה וה-parent אינו קורא מה-pipe, ה-buffer יכול להתמלא וה-child עלול להיחסם.

לכן בקוד אמיתי כדאי לקרוא את שני הזרמים, להגדיר timeout, לבדוק exit code ולסגור משאבים.

Process או Thread?

בחירה בין process ל-thread היא בחירה בין בידוד לבין שיתוף.

Process נותן בידוד חזק יותר: זיכרון נפרד, טבלת משאבים נפרדת, crash מבודד יותר והרשאות שניתן להפריד. המחיר הוא תקורה גבוהה יותר ותקשורת מורכבת יותר.

Thread קל יותר לשיתוף מידע בתוך אותו process. המחיר הוא שאותו זיכרון משותף יכול ליצור race conditions, deadlocks ובאגים שקשה לשחזר.

בדרך כלל:

צריך בידוד, אבטחה או הרצת תוכנית חיצונית → Process
צריך עבודה מקבילית בתוך אותו יישום עם שיתוף זיכרון → Thread

סיכום

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

היא מספקת הפשטות כמו קבצים, sockets ו-processes; מנהלת משאבים כמו CPU, RAM ואחסון; ומגינה על processes זה מזה באמצעות הרשאות, בידוד ומצבי הרשאה של המעבד.

כאשר תוכנית קוראת קובץ, מריצה process, מחכה לרשת או כותבת מידע, היא אינה מדברת ישירות עם החומרה. היא עוברת דרך system calls, kernel, drivers, page cache, scheduler ומנגנוני זיכרון.

ברגע שמבינים את השכבות האלה, התנהגות שנראית מוזרה מתחילה להיות צפויה: למה `write()` אינו מבטיח כתיבה פיזית, למה `thread` יכול להיות `blocked`, למה לולאה אינסופית לא בהכרח מקפיאה את כל המחשב, ולמה `file descriptor` הוא לא כתובת אלא handle למשאב שמערכת ההפעלה מנהלת.

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