## 목차 ##



## 1. 프로세스 개요 ##

(1) 프로세스의 개념

  • 프로세스는 프로그램이 메모리에 올라와 실행중인 동적인 상태를 의미한다. 즉, 프로그램을 실행시켜 프로세스를 생성한다.



(2) 프로그램에서 프로세스로의 전환

  • 프로그램이 메모리에 올라와서 실행되면 '프로세스 제어 블록'이 생성된다.

  • 프로세스 제어 블록(Process Control Block - PCB)은 프로세스를 처리하는데 필요한 정보들이 들어있는 자료구조이다.

    • 프로세스 구분자(PID - Process Identification)
    • 메모리 관련 정보(프로세스의 메모리상 위치, 경계 레지스터와 한계 레지스터)
    • 각종 중간값(프로세스가 어디까지 진행되었는지 등을 위한 레지스터 값들)
    • 기타등등
  • 운영체제 커널은 프로세스 제어블록(PCB)를 참고하여 프로세스를 관리한다.

process_control_block



(3) 프로세스의 상태

  • 생성(create, new)

    • 프로그램이 메모리에 올라와 프로세스가 되고 프로세스 제어 블록이 생성된다.
    • 생성이 완료되면 프로세스 제어 블록을 준비 큐에 삽입하여 준비상태로 옮긴다.
  • 준비(ready)

    • CPU를 할당받을 때 까지 기다리는 상태이다.
    • CPU 스케줄러는 준비상태에 있는 프로세스들 중 다음에 실행할 프로세스를 선택하고 디스패치(dispatch)하여 실행시킨다.
    • 제어 블록은 준비 큐(ready queue)에서 다음 실행을 기다린다.
    • CPU 스케줄러가 준비상태에 있는 프로세스를 dispatch(PID) 명령으로 처리하여 실행한다.
  • 실행(running)

    • 프로세스가 CPU를 얻어 실제 작업을 수행하는 상태이다.
    • 타임 슬라이스 혹은 타임 퀀텀만큼 CPU를 사용할 수 있는 시간이 할당된다.
    • 타임 슬라이스가 초과하면 클럭이 CPU에 타임아웃 인터럽트를 보낸다.
    • 타임아웃이 되면 아직 완료되지 않은 프로세스는 다시 준비상태로 옮겨진다.
    • 타임아웃이 되었을 때 완료된 프로세스는 종료상태로 옮겨진다.
    • 실행 중 프로세스가 입출력을 요청하면 CPU가 입출력 관리자에게 요청을 전달하고 프로세스를 대기상태로 옮겨진다.
  • 종료(terminate)

    • 프로세스 작업이 완료되면 제어 블록이 사라지고 메모리에서 해당 프로세스가 해제된다.
  • 대기(wating)

    • 프로세스가 입출력장치나 네트워크에 데이터를 요청할 때 발생한다.
    • 입출력이 완료될 때 까지 아무것도 하지않고 기다리는것은 비효율적이다.
    • 따라서 입출력을 요청한 프로세스는 대기상태로 들어가고 다른 프로세스에게 CPU 사용권한을 넘긴다.
    • 입출력이 완료되면 입출력장치 제어기가 인터럽트를 보내고 프로세스는 준비상태로 들어가 다음 CPU 선점을 기다린다.

process_state

  • 휴식상태와 보류상태
    • 프로그램이 일시중지 된 상태이다.
    • 휴식상태는 일시중지되었지만 프로세스가 메모리에 그대로 남아있다.
    • 유닉스에서 ctrl+Z를 누르면 휴식상태로 들어간다.
    • 보류상태는 메모리에서 쫓겨나 데이터가 스왑영역(swap area)에 보관된다.
    • 보류상태는 메모리가 꽉 차거나 프로세스에 오류가 있는경우, 혹은 프로세스가 요청한 입출력이 계속 지연되는 등의 이유로 발생한다.



## 2. 프로세스 제어 블록과 문맥 교환 ##

