카테고리 없음 / / 2021. 3. 29. 09:33

운영체제 5주차


<4주차 복습>

멀티 태스킹(?)

다중 프로그래밍은 메모리에 여러 프로세스를 유지시키는 기법(항상 어떤 프로세스를 실행하여 CPU 사용율을 최대화 하려고 함)

시분할 시스템은 프로세스들 사이에서 시간적으로 분할해 CPU를 조금씩 사용할 수 있게 한 시스템

 

스케줄링 큐

  • 운영체제는 스케줄링 위해 PCB(Process Control Block)를 큐(queue)로 관리한다.
    • 입출력 장치 Queue
      • 각 입출력 장치는 자신의 장치 큐를 가진다.
      • 입출력 장치에서 대기하는 프로세스들을 관리
      • 프로세스는 CPU를 할당받아 실행하다가 입출력 요청을 하게 되면, 입출력 요청의 완료를 기다리게 된다. 이때, 입출력 장치에 다른 프로세스도 기다리고 있을 수 있으므로 이런 프로세스들을 연결하여 큐로 관리한다.

    • Ready Queue
      • ready 상태의 프로세스들을 관리
      • ready 상태의 PCB를 링크드 리스트로 관리
        • ready queue의 헤더는 첫 번째 PCB와 마지막 PCB를 가리키는 포인터를 가지고 있다.
        • PCB는 다음 PCB를 가리키는 포인터를 가지고 있다.

