728x90

반복문(Iteration)은 프로그램 내에서 특정 코드 블록을 조건이 만족하는 동안 반복적으로 실행하기 위한 프로그래밍 구조다. 반복문은 코드의 재사용을 통해 작업을 간소화하고, 프로그램의 효율성을 높이는 데 중요한 역할을 한다. 주로 데이터의 집합을 처리하거나, 특정 조건을 만족할 때까지 반복 작업을 수행할 때 사용된다.

 

반복문은 조건이 항상 참이 되어 반복문이 종료되지 않는 상황. 프로그램의 의도하지 않은 동작이나 성능 문제를 일으킬 수 있다. 그리고 break를 사용하여 반복을 즉시 종료하거나, continue를 사용하여 현재 반복을 건너뛰고 다음 반복으로 넘어갈 수 있다.

 

for 반복문 정해진 범위나 순서가 있는 데이터 집합을 순회할 때 사용된다. 반복의 시작과 끝이 명확할 때 주로 사용한다. 리스트의 각 요소에 대해 반복하거나, 정해진 횟수만큼 반복 작업을 수행한다.

 

while 반복문 조건이 참(True)인 동안 코드 블록을 반복 실행한다. 반복 횟수가 불명확하거나, 특정 조건에 따라 반복을 종료해야 할 때 유용하다. 사용자로부터 입력을 받아 처리하는 동안 계속 실행하거나, 특정 상태가 변경될 때까지 반복한다. while 반복문 내에서는 조건이 결국 거짓이 될 수 있도록 조건에 영향을 미치는 변수를 적절히 변경해야 한다.

 

재귀(Recursion)는 함수가 자기 자신을 호출하는 방식을 말한다. 재귀를 사용하면 복잡한 문제를 간단한 부분 문제로 나누어 해결할 수 있으며, 특히 반복적인 계산을 간결하게 표현할 수 있다. 재귀는 컴퓨터 과학에서 매우 중요한 개념으로, 다양한 알고리즘과 데이터 구조(예: 정렬 알고리즘, 트리와 그래프의 탐색)에서 널리 사용된다.

 

기저 조건(Base Case)은 재귀 호출을 중단하고, 함수가 자기 자신을 더 이상 호출하지 않도록 하는 조건. 이는 재귀의 종료 조건이다. 기저 조건을 제대로 설정하지 않으면 무한 재귀 호출로 이어져 스택 오버플로우(Stack Overflow)가 발생할 수 있다.

 

재귀는 문제를 더 작은 단위로 나누어 접근하기 때문에, 분할 정복(Divide and Conquer) 알고리즘을 구현할 때 자연스럽게 적용된다. 꼬리 재귀 최적화(Tail Recursion Optimization)와 같은 기법을 사용하여 재귀 함수의 성능을 개선할 수 있다.

 

꼬리 재귀 최적화(Tail Recursion Optimization, TRO)는 재귀 호출을 효율적으로 처리하는 컴파일러 또는 인터프리터의 기능이다. 이 최적화는 재귀 함수의 마지막 동작이 함수 자신의 호출일 때, 즉 재귀 호출이 꼬리 위치(tail position)에 있을 때 적용할 수 있다. 꼬리 재귀 함수는 현재의 함수 프레임을 재사용하여 새로운 함수 호출을 수행함으로써, 호출 스택의 크기를 증가시키지 않고 재귀 호출을 실행한다. 이로 인해 메모리 사용량이 감소하고, 성능이 향상될 수 있다.

 

# 꼬리 재귀를 사용하지 않는 팩토리얼 함수
def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n - 1)

# 꼬리 재귀를 사용하는 팩토리얼 함수
def factorial_tail_recursive(n, accumulator=1):
    if n == 1:
        return accumulator
    else:
        return factorial_tail_recursive(n - 1, n * accumulator)

 

위 예시에서 factorial_tail_recursive 함수는 꼬리 재귀를 사용한다. 계산 결과를 누적하는 accumulator 인자를 추가하여, 마지막 호출에서 바로 결과를 반환하도록 설계되었다. 이러한 방식으로 함수가 자기 자신을 호출할 때마다 새로운 스택 프레임을 생성하지 않고 기존의 스택 프레임을 재사용할 수 있어, 메모리 사용량이 줄어든다.

 

깊은 복사(deep copy)는 객체와 그 객체가 포함하는 모든 자식 객체들까지 완전히 새로운 복사본을 생성하는 방식이다. 이는 원본 객체와 복사본 객체 사이에 서로 영향을 주지 않으려 할 때 사용한다. 반면, 얕은 복사(shallow copy)는 객체의 최상위 레벨만 복사하여 새 객체를 생성하고, 내부의 객체들은 원본 객체와 동일한 참조를 공유한다. 따라서 내부 객체가 변경되면 얕은 복사본도 영향을 받는다.

 

깊은 복사는 복잡한 객체 구조를 완전히 독립적으로 복제할 때 사용한다. 예를 들어, 객체 내의 리스트나 딕셔너리 같은 다른 객체를 포함하는 경우가 해당한다.

 

얕은 복사는 객체 구조가 간단하거나, 내부 객체들을 공유해도 문제가 없을 때 유용하다.

 

import copy

# 깊은 복사 예시
obj = [1, [2, 3], 4]
deep_copied_obj = copy.deepcopy(obj)
deep_copied_obj[1][0] = '변경'
# 원본 obj는 변경되지 않음
print(obj)

# 얕은 복사 예시
shallow_copied_obj = copy.copy(obj)
shallow_copied_obj[1][0] = '변경'
# 원본 obj의 내부 리스트도 변경됨
print(obj)

#출력 :
#[1, [2, 3], 4]
#[1, ['변경', 3], 4]

 

위 코드에서 deepcopy() 함수는 obj 객체의 깊은 복사본을 생성하여 deep_copied_obj에 할당한다. 내부 리스트를 변경해도 원본 obj는 영향을 받지 않는다. 반면, copy() 함수로 생성한 얕은 복사본 shallow_copied_obj에서 내부 리스트를 변경하면, 원본 obj의 해당 리스트도 함께 변경된다.

728x90
728x90

Array(배열)은 컴퓨터 과학에서 기본적인 자료 구조 중 하나로, 동일한 자료형의 요소들을 순서대로 나열한 것이다. 배열은 연속적인 메모리 위치에 데이터 요소들을 저장하여, 인덱스를 통해 각 요소에 빠르게 접근할 수 있는 장점을 가진다.

 

