더보기

운영체제 SystemCall Shell
프로세스 생명주기
스케줄링 라운드로빈 FCFS SJF 컨텍스트스위칭 PCB TCB
멀티스레드 동기화문제 RaceCondition CriticalSection 뮤텍스 세마포어

 

1. 운영체제

1-1. 운영체제가 하는 일

운영체제는 하드웨어와 소프트웨어를 관리하기 위해 만들어졌다. OS 규격과 관리 목적은 제품에 따라 다르지만 대부분 아래 기능을 수행한다.

  • 메모리, CPU, 저장장치, 네트워크 등을 관리
  • 파일 시스템
  • 프로세스 실행 관리
  • 하드웨어 추상화 계층 (HAL)
    • 프로그래머가 하드웨어에 신경쓰지 않게 해주는 것
    • 운영체제와 운영체제가 컴퓨터 장치를 사용하기 위한 드라이버 사이에서, 사용자가 필요한 동작들에 대하여 표준적인 접근 방법을 제공하는 것 

 

1-2. 운영체제의 종류

  • 유닉스: 벨 연구소
    • macOS, iOS
  • 리눅스: 대학원생 리누스 토르발스
    • 안드로이드
  • 윈도우: MS
  • Mac OS: apple

 

1-3. 운영체제의 구조

컴퓨터 계층

  • 커널
    • 운영체제의 핵심. 항상 메모리에 상주
    • Linux의 경우 OS가 항상 메모리에 상주하기 때문에 운영체제와 커널이 같다
  • System Call
    • 운영체제의 기능을 사용할 수 있게 해주는 API
    • 커널 모드, 유저모드
      • 보안을 위해 존재
      • 프로세스는 유저 모드와 커널 모드 두 가지 모드로 작동됨.
      • 시스템콜이 이뤄지면 커널 모드로 전환. 할 일이 끝나면 다시 유저 모드로 돌아감
  • Shell 
    • 커널과 사용자 사이에 끼어서 명령을 해석하고 처리 결과를 보여주는 프로그램
    • 명령줄 셸 : cmd, powershell, sh, bash
    • 그래픽 셸 : 윈도우 탐색기

 

2. 프로세스

2-1. 프로그램과 프로세스

프로그램 하드디스크에 저장된 (프로그램의) 실행 파일
프로세스 프로그램이 실행되고 있는 상태
즉, 하드디스크에서 메인 메모리로 코드와 데이터를 가져와 현재 실행되고 있는 상태 (프로세스를 실행하려면 독자적인 메모리 공간과 CPU가 필요)
동시에 여러 개가 존재할 수 있음
cmd에 tasklist 명령어를 입력하면 현재 실행 중인 프로세스 목록을 볼 수 있음
프로세스 상태는 상황에 따라 변화함 (생명 주기)

 

2-2. 프로세스의 상태 (생명 주기)

생명 주기

  • 생명 주기
    • Created : 막 생성해서 실행 준비를 하는 단계, 실행 중인 프로세스와 우선순위를 비교
    • Waiting : 순서에 맞춰 실행을 기다리는 상태
    • Running : 운영체제로 부터 CPU를 할당 받아 실행되고 있는 상태 (순서에 맞춰 실행을 기다리는 상태)
      • Dispatch 디스패치 : 다음으로 실행될 프로세스에 CPU를 할당하는 것
      • Preemption 프리엠션 : 실행 중이던 프로세스에서 CPU를 해제하는 것
    • Blocked : I/O(읽고 쓰기처럼 디스크에서 정보를 불러오는 아주 간단한 일) 같이 오랜 시간 걸리는 작업에 들어간 경우 해당 상태로 변경됨 (I/O 작업이 끝나기 전까지 실행 불가능, 이때 실행 가능 상태의 프로세스 중 하나가 CPU를 할당받음)
    • Termiated : 종료, 소멸

 

2-3. 스케줄링

디스패치와 프리엠션이 일어날 때 운영체제가 어떤 프로세스에게 CPU를 할당할까?

  • 스케줄링 : 운영체제가 여러 프로세스의 CPU 할당 순서를 결정하는 것
  • 스케줄러 : 스케줄링을 하는 프로그램
  • 스케줄링 종류
    • 선점형 : 어떤 프로세스가 실행 중에 있어도 스케줄러가 강제로 프로세스를 중지하고 다른 프로세스를 실행(CPU에 할당)
    • 비선점형 : 실행 중인 프로세스가 종료되거나, I/O 작업에 들어가거나, CPU를 반환하기 전까지는 계속해서 실행됨. 우선순위가 높은 프로세스가 생성되어도 실행 중인 프로세스가 자발적으로 CPU를 양보하지 않으면 사용하지 못함
  • 스케줄링 알고리즘의 종류 (제품에 따라 다름)
    • 우선순위(Priority) : 우선순위를 매겨 높은 우선 순위부터 실행 선점형 
      • 기아 상태(starvation) : 낮은 우선순위가 계속 일을 하지 못하는 상황
      • 에이징(aging) : 오랫동안 실행되지 못한 프로세스의 우선순위를 높여 기아 상태 방지
    • 라운드로빈(Round-Robin) : 일정 시간동안 순서대로 할당 비선점형
      • 일정시간 : 타임 슬라이스(time slice) 또는 퀀텀(quantum)이라고 불림
    • FCFS(First Come First Served) : 실행 가능 상태에 먼저 들어온 순서대로 처리 비선점형
    • SJF(Shortest Job First) : 평균 대기시간을 최소화하기 위해 CPU 할당 시간이 가장 짧을 것으로 예측되는 것부터 처리 선점형&비선점형
      • 단점 : 할당 시간이 긴 프로세스는 계속해서 실행되지 않는 기아 상태에 빠질 수 있고, CPU의 실제 할당 시간을 알 수 없으니 예측에 의존해야 함

 

