[2] OS - background
즉 CPU가 작업을 하다가(main memory code 수행(그 코드가 내 프로그램이든 OS 코드이든)) 어떤 때는 내 코드, 어떤 때는 1000만개도 넘는 시스템 코드(ex. interrupt)가 돌아감
- Code(Disk)->Ram(Memory) : 코드 소스파일이 저장되는 위치는 디스크라면 실행했을 때 실행되는 곳은 램.
- Network통신(Ethernet) controller : IO controller
CPU가 IO controller로부터 정보를 얻는 법
기본 철학 : Interrupt
IO Controller : 펌웨어(약간.. CPU 미니 버전같은 느낌)가 돌아가는 전용 제어장치 -> 코드를 내부에 넣고, 그 코드를 넣어버림(embedded system이라고 부르는 이유)
프로세서는 프로세스만 계속 돌림
네트워크를 통해서 온 패킷을 OS가 적절한 목표 패킷에 전달하고 싶은데, 이 메커니즘이 interrupt임
조그만 컴퓨터가 있는 것과 비슷하게 취급할 수 있음
** controller는 CPU와 무관하게 스스로 돌아감 -> 펌웨어코드, 컴파일된 특정 코드만 -> active한 주체라고 볼 수 있음(그러나 계속 이것만 돌기에 OS와 다름) 참고로 코드를 만드는 사람은 그 controller를 만든 회사
ex> Network와 연결된 IO controller : 계속 Network만 함(Ethernet(LAN: 패킷 도착을 모니터링하거나 보낼 패킷이 있는지 체크함), Wifi)
ex> Display와 연결된 IO controller : 보여주는 역할만 함
ex> Network controller: ethernet, wifi
ethernet : LAN line : 패킷 도착 모니터링, 또는 보낼 패킷이 있는지 체크함
-> 예시상황 : 옆컴퓨터에서 데이터를 보내려고 함. 문제는 패킷이 온 걸 알때, 프로세스를 어떻게 처리할지임. -> controller는 OS에게 이 사실을 알려야하고, “이 패킷 OS 너가 해당 목표 process에 전달해라”라고 하고 싶음 -> <h1 style="color: #ff0000">Interrupt</h1>
-> 모든 IO controller는 CPU interrupt 핀에 연결됨
Interrupt : CPU의 physical pin에 signal을 줌으로서 controller가 알려주는 것
CPU는 모든 instruction cycle 끝에 interrupt를 체크한다.
즉 Interrupt가 active이면 그 순간 CPU는 수행하던 일을 멈추고, IO controller가 요구한 interrupt를 처리한다
- 패킷(in buffer)을 Main Memory로 OS가 copy
- CPU는 process를 하다가 Interrupt, 즉 OS 모드로 바뀜(유저가 아니라 운영체제 코드로 jump)
- buffer내용을 Ram 어딘가로 Transfer해야 종료 -> Interrupt Handling
- 이 다음에 다시 process로 돌아감
정리하면
CPU는 메인 메모리와 Process사이를 계속해서 돌고, n개의 IO controller는 독립적으로 계속 돌고 있음
이렇게 본인 일만 하다가 할일이 생기면 Interrupt를 통해 CPU에게 부탁 -> OS는 그 할일을 대행
헷갈리는 부분
- interrupt실행 중 다른 interrupt가 들어올 경우
- 무시하기
- priority 기반의 중첩 interrupt(더 높은 우선순위인 interrupt가 들어온 경우 그걸 실행하고 이전 interrupt를 실행하고, 더 낮은 거면 대기)
- Top Half / Bottom Half
- top half: 발생 직후에 무조건 실행하는 부분
- botton half: 발생 직후에 실행 안하는 부분