대부분의 프로그래밍 언어에서 배열을 생성할 때 그 크기가 고정되며, 나중에 크기를 변경할 수 없다. 모든 배열 요소는 같은 자료형이어야 한다. 이로 인해 메모리 관리가 용이하고, 요소 접근 시간이 단축된다. 배열의 요소는 0부터 시작하는 인덱스를 사용하여 접근할 수 있다. 인덱스를 통한 접근은 O(1)의 시간 복잡도를 가진다.

 

인덱스를 사용하여 배열의 어떤 위치에도 즉시 접근할 수 있고 연속적인 메모리 할당 덕분에 메모리 사용이 효율적이다.

 

배열을 생성할 때 크기를 정해야 하며, 크기를 동적으로 변경하는 것이 어렵다. 그리고  배열 중간에 요소를 삽입하거나 삭제할 경우, 나머지 요소들을 이동해야 하므로 비효율적이다.

 

간단한 리스트 데이터를 다룰 때 유용하며, 크기가 고정되어 있고, 자주 변경되지 않는 데이터 집합에 적합하다. 수학적 계산이나 고정된 데이터 집합을 다루는 알고리즘에서 주로 사용된다.

# 배열(리스트) 생성
numbers = [1, 2, 3, 4, 5]

# 배열 요소에 접근
print(numbers[0])  # 첫 번째 요소 출력, 결과: 1

# 배열 요소 수정
numbers[2] = 10  # 세 번째 요소를 10으로 변경
print(numbers)  # 결과: [1, 2, 10, 4, 5]

# 배열에 요소 추가
numbers.append(6)  # 배열의 끝에 6 추가
print(numbers)  # 결과: [1, 2, 10, 4, 5, 6]

# 배열의 특정 위치에 요소 추가
numbers.insert(1, 20)  # 두 번째 위치에 20 추가
print(numbers)  # 결과: [1, 20, 2, 10, 4, 5, 6]

# 배열의 요소 삭제
del numbers[1]  # 두 번째 요소 삭제
print(numbers)  # 결과: [1, 2, 10, 4, 5, 6]

# 배열의 길이 확인
print(len(numbers))  # 결과: 6

# 배열을 순회하며 요소 출력
for number in numbers:
    print(number)

 

Linked List(연결 리스트)는 선형 자료 구조의 일종으로, 데이터 요소들이 노드 형태로 저장되며 각 노드는 다음 노드를 가리키는 포인터(또는 참조)를 포함한다. 연결 리스트는 배열과 비교했을 때, 동적으로 메모리를 할당하며 요소의 추가와 삭제가 유연하게 이루어진다는 특징을 가진다.

 

노드(Node)데이터와 다음 노드를 가리키는 포인터로 구성된다. 마지막 노드는 다음 노드가 없음을 나타내기 위해 보통 null을 포인터 값으로 갖는다.

 

헤드(Head) 리스트의 첫 번째 노드를 가리키는 포인터이다. 리스트의 시작점 역할을 한다.

 

단일 연결 리스트(Singly Linked List) 각 노드가 다음 노드만을 가리키는 가장 단순한 형태의 연결 리스트다.

 

이중 연결 리스트(Doubly Linked List) 각 노드가 이전 노드와 다음 노드 양쪽을 가리킨다. 양방향 탐색이 가능하다.

 

원형 연결 리스트(Circular Linked List) 마지막 노드가 첫 번째 노드를 가리키는 형태로, 리스트의 끝과 시작이 연결된다.

 

배열과 달리 미리 크기를 지정할 필요가 없으며, 요소의 추가와 삭제 시 리스트의 크기가 동적으로 조절되고  필요할 때마다 메모리 공간을 할당받아 사용하기 때문에, 사용하지 않는 메모리 공간이 생기지 않는다. 포인터만 변경하면 되기 때문에, 배열에 비해 훨씬 효율적으로 데이터의 삽입과 삭제가 이루어진다.

 

배열처럼 인덱스를 통한 직접 접근이 불가능하며, 특정 요소에 접근하기 위해서는 헤드부터 순차적으로 탐색해야 한다. 그리고 각 노드가 데이터 외에도 포인터를 저장해야 하므로, 추가적인 메모리 공간이 필요하다.

 

연결 리스트는 데이터의 삽입과 삭제가 빈번하게 발생하거나, 데이터의 크기를 미리 예측하기 어려운 경우에 유용하게 사용된다. 예를 들어, 운영체제에서 프로세스 관리, 메모리 관리 등에 연결 리스트가 활용된다.

 

class Node:
    def __init__(self, data):
        self.data = data  # 노드가 저장할 데이터
        self.next = None  # 다음 노드를 가리키는 포인터

class LinkedList:
    def __init__(self):
        self.head = None  # 리스트의 첫 번째 노드를 가리키는 헤드

    def append(self, data):
        #리스트의 끝에 새로운 노드를 추가한다.
        new_node = Node(data)
        if self.head is None:
            self.head = new_node
            return
        last_node = self.head
        while last_node.next:
            last_node = last_node.next
        last_node.next = new_node

    def print_list(self):
        #리스트의 모든 요소를 출력한다.
        cur_node = self.head
        while cur_node:
            print(cur_node.data, end=" -> ")
            cur_node = cur_node.next
        print("None")

# 연결 리스트 사용 예시
llist = LinkedList()
llist.append(1)
llist.append(2)
llist.append(3)

llist.print_list()
# 출력: 1 -> 2 -> 3 -> None

 

C로 Linked List 구현 코드

 

Data-Structure-C-Language-Exercises/yunsejin at master · yunsejin/Data-Structure-C-Language-Exercises

NANYANG UNIVERSITY Data-Structure-Exercises. Contribute to yunsejin/Data-Structure-C-Language-Exercises development by creating an account on GitHub.

github.com

 

728x90

'CS > 자료구조' 카테고리의 다른 글

B-tree, B+tree, Trie, Minimum Spanning Tree  (1) 2024.04.01
Graph, Adjacency Matrix, Adjacency List  (0) 2024.03.31
Hash Table (collision, chaining, rehashing)  (1) 2024.03.31
Red-Black Tree 구현  (0) 2024.03.22
이진탐색트리, AVL트리  (0) 2024.03.21
728x90

