מערכות הפעלה�89-231�תרגול מספר 2
קרדיטים: אבשלום אלמלח, חן חג'ג', שני אלקובי, פריאל לוי, מיכל חבאני, גילעד מדמון, אופיר הניג, שמעון כהן, חן רוזנשטיין
היום
מבוא לתהליכים
1
fork
2
exec
3
wait
4
תהליכים
ייצוג של תהליך
ייצוג של תהליך - המשך
ייצוג של תהליך - המשך
ניתן להסתכל על תהליך כישות שיכולה להימצא באחד מהמצבים הבאים:
State של תהליך
Process Table
תהליך הBOOT של מערכת ההפעלה
איך תהליך נוצר
#include <sys/types.h>
#include <unistd.h>
pid_t fork()
fork
fork - המשך
מנגנון copy on write
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void main()
{
pid_t pid;
if ((pid = fork()) == 0)
printf("1");
else
printf("2");
printf("3");
}
נציין כי 3 לא יוכל להיות מודפס ראשון (לדוגמא, הפלט 3312 אינו אפשרי)
Parent
Child
Print “1”
Print “2”
Print “3”
Print “3”
fork - דוגמא
fork();
fork();
fork();
כמה תהליכים (בנים) סה"כ ייווצרו?
שאלה
fork();
fork();
fork();
תשובה
יתומים ואימוץ
�
int main(){
int i;
for (i = 0; i < 3; i++)
if (fork() == 0)
while(1);
}
gcc -o example2 2_6.c
./example2
ps
UID PID PPID NI STAT TT TIME COMMAND
…
...
8385 1668 1 20 R pts/0 0:12 example2
8385 1669 1 20 R pts/0 0:14 example2
8385 1670 1 20 R pts/0 0:13 example2
kill -KILL 1668 1669 1670
X
X
X
i
i
i
1668
1669
1670
State of a process:
S – interruptible sleeping (process is waiting for an event to complete).
R - Running or runnable (on run queue)
Z - Zombie (process terminated and parent not waiting)
T - Stopped.
מציג את רשימת התהליכים הפעילים ע"י היוזר המחובר
fork - דוגמא
מחזיר את המזהה של התהליך הקורא
מחזיר את מזהה תהליך האב של התהליך הקורא
מחזיר את מזהה המשתמש של התהליך הקורא
מחזיר את מזהה הקבוצה של התהליך הקורא
פונקציות שימושיות
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t val;
printf("PID before fork: %d\n",(int)getpid());
val = fork();
if(val>0)
printf("parent PID: %d\n",(int)getpid());
else if(val ==0)
printf("child PID: %d\n",(int)getpid());
else {
//if val==-1 print error to screen
}
}
דוגמא ל-fork
Output
pid_t pid;
pid = getpid();
while (fork() == 0) {� if (pid == getpid())
break;�}
דוגמא
תשובה
דוגמא
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int glob = 6; /* global variable */
char buf[] = "a write to stdout";
int main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
if(puts(buf)==EOF)
printf("error in writing to stdout");
printf("before fork\n");
if ( (pid = fork()) < 0)
printf("fork error");
else{
if (pid == 0)
{ /* child */
glob++; /* modify variables */
var++;
}else
{ /* parent */
sleep(2);
}
printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit(0);
}
}
דוגמא (t2_1.c)
Output
#include <unistd.h>
int execl(char *pathname, char *arg0,…., NULL)
משפחת exec
משפחת exec
ls ��
ls –l
ls -t
ls - תזכורת
#include<stdio.h>
#include<unistd.h>
int main()
{
execl("/bin/ls","/bin/ls","-l", NULL);
printf("can only get here on error\n");
}
execl - דוגמא
משתני סביבה (environment variables)
קיימים מספר וריאנטים לפונקציה: exec{l,v}{optional: e,p}
l - command-line arguments are passed individually (a list) to the function.
v - Command-line arguments are passed to the function as an array (vector) of pointers.
e - an array of pointers to environment variables is explicitly passed to the new process image.
p - Uses the PATH environment variable to find the file named in the file argument to be executed.
משפחת exec
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
-look for the command in the path
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
-look for the command in the path
int execle(const char *path, const char *arg , ..., char * const envp[]);
int execve(const char * path, char *const argv [], char *const envp[]);
-pass the environment
משפחת exec
execl("/bin/ls", “/bin/ls", "-r", "-t", "-l", NULL);
execlp("ls", "ls", "-r", "-t", "-l", NULL);
char *args[] = {"/bin/ls", "-r", "-t", "-l", NULL };
execv("/bin/ls", args);
char *args[] = {"ls", "-r", "-t", "-l", NULL };
execvp("ls", args);
משפחת exec – דוגמאות
#include<stdio.h>
#include<unistd.h>
int main() {
char *argv[] = {“date”, NULL};
execv(“/bin/date”, argv);
printf(“hello”);
return 0;
}
execv - דוגמא
Output
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int status;
char *argv[] = { "/bin/env", 0 };
char *envp[] =
{
"HOME=/",
"PATH=/bin:/usr/bin",
"TZ=UTC0",
"USER=beelzebub",
"LOGNAME=tarzan",
0
};
status=execve(argv[0], &argv[0], envp);
printf(“exec failed\n”);
return -1;
}
משפחת exec (t2_2.c)
Output
מה ההורה עושה בזמן שהילד רץ?
ממתין לילד שיסיים.
ממשיך לבצע פעולותיו.
wait
#include <sys/types.h>
#include<sys/wait>
pid_t wait(int *status)
בקריאה ל-wait, התהליך הקורא (האב) ימתין עד שאחד מבניו יסתיים.
מה status יכיל?
מספר ממנו ניתן לחלץ מידע על סטטוס התהליך.
wait
Inspecting status information
Inspecting status information
fork()
wait()
exec()
exit()
child
parent
New program execute
Parent waits
signal
Parent resumes
wait flow
#include <unistd.h>
#include <sys/types.h>
#include<stdio.h>
int main()
{
pid_t pid;
int stat;
if ((pid = fork()) == 0)
printf("1\n"); �else �{
wait(&stat);
printf("2\n");
}
}
wait – דוגמא 1
1
2
Output
int main()
{
int status;
pid_t pid, pid1, pid2;
if ((pid1 = fork()) == 0)
printf("in child 1\n");
else
if ((pid2 = fork()) == 0)
printf("in child 2\n");
else
{
pid = wait(&status);
if (pid == pid1)
printf("child 1 finished\n");
if (pid == pid2)
printf("child 2 finished\n");
pid = wait(&status);
if (pid == pid1)
printf("child 1 finished\n");
if (pid == pid2)
printf("child 2 finished\n");
}
}
wait – דוגמא 2
int main(int argc, char* argv[])
{
int stat,waited,ret_code;
pid_t pid;
pid = fork();
if (pid == 0)
{ /* Child */
ret_code = execvp(argv[1],&argv[1]);
if (ret_code == -1)
{
perror("exec failed ");
exit(-1);
}
else
printf(“Banana”);
}
else
{ /* Parent */
printf("Father: after fork, son proc id is %d \n",pid);
waited = wait(&stat); /* stat can tell what happened */
printf("Father: Son proc completed, id is %d \n", waited);
}
}
execvp + wait
execvp + wait
ריבוי תהליכים ו-Context Switching
מבט על fork
#include <sys/types.h>
#include<sys/wait>
pid_t waitpid(pid_t pid, int *status, int options)
pid:
waitpid
int main(void){
pid_t pid;
if ((pid = fork()) < 0)
printf("fork error");
else {
if (pid == 0) { /* first child */
printf("first child\n");
if ((pid = fork()) < 0)
printf ("fork error");
else {
if (pid > 0){
/* parent from second fork == first child */
if (waitpid(pid, NULL, 0) != pid)
printf("waitpid error");
exit(0);
}
/* We're the second child; */
sleep(2);
printf("second child, parent pid = %d\n", getppid());
exit(0);
}
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
printf("waitpid error");
/** We're the parent (the original process); we continue executing, knowing that we're not the parent of the second child.*/
printf("original parent done\n");
exit(0);
}
}
דוגמא - waitpid
#include<stdlib.h>
void _exit(int status); //system call
_exit: סיום התהליך הקורא "מייד", אין חיוב שיעשה flush ל-stdio stream buffers.
void exit(int status); //c library function
exit:
exit
int atexit(void (*function)(void));
atexit
atexit – דוגמא 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void fnExit1 (void) {
puts ("Exit function 1.");
}
void fnExit2 (void) {
puts ("Exit function 2.");
}
int main () {
atexit (fnExit1);
atexit (fnExit2);
atexit (fnExit2);
puts ("Main function.");
return 0;
}
Main function.
Exit function 2.
Exit function 2.
Exit function 1.
Output
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void bye(void)
{
printf(“Let’s go home, we learned enough today\n");
}
int main(void)
{
long a;
int i;
a = sysconf(_SC_ATEXIT_MAX);
printf("ATEXIT_MAX = %ld\n", a);
i = atexit(bye);
if (i != 0)
{
fprintf(stderr, "cannot set exit function\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
atexit – דוגמא 2
Output
דוגמא מסכמת:�exit vs. _exit (t2_3.c)
void done()
{
printf("see ya!\n");
}
int main()
{
int status;
atexit(done);
if(fork())
{
wait(&status);
printf("parent PID = %d\n",getpid());
printf("exit status= %d\n",WEXITSTATUS(status));
_exit(73);
}
else
{
sleep(2);
printf("child PID = %d\n",getpid());
exit(55);
}
}
כאשר בן מסתיים והאב לא המתין לו, הבן הזה נחשב zombie process.
ה-PCB שלו עדיין נשמר. למה?
כי תהליך האב עדיין יכול לעשות לו wait. רק אז ה-PCB יימחק.
במידה והאב גם יסיים ולא יעשה wait לבן,
הבן יהפוך לתהליך יתום והתהליך init
יאמץ אותו ויעשה לו wait.
זהירות זומבים!