프로그램은 매우 단순한 일을 한다: 명령어를 실행한다.
프로세서는 명령어를 초당 수백만 번 (요즘은 수십억 번) 반입(fetch)하고, 해석(decode)하고 (즉, 무슨 명령어인지 파악하고), 실행(execute)한다.
명령어 작업을 완료한 후 프로세서는 다음 명령어로, 또 그 다음 명령어로 프로그램이 완전히 종료될 때까지 실행을 계속한다.
OS
컴퓨터에서 실행되고 컴퓨터 하드웨어를 관리하고 다양한 응용 소프트웨어가 효율적으로 실행될 수 있도록 공통 서비스를 제공하는 소프트웨어
컴퓨터 자원을 관리하는 SW이다.
🔷 가상화
OS는 이러한 작업을 위해 가상화(virtualizatoin) 기법을 사용한다.
운영체제는 프로세서,메모리,또는 디스크와 같은 물리적(physical) 자원을 이용하여 일반적이고, 강력하고,사용이 편리한 가상 (virtual)형태의 자원을 생성한다. 때문에 운영체제를 때로는가상 머신(virtualmachine) 이라고 부른다.
OS는 사용자에게 OS의 기능을 이용할 수 있는 API, 시스템 콜(표준 라이브러리)을 제공한다.
가상화는 많은 프로그램들이 CPU를 공유하여, 동시에 실행될 수 있게 한다. 프로그램들이 각자 명령어와 데이터를 접근할 수 있게 한다. 그래서 OS를 자원관리자라고 부르기도 한다.
자원은 CPU, 메모리, 디스크를 일컫는다.
🔳 CPU 가상화
하나의 CPU 또는 소규모CPU집합을 무한 개의 CPU가 존재하는 것처럼 변환하여 동시에 많은 수의 프로그램을 실행시키는 것을 CPU가상화(virtualizing the CPU) 라 한다.
🔳 메모리 가상화
물리적 메모리는 단순한 바이트의 배열이다.
malloc()을 이용해 메모리에 공간을 할당하고, 정수 값을 넣은 뒤 1초마다 1씩 더하며 출력하는 프로그램이 있다.
이 프로그램을 동시에 두 개 실행한다면, 두 프로그램의 malloc()은 동일한 메모리 주소를 반환한다. => 같은 공간을 할당받은 것처럼 보인다.
그러나 두 프로그램의 출력 값은 서로 영향을 미치지 않고, 독립적으로 1초마다 1씩 더하며 출력된다.
마치 두 프로그램이 각각 자신의 메모리를 갖고 있는 것처럼 보인다. 운영체제가 메모리 가상화(virtualizing memory) 를 하기 때문에 가능하다.
각 프로세스는 자신만의가상 주소 공간을 받는다. 운영체제는 이 가상 주소 공간을 컴퓨터의 물리 메모리로 매핑한다.
하나의 프로그램이 수행하는 각종 메모리 연산은 다른 프로그램의 주소 공간에 영향을 주지 않는다.
※ 쓰레드
프로세스: 실행 중인 프로그램의 “그릇”. 운영체제는 프로세스마다 독립된 가상 주소 공간(메모리), 자원(파일 등)을 관리함.
쓰레드: 한 프로세스 안에서 실제로 “CPU에서 실행되는 흐름(실행 단위)”.
한 프로세스는 여러 쓰레드를 가질 수 있다.
멀티쓰레드(Multithreading)의 핵심 특징
(1) 메모리를 공유함
같은 프로세스 안의 쓰레드들은 보통 다음을 공유합니다.
코드 영역(프로그램 코드)
힙(heap)
전역 변수(static/global)
열린 파일(파일 디스크립터 등)
그리고 각 쓰레드는 자기만의 것도 가집니다.
레지스터 상태
스택(stack) (각 쓰레드마다 스택이 따로 있음)
쓰레드 ID 등
멀티쓰레드는 같은 메모리를 여러 실행 흐름이 동시에 만지는 것이어서 동기화 문제가 필연적으로 생긴다.
CPU 코어가 1개여도, OS가 매우 빠르게 쓰레드를 번갈아 실행시키면 동시에 실행되는 것처럼 보임(시분할).
CPU 코어가 여러 개면, 진짜로 동시에 실행될 수 있음(병렬 실행).
경쟁 조건
여러 쓰레드가 같은 데이터(공유 변수) 를 동시에 읽고/쓰면,
실행 순서에 따라 결과가 달라지는 현상
간단하게, 쓰레드를 동일한 메모리 공간에서 함께 실행 중인 여러 개의 함수라고 생각할 수 있다.
쓰레드를 pthread_create 등으로 생성할 때, 안에 함수를 넣어주는데, 쓰레드는 fork처럼 남은 코드를 실행하는 느낌이 아니라 그냥 넣어준 함수를 독자적으로 실행한다는 느낌으로 이해하면 될 것 같다.
코드 중 pthread_join은 쓰레드가 끝날 때까지 기다려주는 함수이다. 이를 실행하지 않으면 호출 프로세스가 종료되며 쓰레드 생성이 의미가 없어질 수 있다.
🔷 병행성
프로그램이 동시에 많은 일을 하려 할 때 발생하는 그리고 반드시 해결해야 하는 문제들
병행성 문제는 운영체제만의 문제가 아니다. 멀티 쓰레드 프로그램도 동일한 문제를 드러낸다.
멀티 쓰레드 프로그램의 예이다.
전역 변수 counter =0 이 존재한다.
두 쓰레드는 counter를 N회만큼 증가시킨다. (counter++)
결과는 counter = 2N이 이상적이나,
더 적은 값이 매번 다르게 나타난다.
이유는 명령어는 한 번에 하나씩만 실행되기 때문인데,
counter++ 코드는
- counter값을 메모리에서 레지스터로 탑재하는 명령어
- 레지스터를 1증가시키는 명령어
- 레지스터의 값을 다시 메모리에 저장하는 명령어
이렇게 세가지의 명령어로 구성된다.
그러나 이 세 개의 명령어는 원자성(한 번에 모두 실행되거나 모두 그렇지 않거나)을 보장받지 못한다.
그렇기 때문에, 조금 로우레벨 단에서 명령 실행 순서가 꼬이며 이상적으로 값이 증가하지 않게 된다.
이를 병행성 문제라고 한다.
OS가 겪는 문제 중 하나이다.
🔷 영속성
DRAM과 같은 장치는 데이터를 휘발성 방식으로 저장하기 때문에, 전원이 꺼지거나 시스템에 문제가 생기면 데이터가 쉽게 손실될 수 있다.
따라서 데이터를 영속적으로 저장할 수 있는 하드웨어와 소프트웨어가 필요하다.
하드웨어에는
- SSD
- 하드 드라이브 등이 있고,
소프트웨어에는
- 파일 시스템이 있다. 파일 시스템은 사용자가 생성한 모든 파일을 안전하게 저장한다.
OS는 CPU나 메모리에 가상화를 제공하지만, 개인 프로그램들에겐 가상 디스크를 따로 제공하지 않는다.
여러 프로그램들이 오히려 파일 공유를 원한다고 가정한다.
🔷 OS의 설계 목표
앞선 내용에서 OS는 CPU, 메모리, DISK를 가상화하고, 파일을 영속적으로 저장한다.
OS의 설계 목표는
- 시스템을 추상화 시스템을 편리하고 사용하기 쉽게 만듦
- 고성능 제공
최소한의 오버헤드
- 시간적 오버헤드 : 많은 명령어
- 공간적 오버헤드 : 많은 저장공간 사용
- 응용프로그램간 보호 격리 (isolation) : 하나의 나쁜 행위 프로세스가 다른 프로세스나 OS자체에 해가 없게
- 높은신뢰도
- 다른문제
- 에너지효율energy-efficiency
- 보안security
- 이동성mobilit
추상화는 하드웨어를 소프트웨어로 개념화하는 것인데,
- CPU → Process structure
- Memory → Memory management structure
- Disk -> Files, File, Inode
이렇게 된다.
가상화는 하나를 여러개처럼 보이게 만드는 것이다.
OS를 가상화, 병행성, 영속성 측면에서 공부할 것이다.
🔷 프로세스
| CPU(처리기)를 할당받아 실행될 수 있는 실행 단위
소프트웨어는 하나 이상의 프로그램으로 구성됨
프로그램은 일련의 명령어로 이루어진 실행 가능한 파일임
소스 코드는 고급언어로 이루어짐. 프로그램이 아님
소스 코드가 컴파일이 된 후 프로그램이 됨
프로세스 = 실행중인 프로그램
사용자 프로세스 : 메모리 내 사용자 공간에 상주, 사용자 모드에서 사용자 코드 실행 시스템 호출을 통해 커널 모드로 전환하여 커널 코드 실행을 요청할 수 있음
커널 : 메모리 내 컨러 공간에 상주, 커널 모드에서 OS커널 코드를 실행 커널은 프로세스로 보긴 애매하다. 커널은 그냥 커널 그 자체다.
하나의 프로그램은 여러 프로세스로 동시에 실행될 수 있음
프로세스의 구성요소
- 프로세스 id : 프로세스 식별자 (pid)
- 사용자 id : 사용자 식별자 (uid)
- 프로세스 그룹 id : 모든 프로세스는 특정한 프로세스 그룹에 속함. 그룹 식별자(pgid)
- 주소 공간 : 프로세스가 저장되는 주 메모리 공간
- 프로그램 : 코드 또는 텍스트
- 정적 데이터
- 동적 데이터
프로세스는 주어진 시간, time quantum이 있다.
프로세스는 CPU를 영원히 쓰지 못함
- 운영체제는 여러 프로세스를 공정하게 실행해야 하므로 각 프로세스에게 **일정 시간(Time quantum)**만 CPU를 줌
시간이 끝나면:
문맥 교환(Context Switch)
다른 프로세스 실행 이 된다.
PCB(Process Control Block)
| 프로세스를 관리하기 위한 커널 자료구조
추상 개념이 아니라 struct / class로 구현되어 있는 실제 코드이다.
식별자 (PID)
→ 프로세스 번호상태
→ running / ready / waiting 등우선순위
프로그램 카운터 (PC)
→ 다음에 실행할 명령어 주소메모리 포인터
→ 코드/데이터/스택 위치문맥 데이터 (레지스터 값)
→ 문맥 교환 시 저장입출력 상태 정보
어카운팅 정보
→ CPU 사용 시간, 실행 시간 등
등등이 들어있다.
프로세스 메모리 (주소 공간)
※이때 주소 공간이라 함은 가상 주소를 일컫는다.
프로세스는 가상 주소로는 연속적인 공간을 할당받지만, 물리적으로는 그렇지 않다.
프로세스 주소 공간은
text 섹션 : 프로그램 코드(컴파일된 코드, 소스 코드 아님) 즉 명령어를 저장하는 곳
데이터 섹션 :
- 동적 데이터
- 정적 데이터
- 힙 : 동적 메모리 할당 구역
- 스택 : 함수의 인수, 지역 변수, 반환 값 등 저장. 함수 호출과 함께 커지고, 반환과 함께 작아짐
이때 힙(동적 메모리)을 사용하는 이유?
- 무언가 변수의 크기를 실행 중에 알게 될 때
- 변수의 수명이 함수보다 길어야 할 때
- 매우 큰 메모리가 필요할 때 => 스택은 작음
- 자료구조 크기가 가변적일 때 (Linked list, Tree, Graph, Hash …)
등을 위해 사용하고,
- 힙은 주소가 낮은 주소 -> 높은 주소로 성장하고,
- 스택은 주소가 높은 주소 -> 낮은 주소로 성장한다.
사이의 공간, 주소는 이 프로세스에 예약된다. 다른 프로세스가 사용하지 못 한다. 이때, 이 사이의 공간이 사용되지 않을 때 낭비되지 않나? 생각이 들었는데,
이러한 모든 주소는 다 가상 주소이다. 64비트 시스템에서 가상주소 공간의 크기는 2의 64제곱. 아주 크다.
즉 실제 물리 공간을 정해두고 사이를 비워두는 것이 아니기 때문에 공간의 낭비라고 보기 어렵다.
프로세스의 link
OS 내의모든 프로세스는 다른 프로세스에 의해 생성됨.
첫 번째 프로세스는 init / systemd 프로세스임
즉 모든 프로세스는 init 혹은 systemd의 직/간접 자식 프로세스임
link는 부모-자식간, 형제간 link가 있음
부모/자식/형제가 없는 경우 해당 link는 null이 된다.
- 프로세스의 자식은 프로세스가 생성한 프로세스
- 형제 프로세스는 부모가 같은 모든 프로세스
형제 프로세스 간의 직접적인 포인터를 두지는 않고, 부모 프로세스의 pcb의 children list에 들어있으면 형제라고 하는데, 말이 달라 추후 확인이 필요할 듯 하다.
프로세스 API
create(생성)
디스크에 있는 프로그램 코드를 메모리에 로딩한다 OS는 로딩 작업은 느리게 수행한다.
프로세스의 가상 주소 공간을 생성하고, 실행 파일의 각 영역을 해당 가상 주소에 매핑한 뒤, 필요 시 디스크로부터 메모리에 적재하여 실행한다.
CPU가 그 프로세스의 가상 주소 공간에 매핑된 메모리에서 명령어와 데이터를 가져온다.
CPU는 디스크를 모름 CPU는 오직:
- 가상 주소 → 물리 주소 → 메모리
이 경로만 사용
- 가상 주소 → 물리 주소 → 메모리
프로그램의 런타임 스택 할당 지역변수, 함수파라미터, 리턴주소 등 함수 인자로 스택 초기화. ex) main의 argc, argv
프로그램의 힙 생성 명시적으로 요청된 동적 할당 malloc() : 할당 free() : 반환
OS는 다른 초기화 작업을 진행한다. I/O 셋업을 하는데, 표준 입력, 표준 출력, 표준 에러 3개의 file descriptor를 할당한다.
진입점인 main()에서 프로그램 시작 이때 OS는 CPU 제어권을 새로 생성된 프로세스로 전달한다.
destory(종료) 프로세스 종료
wait(대기) 부모 프로세스가 자식 프로세스의 종료를 기다리고, 그 종료 상태를 회수(reap)하는 동작
exec 계열 함수
사용자 프로그램이 운영체제에 “새 프로그램을 실행해 달라"고 요청하는 공식 API
status(상태) 프로세스의 상태를 확인
… 등등의 API가 있다.
프로세스의 상태(status)
프로그램은 프로세스 형태로 생성 되어야 수행이 된다.
개별 프로그램의 관점에서 프로그램은 자신에 속한 일련의 명령어 들을 수행한다.
즉 개별 프로세스는 그 프로세스를 위해 수행되는 일련의 명령어 리스트로 프로세스 행위의 특성을 표현할 수 있고, 이때 이 list를 프로세스의 궤적, trace라고 한다.
cpu(처리기)의 관점에서 cpu는 pc 레지스터(프로그램 카운터)가 가리키는 명령어들을 수행한다. 단일 cpu는 여러 프로세스가 시간에 따라 번갈아 수행된다. 즉 pc는 여러 프로세스의 명령어를 가리키게 된다.
즉 cpu의 행위의 특성은 다양한 프로세스들의 trace가 인터리빙된 형태로 나타난다.
2-state process model
state
- running(수행)
- not running(비수행)
상태의 전이
OS는 프로세스를 생성할 때 그 프로세스에 대한 프로세스 제어 블록 (PCB)를 생성하고, 프로세스는 비수행 상태로 시스템 내에 초기화한다.
이후 프로세스는 존재하며 수행될 차례를 기다린다.
때때로 현재 수행 중인 프로세스가 인터럽트에 의해 중단되며,
운영체제의 스케줄러가 다음에 수행할 프로세스를 선택한다.
이때 인터럽트된 이전 프로세스는 수행 상태에서 비수행 상태로 전이되고,
디스패처에 의해 선택된 다른 프로세스가 수행 상태로 전환된다.
PCB 프로세스의 상태, 프로그램 카운터, 레지스터 값, 메모리 관리 정보, 스케줄링 정보 등 각 프로세스의 정보를 저장하는 struct형 커널 자료구조이다.
- 수행 프로세스를 인터럽트한 후 나중에 그 인터럽트가 발생되지 않은 것처럼 프로세스 수행을 재개할 수 있도록 충분한 정보를 유지하는 것
수행되지 않는 프로세스의 PCB는 상태에 따라
ready queue 또는 각종 waiting queue에 저장된다.
three state process model
state
- running (실행) 프로세스가 처리기(cpu)상에서 실행
- ready (준비) 프로세스가 실행 준비
- blocked 프로세스가 특정 작업을 수행 프로세스가 I/O 작업을 요청하면 블럭 상태가 되고, 다른 프로세스가 처리기에서 실행된다.
five state process model
swapping : 프로세스의 일부나 전체를 주기억장치로부터 디스크로 옮겨 놓는 방법
state
- running, ready, blocked (three state와 동일)
- swap-in swap-out 된 프로세스의 전체 혹은 일부 이미지를 스왑 공간 -> 메모리로 이동
- swap-out 종료되지 않은 프로세스의 전체 혹은 일부 이미지를 메모리 -> 스왑 공간으로 이동
스왑 공간은 disk의 일부이다. -> 입출력 연산에 성능 저하 가능성? => swap area 는 보통 linked list로 구성되어 성능 저하가 거의 없다.
9-state model 도 있다.
문맥
- System context
- Memory context
- Register context(HW context)
문맥 교환 어떤 프로세스가 interrupt 될 때 그 순간의 PC, 레지스터 값들이 해당 프로세스의 PCB에 저장되고(save the old context), 해당 프로세스의 상태가 block / ready 등의 상태로 변경, 이후 OS는 다른 프로세스를 선택하여 수행 상태로 만들고, 그 프로세스의 PCB에서 값을 가져와 cpu에 적재해 새로운 프로세스를 수행하는 것
다수의 프로세스를 지원하고 다중 처리를 제공할 수 있게 해주는 도구가 가상화(virtualization)이다.
Proccess API
- fork()
- wait() If no parent waiting (did not invoke wait()) process is a zombie If parent terminated without invoking wait , process is an orphan
- exec()
- pipe()
- open()
- close()
fork와 exec을 분리하면,
fork()로 자식 프로세스를 먼저 만든 뒤,
그 자식 프로세스에서- 입출력 재지정(IO redirection)
- 파이프(pipe) 연결
- 파일 디스크립터 조작
등의 설정을 자유롭게 수행한 후
마지막에
exec()를 호출하여 실제 실행할 프로그램으로 교체할 수 있다.
fork()와exec()를 분리함으로써
프로그램 실행 전에 환경을 조작할 수 있는 유연성이 생긴다
Limited Direct Execution
- cpu는 여러 프로세스가 공유한다.
단, 코어 하나당 하나의 프로세스만 실행이 가능해, 번갈아가며 cpu를 사용한다.
- Interleaved execution
- Timesharing
다수의 프로세스를 지원하고 다중 처리를 제공할 수 있게 해주는 도구가 가상화(virtualization)이다.
가상화를 구현할 때는,
- 시스템의 과도한 오버헤드 없이 가상화를 구현해야한다.
효율적인 가상화를 위해서는 HW와 OS의 지원이 반드시 필요하다.
기본 기술 : Limited Direct Execution
- CPU에 직접 실행
OS가 프로그램을 실행을 시작하면
- process list에 한 항목 생성
- 메모리 페이지 할당
- 디스크에 있는 프로그램 코드를 할당된 메모리 페이지에 로드
- 진입점(main)에 포인터 위치
를 하는데,
이때 다음과 같은 문제점이 있다.
- 프로세스가 원치 않는 일을 안 한다는 보장이 있는가?
- 프로세스 실행 중, OS가 이를 중단시키고, 다른 프로세스를 실행할 수 있는가? => time sharing
OS가 실행중인 프로그램을 제어하지 못하면 그건 그냥 라이브러리에 불과하다. 그냥 필요할 때 다른 프로그램을 호출하는 것 뿐이기 때문이다.
직접 실행의 문제점 #1 제한된 연산
직접 실행은 빠르다!
그러나 프로세스를 CPU에서 직접 실행하게 두면,
프로그램이
- 디스크 I/O를 마음대로 수행하고
- 메모리를 아무 주소나 접근하고
- 파일 권한을 무시하고
- CPU 제어 레지스터를 건드릴 수 있음 → 이는 시스템 전체를 망가뜨릴 수 있는 상황
이때 user mode vs kernel (privilege) mode 이렇게 모드를 나눠서 민감한 연산은 커널 모드로 진행해서 방지가 가능하다.
특권 명령 실행은 (user mode) system call -> trap 특수 명령어 실행 -> kernel mode
특권 명령 종료는 trap 함수 리턴 -> user mode
HW는 다양한 실행 모드를 제공하여 OS를 지원한다.
실행 모드를 이렇게 나눠 놓으면 사용자 프로그램으로부터 OS, PCB 등 주요 OS table을 보호 가능하다.
User mode
- 권한을 덜 가진 보통 사용자 모드, 사용자 프로그램은 일반적으로 user mode로 수행한다.
- 응용 프로그램은 하드웨어 리소스에 OS를 거쳐야만 완전히 액세스 가능하다
Kernel mode
- 더 많은 권한을 가진 모드
- OS는 시스템의 모든 리소스에 액세스 가능하다
- 커널 모드로 수행되는 소프트웨어는 cpu와 모든 명령들, 레지스터들, 메모리를 제어 가능하다
모드를 구분하는 하드웨어가 제공하는 비트가 있다 : 프로그램 상태 워드(PSW)의 한 비트를 사용하여 수행 비트를 나타낸다
user mode -> kernel mode
- 사용자가 OS 서비스 호출(system call 호출)
- 인터럽트가 OS 커널 수행을 촉발(trigger)시킬 때
kernel mode -> user mode
- 제어가 OS에서 사용자 프로세스로 복귀할 때 (return from syscall)
인터럽트, 트랩, 시스템 호출
인터럽트 현재 수행 중인 프로세스와는 독립적으로 외부에서 유발되는 여러 종류의 사건 (입출력완료 등)에 의해 발생
트랩 불법적인 파일 접근 시도처럼 현재 수행되고 있는 프로세스에서 생성되는 오류나 예외 조건 때문에 발생
OS가 트랩에서 리턴을 발행할 때 올바르게 리턴할 수 있도록 호출자의 등록 상태를 저장한다.
인터럽트가 발생한 경우
- 인터럽트 핸들러로 제어가 넘어감
- 인터럽트 핸들러는 기본적 시스템 관리 작업을 수행한 후, 발생된 인터럽트 유행에 따라 관련 OS 루틴으로 분기한다.
인터럽트 예
- 클럭 인터럽트 (clock interrupt) : 시간할당량(time slice)을 모두 사용했는지 여부 결정
- 입출력 인터럽트
- 메모리폴트 (memory fault, 메모리부재)
리눅스에서 시스템 콜 등록하는 방법
시스템 콜 테이블 등록 /usr/src/linux/arch/x86/entry/syscalls/syscall_64.tbl 에
common my_new __x64_sys_my_new이렇게 등록시스템 콜 함수 프로토타입 선언 include/linux/syscalls.h 에
asmlinkage long sys_my_new(int n);이렇게 등록커널 함수 코딩 kernel/에 별도 파일 구현하거나, kernel/sys.c에 구현
커널 컴파일
limited direct execution protocol
커널은 부팅 시 트랩 테이블 만들고, 이를 이용해 시스템을 통제한다.
인터럽트 테이블 (트랩 테이블)로 하드웨어가 예외 사건이 발생하면 어떤 코드가 실행되어야 하는지 알려줄 수 있다.
인터럽트 발생 시 OS는 특권 명령어를 사용하여 인터럽트 테이블을 찾고, HW/SW 인터럽트의 위치를 파악한다. 인터럽트 테이블에 무엇을 해야하는지 커널함수를 정의한다.
제한적 직접 실행 프로토콜
전반부)
- 부팅 시 커널은 인터럽트 테이블 초기화, cpu는 해당 테이블의 위치를 기억한다. 나중에 사용하기 위함이다.
- 이러한 작업은 커널이 커널모드에서 사용하는 특권 명령어를 이용한다.
후반부) 프로세스 실행
- return-from-trap 을 이용하여 사용자 프로세스 시작 시 몇 가지 작업 수행
- 새로운 프로세스를 위한 노드를 할당하고 프로세스 리스트에 삽입하고 메모리 할당 등 작업..
- return-from-trap을 통해 CPU는 사용자 모드로 전환하고 프로세스 시작
- 프로세스가 시스템 콜 호출하면 OS로 트랩 -> 운영체제는 트랩 처리하고 하고 retrun-from-trap을 통해 제어를 프로세에게 넘김
- 이후 프로세스가 일을 다 하면 main() 에서 리턴
- exit() -> 시스템 호출하고 OS로 트랩
직접 실행의 문제점 #2 프로세스간 전환
협조적 접근 방법 (Cooperative approach)
프로세스가 yield()를 호출하여 스스로 cpu를 양보함(OS에게 제어권을 넘김)
또는 비정상적 행위로 trap이 발생하는 경우 OS가 제어권을 가짐
이때, 프로세스가 yield()를 호출하지 못하는 순간이 오면 문제 발생
비협조적 접근 방법 (Non-cooperative approach)
- Timer Interrupt 일정 시간마다 cpu에 인터럽트 발생 LAPIC (Local Advanced Programmable Interrupt Controller) - 타이머 인터럽트를 발생시키는 HW (CPU 내부에 있음)
문맥의 저장과 복구 (Saving & Restoring Context)
OS가 CPU의 제어권을 얻었을 때, “현재 프로세스를 계속 실행할 것인가? 아니면 다른 프로세스로 전환할 것인가?” 의 문제가 발생한다.
Scheduler : 다음에 실행할 프로세스를 결정한다,
A = schedule(); //→ 다음 실행할 프로세스 A 선택 switch(B, A); //→ B에서 A로 전환
PCB를 리눅스에선 task_struct라 한다.
앞서 문맥에는
- System context
- Memory context
- Register context(HW context) 이 있다고 했다.
다른 프로세스로의 전환이 발생하면 문맥 교환이 이루어진다.
모드의 전환은 현재 수행상태에 있는 프로세스의 상태를 바꾸지 않고 변경 가능하다. 이때 발생하는 오버헤드는 거의 없다.
프로세스 교환 예
(1) exit() → schedule() → switch()
좀비(zombie) 상태
상황
- 프로세스가
exit()호출 - 즉, 자기 할 일을 끝냄
- 더 이상 실행할 수 없으므로 OS가
schedule()호출
상태 변화
실행 중 → 종료 상태
자원 정리는 아직 안 됨 → 좀비(zombie) 상태
부모가
wait()하면 완전히 제거
(2) I/O 요청 → schedule() → switch()
(블록 / sleep 상태)
상황
- 프로세스가 디스크, 네트워크 등 I/O 요청
- 결과를 바로 받을 수 없음
CPU는 기다릴 수 없기에,
프로세스를 블록(blocked) / sleep 상태로 변경
schedule()호출다른 프로세스로 문맥 교환
(3) 타이머 인터럽트(Time Quantum) → schedule() → switch()
(준비 상태)
상황
- 프로세스가 잘 실행 중
- 하지만 타임 슬라이스 종료
상태 변화
실행 → 준비(ready) 상태
다시 준비 큐로 들어감 -> swap / suspend 상태…
다시 한 번 프로세스의 교환이 이루어지면
- 현재 실행중인 프로세스의 문맥 저장 (PCB, 커널 스택에)
- Program Counter (PC)
- CPU 레지스터들
- Stack Pointer
- 상태 플래그 등 👉 “지금 어디까지 실행했는지” 전체 스냅샷
- PCB 갱신 (상태 전이, 실행을 멈춘 이유 등 추가 기록)
- 큐 이동 (프로세스 상태에 따라 적절한 큐로 이동)
- 다음 실행할 프로세스 선택
- 선택된 프로세스의 PCB 갱신(상태 준비->실행)
- 메모리 관련 자료구조 갱신
- 문맥 복원
병행성 문제
- 기본적으로 OS는 중첩 인터럽트를 금지 (불능화)
- 인터럽트를 처리하는 중에는 중첩 인터럽트가 처리되지 않음
- 오랫동안 중첩 인터럽트를 금지하면 새로운 인터럽트 처리 문제 발생 -> 락 기법 사용(중요한 부분만 보호하자)