페이지 교체 정책(Page Replacement Policy)은 가상 메모리 시스템에서 메모리가 꽉 찼을 때, 어떤 페이지를 제거하고 새 페이지를 물리 메모리에 적재할지 결정하는 방법이다. 이 정책은 시스템의 성능과 효율성에 중요한 영향을 미친다. 여러 페이지 교체 알고리즘이 있으며, 각기 다른 상황에 적합하다.

 

FIFO(Fist-In, Fist-Out)가장 오래 전에 메모리에 적재된 페이지를 먼저 교체한다. 구현이 간단하지만, 자주 사용되는 페이지를 교체할 위험이 있다.

page_references = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5]  # 주어진 페이지 참조 시퀀스
frame_size = 3  # 프레임 크기
frames = []  # 현재 프레임 상태
page_faults = 0  # 페이지 폴트 수
fifo_table = []  # FIFO 표를 기록할 리스트

for page in page_references:
    # 페이지가 현재 프레임 내에 없으면 페이지 폴트 처리
    if page not in frames:
        page_faults += 1
        # 프레임이 가득 찼으면 가장 오래된 페이지 제거
        if len(frames) == frame_size:
            frames.pop(0)
        frames.append(page)
    # FIFO 표에 현재 프레임 상태 기록
    fifo_table.append(frames.copy())

# FIFO 표 결과와 페이지 폴트 수 반환
print(fifo_table, '페이지 폴트 수 :', page_faults)
 
#output : [[1], [1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 1], [4, 1, 2], [1, 2, 5], [1, 2, 5], [1, 2, 5], [2, 5, 3], [5, 3, 4], [5, 3, 4]] 페이지 폴트 수 : 9

 

LRU(Least Recently Used)가장 오랫동안 사용되지 않은 페이지를 교체한다. 최근 사용 패턴을 기반으로 효율적인 페이지 교체를 제공하지만, 구현이 복잡하다.

 

페이지 참조 시퀀스가 123412512345일 때:

1 1    
2 1 2  
3 1 2 3
4 2 3 4
1 3 4 1
2 4 1 2
5 1 2 5
1 2 5 1
2 5 1 2
3 1 2 3
4 2 3 4
5 3 4 5

 

LFU(Least Frequently Used)가장 적게 사용된 페이지를 교체한다. 사용 빈도를 기반으로 하지만, 최근 사용 패턴을 반영하지 못하는 단점이 있다.

 

NRU(Not Recently Used)는 페이지가 최근에 사용되었는지 여부를 기준으로 교체 대상을 결정한다. 각 페이지마다 참조 비트(reference bit)를 두어, 페이지가 접근되면 이 비트를 설정한다. 주기적으로 운영 체제는 이 비트를 클리어하여, 어떤 페이지가 최근에 사용되지 않았는지를 판단한다. 교체 시점에, 참조 비트가 클리어된 페이지(즉, 최근에 사용되지 않은 페이지) 중에서 교체 대상을 선택한다. NRU는 구현이 간단하고 효율적이지만, 최적의 페이지 교체를 보장하지는 않는다.

 

NFU(Not Frequently Used)각 페이지가 시스템 내에서 얼마나 자주 사용되었는지를 기준으로 교체 대상을 결정한다. 페이지마다 카운터를 두고, 시간이 지날 때마다 페이지가 참조되면 해당 페이지의 카운터를 증가시킨다. 교체가 필요할 때, 가장 카운터 값이 낮은 페이지(즉, 가장 자주 사용되지 않은 페이지)를 교체 대상으로 선택한다. NFU는 사용 빈도를 기반으로 하므로 시간이 지남에 따라 정확도가 높아지지만, 오랜 시간 동안 시스템에 머물렀던 페이지가 불이익을 받을 수 있다는 단점이 있다.

최적 페이지 교체 알고리즘(Optimal Page Replacement)미래에 가장 오랫동안 사용되지 않을 페이지를 교체한다. 이상적인 알고리즘이지만, 실제 시스템에서는 미래의 접근 패턴을 예측할 수 없어 구현이 불가능하다.

 

Clock(Circular Queue)FIFO와 유사하되, 각 페이지에 사용 여부를 나타내는 플래그를 사용한다. '시계' 방식으로 순환하며, 사용되지 않은 페이지(플래그가 0인 페이지)를 찾아 교체한다. LRU의 근사 알고리즘으로, 구현이 비교적 간단하면서도 효과적이다.

 

페이지 참조 시퀀스가 123412512345일 때:

1 1    
2 1 2  
3 1 2 3
4 4 2 3
1 4 1 3
2 4 1 2
5 5 1 2
1 5 1 2 아니오
2 5 1 2 아니오
3 5 3 2
4 5 3 4
5 5 3 4 아니오

 

벨라디의 역설(Belady's Anomaly)은 페이지 교체 알고리즘에서 관찰될 수 있는 현상으로, 가상 메모리 시스템에서 물리 메모리의 프레임 수를 증가시킬 때, 예상과 달리 페이지 폴트의 수가 증가하는 상황을 말한다. 이 역설은 특히 FIFO(First-In, First-Out) 페이지 교체 알고리즘에서 두드러지게 나타난다.

 

일반적으로, 물리 메모리의 크기가 커지면 더 많은 페이지를 저장할 수 있게 되어 페이지 폴트의 수가 감소할 것으로 예상된다. 그러나 벨라디의 역설에서는 메모리 프레임의 수를 증가시키는 것이 오히려 페이지 폴트 수를 증가시킬 수 있음을 보여준다. 이 현상은 FIFO 알고리즘의 특성상, 메모리에 더 많은 페이지를 보관할 수 있게 되면서, 최근에 사용된 페이지가 아닌 오래된 페이지를 메모리에 유지하는 경향 때문에 발생한다.

 

벨라디의 역설은 모든 페이지 교체 알고리즘에서 발생하는 것은 아니다. LRU(Least Recently Used)나 OPT(Optimal Page Replacement) 같은 일부 알고리즘은 이러한 역설적인 상황을 겪지 않는다. 이 알고리즘들은 각각 최근에 가장 적게 사용된 페이지, 또는 미래에 가장 오랫동안 사용되지 않을 페이지를 교체함으로써 메모리 프레임의 수가 증가함에 따라 페이지 폴트 수가 감소하거나 최적화되는 경향을 보인다.

 

벨라디의 역설은 페이지 교체 전략을 설계하고 선택할 때 주의해야 할 중요한 이론적 관점을 제공한다. 이 역설을 통해 알 수 있는 교훈은 물리 메모리의 크기를 증가시킨다고 해서 항상 시스템의 성능이 향상되는 것은 아니라는 점이다. 따라서, 효과적인 메모리 관리 전략을 위해서는 적절한 페이지 교체 알고리즘의 선택이 중요하다.

 

교체 알고리즘이 FIFO이고 페이지 참조 시퀀스가 123412512345일 때:

 

page_references = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5]  # 주어진 페이지 참조 시퀀스
frame_size = 3  # 프레임 크기
frames = []  # 현재 프레임 상태
page_faults = 0  # 페이지 폴트 수
fifo_table = []  # FIFO 표를 기록할 리스트

