개발을 하면서 내가 짠 코드들을 머릿속으로 온전히 이해하기가 쉽지 않다.
그리고 개발이 시작하기 전, 끝난 이후에 구현한 것에 대한 클래스 다이어그램을 직접 그리는 시간이 오래 걸리는 경우가 있다.
이런 경우 ‘IntelliJ에서 제공하는 다이어그램 기능을 쓰면 시각적인 이해 + 시간 절약
이 되지 않을까?’ 하는 마음으로 해당 포스팅을 작성하게 되었다.
다리 건너기
를 예시로 가져왔다.이 글의 사진과 내용은 공룡책 과 컴퓨터학부 수업인 운영체제 강의자료를 기반으로 작성했습니다.
멀티-레벨 큐(MLQ)
MLQ
란 준비 큐를 여러 개로 분할해 관리하는 스케줄링 기법을 말한다.
즉 프로세스들이 CPU를 기다리기 위해 한 줄로 서는 것이 아니라 여러 줄로 서는 것이다.
MLQ는 일반적으로 성격이 다른 프로세스들을 별도로 관리하고, 프로세스의 성격에 맞는 스케줄링을 적용하기 위해 ready queue를 별도로 두게 된다.
미래를 예측하기 위해 과거를 배우는 스케줄러이다.
ML(F)Q는 다수의 개별 queue를 유지한다.
Rule 1
: Run-time이 짧은 job에게 우선순위(priority)를 부여한다.
Rule 2
: 만약 우선순위가 같으면, 라운드 로빈 스케줄링(RR)을 사용한다.
MLQ는 관찰된 동작에 따라 작업의 우선순위를 변경한다.
Example 동작 :
job은 I/O를 기다리는 동안 CPU를 반복적으로 양도한다.
대화형(interative) 프로세스의 동작은 다음과 같습니다.
우선순위를 높게 유지(단시간 슬라이스 포함) -> 응답 시간 향상
job이 장시간 동안 CPU를 집중적으로 사용한다.
일괄(batch) 작업의 동작은 다음과 같습니다.
우선순위 감소(긴 시간 슬라이스 포함) -> 소요 시간 단축
이러한 방식으로 MLQ는 프로세스가 실행될 때 프로세스에 대해 학습하고, 따라서 job의 기록을 사용하여 미래의 동작을 예측합니다.
멀티레벨 피드백 큐(MLFQ)
MLFQ
는 CPU를 기다리는 프로세스를 여러 queue에 줄 세운다는 측면에서 MLQ와 동일하나, 프로세스가 하나의 queue에서 다른 queue로 이동 가능하다는 점이 다르다.이 글의 사진과 내용은 공룡책 과 컴퓨터학부 수업인 운영체제 강의자료를 기반으로 작성했습니다.
32bit CPU의 메모리 주소 레지스터의 크기는 32bit
CPU가 만들어내는(표현할 수 있는) 주소 개수 : 2^32
Stack : 지역 변수
Heap : 할당된 변수 or malloc(), calloc()
OS가 많은 가상의(논리적인) CPU가 존재하는 것처럼 환상을 조장한다.
(single-core system으로 가장한다면) 물리적인 CPU 개수는 1개이다.
Time-sharing
-> 핵심: 시간을 나눠서 사용한다
User가 원하는 만큼 동시에 프로세스를 실행할 수 있다.
Round robin 방식에 의해서 하나의 프로세스를 실행하고, 멈춘 다음에 다른 프로세스를 실행한다.
CPU Virtualization을 구현하기 위해서 필요한 2가지
OS’s low-level mechanism (How) (e.g. Context switch)
OS’s policy (Which) (e.g. Scheduling policies)
Direct execution
: CPU가 프로세스를 수행하는 동안에는 OS가 간섭하지 않는다.위의 표와 같이 한 번 실행하면 종료될 때까지 프로세스는 멈추지 않는다.
하지만 이 방법으로는 지금 구현하려고 하는 CPU 가상화를 구현할 수 없다.
한 번 실행된 프로세스에 대해 제어를 할 수 없으며 Time Sharing을 할 수 없다.
Time Sharing을 하려면 어떠한 Limit를 해줘야 CPU 가상화를 구현할 수 있다.
Direct Execution는 프로그램을 빠르게 실행하는 장점이 있다.
하지만 실행 도중에 (1)디스크에 대한 I/O 요청이 발생하거나 (2)CPU 혹은 Memory에서 더 많은 시스템 리소스에 대한 엑세스 권한을 획득하는 경우를 해결할 수 없기 때문에 사용할 수 없다.
이를 해결하기 위한 방법으로 Limited Direct Execution
을 사용한다.
What to limit?
General memory access
Disk I/O
Certain instructions
이러한 문제를 해결하기 위해 User mode와 Kernel 모드로 상태를 구분한다.
User mode
에서는 수행할 수 있는 작업에 제한을 둬서 Application이 HW 리소스에 대한 완전한 접근을 가질 수 없다.
이를 보완하기 위해Kernel mode
에서는 OS가 CPU 권한을 가지고 I/O요청과 같은 권한이 필요한 모든 작업을 수행할 수 있다.
[System call 이란]
System call은 일종의 SW적인 인터럽트로서 사용자 프로그램이 system call을 할 경우 trap이 발생해 CPU의 제어권이 OS로 넘아가게 된다.
그러면 OS는 해당 system call을 처리하기 위한 루틴으로 가서 정의된 명령을 수행한다.
테스트코드를 작성하면서 JUnit과 AssertJ 사용법을 익혀야 한다는 사실을 알게 되고 난 이후에 따로 공부를 하게 되었다.
그 중 AssertJ 개념을 정리하자.
AssertJ
: Java 테스트에서 유창하고 풍부한 어설션을 작성하는 데 사용되는 오픈 소스 커뮤니티 기반 라이브러리이다.<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.4.1</version>
<scope>test</scope>
</dependency>
이 종속성은 기본 Java 어설션에만 적용된다.
Java 7 및 이전 버전의 경우 AssertJ 코어 버전 2.xx를 사용해야 한다.
최신 버전은 AssertJ 코어 버전 3.23를 제공해주고 있다.
AssertJ는 다음을 위해 유창하고 아름다운 어설션을 쉽게 작성할 수 있도록 하는 일련의 클래스 및 유틸리티 메서드를 제공한다.
Standard Java
Java 8
Guava
Joda Time
Neo4J and
Swing components
import org.assertj.core.api.Assertions;
Assertions.assertThat()
메서드에 전달한 다음 실제 어설션을 따라야 한다.public class Dog {
private String name;
private Float weight;
// standard getters and setters
}
Dog fido = new Dog("Fido", 5.25);
Dog fidosClone = new Dog("Fido", 5.25);
복사
다음과 같이 두 object의 동등성을 비교할 수 있는 방법이 있다.
내용을 비교하려면,
isEqualToComparingFieldByFieldRecursively()
을 사용한다.
assertThat(fido).isEqualToComparingFieldByFieldRecursively(fidosClone);
진실 테스트를 위한 간단한 방법이 있다.
isTrue()
isFalse()
assertThat("".isEmpty()).isTrue();
이 글의 사진과 내용은 공룡책 과 컴퓨터학부 수업인 운영체제 강의자료를 기반으로 작성했습니다.
프로세스
란 실행 중인 프로그램(program in execution)을 뜻한다.
Memory
에 올라가서 실행되기 시작하면 비로소 생명력을 갖는 프로세스가 되며, 프로세스는 CPU를 반환하고 입출력 작업을 수행하기도 한다.프로그램은 “a set of data associated with that code”을 뜻한다.
Code, Program, and Process
Process Creation: A Little More Detail
실행
(Running)상태는 프로세스가 (1)해당 프로세스를 수행하기 위해 필요한 모든 리소스를 갖고 있고, (2)CPU에 대한 권한이 있다.
CPU 권한이 없을 때 Ready 상태로 이동한다.
필요한 정보가 충분하지 못한 상태(I/O 작업이 시작할때)인 경우 Blocked로 이동한다.
준비
(Ready)상태는 (1)해당 프로세스를 수행하기 위해 필요한 모든 리소스는 갖고 있지만, (2)CPU에 대한 권한이 없다.
봉쇄
(Blocked, Wait)상태는 해당 프로세스를 수행하기 위한 필요한 모든 리소스를 갖고 있지 못하고, CPU에 대한 권한도 없다.(ex. 입출력 작업이 진행 중인 경우)
시작
(New)상태는 프로세스을 위한 PCB가 생성되었지만 아직 충분한 리소스를 할당받지 못한 상태이다.(메모리 획득을 승인받지 못한 상태)
종료
(Terminated)상태는 프로세스가 작업을 완료하여 메모리 자원을 반납하고 PCB가 삭제된 상태이다.
실행시킬 프로세스를 변경하기 위해 원래 수행 중이던 프로세스의 문맥을 저장하고 새로운 프로세스의 문맥을 세팅하는 과정을 문맥교환
(context switch)이라고 한다.
예를 들어, 실행 상태에 있던 프로세스가 입출력 요청 등으로
봉쇄 상태로 바뀌는 경우를 들 수 있다.
이때 준비 상태에 있는 프로세스들 중에서
CPU를 할당받을 프로세스를 선택한 후
실제로 CPU의 제어권을 넘겨받는 과정을 CPU 디스패치(dispatch)라고 한다.
그 밖에 상태들
일정기간 exit된 프로세스를 OS가 관리하는 경우를 Zombie
상태라고 한다.
자식 프로세스가 종료되기 전에 부모 프로세스가 종료된 상태를 Orphan
상태라고 한다.
메인 메모리의 용량이 실행해야 하는 더 많은 프로세스를 로드하기에 충분하지 않은 경우 Swapping
을 사용한다.
메모리 공간 확보
를 위해 필요에 의해서 메인 메모리에 있는 프로세스 일부를 디스크에 내려보내는 것을 말한다.Suspend vs Preemption
Suspend : 메모리 공간이 부족해서 급한 것부터 처리하기 위해서 메인 메모리에 있는 Ready 또는 Blocked(Wait)에 있는 것들을 디스크로 내려 보내는 것을 말한다.
Preemption: Running state에서 Ready state로 가는 것을 말한다. (현재 수행되고 있는 것을 멈춘다)
Swap Out : 메모리 -> 디스크
Swap In : 디스크 -> 메모리
프로세스 제어블록
(Process Control Block: PCB)이란 OS가 시스템내의 프로세스들을 관리하기 위해 프로세스마다 유지하는 정보들을 담는 커널 내의 자료구조를 뜻한다.
쉽게 말해, 기본 프로세스에 대한 정보를 갖고 있는 자료구조이다.
프로세스 상태 관리와 문맥 교환을 위해 필요하다.
PCB는 프로세스 생성시 만들어지며 메모리에 유지된다.
PID(Process Identification Number)
Process state (running, blocked, ready) : CPU를 할당해도 되는지 여부이다.
Program counter : 다음에 수행할 명령어의 위치를 가리킨다.
CPU registers : CPU 연산을 위해 현 시점에 레지스터에 어떤 값을 저장하고 있는지를 나타낸다.
Sheduling information : CPU 스케줄링을 위한 필요한 정보이다.
Memory management information : 메모리 할당을 위해 필요한 정보이다.
I/O status information : 프로세스가 오픈한 파일 정보 등 프로세스의 입출력 관련 상태 정보를 나타낸다.
Accounting informaion : 사용자에게 자원 사용 요금을 계산해 청구하는 용도이다.
Program counter와 CPU registers는 “context”에 해당한다.
문맥교환이란 하나의 사용자 프로세스로부터 다른 사용자 프로세스로 CPU의 제어권이 이양되는 과정을 뜻한다.
문맥교환은 타이머 인터럽트가 발생하는 경우 외에 실행 중이던 프로세스가 입출력 요청이나 다른 조건을 충족하지 못해 CPU를 회수당하고 봉쇄 상태가 되는 경우에도 발생할 수 있다.
시스템이 부팅된 후 최초의 프로세스는 OS가 직접 생성하지만 그다음부터는 이미 존재하는 프로세스가 다른 프로세스를 복제, 생성하게 된다.
이때 프로세스를 생성한 프로세스를 부모
프로세스라고 하고, 새롭게 생성된 프로세스를 자식
프로세스라고 한다.
즉, 부모 프로세스가 자식 프로세스를 생성하게 되는 것이다.
이러한 방식을 통해 프로세스는 족보와 같은 계층을 형성하게 된다.
프로세스의 세계에서는 자식이 먼저 죽고, 이에 대한 처리는 자식을 생성했던 부모 프로세스가 담당하는 방식으로 진행된다.
즉, 부모 프로세스는 자식 프로세스들을 연쇄적으로 종료시킨 후에야 종료될 수 있다.
즉, 프로세스 ID(pid)를 제외한 모든 정보를 그대로 복사하는 방법을 사용한다. 따라서 부모 프로세스와 자식 프로세스는 비록 주소 공간을 따로 갖게 되지만 주소 공간 내에는 동일한 내용을 가지게 된다.
fork()를 통해 생성된 자식 프로세스는 exec() 시스템 콜을 통해 새로운 프로그램으로 주소 공간을 덮어씌울 수 있다.
이때 부모 프로세스에서는 자식 pid가 반환되고 자식 프로세스
에서는 0이 반환된다. 만약 fork() 함수 실행이 실패하면 -1을 반환한다.
첫번째 예시( int main() )
위에서 자식 프로세스는 fork() 함수의 반환값이 0이라고 했다.
따라서 pid 가 0일 때는(child) x가 1증가해 “child: x=2”이 출력된다.
부모 프로세스는 자식 pid를 받으므로, 0이 아니기 때문에 “parent: x=0”이 출력된다.
두번째 예시( void fork2() )
fork()를 할 때마다 자식 프로세스가 생성되는 것을 확인할 수 있다.
정리하자면, fork() 반환 결과가 0이라면 자식 프로세스
이고 0이 아니면(자식프로세스의 id를 가질것) 부모 프로세스
이다.