스케줄러

  • 장기 스케줄러(Long-term scheduler or job scheduler)
    • 작업 스케줄러라고도 부르며 어떤 프로세스를 주비 큐에 삽입할지를 결정하는 역할을 한다. 디스크에서 하나의 프로그램을 가져와 커널에 등록하면 프로세스가 되는데 이때 디스크에서 어떤 프로그램을 가져와 커널에 등록할지를 결정. 장기 스케줄러는 수십 초 내지 수 분 단위로 호출되기 때문에 상대적으로 속도가 느린 것이 허용된다. 또한, 장기 스케줄러는 메모리에 동시에 올라가 있는 프로세스 수를 조절하는 역할을 한다. 하지만 현대의 시분할 시스템에서 사용되는 운영 체제에는 일반적으로 장기 스케줄러를 두지 않는 것이 대부분이다. 과거에는 적은 양의 메모리를 많은 프로세스들에게 할당하면 프로세스당 메모리 보유량이 적어져 장기 스케줄러가 이를 조절하는 역할을 했지만 현대의 운영체제에서는 프로세스가 시작하면 장기 스케줄러 없이 바로 그 프로세스에게 메모리를 할당해 준비 큐에 넣어준다.
    • disk에 저장되어 있는 프로그램들 중에서 선택하여 실행하기 위한 기억장치로 적재(지금의 컴퓨터는 사람이 실행하고자 하는 프로그램을 사용자가 선택해서 실행하는 방식이지만 옛날의 컴퓨터 시스템은 여러 개의 프로그램 리스트 중에서 운영체제가 선택하여 실행했다)
    • 초창기 컴퓨터 시스템에서 사용되었으며, 지금은 거의 사용되지 않음.
    • 장기 스케줄러는 다중 프로그래밍 수(degree of multiprogramming)을 제어한다. 즉, 메모리에 적재되는 프로세스의 수. 장기 스케줄러는 이 프로세스의 수를 제어한다. 그런데 메모리에 프로세스를 적재할 때, 프로세스의 성질(IO 중심의 프로세스(IO-bound process)는 CPU 연산보다 입출에 더 많은 시간을 소요하고, CPU 중심의 프로세스(CPU-bount process)는 입출력보다 CPU 연산에 시간을 더 소요한다)을 고려해서 메모리에 적재를 해야 한다. 즉, 다중 스케줄러가 프로그램을 메모리에 적재를 시킬 때, IO-bound process와 CPU-bound process가 적절한 비율로 섞어서 적재하는 게 베스트다. 장기 스케줄러는 요즘에는 거의 사용되지 않지만 다중 프로그래밍 제어, IO-bound process, CPU-bound process 개념은 꼭 알아두도록 하자!
  • 단기 스케줄러(Short-term scheduler or CPU scheduler)
    • 실행 준비가 되어있는 프로세스들 중에서 선택하여 CPU를 할당
    • CPU 스케줄러라고도 하며 준비 상태의 프로세스 중에서 어떤 프로세스를 다음 번에 실행 상태로 만들 것인지 결정한다. 시분할 시스템에서 타이머 인터럽트가 발생하면 단기 스케줄러가 호출된다. 일반적으로 스케줄러라고 하면 단기 스케줄러를 의미하며 단기 스케줄러는 미리 정한 스케줄링 알고리즘에 따라 CPU를 할당할 프로세스를 선택한다. 단기 스케줄러는 밀리 세컨드(ms) 이하의 시간 단위로 매우 빈번하게 호출되기 때문에 수행 속도가 충분히 빨라야 한다.
    • 보통 수십 밀리 초(타이머라는 하드웨어 장치에서 주기적으로 인터럽트를 발생시키고 인터럽트가 수행되면 운영체제가 실행돼서 ISR이 실행되면서 CPU 스케줄러 실행)마다 한 번씩 실행되어 새로운 프로세스를 선택한다.
  • 중기(medium-term) 스케줄러
    • 스왑핑(sqapping): 부분적으로 실행된(아직 끝나지 않은) 프로세스를 디스크에 저장했다가 차후에 다시 주기억장치에 적재를 시킨 후 프로세스 실행을 재개(부분적으로 실행 중인 프로세스를 disk로 빼내는 것은 swap out, disk에 있던 프로세스를 메모리에 다시 적재시키는 것은 swap in이라고 한다)
    • 중기 스케줄러는 어떤 프로세스를 swap out할지, 또 어떤 프로세스를 다시 swap in 할지를 결정
    • 스왑핑은 왜 필요할까? —> 메모리가 부족해서 —> 프로세스들이 메모리에 수십 개, 수백 개 있을 수 있기 때문에, 실행시킨 프로세스들은 메모리에 전부 ready 상태로 대기하고 있으면 메모리가 감당할 수가 없어서 스왑핑 기법을 사용한다.
    • 너무 많은 프로세스에게 메모리를 할당해 시스템의 성능이 저하되는 경우, 이를 해결하기 위해 메모리에 적재된 프로세스 수를 동적으로 조절하기 위해 추가된 프로세스이다. 만약에 메모리에 많은 수의 프로세스가 적재되어 프로세스 당 보유하고 있는 메모리량이 극도로 적어지게 되면 CPU 수행에 당장에 필요한 프로세스의 주소 공간조차도 메모리에 올려놓기 어려운 상황이 발생할 수 있다. 그렇게 되면 디스크 IO가 수시로 발생하게 되어 시스템의 성능이 심각히 저하된다. 이런 경우 메모리에 올라와 있는 프로세스 중 일부로부터 메모리를 통째로 빼앗아 그 내용을 디스크의 스왑 영역에 저장해둔다 -> swap out. 디스크로 스왑 아웃시켜야 하는 경우 봉쇄(blocked, wait, sleep) 상태의 프로세스들을 스왑 아웃시켜도 문제가 해결되지 않는 경우 중기 스케줄러는 타이머 인터럽트가 발생해 준비 큐로 이동하는 프로세스를 추가적으로 스왑아웃 시킨다. 중기 스케줄러는 이러한 방식으로 장시 스케줄러와 마찬가지로 메모리에 올라와 있는 프로세스의 수를 조절하는 역할을 수행한다. 중기 스케줄러의 등장으로 프로세스의 상태에는 중지(suspended, stopped) 상태가 추가되었으며, 중지 상태의 프로세스는 메모리를 통째로 빼앗기고 디스크로 스왑 아웃된다. 중지 상태에는 중지 준비 상태(suspended ready: 준비 상태의 프로세스가 중기 스케줄러에 의해 디스크로 swap out)와 봉쇄 중지 상태(suspended block: 봉쇄 상태(block, wait, sleep)의 프로세스가 중기 스케줄러에 의해 디스크로 swap out)가 있다.

 