for page in page_references:
    # 페이지가 현재 프레임 내에 없으면 페이지 폴트 처리
    if page not in frames:
        page_faults += 1
        # 프레임이 가득 찼으면 가장 오래된 페이지 제거
        if len(frames) == frame_size:
            frames.pop(0)
        frames.append(page)
    # FIFO 표에 현재 프레임 상태 기록
    fifo_table.append(frames.copy())

# FIFO 표 결과와 페이지 폴트 수 반환
print(fifo_table, '페이지 폴트 수 :', page_faults)

 

output :

[[1], [1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 1], [4, 1, 2], [1, 2, 5], [1, 2, 5], [1, 2, 5], [2, 5, 3], [5, 3, 4], [5, 3, 4]] 페이지 폴트 수 : 9

 

프레임 크기가 3일 때, 페이지 폴트 수가 9개가 나온다. 

page_references = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5]  # 주어진 페이지 참조 시퀀스
frame_size = 4  # 프레임 크기
frames = []  # 현재 프레임 상태
page_faults = 0  # 페이지 폴트 수
fifo_table = []  # FIFO 표를 기록할 리스트

for page in page_references:
    # 페이지가 현재 프레임 내에 없으면 페이지 폴트 처리
    if page not in frames:
        page_faults += 1
        # 프레임이 가득 찼으면 가장 오래된 페이지 제거
        if len(frames) == frame_size:
            frames.pop(0)
        frames.append(page)
    # FIFO 표에 현재 프레임 상태 기록
    fifo_table.append(frames.copy())

# FIFO 표 결과와 페이지 폴트 수 반환
print(fifo_table, '페이지 폴트 수 :', page_faults)

 

output : [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 1], [4, 5, 1, 2], [5, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]] 페이지 폴트 수 : 10

 

프레임 크기를 4로 늘렸는데도 불구하고, 페이지 폴트 수가 10으로 더 늘어난 모습이다.

728x90
728x90

Demand-zero 페이지는 컴퓨터 시스템에서 가상 메모리 관리 기법 중 하나이다. 프로세스가 실제로 페이지를 참조하기 전까지는 해당 페이지를 물리적 메모리에 할당하지 않는다. 대신, 프로세스가 특정 페이지에 접근을 시도할 때 해당 페이지가 물리적 메모리에 없으면 페이지 폴트(page fault)가 발생한다. 이때 운영 체제는 요청된 페이지를 디스크에서 찾아 물리적 메모리에 로드하고, 프로세스는 그 페이지를 사용할 수 있게 된다.

 

Demand-zero 페이지의 주요 장점은 메모리 사용의 효율성을 높일 수 있다는 것이다. 실제로 필요할 때까지 메모리를 할당하지 않기 때문에, 미사용 페이지에 대한 메모리 낭비를 줄일 수 있다. 이 방식은 특히 메모리 자원이 제한적인 시스템이나 많은 수의 프로세스를 동시에 실행해야 하는 시스템에서 유용하다. 다만, 페이지 폴트가 발생했을 때 디스크에서 메모리로 페이지를 로드하는 과정이 필요하기 때문에, 페이지 폴트 처리 시간이 시스템의 성능에 영향을 줄 수 있다. 따라서, 시스템 설계 시 메모리 관리 전략을 잘 선택하고, 페이지 폴트 발생 빈도를 최소화하는 최적화 작업이 중요하다.

 

파일-백드 페이지(file-backed page)는 가상 메모리 시스템에서 사용되는 개념으로, 가상 주소 공간의 페이지가 실제 파일의 내용으로 백업되는 경우를 말한다. 이러한 페이지는 일반적으로 실행 가능 파일이나 라이브러리 파일 같은 영구 저장소에 있는 데이터로 채워진다. 파일-백드 메커니즘은 특히 메모리 매핑 파일(memory-mapped file) 기술과 밀접하게 연관되어 있다.

 

파일-백드 페이지는 디스크 상의 파일에 대응되므로, 프로세스 간에 공유될 수 있으며, 시스템이 재부팅되어도 데이터가 유지된다. 이 기법을 사용하면, 파일의 일부를 메모리에 로드하고 접근하는 과정이 최적화될 수 있어, 파일 I/O 작업이 줄어들고 성능이 향상될 수 있다.

 

프로세스가 파일-백드 페이지에 접근하려 할 때 해당 페이지가 메모리에 없으면 페이지 폴트가 발생한다. 운영 체제는 해당 파일의 적절한 부분을 메모리로 로드하여 페이지 폴트를 해결한다. 메모리 매핑된 파일을 사용하여, 다른 프로세스들이 파일의 내용을 통해 데이터를 공유하고 통신할 수 있다.

 

프로그램이 파일을 메모리에 매핑할 때, 해당 파일의 내용은 파일-백드 가상 페이지로 관리된다. 이를 통해 파일 내용에 대한 읽기와 쓰기 작업을 메모리 접근처럼 수행할 수 있다. 운영 체제는 공유 라이브러리나 실행 가능 파일을 프로세스의 가상 주소 공간에 매핑하여, 필요한 코드와 데이터를 메모리로 로드한다. 이 과정에서 파일-백드 페이지가 사용된다.

 

파일-백드 페이지는 필요한 경우에만 메모리로 로드되기 때문에, 스왑 공간의 사용을 줄이고 시스템의 전반적인 성능을 개선할 수 있다.

 