(1) 프로세스 제어블록 구성

  • 포인터

    • 부모 프로세스 포인터(부모 프로세스의 주소)
    • 자식 프로세스 포인터(자식 프로세스의 주소)
    • 프로세스 자신의 포인터(프로세스가 있는 메모리의 주소)
    • 할당된 자원에 대한 포인터(프로세스가 사용중인 혹은 입출력을 요구한 자원의 주소)
    • 프로세스가 입출력 요구로 대기상태로 들어갈 때 각 자원의 대기 큐에 PCB이 추가되는데 이 때 입출력장치는 PCB의 포인터를 확인하고 프로세스에 완료 인터럽트를 보낸다.
  • 프로세스 상태

    • 프로세스의 현재 상태
  • 프로세스 구분자

    • 여러 프로세스를 구분하기 위한 프로세스 고유의 ID
  • 프로그램 카운터

    • 다음에 실행될 명령어의 위치를 기억하기 위한 레지스터
  • 프로세스 우선순위

    • 프로세스의 우선순위
    • 프로세스별로 우선순위가 다르다.
    • 커널 프로세스 같이 중요한 프로세스가 일반 사용자 프로세스보다 우선순위가 높다.
    • 우선순위별로 준비 큐가 따로 운영된다.
  • 레지스터 정보

    • 프로세스들이 CPU를 돌려쓰기 때문에 준비 상태로 가기 전 실행중일 때의 레지스터 정보를 저장한다.
    • 프로세스의 다음 CPU 선점 때 레지스터 정보를 다시 불러와서 이어서 실행한다.
  • 메모리 관리 정보

    • 프로세스가 할당된 메모리 위치
    • 메모리 보호를 위한 경계 레지스터값, 한계 레지스터값
    • 세그멘테이션 테이블, 페이지 테이블 등
  • 할당된 자원 정보

    • 프로세스가 사용하려는 하드웨어 자원(디스크의 파일, 사운드카드 등)의 정보를 저장한다.
  • 계정 보호

    • 계정 번호, CPU 할당 시간, CPU 사용 시간 등
  • 부모 프로세스와 자식 프로세스 구분자

    • 부모 프로세스 구분자(PPID)
    • 자식 프로세스 구분자(CPID)



(2) 문맥 교환(context switching)

  • CPU를 차지하던 프로세스가 준비상태로 가고 새로운 프로세스를 실행상태로 옮기는 작업

    • 실행 -> 준비로 옮기는 프로세스의 제어블록에 지금까지의 작업 내용을 저장한다.
    • 준비 -> 실행으로 옮기는 프로세스의 제어블록 내용을 가지고 CPU 레지스터가 세팅된다.
  • 문맥 교환 원인

    • 타임아웃에 의한 CPU 스케줄러의 명령
    • 경계 레지스터 범위를 넘어선 메모리 접근과 그에따른 인터럽트
    • 인터럽트가 발생하면 인터럽트 관리 프로세스가 실행상태로 들어간다.



## 3. 프로세스의 연산 ##

(1) 프로세스 메모리 구조

프로세스는 메모리 영역을 4가지로 구분하여 사용한다.

  • 코드(code) 영역

    • 프로그램의 실행 코드와 매크로 상수 등
    • 컴파일시에 결정된다.
    • 프세스 실행중에는 바꿀수 없도록 read-only로 지정되어있다.
  • 데이터(data) 영역

    • 전역변수, static 변수 등
    • read-write 가능
  • 스택(stack) 영역

    • 지역변수, 매개변수, 리턴값, 함수 호출이 끝난 뒤 돌아올 주소
    • 컴파일 시에 크기가 정해진다.
    • 지역변수가 너무 많거나 재귀호출이 여러번 반복되어 스택영역을 벗어나 넘치면 stack-overflow 에러가 발생한다.
  • 힙(heap) 영역

    • 프로그래머가 직접 할당하고 해제하는 메모리 영역
    • C언어의 경우 malloc과 calloc으로 할당하고 해제할 수 있다.
    • 런타임(runtime)시에 결정된다.
    • Java와 같이 명시적 메모리 할당/해제를 막아놓은 언어로 작성된 프로그램의 경우 JVM이 대신 이 영역의 할당/해제를 한다.



