ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Project1 : Thread - introduction
    OS/Pintos 2021. 12. 30. 11:03

    Threads - Introduction

    프로그램

    프로그램과 프로세스의 차이

    프로그램 : 생명이없음. 보조기억장치(하드디스크, SSD)에 존재하며 실행되기를 기다리는 명령어(코드)와 정적인 데이터 묶음

    프로세스 : 프로그램의 명령어와 정적데이터가 메모리에 적재되어 생명이 생김! 즉, 실행중인 프로그램

    프로세스의 구성

    PCB(Process Control Block) 안에 프로세스에대한 정보를 담고 있음

    • PID : 운영체제가 각 프로세스를 식별하기위해 부여된 프로세스 식별번호(Process IDentification)
    • 프로세스 상태 : CPU는 프로세스를 빠르게 교체하면서 실행하기에 실행중인 프로세스도 있고, 대기중인 프로세스도 있음. 이러한 상태를 저장함
    • 프로그램 카운터 : CPU가 다음으로 실행할 명령어를 가리키는 값 CPU는 기계어를 한 단위씩 읽어서 처리하는데 프로세스를 실행하기 위해 다음으로 실행할 기계어가 저장된 메모리 주소를 가리키는 값
    • 스케쥴링 우선순위 : 운영체제는 여러개의 프로세스를 동시에 실행하는 환상 제공함. 운영체제가 여러개의 프로세스가 CPU에서 실행되는 순서를 결정하는 것을 스케쥴링이라고 함. 이 스케쥴링에서 우선순위가 높으면 먼저 실행될 수 있는데 이를 스케쥴링 우선순위라고함
    • 권한 : 프로세스가 접근할 수 있는 자원을 결정한 정보.
    • 프로세스의 부모와 자식 프로세스 : 최초로 생성되는 Init프로세스를 제외하고는 모든 프로세스는 부모 프로세스를 복제해서 생성되고 이 계층관계는 트리를 형성함. 각 프로세스는 자식 프로세스와 부모 프로세스에대한 정보를 가지고 있음
    • 프로세스의 데이터와 명령어가 있는 메모리 위치를 가리키는 포인터 : 프로세스는 실행중인 프로그램임. 따라서 프로그램에 대한 정보를 가지고 있어야함. 프로그램에 대한 정보는 프로세스가 메모리에 가지는 자신만의 주소공간에 저장됨. 이 공간에 대한 포인터 값을 가짐
    • 프로세스에 할당된 자원들을 가리키는 포인터
    • 실행문맥 : 프로세스가 실행상태에서 마지막으로 실행한 프로세서의 레지스터 내용을 담고 있음. CPU에 의해 실행되는 프로세스는 운영체제에 의해 계속 교체되는데, 교체되었다가 다시 자신의 차례가 되어서 실행될 때 중단된적 없고 마치 연속적으로 실행된것 처럼 하기 위해 이 레지스터 정보를 가지고 있음

     

    프로세스가 접근 할 수 있는 메모리 공간

    출처 : https://bowbowbow.tistory.com/16?category=175252

     

    Heap 과 Stack 사이 빈 공간 : 컴파일 타임에 지역변수를 얼마나 사용할 지 미리 계산할 수 없기 때문에 런타임에 지역변수 선언 순서에 따라 스택영역은 위쪽으로 주소 값을 매기고 동적 할당될 때 힙영역은 아래쪽으로 주소값을 매김

    Thread란?

    초기 스레드 시스템의 코드를 살펴보면 스레드 생성과 완료, 간단한 스케줄러, 동기화 기본 연산들을 구현하고 있다.

    구현되어있는 코드를 일부 수정 및 추가하여 프로젝트의 목적을 달성하면 됨.

    Pintos에서 새로운 문맥을 생성하는 것 : 새로운 문맥을 만드는것. thread_create()

    스레드가 스케쥴 되고 실행될 때 해당 문맥에서 함수가 실행되고 함수가 리턴되면, 해당 스레드는 종료됨

    각각의 스레드는 핀토스 내부에서 실행되는 작은 프로그램처럼 작동함

    하나의 스레드 실행 → 나머지 스레드 비활성화 됨

    스레드 크기 4kb미만 고정 사이즈 실행스택 할당됨

    스케쥴러는 해당 스레드 이후 어떤 스레드가 실행될 지 결정. 스레드간 문맥전환 이루어짐

    문맥전환 : 현재 실행중인 스레드의 상태를 저장하고, 전환하고자 하는 스레드의 상태를 복구하는 것

    만약 어떤 스레드도 준비 리스트에 포함되어 있지않다면, idle()함수에 구현된 특별한 idle스레드가 실행됨

    idle 스레드 : 실행 가능한 스레드가 하나도 없을 때 wake up 하며, 그렇지 않은 경우 block된 상태 (커널이 무조건 하나의 스레드는 실행하고 있는 상태를 보장하기 위함)

    동기화

    함께 사용하는 공유 자원 또는 데이터가 있을 때 이를 프로세스나 스레드들이 동시에 사용하는것을 막기 위한 방법 서로의 동작을 맞추거나, 서로의 상태 정보를 공유하는 것을 의미함

    만약, 공유자원이 통제된 방식으로 처리되지 않으면 큰 혼란이 발생할 수 있음.

    때문에, Pintos에서는 동기화를 위해 몇가지 동기화 기본 연산을 제공함

    동기화를 수행하는 가장 조잡하고 간단한 방법 : 인터럽트를 비활성화 하는 것(CPU가 인터럽트에 반응하는 것을 일시적으로 막는것, 만약 인터럽트가 꺼진다면 어떤 다른 스레드도 실행 중인 스레드를 선점할 수 없다.)

    “Pintos는 선점 가능한 커널” 즉, 커널 스레드는 스케쥴러를 명시적으로 호출하는 지점에서만 선점 가능함. 명시적 동기화 필요

    프로젝트에서 인터럽트를 비활성화함으로써 해결되는 문제는 커널 스레드와 인터럽트 핸들러 사이에서의데이터 조정 문제이다.

    즉, 커널 스레드와 인터럽트 핸들러 사이에 공유되는 데이터는 인터럽트를 비활성화함으로써 커널 스레드 내에서 보호되어야 한다.

    오직 몇 가지 경우에서만 인터럽트 핸들러에서 스레드 상태에 대한 접근을 요한다.

    인터럽트 비활성화

    인터럽트를 활성화/비활성화 관련 내용은 include/thread/interrupt.h에 포함되어 있다.

    enum intr_level;
    
    //One of INTR_OFF or INTR_ON, denoting that interrupts are disabled or enabled, resepectively.
    //INTR_OFF 또는 INTR_ON 중 하나는 인터럽트가 각각 비활성화 또는 활성화되었음을 나타낸다.
    
    enum intr_level intr_get_level (void)
    
    //Returns the current interrupt state.
    //현재 인터럽트 상태를 반환한다.
    
    enum intr_level intr_set_level (enum intr_level level);
    
    //Turns interrupts on or off according to level. Returns the previous interrupt state.
    //이전 인터럽트 상태에 따라 인터럽트를 끄거나 키고, 이전 인터럽트 상태를 반환한다.
    
    enum intr_level intr_enable (void);
    
    //Turns interrupts on. Returns the previos interrupt state.
    //인터럽트를 켜고 이전 인터럽트 상태를 반환한다.
    
    enum intr_level intr_disable (void);
    
    //Turns interrupts off. Returns the previous interrupt state.
    //인터럽트를 끄고 이전 인터럽트 상태를 반환한다.
    

    세마포어

    Pintos에서 세마포어는 아래 두 개의 연산으로만 조작이 가능한 음이 아닌 정수를 이용한 동기화 방법이다.

    자원이 한개이상인 경우 사용

    • "DOWN" 또는 "P" : 값이 양수가 될 때까지 기다렸다가 감소합니다.("Down" or "P" : wait for the value to become positive, then decrement it.)
    • "UP" 또는 "V": 값을 증가시키고 대기 중인 스레드가 있는 경우 웨이크업합니다.("UP" or "V": increment the value (and wake up one waiting thread, if any)).

    세마포어 관련 내용은 include/threads/synch.h에 포함되어 있다.

    struct semaphore;
    
    //Represents a semaphore.
    //세마포어를 나타낸다.
    
    void sema_init (struct semaphore *sema, unsigned value);
    
    //Initializes sema as a new semaphore with the given initial value.
    //지정된 초기 값을 사용하여 sema를 새로운 세마포어로 초기화한다.
    
    void sema_down (struct semaphore *sema);
    
    //Executes the "down" or "P" operation on sema, waiting for its value to become positive and then decrementing it by one.
    //"down" 또는 "P" 연산을 실행한다. 값이 양수가 될 때까지 기다렸다가 1을 감소시킨다.
    
    bool sema_try_down (struct semaphore *sema);
    
    //Tries to execute the "down" or "P" operation on sema, without waiting. 
    //Returns true if sema was successfully decremented, or false 
    //if it was already zero and thus could not be decremented without waiting. 
    //Calling this function in a tight loop wastes CPU time, so use sema_down() or find a different approach instead.
    
    //기다리지 않고 sema에서 "down" 또는 "P" 작업을 실행하려고 시도한다.
    //sema가 성공적으로 감소되면 true를 반환하고, 이미 0이어서 기다리지 않고 감소할 수 없으면 false를 반환한다.
    //while에서 이 함수를 호출하면 CPU 시간이 낭비되므로 sema_down()대신 다른 접근 방식을 찾아보기
    
    void sema_up (struct semaphore *sema);
    
    //Executes the "up" or "V" operation on sema, incrementing its value. 
    //If any threads are waiting on sema, wakes one of them up. 
    //Unlike most synchronization primitives, sema_up() may be called inside an external interrupt handler.
    
    //"up" 또는 "V" 연산을 실행하여 값을 증가시킨다. 
    //sema에서 대기 중인 스레드가 있으면 해당 중 하나를 깨운다.
    //대부분의 동기화 원시 요소들과 달리 sema_up()은 외부 인터럽트 핸들러 내부에서 호출될 수 있다.
    

    Locks

    락은 초기값이 1인 세마포어와 같다. (즉, 자원의 개수가 1개일 때 사용)

    세마포어의 "up"은 락의 "release"와 같고, "down"은 "acquire"와 같다.

    세마포어와 비교하여 락은 한 가지 제한이 추가된다. 오직 락오너라고 불리는, 락을 acquire한 스레드만이

    그것을 release 할 수 있다.

    struct lock;
    
    //Represents a lock.
    //락을 나타낸다.
    
    void lock_init (struct lock *lock);
    
    //Initializes lock as a new lock. The lock is not initially owned by any thread.
    //락을 초기화 합니다. 이 락은 어떤 스레드에 의해서도 소유되지 않는다.
    
    void lock_acquire (struct lock *lock);
    
    //Acquires lock for the current thread, first waiting for any current owner to release it if necessary.
    //현재 스레드에 대한 락을 획득하고 현재 락의 오너가 그것을 release 하기를 기다린다.
    
    bool lock_try_acquire (struct lock *lock);
    
    //Tries to acquire lock for use by the current thread, without waiting. 
    //Returns true if successful, false if the lock is already owned. 
    //Calling this function in a tight loop is a bad idea because it wastes CPU time, so use lock_acquire() instead.
    
    //기다리지 않고 현재 스레드에서 사용할 잠금을 얻으려고 시도한다.
    //성공하면 true를 반환하고 잠금이 이미 소유되어 있으면 false를 반환한다. 
    //while에서 이 함수를 호출하면 CPU시간 낭비이기에 굳이 사용안함 lock_acquire()대신 사용한다.
    
    void lock_release (struct lock *lock);
    
    //Releases lock, which the current thread must own.
    //현재 스레드가 소유한 락을 release 한다.
    
    bool lock_held_by_current_thread (const struct lock *lock):
    
    //Returns true if the running thread owns lock, false otherwise.
    //실행 중인 스레드가 락을 소유하면 true를 반환하고 그렇지 않으면 false를 반환한다.
    

    Monitors

    모니터는 세마포어나 락보다 높은 수준의 동기화 형태이다.

    모니터는 동기화되는 데이터와 모니터 락이라고 불리는 락 그리고 하나 이상의 조건 변수로 구성된다.

    스레드는 보호된 데이터에 엑세스하기 전에 먼저 모니터 락을 acquire한다. 그 상태를 '모니터 안에' 있다고 말한다.

    모니터 안에 있는 동안 스레드는 모든 보호된 데이터를 자유롭게 검사하거나 수정할 수 있다. 보호된 데이터에 대한 엑세스가 완료되면 모니터 잠금을 release한다. 조건 변수는 "사용자의 마지막 키 입력 이후 10초 이상 경과"와 같은 추상적 조건이다.

    이는 모니터 안의 코드가 조건이 참이 될 때까지 기다리게 한다.

    struct condition;
    
    //Represents a condition variable.
    //조건 변수를 나타낸다.
    
    void cond_init (struct condition *cond);
    
    //Initializes cond as a new condition variable.
    //조건을 새 조건 변수로 초기화한다.
    
    void cond_wait (struct condition *cond, struct lock *lock);
    
    //Waits for cond to be signaled by some other piece of code.
    //조건이 다른 코드에 의해 신호 받기를 기다린다.
    
    void cond_signal (struct condition *cond, struct lock *lock);
    
    //If any threads are waiting on cond (protected by monitor lock lock), then this function wakes up one of them.
    //If no threads are waiting, returns without performing any action
    //조건을 기다리고 있는 스레드들이 있다면 그들 중 하나를 깨운다. 
    //기다리고 있는 스레드가 없다면 작업을 수행하지 않고 반환한다.
    
    void cond_broadcast (struct condition *cond, struct lock *lock);
    
    //Wakes up all threads, if any, waiting on cond (protected by monitor lock lock).
    //조건을 기다리고 있는 스레드들이 있다면 모두 깨운다.
    

    참고블로그 :

    https://bowbowbow.tistory.com/16?category=175252

    https://always-be-wise.tistory.com/150

    'OS > Pintos' 카테고리의 다른 글

    Project2: User Programs(introduction)  (0) 2022.01.10
    Project1 : Thread - Priority Scheduling(3)  (0) 2021.12.30
    Project1 : Thread - Priority Scheduling(2)  (0) 2021.12.30
    Project1 : Thread - Priority Scheduling (1)  (0) 2021.12.30
    Project1. Alarm clock  (0) 2021.12.28

    댓글

Designed by Tistory.