Anonymous 페이지는 가상 메모리 시스템에서 사용되는 용어로, 파일이나 다른 백업 스토리지에 직접 매핑되지 않는 메모리 페이지를 의미한다. 이러한 페이지는 주로 프로세스의 힙(heap)이나 스택(stack)과 같이 실행 중에 동적으로 할당되는 데이터를 저장하는 데 사용된다. 예를 들어, 프로그램에서 동적 할당을 통해 메모리를 요청하면 운영 체제는 이러한 요청을 충족시키기 위해 anonymous 페이지를 할당할 수 있다.

Anonymous 페이지는 디스크 상의 파일에 매핑되지 않기 때문에, 프로그램의 실행 파일이나 데이터 파일과는 직접적인 관련이 없다. 대신, 이 페이지들은 실행 시간에 프로세스의 메모리 요구에 의해 동적으로 생성된다.

 

대부분의 운영 체제는 안전성을 위해 anonymous 페이지를 할당할 때 'zero-fill' 정책을 사용하여 페이지 내용을 0으로 초기화한다. 이는 새로 할당된 메모리가 이전에 사용되었던 데이터를 포함하지 않도록 보장한다.

 

메모리가 부족할 때, 운영 체제는 스왑(swap) 영역이나 페이지 파일을 사용하여 anonymous 페이지를 디스크로 스왑 아웃할 수 있다. 나중에 해당 페이지에 다시 접근할 필요가 있을 때, 운영 체제는 디스크에서 해당 페이지를 다시 메모리로 스왑 인한다.

 

프로그램에서 malloc, calloc, new 등의 함수를 사용하여 메모리를 동적으로 할당할 때, 이러한 할당은 대개 anonymous 페이지를 통해 이루어진다.

 

Lazy loading은 컴퓨터 프로그래밍에서 자주 사용되는 최적화 기법 중 하나로, 객체, 데이터, 또는 다른 리소스를 실제로 필요로 하는 순간까지 로딩을 지연시키는 방식이다. 이 기법은 주로 웹 개발, 소프트웨어 개발, 그리고 운영 체제의 메모리 관리에서 사용된다. Lazy loading의 주 목적은 시스템의 성능을 향상시키고, 리소스 사용을 최적화하여 사용자 경험을 개선하는 것이다.

 

웹 개발에서 lazy loading은 특히 이미지, 스크립트, 비디오 등의 리소스를 페이지 로드 시점이 아닌, 사용자가 해당 리소스를 필요로 하는 순간에 로드하는 데 사용된다. 예를 들어, 사용자가 스크롤을 내려 이미지가 보여야 할 위치에 도달했을 때 해당 이미지를 로드한다. 이 방식은 초기 페이지 로딩 시간을 줄이고, 네트워크 트래픽을 줄이며, 서버의 부하를 경감하는 데 도움을 준다.

 

소프트웨어 개발에서는 객체나 모듈을 실제로 사용할 때까지 로딩을 지연시키는 방식으로 리소스 사용을 최적화한다. 예를 들어, 특정 기능이 사용자에 의해 요청될 때만 해당 기능을 구현하는 클래스의 인스턴스를 생성하거나, 모듈을 로드한다. 이는 메모리 사용을 줄이고, 애플리케이션의 시작 시간을 단축시키는 효과를 가진다.

 

운영 체제의 메모리 관리에서도 lazy loading 기법이 사용될 수 있다. 예를 들어, 프로그램이 시작될 때 모든 데이터와 코드를 메모리에 로드하는 대신, 실제로 필요한 부분만 메모리에 로드하고, 나머지는 필요해질 때까지 로딩을 지연시킨다. 이는 효율적인 메모리 사용을 가능하게 하며, 전체 시스템의 성능을 향상시킬 수 있다.

 

 

728x90

'CS > 운영체제' 카테고리의 다른 글

Pint OS_Project 2 구현 - 1(argument passing)  (1) 2024.03.29
Page Replacement Policy  (0) 2024.03.27
하이퍼바이저, 애뮬레이션, QEMU  (0) 2024.03.26
Pint OS_Project 1 구현  (1) 2024.03.26
Context Switching, Semaphore와 Mutex  (2) 2024.03.25
728x90

페이지 테이블(Page table)은 가상 메모리 시스템에서 중요한 구성 요소로, 가상 주소를 실제 물리 메모리 주소로 매핑하는 정보를 담고 있다. 컴퓨터 시스템에서 프로그램은 가상 주소 공간에서 실행되며, 이 가상 주소 공간은 실제 물리 메모리에 매핑된다. 페이지 테이블은 이러한 매핑 정보를 제공하며, 운영 체제의 메모리 관리 부분에 의해 관리된다.

  • 가상 페이지 번호(Virtual Page Number, VPN): 가상 주소를 페이지로 나누었을 때, 해당 페이지의 번호.
  • 물리 프레임 번호(Physical Frame Number, PFN): 실제 물리 메모리에서 해당 페이지가 위치하는 프레임의 번호.
  • 접근 권한: 페이지에 대한 읽기, 쓰기, 실행 권한을 정의한다.
  • 유효 비트(Valid Bit): 해당 페이지가 물리 메모리에 존재하는지 여부를 나타낸다. 유효하지 않은 경우 페이지 폴트가 발생한다.
  • 수정 비트(Modified Bit or Dirty Bit): 해당 페이지가 수정되었는지 여부를 나타내며, 페이지 교체 정책에서 중요하게 사용된다.

CPU가 발생시킨 가상 주소는 페이지 번호와 오프셋으로 구분된다. 페이지 테이블은 이 페이지 번호를 사용하여 해당 페이지가 물리 메모리의 어느 프레임에 매핑되어 있는지 확인한다. 변환된 물리 주소(물리 프레임 번호 + 오프셋)를 사용하여 실제 메모리에 접근한다. 요청된 페이지가 메모리에 없을 경우(유효 비트가 설정되지 않음), 페이지 폴트가 발생하고, 운영 체제는 해당 페이지를 디스크에서 물리 메모리로 로드한다.

 

대규모 가상 메모리를 관리하기 위해, 페이지 테이블은 종종 계층적 구조로 구성되거나, TLB(Translation Lookaside Buffer)와 같은 캐시 메커니즘을 사용하여 주소 변환 과정을 최적화한다. 이러한 최적화는 메모리 접근 시간을 줄이고, 시스템의 전반적인 성능을 향상시킨다.

 

매핑(Mapping)은 소프트웨어나 하드웨어에서 사용하는 가상 주소와 물리적 주소 사이의 관계를 설정하는 과정입니다. 가상 메모리 시스템에서, 프로그램이 사용하는 가상 주소를 실제 메모리(RAM)의 물리적 주소로 변환하는 것이 대표적인 예이다.

 

