<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>codable</title>
    <link>https://codable.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 13 Jun 2026 11:16:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hin1209</managingEditor>
    <item>
      <title>[Python] 이분 매칭 알고리즘(Bipartite Matching)</title>
      <link>https://codable.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분 매칭 알고리즘은 유량 그래프의 특수한 형태이다. 유량 그래프는 다음 포스팅에서 다루기로 하고... 이분 매칭 알고리즘에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;1282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjRrDj/btsryqMmJeK/J9hqgczdCNkTkMbEJIoYd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjRrDj/btsryqMmJeK/J9hqgczdCNkTkMbEJIoYd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjRrDj/btsryqMmJeK/J9hqgczdCNkTkMbEJIoYd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjRrDj%2FbtsryqMmJeK%2FJ9hqgczdCNkTkMbEJIoYd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;617&quot; height=&quot;768&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;1282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분 매칭 알고리즘을 알아보기 전에, 이분 그래프에 대해 먼저 알고 넘어가야 한다. 이분 그래프란 정점을 두 개의 그룹으로 나누었을 때, 모든 간선의 양 끝 정점이 서로 다른 그룹에 속하는 형태의 그래프를 말한다. 위의 그림에서 보면 파란색 노드의 집합을 X, 빨간색 노드의 집합을 Y로 표현할 수 있고, 따라서 위의 그래프는 이분 그래프이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분 매칭 알고리즘은 위와 같은 이분 그래프에서 최대 매칭의 개수를 찾는 것과 같다. 매칭이란 간선 하나를 선택하는 것을 말하는데, 예를 들어 그림에서 A와 a를 연결하는 간선을 선택하면 (A, a)라는 매칭이 생기는 것이다. 각 노드들은 딱 한 번만 선택이 되어야 하므로, 이 간선을 선택했을 때 A 또는 a와 연결되어 있는 간선은 더 이상 선택이 불가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이분 매칭 알고리즘의 과정을 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;1260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BsuyO/btsrIagF7DE/CAk74w3mSVR5UNirusstm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BsuyO/btsrIagF7DE/CAk74w3mSVR5UNirusstm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BsuyO/btsrIagF7DE/CAk74w3mSVR5UNirusstm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBsuyO%2FbtsrIagF7DE%2FCAk74w3mSVR5UNirusstm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;749&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;1260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 위의 그림과 같이 (A, a) 매칭을 선택한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;1262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JG7oY/btsrwCzPZBh/LFuDRjEfSmyNoenrpeu171/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JG7oY/btsrwCzPZBh/LFuDRjEfSmyNoenrpeu171/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JG7oY/btsrwCzPZBh/LFuDRjEfSmyNoenrpeu171/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJG7oY%2FbtsrwCzPZBh%2FLFuDRjEfSmyNoenrpeu171%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;617&quot; height=&quot;736&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;1262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 (B, c) 매칭을 선택한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;1248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2euNb/btsrEqYoQHP/OI9nWc4dZ3BsCPHDOcKhIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2euNb/btsrEqYoQHP/OI9nWc4dZ3BsCPHDOcKhIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2euNb/btsrEqYoQHP/OI9nWc4dZ3BsCPHDOcKhIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2euNb%2FbtsrEqYoQHP%2FOI9nWc4dZ3BsCPHDOcKhIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;775&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;1248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 (C, a) 매칭을 선택하게 되는데, 이때 a는 이미 (A, a) 매칭에 속해있다. 이러한 상황에서는 (C, a) 매칭을 선택하고 원래 있던 (A, a) 매칭을 끊는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;1256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5VcKU/btsrEriGKHW/y8NuMthwndDRPgE1EuiU3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5VcKU/btsrEriGKHW/y8NuMthwndDRPgE1EuiU3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5VcKU/btsrEriGKHW/y8NuMthwndDRPgE1EuiU3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5VcKU%2FbtsrEriGKHW%2Fy8NuMthwndDRPgE1EuiU3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;784&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;1256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A는 매칭이 끊어졌기 때문에 아직 남아있는 간선을 추가로 탐색하게 되고, (A, b) 매칭이 선택된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;639&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCa4Sx/btsrB1L6fff/mrqjhaZf1OWy3Ly1hk9eH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCa4Sx/btsrB1L6fff/mrqjhaZf1OWy3Ly1hk9eH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCa4Sx/btsrB1L6fff/mrqjhaZf1OWy3Ly1hk9eH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCa4Sx%2FbtsrB1L6fff%2FmrqjhaZf1OWy3Ly1hk9eH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;639&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;639&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 (D, a) 매칭이 선택되고, 원래 있던 (C, a) 매칭은 끊기게 된다. 이후 (C, d) 매칭이 새로 선택된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 노드 E는 가능한 매칭이 (E, b) 뿐인데, 이 매칭이 선택될 경우 노드 A는 Y의 다른 노드와 매칭이 불가능하기 때문에 노드 E는 매칭이 될 수 없다. 따라서 가능한 최대 매칭은 위의 그림과 같이 네 가지가 선택될 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 과정을 코드로 나타내면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692440392770&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# matched_Y : Y 그룹의 노드와 매칭된 X 노드의 번호
# matched_X : X 그룹의 노드와 매칭된 Y 노드의 번호
def dfs(x):
    visited[x] = True
    for y in graph[x]:
        # y 노드와 매칭된 노드가 없는 경우, x 노드와 매칭
        if matched_Y[y] == 0:
            matched_Y[y] = x
            matched_X[x] = y
            return True
        # y 노드가 이미 매칭이 되어있는 경우, y 노드와 매칭되어 있는 노드가 다른 노드와 매칭이 가능한지 확인
        elif not visited[matched_Y[y]] and dfs(matched_Y[y]):
            # 다른 노드와 매칭이 가능한 경우, y 노드와 x 노드를 매칭
            matched_Y[y] = x
            matched_X[x] = y
            return True
    return False


cnt = 0
for i in range(1, n+1):
    if matched_X[i] == 0:
        visited = [False] * (n+1)
        if dfs(i):
            cnt += 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분 매칭 알고리즘 문제로 풀어볼 만한 것들은 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11375&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/11375&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692440587327&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;11375번: 열혈강호&quot; data-og-description=&quot;강호네 회사에는 직원이 N명이 있고, 해야할 일이 M개가 있다. 직원은 1번부터 N번까지 번호가 매겨져 있고, 일은 1번부터 M번까지 번호가 매겨져 있다. 각 직원은 한&amp;nbsp;개의 일만 할 수 있고, 각각&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/11375&quot; data-og-url=&quot;https://www.acmicpc.net/problem/11375&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ccOly4/hyTFmay4fu/u7pPPN8zZfJKRMIUbN6Kp0/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11375&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/11375&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ccOly4/hyTFmay4fu/u7pPPN8zZfJKRMIUbN6Kp0/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;11375번: 열혈강호&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;강호네 회사에는 직원이 N명이 있고, 해야할 일이 M개가 있다. 직원은 1번부터 N번까지 번호가 매겨져 있고, 일은 1번부터 M번까지 번호가 매겨져 있다. 각 직원은 한&amp;nbsp;개의 일만 할 수 있고, 각각&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 이분 매칭 알고리즘 문제다. 위의 그림에서 X 그룹과 Y 그룹을 각각 직원과 일로 구분해서 풀 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11376&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/11376&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692440646192&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;11376번: 열혈강호 2&quot; data-og-description=&quot;강호네 회사에는 직원이 N명이 있고, 해야할 일이 M개가 있다. 직원은 1번부터 N번까지 번호가 매겨져 있고, 일은 1번부터 M번까지 번호가 매겨져 있다. 각 직원은 최대 두 개의 일을&amp;nbsp;할 수 있고, &quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/11376&quot; data-og-url=&quot;https://www.acmicpc.net/problem/11376&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mmcK5/hyTFjSruGf/Xa6kfchaTelIpMN8o1iq4K/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11376&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/11376&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mmcK5/hyTFjSruGf/Xa6kfchaTelIpMN8o1iq4K/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;11376번: 열혈강호 2&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;강호네 회사에는 직원이 N명이 있고, 해야할 일이 M개가 있다. 직원은 1번부터 N번까지 번호가 매겨져 있고, 일은 1번부터 M번까지 번호가 매겨져 있다. 각 직원은 최대 두 개의 일을&amp;nbsp;할 수 있고,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 위의 열혈강호 문제에 딱 한 가지 조건이 추가되었다. 바로 직원 한 명이 최대 두 개까지의 일을 할 수 있는 것인데, 같은 edge를 가진 직원을 하나씩 더 만든다고 생각하고 풀면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11377&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/11377&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692440996967&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;11377번: 열혈강호 3&quot; data-og-description=&quot;첫째 줄에 직원의 수 N과 일의 개수 M, 일을 2개할 수 있는 직원의 수 K가&amp;nbsp;주어진다. (1 &amp;le; N, M &amp;le; 1,000, 1 &amp;le; K &amp;le; N) 둘째 줄부터 N개의 줄의 i번째 줄에는 i번 직원이 할 수 있는 일의 개수와 할 수 있&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/11377&quot; data-og-url=&quot;https://www.acmicpc.net/problem/11377&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jEIpN/hyTFh8aEGZ/BzHgOux0nglzZM8efZBGK1/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11377&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/11377&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jEIpN/hyTFh8aEGZ/BzHgOux0nglzZM8efZBGK1/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;11377번: 열혈강호 3&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 직원의 수 N과 일의 개수 M, 일을 2개할 수 있는 직원의 수 K가&amp;nbsp;주어진다. (1 &amp;le; N, M &amp;le; 1,000, 1 &amp;le; K &amp;le; N) 둘째 줄부터 N개의 줄의 i번째 줄에는 i번 직원이 할 수 있는 일의 개수와 할 수 있&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 백준의 열혈강호 시리즈는 다 이분 매칭 문제들인 것 같다. 이 문제는 직원들 중 몇 명만이 일을 두 개씩 할 수 있다는 추가 조건이 붙는다. 이때 k명의 직원을 특정하지는 않기 때문에 k명 이내에서는 어떤 직원이든 일을 두 개씩 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 이분 매칭을 총 두 번 하는 방식으로 풀 수 있다. 이때 두 번째 이분 매칭에서는 매칭이 총 k개 생긴 경우 매칭을 종료해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <category>Algorithm</category>
      <category>Bipartite Matching</category>
      <category>Python</category>
      <category>백준 열혈강호</category>
      <category>이분 그래프</category>
      <category>이분 매칭</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/33</guid>
      <comments>https://codable.tistory.com/33#entry33comment</comments>
      <pubDate>Sat, 19 Aug 2023 19:33:59 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 2020 KAKAO BLIND RECRUITMENT - 블록 이동하기</title>
      <link>https://codable.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/60063&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/60063&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689400178902&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/60063&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cUi14D/hyTjPK7CiN/GuYeKvqKz4DKxdLHhCERkK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/wshw8/hyTlbeItnG/VN72YjkjkPYHEEFR8Qhiu0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/60063&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/60063&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cUi14D/hyTjPK7CiN/GuYeKvqKz4DKxdLHhCERkK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/wshw8/hyTlbeItnG/VN72YjkjkPYHEEFR8Qhiu0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 2020 카카오 공채 1차 코딩테스트 7번 문제로 출제된 블록 이동하기 문제이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 맵을 탐색하는 문제는 굉장히 흔한데, 그런 문제들과 이 문제의 가장 큰 차이점은 바로 로봇이 한 칸이 아닌 &lt;b&gt;두 칸&lt;/b&gt;을 차지한다는 점이다. 또한 로봇이 이동할 때에도 두 칸이 모두 이동할 수 있어야 한다는 제약이 걸려있다. 또 이러한 점을 고려하여 로봇이 &lt;b&gt;회전&lt;/b&gt;까지 할 수 있다...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 조건들을 고려해 보았을 때, 특정 위치에서 로봇이 할 수 있는 행동은 총 여덟 가지가 나온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 왼쪽 축을 기준으로 시계 방향 회전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 왼쪽 축을 기준으로 반시계 방향 회전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 오른쪽 축을 기준으로 시계 방향 회전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 오른쪽 축을 기준으로 반시계 방향 회전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5, 6, 7, 8. 상하좌우 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 조건을 따라 그대로 구현하기만 하면 된다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이대로 구현만 하면 같은 상황이 반복될 수 있으므로, 같은 상황에서는 검사를 하지 않도록 조치를 취해야 한다. 나는 dp 테이블을 딕셔너리 형태로 만들어 관리했다. 들어가는 값은 (y1, x1, y2, x2, direction)을 넣었고, 만약 같은 값이 dp 테이블에 이미 존재한다면 검사를 하지 않고 넘어가는 방식으로 구현했다. 이때 같은 상황에서 (y1, x1), (y2, x2)의 좌표만 반전되는 경우를 피하기 위해서 x1이 항상 x2보다 작게 오게끔 하였고, 만약 x1이 x2와 같은 상황에서는 y1이 y2보다 작게끔 해주었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 아래와 같다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689400372796&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