[2] Hardware Protection
Dual mode operation
: CPU의 동작 모드를 두가지로 나눈다
- 목적 : incorrect program이 다른 프로그램까지 incorrectly 동작하는 것을 막기 위해서
- 방법 : OS는 hardware support를 지원해서 user mode, kernel mode 두가지로 모드를 나눈다. 이 방법으로 여러 application fault를 막는다
- User mode: Process 1, 2, …, user의 execution
- Kernel mode: System call handler(진입 시점: system call), Scheduler(timer interrupt), Interrupt handler(device interrupt), os의 execution
상황 설명
- OS는 최대한, 여러 프로세스를 공평하게 돌아가게 만들고 싶음
- 그래서 interrupt가 걸린 순간, Process 1이 CPU가 계속 Process 1만 처리하길 원하더라도 OS는 항상 interrupt걸린 CPU를 처리함
- 그렇지만 문제는, Kernel에는 User code들이 쉽게 접근하면 안된다는 점이다. Kernel code는 중요해서, fundamental requirements들이 존재하고, 이를 쉽게 작성하면 안되기에 Kernel code를 보존하고자 한다.
- Interrupt는 알아서 kernel 모드로 접근하지만, System call 상태가 위험하다.
- 그래서 Hacking이 이루어지는 방식은 허용되지 않은 루트로 kernel mode에 접근한 것이다.
방법 설명
- Mode를 나누는 방법은 protected processor register의 status bit으로 표현된다.
- 어떤 machine instruction들은 privileged(protected) instruction으로 작성되고, kernel mode에서만 작동한다.(ex. io instructions)
이 경계를 오가는 상황 설명
user program 이 protected된 명령어..같은 걸 call -> kernel service routine에 control을 넘겼는데, kernel이 parameter가 적합하다는 것을 보장할 때
세가지 상황
-
Hardware interrupt(Interrupt)
: 나와 무관 -> OS가 여러 process를 happy하게 하려면 필요
-
SW interrupt
(exception) : Main memory
ex1> user program을 작동하다가, 내가 요청한 Page가 없다고 생각해보자. 그러면 page fault exception을 내고, 이 때 kernel mode로 진입한다. process가 요구하는 해당 code, data를 main memory에 로드 후 원래의 user에게..
user 관점 : 내꺼 수행 -> system memory에 내 거가 없음 -> kernel이 도와줌 -> exception -> cpu detect
ex2> 0으로 나누기 : kernel이 kill
ex3> memory 잘못 사용(array가 100개 원소 저장하는데 101에 접근)
- System call : User mode: user code가 돌 때, explicit하게 call(IO, fork…)등 -> 철저히 user가 원할 때 돈다 (C library등)(그리고 system call의 구현은 sw interrupt로 이루어진다
Case Study : x86
Real mode(과거) : system BIOS는 real mode에서만 동작하므로, boot code는 real mode에서 동작
Segmented memory : all segments는 64KB 크기이다
Protected mode(요즘): 32bit memory address, memory protection, virtual memory paging, interrupt handling…
즉 사용자가 OS의 memory에 접근할 수 있던 real mode와는 다르게, 메모리 보호 기능을 가지고 있다.
x86 Protected mode: Privilege Level
Privilege Check!
현재 동작하는 프로그램이 허용되지 않은 구역에 접근하지 못하게 한다. -> Protection Rings.
OS Services 종류
- User Services
- Program execution
- IO operation: read, write 등
- File system(read, write, create, delete files) -> 다른 프로세스와 통신
- communication(process가 실행될 때 information 교환(다른 컴퓨터간, 같은 컴퓨터간))
- Error detection(0으로 나누기 등)
- Resource allocation(효율적으로 자원을 사용.. 예를 들어 CPU, GPU 계산 등)
- Accounting(어떤 리소스를 사용하여 billing을 청구할지 계산.. 예를 들어 통신사에서 데이터 요금 청구하는 것처럼)
- Protection : 시스템 자원이 잘 조절되고 있는지
System call
A. 정의, 종류
System call이란? 유저가 OS의 기능에 access할 수 있는 함수
- Process Management
- fork : create process
- waitpid : wait for process
- execve : createprocess= fork + execve
- exit :terminate execution
- kill : send signal
- File Management : open, close, read, write, iseek, stat, chmod
- File System Manage: mkdir, rmdir, link, unlink, mount, umount, chdir
B. 원칙
application과 hardware사이에 extra layer를 넣는 방식이다. 즉, 이 기능만 user에게 open하겠다고 선언해주는 것과 같다.
이는 kernel과 유저가 통신할 수 있는 유일한 창구와 같다. 즉 user는 kernel의 복잡한 기능을 시스템 콜 없이는 접근할 수 없는 것이다.
- 장점
- 프로그램을 짜기 쉽다. low-level까지 유저가 확인할 필요가 없어진다
- system 보안이 올라간다. 커널이 요청의 올바른 정도를 체크한다
- 프로그램 portability가 상승한다. 왜냐하면, Linux와 같은 Unix계열이 있다고 생각해보자. 운영체제마다 system call이 다 다른데Unix는 이미 잘 정의되어있는 system call 에 동의하였기에 Linux 용으로 개발된 앱이 솔라리스와 같은 다른 유닉스 계열에서도 잘 작동한다.
- System calls
C. POSIX APIs and System Calls
- API란: Application Programming Interface의 약자로, 어떻게 주어진 서비스를 획득할 것인지 함수를 정의한 것이다.
- 예를 들면, malloc(), calloc(), free()와 같은 POSIX APIs는 libc의 brk() 시스템 콜로 구현되어있다.
- POSIX-compliant : system이 어플리케이션에게 적절한 API 집합을 제공했을 때 -> 즉, 이런 함수를 구현하면 POSIX compliant하다는 것을 정의함
- 유저 모드 라이브러리 같은 느낌
- System call
- software interrupt를 통한 커널모드 진입 요청
- integer return
- 구현 방법 : libc의 wrapper function으로 구현됨
- 예시
- user가 fork() 호출 -> libc.a의 fork()로 진입하는데, CPU가 커널모드로 바뀌면서 IDT(=Interrupt Disrupt Table: gateway함수를 link) system call()을 호출(<h1 style="color: #ff0000">여기부터는 커널모드</h1>) -> call *SYMBOL_NAME(sys_call_table)(,%eax, 4) 호출 -> sys_call_table에서 sys_fork()호출 -> sys_fork 수행
- 문제없이 완수되면 역순으로, libc.a의 user mode로 들어가서 user task 완료
시스템 프로그램 종류
- file manipulation : create, delete, copy, rename, print, dump
- status information : data, df, du, top
- file modification : editor(vi..)
- programming language support : (코드를 컴퓨터가 이해할 수 있게)
- program loading and execution : library와 내 코드 연결 -> Loader, linkage editor, debugger
- application programs : DBMS, Web browsers, …
헷갈리는 부분 정리
- interrupt, exception, trap은 CPU가 정상 흐름을 끊고 핸들러로 들어가는 사건 그 자체
- interrupt: 외부 이벤트가 cpu에 일을 시키는 경우 발생하는 사건
- exception: 현재 실행 중인 명령 때문에 발생하는 사건으로, 버그성 예외와 의도된 예외를 모두 포함
- Intel은 “프로세서가 프로그램 실행 중 에러를 감지할 때 예외를 생성한다”고 설명하고, ARM은 예외를 “정상 실행을 멈추고 예외 핸들러를 실행해야 하는 모든 조건”으로 설명
- ARM 쪽에서는 interrupt도 exception의 한 종류라고 보는 문서가 많음
- trap
- RISC-V에선 trap = interrupt + exception
- x86, intel: exception의 하위 분류 중 하나가 trap
- faults, traps, aborts로 분류
- fault는 보통 수정 가능하고, 수정 후 그 명령을 다시 실행할 수 있는 exception
- Intel에서 abort는 정확한 원인 명령 위치가 항상 보장되지 않고 재시작이 어려우며 심각한 오류를 보고하는 exception
- 즉, intel에서는 interrupt, exception(여기에 trap포함, 3개 분류)
- risc-v에서는 trap = interrupt + exception
- system call은 사용자 프로그램이 OS 기능을 요청하는 행위, 즉 커널 서비스를 요청하는 의도적이고 동기적인 성격을 띄는 서비스 요청 인터페이스
- 즉, syscall은 exception(risc), trap(linux)을 통해 구현됨
3. OS Design
- 목표
- user 목표 : OS가 쉽게 사용할 수 있으며, 쉽게 배울 수 있고, 믿을 수 있고, 안전하며, 빠를 것. 즉 crash없고, safe, 빠르다.
- system 목표 : 쉽게 디자인할 수 있고, 개발할 수 있고, 유지할 수 있고, 유연하며, 믿을 수 있고, 에러가 없으며, 효율적일 것
- Mechanism : 어떻게 할 것인가(time sharing system에서 time slicing)
- Policy : 무엇을 할 것인가(time quantum: 구현된 실제 코드)
- (디자인1) Layering Approach
- 배경: 초기에는 기능 위주였는데, OS의 코드가 너무 길어지면서 방대한 코드가 되어버림. OS를 이렇게 기능마다 확장할지 고민하게 됨. SW engineering, module approach를 해보자
- OS가 여러개의 레이어로 구성되어있음
- 장점
- modularity: layer는 각각 자기보다 낮은 레벨의 레이어에 있는 함수와 서비스들만을 사용한다
- 디버깅, system verification이 쉽다
- 단점
- 레이어 정의를 조심해서 해야한다
- 성능
- OS에서는 성능이 중요하다. 빠르게 결과를 내고, 전체 퍼포먼스를 내는 것이 중요한 것이다. 최상위에서 최하위까지 너무 많은 레이어가 있으면 성능이 감소한다.
- (디자인2) System Implementation
- 전통적으로 어셈블리어로 썼다면, 요즘은 high-level로 쓴다.
- 어셈블리어로 짠다면 : 특정 IS(Instruction Set)에 특화된다. 다른 IS에 맞게 짤 수가 없는데, 명령어 내용을 변경해서 Portability를 높이려면 어떻게 해야 할까?
- 특히, syntax, pointer같은 것들이 너무 많아지는데 이게 거의 암호처럼 되어버린다. semantically 어려운데 이 때, 효율적 구현을 위해 차라리 운영체제를 high level로 짜자는 움직임이 생기고 이게 바로 C 언어이다. OS를 잘짜기 위한 특별한 프로그래밍 언어를 만들고자 했고, 이렇게 만들어진 언어가 C언어인 것이다.
- High level : 다른 아키텍쳐에 porting이 쉽다.
- C언어는 high level임에도 매우 효율적이다. 같은 문제를 그 언어의 symbolic으로 프로그래밍 했을 때, C언어로 짜고, 어셈블리어로 짰을 때 C언어와 어셈블리어의 차이가 10%밖에 나지 않은것이다.
Microkernels
- Monolithic Kernel
- 모든 운영체제 system 서비스는 하나의 큰 monolithic kernel에 구현된다.
- 사실상, 어떤 과정이 다른 과정 어떤 것이든 호출할 수 있다.
- 우리가 쓰는 대부분의 운영체제
- 큰 덩치의 커널, 모든 기능이 하나의 커널에 포함된다.
- 많은 procedure, 하나가 다른 것 부를 수 있다.
- user는 usermode, kernel mode에서 도는 것의 차이만 있음
- 커널 서비스가 필요하면 커널 모드로 들어가서 하고 나옴
- 단점 : 문제가 생기면 debug 어려움
- 장점 : kernel 수행이 빠르다. layering approach가 필요없다.
- Microkernel
- essential core OS 함수만 커널에 있다.
- 덜 중요한 서비스와 application은 microkernel에 있으며 이를 user mode에서 수행한다.
- OS service는 독립된 프로세스들의 집합으로 structured된다.
- communication은 유저모듈간 message passing으로 이루어진다.
- 단점 : 좋아보이지만, layering approach의 performance 문제가 생김
- mode, process 스위치를 위한 서비스를 계속 invoke. 커널 내의 이동이 어려움.
- user mode에서 수행하기 위한 필수적인 system service
- 장점 : 확장성, modularity
- 작은 커널이 디버그하기 쉽게 만들고, 더 효율적으로 만든다
- 내장형, 초소형에서 사용하기 쉬움
4. Virtual Machine
과거) application program이 ISA + OS라는 특정한 platform에 매여있었음.
Virtual Machine(VM)이란?
- real-platform contraint를 없애줌 -> Portability, flexibility 증가
- SW for cross-platform compatibility
VM의 필요성 :
- Portability : networked computing에서 필수적임.
- 모바일이나 무선장치에서 소통한다고 생각해보자. 우리가 consistent한 environment를 가지고 있지 않다. CPU, OS, hardware모두 다 다른 걸 가지고 있을 것이다. JVM(Java VM)은 이를 돌리는 하드웨어, 운영체제가 무엇이든 이 위에서 코드가 돌아간다.
- CPU 혁신은 오래된 interface에 의해서 막혀있었는데, 왜냐하면 강력한 CPU가 등장해도 기존의 x86 binary를 수행하지 못하는 CPU는 시장에서 쓸모가 없었기 때문이다. 즉 옛날 것을 수정없이 돌리고 싶은 마음이 있었던 것이다.
- 단일 OS는 security에 문제가 있는데, 생각해보자.. 여러 유저가 하나의 서버를 사용한다면? 만약 해커가 뚫으면 프로세스가 망가짐. 그래서 OS를 철저히 분리하는 게 어떤가?
- Sandboxing: 원래 OS를 건드리지 않고 커널 내부에 구현. 즉 개발중인 OS가 잘못되어도 원래 운영체제 리부트 없이 VM 튜닝만 하면 됨.
- VM Solution
- mapping virtual guest system, real host system
- VM 종류
- Process VM : virtualization of individual processes, 즉 하나의 os위에서 특정 프로세스만 가상화
1) Run executables of different ISA or OS
2) runtime이라고 부름
Process Virtual Machine
: 하나의 프로그램(프로세스)을 실행할 수 있는 가상화된 환경으로, 프로그램이 실행될 때 그 위에서 돌아가는 일종의 가상 컴퓨터. VM emulates ABI(application binary interface). Runtime이라고 불림
이 때 Runtime (Runtime Environment): 프로그램이 실행될 때 필요한 환경이나 인프라를 뜻하는데, 이게 바로 process virtual machine 역할을 하는 경우가 많음.
ex. x86 application을 Alpha system위에서 돌리기
- runtime(시간) = 코드 실행 중
- runtime(시스템) = 코드 실행 환경
- 그 실행 환경이 VM 형태일 수도 있고, 아닐 수도 있다.
- System VM : virtualization of complete system, 즉 완벽한 os를 host위에서 돌림
1) 전체 OS(es)와 executables run
ex. Linux를 Window에서 돌리기
2) VMM, Hypervisor라고 부름
3) Hosted, Stand-alone 으로 구분됨
- hosted : runs as a process on existing host OS (일반적으로 호스트 OS 위에 가상화 소프트웨어(VMware Workstation, VirtualBox 등)를 설치하고 그 위에서 VM을 실행.)
- 하드웨어 interaction을 위해서는 host OS에 의존하며, native application도 그대로 있다
- VMWare Workstation, User-Mode Linux…
- Stand-alone : VMM on top of bare hardware
- VMM위에 여러개의 운영체제가 있고, 그위에 각각에 맞는 앱이 있음
-VMWare ESX …
5. Linux
위에는 UNIX의 진화 지도이다.
Linux는 UNIX의 클론이다. Linus라는 사람이 MINIX를 보고 완벽히 오픈소스로 새로운 unix-likely source를 만들었다.
GNU : Open Source free software. UNIX-like program이었지만 커널은 없었음. 세상의 모든 프로젝트는 공유가 필요하다는 오픈 소스 철학을 갖고 만들어짐
-> 목표는 컴파일러를 만드는 것이었고, 만들어서 open하였음.
-> 많은 것을 해냈는데, OS-kernel이 없어서 UNIX Kernel과 비슷한 것을 만들기로 함. -> Linux 만들어짐
Linux의 장점
- Free, Open
- Portable from mainframe to handhelds
- Compatibility, Efficiency
Highly compatible with many common OS
- Maturity
- Well supported: 거의 모든 하드웨어에서 사용 가능함
Linux의 특징
- Monolithic kernel: 고성능
- module을 지원함 : 동적으로 커널 코드의 일부를 load, unload하기 위함.
- kernel threading : kernel thread란 커널이 자체적으로 처리하는 스레드로, common address space에 독립적으로 scheduled될 수 있는 execution context가 된다.
- Multithreaded application support
- Preemptive kernel : 급박한 거 먼저
- Multiprocessor : CPU 여러개
- File system 지원
** Fig첫번째: Linux Kernel Architecture. UserLevel : syscall로 접근하는데, 이는 Linux라기보다는 대부분의 운영체제 일반적인 내용이다.
** Fig두번째: from https://slidesplayer.org/slide/15031576/
arch가 가리키는 12개의 노드들 : 리눅스의 모든 CPU architecture
architecture 종속 kernel code