페이징(Paging)가장 흔히 메모리 관리에서 사용되는 페이징은 가상 메모리 시스템에서 실제 메모리(RAM)를 더 효율적으로 사용하기 위해 고안된 기법이다. 이는 메모리를 고정된 크기의 블록(페이지)으로 나누고, 프로그램이 사용하는 가상 주소 공간을 이 페이지에 매핑하여 관리한다. 페이징을 통해, 시스템은 필요한 페이지만 메모리에 적재하고, 나머지는 디스크에 저장함으로써 메모리 사용을 최적화할 수 있다.

 

페이지 폴트(page fault)는 가상 메모리 시스템에서 발생하는 현상이다. 프로세스가 접근하려는 페이지가 물리 메모리(RAM)에 없을 때 발생한다. 이는 해당 페이지가 디스크에 위치하거나, 아직 메모리에 할당되지 않은 상태일 수 있다. 페이지 폴트가 발생하면, 운영 체제는 필요한 페이지를 디스크에서 찾아 물리 메모리로 로드한다. 이 과정을 통해 프로그램은 계속 실행될 수 있다.

 

페이지 폴트는 모든 가상 메모리 시스템에서 자연스러운 현상이지만, 빈번하게 발생하면 시스템의 성능에 부정적인 영향을 미칠 수 있다. 페이지 폴트가 많이 발생한다는 것은 프로세스가 필요로 하는 데이터나 코드가 물리 메모리에 적절히 위치하지 않고, 디스크 접근이 잦다는 것을 의미한다. 이러한 상황을 최소화하기 위해, 운영 체제는 페이지 교체 알고리즘을 사용하여 어떤 페이지를 메모리에 유지하고 어떤 페이지를 디스크로 내보낼지 결정한다.

 

세그멘테이션 폴트(segmentation fault)는 프로그램이 자신에게 허용되지 않은 메모리 영역에 접근하려고 할 때 발생하는 오류다. 이는 프로그램의 안정성과 보안을 위협할 수 있으며, 일반적으로 운영 체제에 의해 프로그램이 비정상적으로 종료된다.

 

세그멘테이션 폴트는 기화되지 않은 포인터나 이미 해제된 메모리를 참조할 때, 배열의 범위를 넘어서는 위치에 접근하려고 할 때, 혹은 읽기 전용 메모리에 쓰기 시도나 실행할 수 없는 메모리 영역에서 코드를 실행하려고 할 때 발생한다.

 

스와핑(swapping)은 메모리 관리 기법 중 하나이다. 이 기법은 시스템의 가용 메모리가 부족할 때, 메모리에 있는 데이터나 프로세스를 하드 디스크와 같은 보조 저장 장치로 옮기는 과정을 말한다. 스와핑을 통해, 시스템은 메모리를 더 효율적으로 사용할 수 있게 된다.

 

운영 체제는 메모리가 부족하다고 판단할 때, 현재 메모리에서 사용 빈도가 낮은 데이터나 프로세스를 선정한다. 선정된 데이터나 프로세스는 보조 저장 장치로 이동된다. 이 과정을 스와핑 아웃(swap out)이라 한다. 필요할 때, 보조 저장 장치에 저장된 데이터나 프로세스를 다시 메모리로 가져온다. 이 과정을 스와핑 인(swap in)이라 한다.

 

스와핑은 시스템의 전반적인 성능에 영향을 줄 수 있다. 보조 저장 장치는 메모리에 비해 접근 속도가 느리기 때문에, 스와핑 작업이 빈번하게 발생하면 시스템의 반응 시간이 길어질 수 있다. 이를 스와핑 스트레스라고 하며, 시스템의 성능 저하의 원인이 될 수 있다.

 

스레싱(thrashing)은 운영체제에서 멀티태스킹 환경에서 발생할 수 있는 문제로, 시스템의 자원을 대부분 페이지 교체나 스왑 아웃, 스왑 인 같은 메모리 관리 작업에 사용하게 되어 실제 유용한 작업 처리량이 급격히 감소하는 현상을 말한다. 이는 시스템에 너무 많은 프로그램이 동시에 실행되어 메모리 요청이 메모리 용량을 초과할 때 주로 발생한다. 결과적으로 CPU는 대부분의 시간을 유효한 데이터를 찾아 메모리와 스왑 공간(swap space) 사이를 이동하는 데 소비하게 되며, 이로 인해 시스템의 전반적인 성능이 크게 저하되는 것을 말한다.

728x90
728x90

캐시(Cache)는 컴퓨터 아키텍처에서 데이터나 명령어를 미리 복사해 놓는 임시 저장 공간을 말한다. 이 저장 공간은 CPU 내부나 그와 매우 가까운 곳에 위치하여, CPU가 주기억장치(RAM)이나 다른 보다 느린 저장 장치보다 빠르게 데이터에 접근할 수 있도록 돕는다. 캐시의 주된 목적은 시스템의 성능을 향상시키는 것이며, 이는 데이터 접근 시간을 줄임으로써 가능해진다.

 

캐시는 매우 빠른 속도로 데이터를 제공할 수 있는데, 이는 CPU가 필요로 하는 데이터를 캐시에서 직접 가져올 수 있기 때문이다. 캐시는 주기억장치에 비해 상대적으로 작은 용량을 가지고 있다. 이는 높은 속도의 메모리가 비싸기 때문이다. 캐시는 CPU에 매우 가까이 위치하여 빠른 데이터 전송을 가능하게 한다.

 

현대의 컴퓨터 시스템에서는 보통 여러 레벨의 캐시(L1, L2, L3)를 사용한다. 이 레벨은 캐시의 속도와 크기, CPU로부터의 거리에 따라 다르다. 캐시는 자주 사용되는 데이터나 명령어를 저장함으로써, CPU가 같은 데이터나 명령어에 대해 반복적으로 느린 주기억장치에 접근하는 것을 방지한다. 이러한 방식으로 시스템의 전반적인 성능을 향상시킨다.

 

MMU(Memory Management Unit)CPU와 메모리 사이에 위치하여 물리적 및 가상 메모리 주소 간의 변환을 담당한다. 가상 메모리 주소를 물리적 메모리 주소로 변환한다. 이는 프로그램이 물리적 메모리 위치를 직접 알 필요 없이 실행될 수 있게 한다.

 