프로세스와 관련된 연산(operation)

  • 프로세스의 생성(creation)
    • 운영체제는 부팅 시(메모리에 적재되면) 최초의 프로세스를 생성한다(Unix의 경우 init 프로세스를 생성하고 init 프로세스가 최초의 프로세스가 된다.)
    • 최초의 프로세스는 운영체제에 의해서 생성되고, 그 이후 프로세스는 프로세스들이 수행 되면서 새로운 프로세스를 생성할 수 있다. (Unix의 경우, fork 시스템 호출)
    • 생성하는 프로세스 → parent process, 생성되는 프로세스 → child process
    • 컴퓨터 전원을 킨다 → 부트로더가 수행된다 → 부트로더는 운영체제를 운영체제 파일을 찾아서 메모리 적재시킨다 → 운영체제가 메모리에 적재가 되고, 운영체제는 시스템을 초기화를 수행하고 최초의 프로세스인 init 프로세스를 실행하기 위해 inti 프로그램을 디스크에서 찾아서 메모리에 올린다 → 그 이후는 프로세스들이 다른 프로세스를 생성한다
  •  

CPU 할당 및 자식 프로세스 호출

우리가 어떤 프로세스를 실행하기 위해서는 CPU를 할당해야 한다. 여기서 "CPU를 할당한다"의 의미는 프로세스의 정보가 CPU의 레지스터에 할당된다는 것을 의미한다. 위 템플릿을 참고하자. (cs레지스터는 프로세스의 code 영역의 시작주소, ss는 프로세스 code의 stack(코드의 맨 끝) 시작주소, ds는 데이터 영역의 시작주소)

code 영역 내에 fork()가 호출되면, fork는 시스템 콜이므로 인터럽트가 발생한다. 인터럽트가 발생하면 os가 실행되고 인터럽트 서비스 루틴(인터럽트가 할 일)이 실행되면서 (운영체제 내의)fork 함수가 실행된다. fork 함수에 의해서 자식 프로세스(부모 프로세스의 메모리 내용 ⇒ code, data, stack, heap을 그대로 복제)를 생성한다. fork로 복사된 프로세스는 부모 프로세스의 코드 영역까지 그대로 복사가 된다. 그러면 fork() 명령도 복사가 되기 때문에, '무한으로 자식 프로세스가 생기지 않을까' 라고 생각할 수 있겠지만, 다행이 PC가 fork가 수행되고 리턴된 그 직후의 상태로 복사되기 때문에 자식 프로세스가 자식 프로세스를 생성할 일은 없다.

자식 프로세스에 있는 execlp("/bin/ls", "ls", NULL) 명령으로 인해 자식 프로세스는 완전히 다른 프로세스가 된다. 이게 무슨 말이냐면, execlp 명령은 bin 폴더에 있는 ls 프로그램을 자식 프로세스에 실행(overwrite)시킨다. 자식 프로세스가 Overwrite된 후, ls 프로그램의 main으로 가서 명령을 실행한다. 새로운 프로세스가 생긴 것이 아니라 덧씌워졌기 때문에 PID는 바뀌지 않지만 프로세스를 구성하는 코드(기계어)와 데이터, 힙, 그리고 스택 영역의 값들이 exec으로 인해 새로운 프로그램의 것을 바뀌게 된다. 보통 자식 프로세스를 만들어서 실행하는 경우는, fork로 자신의 프로세스 내용을 복사한 다음, 자식 프로세스가 exec 계열의 명령을 덮어씌움으로써 새로운 프로세스를 만드는 경우가 대부분이다.

  • 자원 공유: 부모-자식 간의 자원 공유 역시 3가로 나뉜다. 부모와 자식이 모든 자원을 공유(기억장치, 개방된 파일, 입출력 버퍼 등)하거나, 일부분만 공유하거나, 아예 공유하지 않거나...
  • 실행
    • 부모와 자식이 서로 독립된 프로세스로서 함께 실행한다.
    • 부모는 자식이 끝날 때까지 기다린다.

