B.ממן 01. מערכות הפעלה. 2026א. עדיאל בן משה 208969378

שאלה 2

א.

נועד בשביל לעבור מ- user mode ל kernel mode. כדי שנוכל שהמ”ה תוכל לבצע פעולות כמו קריאה/כתיבה. מתבצע כאשר קוראים read, write, open וכו’.

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

ב.

write(fd, buffer, nbytes);

הארגומנטים נשמרים לאוגרים. נצטט מהספר, 1.6 System Calls.

For instance, on x86-64 CPUs, Linux, FreeBSD, Solaris, and macOS use the System V AMD64 ABI calling convention, which means that the first six parameters are passed in registers RDI, RSI, RDX, RCX, R8, and R9.

לאוגר RAX נשמר ה system call number של write שהוא 1.

בעזרת syscall גורמים ל trap, נכנסים ל kernel mode. שומרים את ה state של ה CPU. עושים jump לכתובת מסוימת בקרנל. הקרנל קורא מ RAX את ה system call number. ואז בודק ב interrupt vector table את ה- handler המתאים. במקרה הזה RAX=1 אז נקבל sys_write שהקרנל יבצע. .

הקרנל מחזיר, נצטט מה- man pages:

On success, the number of bytes written is returned. On error, -1 is returned, and errno is set to indicate the errorr.

הערך יהיה ב RAX.

ואז נחזור ל user mode.

ג.

תשובה: write היא syscall. שנגשים אליה דרך libc של C. משמשת לכתוב לתוך fd.

אבל printf היא פונקציית ספרייה של libc שמדפיסה ל stdout, וגם עושה format למחרוזת. מאחורי הקלעים היא משתמשת ב write.

שאלה 3

א.

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define N 5
int sem;     /* 0 = closed (unavailable), 1 = open (available)  */
int waiters; // num of threads waiting
sigset_t sem_mask;
 
void sem_init(int status)
{
    sigemptyset(&sem_mask);
    sigaddset(&sem_mask, SIGUSR1);
    sigprocmask(SIG_BLOCK, &sem_mask, NULL);
    sem = status ? 1 : 0;
    waiters = 0;
}
 
void sem_up(void)
{
    if (waiters == 0)
    {
        sem = 1;
        return;
    }
    kill(getpid(), SIGUSR1);
    printf("Signaled a waiting thread\n");
}
 
void sem_down(void)
{
    int sig;
    if (sem == 1)
    {
        sem = 0;
        return;
    }
    waiters++;
    sigwait(&sem_mask, &sig);
    waiters--;
}
 
void* worker(void* arg)
{
    long id = (long) arg;
    printf("Thread %ld: attempting sem_down...\n", id);
    sem_down();
    printf("Thread %ld: entered critical section.\n", id);
    sleep(1); /* simulate some work */
    printf("Thread %ld: leaving critical section.\n", id);
    sem_up();
    return NULL;
}
 
 
int main(void)
{
    pthread_t t[N];
    int i;
    sem_init(1); /* initialize the semaphore to 1 (open) */
    for (i = 0; i < N; i++) {
        if (pthread_create(&t[i], NULL, worker, (void*) i) != 0) {
            perror("pthread_create");
            _exit(1);
        }
    }
    for (i = 0; i < N; i++) {
        pthread_join(t[i], NULL);
    }
    printf("All threads completed.\n");
    return 0;
}

ב.

שאלה 4

א. האם M:1 מאפשר לנצל מספר ליבות במעבד cores CPU ? נמקו.

לא. ה OS רואה ומתזמנת רק את ה kernel threads. והיא רואה רק thread אחד. לכן בכל פעם שהתהליך רץ, רק thread אחד רץ. ואז לא מנצלים יותר מליבה אחת.

מויקיפדיה:

One of the major drawbacks, however, is that it cannot benefit from the hardware acceleration on multithreaded processors or multi-processor computers: there is never more than one thread being scheduled at the same time

ב. האם ב M:1 חסימת אחד מ user threads תגרום לחסימת כל התהליך? נמקו.

בפשטות, כן. כמו שאמרנו, רק thread אחד רץ, וה OS לא יודעת לגבי threads אחרים, לכן היא לא תיתן להם לרוץ בגלל שאחד אחר חסום. (אבל אולי אפשר ליישם ברמת ה user משהו שימנע מצב כזה..)

If one of the threads needs to execute an I/O request, the whole process is blocked and the threading advantage cannot be used

ג. מה המשמעות וההשפעה של מושגים user threads ו kernel threads ב- 1:1 model?

  • כל thread ביוזר מקביל ל thread בקרנל.
  • לכן ה OS יכולה לתזמן בצורה מקבילה כמה thread-ים.
  • אם thread אחד נחסם, האחרים לא יחסמו גם כן.
  • חסרון: מגדיל את ה overhead.

שאלה 5

א.

ב.

אם נהפוך את הסדר. הפתרון לא יהיה תקין. נראה מצב כזה:

(P1) turn = 1
(P0) turn = 0
(P0) interested[0] = true
(P0) while(interested[1]==true && turn==0) { /* loop */ }
# interested[1]==false => P0 is in cirtial section
(P1) interested[1] = true 
(P1) while(interested[0]==true && turn==1) { /* loop */ }
# turn==0 => P0 is in cirtial section
# both P0 and P1 are in cirtial section!