해당 내용은 공룡책(Operating System Concepts 10th Ed. : Abraham Silberschatz, Peter Baer Galvin, Greg Gagne)과 대학 강의를 기반으로 재구성하여 정리한 공부 내용입니다.
1. 개요(Overview)
동기(Motivation)
프로세스는 운영체제가 응용 프로그램을 적재하는 단위, 자원을 할당받는 단위
스레드는 실행(스케줄링) 단위
스레드 = CPU 이용의 기본 단위
스레드 ID, 프로그램 카운터(PC), 레지스터 집합, 스택으로 구성
같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 열린 파일이나 신호와 같은 운영체제 자원 공유
현대의 거의 모든 응용 소프트웨어는 다중 스레드를 이용
스레드는 application 안에서 함께 실행된다.
application에서 멀티태스킹은 분리된 스레드들에 의해 구현될 수 있다.
ex. 화면 업데이트, 데이터 패칭, 맞춤법 검사, 네트워크 요청 응답 등
프로세스는 중량 프로세스(HWP)이지만, 스레드는 경량(LWP)으로 생성 → 코드 간결, 효율성 증대
운영체제의 커널도 일반적으로 멀티스레드이다.
웹 서버가 멀티스레드화 되면 서버가 클라이언트 요청을 listen하는 별도의 스레드를 생성하며, 추가적인 클라이언트 요청을 listen하는 작업을 재개한다. 즉 프로세스 안에 여러 스레드를 만들어 나가면서 오버헤드를 감수한다.
장점(Benefits)
1. 응답성(responsiveness): 응용 프로그램의 일부가 블락되거나 긴 작업을 수행해도 프로그램의 수행이 계속되는 것을 허용하여 사용자에 대한 응답성을 증가시킨다. 특히 UI에 있어 유용하다.
2. 자원 공유(resource sharing): 프로세스가 공유 메모리나 메시지 패싱을 통해야만 자원을 공유할 수 있는 것과 달리 스레드는 자동으로 속한 프로세스의 자원들과 메모리를 공유하여 더 쉽다.
3. 경제성(economy): 프로세스 생성에 비해 스레드는 자신이 속한 프로세스의 자원들을 공유하여 시간과 메모리를 덜 소비하고, 프로세스 문맥 교환보다 스레드 스위칭이 더 적은 오버헤드를 발생시키며 더 빠르다.
4. 규모 적응성(scalability): 멀티 프로세스 구조에서 각각의 스레드가 다른 프로세서에서 병렬로 수행되어 이점이 더욱 증가한다.
* 스레드는 동일한 주소 공간을 가짐! → 서로 다른 메모리 공간으로 전환하기 위한 비용이 큼 → 프로세스에 비해 생성이 가벼워짐
프로세스 스위칭은 메모리 맵을 변경(MMU의 페이지 테이블 업데이트)하고, TLB 플러시, 캐시 초기화를 해야 하는데 스레드 스위칭은 이 작업들을 생략하여 스위칭이 가벼워짐
2. 다중 코어 프로그래밍(Multicore Programming)
싱글 코어를 사용하는 경우: 한 번에 하나의 스레드만 실행 가능(Concurrency, 병행성)
멀티 코어를 사용하는 경우: 시스템이 각 코어에 별도의 스레드를 할당하여 일부 스레드가 병렬로 실행 가능(Parallelism, 병렬성)
프로그래밍 도전 과제(Programming Challenges)
병행 실행만 가능했던 단일 프로세서에서 다중 코어 시스템으로 발전함으로써, 기존 프로그램을 다중 스레드를 사용하도록 수정하고 새로운 다중 스레드 프로그램을 설계하여 다중 코어의 활용도를 높여야 하는 도전에 당면했다.
다중 코어 프로그래밍을 위한 5개 극복 과제:
1. identifying tasks: 독립된 코어에서 병행 가능한 태스크로 나누어 실행할 수 있는 응용 프로그램의 영역을 찾아야 한다.
2. balance: 찾은 부분들이 전체 작업에 균등한 기여도(가치와 노력)를 가지도록 하여 코어 사용을 최적화해야 한다.
3. data spliting: 각 태스크가 접근하고 조작하는 데이터도 개별 코어에서 사용할 수 있도록 나눠 접근 충돌을 최소화한다.
4. data dependency: 어떤 데이터에 접근하는 둘 이상의 태스크 사이에 종속성이 없는지 검토하고, 데이터 종속성을 수용할 수 있도록 태스크 수행을 동기화해야 한다.
5. testing and debugging: 다중 코어에서 프로그램을 병렬 실행하는 프로그램을 시험하고 디버깅해야 하는 어려움
+) Synchronization: 데이터 종속성을 관리하고 스레드의 안전한 운영을 보장하는 전략을 개발해야 한다.
병렬 실행의 유형(Types of Parallelism)
1. 데이터 병렬 실행(Data Parallelism)
동일한 데이터의 부분집합을 다수의 계산 코어에 분배한 뒤 각 코어에서 동일한 연산을 병렬적으로 실행
ex. 두 코어에 나눠 배열의 내용을 더하는 경우; 각 코어가 원소를 반씩 나누어 더함
2. 태스크 병렬 실행(Task Parallelism)
태스크(스레드)를 다수의 코어에 분배한 뒤 각 스레드가 개별 코어에서 병렬로 각각 고유한 연산을 실행
다른 스레드들이 동일한 데이터에 대해 연산을 실행할 수도 있음
ex. 두 스레드가 개별 코어에서 하나의 배열에 대해 각자 고유한 연산을 할 수 있음
* Amdahl's Law
순차 실행 구성요소와 병렬 실행 구성요소로 이루어진 응용 프로그램에 추가의 계산 코어를 더했을 때 얻을 수 있는 잠재적인 성능 이득을 나타내는 공식
= N개의 처리 코어를 가진 시스템에서 실행되는 응용 프로그램 중 반드시 순차적으로 실행되어야만 하는 구성요소가 S일 때 속도 향상을 표현
ex. 75%의 병렬 실행 구성요소와 25%의 순차 실행 구성요소(S)를 가진 응용 프로그램을 2개의 코어를 가진 시스템에서 실행시킬 경우, 1.6배의 속도 향상을 얻을 수 있고, 4개 코어 시스템에서 실행하는 경우 2.28배 더 빨라진다.
N이 무한대에 가까워지면 속도는 1/S에 수렴하게 된다.
ex. 순차 실행 요소가 40%이면, 코어를 아무리 추가해도 최대 2.0배 이상 속도 향상을 얻을 수 없음
→ 응용 프로그램의 순차 실행 구성요소는 코어를 추가하여 얻을 수 있는 성능 향상에 영향을 미친다.
3. 다중 스레드 모델(Multithreading Models)
* 스레드 제어 블록(Thread Control Block, TCB)
= thread entity, scheduling entity
스레드를 실행 단위로 다루기 위해 스레드에 관한 정보를 담은 구조체
커널 영역에 만들어지고 커널에 의해 관리됨
현대 운영체제는 대부분 프로세스 내부의 스레드 각각을 개별 실행 단위로 인식하여 CPU 코어에 할당한다.
User Threads vs Kernel Threads
스레드를 위한 지원은 두 가지 수준에서 제공된다.
→ 시스템 자원의 효율적인 관리와 안정적인 멀티태스킹 목표
- User threads(사용자 스레드): 커널 위에서 커널과 독립적으로 사용자 수준에서 관리됨
응용 프로그램이 라이브러리 함수를 호출하여 사용자 레벨 스레드를 생성
스레드 라이브러리가 스레드 관리 정보(핸들, 속성, 상태 등)를 사용자 공간에 생성하고 소유한다.
스레드 라이브러리,스레드 주소 공간(스레드 코드와 데이터)은 사용자 공간에 존재
스레드 라이브러리에 의해 스케줄된다.
커널은 사용자 레벨 스레드의 존재에 대해 알지 못 한다.
- Kernel threads(커널 스레드): 운영체제에 의해 커널 수준에서 직접 지원, 관리됨
스레드에 대한 정보(TCB)는 커널 공간에 생성되며 커널에 의해 소유됨
응용 프로그램이 시스템 호출을 통해 커널 레벨 스레드 생성
커널이 만들고, 커널에 의해 스케줄됨
스레드 주소 공간(스레드 코드와 데이터)은 사용자 공간에 존재
main 스레드 = 커널 스레드
응용 프로그램을 적재하고 프로세스를 생성할 때 커널이 자동으로 main 스레드 생성
main 스레드의 TCB는 커널에 생성한다.
커널 스레드가 시스템 콜을 호출하면, 해당 커널 스레드가 사용자 모드에서 커널 모드로 전환되어 작업을 처리한다.
일반적으로 스레드는 커널 스레드 기반으로 관리된다.
현대 운영체제(Windows, Linux, macOS)는 커널 스레드를 지원한다.
사용자 스레드와 커널 스레드 간 연관 관계가 존재해야 한다.
→ 이 관계를 확립하는 3가지 일반적인 모델이 존재; 다대일, 일대일, 다대다 모델
다대일 모델(Many-to-One Model)
많은 유저 레벨 스레드를 하나의 커널 스레드로 매핑한다.
스레드 관리는 사용자 공간의 스레드 라이브러리에 의해 행해진다. (스레드 라이브러리가 자체 스케줄러 보유)
ex. Solaris의 Green Threads, JAVA의 초기 버전
장점:
커널과 연관 없이 사용자 공간 안에서 효율적인 스레드 관리 가능
간단한 스레드 생성, 스케줄링, 관리 프로세스
핵심적인 한계:
Blocking calls: 한 스레드가 blocking system call을 할 경우 전체 프로세스가 봉쇄된다.
Multicore Utilization: 한 번에 하나의 스레드만이 커널에 접근 가능 → 다중 스레드가 다중 코어 시스템에서 병렬 실행 불가
→ 다중 처리 코어의 이점을 살리지 못해 현대에는 거의 사용하지 않는다.
일대일 모델(One-to-One Model)
각 유저 스레드를 각각 하나의 커널 스레드로 직접 매핑한다.
하나의 스레드가 blocking system call을 호출하더라도 다른 스레드가 실행 가능 → 다대일 모델보다 더 많은 병렬성 제공
유저 스레드는 매핑된 커널 스레드가 스케줄될 때 실행된다.
사용자 공간의 API 호출을 통해 커널에게 요청하여 실제 실행 단위인 커널 스레드를 생성 후, 사용자 공간에서는 이 커널 레드를 가리키고 제어하기 위한 핸들(유저 스레드 개념)을 얻는 과정 (두 개의 스레드가 생겨나는 게 아님!)
장점:
Enhanced Concurrency: Non-blocking 스레드가 계속 실행되어 응답성을 향상
Parallel Execution: 멀티 코어 시스템이 true parallelism 지원 → 멀티 프로세서에서 멀티 스레드가 다른 코어에서 병렬로 동시 실행 가능
단점:
Resource Intensive: 각 유저 스레드는 해당 커널 스레드가 요구됨 → 많은 수의 커널 스레드가 시스템 성능에 부담을 줄 수도 있음.
Performance Impact: 많은 커널 스레드를 관리하는 데의 오버헤드가 시스템 성능을 저하할 수 있다.
ex. Linux, Windows NT/XP/2000, Solaris 9 이후의 운영체제
다대다 모델(Many-to-Many Model)
여러 개의 유저 스레드를 그보다 작거나 같은 수의 커널 스레드로 멀티플렉스한다.
커널 스레드의 수는 응용 프로그램이나 특정 기계에 따라 결정된다.
Solaris 9 이전, Windows NT/2000
일대일 모델에서처럼 너무 많은 스레드를 생성하지 않도록 주의할 필요 없이, 필요한 만큼 많은 유저 스레드를 생성할 수 있으며, 그에 상응하는 커널 스레드가 멀티 프로세서에서 병렬로 수행될 수 있다.
스레드가 blocking system call을 발생시켰을 때 커널이 다른 스레드의 실행을 스케줄할 수 있다.
한 유저 스레드가 하나의 커널 스레드에만 연관되는 것도 허용한다. → 두 수준 모델(two-level model)
IRIX, HP-UX, Tru64 UNIX, Solaris 8 이전
다대다 모델은 융통성 있어 보이지만, 구현이 어렵고 커널 스레드 수를 제한하는 것의 중요성이 줄어들었다.
대부분의 현대 운영체제는 처리 코어 수가 증가함에 따라 일대일 모델을 사용한다.
4. 스레드 라이브러리(Threads Library)
스레드 라이브러리는 프로그래머에게 스레드를 생성하고 관리하기 위한 API를 제공한다.
POSIX Pthreads, Windows, Java 세 종류의 라이브러리가 주로 사용된다.
구현 방법
1) 커널 지원 없이 사용자 공간에서만 제공
라이브러리의 모든 코드와 자료구조가 사용자 공간에 존재
라이브러리 함수를 호출하면 시스템 콜이 아니라 사용자 공간의 지역 함수를 호출하게 되는 것을 의미
2) 운영체제에 의해 지원되는 커널 수준 라이브러리 구현
라이브러리를 위한 코드와 자료구조가 커널 공간에 존재
라이브러리 API를 호출하면 커널 시스템 콜을 부르게 되는 것을 의미
Pthreads
POSIX standard(IEEE 1003.1c)가 스레드 생성과 동기화를 위해 제정한 표준 API =스레드 확장판
스레드의 동작에 관한 명세일 뿐, 구현한 것이 아님. 운영체제 설계자가 구현.
사용자 또는 커널 수준 라이브러리로 제공 가능
UNIX, Linux, macOS 시스템에서 사용
전역 변수로(함수 외부에) 선언된 데이터는 같은 프로세스에 속한 모든 스레드가 공유
대체로 one-to-one 방식으로 구현됨
주요 함수:
pthread_create(): 별도의 스레드 생성
pthread_join(): 자식 스레드가 종료하기를 부모 스레드가 기다리게 함
pthread_mutex_lock()
pthread_cond_wait()
*TLS(Threads Local Storage): 한 프로세스 내에서도 스레드 별로 다른 전역 변수 효과를 내고 싶을 때 유용
별도의 스레드에서 음이 아닌 정수의 합을 구하는 다중 스레드 프로그램(C) 예제:
스레드 주소 공간을 그리는 예제:
Windows
Windows 시스템에서 사용 가능한 커널 수준 라이브러리
전역 변수로(함수 외부에) 선언된 데이터는 같은 프로세스에 속한 모든 스레드가 공유
CreateThread()에 의해 생성, WaitForSingleObject()에 의해 자식 스레드가 종료할 때까지 생성 스레드 봉쇄
Java
Java 프로그램에서 직접 스레드 생성과 관리를 가능하게 함.
But 대부분 JVM 구현은 호스트 운영체제에서 실행 → 통상 호스트 시스템에서 사용 가능한 스레드 라이브러리를 이용해 구현
JVM 내부에서 Java 스레드를 실제 운영체제의 스레드로 구현하는 방식은 각기 달라도, 개발자는 OS별 차이를 신경 쓸 필요 없이 개발(플랫폼 독립성) → 표준 Java API를 통해 일관된 방식으로 스레드를 사용할 수 있다.
공유 데이터에 대한 접근이 스레드 사이에 명시적으로 조율되어야 함
Java 프로그램에서 스레드를 생성하는 기법:
1) Thread 클래스 확장: Thread 클래스를 상속받아 run() 메소드 재정의
2) Runnable 인터페이스 구현: Runnable 인터페이스를 구현하여 Thread 객체에 전달
람다 표현식을 이용해 스레드 생성:
join() 메소드:
자바에서 부모 스레드가 자식 스레드의 작업 완료를 기다리는 기능 제공
여러 스레드를 기다려야 할 때는 join() 메소드를 for 루프 안에 넣어 사용
5. 암묵적 스레딩(Implicit Threading)
스레드 생성과 관리의 책임을 응용 프로그램 개발자에서 컴파일러와 런타임 라이브러리로 이전
→ 개발자는 병렬 작업만 식별하면 되고 라이브러리는 스레드 생성 및 관리에 대한 특정 세부 사항을 결정함
멀티 코어 프로세서를 활용하는 동시성 및 병렬 애플리케이션 설계를 지원하기 위한 점점 인기 있는 추세
스레드 풀(Thread Pool)
배경) 다중 스레드 서버의 문제점:
1. 서비스할 때마다 스레드를 생성하는 데 소요되는 많은 시간, 이에 비해 작업 종료 후 바로 폐기되는 점
2. 모든 요청마다 새 스레드를 만들어 서비스하게 되면 시스템에서 동시에 실행할 수 있는 최대 스레드 수의 한계를 정해야만 함(무한정 만들면 자원이 고갈됨)
스레드 풀: 미리 일정 개수의 스레드를 생성해놓고(pool), 필요할 때 작업을 할당하여 재사용하는 방식
스레드 풀은 미리 생성된 스레드 집합을 관리하고, 새로운 요청이 있을 때마다 새 스레드를 만드는 대신에 풀에서 스레드를 재사용한다.
장점:
Quick Response: 새 스레드 생성보다 기존 스레드 서비스가 더 빠름
Resource Management: 임의 시각에 존재할 스레드 개수를 제한 → 제한된 자원을 사용하는 시스템에 중요
Task Management: 스레드 관리로부터 태스크 실행을 분리 → 태스크를 지연 스케줄하거나 주기적으로 실행되도록 하는 등 유연한 태스크 실행 전략이 가능하다.
OpenMP
컴파일러 지시자(directive)를 사용하여 코드의 특정 영역(주로 루프)을 병렬로 실행하도록 컴파일러에게 지시
C, C++, FORTRAN으로 작성된 API와 컴파일러 디렉티브의 집합.
공유 메모리 환경에서 병렬 프로그래밍을 할 수 있도록 도와준다.
병렬로 실행될 수 있는 블록을 찾아 병렬 영역(parallel regions)이라 구별한다.
6. 스레드와 관련된 문제들(Threading Issues)
Fork() 및 Exec() 시스템 콜(The fork() and exex() System Calls)
한 프로그램의 스레드가 fork()를 호출하여 새로운 프로세스 생성 시, 존재하는 모든 스레드를 복제해야 하는가, fork()를 호출한 스레드만 가지는 프로세스여야 하는가?
→ UNIX는 두 가지 버전의 fork()를 모두 제공
exec()를 호출하면 현재 프로세스 주소공간 전체가 새로운 프로그램으로 대체되어 기존 스레드들이 소멸됨
exec()가 성공적으로 호출되면 exec()를 호출한 스레드를 제외하고 기존 모든 스레드가 즉시 소멸, exec()를 호출한 스레드는 새로운 프로그램의 시작점(entry point)부터 실행을 시작
→ 사실상 단일 스레드 상태에서 새로운 프로그램을 실행하게 되는 것
→ fork()를 부르자마자 다시 exec()를 부르는 것은 불필요하게 되므로, 이 경우 fork()를 호출한 스레드만 복사하는 게 적절.
→ 새 프로세스가 fork() 후 exec()를 하지 않는 경우, 새 프로세스는 모든 스레드들을 복제해야 함.
신호 처리(Signal Handing)
Signal: UNIX에서 프로세스에 어떤 이벤트가 일어났음을 알려주기 위해 사용. 동기식 또는 비동기식으로 전달됨.
Synchronus Signals: 실행 중인 프로세스가 불법적인 메모리 접근, 0으로 나누기 등의 행동을 하면 신호가 발생. 신호를 발생시킨 연산을 수행한 동일한 프로세스에 전달됨.
Asynchronous Signals: Ctrl+C로 프로세스 강제 종료시키는 경우, 타이머가 만료되는 경우 등 신호가 실행 중인 프로세스 외부로부터 발생하면 그 프로세스는 비동기식으로 신호를 전달받음. 보통 다른 프로세스에 전달됨.
모든 신호마다 커널이 실행시키는 Default signal handler가 존재한다.
but 신호를 처리하기 위해 호출되는 User-defined signal handler에 의해 대체될 수 있음 → 신호를 무시하거나, 특정 액션을 취할 수 있음
싱글 스레드에서는 시그널이 오면 해당 프로세스가 처리했지만, 멀티 스레드에서는 '어느' 스레드가 시그널을 처리할 것인가가 문제됨
선택지:
1. 신호가 적용될 모든 스레드에 전달
2. 그냥 모든 스레드에 전달
3. 몇몇 스레드에 선택적으로 전달
4. 특정 스레드가 모든 신호를 전달받도록 지정; 시그널 전담 처리 스레드 할당
동기식 신호의 경우 신호가 발생한 스레드에만 전달되어야 함
비동기식 신호의 경우 불명확하여 프로세스 내 모든 스레드에 전달되어야 할 수도 있음
신호를 전달하는 데 사용되는 표준 UNIX 함수=kill, POSIX Pthreads 함수=pthread_kill:
특정 신호가 전달될 프로세스를 지정
스레드 취소(Thread Cancellation)
스레드가 끝나기 전에 강제 종료시키는 작업
목적 스레드(target thread): 취소되어야 할 스레드
목적 스레드의 취소 발생 방식:
비동기 취소(즉시 강제 종료) vs 지연 취소(스레드 스스로 주기적으로 강제 종료되어야 할지 체크 후 안전하게 종료)
Pthreads의 스레드 취소 예시:
pthread_cancel() 함수는 스레드를 취소하라는 요청만 하고, 실제 취소는 스레드 상태에 달려 있다.
Pthreads가 지원하는 3가지 취소 모드:
취소가 비활성(disabled)되어 있으면 스레드를 취소할 수 없고, 취소 요청이 보류되어 있어 나중에 응답할 수 있다.
기본 취소 유형은 지연 취소(Deferred); 스레드가 취소 점(cancellation point)에 도달한 경우에만 취소 발생
pthread_testcancel() 호출하여 취소 점을 설정할 수도 있다.
Linux 시스템에서 스레드 취소는 신호를 통해 처리된다.
Pthreads는 스레드가 취소될 때 정리 핸들러(cleanup handler)라는 함수가 호출되게 하여 스레드 종료 전 스레드가 가진 모든 자원을 해제할 수 있다.
스레드-로컬 저장장치(Thread-Local Storage)
Thread-local storage(TLS): 각 스레드가 자기만 액세스할 수 있는 데이터
지역 변수와 다른 개념; 지역 변수는 하나의 함수가 호출되는 동안에만 보이지만, TLS는 전체 함수 호출에 걸쳐 보인다.
개발자가 스레드 풀처럼 암묵적 기법을 사용하는 경우 등 스레드 생성 과정에 대해 제어할 수 없는 경우 유용하다.
정적 데이터(static data)와 유사하지만, TLS 데이터는 스레드마다 고유하다는 점이 다르다.
스케줄러 액티베이션(Scheduler Activations)
스레드 라이브러리와 커널의 통신 문제; 적절한 커널 스레드의 수를 동적으로 유지하는 일
다대다 모델 또는 두 수준 모델을 구현하는 시스템들은 사용자와 커널 스레드 사이에 중간 자료구조 LWP(경량 프로세스)를 둔다.
응용 프로그램이 사용자 스레드를 실행하기 위해 스케줄할 virtual processor처럼 표현됨
각 LWP는 하나의 커널 스레드에 부속되어 있다.
LWP에 연결된 커널 스레드가 블락되면 LWP와 이에 연결된 유저 스레드도 블락되기 때문에 적절한 LWP 개수도 필요하다.
스케줄러 액티베이션(Scheduler activation)이 Upcall 프로시저를 제공:
스레드가 봉쇄되려고 하면 커널은 응용 프로그램에게 특정 이벤트에 대해 upcall로 알려주고, 커널은 새로운 LWP를 응용 프로그램에 할당하여 응용 프로그램은 새로운 LWP상에서 Upcall handler를 실행한다.
Upcall은 스레드 라이브러리의 Upcall handler에 의해 봉쇄되는 스레드의 상태를 저장하고 실행 중이던 LWP를 반환하여 새로운 LWP에서 실행 가능한 다른 스레드를 스케줄한다.
봉쇄되었던 스레드가 실행 가능해지면 이에 대한 upcall도 수행하여 가용 가능한 스레드를 체크할 수 있다.
응용 프로그램이 정확한 수의 커널 스레드를 유지하도록 해준다.
7. 운영체제 사례(Operating System Examples)
리눅스 스레드(Linux Threads)
리눅스는 스레드보다 태스크라는 용어를 사용
clone() 시스템 콜을 통해 스레드를 생성; 호출 시 부모와 자식 태스크가 자료구조를 얼마나 공유할지 결정하는 플래그 집합이 전달된다.
시스템 태스크마다 고유한 커널 자료구조(struct task_struct) 존재; 데이터가 저장된 다른 자료구조를 가리키는 포인터를 포함
'Study > OS' 카테고리의 다른 글
[운영체제] 6. 스레드 동기화(thread synchronization) (0) | 2025.06.05 |
---|---|
[운영체제] 5. CPU Scheduling (0) | 2025.04.18 |
[운영체제] 3. Process (3) | 2025.04.17 |
[운영체제] 2. Operating System Structures (1) | 2025.04.09 |
[운영체제] 1. Introduction (2) | 2025.04.06 |