다른 프로그램의 메모리 영역에 무단으로 접근하는 것을 방지하여 시스템의 안정성과 보안을 유지한다. 가장 효율적인 메모리 접근을 위해 캐시의 사용을 관리한다.

 

TLB (Translation Lookaside Buffer)MMU의 일부로, 가장 최근에 사용된 주소 변환의 결과를 저장하는 작은 캐시다. 가상 주소를 물리적 주소로 변환하는 데 필요한 시간을 줄이기 위해, 자주 사용되는 변환 정보를 저장한다. TLB를 통해 메모리 접근 시간을 줄이고, 시스템의 전반적인 성능을 향상시킨다. TLB가 변환 정보를 가지고 있지 않은 경우에만 MMU가 전체 메모리 변환 과정을 수행한다.

 

 

TLB의 효율성은 'TLB hit rate'라는 메트릭으로 측정되는데, 이는 전체 메모리 접근 중 TLB가 변환 정보를 이미 가지고 있어 바로 제공할 수 있는 비율을 의미한다. TLB hit rate가 높을수록 시스템 성능이 더 좋다. MMU와 TLB는 함께 작동하여 메모리 접근 시간을 최적화하고, 프로그램이 효율적으로 실행되도록 한다.

 

가상 메모리는 컴퓨터 시스템에서 물리적 메모리(주기억장치) 크기를 초과하는 프로그램을 실행할 수 있게 해주는 메모리 관리 기법이다. 이 기술은 물리적 메모리와 보조기억장치(예: 하드 드라이브, SSD)의 일부를 결합해, 마치 더 큰 단일 메모리 공간이 있는 것처럼 운영 체제와 응용 프로그램에게 제공한다.

 

가상 메모리는 페이지(데이터의 작은 블록)를 기반으로 작동한다. 필요한 페이지는 물리적 메모리로 로드되고, 필요하지 않은 페이지는 보조기억장치로 이동된다. 프로그램은 가상 주소를 사용해 메모리에 접근한다. CPU 내부의 메모리 관리 장치(MMU)는 이 가상 주소를 물리적 주소로 변환한다.

 

프로그램은 실제 물리적 메모리보다 더 큰 메모리 공간을 사용할 수 있어, 복잡한 작업과 큰 데이터 집합을 처리할 수 있다. 각 프로그램은 독립적인 가상 메모리 공간을 가지므로, 한 프로그램의 오류가 다른 프로그램이나 운영 체제에 영향을 미치지 않는다.

 

가상 주소를 물리적 주소로 변환하는 과정과 페이지 교체 과정은 시간이 소요되며, 이는 시스템의 전반적인 성능 저하로 이어질 수 있다.

728x90
728x90

레지스터와 메모리는 컴퓨터 시스템에서 데이터를 저장하고 처리하는 데 필수적인 두 가지 리소스이며 휘발성이라는 공통점을 가진다. 이 두 리소스들은 용량, 용도, 속도 등 여러 면에서 차이점을 가진다.

메모리(RAM)는 레지스터와 달리 CPU의 외부에 위치하며, 레지스터에 비해 접근속도가 상대적으로 느리다. 메모리는 데이터와 프로그램 코드를 저장하는 데 사용되며 CPU와 시스템 버스를 통해 이루어진다. 그리고 물리 메모리 초과를 방지하기 위해 가상 메모리를 사용한다.

레지스터는 cache보다 CPU 코어에 근접해 있다. CPU코어 - 레지스터 - 캐시 - 메모리 순으로 위치 하여 있으며 가까울 수록 임시 저장 및 처리를 더 빠르게 하고 있는 것이다.

레지스터는 계산 중간 결과나 계산에 자주 사용되는 임시 값들을 저장한다. 운영체제는 context switching시 레지스터의 상태를 저장하고 복원하는 작업을 한다.

레지스터는 PC와 스택 포인터 등 특수목적 레지스터를 포함하여 상태 레지스터,작업 레지스터, 범용 레지스터, 명령어 레지스터를 일괄하여 말한다. 이 중 범용 레지스터는 프로그램의 실행 동안 가장 일반적이고 자주 사용되는 레지스터이다.

PC(Program Counter)는 CPU가 현재 실행하고 있는 명령어의 메모리 주소를 저장하는 레지스터이다. CPU가 다음에 실행할 명령어의 위치를 알려주며, 일반적으로 명령어가 실행될 때 마다 자동으로 증가하여 다음 명령어의 주소를 가리킨다. 프로그램의 실행 흐름을 변경하는 분기나 점프 명령어가 실행될 때, 프로그램 카운터는 새로운 주소로 업데이트되어 CPU가 새로운 위치에서 명령어를 계속해서 실행할 수 있게 한다.

명령어 레지스터는 현재 CPU가 실행중인 명령어를 저장하여 CPU가 현재 어떤 작업을 하고 있는지 알고 싶을 때 사용된다.

상태 레지스터는 보통 몇 비트로 이루어져 있으며, 각 비트는 특정 상태나 조건 (예 : 캐리 아웃, 오버플로우, 언더플로우, 제로, 음수 등)을 나타낸다. 이런 단일 연산 결과에 대한 정보를 제공하므로 상태 레지스터의 크기는 상대적으로 작다.

작업 레지스터는 특정 아키텍처에선 범용 레지스터라고 불리며, 일시적으로 데이터를 처리하기 위한 임시 저장소이다.

범용 레지스터는 데이터 연산, 임시 데이터 저장, 메모리 주소의 저장 등 다양한 목적으로 사용된다. 이들은 프로그램의 데이터를 처리하고, 메모리와의 데이터 교환을 위한 주요 저장소 역할을 한다.

 

디스크는 컴퓨터에서 데이터를 저장하는 장치를 말한다. 크게 두 종류로 나뉜다

 

HDD(Hard Disk Drive) 자기 저장 방식을 사용해 데이터를 저장하는 장치로, 내부에는 회전하는 디스크(플래터)가 있으며, 이 위에 데이터를 읽고 쓰는 헤드가 움직이며 작동한다. 대용량 데이터를 저장할 수 있고, 가격이 상대적으로 저렴하지만, 움직이는 부품이 있어 충격에 약하고, SSD에 비해 속도가 느리다.

 

