[WEEK08~13][WEEK08]PINTOS : GETTING STARTED

Pyotatoยท2023๋…„ 5์›” 25์ผ
2

[C์–ธ์–ด]

๋ชฉ๋ก ๋ณด๊ธฐ
5/5

๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •

๐Ÿ’กAWS EC2 Ubuntu 18.04 (x86_64) ์—์„œ ์ง„ํ–‰!

  • ๋‚ด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ
    • ubuntu
  • EC2 ์„ธํŒ…
$ sudo apt update
$ sudo apt install -y gcc make qemu-system-x86 python3
  • ์›๋ณธ ๊ฑด๋“œ๋ฆฌ๋Š” ๊ฑฐ ์•„๋‹˜!! ํŒ€์˜ ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ๋กœ ๋ณต์ œํ•ด์„œ ์ž‘์—…ํ•˜๋Š” ๊ฒƒ!
    • pyotato๋กœ ๋ฆฌํฌ ๋ณต์ œ
    • ๋‚ด ๋ฆฌํฌ ๋‚ด์—์„œ ๋ธŒ๋žœ์น˜ ๋งŒ๋“ค์–ด์„œ
    • ์›๋ณธ master์— ์˜ฌ๋ฆฌ๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ๋ณต์ œํ•œ ๋‚ด ๋ฆฌํฌ์—์„œ ์ž‘์—…ํ•˜๊ธฐ
  • ๋ณต์ œ
$ git clone --bare https://github.com/casys-kaist/pintos-kaist.git
$ cd pintos-kaist.git
$ git push --mirror https://github.com/pyotato/pintos-kaist.git
$ cd ..
$ rm -rf pintos-kaist.git
$ git clone https://github.com/pyotato/pintos-kaist.git
  • pintos ์ž‘๋™ ์„ฑ๊ณต ํ™•์ธ โœ… 20 of 27 tests failed ๋œจ๋ฉด ์„ฑ๊ณต์ !
$ cd pintos-kaist
$ source ./activate
$ cd threads
$ make check
# .............. tons of operation results ............
20 of 27 tests failed.
  • ์ผ๋‹จ์€ ํด๋ก ์€ ์„ฑ๊ณต

Kaist gitbook

Pintos Project 1 - Thread (Youtube)

1. โฐAlarm clock

Main goal

  • Modify the system timer_alarm(int ticks)
    • timer_alarm(int ticks) : A system call that wakes up a process in ticks amount of time.
  • Currently Pintos is using busy waiting
    • busy waiting :

Files to modify

  • threads/thread
  • devices/timer
  • current timer_sleep()

device/thread.c

/* Yields the CPU.  The current thread is not put to sleep and
   may be scheduled again immediately at the scheduler's whim. */
void thread_yield (void) 
{
	struct thread *curr = thread_current (); /* 1๏ธโƒฃgets the pointer to the current thread struct "curr"*/
	enum intr_level old_level;

	ASSERT (!intr_context ());

	old_level = intr_disable (); /*2๏ธโƒฃdisables disrupt*/
	if (curr != idle_thread)
		list_push_back (&ready_list, &curr->elem); /*3๏ธโƒฃ puts the current struc to the end of the ready_list*/
	do_schedule (THREAD_READY);/* 4๏ธโƒฃchanges the state of the currently running thread to "THREAD_READY" & context switch*/
	intr_set_level (old_level);
}

(YOUTUBE) pintos/src/device/timer.c

void thread_yield (void)
{
	struct thread *cur = thread_current ();/* 1๏ธโƒฃgets the pointer to the current thread struct "curr"*/
    enum intr_level old_level;
    
    old_level = intr_disable (); /*2๏ธโƒฃdisables disrupt*/
    if (cur != idle_thread)
    	list_push_back (&ready_list, &cur->elem);/*3๏ธโƒฃ puts the current struc to the end of the ready_list*/
    cur->status = THREAD_READY;/* 4๏ธโƒฃchanges the state of the currently running thread to "THREAD_READY"*/
    schedule(); /* 5๏ธโƒฃswitch context */
    intr_set_level (old_level); /* 6๏ธโƒฃset intr level back to original state */


}

