410 likes | 704 Views
מערכות הפעלה. תרגול 4 – ניהול תהליכים, מבני נתונים למימוש תהליכים. מה בתכנית?. ניהול תהליכים מימוש תהליכים ב- XINU טבלת תהליכים מימוש תורי תהליכים תרגילים. ניהול תהליכים. המעבד מריץ את כל התהליכים אך בכל רגע נתון הוא מריץ תהליך בודד
E N D
מערכות הפעלה תרגול 4 – ניהול תהליכים, מבני נתונים למימוש תהליכים
מה בתכנית? • ניהול תהליכים • מימוש תהליכים ב-XINU • טבלת תהליכים • מימוש תורי תהליכים • תרגילים
ניהול תהליכים • המעבד מריץ את כל התהליכים אך בכל רגע נתון הוא מריץ תהליך בודד • התהליך המורץ במעבד נקרא תהליך נוכחי (current process) • תהליכים אחרים מחכים לביצוע במעבד, או מחכים לקבלת משאב אחר
ניהול תהליכים • מערכת ההפעלה אחראית על כך שבכל רגע ורגע יהיה תהליך אחד מוגדר כתהליך הנוכחי • במידה והתהליך מוותר על מעמדו כתהליך הנוכחי או שמערכת ההפעלה מחליטה להחליפו, עליה לבחור תהליך אחר שיחליף אותו
ניהול תהליכים • האלגוריתם המחליט האם להחליף את התהליך הנוכחי ומי התהליך שיחליפו נקרא זמן (Scheduler) • בזמן החלפת התהליך הנוכחי יש לשמור את ההקשר שלו (context) (הכולל את ערכי הרגיסטרים שונים) כדי שניתן יהיה לשחזר אותו בזמן החזרתו לביצוע (החלפת הקשר, Context Switching)
מדיניות הזימון • התהליך הנוכחי נבחר בין תהליכים המחכים למעבד • קיימות כמה שיטות לבחירת התהליך הנוכחי: • מדיניות FIFO • בחירה סדרתית (Round-Robin) • בחירה לפי עדיפות • בחירה לפי זמן ריצה
First Come First Serve (or FIFO) • במקור, תהליך אחד משתמש במעבד עד לסיומו • כיום, תהליך משתמש במעבד עד שנחסם בהמתנה למאורע כלשהו • מדיניות ללא הפקעה (non-preemptive) תהליכים קצרים "נתקעים" אחרי תהליכים ארוכים => תהליכים קצרים נמצאים הרבה זמן בתור המוכנים, יחסית לזמן החישוב שהם צורכים
Round-Robin • כל תהליך מקבל את המעבד לפרק זמן קצוב, ולאחריו הוא מוחלף באחר אף אם לא סיים. ההחלפה נעשית לפי סדר קבוע (Round-Robin) • חלוקת זמן בין התהליכים השונים הוגנת • דוגמה: נוכחי
מדיניות הזימון • לפי עדיפות: לכל תהליך משוייך מספר עדיפות. התהליך בעל עדיפות גבוהה ביותר המחכה למעבד הופך לתהליך הנוכחי • סדר העדיפויות: 1 6 8 9 10 13
מדיניות הזימון לפי עדיפות • התהליך הנוכחי יאבד את המעבד רק אם תהליך בעל עדיפות גבוהה יותר ידרוש אותו, או עם יוותר עליו בעצמו • אם תהליך בעל עדיפות גבוהה יותר לא ישחרר את המעבד, תהליכים בעלי עדיפות נמוכה יותר לא יתבצעו • קיימות מ"ה בהן תהליך שמתבצע מאבד את עדיפותו עם הזמן, ותהליך שמחכה משפר את עדיפותו כדי לאפשר "צדק" גדול יותר בחלוקת משאב המעבד
מדיניות הזימון – היישום ב-XINU • XINU משתמשת בשיטת העדיפויות כאשר בין התהליכים באותה עדיפות מתבצע Round-Robin • העדיפות של תהליך נקבעת בזמן יצירתו, אך ניתנת לשינוי בכל עת.
תהליך - דיאגרמת מצבים send receive RECEIVING wait signal WAITING sleep wake up SLEEPING resched resched CURRENT READY SUSPENDED suspend suspend resume Create
טבלת תהליכים • קיימת טבלת תהליכים, המיוצגת כוקטור של רשומות, בה כל רשומה מייצגת הקשר של תהליך, ולכל תהליך מוקצת רשומה • רשומה מכילה את המידע על מצב התהליך ואת כל המידע על הקשר התהליך הדרוש להפעלתו • קיימים מספר משתנים המייצגים את מצב התהליך או שמציינים שתהליך מחכה למשאב כלשהו
טבלת תהליכים - המשך • האינדקס בטבלת התהליכים משמש כמזהה של התהליך (pid – process identifier) • המזהה של התהליך הנוכחי שמור במשתנה נפרד המאפשר התיחסות לתהליך הנוכחי מקוד מ"ה • התהליכים המחכים למעבד שמורים בתור דו-כווני על פי עדיפותם. תור זה נקרא ready queue ומבוצע על ידי rdyhead, rdytail
טבלת תהליכיםproc.h - #define NULLPROC 0 /* id of the null process; it */ /* is always eligible to run */ extern struct pentry proctab[]; extern int numproc; /* currently active processes */ extern int nextproc; /* search point for free slot */ extern int currpid; /* currently executing process */
טבלת תהליכיםproc.h - /* process table entry */ struct pentry { char pstate; /* process state: PRCURR, etc. */ int pprio; /* process priority */ int psem; /* semaphore if process waiting */ int pmsg; /* message sent to this process */ int phasmsg; /* nonzero iff pmsg is valid */ char *pregs; /* saved environment */ char *pbase; /* base of run time stack */ word plen; /* stack length in bytes */ char pname[PNMLEN+1]; /* process name */ int pargs; /* initial number of arguments */ int (*paddr)(); /* initial code address */ };
טבלת תהליכיםproc.h - #defineNPROC 30 /* process state constants */ #definePRCURR '\01' // process is currently running #definePRFREE '\02' // process slot is free #definePRREADY '\03' // process is on ready queue #definePRRECV '\04' // process waiting for message #definePRSLEEP '\05' // process is sleeping #definePRSUSP '\06' // process is suspended #definePRWAIT '\07' // process is on semaphore queue
תהליך ה-NULL מה עושה מ"ה כאשר אין תהליך הדורש מעבד? • אחת הדרכים האפשריות היא לתת לזמן לבצע לולאה ולחכות עד שתהליך כלשהו יהיה מוכן לחזור לביצוע אך לשם כך שקוד הזמן יהיה מורכב יותר • דרך מועדפת הינה ליצור תהליך שתמיד יהיה מוכן לחישוב
תהליך ה-NULL - המשך • תהליך זה נקרא NULL process ובנוי כלולאה אינסופית מהצורה While (TRUE) /* Do nothing */; • תהליך ה-NULL יקבל את העדיפות המזערית האפשרית ולכן יהפך לנוכחי רק כאשר אין אף תהליך אחר שדורש את המעבד • המזהה של תהליך זה הוא 0
רשימות תהליכים ב-XINU • במהלך הריצה, בכל רגע, חלק ניכר מתהליכים נמצאים במצב המתנה למשאבים (CPU, קלט, הודעה וכו') • מכיוון שמספר תהליכים עלולים להמתין לאותו משאב יש לנהל תור המתנה • כלל: התהליך יכול להופיע בכל זמן נתון בתור אחד • מספר התהליכים והתורים השונים הוא סופי
מימוש הרשימות • בעיה: הרשימות הנחוצים עבור המשאבים השונים הם מגוונים (FIFO,לפי עדיפות, תור הפרשים) עובדה זו מקשה על ניהול מערכת הרשימות • פתרון: מבנה אחיד לכל הרשימות שמספק את כל מגוון הדרישות ומאפשר אחידות בטיפול
מבנה רשומה • qprev -מצביע האיבר הבא • qnext -מצביע האיבר הקודם • qkey -מפתח, עדיפות של התהליך
הגדרת תור – q.h מבנה רשומה: struct qent { int qkey; int qnext; int qprev; };
תורים ב-XINU • תור התהליכים המחכים למעבד • תור עדיפויות, הכנסה לפי מפתח – עדיפות • בתור עדיפויות התהליכים מסודרים בסדר עולה בכיוון ראש => זנב • הוצאה – איבר אחרון • תור התהליכים המחכים לסמפור – FIFO • הוצאה – מראש התור • תור הרדומים – תור ההפרשים
מימוש רשימהבעזרת מערך Key Next Prev 0 25 33 3 1 2 NPROC 140 32 3 ... איבר קודם NPROC-1 איבר הבא NPROC ... 2NSEM+4 Head=32 MININT 3 -1 Tail=33 MAXINT -1 1
הגדרת תור – q.h Inline list manipulation procedures extern struct qent q[]; #define isempty(list) (q[(list)].qnext >= NPROC) #define nonempty(list) (q[(list)].qnext < NPROC) #define firstkey(list) (q[q[(list)].qnext].qkey) #define lastkey(tail) (q[q[(tail)].qprev].qkey) #define firstid(list) (q[(list)].qnext)
פונקציות לטיפול ברשימות • enqueueהכנסת איבר לסוף הרשימה • dequeueניתוק איבר מהרשימה והחזרת האינדקס שלו • insertהכנסת תהליך לרשימה לפי ערכו של key • getfirstהסרת התהליך הראשון ברשימה והחזרת האינדקס • getlast הסרת התהליך האחרון ברשימה והחזרת האינדקס • newqueueאיתחול רשימה חדשה במבנה ה-q
הכנסה לפי מפתח – insert.c #include <conf.h> #include <kernel.h> #include <q.h> int insert(proc, head, key) int proc; /* process to insert */ int head; /* q index of head of list */ int key; /* key to use for this process */ { int next; /* runs through list */ int prev; next = q[head].qnext; while (q[next].qkey < key) /* tail key is MAXINT */ next = q[next].qnext; q[proc].qnext = next; q[proc].qprev = prev = q[next].qprev; q[proc].qkey = key; q[prev].qnext = proc; q[next].qprev = proc; return(OK); }
הוספה לסוף התור – queue.c #include <conf.h> #include <kernel.h> #include <q.h> int enqueue(item, tail) int item; int tail; { structqent *tptr; /* tail entry */ structqent *mptr; /* item entry */ tptr = &q[tail]; mptr = &q[item]; mptr->qnext = tail; mptr->qprev = tptr->qprev; q[tptr->qprev].qnext = item; tptr->qprev = item; return(item); }
הוצאה מתור – queue.c #include <conf.h> #include <kernel.h> #include <q.h> int dequeue(item) int item; { struct qent *mptr; /* q entry for item */ mptr = &q[item]; q[mptr->qprev].qnext = mptr->qnext; q[mptr->qnext].qprev = mptr->qprev; return(item); }
הסרת התהליך הראשון ברשימה – getitem.c #include <conf.h> #include <kernel.h> #include <q.h> int getfirst(head) int head; /* q index of head of list */ { int proc; /* first process on the list */ if ((proc=q[head].qnext) < NPROC) return( dequeue(proc) ); else return(EMPTY); }
הסרת התהליך האחרון ברשימה – getitem.c #include <conf.h> #include <kernel.h> #include <q.h> int getlast(tail) int tail;/* q index of tail of list */ { int proc;/* last process on the list*/ if ((proc=q[tail].qprev) < NPROC) return( dequeue(proc) ); else return(EMPTY); }
איתחול רשימה חדשה – newqueue.c int newqueue() { struct qent *hptr; /* address of new list head */ struct qent *tptr; /* address of new list tail */ int hindex, tindex; /* head and tail indexes */ /* nextqueue is global variable */ hptr = &q[ hindex=nextqueue++ ]; /* giving next used q pos. */ tptr = &q[ tindex=nextqueue++ ]; hptr->qnext = tindex; hptr->qprev = EMPTY; hptr->qkey = MININT; tptr->qnext = EMPTY; tptr->qprev = hindex; tptr->qkey = MAXINT; return(hindex); }
טבלת תהליכים/ תורים יישום התורים של התהליכים מתבצע על ידי פונקציות ניהול תורים לכן: • אין בטבלת תהליכים מצביעים על סדר התורים • ניהול התורים נעשה בטבלת התורים (q) • לכל תהליך משוייכת רשומה בטבלת תהליכים וכן בטבלת התורים
טבלת תהליכים/ תורים • בטבלת התורים יש יותר רשומות מטבלת התהליכים.NPROC רשומות ראשונות בטבלת התורים משויכות לתהליכים המקבילים בטבלת התהליכים. • שאר הרשומות בטבלת התורים משמשות כראשי/זנבות תורים שונים (Ready ,סמפורים, וכו')
תרגיל 1 • נתון: • קיימים שני תהליכים • תהליך ראשון מעלה את המונה ב-1 • מטרה: • על התהליך השני להדפיס את ערך המונה בכל פעם שמתבצעת החלפת התהליכים במעבד
פתרון unsigned long int who_is_current = 0, count = 0; xmain() { int Inc(), Pr(); resume( create(Inc, INITSTK, INITPRIO, "proc 1", 0)); resume( create(Pr, INITSTK, INITPRIO, "proc 2", 0)); while(1) ; // Do nothing but serve as alternative to Inc // Pr may not be ready (printing) }
פתרון – המשך 1 Inc() { int mypid; mypid = getpid(); /* Store my pid */ while (1) { count = 0; who_is_current = mypid; /* Signal I am current */ /* As long as I am current, increment count */ while(who_is_current == mypid) count++; /* I have been replaced and become current again */ } /* while */ } /* Inc */
פתרון – המשך 2 Pr() { char str[80]; int count_value; int mypid; mypid = getpid(); while(1) { count_value = count; /* Store count value aside, locally */ who_is_current = mypid; /* Signal I am current */ sprintf(str, "count_value = %lu\n", count_value); printf(str); while (who_is_current == mypid) /* Do not continue until */ ; /* I am replaced and current again */ } /* while */ } /* Pr */
תרגיל 2 יש למלא את המבנה של Q כפי שהוא נראה ברגע שהתכנית הגיע אל הנקודה המסומנת ב- /**/ #include <kernel.h> extern SYSCALL sleep(), wait(); int nop(); xmain() { int sem = screate(1); resume(create(sleep, INITSTK, 20, “pr1”, 1, 25)); resume(create(wait, INITSTK, 22, “pr2”, 1, sem)); resume(create(wait, INITSTK, 24, “pr3”, 1, sem)); resume(create(sleep, INITSTK, 21, “pr4”, 1, 10)); resume(create(nop, INITSTK, 20, “pr5”, 0)); resume(create(sleep, INITSTK, 20, “pr6”, 1, 30)); resume(create(wait, INITSTK, 20, “pr7”, 1, sem)); resume(create(nop, INITSTK, 20, “pr8”, 0)); create(sleep, INITSTK, 33, “pr9”, 1, 44); /**/ nop(); } nop() { while(1){}; }
תרגיל 3 pr3 pr7 SEM pr4 (10) pr1 (15) pr6 (5) SLEEP pr5 (20) 1 2 pr8 (20) READY