프로세스 종료

  • 프로세스는 마지막 명령어의 실행을 끝내고 종료하게 된다.
  • 이 때 프로세스는 exit 시스템 콜 호출을 통해 OS에게 프로세스 삭제를 요청한다.
    • 소스코드에서 사용자가 직접 exit 시스템 콜 코드를 삽입하거나
    • 컴파일러가 기계어 실행파일을 만들 때, 마지막에 exit 시스템 콜을 추가해줌.
  • 운영체제는 프로세스에 할당된 자원(기억장치, 개방된 파일 등)을 회수하고, PCB를 삭제하여 프로세스를 제거한다.
    • 기다리고 있는 부모 프로세스가 있다면(wait 시스템 콜 호출로 인해서) 운영체제는 그 결과를 부모 프로세스에게 알려 대기(wait) 상태에서 준비(ready) 상태로 만든다. 이 때 자식 프로세스의 PCB는 부모 프로세스가 확인한 후 폐기된다.
  • 부모 프로세스가 자식 프로세스를 강제로 종료 → abort(자식 pid) 시스템 콜
    • 사용자가 프로세스를 강제로 종료 → kill 시스템 콜

프로세스 간 통신(Interprocess Communication)

  • 상호 프로세스 간에 협조가 필요한 병행 수행(?)은 프로세스 간에 통신하고 두 프로세스의 행동을 동기화할 수 있는 방법이 필요하다
    • IPC(Interprocess Communication): 프로세스들 간의 통신을 할 수 있게 하고, 프로세스들 간이 행위에 동기화를 제공해주는 기법
    • IPC 매커니즘
      • 공유 메모리(Shared Memory): 프로세스가 메모리를 공유하며 통신
        • e.g. 인쇄 프로그램은 버퍼에 문자들을 저장하고 프린터는 버퍼에 저장된 문자들을 출력한다.
      • 메시지 전달(Message passing): 프로세스들이 서로 메시지를 송수신하며 통신(직접 통신, 간접 통신)
    • send: P1이 P2에게 메시지를 보내려면 send(P2, msg) 시스템 콜 호출 → 시스템 콜이므로 운영체제 호출 → 운영체제에서 send 처리 → 2번 PCB에게 P1으로부터 받은 메시지를 복사한 후 연결해서 관리
    • receive: P2가 P1으로부터 받은 메시지 recevie(P1, buf) 시스템 콜 실행 → 시스템 콜이므로 운영체제 호출 → 운영체제 내의 receive 함수 실행 → P2의 PCB을 참조하여, P2의 PCB에 연결된 msg 내용을 buffer에 복사 → msg를 buffer에 복사했으므로, 운영체제 내의 P2 PCB에 연결된 msg 삭제
    ※ 위 템플릿에서는 단방향 링크로 예를 들었지만, 양방향 링크도 가능하다.
    간접 통신은 우체통(mailbox or port)을 사용하는데, 우체통의 기능을 생각해보면, 편지를 보낼 때, 상대방에게 직접 보내는 게 아니라, 우체통을 통해서 전달하게 된다. 즉, 프로세스들이 메일박스들을 통해서 데이터를 송수신한다. 간접 통신에서의 통신 링크는 메일 박스!
  •  
  •  

※메일박스 이름은 운영체제마다 다를 수 있다.

e.g. P1과 P2가 메일 박스를 통해서 간접 통신을 하려고 한다 → P1이 create_mailbox("명지대 메일박스") 시스템 콜로 메일 박스 생성 → 운영체제가 메일박스 생성 후, 메일 박스 id return → P2 역시 P1과 통신하기 위해서 똑같은 이름의 메일 박스를 생성해야 통신 가능 → create_mailbox("명지대 메일박스") → 운영체제는 "명지대" 라는 메일박스를 생성해야 하지만, 이미 "명지대" 라는 메일박스가 생성되었기 때문에 이미 생성된 "명지대" 메일 박스 id를 P2에게 return → 부여 받은 메일박스 id가 같으므로 양 프로세스는 메일 박스를 공유하고 있다 → P1이 send(id, msg) 시스템 콜 호출 → 운영체제는 메시지를 복사해서 100번 메일박스에 연결 → P2 프로세스가 메시지를 받기 위해 receive(id, buf) 시스템 콜 호출 → 운영체제는 메일 박스 id를 확인한 후, 100번 메일박스에 있는 메시지를 복사해서 buf에 저장(누구한테 왔는지 무관)하고 기존에 메일 박스에 있던 메시지 삭제

 

 

무한 용량은 사실상 가능하지 않고, 대부분 통신 링크에 저장될 수 있는 메시지 용량은 제한돼 있다!!

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유