2-4. 컨텍스트 스위칭

  • 컨텍스트 스위칭 : 실행되고 있는 프로세스가 변경될 때(디스패치, 프리엠션이 일어날 때) CPU에 프로세스를 실행하기 위한 정보(PCB)가 교체되는 것
    • 이때 현재 CPU의 레지스터 값들을 전환, 컨텍스트 스위칭이 일어날 때 기존 프로세스의 CPU 상태 정보는 PCB에 기록해 둠
      • CPU의 상태 정보 : 다음에 CPU를 점유하면 실행할 다음 인스트럭션의 위치(프로그램 카운터 정보), 현재 쌓인 스택 프레임의 정보(스택 포인터와 프레임 포인터의 정보), 현재 연산 중인 데이터가 저장된 레지스터 값(범용 레지스터 정보) 등
    • 잦은 컨텍스트 스위칭은 성능을 떨어트림
  • PCB(Process Control Block) : 프로세스의 CPU 상태와 프로세스의 상태를 저장해 둔 메모리 블록
    • 프로세스의 상태 : 현재 실행 중인 인스트럭션, 스택 프레임, 프로그램 카운터 값, 스택 포인터와 프레임 포인터와 범용 레지스터의 값
    • 이 정보는 커널에 위치함 (OS가 사용하는 영역)

 

3. 멀티 스레드

3-1. 멀티 프로세스와 멀티 스레드

  멀티 프로세스 멀티 스레드
그게 뭐죠? 프로세스 : 알잖아
멀티 프로세스 : 프로세스 여러 개를 동시에 실행하는 것
스레드 : 프로세스 안의 실행 흐름 단위로, 스레드 단위로 CPU를 할당받을 수 있다.
멀티 스레드 : 스레드 여러 개를 동시에 실행하는 것
가진 것 PCB TCB (PCB랑 거의 같음)
이미지
통신 방법 소켓, 메시지 큐, 파이프 공유되는 메모리 공간 (ex. 전역변수)
장점 독립된 구조로 안정성이 있음 컨텍스트 스위칭이 단순해 성능이 좋아짐
단점 프로세스 컨텍스트 스위칭이 일어나 성능이 떨어짐 (통신 비용이 큼) 자원을 공유할 때 동기화 문제 발생 가능성이 있음 (아래에서 설명할 공유 자원 문제)

 

3-2. 멀티 스레딩을 Python으로 구현하기

아래는 공유 자원 문제(동기화 문제)가 일어나는 코드이다.

# 공유 자원 문제가 일어나는 코드

import threading

g_count = 0

def thread_main():
    global g_count
    for i in range(1000000):
        g_count += 1

threads = [ ]
for i in range(50): #3
    th = threading.Thread(target = thread_main)
    threads.append(th)

for th in threads:
    th.start()

for th in threads:
    th.join()

print('g_count :{:,}'.format(g_count))

경쟁 조건에 의해 실행할 때마다 다른 값이 나옴


이러한 문제가 발생하는 이유는?

위의 경우 단순히 변수에 1을 더하는 작업이지만 instruction 기준으로 보면 여러 단계. 어느 시점에 컨텍스트 스위칭이 일어나는지에 따라 결과가 잘못 나온다.

  • 경쟁 조건(race condition) : 스레드 여러 개가 공유 자원에 동시에 접근하는 것
  • 임계 영역(critical section) : 공유 자원에 접근해 변경을 시도하는 코드 영역

 

3-3. 공유 자원 문제(동기화 문제) 해결

문제를 해결하기 위해, 공유 자원에 여러 스레드가 동시에 접근하지 못하도록 제어해야 한다.

# 문제 해결 코드 

import threading

g_count = 0

def thread_main():
    global g_count
    lock.acquire()    
    for i in range(1000000):
        g_count += 1
    lock.release()

lock = threading.Lock()
threads = [ ]
for i in range(50): 
    th = threading.Thread(target = thread_main)
    threads.append(th)

for th in threads:
    th.start()

for th in threads:
    th.join()

print('g_count :{:,}'.format(g_count))

이것을 상호 배제라고 한다.

  • 뮤텍스 (상호배제, Mutual Exclusion = Mutex)
    • 스레드 하나가 공유 자원을 이용하는 동안 다른 스레드가 접근하지 못하게 막는 것
    • 1칸 짜리 화장실
  • 세마포어 (더 좋은 방법, Semaphore)
    • 공유 자원에 최대 접근 가능한 스레드 개수를 제한하는 것
    • 자원에 접근 중인 스레드를 count해서 구현
    • 최대 접근 가능한 스레드 개수를 1로 하면 뮤텍스가 됨
      • 세마포어는 뮤텍스가 될 수 있지만, 뮤텍스는 세마포어가 될 수 없음
    • N칸 짜리 화장실

 

3-4. 교착 상태 (DeadLock)

둘 이상의 스레드가 다른 스레드가 점유하고 있는 자원을 서로 기다릴 때와 같이 무한 대기에 빠지는 상황

+ Recent posts