def check_turn(y1, x1, y2, x2, board, direction):
    if direction == 0:
        if board[y1-1][x1] == 0 and board[y2-1][x2] == 0:
            return True
        else:
            return False
    elif direction == 1:
        if board[y1+1][x1] == 0 and board[y2+1][x2] == 0:
            return True
        else:
            return False
    elif direction == 2:
        if board[y1][x1-1] == 0 and board[y2][x2-1] == 0:
            return True
        else:
            return False
    elif direction == 3:
        if board[y1][x1+1] == 0 and board[y2][x2+1] == 0:
            return True
        else:
            return False
        
def solution(board):
    answer = 0
    dx = [1, 0, -1, 0]
    dy = [0, 1, 0, -1]
    robot = [[0, 0], [0, 1], 0, 0]
    q = deque()
    q.append(robot)
    stop = 0
    dp = {}
    while q:
        tmp = q.popleft()
        if tmp[0][0] == len(board) - 1 and tmp[0][1] == len(board) - 1:
            answer = tmp[3]
            break
        if tmp[1][0] == len(board) - 1 and tmp[1][1] == len(board) - 1:
            answer = tmp[3]
            break
        if (tmp[0][0], tmp[0][1], tmp[1][0], tmp[1][1], tmp[2]) in dp:
            continue
        dp[(tmp[0][0], tmp[0][1], tmp[1][0], tmp[1][1], tmp[2])] = True
        
        y1, x1 = tmp[0]
        y2, x2 = tmp[1]
        direction = tmp[2]
        time = tmp[3]
        if direction == 0:
            if y1 &amp;gt; 0 and check_turn(y1, x1, y2, x2, board, 0):
                q.append(([y2-1, x1], [y1, x1], 1, time+1))
                q.append(([y2-1, x2], [y2, x2], 1, time+1))
            if y1 &amp;lt; len(board) - 1 and check_turn(y1, x1, y2, x2, board, 1):
                q.append(([y1, x1], [y2+1, x1], 1, time+1))
                q.append(([y2, x2], [y2+1, x2], 1, time+1))
        elif direction == 1:
            if x1 &amp;gt; 0 and check_turn(y1, x1, y2, x2, board, 2):
                q.append(([y1, x1-1], [y1, x1], 0, time+1))
                q.append(([y2, x2-1], [y2, x2], 0, time+1))
            if x1 &amp;lt; len(board) - 1 and check_turn(y1, x1, y2, x2, board, 3):
                q.append(([y1, x1], [y1, x1+1], 0, time+1))
                q.append(([y2, x2], [y2, x2+1], 0, time+1))
                    
        for i in range(4):
            nx1 = tmp[0][1] + dx[i]
            ny1 = tmp[0][0] + dy[i]
            nx2 = tmp[1][1] + dx[i]
            ny2 = tmp[1][0] + dy[i]
            if 0 &amp;lt;= nx1 &amp;lt; len(board) and 0 &amp;lt;= ny1 &amp;lt; len(board) and 0 &amp;lt;= nx2 &amp;lt; len(board) and 0 &amp;lt;= ny2 &amp;lt; len(board):
                robot = ([ny1, nx1], [ny2, nx2], tmp[2], tmp[3]+1)
                answer = robot[3]
                if board[ny1][nx1] == 0 and board[ny2][nx2] == 0:
                    q.append(robot)
                    
        if stop == 1:
            break
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <category>2020 kakao blind recruitment</category>
      <category>BFS</category>
      <category>DP</category>
      <category>Dynamic Programming</category>
      <category>블록 이동하기</category>
      <category>카카오 1차 코딩테스트</category>
      <category>카카오 공채</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/32</guid>
      <comments>https://codable.tistory.com/32#entry32comment</comments>
      <pubDate>Sat, 15 Jul 2023 15:05:26 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 2023 KAKAO BLIND RECRUITMENT - 표 병합</title>
      <link>https://codable.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150366&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/150366&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689389890133&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150366&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnrZwD/hyTk9A76H9/rQiLMker8erZmoelL90TY0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/rgnMe/hyTk7pMhie/q8RZkfMbRaPWnG1qJ44Do1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150366&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150366&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnrZwD/hyTk9A76H9/rQiLMker8erZmoelL90TY0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/rgnMe/hyTk7pMhie/q8RZkfMbRaPWnG1qJ44Do1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 작년 하반기 카카오 공채 문제로 나왔던 표 병합 문제이다! union-find 문제인데 당시에 문제를 풀 때는 거의 마지막쯤에나 깨달아서 결국 효율성 점수를 받지 못했었다...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;union-find 알고리즘 자체가 어려운 편은 아니라 구현만 꼼꼼하게 한다면 충분히 풀 수 있는 문제다. 구현 문제인 만큼 문제를 꼼꼼히 읽고 시작하자!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 엑셀을 연상시키는 것 같은 문제였는데, 엑셀과는 달리 멀리 떨어져 있는 셀끼리의 병합도 가능했다. 문제에서 주어지는 셀들이 표 형태이기 때문에 2차원 리스트 형태로 관리해야 했는데, 각각의 element들을 union-find 알고리즘을 사용하기 위해 parent 배열로 만들어줘야 했던 점이 힘들었던 것 같다. 나는 기존의 union-find 함수들을 그대로 사용하기 위해서 2차원 배열의 좌표를 선형적으로 변환해 parent 배열을 만들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서 표의 최대 크기가 50 x 50이라는 제한을 두었기 때문에, parent 배열의 크기도 마찬가지로 51 x 51로 설정하였다. 여기에서 parent 배열의 크기가 50이 아닌 51인 이유는, 셀들의 좌표가 0이 아닌 1부터 시작하기 때문이다. parent 배열에서도 0번 인덱스는 사용하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌표를 인덱스로 변환하는 과정은, row 번호에는 50을 곱해주고, 이후에 col 번호를 더해주는 방식으로 구현하였다. 역으로 인덱스를 좌표로 변환할 때에는 인덱스를 50으로 나눈 몫을 row 번호로, 나머지를 col 번호로 사용했는데, 이때 col 번호가 50인 경우 나머지가 0으로 나오는 경우가 있었다. 따라서 나머지가 0인 경우 따로 예외 처리를 해주었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689390079021&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def find_parent(parent, x):
    if parent[x] != x:
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

def union_parent(parent, a, b):
    a = find_parent(parent, a)
    b = find_parent(parent, b)
    
    parent[b] = a

