Operating Systems: Three Easy Pieces: Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
https://pages.cs.wisc.edu/~remzi/OSTEP/
본 글은 위 교재을 주교재로 한 학교 '운영체제(Operating Systems)' 수업을 들으며 공부한 내용을 정리한 글입니다.
해당 글은 개인 공부 및 교육 목적으로 작성하였으며, 일부 교재에 첨부된 사진(또는 교재에서 강의노트로 첨부된 사진)들을 포함하고 있습니다.
교재 출처 사진을 최소화하고자 블로그에는 핵심 사진을 제외하곤 옮기지 않은 사진들도 있습니다. 따라서 고의로 누락한 사진들이 존재하며, 해당 사진들을 언급하는 내용이 있을 수 있습니다. 누락한 사진들은 직접 교재를 참고해 주세요.
혹시 문제가 있다면, 댓글 남겨주시면 빠른 시일 내에 확인 후 적절한 조치를 취하겠습니다.
How to provide the illusion of many CPUs?
- CPU 가상화(CPU virtualizing)를 사용
- OS는 많은 가상 CPU들이 존재한다는
환상(illusion)
을 제공함. Time sharing
: 하나의 cpu core로 여러task(프로세스 또는 스레드)
를 빠르게 전환해가며 처리하는 것.- 여담) cpu와 core에 대해선, 이 책에선 cpu와 core를 거의 구분않고 서술하고 있음. 그러나 교수님은 개인적으로 cpu와 core를 구분하는 편. core = components of cpu. 그리고 task는 process 또는 thread를 의미하는데, 주로 엔지니어들이 뭉뚱그려 task라고 말하는 경우가 많음.
A Process
- 한 줄 요약:
A process is a program in execution
- 프로그램(Program): 스토리지에 저장되어 있는 유한 코드 집합.
- 프로세스(Process): 프로그램이 메모리에 로드되어 실행 중이면 프로세스라고 부름.
- 앞서 언급했듯, 각 프로세스는 고유의
Process ID, (PC 레지스터를 포함한) a set of registers, (가상) 메모리 공간
을, 각 스레드는 고유의Thread ID, PC(Program Counter), (PC 레지스터를 포함한) a set of registers, and a stack
을 가짐. - 하나의 프로그램에 대한 프로세스가 여러 개 생길 수도 있음.
- 예를 들어,
ls
명령어는 ‘프로그램’임. 그리고 해당 ls 명령어를 여러 사용자가 동시에 입력하면, ls 프로세스가 여러 개 생기는 거임.
Process API
- 프로세스를 제어하기 위한 API들로, modern OS면 아무 OS에서나 다 통용됨(어느정도 표준임).
생성(Create)
- 프로그램을 실행하기 위한 새 프로세스를 생성함.
- 리눅스에선
fork()
나exec() family
로 구현됨. - 윈도우나 우분투 데스크탑용 등의 GUI에서 응용 프로그램의 아이콘을 더블 클릭해 프로그램을 실행시키는 것도 이에 해당됨.
종료(Destroy)
- 실행 중인 프로세스를 강제로 종료시킴.
- 대부분의 프로세스는 실행되고 할 일을 다하면 스스로 종료하긴 하지만, 프로세스가 스스로 종료하지 않으면 사용자는 그 프로세스를 종료하고 싶을 것이고 그 때 이 API 호출이 필요함.
- 리눅스에선
kill()
로 구현됨.
대기(Wait)
- 다른 프로세스가 종료될 때까지 현재 프로세스를 잠시 대기시킴.
- 리눅스에선
wait()
과waitpid()
로 구현됨.
각종 제어(Miscellaneous Control)
- 여러 가지 제어 기능들을 말하는데,
- 예를 들어 프로세스를 잠깐 중지시킨 후 다시 실행하는 기능 등이 있음.
- 리눅스에선
kill()
에SIGSTOP
,SIGCONT
시그널을 사용해 구현됨.
상태(Status)
- 해당 프로세스에 대한 상태 정보를 얻음.
- 상태 정보에는 얼마 동안 실행되었는지, 아님 현재 프로세스가 어떤 상태에 있는지 등이 포함됨.
- 리눅스에선
ps
(정적),top
(동적),stat()
등으로 구현됨.
Process Creation
- 밑에 파란색은 disk, 중간에 하얀 선들은 buses
- 왼쪽은 프로그램이 그냥 disk안에 저장되어 있는 상태고,
- 해당 프로그램이 메모리에 로드되어
PID, (가상) 메모리 공간, (PC를 포함한) a set of registers
를 가지게 되면 프로세스라고 불리게 되는 거다.
참고1) 버스(bus)
- 버스(bus): CPU, 메모리, 입출력 장치들이 데이터, 제어 신호 등을 주고 받기 위해 사용하는 통로
참고2) 저장소(Storage) vs 디스크(Disk) 차이
storage
는 메모리, HDD, SSD같은 저장 장치뿐만 아니라 데이터 저장 기술같은 추상적인 개념까지 포함한 넓은 개념임.- 이에 비해
disk
는 보통 HDD, SDD와 같은 물리적인 저장 장치를 의미하며, 이들은 데이터를 블록 단위로 저장한다는 특징이 있음.
참고3) 섹터(Sector), 블록(Block), 파티션(Partition)의 차이
- disk에서 저장 단위는
섹터 -> 블록 -> 파티션
순으로 확장됨. - 섹터는 기본적인 물리적 저장 단위로 디스크의 최소 저장 단위이며, 보통 512바이트 사용.
- 블록은 운영체제의 파일 시스템이 파일을 관리할 때 쓰는 논리적 저장 단위로, 여러 개의 섹터를 묶어 하나의 블록으로 처리함. 파일 시스템이 데이터를 저장할 때 이 블록 단위로 처리하며, 4KB, 8KB 등 설정 가능함. 이 블록 크기가 클 수록 큰 파일을 빠르게 읽지만, 작은 파일 저장 시 공간 낭비 발생. 즉, 쉽게 말해 파일 시스템이 데이터를 저장하는 논리적 단위임.
- 파티션은 하나의 디스크(HDD, SDD)를 논리적으로 나눈 독립적인 공간이며, C: 드라이브나 D: 드라이브같은게 그 예시. 보통 운영체제 설치 파티션과 데이터 저장 파티션으로 분리하는 용도로 사용함. 보통은 디스크 하나당 파티션을 하나만 구성하지만, 필요에 따라 디스크 하나를 여러 파티션으로 나눌 수도 있음.
- 정리하면 디스크는 여러 개의 섹터로 구성되고, 운영체제의 파일 시스템은 이 여러 개의 섹터를 묶어 블록 단위로 관리하며, 디스크는 하나 이상의 파티션으로 나뉘는데 각 파티션은 블록 단위로 파일을 저장한다.
Process Creation steps.
1. 프로세스의 주소 공간(가상 메모리 공간)의 code 영역에 해당 프로그램의 코드를 로드함.
- 프로그램은 처음엔 실행 파일 형태로 디스크에 저장되어 있다가, OS가 프로그램을 ‘lazy하게’ 로드함. 지연 로딩(lazy loading) 방식을 사용해 로드한다고도 말함.
- 뭔 소리냐면, 프로그램이 막 몇기가고 그러면 메모리에 다 로드하는데 엄청 오래걸릴거 아님? 그러니 처음부터 전체 코드를 메모리에 로드하지 않고, 실행에 반드시 필요한 프로그램 헤더, main() 함수, 초기화 코드같은 것들만 먼저 로드함.
- 프로세스가 가상 메모리 주소를 참조하려고 할 때마다 *MMU는 페이지 테이블을 확인해 해당 가상 메모리 주소와 매핑되어 있는 실제 물리 메모리 주소를 알려주게 되는데, *PTE(Page Table Entry)의 유효비트(R, resident)가 1이면 실제 메모리에 적재된 페이지, 0이면 실제 메모리에 적재되어 있지 않은 페이지임을 의미함(0으로 초기화됨).
- 그렇게 프로그램이 실행되다가 MMU가 실제 메모리에 적재되어 있지 않은 페이지(즉, R = 0인 페이지)를 참조하게 되면, MMU가 페이지 폴트 예외(Page Fault exception)를 발생시켜 운영체제가 이를 처리하도록 함.
- 그러면 이제 운영체제는 페이지 폴트 핸들러(Page pault handler)를 실행시켜, 해당 페이지가 디스크 어디에 저장되어 있는지 확인하고 디스크에서 해당 페이지를 실제 메모리 주소에 로드하고 MMU의 페이지 테이블에도 해당 매핑을 정의한 후 프로그램 실행을 재개한다.
- 즉, 프로그램 실행 중 아예 사용되지 않은 함수나 데이터 등은 실제 물리적 메모리에 아예 로드되지 않을 수도 있음.
참고) https://preamtree.tistory.com/21, https://prodyou.tistory.com/27, https://microelectronics.tistory.com/92
- 이렇게 code 영역이 생성될 때 data 영역도 같이 생성되는 걸로 보임.
2. 프로그램의 런타임 스택이 할당됨.
- 스택은 지역 변수, 함수의 매개변수들, 반환 주소 등을 저장하기 위해 사용되는데,
- 이 스택 영역을 argc, argv 두 인자로 초기화함.
- 뭔소리냐면, main() 함수가 argc, argv 두 인자를 써야 하잖음?? 그러니 스택 영역을 저 두 인자로 초기화해서(저 두 인자를 넣은 상태로 시작해서) main() 함수에 전달할 준비를 하는 거임.
- 그니까 결국 main() 함수도 함수잖음?? 그러니 실행하려면 스택이 필요하니까 코드 영역 다음에 스택 영역을 할당하는 거임.
3. 프로그램의 힙 영역이 생성됨.
- 힙은 명백하게 동적으로 할당된 데이터들을 저장하기 위해 사용되는데,
malloc()
함수와free()
함수 등을 사용해서 해당 공간의 메모리를 요청하고 해제할 수 있음.
4. 운영체제가 추가적인 초기화 작업을 수행함.
- 입출력(I/O) 설정을 함.
- Unix 시스템에서 각각의 프로세스들은 기본적으로 3개의 파일 디스크립터들을 가짐.
- 해당 내용에 대해선 세 번째 파트 영속성 부분에서 자세히 다룸.
- 이는 각각 stdin, stdout, stderr임.
5. 프로그램을 시작 지점(entry point)인 main() 함수에서부터 실행함.
- 운영체제는 이제 그렇게 생긴 새로운 프로세스에게 CPU 제어권을 넘김.(스케쥴링해서 빌려주는거)
- 이제 main() 함수가 실행되며 main()함수의 return address, 지역 변수 등이 스택에 저장됨.
- ‘control’이라는 단어가 굉장히 흥미로운데, 이 control은 어떤 프로세스가 OS에 의해 CPU를 할당받아 CPU의 제어권을 가지고 있을 때, 그 제어권을 말하는 거임.
Loading: From Program To Process
- 가볍게 읽고 넘어가자.
Process States - 3가지 분류법
- 프로세스 상태는 세 가지가 있음. 러닝, 레디, 블락드.
러닝(Running)
- 러닝은 말 그대로 프로세스가 프로세서(cpu or core)에 의해 실행되고 있는 상태.
레디(Ready)
- 레디는 러닝할(실행될) 준비가 다 됐지만 어떤 이유든간에 OS한테 아직 선택받지 못한 상태(그냥 스케쥴링 아직 못 받은 거임).
블락드(Blocked)
- 블락드는 뭔가 특정 작업을 수행중인 상태.
- 우리가 I/O device에 commands를 issue할 때, I/O device는 CPU보다 느리므로, 그 프로세스는 issue도중이 기다리고 있어야 함. 이 때 블락드에 들어가고, 그 issue가 끝나면 다시 러닝이나 레디 상태로 넘어감.
- 즉, I/O와 같이 CPU 연산에 비해 시간이 오래 걸리는 작업들이 처리되는 동안 CPU가 그 프로세스에 계속 붙어있으면 시간 낭비가 크니까, 일단 걔는 그 작업 끝날 때까지 블락드 상태로 두고, CPU는 다른 프로세스 처리하러 가는 것.
- 핵심은, 러닝과 레디는 CPU에 의해 스케쥴링이 됐다가 빠졌다가 하는 거고, 시간이 오래걸리거나 명시적으로 기다려야 하는 작업은 blocked상태로 넘어갔다가, 그 작업이 종료되었다는 이벤트에 의해 깨어나 러닝으로 상태가 바뀌게 됨.
- 이거, 비동기 동기 개념과 헷갈릴 순 있는데, 그거랑은 다른 개념의 내용이니 나중에 그 챕터에서 다루자.
// 교재 figure 7.4 인용
시간 | Process0 | Process1 | 비고
---- | -------- | -------- | ---------------------------
1 | 실행 | 준비 | -
2 | 실행 | 준비 | -
3 | 실행 | 준비 | Process0이 입출력을 시작
4 | 대기 | 실행 | Process0이 대기 상태가 됨
5 | 대기 | 실행 | Process1이 실행됨
6 | 대기 | 실행 | -
7 | 준비 | 실행 | Process0 입출력 종료
8 | 준비 | 종료 | Process1 종료
9 | 실행 | - | -
10 | 종료 | - | Process0 종료
- 실제 예시를 들어보면 위와 같음.
Process States - 5가지 분류법
- 공룡책과 같은 타 운영체제 책들에선, 프로세스 상태를 아래처럼 5가지로 분류하기도 함.
- New : 프로세스가 생성되고 있는 상태. 방금 막 프로그램이 실행된 상태임.
- 레디(Ready) : 프로세서한테 할당받기를 기다리는 상태. 실행 대기 상태. CPU에 줄을 서게 됨. CPU를 사용할 준비가 되어 있지만, 다른 프로세스들이 실행 중이라 CPU가 할당될 때까지 대기함.
- 러닝(Running) : 프로세스가 프로세서에 의해 실행되고 있는 상태.
- 웨이팅(Waiting)(←블락드(Blocked)) : 프로세스가 특정 이벤트(ex. I/O 작업)가 완료될 때까지 대기하는 상태. 필요한 이벤트가 완료되면 다시 ready 상태로 전환됨.
- Terminated : 프로세스의 실행이 완료되었거나, 오류로 인해 종료된 상태. 종료된 프로세스는 시스템에서 자원이 해제되고, 더 이상 실행되지 않음.
Thread도 States가 존재한다!!!
- 우리가 지금 교과서에서 배우고 있는 건, single-thread-process로 간주하고 하는 얘기들임.
- 그렇다면, 프로세스가 멀티스레드를 가질 때는, 그 프로세스의 state를 어떻게 간주해야 할까?? 그리고, 해당 스레드들도 각기 state가 정의되나??
https://www.geeksforgeeks.org/thread-states-in-operating-systems/
- 위 글을 보면, 스레드 상태 또한 프로세스 상태와 똑같은 개념으로 thread state가 존재함.
https://stackoverflow.com/questions/45644376/whats-the-state-of-the-process-when-it-is-multithread
- 위에도 Ripple의 현 CTO분께서 달아주신 답변인데, 결론은 프로세스가 멀티스레드를 가질 경우, 각각의 스레드들이 각기 다른 state들을 가질 수 있다는 거임.
https://stackoverflow.com/questions/15044559/unclear-about-process-state-of-a-multi-threaded-process
- 위 질문에 .NET 개발자분이 달아주신 답변도 비슷한 얘기인데, 멀티스레드를 가지는 프로세스의 상태는, 그냥 ‘해당 프로세스에 포함된 스레드들의 상태’로 정의하면 된다는 얘기. ‘a process is a "container" for multiple threads’ 라고 얘기하심.
Data Structures
- 운영체제는 다양한 관련된 정보 조각들을 추적하기 위해, 몇 가지 특별한 자료 구조들을 사용함.
- 즉, OS는 모든걸 다 알고있어야 하기 때문에, 각 프로세스의 상태가 어떤지, 각 상태의 프로세스에는 어떤 것들이 있는지 등에 대해 알고 있어야 함.
- 프로세스 리스트(Process list): 각 상태에 해당되는 프로세스들 목록 리스트
- Ready processes
- Blocked processes
- Current running processes
- 레지스터: CPU옆에 붙어있는 아주 작고 매우 빠른 메모리 공간. CPU가 operand를 가진 instruction을 실행하면, 그 operand는 레지스터임.
- Context: context switching이 일어날 때 저장되고 복원되는 프로세스 관련 정보들을 말함. context는 PCB에 저장됨.
- Context Switching: CPU가 현재 실행 중인 프로세스의 Context를 PCB에 저장하고, 새로운 프로세스의 Context를 PCB에서 복원하여 실행을 전환하는 과정을 의미.
- Register context: OS가 context switching 과정에서, 해당 프로세스의 레지스터들 값을 저장하고 복원하는 데 사용하는 자료구조. 각 프로세스의 Register context는 PCB에 속해있음.
- PCB(Process Control Block): 각 프로세스에 대한 정보(즉, context)를 포함하고 있는 C 구조체 자료구조. OS가 context switching 과정에서 각 프로세스의 현재 실행 정보를 저장해두고 복원하기 위해 사용함.
PID, PPID, State, Register context, 프로세스 메모리의 시작 주소와 메모리 크기
등이 저장되어 있음.- 프로세스가 생성될 때마다 하나씩 늘어나는 Linked List 형태(삽입 삭제가 용이한 Linked List의 특성 활용)
- 프로세스가 종료되면 해당 프로세스의 PCB 또한 제거됨.
Example) The xv6 kernel Proc Structure
- xv6은 MIT에서 개발한 교육용 운영체제임. full scale simulator.
- Unix의 간단한 버전을 기반으로 하여, 운영체제를 학습할 수 있도록 설계됨.
- 프로세스, 스케줄링, 메모리 관리, 시스템 호출, 파일 시스템 등 핵심적인 운영 체제 기능들이 실제 코드로 구현되어 있음.
- xv6를 실제로 구현하기엔 어려움이 있음. 일단 우리 윈도우 환경 랩탑에서는 시작도 못함.
- 따라서 우리는 조금 다른 방식으로 과제를 수행할 거라고 함. (xv6랑 비슷하다함)
// 프로세스를 멈추고 다시 시작할 때,
// xv6가 저장하고 복원할 레지스터들
// 그니까, CPU가 context switching할 때마다 프로세스 정보들을
// 저장하고 복원하고를 반복하잖음??
// 그 때 저장하고 복원할 레지스터들을 구조체로 정의해 둔거.
// 해당 레지스터 값들이 context라고 볼 수 있음.
struct context {
int eip; // 인덱스 포인터 레지스터 (프로그램 카운터)
int esp; // 스택 포인터 레지스터
int ebx; // 베이스 레지스터 (기본 레지스터)
int ecx; // 카운터 레지스터 (반복문에서 자주 사용)
int edx; // 데이터 레지스터 (일반적인 데이터 처리용)
int esi; // 소스 인덱스 레지스터
int edi; // 목적지 인덱스 레지스터
int ebp; // 스택 베이스 포인터 레지스터 (스택의 기준 위치)
};
// 위에서 스택 포인터 레지스터랑 베이스 레지스터 보이지??
// 저거 시프실 시간에 배운 거 기억날거임 ㅎㅎ
// 프로세스가 가질 수 있는 다양한 상태
// 우리가 배운 거에서 몇 가지 상태가 추가되어 있음.
enum proc_state {
UNUSED, // 사용되지 않는 상태
EMBRYO, // 초기화 중인 상태
SLEEPING, // 잠자는 상태 (일시 정지 상태)
RUNNABLE, // 실행 대기 상태
RUNNING, // 실행 중인 상태
ZOMBIE // 종료된 상태 (자원 해제 대기 중)
};
// 각 프로세스에 대해 xv6가 추적하는 정보
// 이 정보에는 프로세스의 레지스터 컨텍스트와 상태가 포함됨
// 얘는 각 프로세스에 종속적인 부분임. 각 프로세스 자체를 의미.
// PCB와 같은 역할을 하고 있음.
struct proc {
char *mem; // 프로세스 메모리의 시작 위치
uint sz; // 프로세스 메모리의 크기
char *kstack; // 커널 스택의 바닥 (커널 모드에서 사용할 스택)
enum proc_state state; // 프로세스 상태 (위의 enum 값 중 하나)
int pid; // 프로세스 ID
struct proc *parent; // 부모 프로세스
void *chan; // non-zero일 경우, 채널에서 잠자고 있는 중
int killed; // non-zero일 경우, 프로세스가 종료된 상태
struct file *ofile[NOFILE]; // 열린 파일들
struct inode *cwd; // 현재 디렉토리
struct context context; // 프로세스를 실행할 때 사용할 컨텍스트
struct trapframe *tf; // 현재 인터럽트에 대한 트랩 프레임
};
- 주석으로 각 구조체에 대해 설명해놨음.
'CS > 운영체제' 카테고리의 다른 글
[OSTEP] Ch3.1. Scheduling: turnaround time, FIFO, SJF, STCF. (0) | 2025.03.25 |
---|---|
[OSTEP] Ch2.4. Process: Interrupt and Context Switch (0) | 2025.03.17 |
[OSTEP] Ch2.3. Process: System Call and Trap (0) | 2025.03.17 |
[OSTEP] Ch2.1. Process: Process APIs (0) | 2025.03.17 |
[OSTEP] Ch1. What is an OS? (0) | 2025.03.11 |