יצירת תהליכים והרצת תוכניות
בתרגיל זה נלמד ליצור תהליך חדש ולהריץ בו תוכנית. דרך הפעולה של התוכנית
שנכתוב דומה מאוד לדרך הפעולה של תוכניות מעטפת (shell) כגון cmd.exe,
אם כי תוך שימוש בממשק פשוט בהרבה שאינו דורש פענוח של שורת פקודה מורכבת.
יצירת תהליך חדש:
BOOL CreateProcess(LPCTSTR path,
LPTSTR command_line,
LPSECURITY_ATTRIBUTES process_sa,
LPSECURITY_ATTRIBUTES thread_sa,
BOOL inherit_handles,
DWORD creation_flags,
LPVOID environment,
LPTSTR current_directory,
LPSTARTUPINFO startup_info,
LPPROCESS_INFORMATION process_info);
קריאת המערכת יוצרת תהליך חדש ומריצה בו תוכנית. למעשה, הקריאה יוצרת
תהליך וחוט---התהליך הוא מעין מחשב וירטואלי עם זיכרון פרטי, והחוט הוא
מעבד וירטואלי במחשב הוירטואלי. שני הארגומנטים הראשונים מתארים את התוכנית
שאנו מבקשים להריץ ואת הפרמטרים (שורת הפקודה) שלה. אם הארגומנט הראשון
אינו NULL, אז הוא חייב להכיל שם של קובץ שמבקשים להריץ.
במקרה כזה, מערכת ההפעלה אינה מחפשת תוכנית מתאימה, היא פשוט מפעילה
את קובץ הריצה ששמו נתון. הארגומנט השני מכיל את שורת הפקודה שהתוכנית
תקבל. אם הארגומנט הראשון הוא NULL, אז מערכת ההפעלה מתייחסת
לארגומנט השני כאל שורה שמוקלדת בתוכנת המעטפת (shell), בדרך כלל
cmd.exe. במקרה כזה, מערכת ההפעלה מחפשת תוכנית ששמה הוא
המילה הראשונה בשורת הפקודה; החיפוש מתבצע במדריך שממנו הופעלה התוכנית
שביצעה את קריאת המערכת, במדריך הנוכחי של התוכנית, במדריכים של מערכת
ההפעלה עצמה, ובמדריכים ששמם מופיע במשתנה הסביבה PATH.
שני הארגומנטים הבאים מאפשרים לקבוע הרשאות גישה לתהליך ולחוט שייווצרו.
הערך NULL מציין בקשה לשימוש בברירות מחדל. הארגומנט הבא
מציין האם אנו מעוניינים שהתהליך החדש יירש את המזהים של קבצים פתוחים
ומשאבים אחרים ויוכל להשתמש בהם.
הארגומנט השישי מאפשר לשלוט בצורה שבה ייווצר התהליך החדש. ניתן ליצור
את התהליך כך שירוץ בחלון הקיים (של ההורה שיוצר אותו), בחלון חדש, או
ללא חלון כלל. ניתן ליצור את התהליך החדש כך שיהיה חלק מקבוצת התהליכים
שגם ההורה שייך אליה, או שיתחיל קבוצה חדשה. קבוצת התהליכים משפיעה על
תגובה לאירועים חיצוניים כגון נסיון להפסיק ריצה על ידי הקשת control-c.
יש פרמטרים נוספים שניתן לשלוט בהם דרך הארגומנט הזה. לפרטים נוספים,
ראו בתיעוד המקוון. הערך 0 משתמש בברירות מחדל נוחות למקרים
פשוטים.
הארגומנט השביעי מצביע לשטח זיכרון שמתאר את משתני הסביבה שהתוכנית תוכל
לגשת אליהם. משתני הסביבה הם המשתנים שניתן לקבוע את ערכם ולשלוף את
ערכם בעזרת הפקודה set במעטפת, וניתן לקרוא אותם גם מתוך
תוכניות. משתני הסביבה מתוארים על ידי סדרת מחרוזות מהצורה name=value
שכל אחת מהן מסתיימת ב--0, הן משורשרות ברצף
אחת אחרי השניה, ואחרי האחרונה יש ערך 0 נוסף. המחרוזות
הללו הן בדרך כלל של תווי ascii, אלא אם דגל בארגומנט הקודם
ציין שהמחרוזות הן מחרוזות יוניקוד. הערך NULL גורם לירושת
משתני הסביבה של ההורה. הארגומנט השמיני מאפשר להתחיל את התהליך החדש
במדריך אחר מזה שהתהליך היוצר משתמש בו. גם כאן הערך NULL
גורם לירושה מהתהליך היוצר.
הארגומנט התשיעי מאפשר לשלוט על שני דברים. הראשון, המראה של החלון שבו
ירוץ התהליך החדש, אם הוא ירוץ בחלון חדש. השני, על ערוצי הקלט, פלט,
ושגיאה הסטנדרטיים של התהליך החדש (stdin, stdout,
ו--stderr בספריה הסטנדרטית של שפת C). אם לא קובעים
ערוצים כאלה, התהליך החדש ישתמש בערוצים שמחוברים לחלון שבו הוא רץ,
אם הוא אכן רץ בחלון טקסט. הערך NULL
אינו חוקי כאן; יש להעביר כתובת של מבנה מטיפוס STARTUPINFO,
שהשדה cb שלו מכיל את גודלו בבתים. שאר השטח יכול להכיל
אפסים אם מעוניינים להשתמש בברירת המחדל. אפשר להשתמש בשגרה ZeroMemory
לצורך איפוס השטח, אבל ניתן גם להשתמש בלולאה פשוטה.
הארגומנט האחרון מחזיר מידע על התהליך והחוט החדש שנוצרו במבנה מטיפוס
PROCESS_INFORMATION. משום מה, יש לאפס גם את המבנה הזה
לפני הקריאה. המבנה מכיל ארבעה שדות: מזהים פתוחים (מטיפוס HANDLE)
לתהליך ולחוט, ששמותיהם hProcess ו--hThread,
והמזהה המספרי שלהם, dwProcessId ו--dwThreadId.
המזהים המספריים הם למעשה השמות של התהליך והחוט, ובעזרתם ניתן לבצע
פעולות כגון הפסקת פעולה של תהליך. ניתן לראות את המזהה של כל תהליך
(Process Identifier או PID) ב--task manager; השדה הזה
אינו מוצג בדרך כלל, אך ניתן לבחור אותו להצגה. את המזהים הפתוחים יש
לסגור באמצעות CloseHandle כאשר לא זקוקים להם יותר.
המתנה (כמעט לכל דבר).
במערכות חלונות יש קריאות מערכת שגורמות לתוכנית לחכות לאירוע כלשהוא,
כגון סיום פעולת חוט, סיום פעולת תהליך, ועוד. בחלונות, כל עצם יכול
להמצא באחד משני מצבים: מסומן (signaled) או לא מסומן. חוט או תהליך
אינם מסומנים כל זמן שהם רצים או עשויים לרוץ בעתיד, ומסומנים כאשר הם
מסיימים את פעולתם.
קריאת המערכת שנשתמש בה על מנת להמתין שעצם יגיע למצב מסומן היא WaitForSingleObject.
הקריאה ממתינה שחוט או תהליך יסיים את פעולתו, או שעצם מסוג אחר יגיע
למצב מסומן.
DWORD WaitForSingleObject(
HANDLE object,
DWORD timeout_in_milliseconds);
הארגומנט הראשון הוא המזהה של העצם שמבקשים לחכות לאירוע הקשור בו, והארגומנט
השני הוא זמן ההמתנה המקסימלי באלפיות שניה, או הקבוע INFINITE
אם ההמתנה אינה מוגבלת בזמן. השגרה מחזירה אחד משלושה ערכים: WAIT_OBJECT_0
אם ההמתנה הסתיימה בהצלחה, WAIT_TIMEOUT אם ההמתנה הסתיימה
בגלל שפרק הזמן המקסימלי שציינו עבר, או WAIT_ABANDONED,
שיכול להיות מוחזר רק כאשר העצם הוא מנעול (mutex).
זמן הריצה של תהליך.
הקריאה GetProcessTimes מחזירה מידע לגבי תקופת הריצה של
תהליך ומשאבי המעבד שהוא צרך.
BOOL GetProcessTimes(HANDLE process
LPFILETIME creation_time,
LPFILETIME exit_time,
LPFILETIME kernel_time,
LPFILETIME user_time);
הארגומנט הראשון הוא המזהה של התהליך שמבקשים מידע אודותיו. כל שאר הארגומנטים
הם מבנים מסוג FILETIME שמתארים תקופת זמן ביחידות של 100
ננו-שניות, בעזרת 64 סיביות. הסיביות הללו מחולקות לשתי
מילים של 32 סיביות, dwLowDateTime ו--dwHighDateTime.
הארגומנטים השני והשלישי מתארים נקודת זמן ספציפית, של יצירת התהליך
וסיום הריצה שלו; תקופת הזמן שהמבנה מחזיק היא התקופה שמנקודת ציון קבועה
בעבר (תחילת שנת 1601 באיזור הזמן של גריניץ', אנגליה)
ועד לנקודת הזמן המתוארת. השגרה FileTimeToSystemTime ממירה
ייצוג כזה לייצוג שמכיל תאריך ושעה באופן מפורש. שני הארגומנטים האחרונים
מתארים פשוט כמות זמן, את הכמות שהתהליך צרך על המעבד במצב מיוחס ובמצב
משתמש.
הערך המוחזר מתהליך.
תהליך יכול להחזיר ערך שלם כאשר הוא מסיים את פעולתו. הערך הזה משמש
בדרך כלל לציון הצלחה או כישלון במשימה שהתהליך ניסה לבצע. הערך יכול
להיות מוחזר על ידי הפקודה return מהשגרה main
בתוכנית, על ידי קריאה לפונקצית הספריה exit של שפת C,
או על ידי קריאות המערכת ExitProcess ו--TerminateProcess.
BOOL GetExitCodeProcess(HANDLE process
LPDWORD exit_code);
הארגומנט הראשון הוא המזהה של התהליך שמבקשים מידע אודותיו, והשני הוא
מצביע למשתנה שיכיל את הערך שהתהליך החזיר אם הוא סיים את פעולתו או
הערך הסימבולי STILL_ACTIVE אם הוא עדיין רץ או עשוי לרוץ.
במעטפת (cmd.exe), ניתן לגשת לערך המוחזר מהתוכנית האחרונה
שהמעטפת הריצה או ניסתה להריץ דרך המשתנה %errorlevel%,
למשל
c:\> lpq -Smambo -Phpintel
hpintel@mambo 1 job
c:\> echo %errorlevel%
0
c:\> nosuchprogram
'nosuchprogram' is not recognized as an internal or external command,
operable program or batch file.
c:\> echo %errorlevel%
9009
c:\>dir l:
The system cannot find the path specified.
c:\> echo %errorlevel%
1
התרגיל.
עליך לכתוב תוכנית בשם ex-processtimes המפעילה תכנית אחרת
ומדפיסה את הערך המוחזר ממנה ואת זמן הריצה שלה לאחר שהיא מסיימת. התכנית
צריכה לקבל מהמשתמש שם תוכנית וארגומנטים על שורת הפקודה, להריץ את התוכנית,
להמתין שתסיים, ולאחר מכן להדפיס את זמני הריצה של התוכנית שהורצה. הפעלה
לדוגמה של התכנית:
c:\> ex-processtimes c:\windows\sysem32\lpq.exe -Smambo -Phpintel
hpintel@mambo 0 jobs
error level 0
elapsed time 0.110 seconds
user time 0.010 seconds
kernel time 0.040 seconds
Copyright Sivan Toledo 2004