def solution(commands):
    answer = []
    parent = [0] * (51 * 51)
    graph = [[0] * 51 for _ in range(51)]
    
    for i in range(len(parent)):
        parent[i] = i
    
    for command in commands:
        command = command.split()
        if command[0] == &quot;UPDATE&quot;:
            update_command(command[1:], graph, parent)
        elif command[0] == &quot;MERGE&quot;:
            merge_cell(parent, graph, int(command[1]), int(command[2]), int(command[3]), int(command[4]))
        elif command[0] == &quot;UNMERGE&quot;:
            unmerge_cell(parent, graph, int(command[1]), int(command[2]))
        elif command[0] == &quot;PRINT&quot;:
            y, x = int(command[1]), int(command[2])
            parent_idx = find_parent(parent, 50*y+x)
            if parent_idx % 50 == 0:
                value = graph[parent_idx//50-1][50]
            else:
                value = graph[parent_idx//50][parent_idx%50]
            if value == 0:
                answer.append(&quot;EMPTY&quot;)
            else:
                answer.append(value)
            
    return answer

def convert_to_idx(y, x):
    return 50 * y + x

def check_parent_value(parent, graph, x):
    x = find_parent(parent, x)
    if x % 50 == 0:
        return graph[x//50-1][50]
    return graph[x//50][x%50]

def update_parent_value(parent, graph, x, value):
    x = find_parent(parent, x)
    if x % 50 == 0:
        graph[x//50-1][50] = value
    else:
        graph[x//50][x%50] = value

def update_command(contents, graph, parent):
    if len(contents) == 3:
        idx = convert_to_idx(int(contents[0]), int(contents[1]))
        value = contents[2]
        parent_idx = find_parent(parent, idx)
        if parent_idx % 50 == 0:
            graph[parent_idx//50-1][50] = value
        else:
            graph[parent_idx//50][parent_idx%50] = value
    else:
        prev_value = contents[0]
        update_value = contents[1]
        for i in range(1, 51):
            for j in range(1, 51):
                parent_value = check_parent_value(parent, graph, 50*i+j)
                if parent_value == prev_value:
                    update_parent_value(parent, graph, 50*i+j, update_value)

def merge_cell(parent, graph, r1, c1, r2, c2):
    idx1 = 50 * r1 + c1
    idx2 = 50 * r2 + c2
    parent1 = find_parent(parent, idx1)
    parent2 = find_parent(parent, idx2)
    
    if parent1 == parent2:
        return 
    
    if parent1 % 50 == 0:
        py1, px1 = parent1//50 - 1, 50
    else:
        py1, px1 = parent1//50, parent1%50
    if parent2 % 50 == 0:
        py2, px2 = parent2//50 - 1, 50
    else:
        py2, px2 = parent2//50, parent2%50
    
    value1 = graph[py1][px1]
    value2 = graph[py2][px2]
    
    if value1 == 0:
        graph[py1][px1] = graph[py2][px2]
    
    union_parent(parent, idx1, idx2)
    
def unmerge_cell(parent, graph, r, c):
    group_num = find_parent(parent, 50*r+c)
    parent_value = check_parent_value(parent, graph, 50*r+c)
    unmerge_list = []
    for i in range(1, 51):
        for j in range(1, 51):
            idx = 50*i + j
            if find_parent(parent, idx) == group_num:
                unmerge_list.append((i, j))
    for y, x in unmerge_list:
        graph[y][x] = 0
        parent[50*y+x] = 50*y+x
    graph[r][c] = parent_value&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <category>Algorithm</category>
      <category>KAKAO BLIND RECRUITMENT</category>
      <category>Python</category>
      <category>Union-Find</category>
      <category>카카오 1차 코딩테스트</category>
      <category>카카오 공채</category>
      <category>표 병합</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/31</guid>
      <comments>https://codable.tistory.com/31#entry31comment</comments>
      <pubDate>Sat, 15 Jul 2023 12:08:30 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 2022 KAKAO TECH INTERNSHIP 코딩 테스트 공부</title>
      <link>https://codable.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/118668&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/118668&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689070875625&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/118668&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GA3MK/hyTiAMzNoO/gkPJSBnP4Ht2t7Du0duAnK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bD5Oeu/hyTiqJX1mT/vD6Z2BUhnOPbDLFRf9y5Pk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/118668&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/118668&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GA3MK/hyTiAMzNoO/gkPJSBnP4Ht2t7Du0duAnK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bD5Oeu/hyTiqJX1mT/vD6Z2BUhnOPbDLFRf9y5Pk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 카카오 인턴십 문제로 출제된 코딩 테스트 공부이다! dp 문제는 언제나 구현하는 시간보다 생각하는 시간이 길어지는 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 항상 dp 문제와 그리디 문제를 헷갈려하는데, 풀 때 먼저 그리디 하게 풀려고 했을 때 직관적인 풀이가 잘 떠오르지 않으면 dp로 푸는 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 테스트 공부 문제는 완전 탐색과 dp를 결합한 문제이다. 문제 요구 사항을 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;문제에서 초기 알고력, 코딩력이 주어지고 알고리즘 문제 리스트가 주어진다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;알고리즘 문제는 다음과 같이 주어진다. &lt;br /&gt;[문제를 풀기 위한 알고력, 문제를 풀기 위한 코딩력, 풀었을 때 오르는 알고력, 풀었을 때 오르는 코딩력, 푸는 데 걸리는 시간]&lt;br /&gt;문제를 풀면 주어진만큼 알고력과 코딩력이 오르는데, 알고력과 코딩력은 공부를 해서 올릴 수 있다. 이때 시간을 1만큼 사용하면 알고력 또는 코딩력을 1만큼 올릴 수 있다. 문제를 여러 번 풀면 reward가 여러 번 적용된다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;주어진 문제를 모두 풀기 위한 알고력과 코딩력을 갖추는데 걸리는 최소 시간을 구해라.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 처음 봤을 때는 냅색 문제를 떠올렸었다. 냅색 문제를 떠올려보면, 보통 한 가지를 최대화하면서 동시에 나머지 하나는 최소화하는 문제 유형이다. 이 문제도 마찬가지로 알고력과 코딩력을 올리는 동시에 시간은 최소화해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 점을 고려하여, 나는 dp를 2차원 배열의 형태로 만들었다. 각각의 row와 column은 알고력과 코딩력을 의미하며, 문제에서 알고리즘 문제가 요구하는 최대 알고력과 코딩력은 각각 150으로 제한이 걸려있었기 때문에 배열의 크기는 151 x 151으로 설정했다. dp에 들어가는 값은 해당 (알고력, 코딩력)까지 도달하는데 걸리는 최소 시간을 의미한다. 따라서 처음에는 초기값을 제외하고는 모두 INF로 설정했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 (0, 초기 알고력, 초기 코딩력)을 heapq에 넣은 후, 반복문을 돌면서 모든 경우를 계산해 주었다. 여기서 모든 경우란 현재의 알고력과 코딩력을 기반으로 풀 수 있는 문제들을 풀었을 때의 시간과 (알고력, 코딩력)을 계산하는 것을 말한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문을 종료하는 조건은 힙이 비어있거나, 현재의 (알고력, 코딩력)이 모든 문제를 풀기에 충분한 정도일 때이다. 시간을 기준으로 힙에 삽입했기 때문에, 가장 처음으로 (알고력, 코딩력)이 모든 문제를 풀기 충분할 때가 가장 짧은 시간임을 보장할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689075398023&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import heapq

def solution(alp, cop, problems):
    INF = int(1e9)
    answer = INF
    
    max_alp = 0
    max_cop = 0
    
    for problem in problems:
        max_alp = max(max_alp, problem[0])
        max_cop = max(max_cop, problem[1])
    
    dp = [[INF] * 151 for _ in range(151)]
    dp[alp][cop] = 0
    
    h = []
    heapq.heappush(h, (0, -alp, -cop))
    problems += [[0, 0, 1, 0, 1], [0, 0, 0, 1, 1]]
    problems.sort()
    while h:
        time, alp, cop = heapq.heappop(h)
        alp *= -1 
        cop *= -1
        if max_alp &amp;lt;= alp and max_cop &amp;lt;= cop:
            answer = time
            break
        for problem in problems:
            if alp &amp;lt; problem[0]:
                break
            if cop &amp;lt; problem[1]:
                continue
            next_alp = alp + problem[2]
            next_cop = cop + problem[3]
            if next_alp &amp;gt; 150:
                next_alp = 150
            if next_cop &amp;gt; 150:
                next_cop = 150
            next_time = time + problem[4]
            if dp[next_alp][next_cop] &amp;gt; next_time:
                dp[next_alp][next_cop] = next_time
                heapq.heappush(h, (next_time, -next_alp, -next_cop))
    
    return answer&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <category>DP</category>
      <category>Dynamic Programming</category>
      <category>KAKAO BLIND RECRUITMENT</category>
      <category>카카오 인턴십 코딩테스트</category>
      <category>코딩 테스트 공부</category>
      <category>프로그래머스</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/30</guid>
      <comments>https://codable.tistory.com/30#entry30comment</comments>
      <pubDate>Tue, 11 Jul 2023 20:39:07 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Project 4 - File System(FAT)</title>
      <link>https://codable.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주는 핀토스의 마지막 주차인 파일 시스템이다. 보통 핀토스는 프로젝트 2와 3이 가장 어려운 편이고, 마지막 프로젝트 4가 쉬운 편이라고 하는데 그렇다고 만만할 정도는 아니었다... 그래도 핀토스에 대한 흐름이 어느 정도 잡혀있어서 그런지 초반에 개념을 잡고 나니 구현은 생각보다 빠르게 진행됐다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반에 개념 잡는 게 어려우니 오늘 포스팅은 파일 시스템의 기본 개념에 대해서 다뤄보려고 한다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Inode&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 핀토스가 제공하는 기본 코드에 대해서 살펴보자. 커널에서 파일이나 directory에 접근할 때 항상 inode라는 구조체를 거쳐서 접근하게 된다. inode 구조체를 살펴보자!&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1685371257139&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* In-memory inode. */
struct inode
{
	struct list_elem elem;	/* Element in inode list. */
	disk_sector_t sector;	/* Sector number of disk location. */
	int open_cnt;			/* Number of openers. */
	bool removed;			/* True if deleted, false otherwise. */
	int deny_write_cnt;		/* 0: writes ok, &amp;gt;0: deny writes. */
	struct inode_disk data; /* Inode content. */
};

/* On-disk inode.
 * Must be exactly DISK_SECTOR_SIZE bytes long. */
struct inode_disk
{
	disk_sector_t start; /* First data sector. */
	off_t length;		 /* File size in bytes. */
	int is_dir;
	unsigned magic;		  /* Magic number. */
	uint32_t unused[124]; /* Not used. */
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핀토스에서 사용하는 inode 구조체에는 inode_disk라는 구조체를 멤버 변수로 사용하고 있다. 간략하게 설명하자면 inode는 메모리 공간 상에서만 존재하는 데이터이고, inode_disk는 실제로 하드 디스크에 저장되어 있는 정보이다. 각각의 멤버 변수에 대해 조금 더 자세하게 알아보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;inode&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;elem&lt;/b&gt; : inode가 open 되었을 때 open 된 inode들을 관리하는 리스트에 삽입하기 위해 있는 변수이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sector&lt;/b&gt; : inode_disk에 대한 데이터가 저장되어 있는 sector를 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;open_cnt&lt;/b&gt; : 현재 inode를 open 한 프로세스의 개수를 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;removed&lt;/b&gt; : inode가 가리키고 있는 파일의 삭제 유무를 나타낸다. 만약 removed가 true로 설정되면, inode를 참조하고 있는 마지막 프로세스가 inode를 close 할 때 파일 삭제를 진행한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;deny_write_cnt&lt;/b&gt; : 파일의 write를 deny 설정한 횟수를 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;data&lt;/b&gt;&lt;/span&gt; : 실제 디스크에 저장되어 있는&lt;b&gt; inode의 내용&lt;/b&gt;이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;inode_disk&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;start&lt;/b&gt; : &lt;b&gt;실제 데이터가 저장&lt;/b&gt;되어 있는 sector의 시작점을 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;length&lt;/b&gt; : 파일의 길이를 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;is_dir&lt;/b&gt; : 구현 과정에서 추가한 변수로, 현재 inode가 directory인지를 나타내는 변수이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;unused&lt;/b&gt; : inode_disk의 크기를 하드디스크의 한 sector와 맞추기 위한 변수이다. 실제로 사용되지는 않는다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 파일이나 directory는 이 inode를 통해 관리된다. directory 또한 파일처럼 취급하고 따라서 directory에도 파일과 마찬가지로 inode 구조체가 붙는다. 모든 파일이나 directory는 고유한 inode를 가지며, inode가 저장되어 있는 sector를 inumber라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;57&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SRHGk/btshCg0hXJZ/XTsnOC89xS8uQe8QRei6c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SRHGk/btshCg0hXJZ/XTsnOC89xS8uQe8QRei6c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SRHGk/btshCg0hXJZ/XTsnOC89xS8uQe8QRei6c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSRHGk%2FbtshCg0hXJZ%2FXTsnOC89xS8uQe8QRei6c0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;57&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;57&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 ls -i 옵션을 통해 파일의 inumber를 확인해 볼 수 있다. inode_disk는 아주 간략한 정보만 담고 있지만, 실제로 inode는 파일의 권한이나 마지막 수정 시간 등 더 많은 메타데이터를 담고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pEKqG/btshHN4aCm9/ZkrLYPgTkRj3lJ0k3mkDkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pEKqG/btshHN4aCm9/ZkrLYPgTkRj3lJ0k3mkDkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pEKqG/btshHN4aCm9/ZkrLYPgTkRj3lJ0k3mkDkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpEKqG%2FbtshHN4aCm9%2FZkrLYPgTkRj3lJ0k3mkDkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;126&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 stat 명령어를 통해 파일이나 directory의 더욱 자세한 정보를 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;How to access File/Directory&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VqrvJ/btshBlAK7XS/5KDKi8oXueZRVcZLgo0eT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VqrvJ/btshBlAK7XS/5KDKi8oXueZRVcZLgo0eT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VqrvJ/btshBlAK7XS/5KDKi8oXueZRVcZLgo0eT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVqrvJ%2FbtshBlAK7XS%2F5KDKi8oXueZRVcZLgo0eT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;943&quot; height=&quot;175&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 inode에 담겨있는 정보를 통해 실제 directory나 파일에 접근하는 방법을 알아보자. 하드 디스크가 위의 그림과 같이 있다고 해보자. 아주 간단하게 표현하긴 했지만 위의 그림과 같이 디스크의 맨 앞부분은 예약된 영역으로 사용한다. 이후 우리가 사용할 FAT가 위치하고, 그 뒷부분이 모두 data region이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 root directory cluster는 그림과 같이 FAT의 바로 뒤에 위치한다. root directory cluster의 안을 조금 더 자세히 들여다보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAmGoN/btshCdCr3uR/p5tiGpNjKkVAJIY6W6g8B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAmGoN/btshCdCr3uR/p5tiGpNjKkVAJIY6W6g8B0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAmGoN/btshCdCr3uR/p5tiGpNjKkVAJIY6W6g8B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAmGoN%2FbtshCdCr3uR%2Fp5tiGpNjKkVAJIY6W6g8B0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;319&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림과 같이 root directory cluster에는 여러 파일 또는 directory의 directory entry들이 담겨있다. 구조체를 확인하고 넘어가자.&lt;/p&gt;
&lt;pre id=&quot;code_1685373075026&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* A single directory entry. */
struct dir_entry
{
	disk_sector_t inode_sector; /* Sector number of header. */
	char name[NAME_MAX + 1];	/* Null terminated file name. */
	bool in_use;				/* In use or free? */
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 세 개의 변수가 있는 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;inode_sector&lt;/b&gt; : 파일 또는 directory에 대한 inode가 위치한 sector를 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;name&lt;/b&gt; : 파일 또는 directory의 이름을 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;in_use&lt;/b&gt; : directory 내에서 실제로 사용되고 있는지를 확인하는 flag이다. 만약 false로 설정되어 있다면, 존재하지 않거나 삭제된 파일을 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;directory 내의 entry들은 모두 크기가 동일하기 때문에, 파일이나 directory를 찾을 때에는 directory entry들을 검사하며 name을 비교하여 찾을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y7ZHl/btshE9GoB5B/UKmSkWQkTCk2d8GF4Gi0fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y7ZHl/btshE9GoB5B/UKmSkWQkTCk2d8GF4Gi0fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y7ZHl/btshE9GoB5B/UKmSkWQkTCk2d8GF4Gi0fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy7ZHl%2FbtshE9GoB5B%2FUKmSkWQkTCk2d8GF4Gi0fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;707&quot; height=&quot;517&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;directory entry에 들어있는 내용들을 나타내면 위의 그림과 같다. 각각의 entry들이 모두 파일의 inode 위치와 이름을 가지고 있는 것을 볼 수 있다. 여기서 만약 jungle이라는 이름의 directory로 접근한다고 생각해 보자. 그림에서는 dir1이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRALYB/btshA40kXXH/OM3vx2PKLTkbEBewwwBM61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRALYB/btshA40kXXH/OM3vx2PKLTkbEBewwwBM61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRALYB/btshA40kXXH/OM3vx2PKLTkbEBewwwBM61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRALYB%2FbtshA40kXXH%2FOM3vx2PKLTkbEBewwwBM61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;717&quot; height=&quot;448&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dir1에 대한 entry를 읽어 들여 inode_sector가 45번인 것을 알아냈다! 이제 디스크의 45번 sector로 접근해 정보를 불러온다. 여기에서 우리는 jungle directory에 대한 data가 56번 sector에 저장되어 있으며, 길이는 58475byte인 것을 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1501&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpx1Nu/btshKRSS9Iw/l83eiaP5KZfAXgsP7huqT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpx1Nu/btshKRSS9Iw/l83eiaP5KZfAXgsP7huqT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpx1Nu/btshKRSS9Iw/l83eiaP5KZfAXgsP7huqT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbpx1Nu%2FbtshKRSS9Iw%2Fl83eiaP5KZfAXgsP7huqT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;130&quot; data-origin-width=&quot;1501&quot; data-origin-height=&quot;271&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이제 위와 같이 jungle directory에 대한 data에 접근할 수 있게 되었다! 현재 접근한 파일은 directory이기 때문에, dir1 data에는 위에서 root directory를 살펴본 것과 같이 directory entry들이 담겨있을 것이다. 파일에 대한 접근 또한 이와 동일하며, 단지 읽어 들일 데이터의 형태만 다를 뿐이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;FAT(File Allocation Table)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 위의 그림에서 나와있는 FAT에 대해 알아보자. 기본적으로 핀토스에서는 디스크 sector의 할당 여부를 free_map을 활용하여 나타내고 있다. free_map은 bitmap 자료구조로 구현되어 있으며, sector 할당 요청이 들어왔을 때 bitmap_scan_and_flip 함수를 통해 &lt;b&gt;연속적&lt;/b&gt;으로 할당 가능한 sector들을 찾고 bitmap의 bit들을 1로 뒤집는다. 이러한 방식 때문에, 기존의 free_map을 활용한 방식에서는 파일을 만들기 위해서 파일 크기만큼의 연속적인 sector가 비어있어야 했다. 이를 FAT 방식으로 바꾸어 파일을 흩어진 sector들에 나누어 저장하는 방식을 구현하는 것이 프로젝트 4의 과제이다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FAT에서는 sector들을 관리하기 위해 디스크의 sector를 cluster 단위로 나누어 관리한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfeGUQ/btshVBaGoy2/iNPtsZhpBNlzukyvSc3efk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfeGUQ/btshVBaGoy2/iNPtsZhpBNlzukyvSc3efk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfeGUQ/btshVBaGoy2/iNPtsZhpBNlzukyvSc3efk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfeGUQ%2FbtshVBaGoy2%2FiNPtsZhpBNlzukyvSc3efk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;758&quot; height=&quot;112&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림으로 나타내면 대략 위와 같다! 여기에서 우리가 만약 어떤 파일을 만드는데, 총 세 개의 cluster가 필요하다고 해보자. 원래의 free_map 방식이었으면 위의 그림에서 CLUSTER2에서 CLUSTER4 까지를 할당했을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kE5GU/btshBk9KpmT/Hs8u7GoutFPmtbvPhoWDhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kE5GU/btshBk9KpmT/Hs8u7GoutFPmtbvPhoWDhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kE5GU/btshBk9KpmT/Hs8u7GoutFPmtbvPhoWDhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkE5GU%2FbtshBk9KpmT%2FHs8u7GoutFPmtbvPhoWDhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;114&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 같은 상황에서 위와 같이 CLUSTER2와 CLUSTER4가 이미 할당된 상태라면 어떻게 될까? free_map 방식에서는 CLUSTER3은 연속적으로 할당이 불가능하기 때문에 사용할 수 없게 된다. 하지만 FAT 방식에서는 CLUSTER3도 사용할 수 있다!&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zH1Wn/btshE12Rla2/5zXpTnVaapMADcYlaAEHcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zH1Wn/btshE12Rla2/5zXpTnVaapMADcYlaAEHcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zH1Wn/btshE12Rla2/5zXpTnVaapMADcYlaAEHcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzH1Wn%2FbtshE12Rla2%2F5zXpTnVaapMADcYlaAEHcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;761&quot; height=&quot;100&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 세 개의 cluster를 요청했을 때, CLUSTER 3, 4, 11을 할당해 준다고 하자. 이때 FAT는 다음과 같이 설정된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgevmO/btshTvVZ2fw/YYvXEpPdssxlEFzBau0dX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgevmO/btshTvVZ2fw/YYvXEpPdssxlEFzBau0dX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgevmO/btshTvVZ2fw/YYvXEpPdssxlEFzBau0dX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgevmO%2FbtshTvVZ2fw%2FYYvXEpPdssxlEFzBau0dX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;382&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;607&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에서 왼쪽은 테이블의 인덱스, 오른쪽은 해당 인덱스의 value를 나타낸 것이다. 이렇게 다음 내용이 저장되어 있는 cluster를 FAT에 저장함으로써 서로 다른 부분에 데이터가 저장되어 있어도 파일을 연속적으로 읽는 것이 가능해진다. 이때 마지막 cluster는 EOChain 값을 설정하여 파일의 끝을 의미하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Operating System</category>
      <category>FAT</category>
      <category>File</category>
      <category>File Allocation Table</category>
      <category>File System</category>
      <category>Index-node</category>
      <category>inode</category>
      <category>KAIST PintOS</category>
      <category>Pintos</category>
      <category>Pintos Project 4</category>
      <category>Unix</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/29</guid>
      <comments>https://codable.tistory.com/29#entry29comment</comments>
      <pubDate>Tue, 30 May 2023 00:55:48 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] User VA, Kernel VA</title>
      <link>https://codable.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 개념에 관한 정리보다는 프로젝트를 진행하면서 궁금했던 점을 위주로 정리해보려고 한다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmxNCP/btsg1doKcnE/6PN1eQsFeKmk25FSZ90k00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmxNCP/btsg1doKcnE/6PN1eQsFeKmk25FSZ90k00/img.png&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;215&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;46.95&quot; style=&quot;width: 46.4001%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmxNCP/btsg1doKcnE/6PN1eQsFeKmk25FSZ90k00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmxNCP%2Fbtsg1doKcnE%2F6PN1eQsFeKmk25FSZ90k00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AUOAM/btsg2Ykksri/RdSjLxo3pJFR3k50rtw6tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AUOAM/btsg2Ykksri/RdSjLxo3pJFR3k50rtw6tK/img.png&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;194&quot; data-is-animation=&quot;false&quot; style=&quot;width: 52.4371%;&quot; data-widthpercent=&quot;53.05&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AUOAM/btsg2Ykksri/RdSjLxo3pJFR3k50rtw6tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAUOAM%2Fbtsg2Ykksri%2FRdSjLxo3pJFR3k50rtw6tK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 3을 진행하다 보면, anon page나 file page에서 swap in 함수로 load_segment 함수를 사용할 일이 생긴다. 이때 load를 할 주소 값을 user virtual address로 넘겨주는 경우와 kernel virtual address로 넘겨주는 경우의 차이가 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이가 왜 발생하는지, 또 user virtual address의 경우 kernel virtual address와의 mapping 정보를 page table에 추가해 주었기 때문에 참조가 가능한데, kernel virtual address는 어떻게 바로 참조가 가능한지에 대해 알아보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;User Virtual Address &amp;amp; Kernel Virtual Address&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 포스팅에서도 언급했었지만, 다시 한번 간략하게 정리하고 넘어가려고 한다. 우선 user virtual address는 유저가 사용하는 가상 주소 공간을 말한다. 이 주소 공간은 모든 프로세스가 독립적으로 사용한다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;즉,&lt;/b&gt;&lt;/span&gt; &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;프로세스 A가 사용하는 0x1000 주소는 프로세스 B가 사용하는 0x1000 주소와 실제로 매핑되어 있는 물리 프레임이 다르다는 것이다!&lt;/b&gt;&lt;/span&gt; 이를 통해 프로세스 A에서 프로세스 B가 사용하는 주소인 0x1000에 접근한다고 해서 B가 사용하고 있는 데이터를 얻어오는 것이 불가능해진다. 커널에서는 이러한 방식으로 프로세스 간의 memory protection 기능을 제공하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 kernel virtual address는 실제 물리 프레임과 1대 1 매핑이 되어있는 가상 주소 공간을 말한다. 핀토스에서는 KERN_BASE를 기준으로 유저 가상 주소 공간과 커널 가상 주소 공간을 나누고 있으며, KERN_BASE 위의 주소들은 실제 물리 프레임과 1대 1로 매핑이 되어있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 1대 1로 매핑이 되어있다는 것이 무슨 의미일까? 핀토스가 실행될 때의 코드를 한번 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1684764298753&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void
paging_init (uint64_t mem_end) {
	uint64_t *pml4, *pte;
	int perm;
	pml4 = base_pml4 = palloc_get_page (PAL_ASSERT | PAL_ZERO);

	extern char start, _end_kernel_text;
	// Maps physical address [0 ~ mem_end] to
	//   [LOADER_KERN_BASE ~ LOADER_KERN_BASE + mem_end].
	for (uint64_t pa = 0; pa &amp;lt; mem_end; pa += PGSIZE) {
		uint64_t va = (uint64_t) ptov(pa);

		perm = PTE_P | PTE_W;
		if ((uint64_t) &amp;amp;start &amp;lt;= va &amp;amp;&amp;amp; va &amp;lt; (uint64_t) &amp;amp;_end_kernel_text)
			perm &amp;amp;= ~PTE_W;

		if ((pte = pml4e_walk (pml4, va, 1)) != NULL)
			*pte = pa | perm;
	}

	// reload cr3
	pml4_activate(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 보면, 주석에도 적혀있는 것처럼 커널의 가상 주소와 실제 물리 프레임 주소 간의 매핑을 진행한다. 이때 매핑 정보는 커널의 page table인 base_pml4에 추가되는 것을 볼 수 있다. 이 코드를 통해 우리는 커널 또한 page table을 가지고 있으며, 그 page table에는 kernel virtual address와 physical frame 간의 1대 1 매핑 정보가 담겨있다는 것을 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음에는 유저 프로세스를 생성할 때, page table을 생성하는 부분을 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0S7nG/btsg1ssFaaA/lflBrPSjmg6apxMHzlkxm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0S7nG/btsg1ssFaaA/lflBrPSjmg6apxMHzlkxm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0S7nG/btsg1ssFaaA/lflBrPSjmg6apxMHzlkxm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0S7nG%2Fbtsg1ssFaaA%2FlflBrPSjmg6apxMHzlkxm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;312&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 fork 또는 exec 시스템 콜을 호출해서 __do_fork 함수나 process_exec 내의 load 함수에서 pml4_create 함수가 호출된다. 이 함수는 새로운 page table을 생성하는 함수이다. 위의 코드에서 빨간색 네모 박스 부분을 살펴보면, base_pml4에서 값을 복사해 오는 부분을 확인할 수 있다. 이 말은 커널의 page table 정보를 유저 프로세스가 똑같이 복사해 온다는 것을 의미한다. 즉, 유저 프로세스 또한 처음 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;시작할 때부터 실제 물리 프레임과 1대 1로 매핑되어 있는 kernel virtual address의 정보가 page table에 담겨있다는 것을 의미한다&lt;/b&gt;&lt;/span&gt;. 이를 통해 위에서 언급했던 의문들이 해소될 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CPU가 특정 주소값으로 접근할 때는 항상 page table을 거쳐간다&lt;/b&gt;&lt;/span&gt;. 이는 CPU가 받은 주소값이 실제 물리 프레임과 매핑되어 있는 주소인지, 아니면 page table을 통해 kernel virtual address를 얻어와야 하는 주소인지 알 수 없기 때문이다. 따라서 모든 주소에 접근할 때 항상 page table을 거쳐가고, 이에 따라 모든 프로세스들은 kernel virtual address가 물리 프레임과 매핑되어 있는 정보를 page table에 담을 필요가 있다. 이는 kernel mode에서 프로세스가 kernel virtual address에 접근해야 하는 상황이 발생할 수 있기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 우리가 user virtual address에 write 작업을 하면, user virtual address에 해당하는 page table entry의 dirty bit이 활성화되고, kernel virtual address에 write 작업을 하면 kernel virtual address에 해당하는 page table entry의 dirty bit이 활성화되는 것이다. 이를 통해 user virtual address에 해당하는 page table entry의 dirty bit을 활성화시키지 않으면서, 페이지에 값을 쓰는 것이 가능해진다. 이러한 상황은 페이지에 초기 내용을 load 하는 과정에서 발생한다. load 과정에서는 파일을 실행하기 위한 정보가 쓰이는 것이므로, 엄밀히 따지면 user의 write 작업이라고 보기 힘들다. 따라서 load 과정에서 dirty bit이 활성화되는 것은 적절하지 않으며, 우리는 kernel virtual address를 통해 page에 값을 써주어 user page entry의 dirty bit이 활성화되는 것을 방지하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8FVf7/btsg0tFEEyH/CYhj0vorC0s6nC65bJOHGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8FVf7/btsg0tFEEyH/CYhj0vorC0s6nC65bJOHGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8FVf7/btsg0tFEEyH/CYhj0vorC0s6nC65bJOHGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8FVf7%2Fbtsg0tFEEyH%2FCYhj0vorC0s6nC65bJOHGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1380&quot; height=&quot;305&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 내용을 이어가기에 앞서, virtual address의 구조를 조금 살펴보고 가자. 상위 비트 값들은 페이지 테이블에 접근하기 위한 offset이나 pointer인데, 이와 관련한 내용들은 docs의 introduction에도 잘 나와있고, 잘 정리해 둔 글들도 많기 때문에 이번 포스팅에서 주로 다루는 내용인 physical offset에 관해서만 언급하고 넘어가겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 virtual address를 통해 얻고자 하는 것은 physical frame의 address이다. 하지만 위의 virtual address structure를 보면 상위 48 비트는 실제 frame의 address와는 직접적인 연관이 없음을 알 수 있다. 반면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;하위 12 비트의 physical offset은 우리가 return 값으로 받기 원하는 frame의 address에 직접적으로 쓰이는 값이다&lt;/b&gt;&lt;/span&gt;. 이를 통해 알 수 있는 것이 하나 있는데, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;바로 page table entry에 저장되는 physical frame address의 하위 12 비트는 아무런 쓸모가 없다는 것이다&lt;/b&gt;&lt;b&gt;!&lt;/b&gt;&lt;/span&gt; 왜냐하면 우리가 page table entry에 접근하여 상위 48 비트만큼을 실제 물리 주소로 사용하고, 하위 12 비트는 버리고 나서 인자로 들어온 virtual address의 하위 12 비트를 사용하기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이렇게 쓸모없는 12 비트조차도 쓸모 있게 사용하기 위해, 우리는 page table entry의 하위 12 비트를 각종 flag를 나타내기 위해 사용한다. 이 정보는 include/threads/pte.h 파일에 나와있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPOeUl/btsg0XmdLHQ/2oCeNF3kX94dWPogG2QpB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPOeUl/btsg0XmdLHQ/2oCeNF3kX94dWPogG2QpB0/img.png&quot; data-alt=&quot;Intel&amp;amp;reg; 64 and IA-32 Architectures Software Developer&amp;amp;rsquo;s Manual&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPOeUl/btsg0XmdLHQ/2oCeNF3kX94dWPogG2QpB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPOeUl%2Fbtsg0XmdLHQ%2F2oCeNF3kX94dWPogG2QpB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;820&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Intel&amp;reg; 64 and IA-32 Architectures Software Developer&amp;rsquo;s Manual&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림은 인텔의 공식 문서에 나와있는 내용이다. pte.h 보다 조금 더 보기 편하게 정리되어 있어 가져왔다. 우리가 프로젝트 3을 하면서 신경 써야 할 flag는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0번 bit : 현재 페이지가 page table에 존재하는지를 나타내는 flag이다. 1이면 page table에 존재하는 것.&lt;/li&gt;
&lt;li&gt;1번 bit : 현재 페이지가 read only page인지를 나타내는 flag이다. 값이 0이면 read only임을 나타내고, 만약 앞의 present bit이 0이면 이 값은 아무 의미도 가지지 않는다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;2번 bit : 현재 페이지가 kernel만 접근 가능한 페이지인지, 아니면 user/kernel 모두 접근 가능한 페이지인지를 나타내는 flag이다. 만약 값이 0이면 kernel만 접근 가능한 페이지이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;5번 bit : 현재 페이지에 접근한 적이 있는지를 나타내는 flag이다. 우리가 가상 주소를 통해 어떤 데이터에 접근하면, CPU가 자동으로 page table entry에서 access bit을 1로 활성화시켜 준다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;6번 bit : 현재 페이지에 write 작업이 수행된 적이 있는지를 나타내는 flag이다. 유저 프로그램에서 가상 주소를 통해 write 작업을 하면, access bit과 마찬가지로 CPU가 자동으로 dirty bit을 1로 활성화시켜 준다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1405&quot; data-origin-height=&quot;761&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhxjfC/btsgNWPuQ3m/MaS6B4J7WrZP0PcdZkTD5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhxjfC/btsgNWPuQ3m/MaS6B4J7WrZP0PcdZkTD5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhxjfC/btsgNWPuQ3m/MaS6B4J7WrZP0PcdZkTD5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhxjfC%2FbtsgNWPuQ3m%2FMaS6B4J7WrZP0PcdZkTD5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1405&quot; height=&quot;761&quot; data-origin-width=&quot;1405&quot; data-origin-height=&quot;761&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 kernel virtual address에 대한 매핑 정보가 유저 프로세스의 page table에도 존재하는지 확인해 보기 위해 printf로 pte 값을 출력해 보았다. pml4_get_page 함수를 통해 값을 얻어오면, 실제 물리 프레임과 매핑되어 있는 virtual address를 return 하기 때문에 flag를 확인할 수 없다. 그래서 pml4_get_page 내부에서 printf를 통해 pte 값을 출력해 보았다. 먼저 user virtual address 영역에 있는 buffer를 통해 kva 값을 얻어오고, 다시 kva 값을 통해 pgae table에서 kva에 해당하는 page table entry를 찾아주었다. 그 결과 두 개의 pte 값을 얻을 수 있었고, 각 pte 값들을 이진수로 변환한 결과 위의 그림과 같이 나왔다. 이때 buffer로 찾은 page는 3번 비트가 1로 활성화되어 있어 user page임을 알 수 있었고, 반면에 kva 값을 통해 찾은 page는 3번 비트가 0으로 꺼져있어 kernel page 임을 알 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 과정을 통해 결론적으로 user virtual address와 kernel virtual address 모두 둘을 구분하지 않고 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CPU에서 주소에 접근할 때는 항상 page table에 접근하여 주소값을 얻어온다&lt;/b&gt;&lt;/span&gt;는 것을 알게 되었다. 프로젝트 3의 경우 시간이 조금 여유롭게 남았기 때문에 이전 프로젝트들보다 좀 더 근본적인 궁금증을 해결하는데 집중했던 것 같다. 덕분에 가상 메모리와 페이지 테이블에 대해 조금 더 깊은 이해를 할 수 있게 되었던 것 같다!&amp;nbsp;&lt;/p&gt;</description>
      <category>Operating System</category>
      <category>Kaist-Pintos</category>
      <category>Kernel VA</category>
      <category>page table</category>
      <category>Page Table Entry</category>
      <category>Pintos</category>
      <category>Pintos Project 3</category>
      <category>sw사관학교 정글</category>
      <category>User VA</category>
      <category>Virtual Memory</category>
      <category>vm</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/28</guid>
      <comments>https://codable.tistory.com/28#entry28comment</comments>
      <pubDate>Tue, 23 May 2023 00:25:47 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Project 2 - System Call(fork, wait, exec, exit)</title>
      <link>https://codable.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 대망의 프로세스 관련 시스템 콜이다..! 앞에서 소개했던 파일 입출력 관련 시스템 콜은 거의 래핑 함수 위주여서 구현이 어렵지는 않았던 반면, 프로세스 관련 시스템 콜들은 구현할 양도 앞의 포스팅에 비해 많은 편이고 무엇보다도 개념 자체가 쉽게 와닿지 않아 많이 어려웠다고 느꼈다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스와 관련된 시스템 콜은 fork, exit, exec, wait 으로 총 네 가지가 있다. 이 파트가 특히 어려웠던 점은, 네 개의 시스템 콜 중 하나라도 제대로 구현이 되어있지 않으면 거의 모든 프로세스 시스템 콜 테스트가 통과되지 않아 네 개를 모두 구현하고 나서야 결과를 확인할 수 있었다는 점이다. 구현할 양도 꽤나 많은 편이라 네 개를 모두 구현하고 나서 문제가 생기면 그 문제를 찾기가 꽤나 힘들어진다... 특히 fork가 구현되고 나서부터는 동기화 문제도 발생하기 때문에 디버깅이 아주 힘들어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Fork&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork 시스템 콜은 현재 프로세스와 완전히 동일한 프로세스를 하나 더 만드는 시스템 콜이다.&amp;nbsp; fork 함수의 리턴 값은 크게 세 가지 종류로 나눌 수 있는데, 정리해보면 아래와 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;fork()의 return value&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;1. Negative Value : 자식 프로세스가 정상적으로 만들어지지 않았을 때를 의미한다.&amp;nbsp;&lt;br /&gt;2. Zero : 새로 만들어진 자식 프로세스가 받는 값이다. fork를 호출한 부모 프로세스와 새로 만들어진 자식 프로세스를 구분하기 위해 자식 프로세스에게는 0을 리턴한다.&amp;nbsp;&lt;br /&gt;3. Positive Value : 자식 프로세스가 정상적으로 만들어졌을 때, 부모 프로세스가 받는 리턴 값이다. 자식 프로세스의 pid 값이 리턴된다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모와 자식의 리턴 값이 다르다는 것이 무슨 의미일까? 아래의 예시를 보면서 이해해보자!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzC0DM/btsgDML4aJo/hdvciCxK49JwQdv9KFZe4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzC0DM/btsgDML4aJo/hdvciCxK49JwQdv9KFZe4K/img.png&quot; data-alt=&quot;GeeksforGeeks fork 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzC0DM/btsgDML4aJo/hdvciCxK49JwQdv9KFZe4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzC0DM%2FbtsgDML4aJo%2FhdvciCxK49JwQdv9KFZe4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;385&quot; height=&quot;427&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;GeeksforGeeks fork 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 코드를 실행한다고 해보자.&lt;b&gt; forkexample()&lt;/b&gt; 함수가 실행되면 함수 안에서 fork()를 호출하는 것을 볼 수 있다. 조건문을 보면 fork()의 리턴 값을 기준으로 분기되는 것을 볼 수 있는데, 분기 조건이 fork()의 리턴 값이 0인지를 확인하는 것이다. 그렇다면 부모 프로세스가 fork를 통해 자식 프로세스를 만들면 어떻게 될까?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1291&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dadvmN/btsgCFUs8sr/bPr2472k0HWFAh8u8fX7F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dadvmN/btsgCFUs8sr/bPr2472k0HWFAh8u8fX7F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dadvmN/btsgCFUs8sr/bPr2472k0HWFAh8u8fX7F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdadvmN%2FbtsgCFUs8sr%2FbPr2472k0HWFAh8u8fX7F0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;766&quot; height=&quot;433&quot; data-origin-width=&quot;1291&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork 시스템 콜을 호출하면 자식 프로세스는 부모 프로세스의 context 까지 동일하게 복사를 하기 때문에, 자식 프로세스는 부모 프로세스가 마지막으로 실행하던 코드부터 실행하게 된다. 이때 부모의 마지막 context가 fork() 함수를 호출했을 때 이므로, 자식 또한 fork() 함수가 종료된 시점부터 코드를 실행한다. 이때 fork 함수가 끝난 뒤에 코드를 실행하는 프로세스가 fork 함수를 원래 호출했던 부모 프로세스인지, 아니면 fork 함수를 통해 새로 만들어진 자식 프로세스인지를 구분하기 위해 fork()의 리턴 값을 자식에게는 0으로 리턴한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1321&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yFWpH/btsgCCJYTjH/nlBhkIrxKX9Ni0UPbXpdD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yFWpH/btsgCCJYTjH/nlBhkIrxKX9Ni0UPbXpdD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yFWpH/btsgCCJYTjH/nlBhkIrxKX9Ni0UPbXpdD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyFWpH%2FbtsgCCJYTjH%2FnlBhkIrxKX9Ni0UPbXpdD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;435&quot; data-origin-width=&quot;1321&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 부모와 자식이 각각 코드를 더 실행하게 되면, if문에서 분기처리되어 위의 그림과 같이 실행된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핀토스에서 fork를 구현할 때 주의해야 할 점이 있다. 위의 그림과 같이 fork에서는 자식 프로세스가 부모 프로세스의 context를 복사해 오는 과정이 필요하다. fork는 시스템 콜이므로 유저 프로그램에서 fork를 호출하면 먼저 syscall_handler 함수로 진입하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzEf8d/btsgCjcPzEx/7QWE1eWLpAEvBhqWXnQ7R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzEf8d/btsgCjcPzEx/7QWE1eWLpAEvBhqWXnQ7R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzEf8d/btsgCjcPzEx/7QWE1eWLpAEvBhqWXnQ7R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzEf8d%2FbtsgCjcPzEx%2F7QWE1eWLpAEvBhqWXnQ7R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1360&quot; height=&quot;743&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핀토스에서 thread 구조체에는 context switching을 위해 인터럽트 프레임을 따로 저장해둔다. 이 인터럽트 프레임은 context switching이 발생할 때마다 값이 바뀐다. 이제 fork의 진행 과정을 살펴보자. 먼저 유저 프로그램에서 fork 시스템 콜을 호출하고, syscall_handler로 진입하게 된다. 이때 syscall_handler의 인자로 &lt;b&gt;user context의 interrupt frame&lt;/b&gt;이 전달된다. 이후 fork 과정에서 부모 프로세스는 자식 프로세스를 thread_create() 함수를 통해 생성하고, 자식 프로세스가 부모 프로세스의 리소스를 모두 load 할 때까지 sema_down을 통해 block 상태로 대기하게 된다. 여기에서 자식 프로세스 또는 또 다른 프로세스로 context switching이 발생하게 되는데, 위에서 언급했다시피 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;context switching에 의해 부모 프로세스의 interrupt frame이 바뀌게 된다&lt;/b&gt;&lt;/span&gt;. 부모 프로세스가 커널의 코드를 실행하는 도중에 context switching이 발생하여 interrupt frame이 바뀐 것이기 때문에, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;부모의 interrupt frame에는 kernel의 context가 담겨있다&lt;/b&gt;&lt;/span&gt;. 따라서 자식 프로세스가 부모 프로세스의 리소스를 복사하는 과정이 담겨있는 &lt;b&gt;__do_fork&lt;/b&gt; 함수에서 부모의 user context interrupt frame을 가져오기 위해서는 다른 방법이 필요하다..! 단순히 부모 프로세스에 접근해 parent-&amp;gt;tf와 같은 방식으로 부모의 interrupt frame을 가져오면 kernel context의 interrupt frame을 가져오게 된다. 나는 thread 구조체에 interrupt frame을 저장하는 변수를 하나 더 만들어 syscall_handler에 들어올 때마다 user context의 interrupt frame을 해당 변수(parent_if)에 저장하고, __do_fork 함수에서는 부모 스레드의 parent_if를 가져와 값을 복사해 주는 방식으로 구현했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Wait&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 살펴볼 시스템 콜은 wait이다. 파라미터로 기다릴 프로세스의 pid 값이 들어가며, 해당 프로세스가 종료될때까지 다른 일을 하지 않고 멈춰있게 하는 시스템 콜이다. 핀토스가 처음에 실행되고 initial thread에서 idle thread를 생성하는 경우에도 wait 시스템 콜과 비슷하게 동작한다. 이때 또한 세마포어를 활용하여 idle thread가 다 만들어지고 스스로 block 될 때까지 initial thread는 기다리게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wait을 호출한 부모 프로세스는 wait 함수의 리턴 값으로 자식 프로세스의 exit status를 받게 된다. 만약 자식이 정상적으로 종료되었다면exit status는 -1이 아닌 다른 값이 나올 것이고, 비정상적으로 종료되었다면 wait의 리턴 값으로 -1을 받을 것이다. 이를 통해 부모 프로세스는 자식 프로세스가 정상적으로 종료되었는지 확인할 수 있다. 또한 wait 시스템 콜을 통하여 자식이 자신의 종료를 부모에게 알리고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;자식이 할당받은 자원들을 모두 반환한 뒤 종료&lt;/b&gt;&lt;/span&gt;하는 작업을 추가해주어야 한다! 예를 들면 현재 실행중인 파일을 닫거나, 실행 과정 중에 열었던 파일들을 닫거나 하는 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Exec&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork와 거의 짝꿍처럼 쓰이는 시스템 콜이다. fork가 부모의 리소스를 모두 복사해 자식 프로세스를 만드는 시스템 콜이었다면, exec은 현재 실행 중인 context를 버리고 새로운 파일을 실행하는 시스템 콜이다. 프로세스가 옷을 갈아입는 느낌으로 생각하면 이해하기 편하다! exec은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;fork와는 달리 새로운 프로세스가 생기는 것이 아니다&lt;/b&gt;&lt;/span&gt;. exec 시스템 콜은 파라미터로 실행할 파일의 이름을 넘겨준다. 현재 context를 아예 전환하는 함수이므로 가지고 있던 리소스를 지워줘야한다. 이 작업은 process_cleanup 함수에서 해주니까 지금은 넘어가자..! 하지만 열어두었던 파일 디스크립터들은 exec이 호출되어도 계속 가지고 간다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Exit&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 exit 시스템 콜이다. fork와 exec이 자주 같이 쓰이는 것처럼 exit은 wait과 관련이 깊다. exit 시스템 콜은 현재 프로세스의 exit status를 출력하고 프로세스가 가지고 있던 리소스를 모두 지워주는 시스템 콜이다. 또한 프로세스가 종료할 때 부모 프로세스에게 자신의 종료를 sema_up을 통해 알린다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;645&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/emsFih/btsgGj3vTov/zMQ3w7VoK6GoTkxVzi2g40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/emsFih/btsgGj3vTov/zMQ3w7VoK6GoTkxVzi2g40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/emsFih/btsgGj3vTov/zMQ3w7VoK6GoTkxVzi2g40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FemsFih%2FbtsgGj3vTov%2FzMQ3w7VoK6GoTkxVzi2g40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;404&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;645&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wait과 exit의 큰 흐름을 보면 위의 그림과 같다. 위의 그림처럼 구현하면서 의문점이 하나 들 수 있다. 바로 굳이 왜 child process를 exit_sema에서 다시 block 상태로 만드는걸까? 이러한 이유는 &lt;b&gt;부모 프로세스가 child list에서 해당 프로세스를 지워야하는데, 만약 child가 sema_down을 통해 block 되지 않는다면 먼저 모든 리소스를 free 해주었을 수도 있기 때문이다&lt;/b&gt;. 따라서 child process를 진짜 destroy 하기 전에 먼저 sema_down을 통해 block 상태로 만든 후, parent process에서 child process에 관한 모든 정보를 지운 뒤 child process가 destroy list에 들어가야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Operating System</category>
      <category>exec</category>
      <category>exit</category>
      <category>fork</category>
      <category>Operating System</category>
      <category>Pintos</category>
      <category>Pintos Project 2</category>
      <category>Process</category>
      <category>system call</category>
      <category>Unix</category>
      <category>wait</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/27</guid>
      <comments>https://codable.tistory.com/27#entry27comment</comments>
      <pubDate>Sat, 20 May 2023 20:03:40 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Project 3 - Memory Management</title>
      <link>https://codable.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주는 가상 메모리와 관련된 과제를 진행하였다. VM 파트는 크게 Memory Management, Anonymous Page, Stack Growth, Memory Mapped Files, Swap in/out으로 구분되며 extra로 Copy-on-Write까지 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 위의 내용들 중에서 Memory Management 파트에 대해 다뤄볼 예정이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;User Pool &amp;amp; Kernel Pool&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 memory management 파트에서는 구현할 부분이 다른 파트에 비해 많지는 않은 편이다. 다만 초반에 코드의 흐름을 잡기가 힘들고, 가상 메모리 개념 자체가 잘 와닿지 않기때문에 핀토스의 메모리 구조와 프레임 할당 방식에 관해 중점적으로 다뤄볼 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 핀토스는 아래 그림과 같이 물리 메모리 영역을 유저 풀과 커널 풀의 두 가지 영역으로 나눈다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;679&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6YRRz/btsf3rO1qGD/ZBfUKr91LXgkkhX4uIoedK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6YRRz/btsf3rO1qGD/ZBfUKr91LXgkkhX4uIoedK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6YRRz/btsf3rO1qGD/ZBfUKr91LXgkkhX4uIoedK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6YRRz%2Fbtsf3rO1qGD%2FZBfUKr91LXgkkhX4uIoedK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;457&quot; height=&quot;533&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;679&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에서는 대부분 물리 메모리 영역을 20MB로 잡기 때문에 그림에서도 20MB로 표현했다. 기본적으로 커널 풀과 유저 풀은 각각 물리 메모리의 절반을 차지한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 핀토스에서 사용하는 palloc 관련 함수들이 바로 이 물리 메모리 영역을 할당받는 함수이다. threads/palloc.c 파일을 보면 자세히 기술되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핀토스에서 메모리를 할당할때에는 무조건 페이지 단위로 맞춰서 메모리 할당을 진행한다. 페이지의 크기는 4096바이트이며, 따라서 모든 페이지의 주소 값은 4096의 배수 형태로 나타나고, 이로 인해 주소값에서 하위 12비트는 페이지의 오프셋을 나타낸다. 물리 메모리 영역을 페이지 크기로 쪼개어 놓은 것을 앞으로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;프레임&lt;/b&gt;&lt;/span&gt;이라고 부르자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pintos Virtual Address&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 핀토스에서 사용하는 모든 주소 값들은 Virtual Address이다. 즉, palloc을 통해 할당받는 주소 값들 또한 모두 가상 주소이고, 실제로 물리 메모리의 주소 값을 참조하는 일은 없다. 여기까지 왔으면 아마 스레드 구조체의 주소 값을 많이 찍어봤을 텐데, initial thread의 주소 값을 찍어보면 KERNEL_BASE와 같은 0x8004000000을 볼 수 있을 것이다. 핀토스는 init.c 에서 처음 시작할 때 KERNEL_BASE부터 시작하여 KERNEL_BASE + (물리 메모리의 크기)까지 실제 물리 메모리와 mapping 작업을 진행한다. 또한 위에서 유저 풀과 커널 풀에 대해서 이야기했는데, 사실 유저 풀과 커널 풀도 물리 메모리에서 나뉘는 것이 아니라 커널의 가상 주소 영역에서 나뉜다. 그림으로 나타내보면 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nCrNS/btsf6DnCYJq/aIhR4X8k4UfvKuCugk3EkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nCrNS/btsf6DnCYJq/aIhR4X8k4UfvKuCugk3EkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nCrNS/btsf6DnCYJq/aIhR4X8k4UfvKuCugk3EkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnCrNS%2Fbtsf6DnCYJq%2FaIhR4X8k4UfvKuCugk3EkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;618&quot; height=&quot;401&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림에서는 물리 메모리 영역도 두 가지 풀로 나눠뒀는데, 실제로 물리 메모리가 나뉘어져 있지는 않다. 다만 커널의 가상 주소 영역을 유저 풀과 커널 풀로 나누어 매핑하기 때문에 물리 메모리에도 그러한 영역이 나타나는 것.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;include/threads/vaddr.h 파일을 보면, is_kernel_addr 이라는 매크로 함수가 있는 것을 볼 수 있다. 인자로 들어온 주소가 커널 영역의 주소, 즉 물리 프레임과 직접 매핑이 되어 있는 주소인지 아니면 유저 영역의 주소인지를 확인해 주는 함수이다. 판단 방법은 인자로 들어온 주소가 KERNEL_BASE보다 큰지 작은지 판별하는 것이다. 그런데 지금 그림에서는 커널 영역의 주소만이 물리 프레임과 매핑이 되어있다.&amp;nbsp; 여기에서 유저 영역의 가상 주소까지 표현하면 다음과 같아진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKnbJJ/btsf4eBVypQ/uKFHFOZUkpsKYvFbwJPJ5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKnbJJ/btsf4eBVypQ/uKFHFOZUkpsKYvFbwJPJ5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKnbJJ/btsf4eBVypQ/uKFHFOZUkpsKYvFbwJPJ5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKnbJJ%2Fbtsf4eBVypQ%2FuKFHFOZUkpsKYvFbwJPJ5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;470&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;737&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림과 같이 가상 메모리가 실제 물리 메모리에 비해 아주 큰 것을 볼 수 있다. Kaist-Pintos에서는 64비트 운영체제를 사용하고, 64비트 중 총 48비트를 가상 주소를 표현하는데 사용한다. 따라서 가상 주소로 표현할 수 있는 주소 공간은 무려 &lt;b&gt;256TB&lt;/b&gt;이다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 방대한 가상 주소 공간에 비해 실제 물리 메모리는 아주 작다. 프로젝트 3에서는 유저가 이렇게 방대한 가상 메모리 공간을 혼자 모두 사용하고있다고 착각하게 만드는 것이 목표이다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Page Table&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/McaxW/btsf3sf5pbF/mAeYDX0BrNVzzqsKYetEkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/McaxW/btsf3sf5pbF/mAeYDX0BrNVzzqsKYetEkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/McaxW/btsf3sf5pbF/mAeYDX0BrNVzzqsKYetEkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMcaxW%2Fbtsf3sf5pbF%2FmAeYDX0BrNVzzqsKYetEkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;383&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림은 핀토스에서 사용하는 Page Table 구조를 나타낸 것이다. 가상 주소를 해석할때 비트의 자릿수를 나누어 해당 테이블에서의 offset으로 활용하는 것을 볼 수 있다. CPU는 CR3 레지스터에 세팅된 값을 읽어 PML4 Table의 위치를 얻어오고, 주소의 offset 값을 활용해 최종적으로 물리 메모리의 주소를 얻어내는 것을 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핀토스의 코드를 보면, process_activate 라는 함수가 있는 것을 알 수 있는데, 이 함수는 프로세스가 처음 실행되거나 context switching이 발생하는 상황에서 호출된다. 이 함수는 CR3 레지스터의 값을 context switching이 발생한 후 CPU를 점유하게 되는 스레드의 PML4 값으로 바꿔주는 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 프로젝트 3을 진행하기 전에, 이미 핀토스에는 이러한 페이지 테이블이 존재한다. 하지만 기존 페이지 테이블의 가장 큰 문제점이 있는데, 바로 &lt;b&gt;가상 주소 공간이 실제 물리 메모리의 공간만큼 할당이 되면 더 이상 메모리를 할당하지 못한다는 것이다!&lt;/b&gt; 이번 과제는 여러 가지 기법들을 통해서 유저 프로그램이 실제 물리 메모리의 공간보다 더 큰 가상 메모리 공간을 모두 사용하는 것처럼 느끼게 하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Supplemental Page Table&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 첫번째로 구현해야 할 자료구조이다. 여러 가지 방법으로 구현할 수도 있겠지만 나는 Hash Table 형태로 구현했다. 그렇다면 Supplemental Page Table은 무엇이고, 이미 Page Table이 존재하는데 이 자료구조가 왜 필요할까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Supplemental Page Table은 기존 페이지 테이블을 조금 더 보완하는 기능을 한다. 기존의 페이지 테이블과 가장 다른 점을 꼽자면, 페이지 테이블은 실제 물리 메모리를 할당받은 페이지들에 대한 정보만 가지고 있지만, &lt;b&gt;Supplemental Page Table은 실제 물리 메모리를 할당받지 못한 페이지들 또한 가지고 있다는 점&lt;/b&gt;이다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfBM31/btsfWWpgIYf/GXdZM8VKwHt46DeCDxOTZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfBM31/btsfWWpgIYf/GXdZM8VKwHt46DeCDxOTZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfBM31/btsfWWpgIYf/GXdZM8VKwHt46DeCDxOTZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfBM31%2FbtsfWWpgIYf%2FGXdZM8VKwHt46DeCDxOTZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;464&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림으로 살펴보자면, 위의 그림이 기존의 페이지 테이블이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;804&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7X6JD/btsfOLhgaGN/ft2BxnG5Msb7BHect74kW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7X6JD/btsfOLhgaGN/ft2BxnG5Msb7BHect74kW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7X6JD/btsfOLhgaGN/ft2BxnG5Msb7BHect74kW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7X6JD%2FbtsfOLhgaGN%2Fft2BxnG5Msb7BHect74kW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;703&quot; height=&quot;417&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;804&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위의 그림이 Supplemental Page Table이다. 실제 물리 메모리에 없는 페이지의 정보도 가지고 있고, 그러한 페이지의 값은 실제로 Memory 보다는 조금 더 멀리 있는 Disk에 저장되어 있다. 페이지의 종류에 따라 쫓겨나는 장소가 Swap Disk 또는 File로 달라지는데, 이 내용은 추후에 포스팅할 예정이다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 핀토스의 가상 메모리 구조와 우리가 구현해야 할 Supplemental Page Table에 대해 알아보았다! 핀토스 코드에서 threads/mmu.c 파일에 기존 페이지 테이블의 코드가 아주 상세히 나와있으니, 궁금하면 참고해도 좋다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Operating System</category>
      <category>Kaist-Pintos</category>
      <category>Memory Management</category>
      <category>Operating System</category>
      <category>page table</category>
      <category>Pintos</category>
      <category>Supplemental Page Table</category>
      <category>Virtual Address</category>
      <category>Virtual Memory</category>
      <category>vm</category>
      <category>x86-64</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/26</guid>
      <comments>https://codable.tistory.com/26#entry26comment</comments>
      <pubDate>Tue, 16 May 2023 01:56:56 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] System Call - File I/O</title>
      <link>https://codable.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핀토스 프로젝트 2의 시작은 시스템 콜부터다. 사실상 argument passing은 몸풀기... 문제는 몸풀기 수준이었던 argument passing조차도 쉽지만은 않았다는 거..!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 시스템 콜 중에서 파일 입출력과 관련된 시스템 콜들을 다뤄볼 예정이다. 저번에 웹 프록시 서버를 구현할 때 소켓을 파일로 추상화하여 소켓에게도 파일 디스크립터가 붙었는데, 이번에도 파일 디스크립터 개념을 사용한다. 파일 디스크립터와 관련해서는 다뤘던 포스팅이 있으니 참고하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codable.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codable.tistory.com/19&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683558237734&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Computer System] File Descriptor&quot; data-og-description=&quot;File Descriptor 유닉스 시스템에서는 많은 것들을 파일로 관리한다. 정규 파일들뿐만 아니라 디렉터리, 소켓, 기타 입출력 장치들 모두 파일의 형태로 취급하는데, 이 파일들을 구분하기 위해 파일 &quot; data-og-host=&quot;codable.tistory.com&quot; data-og-source-url=&quot;https://codable.tistory.com/19&quot; data-og-url=&quot;https://codable.tistory.com/19&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/l2zDf/hySys3uBuv/0w6c6Dl7vaUKLNk3XTdLf0/img.png?width=300&amp;amp;height=147&amp;amp;face=0_0_300_147,https://scrap.kakaocdn.net/dn/ofNYP/hySyifutsV/2M5xZDqRGq0oG2t3lx9Pi0/img.png?width=300&amp;amp;height=147&amp;amp;face=0_0_300_147,https://scrap.kakaocdn.net/dn/c4QrJs/hySysh5Apo/d4K2qELAG0qFzwOegHmpQ1/img.png?width=1744&amp;amp;height=1070&amp;amp;face=0_0_1744_1070&quot;&gt;&lt;a href=&quot;https://codable.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codable.tistory.com/19&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/l2zDf/hySys3uBuv/0w6c6Dl7vaUKLNk3XTdLf0/img.png?width=300&amp;amp;height=147&amp;amp;face=0_0_300_147,https://scrap.kakaocdn.net/dn/ofNYP/hySyifutsV/2M5xZDqRGq0oG2t3lx9Pi0/img.png?width=300&amp;amp;height=147&amp;amp;face=0_0_300_147,https://scrap.kakaocdn.net/dn/c4QrJs/hySysh5Apo/d4K2qELAG0qFzwOegHmpQ1/img.png?width=1744&amp;amp;height=1070&amp;amp;face=0_0_1744_1070');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Computer System] File Descriptor&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;File Descriptor 유닉스 시스템에서는 많은 것들을 파일로 관리한다. 정규 파일들뿐만 아니라 디렉터리, 소켓, 기타 입출력 장치들 모두 파일의 형태로 취급하는데, 이 파일들을 구분하기 위해 파일&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codable.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;File I/O System Call 목록&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;1. create&lt;br /&gt;2. remove&amp;nbsp;&lt;br /&gt;3. open&lt;br /&gt;4. filesize&lt;br /&gt;5. read&lt;br /&gt;6. write&lt;br /&gt;7. seek&lt;br /&gt;8. tell&lt;br /&gt;9. close&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 입출력 관련 시스템 콜을 구현하기 전에, 각 스레드들은 고유한 file descriptor table을 가지고 있어야 한다. 매번 쓰기에 너무 기니까 포스팅에서는 파일 테이블이라고 언급하겠다. 파일을 open 하면 새로 연 파일을 파일 테이블에 추가해 주고, 추가한 위치의 인덱스가 file descriptor라고 알려주어야 한다. 이를 위해 스레드 구조체에 파일 테이블을 추가해 주는데, 이때 파일 테이블을 &lt;b&gt;struct file *fdt[128]&lt;/b&gt;과 같이 포인터들을 담은 배열 형태로 선언하면 스레드 구조체의 크기가 너무 커지게 된다! 이를 방지하기 위해 스레드 구조체에는 파일 테이블의 시작 주소만 가지고 있을 수 있게끔 &lt;b&gt;struct file **fdt&lt;/b&gt; 형태로 선언해두고, 스레드가 생성될 때 파일 테이블을 동적으로 할당받게끔 해주자.(palloc)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 파일 입출력 관련 시스템 콜들은 대부분이 wrapping 함수의 형태를 띠고 있기 때문에 구현에서 크게 어려운 부분은 없었다. 예외 처리만 조금 신경 써서 해주자!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;File I/O System Call&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. create(const char *file, unsigned int initial_size)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째로 create 함수이다. 생성할 파일의 이름과 만들 파일의 사이즈를 파라미터로 받고, 디스크에 해당 이름으로 파일을 만드는 시스템 콜이다. 파일 생성에 성공하면 true를, 실패하면 false를 리턴한다. &lt;b&gt;filesys_create&lt;/b&gt; 함수를 통해 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. remove(const char *file)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지울 파일의 이름을 파라미터로 받고, 디스크에서 해당 이름과 같은 이름을 가진 파일을 지우는 시스템 콜이다. 파일 삭제에 성공하면 true를, 실패하면 false를 리턴한다. &lt;b&gt;filesys_remove&lt;/b&gt; 함수를 통해 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. open(const char *file)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 받은 file과 같은 이름을 가진 파일을 디스크에서 찾아 연다. &lt;b&gt;filesys_open&lt;/b&gt; 함수를 통해 구현할 수 있으며, 파일을 정상적으로 열지 못한 경우 -1을 리턴한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. filesize(int fd)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 크기를 구할 파일의 파일 디스크립터를 받는다. 해당 파일 디스크립터에 존재하는 파일의 크기를 리턴하는 함수이다. &lt;b&gt;file_length&lt;/b&gt; 함수를 통해 쉽게 구현할 수 있다. 만약 파일을 찾지 못하면 -1을 리턴한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. read(int fd, void *buffer, unsigned int size)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read와 write 함수는 다른 시스템 콜들과는 다르게 락을 활용해야 한다. 파일을 읽는 도중, 또는 파일에 값을 쓰는 과정에서 다른 스레드가 파일에 접근하여 값을 바꿔버릴 수 있으므로, 한 파일에는 하나의 스레드만 접근할 수 있게 하기 위해 락을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 파일의 디스크립터 fd, 파일에서 값을 읽어 저장할 buffer, 읽어들일 값의 크기인 size를 받아온다. read의 경우 &lt;b&gt;standard input&lt;/b&gt;에서 값을 읽어올 수 있는데, &lt;b&gt;이 경우 파일 디스크립터 값이 0이다&lt;/b&gt;. standard input에서 값을 읽어오는 경우 &lt;b&gt;input_getc&lt;/b&gt; 함수를 통해 구현할 수 있고, 다른 파일을 열어서 읽는 경우 &lt;b&gt;file_read&lt;/b&gt; 함수를 통해 구현할 수 있다. 파일을 제대로 읽어오지 못하는 경우 -1을 리턴하며, 다른 경우에는 읽어온 값의 크기를 리턴한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. write(int fd, void *buffer, unsigned int size)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read와 마찬가지로 락을 사용하여 구현해야한다. 값을 쓸 파일의 디스크립터 fd, 쓸 값이 들어있는 buffer, 쓸 값의 크기인 size를 파라미터로 받는다. write도 read와 비슷하게 &lt;b&gt;standard output&lt;/b&gt;에 값을 쓸 수 있다. &lt;b&gt;이때 파일 디스크립터 값은 1이며&lt;/b&gt;, 콘솔에 값을 쓰는 경우에 해당한다. 함수는 &lt;b&gt;putbuf&lt;/b&gt;를 통해 구현할 수 있으며, 다른 파일에 값을 쓰는 경우에는 &lt;b&gt;file_write&lt;/b&gt; 함수를 통해 구현할 수 있다. 파일에 값을 쓰지 못한 경우에는 -1을 리턴하며, 값을 쓴 경우에는 쓴 값의 크기를 리턴한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. seek(int fd, unsigned int position)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일의 offset을 position으로 바꿔주는 함수이다. &lt;b&gt;file_seek&lt;/b&gt; 함수를 통해 구현할 수 있다. void 형태로 리턴 값이 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8. tell(int fd)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일의 offset 값을 리턴하는 함수이다. 존재하지 않는 fd 값이 들어오거나 이미 close 한 파일을 찾는 경우 -1을 리턴한다. &lt;b&gt;file_tell&lt;/b&gt; 함수를 통해 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;9. close(int fd)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;open으로 열었던 파일을 닫는 함수이다. &lt;b&gt;file_close&lt;/b&gt;를 통해 구현할 수 있으며, void 형태의 함수로 리턴 값은 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;About Memory Allocation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 2에는 메모리 누수를 체크하는 multi-oom 테스트가 존재한다. 여기서 open 시스템 콜이 굉장히 많이 호출되는데, 열었던 파일을 제대로 닫아주지 않으면 메모리 누수가 발생하여 테스트를 통과하지 못하게 된다. 처음에 원인이 파일 I/O system call인 open과 close에 있다고 생각하여 조금 자세히 들여봤었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;file_open&lt;/h4&gt;
&lt;pre id=&quot;code_1683562050951&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct file *
filesys_open (const char *name) {
	struct dir *dir = dir_open_root ();
	struct inode *inode = NULL;

	if (dir != NULL)
		dir_lookup (dir, name, &amp;amp;inode);
	dir_close (dir);

	return file_open (inode);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file_open 함수를 보기 전에, filesys_open 함수를 먼저 보자. 위의 코드를 보면 파일을 open하기 전에 먼저 디렉터리에서 파일을 찾는 것을 볼 수 있다. 파일이 존재하는 inode를 찾아 그 inode를 파라미터로 넘겨 file_open 함수를 실행시키는 것을 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1683561954661&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct file *
file_open (struct inode *inode) {
	struct file *file = calloc (1, sizeof *file);
	if (inode != NULL &amp;amp;&amp;amp; file != NULL) {
		file-&amp;gt;inode = inode;
		file-&amp;gt;pos = 0;
		file-&amp;gt;deny_write = false;
		return file;
	} else {
		inode_close (inode);
		free (file);
		return NULL;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file_open 함수의 코드이다. filesys/file.c 에 적혀있다. 코드를 보면 파일을 열때 calloc을 통해 메모리를 동적으로 할당받는 것을 볼 수 있다. 새로 할당받은 파일과 inode를 연결하여 파일 구조체가 실제 값을 참조할 수 있게 해 주었다. 여기에서 동적 할당이 이루어지기 때문에 파일을 닫거나 스레드가 종료할 때 할당받은 파일들을 모두 free 해주어야 한다고 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;file_close&lt;/h4&gt;
&lt;pre id=&quot;code_1683562357277&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Closes FILE. */
void
file_close (struct file *file) {
	if (file != NULL) {
		file_allow_write (file);
		inode_close (file-&amp;gt;inode);
		free (file);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file_close 함수는 file_open 과는 다르게 wrapping 함수가 없었다. 파일 구조체의 주소값을 인자로 받아 free 해주는 함수이다. 중간에 파일의 inode도 close를 해준다. 아마 프로젝트 4에서 좀 더 자세히 다룰 것 같아서 더 깊게 파보지는 않았다. 이렇게 file_close 함수에서 할당받은 메모리를 반환하는 과정을 확인할 수 있었고, 이를 통해 스레드가 갑자기 비정상적으로 종료하거나 아니면 정상적으로 종료하는 상황에서도 열었던 파일들에 대해 close를 진행해 주어야겠다는 생각을 했다. 물론 원인이 여기는 아니었지만... multi-oom 테스트와 관련한 메모리 누수 문제는 다음 포스팅에서 다루도록 하겠다. 아무튼 원인이 여기가 아닐지라도 스레드가 종료할 때 파일 테이블에 존재하는 모든 파일들을 닫아주는 작업이 진행되어야 한다!&amp;nbsp;&lt;/p&gt;</description>
      <category>Operating System</category>
      <category>File I/O</category>
      <category>KAIST PintOS</category>
      <category>Operating System</category>
      <category>Pintos</category>
      <category>Pintos Project 2</category>
      <category>stdin</category>
      <category>stdout</category>
      <category>sw사관학교 정글</category>
      <category>system call</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/25</guid>
      <comments>https://codable.tistory.com/25#entry25comment</comments>
      <pubDate>Tue, 9 May 2023 01:17:29 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Multi-Level Feedback Queue Scheduler(mlfqs)</title>
      <link>https://codable.tistory.com/24</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;핀토스 프로젝트 1의 extra 과제이다. multi-level feedback queue scheduler의 구현인데, 일단 저번 포스팅에서 소개했던 스케줄러들과 비교해서 어떤 점이 좋은지 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 Priority Scheduler의 문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 우선순위 스케줄러는 저번 포스팅에서도 언급했다시피 우선순위가 낮은 작업이 계속해서 순서가 밀려 CPU를 얻지 못하는 Starvation 현상이 발생할 수 있다는 점이 있었다. 물론 핀토스에서 구현하는 우선순위 스케줄러에서는 priority donation도 있어 우선순위가 낮은 작업도 충분히 실행될 기회를 잡을 수 있지만, 특별히 락을 소유하지 않고 우선순위도 낮은 작업들은 이러한 조정도 받지 못할 수 있다. 이로 인해 task들의 평균 대기 시간이 길어질 수 있는 문제점이 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Multi-Level Feedback Queue Scheduler(mlfqs)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 mlfqs에서는 priority aging 기법을 활용하여 해결한다. priority aging이란 ready list에서 대기를 오래 한 작업일수록 priority를 높여주고 CPU를 오래 쓴 작업일수록 priority를 낮춰주는 식으로 우선순위를 보정하는 기법을 말한다. 이를 통해 처음에 우선순위가 낮은 작업일지라도 오랫동안 순서가 밀리면 후에는 우선순위가 높게 보정되어 기존의 starvation이 발생할 수 있었던 문제를 해결할 수 있다. 또한 mlfqs에서는 스레드들이 nice value를 갖는데, 이 값이 높을수록 다른 스레드에게 순서를 양보하려는 경향이 있다. 정확히 무슨 의미인지는 아래의 식을 보면서 이해해 보자!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nice Value&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nice value는 각각의 스레드별로 존재하는 값이다. 정수 값을 가지며 위에서 언급했다시피 이 값이 클수록 다른 스레드에게 CPU를 양보하려는 경향이 있다. nice value가 가질 수 있는 값의 범위는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ -20 \leq \text{nice value} \leq 20 $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범위를 보면 음수 값을 가질수도 있는데, nice value가 음수의 값을 가진다는 것은 다른 스레드로부터 CPU를 뺏어오려는 경향이 강하다는 것을 의미한다. 이 값이 어떤 방식으로 반영되는지는 뒤의 다른 값들의 계산식들을 살펴보면 이해할 수 있을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nice value는 스레드 생성 시 설정해 주는 값이며, 직접 nice value를 바꿔주지 않는 이상 스레드가 종료될 때까지 값은 바뀌지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Load Avg&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nice value나 recent cpu 값과는 달리 모든 스레드가 공유하는 공유 변수이다. 이 값은 nice value와는 달리 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실수&lt;/b&gt;&lt;/span&gt;의 값을 가진다. CPU가 최근 1분 동안 수행 가능한 스레드의 평균 개수를 나타내는 값이다. recent cpu의 값을 계산할 때 감쇠 계수로 쓰인다. 계산식은 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \text{load avg} = \frac{59}{60}*\text{load avg} + \frac{1}{60} * \text{ready threads} $$&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 식에서 ready threads는 idle thread를 제외하고 현재 ready list에 있는 스레드와 실행중인 스레드의 개수를 합한 값이다. load avg 값은 초기에 0으로 시작하며, 매 초마다 위의 식에 따라 값을 새로 계산한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Recent CPU&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 load avg 값과 같이&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실수&lt;/b&gt;&lt;/span&gt;의 값을 가진다. nice value와 마찬가지로 스레드마다 개별적으로 가지고 있는 값이며 스레드가 최근에 CPU를 얼마나 많이 사용했는지를 나타낸다. 식은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \text{recent cpu} = \frac{2 * \text{load avg}}{2 * \text{load avg} + 1} * \text{recent cpu} + \text{nice} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Priority&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 우선순위를 재조정하는 식을 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \text{priority} = \text{PRI_MAX} - \frac{1}{4} * \text{recent cpu} - 2 * \text{nice} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식을 살펴보면 다음과 같은 사실을 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;recent cpu 값이 클수록 우선순위가 많이 낮아진다.&lt;/li&gt;
&lt;li&gt;nice 값이 높을수록 우선순위가 많이 낮아진다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recent cpu의 값이 클수록 최근에 CPU를 많이 사용했다는 의미이므로 값이 크면 우선순위가 많이 낮아지는 것이다. 여기서 load avg는 recent cpu의 감쇠 계수의 역할을 한다고 했다. load avg가 클수록 recent cpu의 감쇠 계수는 1에 가까워지고, load avg가 작을수록 감쇠 계수는 0에 가까워진다. 즉, load avg가 클수록 recent cpu의 값이 천천히 감소한다는 의미이다. 이는 현재 단위 시간당 수행 가능한 스레드의 수가 많을 때는 우선순위의 조정을 조금 천천히 진행하고, 반대로 수행 가능한 스레드의 수가 적을 때는 우선순위의 조정을 빠르게 한다는 의미를 지닌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fixed-point arithmetic&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 식에서 nice value와 priority를 제외한 값들은 모두 실수 값을 가진다는 것을 살펴보았다. 우리의 핀토스는 부동 소수점 연산을 지원하지 않기 때문에, 고정 소수점 연산(fixed-point arithmetic)을 활용해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/paH5m/btserfQxUPg/yxmMUW2I652A7mwfgHCcb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/paH5m/btserfQxUPg/yxmMUW2I652A7mwfgHCcb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/paH5m/btserfQxUPg/yxmMUW2I652A7mwfgHCcb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpaH5m%2FbtserfQxUPg%2FyxmMUW2I652A7mwfgHCcb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;188&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 소수점 연산이란, 위의 그림과 같이 총 32비트의 정수형 자료형에 뒤의 14bit는 소수 부분을, 그 다음 17bit은 정수 부분을, 마지막 1bit은 값의 부호를 나타내는 방식을 말한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 이렇게 고정 소수점을 사용하는 방식에서는 연산에 주의가 필요하다. 아래의 예를 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvzyxb/btses5sSxif/OzPZVWOo0lvfLbXC6jXLqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvzyxb/btses5sSxif/OzPZVWOo0lvfLbXC6jXLqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvzyxb/btses5sSxif/OzPZVWOo0lvfLbXC6jXLqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvzyxb%2Fbtses5sSxif%2FOzPZVWOo0lvfLbXC6jXLqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;196&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 고정 소수점으로 나타낸 5.5와 1.5를 곱하면 아래의 결과를 얻을 수 있다. 이 값을 그냥 사용하게되면 우리가 연산의 결과로 기대하는 값과 아주 다른 값이 나온다. 그리고 만약 1.5가 아닌 조금만 더 큰 수를 곱했다면 표현할 비트가 모자라 오버플로우가 발생했을 수도 있다. 따라서 고정 소수점을 사용한 두 수를 곱할 때는 먼저 앞의 수를 (int64) 자료형으로 타입 캐스팅을 진행한 뒤에, 두 수를 곱하고 $ 2^{14} $으로 나누는 연산이 필요하다. 그 결과 아래와 같은 값을 얻을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0JzD5/btseoXpKSn9/Q9IeeW3HwkKvkkNAKASZPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0JzD5/btseoXpKSn9/Q9IeeW3HwkKvkkNAKASZPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0JzD5/btseoXpKSn9/Q9IeeW3HwkKvkkNAKASZPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0JzD5%2FbtseoXpKSn9%2FQ9IeeW3HwkKvkkNAKASZPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;525&quot; height=&quot;186&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 고정 소수점을 다루기 위해서는 오퍼랜드에 따라 연산 방식이 조금씩 다르다. 모든 연산을 전부 다 이렇게 다루기에는 양이 너무 많으니... 연산 테이블만 첨부한다! 참고로 핀토스 문서에 보면 자세하게 나와있으니 그걸 참고해도 좋다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 표에서 n은 정수, x와 y는 고정 소수점 형태의 실수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mhvD0/btsepKqzAme/dIXvYP3gnIzMm0CpIN1At1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mhvD0/btsepKqzAme/dIXvYP3gnIzMm0CpIN1At1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mhvD0/btsepKqzAme/dIXvYP3gnIzMm0CpIN1At1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmhvD0%2FbtsepKqzAme%2FdIXvYP3gnIzMm0CpIN1At1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;633&quot; height=&quot;324&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 mlfqs까지 구현을 완료하면 프로젝트 1에서는 다음과 같이 all pass를 받을 수 있다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vEAtv/btsesdLsfHW/BHsSSInIjUmmSCUghO28S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vEAtv/btsesdLsfHW/BHsSSInIjUmmSCUghO28S1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vEAtv/btsesdLsfHW/BHsSSInIjUmmSCUghO28S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvEAtv%2FbtsesdLsfHW%2FBHsSSInIjUmmSCUghO28S1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;361&quot; height=&quot;392&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Operating System</category>
      <category>BSD Scheduler</category>
      <category>CPU Scheduler</category>
      <category>KAIST PintOS</category>
      <category>MLFQS</category>
      <category>Multi-Level Queue</category>
      <category>Operating System</category>
      <category>Pintos</category>
      <category>Priority Aging</category>
      <category>sw사관학교 정글</category>
      <author>hin1209</author>
      <guid isPermaLink="true">https://codable.tistory.com/24</guid>
      <comments>https://codable.tistory.com/24#entry24comment</comments>
      <pubDate>Mon, 8 May 2023 23:56:35 +0900</pubDate>
    </item>
  </channel>
</rss>