functions in thread_yield()

  • thread_current() : return the current thread
  • intr_disable() : disable the interrupt & return previous interrupt state
  • intr_set_level(old_level) : set a state of interrupt to the state passed to parameter & return previous interrupt state
  • list_push_back(&ready_list, &cur->elem) : insert the given entry to the last of list
  • put the current structure (&cur->elem) at the end of the ready_list
  • schedule() : do context switch

Improving timer_sleep()


1. OS(Operating System) puts itself to the blocked state
2. OS is responsible to wake the blocked process, frequently checking the timer interrupt
๐Ÿค— This approach saves the CPU cycle & power consumption

  • 2 types of list in current pintos
    • all_list
    • ready_list : data structure that represents a set of processes that are waiting for a CPU to be executed
  • Need to implement new list
    • sleep_list : list of blocked threads, represent a set of threads that are in the blocked state
  • When a process calls timer_sleep()
    1. OS puts the thread to a sleep_list()
    2. OS keeps check of the timer
    3. When the timer if up, it wakes up and moves the thread from the sleep_list to the read_list

Implementation Details

  1. Define Sleep Queue.
static struct list sleep_list;
  1. Initial Sleep Queue.
  2. CheckPoints ๐Ÿค”
  • Where to declare the list
  • When to initialise it
  • Implementing blocked base to timer_sleep
    • The basic design idea : Every time when a timer interrupt handler is executed, a Kernel needs to check if there are any threads to wake up
    1. Scan the blocked thread list
    2. Find if local tick for the block thread > global tick
    3. If the current time >= tick : need to wake up some threads in the blocked list (otherwise it doesn't have to scan all the blocked thread)

Implementation of Alarm Clock

  • Thread : Move thread (itself) to the sleep queue
    • When timer_sleep() is called, check the tick
    • If there is time left till the wakeup, remove the caller thread from ready_list and insert it to the sleep queue.
/* Suspends execution for approximately TICKS timer ticks. */
void
timer_sleep (int64_t ticks) {
	int64_t start = timer_ticks ();

	ASSERT (intr_get_level () == INTR_ON);
	// while (timer_elapsed (start) < ticks)
	// 	thread_yield ();
	if(timer_elapsed(start) < ticks)
		thread_sleep(start+ticks); /*start+ticks : pass alarm time it needs to wake up */	/* ๐Ÿค—implement yourself*/
}
  • Challenges : start time might not be valid since context switchinh occurs before line (2)

    timer interrupt()

    • Timer interrupt is heart of everything!

    • Determine which threads to wake up everytime

    • For the threads to wake up, remove them from the sleep queue and insert it to the ready_list (Don't forget to change the state of the thread from sleep to ready!!)

    • Depending on the Organisation the sleep_list, the time to identify the threads to wake up might differ!

SUMMARY OF FUNCTIONS TO MODIFY

  • thread_init() : add the code to initialise the sleep queue data structure
  • timer_sleep() : call the function that insert thread to the sleep queue
  • timer_interrupt() : at every tick, check whether some thread mush wake up from sleep queue and call wake up function

Design tips for modularization

  • Functions to add
  1. A function that sets thread state to blocked & wait , then insert it to sleep queue.
  2. A function that find the thread to wake up from sleep queue and wake it up.
    • Wake up threads : take it from sleep queue and put it in ready_list
  3. A function that save the minimum value of tick that threads have.
  4. A function that return the minimum value of tick.

Testing results


$ pintos -- -q run alarm-multiple


2. ๐Ÿฅ‡Priority Scheduling

Main goal

  • Pintos uses FIFO scheduling
  • Modify PintOS scheduler for priority scheduling (Expected results)
    • Sort the ready list by the thread priority
    • Sort the wait list for synchronisation primitives (semaphore, condition variable, and locks)
    • Implement the preemption
    • Preemption point : when the thread is put into the ready_list (not everytime when the timer interrupt is called)

Files to modify

  • threads/thread.*
  • threads/synch.*

3 Things to consider

  1. When selecting a thread to run in the ready_list select the one with the highest priority
  2. Preemption
  • When inserting the new thread to the ready list, compare the priority with the running thread
  • Schedule the newly inserted thread if it has the higher priority with the currently running thread
  1. Lock : semaphore, condition variable
  • When selecting a thread from the set of threads waiting for a lock (or condition variable), select the one with the highest priority

Priority in PintOS

  • Priority rangeds from PRI_MIN(=0) to PRI_MAX(=63)
    • The larger the number, the higher the priority
    • Default is PRI_DEFAULT(=31)
  • PintOS sets the initial priority when the thread is created by thread_create()
  • Existing functions
/* Change priority of the current thread to new_priority */
void thread_set_priority(int new_priority) 

/* Return priority of the current thread*/
int thread_get_priority(void)

Implementation of Priority Scheduling

tid_t thread_create(const char *name, int priority, thread_func * function, void * aux)

Codes we have to modify

  • Insert thread in ready_list in the order of priority (not that it is not scalable)
  • When the thread is added to the ready_list , compare priority of new thread & priority of the current thread
  • If the priority of the new thread is higher, call schedule() (the current thread yields CPU)
  • void thread_unblock(struct thread *t) : When the thread is unblocked, it is inserted to ready_list in the priority order
  • void thread_yield(void) : The current thread yields CPU and it is inserted to ready_list in priority order
  • void thread_set_priority(int new_priority) : Set priority of the current thread, reorder the ready_list
    • current thread_set_priority() only sets value to new_priority.

    • we need to modify thread_set_priority() value, position of the thread within ready_list

๐Ÿค” ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” cmp_priority๊ฐ€ undefined ์ธ๋ฐ ๋ญ๋กœ ๋ฐ”๊ฟ”์ค˜์•ผํ•˜์ง€?

/* Transitions a blocked thread T to the ready-to-run state.
   This is an error if T is not blocked.  (Use thread_yield() to
   make the running thread ready.)

   This function does not preempt the running thread.  This can
   be important: if the caller had disabled interrupts itself,
   it may expect that it can atomically unblock a thread and
   update other data. */
void
thread_unblock (struct thread *t) {
	enum intr_level old_level;

	ASSERT (is_thread (t));

	old_level = intr_disable ();
	ASSERT (t->status == THREAD_BLOCKED);
	// list_push_back (&ready_list, &t->elem); /*delete this code*/
	list_insert_ordered(& ready_list, & t->elem,cmp_priority,NULL); /*put the newly unblocked thread in the ready_list, according to the priority */
	t->status = THREAD_READY;
	intr_set_level (old_level);
}

Change the synchronization primitives

  • Lock
  • Semaphore
  • Condition variables

Wake the waiting thread with respect to the thread's priority

  • Priority inversion
    • Cause : PintOS lock/unlock threads in FIFO

Semaphore in PintOS


/* initialise semaphore to the given value */
void sema_init(struct semaphore *sema, unsigned value)

/* request the semaphore. 
Acquired the semaphore? decrease the value by 1 : process has to block */
void sema_down(struct semaphore *sema) 

/* release the semaphore and increase the value by 1 */
void sema_up(struct semaphore *sema)
Need to modify

โœ… void sema_down(struct semaphore *sema)
โœ… void sema_up(struct semaphore *sema)

Lock in PintOS


/* initialise the lock data structure*/
void lock_init(struct lock *loc)

/* request the lock */
void lock_acquire(struct lock *loc) 

/* release the lock*/
void lock_release(struct lock *loc)
  • lock_init() is already implemented by semaphore, in order to update lock data structure according to priority, it is efficient to modify semaphore!

Condition Variable in PintOS


/* initialise the condition variable data structure*/
void cond_init(struct lock *loc)

/* 
once process calls cond_wait(), the process is set to block state 
and waits for signal by the condition variable
*/
void cond_wait(struct lock *loc) 

/* send a signal to thread of the highes priority wiating in the condition variable*/
void cond_signal(struct lock *loc)

/*  send a signal to all threads waiting in the condition variable*/
void cond_broadcast(struct lock *loc)
Need to modify
  • Modify to insert threadt at waiters list in order of priority
  • If the process is set to wait list, we need to sort the process according to priority
    โœ… void sema_down(struct semaphore * sema)
    โœ… void cond_wait(struct condition * cond,struct lock * lock)
  • Sort the waiters list in order of priority
    • It is to consider the case of changing priority of threads in waiters list
      โœ… void sema_up(struct semaphore *sema)
      โœ… void cond_signal(struct condition *cond, struct lock *lock UNUSED)

Priority Inversion

  • Real life implications (ft. NASA)

Solutions for Priority Inversion

1.Priority Donation

  • Inheriting the priority of the lock holder

  • The thread that acquired the LOCK inherits the highest priority of the incoming threads

Issues to consider when implementing

๐Ÿชบ Nested Donation

  • we need to make our priority donations to implement this nested donation
๐Ÿ‘ฅ Multiple Donation

  • the priority of the current thread should always have the updated HIGHEST PRIORITY (not it's original priority)
Data structure for Multiple Donation
  • KEYPOINTS
    • Thread has to maintain a list of doners!
Data structure for Nested Donation
  • KEYPOINTS
    • wait_on_lock : lock that it waits for
    • Need to maintain the lock that it waits for
    • Once we inherit a priority
    • Check if we need to inherit the prioity to the child

Implementing Priority Donation

Functions to modify
/* initialises data structure for priority donation */
static void init_thread(struct thread *t, const char *name, int priority)

/*
1๏ธโƒฃ if the lock is not available, store address of the lock
2๏ธโƒฃ store the current priority & maintain donated threads on list (multiple donation)
3๏ธโƒฃ donate priority
*/
void lock_acquire(struct lock * lock)

/* when the lock is released , REMOVE the thread that holds the lock on donation list & set priority properly
*/
void lock_release(struct lock * lock)

/* set priority considering the donation */
void thread_set_priority(int new_priority)


Checking results


์ฝ”๋“œ ๋œฏ์–ด๋ณด๊ธฐ (์ง„ํ–‰์ค‘)

/devices/timer.c

1. void timer_sleep(int64_t ticks)

  • ๋Œ€๋žต TICKS ๋‹จ์œ„ ๋™์•ˆ (ํƒ€์ด๋จธ๊ฐ€ ์งธ๊น๊ฑฐ๋ฆด ๋™์•ˆ) ์‹คํ–‰ ๋ฏธ๋ฃจ๊ธฐ
/* 
Suspends execution for approximately TICKS timer ticks. 

*/
void
timer_sleep (int64_t ticks) {
	int64_t start = timer_ticks ();

	ASSERT (intr_get_level () == INTR_ON);
	while (timer_elapsed (start) < ticks)
		thread_yield ();
}
  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์ž‘๋™ํ•˜์ง€๋งŒ busy wait ์ค‘์ž„!
    • busy wait : loop๋ฅผ ๋Œ๋ฉด์„œ ํ˜„์žฌ ์‹œ๊ฐ„์„ ์ฒดํฌํ•˜๋ฉด์„œ ์‹œ๊ฐ„์ด ์ถฉ๋ถ„ํžˆ ํ๋ฅผ ๋•Œ๊นŒ์ง€ thread_yield() ํ•จ์ˆ˜ ํ˜ธ์ถœ

    /threads/thread.c

    1. void thread_yield(void)

  • CPU ์–‘๋ณดํ•ด์คŒ. ํ˜„์žฌ thread๋ฅผ sleep ์ƒํƒœ์— (ํ)์— ๋„ฃ์ง€ ์•Š๊ณ  ์Šค์ผ€์ค„๋Ÿฌ๊ฐ€ ํ•œ๊ฐ€ํ•˜๋ฉด ๊ณง๋ฐ”๋กœ ์ด thread๊ฐ€ ์Šค์ผ€์ค„๋ง๋  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜
    1. ์ด์ „ ๋ ˆ๋ฒจ์˜ interrupt๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ด์ฃผ๊ธฐ
    2. ํ˜„์žฌ thread๊ฐ€ idleํ•˜์ง€ ์•Š๋‹ค๋ฉด (external interrupt ์ค‘์ด ์•„๋‹ˆ๋ผ๋ฉด)
    3. ready_list์˜ ๋งจ ๋์œผ๋กœ ํ˜„์žฌ thread๋ฅผ ์ค„์„ธ์šฐ๊ธฐ
    4. ์Šค์ผ€์ค„๋Ÿฌ
void thread_yield (void) {
	struct thread *curr = thread_current (); /* ํ˜„์žฌ run(์‹คํ–‰)์ค‘์ธ thread ๋ฆฌํ„ด.*/
	enum intr_level old_level; /*interrupt ํ™œ์„ฑํ™” ์—ฌ๋ถ€*/

	ASSERT (!intr_context ()); /*#####test######*/

	old_level = intr_disable (); /* interrupt ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  ์ด์ „ interrupt ์ƒํƒœ ๋ฆฌํ„ดํ•ด์คŒ */
	if (curr != idle_thread)/* Returns true during processing of an external interrupt and false at all other times. */
		list_push_back (&ready_list, &curr->elem); /* ready_list ๋์— ํ˜„์žฌ elem ์‚ฝ์ž…. */
		do_schedule (THREAD_READY);    /* ์ƒˆ๋กœ์šด process ์Šค์ผ€์ค„๋ง ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜. ํ˜„์žฌ thread์˜ ์ƒํƒœ๋ฅผ THREAD_READY๋กœ ๋ฐ”๊ฟ”์ฃผ๊ณ  run(์‹คํ–‰)ํ•ด์•ผํ•  ๋‹ค๋ฅธ thread์ฐพ๊ณ  ๊ทธ thread๋กœ switchํ•จ โš ๏ธ์ง„์ž… ์ง€์ ์— interrupt ๋น„ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—ฌ์•ผํ•จ. โš ๏ธ */
		intr_set_level (old_level);    /* old_level์—์„œ ์ƒ์„ธํ•œ ๋Œ€๋กœ interrupt์„ ํ™œ์„ฑ/๋น„ํ™œ์„ฑํ™”ํ•ด์ฃผ๊ณ  ์ด์ „ interrupt ์ƒํƒœ๋ฅผ ๋ฆฌํ„ดํ•ด์คŒ */
}

Project1 ๋ณ€๊ฒฝํ•ด์•ผํ•  ํ•จ์ˆ˜๋“ค ์ด์ •๋ฆฌ

1. Alarm



2. Priority Scheduling



3. 4BSD like sceduling



Semaphore & Mutex

1. Deadlock (๊ต์ฐฉ์ƒํƒœ)

1-1. ๊ต์ฐฉ ์ƒํƒœ ๋ฐœ์ƒ ํ•„์š”์กฐ๊ฑด(4๊ฐ€์ง€ ๋งŒ์กฑํ•ด์•ผ ๋ฐœ์ƒ)

3. Mutex

3-1. Mutex == Mut(ual) Ex(clusion)

3-2. lock์ด ๊ฑธ๋ ธ๋‹ค๋ฉด ๋Œ€๊ธฐ์‹ค๋กœ~

3-3. busy-waiting == (while thread==์†๋‹˜)์ž์›์š”์ฒญ

4. Semaphore

4-1. Semaphore๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ๊ณต์œ  ์ž์›์— ๋Œ€ํ•œ ์ ‘๊ทผ

4-2. thread๊ฐ€ ์ž„๊ณ„์˜์—ญ์— ์ง„์ž…ํ•  ๋•Œ๋งˆ๋‹ค 'P'์™ธ์น˜๊ธฐ (thread๊ฐ€ ์ฐจ์ง€ํ•œ ํ…Œ์ด๋ธ” ++)

4-3. thread์˜ ์ž‘์—… ์™„๋ฃŒ (๊ณต์œ ์ž์› ์‚ฌ์šฉ ํ•ด์ œ) 'V'์™ธ์น˜๊ธฐ (thread๊ฐ€ ์ฐจ์ง€ํ•œ ํ…Œ์ด๋ธ” --)

5. ๊ฐ„๋‹จ ์š”์•ฝ

Mutex

  • ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ™˜๊ฒฝ์—์„œ ์ž์›์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ๊ฐ•์ œํ•˜๊ธฐ ์œ„ํ•œ ๋™๊ธฐํ™” ๋งค์ปค๋‹ˆ์ฆ˜
  • Boolean ํƒ€์ž… Lock๋ณ€์ˆ˜ ์‚ฌ์šฉ
  • ๊ณต์œ ์ž์›์„ ์‚ฌ์šฉ์ค‘์ธ thread ์žˆ์„ ๊ฒฝ์šฐ, ๋‹ค๋ฅธ thread๊ฐ€ ๊ณต์œ ์ž์› ์š”์ฒญ ์‹œ Blockํ•˜๊ณ  wait ํ์— ๋„ฃ๊ธฐ
  • Lock์€ lock์„ ๊ฑด thread๋งŒ ํ•ด์ œ ๊ฐ€๋Šฅ

SpinLock

  • Mutex์™€ ์œ ์‚ฌ, Busy-waiting ํ•˜๋ฏ€๋กœ waitํ๊ฐ€ ์—†์Œ (์ „์ฒด thread์™€ ์‹คํ–‰ ์ค‘์ธ thread ํ, 2๊ฐœ)
  • Mutex-nonblocking ๋ชจ๋ธ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Œ (๊ณต์œ ์ž์› ์ ‘๊ทผ์„ ๋ง‰์ง€ ์•Š์Œ)

Semaphore

  • semaphore ๋ณ€์ˆ˜(counter)๋ฅผ ํ†ตํ•ด wait, signal ๊ด€๋ฆฌ
  • counter๊ฐ€ ์–‘์ˆ˜์ผ ๋•Œ๋Š” ๊ณต์œ ์ž์› ์š”์ฒญ์„ ์ˆ˜๋ฝํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ์ˆ˜ (0์ด๋ฉด ๋ชจ๋‘ lock์ด ๊ฑธ๋ ค ๋Œ€๊ธฐ(wait)ํ•ด์•ผํ•˜๋Š” ์ƒํƒœ, ์Œ์ˆ˜๋ฉด ์ ˆ๋Œ€๊ฐ’์ด lock์ด ๊ฑธ๋ฆฐ ๊ฐœ์ˆ˜)
  • ๊ณ„์ˆ˜ ์„ธ๋งˆํฌ์–ด๋กœ ์‚ฌ์šฉ๊ฐ€๋Šฅ, ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๊ณต์œ  ์ž์›์˜ ์ˆ˜๊ฐ€ 1๊ฐœ ์ผ๋•Œ (์ด์ง„ ์„ธ๋งˆํฌ์–ด)๋Š” ๋ฎคํ…์Šค์ฒ˜๋Ÿผ ์‚ฌ์šฉ๊ฐ€๋Šฅ
  • Lock์„ ๊ฑธ์ง€ ์•Š์€ thread๋„ signal์„ ๋ณด๋‚ด lock ํ•ด์ œ ๊ฐ€๋Šฅ

6. C์–ธ์–ด๋กœ ๋ณด๊ธฐ (์—…๋กœ๋“œ ์˜ˆ์ •)

profile
https://pyotato-dev.tistory.com/ ๋กœ ์ด์‚ฌ์ค‘ ๐Ÿšš๐Ÿ’จ๐Ÿš›๐Ÿ’จ๐Ÿšš๐Ÿ’จ

1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2023๋…„ 5์›” 29์ผ

๋ฏ€์ฐŒ๋‹ค ํ‘œํ˜œ๋ฏผ

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