(2) 프로세스의 생성과 복사

  • fork()

    • 커널에서 제공하는 시스템 호출 함수이다.
    • 실행중인 프로세스로부터 새롱누 프로세스를 복사하는 함수
    • ex) 웹 브라우저를 실행하는중에 새로운 웹 브라우저를 하나 더 실행하면 새로운 프로세스가 생성되지 않고 기존 웹 브라우저를 fork()하여 만들어진다.
    • fork()는 새로 프로세스를 생성하는 것보다 빠르다.
    • 기존 프로세스는 부모 프로세스이고 새로운 프로세스는 자식 프로세스이다.
  • fork() 호출 동작 과정

    • 부모 프로세스의 제어블록 대부분이 복사되어 자식 프로세스 제어블록이 만들어진다.
    • 프로세스 구분자(PID), 메모리 정보는 바뀐다.
  • fork()의 장점

    • 프로세스 생성 속도가 빠르다.(하드디스크로부터 새로 프로그램을 가져오지 않고 메모리에서 복사하기 때문)
    • 추가 작업 없이 자원을 상속할 수 있다.(부모가 만들어놓은 자원을 그대로 사용할 수 있어 효율적)
    • 자식 프로세스가 종료되면 부모프로세스가 메모리, 하드웨어 자원, 파일등을 대신 정리하므로 효율적이다.

process_fork

  • fork()문을 기준으로 부모 프로세스와 자식 프로세스가 갈라진다.
    • 부모 프로세스에서는 fork()가 자식 프로세스의 PID를 리턴한다.
    • 자식 프로세스에서는 fork()가 0을 리턴한다.
    • 음수를 리턴하면 자식 프로세스 생성이 실패한 것이다.
int main()
{
    int pid = fork();

    if(pid > 0)
        printf("%s", "parent");
    else if(pid == 0)
        printf("%s", "child");
    else
        printf("%s", "error");

    return 0;
}

위 코드에서는 fork()가 성공하면 결국 "parent"와 "child"가 모두 출력된다. 그러나 부모 프로세스와 자식 프로세스는 서로 독립적이라 무엇이 먼저 출력될지는 알 수 없다.



(3) 프로세스의 전환

  • exec()

    • 기존의 프로세스를 덮어쓰는 새로운 프로세스 생성
    • fork()가 기존 프로세스를 그대로 둔 채 새로운 프로세스를 복사하는 것과 다르게 exec()은 현재 프로세스를 완전히 다른 프로세스로 전환한다.
    • 프로세스의 구조를 재활용하기 위해 사용한다.
  • exec() 호출 동작 과정

    • 코드 영역을 지우고 새로운 코드로 바꾼다.
    • 데이터 영역이 새로운 변수로 채워진다.
    • 스택 영역은 리셋된다.
    • PID, PPID, CPID는 유지된다.
    • 프로세스 생성과정을 처음부터 거치는 것이 비효율적이기 때문에 기존에 확보한 메모리 영역을 그대로 쓰기위해 사용하는 시스템 호출 함수이다.



(4) 유닉스 프로세스의 계층 구조

  • 유닉스가 부팅되면 init이라는 커널관련 프로세스가 생성되며 이 프로세스는 모든 프로세스의 부모이다.

  • 사용자별로 login 프로세스를 실행시킬 때는 기존 login 프로세스를 fork()한다.

  • login이 끝나면 shell을 새로 실행시키는 것이 아닌 login 프로세스를 exec()하여 shell을 만든다.

    • 자원 사용이 효율적이게 된다.
  • shell에서 명령어를 통해 새로운 프로그램을 실행시키면 shell은 fork()를 통해 프로세스를 복사하고 이를 exec()하여 새로운 프로세스를 생성한다.

    • ex) shell에서 웹브라우저를 실행시키면 shell을 fork()하여 새로운 shell을 만들고 이것을 exec()하여 웹브라우저 프로세스로 덮어쓴다.
  • 계층구조를 사용하면 자원 회수가 용이하다.

    • 모든 자식 프로세스가 종료될 때는 부모 프로세스가 자원을 회수하므로 운영체제의 부담이 줄어든다.
  • 미아 프로세스

    • 부모 프로세스가 먼저 종료된 경우
    • 자식 프로세스가 비정상 종료되어 부모 프로세스가 미처 자원을 회수하지 못한 경우
    • 자원 낭비가 발생한다.

unix_process_hierarchy



## 4. 스레드 ##