SSD(Solid State Drive)반도체 칩을 사용해 데이터를 저장하며, 움직이는 부품이 없어 충격에 강하고 빠른 데이터 읽기와 쓰기 속도를 제공한다. HDD에 비해 가격이 높지만, 성능과 내구성 면에서 우수하다.

 

M.2SSD의 한 형태로, 소형화된 카드 형태의 설계로 제공된다. PCIe 또는 SATA 인터페이스를 통해 데이터 전송을 지원하며, 특히 NVMe 프로토콜을 사용하는 PCIe M.2 SSD는 매우 높은 속도를 제공한다. 노트북이나 소형 PC에서 공간 절약과 고속 데이터 전송이 필요할 때 사용된다.

 

이들은 다 보조기억 장치로 분류된다. 보조기억장치는 컴퓨터에서 데이터를 영구적으로 저장하는 역할을 하며, 주기억장치(RAM)과 달리 전원이 꺼져도 정보를 유지한다.(비휘발성) 이러한 장치들은 대용량 데이터 저장, 시스템 부팅, 소스트웨어 실행 등 다양한 목적으로 사용된다.

 

ROM(Read-Only Memory)은 읽기 전용 메모리로, 주로 컴퓨터나 전자기기의 펌웨어나 시스템 부트로더 등이 저장되는 곳이다. 전원이 꺼져도 데이터를 유지한다는 점에서 보조기억장치의 특성을 가지고 있지만, 일반적으로 사용자가 데이터를 저장하고 변경할 수 있는 HDD, SSD와 같은 저장 장치와는 구분된다. ROM은 보통 한 번 프로그래밍되면 변경이 어렵거나 불가능한 데이터를 저장하는 데 사용되므로, 전통적인 의미에서의 보조기억장치와는 조금 다른 역할을 한다. 그럼에도 불구하고 넓은 의미에서는 데이터를 저장하는 장치로서 보조기억장치의 범주에 포함시킬 수 있다.

 

728x90
728x90

하이퍼바이저는 가상화 기술을 구현하는 소프트웨어다. 물리적 하드웨어 위에서 여러 운영체제를 동시에 실행할 수 있게 해준다. 하이퍼 바이저는 크게 두 가지 유형이 있다.

 

https://aws.amazon.com/ko/compare/the-difference-between-type-1-and-type-2-hypervisors/

 

1형 및 2형 하이퍼바이저 비교 - 하이퍼바이저 유형 간의 차이점 - AWS

Amazon Web Services(AWS)는 네트워킹, 컴퓨팅, 스토리지 및 데이터베이스를 비롯한 광범위한 인프라에서 가상화 솔루션을 제공합니다. 클라우드는 가상화를 기반으로 하며 모든 사용자와 조직의 요구

aws.amazon.com

 

타입 1 하이퍼바이저는 하드웨어에 직접 설치되어 운영체제 없이 작동하며, 고성능 가상화 환경을 제공한다. 기본 물리적 호스트 머신 하드웨어에서 실행되며, 대규모, 리소스 집약적 또는 고정용 워크로드에 적합하다. 시스템 관리자 이는 수준의 지식이 필요하다. 예시로 VMware ESXi, Microsoft Hyper-V, KVM이 있다.

 

타입 2 하이퍼바이저는 기존 운영체제 위에 설치되어, 일반적인 애플리케이션처럼 실행된다. 가상머신을 만들고 관리하는 데 사용된다. 기본 운영 체제에서 실행되고 데스크톱 및 개발 환경에 적합하다. 예시로 Oracle VM VirtualBox, VMware Workstation, Microsoft Virtual PC가 있다.

 

애뮬레이션은 하나의 시스템이 다른 시스템의 기능을 모방하게 하는 기술이다. 이는 소프트웨어, 하드웨어, 또는 둘의 조합을 통해 이루어질 수 있다. 예를 들어, 특정 비디오 게임 콘솔용으로 개발된 게임을 PC에서 실행할 수 있게 해주는 소프트웨어 애뮬레이터가 이에 해당한다. 애뮬레이션은 다양한 용도로 사용되며, 오래된 시스템의 소프트웨어를 새로운 하드웨어에서 실행하거나, 소프트웨어 개발 과정에서 다른 플랫폼을 대상으로 하는 애플리케이션의 테스트에 사용되기도 한다.

 

하이퍼바이저(가상화)는 물리적 하드웨어 위에 직접 구축되며, 하드웨어 자원을 여러 가상 머신 간에 분할한다. 이는 주로 성능 저하 없이 효율적인 자원 분배와 관리를 목표로 한다. 하이퍼바이저는 주로 같은 아키텍처 내에서의 가상화에 초점을 맞춘다.

 

따라서, 하이퍼바이저는 가상 머신을 생성하고 관리하는 역할을 하며, 가상 머신은 이러한 환경에서 실행되는 독립적인 컴퓨터 시스템이라 할 수 있다.

 

애뮬레이터는 하드웨어, 소프트웨어, 또는 둘의 조합을 모방하는 소프트웨어로, 한 플랫폼에서 다른 플랫폼의 시스템을 실행할 수 있게 한다. 예를 들어, 한 종류의 프로세서 아키텍처를 가진 시스템에서 다른 종류의 프로세서 아키텍처를 사용하는 시스템을 모방할 수 있다. 애뮬레이션은 주로 호환성이나 구형 시스템의 소프트웨어를 새 시스템에서 실행하기 위해 사된다.

 

QEMU는 오픈소스 가상화 소프트웨어로, 다양한 하드웨어 아키텍처에서 가상 머신을 생성하고 실행할 수 있다. 하이퍼바이저 기능과 함께 애뮬레이션 기능도 제공하여, 하나의 플랫폼에서 다른 플랫폼의 운영 체제를 실행할 수 있게 해준다. 예를 들어, x86 프로세서를 사용하는 컴퓨터에서 ARM 프로세서를 사용하는 운영 체제를 실행할 수 있다. QEMU는 개발자들이 소프트웨어를 다양한 하드웨어 환경에서 테스트하거나, 가상화 환경을 구축할 때 널리 사용된다.

728x90

'CS > 운영체제' 카테고리의 다른 글

Page Replacement Policy  (0) 2024.03.27
Demand-zero page, Anonymous page, File-backed page  (0) 2024.03.27
Pint OS_Project 1 구현  (1) 2024.03.26
Context Switching, Semaphore와 Mutex  (2) 2024.03.25
CPU Scheduling, 4BSD, nice  (1) 2024.03.25

+ Recent posts