(1) 스레드의 개념

  • 스레드의 정의

    • CPU 스케줄러는 프로세스가 해야할 일을 CPU에 전달하고 실제 작업은 CPU가 수행한다.
    • CPU 스케줄러가 CPU에 전달하는 '일 하나'가 스레드이다.
    • CPU가 처리하는 작업 단위가 프로세스로부터 전달받은 스레드이다.
    • 운영체제 입장에서의 작업 단위가 프로세스라면 CPU 입장에서의 작업 단위는 스레드이다.
  • 멀티 스레드

    • 프로세스 내 작업을 여러개로 분할하여 동시에 실행
  • 멀티 태스킹

    • 멀티 스레드에서 여러 스레드에 잘게 시간을 배분하여 CPU를 할당하여 여러 일을 동시에 처리하는 것
  • 멀티 프로세스

    • CPU를 여러개 혹은 코어를 여러개 사용하여 여러 프로세스를 동시에 실행하는것
    • 슈퍼스칼라
  • CPU 멀티스레드

    • 파이프라이닝을 통하여 하나의 CPU에서 여러 스레드를 동시에 처리하는 병렬처리 기법



(2) 멀티 스레드의 구조

  • 동시 처리를 위해 fork()를 사용하면 코드 영역과 데이터 영역이 새로 할당되고 그 중 일부가 메모리에 중복되어 존재하게 되어 낭비를 불러온다.

  • 같은 프로세스의 스레드끼리는 코드영역과 데이터영역을 공유하므로 자원 낭비가 없어 효율성이 올라간다.

multi_thread



(3) 멀티스레드의 장단점

  • 장점

    • 응답성 향상

      • 입출력과 다른 부분을 멀티스레드로 나누면 입출력 요청이 발생해 해당 스레드가 대기상태로 들어가더라도 나머지 스레드는 실행상태에 있을 수 있다.
    • 자원 공유

      • 자원 공유로 인한 메모리 효율성이 증가한다.
    • 다중 CPU 지원

      • 여러 CPU나 코어를 가진 컴퓨터의 경우 여러 스레드가 동시에 처리될 수 있다.


  • 단점
    • 자원을 공유하므로 하나의 스레드에 문제가 생기면 같은 프로세스 내의 다른 스레드와 상관없이 프로세스 전체에 문제가 발생한다.
    • ex) 인터넷 익스플로러는 멀티탭을 멀티 스레딩으로 구현하여 하나의 탭에서 문제가 생겨 종료하면 익스플로러 전체가 종료된다. 구글 크롬은 멀티 프로세싱 방식으로 멀티탭을 구현했다.



(4) 멀티스레드 모델

  • 사용자 레벨 스레드
    • 초기의 스레드 시스템
    • OS가 멀티스레드를 지원하지 않아 만들어 사용하는 방식
    • 라이브러리를 사용하여 사용자 레벨에서 직접 구현
    • 커널 입장에서 이 스레드는 하나의 프로세스로 보인다.
    • 하나의 커널 스레드와 사용자의 여러 스레드가 연결되는 1 to N 모델
    • 문맥 교환이 필요없어 효율적
    • 전체 프로세스에 배정받는 하나의 타임 슬라이스를 공유하므로 CPU 점유시간이 작다.
    • 하나의 스레드가 입출력 요청을 하면 전체 스레드가 묶여서 대기상태로 옮겨진다.


  • 커널 레벨 스레드
    • 커널이 지원해주는 멀티스레드
    • 하나의 커널 스레드가 하나의 사용자 스레드와 연결되는 1 to 1 모델
    • 커널 레벨 스레드는 독립적으로 스케줄링된다.
    • 하나의 사용자 스레드가 대기상태에 들어가도 프로세스의 나머지 스레드는 실행 가능
    • 스레드별로 문맥교환이 발생하여 자원 측면에서는 사용자 레벨 스레드에 비해 비효율적이다.


  • 멀티 레벨 스레드(하이브리드 스레드)
    • 사용자 레벨 스레드와 커널 레벨 스레드를 혼합한 방식
    • 하나의 커널 레벨 스레드가 대기상태에 들어가면 다른 커널레벨 스레드가 대신 작업
    • 문맥 교환에 의한 오버헤드는 사용자 레벨 스레드보다 크고 커널 레벨 스레드보다는 작다

+ Recent posts