<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hi_soap.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 28 Jun 2026 07:02:04 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hi_soap.log</title>
            <url>https://velog.velcdn.com/images/hi_soap/profile/dfb4c39c-d5a8-48d4-8633-af3ea1720216/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hi_soap.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hi_soap" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[프렌즈 4블록]]></title>
            <link>https://velog.io/@hi_soap/%ED%94%84%EB%A0%8C%EC%A6%88-4%EB%B8%94%EB%A1%9D</link>
            <guid>https://velog.io/@hi_soap/%ED%94%84%EB%A0%8C%EC%A6%88-4%EB%B8%94%EB%A1%9D</guid>
            <pubDate>Sun, 28 Jun 2026 07:02:04 GMT</pubDate>
            <description><![CDATA[<p><strong>2026.06.28</strong></p>
<h2 id="문제-풀이">문제 풀이</h2>
<h3 id="1차-실행-오류">1차 실행 오류</h3>
<hr>
<p><code>63.6/100</code></p>
<p>블록을 내릴 때 구조가 잘못되었음</p>
<pre><code>O O O O O
O O O O O
O O O O O
X X O O O
X X O O O
O O O O O
X X O O O
X X O O O
O O O O O</code></pre><p>와 같은 상황에서 <code>empty == 4</code>일 때, 
<code>[5][0] = [1][0]</code>
<code>[1][0] = &#39; &#39;</code>이 되는 문제가 발생하게 됨</p>
<hr>
<pre><code class="language-java">import java.util.List;
import java.util.ArrayList;

class Solution {
    public int solution(int m, int n, String[] board) {
        int answer = 0;
        List&lt;int[]&gt; remove = new ArrayList&lt;&gt;(); // 제거 할 블록의 인덱스 저장
        boolean isFin = false; // 제거할 블록의 유/무 표시

        char blocks[][] = new char[m][n];
        for (int i = 0; i &lt; m; i++) {
            for (int j = 0; j &lt; n; j++) {
                blocks[i] = board[i].toCharArray();
            }
        }

        while(true) {
            if (isFin) {
                break;
            }
            for (int i = 0; i &lt; m - 1; i++) {
                for (int j = 0; j &lt; n - 1; j++) {
                    char b1 = blocks[i][j];
                    char b2 = blocks[i][j + 1];
                    char b3 = blocks[i + 1][j];
                    char b4 = blocks[i + 1][j + 1];

                    if (b1 == b2 &amp;&amp; b2 == b3 &amp;&amp; b3 == b4 &amp;&amp; b1 != &#39; &#39;) { // 2 x 2 블록이 모두 같을 때
                        remove.add(new int[]{i, j});
                        remove.add(new int[]{i, j + 1});
                        remove.add(new int[]{i + 1, j});
                        remove.add(new int[]{i + 1, j + 1});
                        // 해당하는 인덱스 모두 저장
                    }
                }
            }
            if (remove.isEmpty()) { // 더 이상 제거할 블록이 없을 때
                isFin = true;
            }
            for (int i = 0; i &lt; remove.size(); i++) { // 블록에서 전체 제거
                if (blocks[remove.get(i)[0]][remove.get(i)[1]] == &#39; &#39;) {
                    continue;
                }
                else {
                    blocks[remove.get(i)[0]][remove.get(i)[1]] = &#39; &#39;;
                    answer++;
                }
            }
            remove.clear(); // remove 원소 전체 제거

            // 블록 내리기
            for (int i = 0; i &lt; n; i++) {
                int empty = 0;
                for (int j = m - 1; j &gt;= 0; j--) {
                    if (empty &gt; 0 &amp;&amp; blocks[j][i] != &#39; &#39;) { 
                        blocks[j + empty][i] = blocks[j][i];
                        blocks[j][i] = &#39; &#39;;
                    }
                    if (blocks[j][i] == &#39; &#39;) { // 비어있는 공간 발견 시,
                        empty++;
                    }
                }
            }
        }
        return answer;
    }
}</code></pre>
<h3 id="2차-실행-오류">2차 실행 오류</h3>
<hr>
<p><code>81.8/100</code></p>
<p><code>Deque</code>를 사용해서 블록을 내리는 구조를 바꾸었음</p>
<pre><code class="language-java">if (q.size() &gt; 0 &amp;&amp; blocks[j][i] != &#39; &#39;) {
    int p[] = q.poll();
    blocks[p[0]][p[1]] = blocks[j][i];
    blocks[j][i] = &#39; &#39;;
}
if (blocks[j][i] == &#39; &#39;) { // 비어있는 공간 발견 시,
    q.offer(new int[]{j, i});
}</code></pre>
<p>에서 이미 아래의 조건문에 <code>q.offer(new int[]{j, i})</code>가 있으므로 중복됨</p>
<hr>
<pre><code class="language-java">import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.ArrayDeque;

class Solution {
    public int solution(int m, int n, String[] board) {
        int answer = 0;
        List&lt;int[]&gt; remove = new ArrayList&lt;&gt;(); // 제거 할 블록의 인덱스 저장
        boolean isFin = false; // 제거할 블록의 유/무 표시

        char blocks[][] = new char[m][n];
        for (int i = 0; i &lt; m; i++) { // char 배열로 저장
            for (int j = 0; j &lt; n; j++) {
                blocks[i] = board[i].toCharArray();
            }
        }

        while(true) {
            if (isFin) {
                break;
            }
            for (int i = 0; i &lt; m - 1; i++) {
                for (int j = 0; j &lt; n - 1; j++) {
                    char b1 = blocks[i][j];
                    char b2 = blocks[i][j + 1];
                    char b3 = blocks[i + 1][j];
                    char b4 = blocks[i + 1][j + 1];

                    if (b1 == b2 &amp;&amp; b2 == b3 &amp;&amp; b3 == b4 &amp;&amp; b1 != &#39; &#39;) { // 2 x 2 블록이 모두 같을 때
                        remove.add(new int[]{i, j});
                        remove.add(new int[]{i, j + 1});
                        remove.add(new int[]{i + 1, j});
                        remove.add(new int[]{i + 1, j + 1});
                        // 해당하는 인덱스 모두 저장
                    }
                }
            }
            if (remove.isEmpty()) { // 더 이상 제거할 블록이 없을 때
                isFin = true;
            }
            for (int i = 0; i &lt; remove.size(); i++) { // 블록에서 전체 제거
                if (blocks[remove.get(i)[0]][remove.get(i)[1]] == &#39; &#39;) {
                    continue;
                }
                else {
                    blocks[remove.get(i)[0]][remove.get(i)[1]] = &#39; &#39;;
                    answer++;
                }
            }
            remove.clear(); // remove 원소 전체 제거

            // 블록 내리기
            Deque&lt;int[]&gt; q = new ArrayDeque&lt;&gt;();
            for (int i = 0; i &lt; n; i++) {
                q.clear();
                for (int j = m - 1; j &gt;= 0; j--) {
                    if (q.size() &gt; 0 &amp;&amp; blocks[j][i] != &#39; &#39;) {
                        int p[] = q.poll();
                        blocks[p[0]][p[1]] = blocks[j][i];
                        q.offer(new int[]{j, i});
                        blocks[j][i] = &#39; &#39;;
                    }
                    if (blocks[j][i] == &#39; &#39;) { // 비어있는 공간 발견 시,
                        q.offer(new int[]{j, i});
                    }
                }
            }
        }
        return answer;
    }
}</code></pre>
<h3 id="나의-코드">나의 코드</h3>
<h4 id="소요-시간-1시간-18분">소요 시간: 1시간 18분</h4>
<h4 id="시간-복잡도-om--n²">시간 복잡도: $O((m * n)²)$</h4>
<pre><code class="language-java">import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.ArrayDeque;

class Solution {
    public int solution(int m, int n, String[] board) {
        int answer = 0;
        List&lt;int[]&gt; remove = new ArrayList&lt;&gt;(); // 제거 할 블록의 인덱스 저장
        boolean isFin = false; // 제거할 블록의 유/무 표시

        char blocks[][] = new char[m][n];
        for (int i = 0; i &lt; m; i++) { // char 배열로 저장
            for (int j = 0; j &lt; n; j++) {
                blocks[i] = board[i].toCharArray();
            }
        }

        while(true) {
            if (isFin) {
                break;
            }
            for (int i = 0; i &lt; m - 1; i++) {
                for (int j = 0; j &lt; n - 1; j++) {
                    char b1 = blocks[i][j];
                    char b2 = blocks[i][j + 1];
                    char b3 = blocks[i + 1][j];
                    char b4 = blocks[i + 1][j + 1];

                    if (b1 == b2 &amp;&amp; b2 == b3 &amp;&amp; b3 == b4 &amp;&amp; b1 != &#39; &#39;) { // 2 x 2 블록이 모두 같을 때
                        remove.add(new int[]{i, j});
                        remove.add(new int[]{i, j + 1});
                        remove.add(new int[]{i + 1, j});
                        remove.add(new int[]{i + 1, j + 1});
                        // 해당하는 인덱스 모두 저장
                    }
                }
            }
            if (remove.isEmpty()) { // 더 이상 제거할 블록이 없을 때
                isFin = true;
            }
            for (int i = 0; i &lt; remove.size(); i++) { // 블록에서 전체 제거
                if (blocks[remove.get(i)[0]][remove.get(i)[1]] == &#39; &#39;) {
                    continue;
                }
                else {
                    blocks[remove.get(i)[0]][remove.get(i)[1]] = &#39; &#39;;
                    answer++;
                }
            }
            remove.clear(); // remove 원소 전체 제거

            // 블록 내리기
            Deque&lt;int[]&gt; q = new ArrayDeque&lt;&gt;();
            for (int i = 0; i &lt; n; i++) {
                q.clear();
                for (int j = m - 1; j &gt;= 0; j--) {
                    if (q.size() &gt; 0 &amp;&amp; blocks[j][i] != &#39; &#39;) {
                        int p[] = q.poll();
                        blocks[p[0]][p[1]] = blocks[j][i];
                        blocks[j][i] = &#39; &#39;;
                    }
                    if (blocks[j][i] == &#39; &#39;) { // 비어있는 공간 발견 시,
                        q.offer(new int[]{j, i});
                    }
                }
            }
        }
        return answer;
    }
}</code></pre>
<h3 id="ai-코드">AI 코드</h3>
<h4 id="시간-복잡도-om--n²-1">시간 복잡도: $O((m * n)²)$</h4>
<hr>
<p><code>List</code>를 사용하는 대신 <code>boolean</code>을 사용하여
시간 복잡도는 동일하지만, <code>List</code>의 오버헤드를 제거함</p>
<hr>
<pre><code class="language-java">import java.util.ArrayDeque;
import java.util.Deque;

class Solution {
    public int solution(int m, int n, String[] board) {
        int answer = 0;
        char[][] blocks = new char[m][n];
        for (int i = 0; i &lt; m; i++) {
            blocks[i] = board[i].toCharArray();
        }

        while (true) {
            boolean[][] removed = new boolean[m][n];
            boolean found = false;

            for (int i = 0; i &lt; m - 1; i++) {
                for (int j = 0; j &lt; n - 1; j++) {
                    char b = blocks[i][j];
                    if (b != &#39; &#39; &amp;&amp; b == blocks[i][j+1] &amp;&amp; b == blocks[i+1][j] &amp;&amp; b == blocks[i+1][j+1]) {
                        removed[i][j] = removed[i][j+1] = true;
                        removed[i+1][j] = removed[i+1][j+1] = true;
                        found = true;
                    }
                }
            }

            if (!found) break;

            for (int i = 0; i &lt; m; i++) {
                for (int j = 0; j &lt; n; j++) {
                    if (removed[i][j]) {
                        blocks[i][j] = &#39; &#39;;
                        answer++;
                    }
                }
            }

            Deque&lt;int[]&gt; q = new ArrayDeque&lt;&gt;();
            for (int i = 0; i &lt; n; i++) {
                q.clear();
                for (int j = m - 1; j &gt;= 0; j--) {
                    if (q.size() &gt; 0 &amp;&amp; blocks[j][i] != &#39; &#39;) {
                        int[] p = q.poll();
                        blocks[p[0]][p[1]] = blocks[j][i];
                        blocks[j][i] = &#39; &#39;;
                    }
                    if (blocks[j][i] == &#39; &#39;) q.offer(new int[]{j, i});
                }
            }
        }

        return answer;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[오픈채팅방]]></title>
            <link>https://velog.io/@hi_soap/%EC%98%A4%ED%94%88%EC%B1%84%ED%8C%85%EB%B0%A9</link>
            <guid>https://velog.io/@hi_soap/%EC%98%A4%ED%94%88%EC%B1%84%ED%8C%85%EB%B0%A9</guid>
            <pubDate>Sat, 27 Jun 2026 06:24:31 GMT</pubDate>
            <description><![CDATA[<p><strong>2026.06.27</strong></p>
<h2 id="문제-풀이">문제 풀이</h2>
<h3 id="나의-코드">나의 코드</h3>
<h4 id="소요-시간-34분">소요 시간: 34분</h4>
<h4 id="시간-복잡도-on">시간 복잡도: $O(n)$</h4>
<hr>
<p><code>map</code>은 <code>uid와 실제 이름</code>을 저장하고,
<code>access</code>는 <code>Enter, Leave, Change</code>등의 상태 정보와 <code>uid</code>를 저장하였음.</p>
<p><code>access</code>의 상태 정보와 <code>map</code>에 저장된 실제 이름을 
<code>uid</code>를 통해 매핑하여 문제를 해결함</p>
<hr>
<pre><code class="language-java">import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

class Solution {
    public String[] solution(String[] record) {
        int len = record.length;
        Map&lt;String, String&gt; map = new HashMap&lt;&gt;(); // uid와 이름 저장
        List&lt;String[]&gt; access = new ArrayList&lt;&gt;(); // 출입 여부와 uid 저장

        for (int i = 0; i &lt; len; i++) {
            String info[] = record[i].split(&quot; &quot;);

            if (!info[0].equals(&quot;Leave&quot;)) {
                map.put(info[1], info[2]); // uid와 이름 저장
            }
            access.add(new String[]{info[0], info[1]});
        }
        List&lt;String&gt; result = new ArrayList&lt;&gt;();

        for (int i = 0; i &lt; access.size(); i++) {
            if (access.get(i)[0].equals(&quot;Change&quot;)) {
                continue;
            }

            String name = map.get(access.get(i)[1]); // uid로 이름 찾기

            if (access.get(i)[0].equals(&quot;Enter&quot;)) {
                result.add(name + &quot;님이 들어왔습니다.&quot;);
            }
            else {
                result.add(name + &quot;님이 나갔습니다.&quot;);
            }
        }

        return result.toArray(new String[0]);
    }
}</code></pre>
<h3 id="ai-코드">AI 코드</h3>
<h4 id="시간-복잡도-on-1">시간 복잡도: $O(n)$</h4>
<hr>
<p><code>record</code>만 2번 순회하는 방식을 사용하여 <code>access</code>리스트 없이 해결함</p>
<hr>
<pre><code class="language-java">import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;

class Solution {
    public String[] solution(String[] record) {
        Map&lt;String, String&gt; map = new HashMap&lt;&gt;();
        List&lt;String&gt; result = new ArrayList&lt;&gt;();

        for (String r : record) {
            String[] info = r.split(&quot; &quot;);
            if (!info[0].equals(&quot;Leave&quot;)) {
                map.put(info[1], info[2]);
            }
        }

        for (String r : record) {
            String[] info = r.split(&quot; &quot;);
            if (info[0].equals(&quot;Enter&quot;)) {
                result.add(map.get(info[1]) + &quot;님이 들어왔습니다.&quot;);
            } else if (info[0].equals(&quot;Leave&quot;)) {
                result.add(map.get(info[1]) + &quot;님이 나갔습니다.&quot;);
            }
        }

        return result.toArray(new String[0]);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[파일명 정렬]]></title>
            <link>https://velog.io/@hi_soap/%ED%8C%8C%EC%9D%BC%EB%AA%85-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@hi_soap/%ED%8C%8C%EC%9D%BC%EB%AA%85-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Fri, 26 Jun 2026 06:52:31 GMT</pubDate>
            <description><![CDATA[<p><strong>2026.06.26</strong></p>
<h2 id="문제-설명">문제 설명</h2>
<p>파일명 정렬
세 차례의 코딩 테스트와 두 차례의 면접이라는 기나긴 블라인드 공채를 무사히 통과해 카카오에 입사한 무지는 파일 저장소 서버 관리를 맡게 되었다.</p>
<p>저장소 서버에는 프로그램의 과거 버전을 모두 담고 있어, 이름 순으로 정렬된 파일 목록은 보기가 불편했다. 파일을 이름 순으로 정렬하면 나중에 만들어진 ver-10.zip이 ver-9.zip보다 먼저 표시되기 때문이다.</p>
<p>버전 번호 외에도 숫자가 포함된 파일 목록은 여러 면에서 관리하기 불편했다. 예컨대 파일 목록이 [&quot;img12.png&quot;, &quot;img10.png&quot;, &quot;img2.png&quot;, &quot;img1.png&quot;]일 경우, 일반적인 정렬은 [&quot;img1.png&quot;, &quot;img10.png&quot;, &quot;img12.png&quot;, &quot;img2.png&quot;] 순이 되지만, 숫자 순으로 정렬된 [&quot;img1.png&quot;, &quot;img2.png&quot;, &quot;img10.png&quot;, img12.png&quot;] 순이 훨씬 자연스럽다.</p>
<p>무지는 단순한 문자 코드 순이 아닌, 파일명에 포함된 숫자를 반영한 정렬 기능을 저장소 관리 프로그램에 구현하기로 했다.</p>
<p>소스 파일 저장소에 저장된 파일명은 100 글자 이내로, 영문 대소문자, 숫자, 공백(&quot; &quot;), 마침표(&quot;.&quot;), 빼기 부호(&quot;-&quot;)만으로 이루어져 있다. 파일명은 영문자로 시작하며, 숫자를 하나 이상 포함하고 있다.</p>
<p>파일명은 크게 HEAD, NUMBER, TAIL의 세 부분으로 구성된다.</p>
<ul>
<li>HEAD는 숫자가 아닌 문자로 이루어져 있으며, 최소한 한 글자 이상이다.</li>
<li>NUMBER는 한 글자에서 최대 다섯 글자 사이의 연속된 숫자로 이루어져 있으며, 앞쪽에 0이 올 수 있다. 0부터 99999 사이의 숫자로, 00000이나 0101 등도 가능하다.</li>
<li>TAIL은 그 나머지 부분으로, 여기에는 숫자가 다시 나타날 수도 있으며, 아무 글자도 없을 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/617cf142-23c5-47a6-8388-52b60e715749/image.png" alt=""></p>
<p>파일명을 세 부분으로 나눈 후, 다음 기준에 따라 파일명을 정렬한다.</p>
<ul>
<li>파일명은 우선 HEAD 부분을 기준으로 사전 순으로 정렬한다. 이때, 문자열 비교 시 대소문자 구분을 하지 않는다. MUZI와 muzi, MuZi는 정렬 시에 같은 순서로 취급된다.</li>
<li>파일명의 HEAD 부분이 대소문자 차이 외에는 같을 경우, NUMBER의 숫자 순으로 정렬한다. 9 &lt; 10 &lt; 0011 &lt; 012 &lt; 13 &lt; 014 순으로 정렬된다. 숫자 앞의 0은 무시되며, 012와 12는 정렬 시에 같은 같은 값으로 처리된다.</li>
<li>두 파일의 HEAD 부분과, NUMBER의 숫자도 같을 경우, 원래 입력에 주어진 순서를 유지한다. MUZI01.zip과 muzi1.png가 입력으로 들어오면, 정렬 후에도 입력 시 주어진 두 파일의 순서가 바뀌어서는 안 된다.</li>
</ul>
<p>무지를 도와 파일명 정렬 프로그램을 구현하라.</p>
<h2 id="입력-형식">입력 형식</h2>
<p>입력으로 배열 files가 주어진다.</p>
<ul>
<li>files는 1000 개 이하의 파일명을 포함하는 문자열 배열이다.</li>
<li>각 파일명은 100 글자 이하 길이로, 영문 대소문자, 숫자, 공백(&quot; &quot;), 마침표(&quot;.&quot;), 빼기 부호(&quot;-&quot;)만으로 이루어져 있다. 파일명은 영문자로 시작하며, 숫자를 하나 이상 포함하고 있다.</li>
<li>중복된 파일명은 없으나, 대소문자나 숫자 앞부분의 0 차이가 있는 경우는 함께 주어질 수 있다. (muzi1.txt, MUZI1.txt, muzi001.txt, muzi1.TXT는 함께 입력으로 주어질 수 있다.)</li>
</ul>
<h2 id="입출력-예">입출력 예</h2>
<p>입력: [&quot;img12.png&quot;, &quot;img10.png&quot;, &quot;img02.png&quot;, &quot;img1.png&quot;, &quot;IMG01.GIF&quot;, &quot;img2.JPG&quot;]</p>
<p>출력: [&quot;img1.png&quot;, &quot;IMG01.GIF&quot;, &quot;img02.png&quot;, &quot;img2.JPG&quot;, &quot;img10.png&quot;, &quot;img12.png&quot;]</p>
<h2 id="문제-풀이">문제 풀이</h2>
<hr>
<p><code>30/100</code></p>
<p>먼 인덱스의 데이터와 비교하는 방식을 사용하여 
정렬 안정성이 깨지는 오류가 발생함</p>
<hr>
<h3 id="1차-실행-오류">1차 실행 오류</h3>
<pre><code class="language-java">class Solution {
    public String[] solution(String[] files) {
        int len = files.length;
        String[][] file = new String[len][2];

        for (int i = 0; i &lt; len; i++) { // 각 파일의 head와 number를 나누어서 저장
            StringBuilder head = new StringBuilder();
            StringBuilder number = new StringBuilder();
            boolean isNum = false;

            for (int j = 0; j &lt; files[i].length(); j++) {
                char c = files[i].charAt(j);
                if (Character.isDigit(c)) { // 문자가 숫자일 때
                    isNum = true;
                }
                if (!isNum) {
                    head.append(Character.toUpperCase(c));
                    // 대소문자 구분하지 않으므로, 대문자로 통일하여 저장
                }
                else if (isNum) {
                    if (!Character.isDigit(c)) {
                        break;
                    }
                    number.append(c);
                }
            }
            file[i][0] = head.toString();
            file[i][1] = number.toString();
        }

        for (int i = 0; i &lt; len; i++) {
            for (int j = i + 1; j &lt; len; j++) {
                int n = file[i][0].compareTo(file[j][0]);
                if (n &gt; 0) { // file[i][1] &gt; file[j][1]일 때
                    String headTmp = file[i][0];
                    file[i][0] = file[j][0];
                    file[j][0] = headTmp;
                    String numberTmp = file[i][1];
                    file[i][1] = file[j][1];
                    file[j][1] = numberTmp;

                    String temp = files[i];
                    files[i] = files[j];
                    files[j] = temp;
                }
                else if (n == 0) { // 두 문자열이 같을 때
                    int number1 = Integer.parseInt(file[i][1]);
                    int number2 = Integer.parseInt(file[j][1]);

                    if (number1 &gt; number2) {
                        String headTmp = file[i][0];
                        file[i][0] = file[j][0];
                        file[j][0] = headTmp;
                        String numberTmp = file[i][1];
                        file[i][1] = file[j][1];
                        file[j][1] = numberTmp;

                        String temp = files[i];
                        files[i] = files[j];
                        files[j] = temp;
                    }
                }
            }
        }

        return files;
    }
}</code></pre>
<h3 id="나의-코드">나의 코드</h3>
<h4 id="소요-시간-1시간-14분">소요 시간: 1시간 14분</h4>
<h4 id="시간-복잡도-on2">시간 복잡도: $O(n^2)$</h4>
<hr>
<p>AI를 활용하여 실패 원인을 분석하였고,
인접한 데이터끼리 비교하는 방식으로 코드를 직접 짜서 
정렬 안정성을 확보함</p>
<hr>
<pre><code class="language-java">class Solution {
    public String[] solution(String[] files) {
        int len = files.length;
        String[][] file = new String[len][2];

        for (int i = 0; i &lt; len; i++) { // 각 파일의 head와 number를 나누어서 저장
            StringBuilder head = new StringBuilder();
            StringBuilder number = new StringBuilder();
            boolean isNum = false;

            for (int j = 0; j &lt; files[i].length(); j++) {
                char c = files[i].charAt(j);
                if (Character.isDigit(c)) { // 문자가 숫자일 때
                    isNum = true;
                }
                if (!isNum) {
                    head.append(Character.toUpperCase(c));
                    // 대소문자 구분하지 않으므로, 대문자로 통일하여 저장
                }
                else if (isNum) {
                    if (!Character.isDigit(c)) {
                        break;
                    }
                    number.append(c);
                }
            }
            file[i][0] = head.toString();
            file[i][1] = number.toString();
        }

        for (int i = 0; i &lt; len; i++) {
            for (int j = 0; j &lt; len - 1; j++) {
                int n = file[j][0].compareTo(file[j + 1][0]);
                if (n &gt; 0) { // file[i][1] &gt; file[j][1]일 때
                    String headTmp = file[j][0];
                    file[j][0] = file[j + 1][0];
                    file[j + 1][0] = headTmp;

                    String numberTmp = file[j][1];
                    file[j][1] = file[j + 1][1];
                    file[j + 1][1] = numberTmp;

                    String temp = files[j];
                    files[j] = files[j + 1];
                    files[j + 1] = temp;
                }
                else if (n == 0) { // 두 문자열이 같을 때
                    int number1 = Integer.parseInt(file[j][1]);
                    int number2 = Integer.parseInt(file[j + 1][1]);

                    if (number1 &gt; number2) {
                        String headTmp = file[j][0];
                        file[j][0] = file[j + 1][0];
                        file[j + 1][0] = headTmp;
                        String numberTmp = file[j][1];
                        file[j][1] = file[j + 1][1];
                        file[j + 1][1] = numberTmp;

                        String temp = files[j];
                        files[j] = files[j + 1];
                        files[j + 1] = temp;
                    }
                }
            }
        }

        return files;
    }
}</code></pre>
<h3 id="ai-코드">AI 코드</h3>
<h4 id="시간-복잡도-on-log-n">시간 복잡도: $O(n log n)$</h4>
<hr>
<p>나의 코드와 많은 차이가 나는 것을 보며 인덱스의 활용이나, 
라이브러리 함수의 활용을 잘 해야겠다는 생각이 들었음</p>
<hr>
<pre><code class="language-java">import java.util.Arrays;

class Solution {
    public String[] solution(String[] files) {
        return Arrays.stream(files)
            .sorted((a, b) -&gt; {
                String[] pa = parse(a);
                String[] pb = parse(b);
                int cmp = pa[0].compareTo(pb[0]);
                if (cmp != 0) return cmp;
                return Integer.compare(Integer.parseInt(pa[1]),
                                       Integer.parseInt(pb[1]));
            })
            .toArray(String[]::new);
    }

    private String[] parse(String file) {
        int i = 0;
        while (i &lt; file.length() &amp;&amp; !Character.isDigit(file.charAt(i))) i++;
        int j = i;
        while (j &lt; file.length() &amp;&amp; Character.isDigit(file.charAt(j))) j++;

        String head = file.substring(0, i).toUpperCase();
        String number = file.substring(i, j);
        return new String[]{head, number};
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2 x n 타일링]]></title>
            <link>https://velog.io/@hi_soap/2-x-n-%ED%83%80%EC%9D%BC%EB%A7%81</link>
            <guid>https://velog.io/@hi_soap/2-x-n-%ED%83%80%EC%9D%BC%EB%A7%81</guid>
            <pubDate>Wed, 24 Jun 2026 04:38:35 GMT</pubDate>
            <description><![CDATA[<p><strong>2026.06.24</strong></p>
<h2 id="문제-설명">문제 설명</h2>
<p>가로 길이가 2이고 세로의 길이가 1인 직사각형모양의 타일이 있습니다. 이 직사각형 타일을 이용하여 세로의 길이가 2이고 가로의 길이가 n인 바닥을 가득 채우려고 합니다. 타일을 채울 때는 다음과 같이 2가지 방법이 있습니다.</p>
<ul>
<li>타일을 가로로 배치 하는 경우</li>
<li>타일을 세로로 배치 하는 경우</li>
</ul>
<p>예를 들어서 n이 7인 직사각형은 다음과 같이 채울 수 있습니다.
<img src="https://velog.velcdn.com/images/hi_soap/post/6f94a007-9e6a-4dd7-94e0-972b5de7a98a/image.png" alt=""></p>
<p>직사각형의 가로의 길이 n이 매개변수로 주어질 때, 이 직사각형을 채우는 방법의 수를 return 하는 solution 함수를 완성해주세요.</p>
<h2 id="제한사항">제한사항</h2>
<ul>
<li><p>가로의 길이 n은 60,000이하의 자연수 입니다.</p>
</li>
<li><p>경우의 수가 많아 질 수 있으므로, 
경우의 수를 1,000,000,007으로 나눈 나머지를 return해주세요.</p>
</li>
</ul>
<h2 id="입출력-예">입출력 예</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/03dcfd14-5a62-4ccf-826f-97fe15d9add9/image.png" alt=""></p>
<h2 id="문제-풀이">문제 풀이</h2>
<hr>
<p>시간 초과 오류 발생</p>
<hr>
<h3 id="1차-실행-오류">1차 실행 오류</h3>
<pre><code class="language-java">class Solution {
    public int fibo(int n) {
        if (n &lt;= 2) {
            return n;
        }
        return fibo(n - 2) + fibo(n - 1);
    }
    public int solution(int n) {
        return fibo(n) % 1000000007;
    }
}</code></pre>
<h3 id="나의-코드--ai-코드">나의 코드 &amp; AI 코드</h3>
<h4 id="소요-시간-1시간">소요 시간: 1시간</h4>
<h4 id="시간-복잡도-on">시간 복잡도: $O(n)$</h4>
<hr>
<p>수학적 규칙을 찾아보던 중, 
<strong>피보나치 수열</strong>과 같은 규칙을 가지고 있다는 것을 알아냄
<code>n == 1</code>일 때, 1
<code>n == 2</code>일 때, 2
<code>n == 3</code>일 때, 3
<code>n == 4</code>일 때, 5
<code>n == 5</code>일 때, 8
<code>n == 6</code>일 때, 13
<code>n == 7</code>일 때, 21
<code>n == 8</code>일 때 34 ...</p>
<p>다만 재귀로 구현하여 시간 초과 오류가 발생하였고,
AI를 통해 
<strong><code>DP(Dynamic Programming), 이미 계산한 결과를 저장해서 재사용 하는 방식</code></strong>
를 사용하여 해결함</p>
<hr>
<pre><code class="language-java">class Solution {
    public int solution(int n) {
        if (n &lt;= 2) {
            return n;
        }
        int fibo[] = new int[n + 1];
        fibo[1] = 1;
        fibo[2] = 2;
        for (int i = 3; i &lt;= n; i++) {
            fibo[i] = (fibo[i - 2] + fibo[i - 1]) % 1000000007;
        }

        return fibo[n];
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[주차 요금]]></title>
            <link>https://velog.io/@hi_soap/%EC%A3%BC%EC%B0%A8-%EC%9A%94%EA%B8%88-ajtf8ukq</link>
            <guid>https://velog.io/@hi_soap/%EC%A3%BC%EC%B0%A8-%EC%9A%94%EA%B8%88-ajtf8ukq</guid>
            <pubDate>Tue, 23 Jun 2026 04:58:35 GMT</pubDate>
            <description><![CDATA[<p><strong>2026.06.23</strong></p>
<h2 id="문제-설명">문제 설명</h2>
<p>주차장의 요금표와 차량이 들어오고(입차) 나간(출차) 기록이 주어졌을 때,
차량별로 주차 요금을 계산하려고 합니다. 아래는 하나의 예시를 나타냅니다.</p>
<ul>
<li><p>요금표
<img src="https://velog.velcdn.com/images/hi_soap/post/32fa71e5-da28-4926-beca-1b6b083af000/image.png" alt=""></p>
</li>
<li><p>입/출차 기록
<img src="https://velog.velcdn.com/images/hi_soap/post/4fc9f0ca-6bd5-4a08-a290-a2c5e0ed6a87/image.png" alt=""></p>
</li>
<li><p>자동차별 주차 요금
<img src="https://velog.velcdn.com/images/hi_soap/post/359c16e2-8f90-4ddd-83a0-c4884ddb62cf/image.png" alt=""></p>
</li>
<li><p>어떤 차량이 입차된 후에 출차된 내역이 없다면, 23:59에 출차된 것으로 간주합니다.</p>
</li>
<li><ul>
<li>0000번 차량은 18:59에 입차된 이후, 출차된 내역이 없습니다. 따라서, 23:59에 출차된 것으로 간주합니다.</li>
</ul>
</li>
<li><p>00:00부터 23:59까지의 입/출차 내역을 바탕으로 차량별 누적 주차 시간을 계산하여 요금을 일괄로 정산합니다.</p>
</li>
<li><p>누적 주차 시간이 기본 시간이하라면, 기본 요금을 청구합니다.</p>
</li>
<li><p>누적 주차 시간이 기본 시간을 초과하면, 기본 요금에 더해서, 초과한 시간에 대해서 단위 시간 마다 단위 요금을 청구합니다.</p>
</li>
<li><ul>
<li>초과한 시간이 단위 시간으로 나누어 떨어지지 않으면, 올림합니다.</li>
</ul>
</li>
<li><ul>
<li>⌈a⌉ : a보다 작지 않은 최소의 정수를 의미합니다. 즉, 올림을 의미합니다.</li>
</ul>
</li>
</ul>
<p>주차 요금을 나타내는 정수 배열 fees, 자동차의 입/출차 내역을 나타내는 문자열 배열 records가 매개변수로 주어집니다. 차량 번호가 작은 자동차부터 청구할 주차 요금을 차례대로 정수 배열에 담아서 return 하도록 solution 함수를 완성해주세요.</p>
<h2 id="제한-사항">제한 사항</h2>
<ul>
<li><p>fees의 길이 = 4</p>
</li>
<li><ul>
<li>fees[0] = 기본 시간(분)</li>
</ul>
</li>
<li><ul>
<li>1 ≤ fees[0] ≤ 1,439</li>
</ul>
</li>
<li><ul>
<li>fees[1] = 기본 요금(원)</li>
</ul>
</li>
<li><ul>
<li>0 ≤ fees[1] ≤ 100,000</li>
</ul>
</li>
<li><ul>
<li>fees[2] = 단위 시간(분)</li>
</ul>
</li>
<li><ul>
<li>1 ≤ fees[2] ≤ 1,439</li>
</ul>
</li>
<li><ul>
<li>fees[3] = 단위 요금(원)</li>
</ul>
</li>
<li><ul>
<li>1 ≤ fees[3] ≤ 10,000</li>
</ul>
</li>
<li><p>1 ≤ records의 길이 ≤ 1,000</p>
</li>
<li><ul>
<li>records의 각 원소는 &quot;시각 차량번호 내역&quot; 형식의 문자열입니다.</li>
</ul>
</li>
<li><ul>
<li>시각, 차량번호, 내역은 하나의 공백으로 구분되어 있습니다.</li>
</ul>
</li>
<li><ul>
<li>시각은 차량이 입차되거나 출차된 시각을 나타내며, HH:MM 형식의 길이 5인 문자열입니다.</li>
</ul>
</li>
<li><ul>
<li><ul>
<li>HH:MM은 00:00부터 23:59까지 주어집니다.</li>
</ul>
</li>
</ul>
</li>
<li><ul>
<li><ul>
<li>잘못된 시각(&quot;25:22&quot;, &quot;09:65&quot; 등)은 입력으로 주어지지 않습니다.</li>
</ul>
</li>
</ul>
</li>
<li><ul>
<li>차량번호는 자동차를 구분하기 위한, `0&#39;~&#39;9&#39;로 구성된 길이 4인 문자열입니다.</li>
</ul>
</li>
<li><ul>
<li>내역은 길이 2 또는 3인 문자열로, IN 또는 OUT입니다. IN은 입차를, OUT은 출차를 의미합니다.</li>
</ul>
</li>
<li><ul>
<li>records의 원소들은 시각을 기준으로 오름차순으로 정렬되어 주어집니다.</li>
</ul>
</li>
<li><ul>
<li>records는 하루 동안의 입/출차된 기록만 담고 있으며, 입차된 차량이 다음날 출차되는 경우는 입력으로 주어지지 않습니다.</li>
</ul>
</li>
<li><ul>
<li>같은 시각에, 같은 차량번호의 내역이 2번 이상 나타내지 않습니다.</li>
</ul>
</li>
<li><ul>
<li>마지막 시각(23:59)에 입차되는 경우는 입력으로 주어지지 않습니다.</li>
</ul>
</li>
<li><ul>
<li>아래의 예를 포함하여, 잘못된 입력은 주어지지 않습니다.</li>
</ul>
</li>
<li><ul>
<li><ul>
<li>주차장에 없는 차량이 출차되는 경우</li>
</ul>
</li>
</ul>
</li>
<li><ul>
<li><ul>
<li>차장에 이미 있는 차량(차량번호가 같은 차량)이 다시 입차되는 경우</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="입출력-예">입출력 예</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/578fefbb-356b-4781-ace1-81ffe5b1ca72/image.png" alt=""></p>
<h2 id="문제-풀이">문제 풀이</h2>
<h3 id="1차-실행-오류">1차 실행 오류</h3>
<hr>
<pre><code class="language-java">if (!tempMap.isEmpty()) { // 주차장이 비어 있지 않을 때
    for (String key : tempMap.keySet()) { // 남은 차량 시간 정리
        int time = 1439 - tempMap.get(key);
        resultMap.put(key, time + resultMap.get(key));
    }
}</code></pre>
<p>현재 주차장에 있는 차량이 한 번도 나간 기록이 없을 때,
<code>resultMap.get(key)</code> 값은 <code>null</code>이 되는 경우를 고려하지 않음</p>
<hr>
<pre><code class="language-java">import java.util.Map;
import java.util.HashMap;

class Solution {
    public int[] solution(int[] fees, String[] records) {
        Map&lt;String, Integer&gt; tempMap = new HashMap&lt;&gt;(); // 현재 주차장에 남아 있는 차량
        Map&lt;String, Integer&gt; resultMap = new HashMap&lt;&gt;(); // 차량이 사용한 총 시간

        int defaultTime = fees[0];
        int baseFee = fees[1];
        int unitTime = fees[2];
        int unitFee = fees[3];

        for (int i = 0; i &lt; records.length; i++) {
            String[] record = records[i].split(&quot; &quot;); // 시간, 차량번호, 출입 내역 나누기
            String[] parts = record[0].split(&quot;:&quot;); // 시간과 분으로 나누기
            int time = Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]); // 시간과 분을 분으로 변환

            if (record[2].equals(&quot;IN&quot;)) { // 차량이 들어갈 때
                tempMap.put(record[1], time); // 차량번호와 들어간 시간 저장
            }
            else { // 차량이 나올 때
                int timeIn = tempMap.get(record[1]); // 차량이 들어간 시간
                if (resultMap.get(record[1]) != null) { // 출입 기록이 이미 있는 차량일 때
                    int timeRemain = resultMap.get(record[1]); // 남아 있던 시간 저장
                    resultMap.put(record[1], time - timeIn + timeRemain); // 원래 남아 있던 시간 + 사용한 시간
                }
                else {
                    resultMap.put(record[1], time - timeIn); // 차량이 사용한 시간 저장
                }
                tempMap.remove(record[1]); // 주차장에 주차 되어 있던 차량 빼기
            }
        }
        if (!tempMap.isEmpty()) { // 주차장이 비어 있지 않을 때
            for (String key : tempMap.keySet()) { // 남은 차량 시간 정리
                int time = 1439 - tempMap.get(key);
                resultMap.put(key, time + resultMap.get(key));
            }
        }

        int[] answer = new int[resultMap.size()];
        int index = 0;
        for (String key : resultMap.keySet()) {
            int time = resultMap.get(key);
            if (time &lt; defaultTime) { // 사용 시간이 기본 시간보다 적을 때
                answer[index++] = baseFee;
            }
            else {
                if ((time - defaultTime) % unitTime != 0) {
                    answer[index++] = baseFee + (((time - defaultTime) / unitTime) + 1) * unitFee;
                }
                else {
                    answer[index++] = baseFee + ((time - defaultTime) / unitTime) * unitFee;    
                }
            }
        }
        return answer;
    }
}</code></pre>
<h3 id="2차-실행-오류">2차 실행 오류</h3>
<hr>
<p>차량 번호 순서대로 오름차순 정렬하여 출력해야 함</p>
<hr>
<pre><code class="language-java">import java.util.Map;
import java.util.HashMap;

class Solution {
    public int[] solution(int[] fees, String[] records) {
        Map&lt;String, Integer&gt; tempMap = new HashMap&lt;&gt;(); // 현재 주차장에 남아 있는 차량
        Map&lt;String, Integer&gt; resultMap = new HashMap&lt;&gt;(); // 차량이 사용한 총 시간

        int defaultTime = fees[0];
        int baseFee = fees[1];
        int unitTime = fees[2];
        int unitFee = fees[3];

        for (int i = 0; i &lt; records.length; i++) {
            String[] record = records[i].split(&quot; &quot;); // 시간, 차량번호, 출입 내역 나누기
            String[] parts = record[0].split(&quot;:&quot;); // 시간과 분으로 나누기
            int time = Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]); // 시간과 분을 분으로 변환

            if (record[2].equals(&quot;IN&quot;)) { // 차량이 들어갈 때
                tempMap.put(record[1], time); // 차량번호와 들어간 시간 저장
            }
            else { // 차량이 나올 때
                int timeIn = tempMap.get(record[1]); // 차량이 들어간 시간
                if (resultMap.get(record[1]) != null) { // 출입 기록이 이미 있는 차량일 때
                    int timeRemain = resultMap.get(record[1]); // 남아 있던 시간 저장
                    resultMap.put(record[1], time - timeIn + timeRemain); // 원래 남아 있던 시간 + 사용한 시간
                }
                else {
                    resultMap.put(record[1], time - timeIn); // 차량이 사용한 시간 저장
                }
                tempMap.remove(record[1]); // 주차장에 주차 되어 있던 차량 빼기
            }
        }
        if (!tempMap.isEmpty()) { // 주차장이 비어 있지 않을 때
            for (String key : tempMap.keySet()) { // 남은 차량 시간 정리
                int time = 1439 - tempMap.get(key);
                if (resultMap.get(key) != null) {
                    resultMap.put(key, time + resultMap.get(key));
                }
                else {
                    resultMap.put(key, time);
                }
            }
        }

        int[] answer = new int[resultMap.size()];
        int index = 0;
        for (String key : resultMap.keySet()) {
            int time = resultMap.get(key);
            if (time &lt; defaultTime) { // 사용 시간이 기본 시간보다 적을 때
                answer[index++] = baseFee;
            }
            else {
                if ((time - defaultTime) % unitTime != 0) {
                    answer[index++] = baseFee + (((time - defaultTime) / unitTime) + 1) * unitFee;
                }
                else {
                    answer[index++] = baseFee + ((time - defaultTime) / unitTime) * unitFee;    
                }
            }
        }
        return answer;
    }
}</code></pre>
<h3 id="나의-코드">나의 코드</h3>
<h4 id="소요-시간-58분">소요 시간: 58분</h4>
<h4 id="시간-복잡도-on">시간 복잡도: $O(n)$</h4>
<hr>
<p><code>HashMap</code>과 <code>key</code> 값을 기준으로 오름차순 정렬하는 <code>TreeMap</code>을 사용하여 해결
코드를 작성하기 전 코드 설계부터 하니 이전과 비교했을 때, 
확실히 오류가 적어졌음.</p>
<p>다만 여전히 설계를 처음부터 끝까지 꼼꼼히 하는 습관과 
문제를 꼼꼼히 읽는 습관이 필요함</p>
<hr>
<pre><code class="language-java">import java.util.Map;
import java.util.HashMap;
import java.util.TreeMap;

class Solution {
    public int[] solution(int[] fees, String[] records) {
        Map&lt;String, Integer&gt; tempMap = new HashMap&lt;&gt;(); // 현재 주차장에 남아 있는 차량
        Map&lt;String, Integer&gt; resultMap = new TreeMap&lt;&gt;(); // 차량이 사용한 총 시간

        int defaultTime = fees[0];
        int baseFee = fees[1];
        int unitTime = fees[2];
        int unitFee = fees[3];

        for (int i = 0; i &lt; records.length; i++) {
            String[] record = records[i].split(&quot; &quot;); // 시간, 차량번호, 출입 내역 나누기
            String[] parts = record[0].split(&quot;:&quot;); // 시간과 분으로 나누기
            int time = Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]); // 시간과 분을 분으로 변환

            if (record[2].equals(&quot;IN&quot;)) { // 차량이 들어갈 때
                tempMap.put(record[1], time); // 차량번호와 들어간 시간 저장
            }
            else { // 차량이 나올 때
                int timeIn = tempMap.get(record[1]); // 차량이 들어간 시간
                if (resultMap.get(record[1]) != null) { // 출입 기록이 이미 있는 차량일 때
                    int timeRemain = resultMap.get(record[1]); // 남아 있던 시간 저장
                    resultMap.put(record[1], time - timeIn + timeRemain); // 원래 남아 있던 시간 + 사용한 시간
                }
                else {
                    resultMap.put(record[1], time - timeIn); // 차량이 사용한 시간 저장
                }
                tempMap.remove(record[1]); // 주차장에 주차 되어 있던 차량 빼기
            }
        }
        if (!tempMap.isEmpty()) { // 주차장이 비어 있지 않을 때
            for (String key : tempMap.keySet()) { // 남은 차량 시간 정리
                int time = 1439 - tempMap.get(key);
                if (resultMap.get(key) != null) {
                    resultMap.put(key, time + resultMap.get(key));
                }
                else {
                    resultMap.put(key, time);
                }
            }
        }

        int[] answer = new int[resultMap.size()];
        int index = 0;
        for (String key : resultMap.keySet()) {
            int time = resultMap.get(key);
            if (time &lt; defaultTime) { // 사용 시간이 기본 시간보다 적을 때
                answer[index++] = baseFee;
            }
            else {
                if ((time - defaultTime) % unitTime != 0) {
                    answer[index++] = baseFee + (((time - defaultTime) / unitTime) + 1) * unitFee;
                }
                else {
                    answer[index++] = baseFee + ((time - defaultTime) / unitTime) * unitFee;    
                }
            }
        }
        return answer;
    }
}</code></pre>
<h3 id="ai-코드">AI 코드</h3>
<h4 id="시간-복잡도-on-1">시간 복잡도: $O(n)$</h4>
<hr>
<p>기존 <code>if/else</code> 문으로 <code>null</code> 값을 파악하던 방법과 달리,
<code>getOrDefault()</code>를 이용하여 한 줄로 해결</p>
<hr>
<pre><code class="language-java">import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

class Solution {
    public int[] solution(int[] fees, String[] records) {
        Map&lt;String, Integer&gt; tempMap = new HashMap&lt;&gt;();
        Map&lt;String, Integer&gt; resultMap = new TreeMap&lt;&gt;();

        for (String record : records) {
            String[] parts = record.split(&quot; &quot;);
            String[] timeParts = parts[0].split(&quot;:&quot;);
            int time = Integer.parseInt(timeParts[0]) * 60 + Integer.parseInt(timeParts[1]);
            String carNum = parts[1];

            if (parts[2].equals(&quot;IN&quot;)) {
                tempMap.put(carNum, time);
            } else {
                resultMap.put(carNum, resultMap.getOrDefault(carNum, 0) + time - tempMap.remove(carNum));
            }
        }

        for (String key : tempMap.keySet()) {
            resultMap.put(key, resultMap.getOrDefault(key, 0) + 1439 - tempMap.get(key));
        }

        int[] answer = new int[resultMap.size()];
        int idx = 0;
        for (int time : resultMap.values()) {
            int fee = fees[1];
            if (time &gt; fees[0]) {
                fee += (int) Math.ceil((double)(time - fees[0]) / fees[2]) * fees[3];
            }
            answer[idx++] = fee;
        }

        return answer;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스킬트리]]></title>
            <link>https://velog.io/@hi_soap/%EC%8A%A4%ED%82%AC%ED%8A%B8%EB%A6%AC</link>
            <guid>https://velog.io/@hi_soap/%EC%8A%A4%ED%82%AC%ED%8A%B8%EB%A6%AC</guid>
            <pubDate>Mon, 22 Jun 2026 06:55:29 GMT</pubDate>
            <description><![CDATA[<p><strong>2026.06.22</strong></p>
<h2 id="문제-설명">문제 설명</h2>
<p>선행 스킬이란 어떤 스킬을 배우기 전에 먼저 배워야 하는 스킬을 뜻합니다.</p>
<p>예를 들어 선행 스킬 순서가 스파크 → 라이트닝 볼트 → 썬더일때, 썬더를 배우려면 먼저 라이트닝 볼트를 배워야 하고, 라이트닝 볼트를 배우려면 먼저 스파크를 배워야 합니다.</p>
<p>위 순서에 없는 다른 스킬(힐링 등)은 순서에 상관없이 배울 수 있습니다. 따라서 스파크 → 힐링 → 라이트닝 볼트 → 썬더와 같은 스킬트리는 가능하지만, 썬더 → 스파크나 라이트닝 볼트 → 스파크 → 힐링 → 썬더와 같은 스킬트리는 불가능합니다.</p>
<p>선행 스킬 순서 skill과 유저들이 만든 스킬트리1를 담은 배열 skill_trees가 매개변수로 주어질 때, 가능한 스킬트리 개수를 return 하는 solution 함수를 작성해주세요.</p>
<h2 id="제한-조건">제한 조건</h2>
<ul>
<li>스킬은 알파벳 대문자로 표기하며, 모든 문자열은 알파벳 대문자로만 이루어져 있습니다.</li>
<li>스킬 순서와 스킬트리는 문자열로 표기합니다.</li>
<li><ul>
<li>예를 들어, C → B → D 라면 &quot;CBD&quot;로 표기합니다</li>
</ul>
</li>
<li>선행 스킬 순서 skill의 길이는 1 이상 26 이하이며, 
스킬은 중복해 주어지지 않습니다.</li>
<li>skill_trees는 길이 1 이상 20 이하인 배열입니다.</li>
<li>skill_trees의 원소는 스킬을 나타내는 문자열입니다.</li>
<li><ul>
<li>skill_trees의 원소는 길이가 2 이상 26 이하인 문자열이며, 스킬이 중복해 주어지지 않습니다.</li>
</ul>
</li>
</ul>
<h2 id="입출력-예">입출력 예</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/fc2fae34-6de9-4076-bb29-0af9dde18048/image.png" alt=""></p>
<h2 id="문제-풀이">문제 풀이</h2>
<h3 id="나의-코드">나의 코드</h3>
<h4 id="소요-시간-29분">소요 시간: 29분</h4>
<h4 id="시간-복잡도-on">시간 복잡도: $O(n)$</h4>
<hr>
<p><code>HashSet</code>을 이용하여 해당 스킬이 순서가 필요한 스킬인지 판별</p>
<p>판별 후 현재 찍을 수 있는 상태인지 다시 판별하고 가능하면 넘어가고,
불가능하다면 반복문 중지</p>
<hr>
<pre><code class="language-java">import java.util.Set;
import java.util.HashSet;

class Solution {
    public int solution(String skill, String[] skill_trees) {
        int count = 0;
        Set&lt;Character&gt; set = new HashSet&lt;&gt;();
        char skill_order[] = new char[skill.length()];

        for (int i = 0; i &lt; skill.length(); i++) {
            set.add(skill.charAt(i));
        }

        for (int i = 0; i &lt; skill_trees.length; i++) {
            int index = 0;
            boolean isPossible = true;
            for (int j = 0; j &lt; skill_trees[i].length(); j++) {
                char c = skill_trees[i].charAt(j);
                if (set.contains(c)) { // 스킬 트리에 존재하는 스킬인 경우
                    if (skill.charAt(index) == c) { // 현재 배우는 것이 가능할 때
                        index++;
                    }
                    else { // 현재 배우는 것이 불가능할 
                        isPossible = false;
                        break;
                    }
                }
            }
            if (isPossible) {
                count++;
            }
        }
        return count;
    }
}</code></pre>
<h3 id="ai-코드">AI 코드</h3>
<h4 id="시간-복잡도-on-1">시간 복잡도: $O(n)$</h4>
<hr>
<p><code>HashSet</code>을 사용하지 않고,</p>
<p>해당 값이 존재한다면 해당 인덱스를, 존재하지 않는다면 <code>-1</code>을 
리턴하는 문자열에서 특정 문자의 인덱스를 반환하는 메서드인 
<code>indexOf()</code>를 사용함</p>
<hr>
<pre><code class="language-java">class Solution {
    public int solution(String skill, String[] skill_trees) {
        int count = 0;

        for (String tree : skill_trees) {
            int index = 0;
            boolean isPossible = true;
            for (char c : tree.toCharArray()) {
                if (skill.indexOf(c) != -1) {
                    if (skill.charAt(index) == c) {
                        index++;
                    } else {
                        isPossible = false;
                        break;
                    }
                }
            }
            if (isPossible) count++;
        }
        return count;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[교내 공학 경진대회]]></title>
            <link>https://velog.io/@hi_soap/%EA%B5%90%EB%82%B4-%EA%B3%B5%ED%95%99-%EA%B2%BD%EC%A7%84%EB%8C%80%ED%9A%8C</link>
            <guid>https://velog.io/@hi_soap/%EA%B5%90%EB%82%B4-%EA%B3%B5%ED%95%99-%EA%B2%BD%EC%A7%84%EB%8C%80%ED%9A%8C</guid>
            <pubDate>Mon, 22 Jun 2026 06:14:05 GMT</pubDate>
            <description><![CDATA[<p><strong>2026.06.22</strong></p>
<h2 id="문제-정의">문제 정의</h2>
<h3 id="1-실내-길찾기-서비스">1. 실내 길찾기 서비스</h3>
<ul>
<li>wi-fi 핑거프린팅 활용</li>
<li>local LLM을 활용한 부가 기능 개발</li>
<li>장애인을 위한 추가적인 계단 회피 경로</li>
</ul>
<h3 id="2-코골이-베개">2. 코골이 베개</h3>
<ul>
<li>라즈베리 파이 센서 활용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[인터넷연결]]></title>
            <link>https://velog.io/@hi_soap/%EC%9D%B8%ED%84%B0%EB%84%B7%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@hi_soap/%EC%9D%B8%ED%84%B0%EB%84%B7%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Fri, 12 Jun 2026 04:16:31 GMT</pubDate>
            <description><![CDATA[<h2 id="학습-목표">학습 목표</h2>
<ul>
<li>인터넷 연결에 필요한 권한을 이해하고 사용할 수 있다.</li>
<li>네트워크 연결 상태를 확인하는 방법을 이해하고 구현할 수 있다.</li>
<li>자바 API를 사용하여 네트워크 프로그래밍을 할 수 있다.</li>
<li>Retrofit을 사용하여 프로그래밍할 수 있다.</li>
<li>DownloadManager를 이용하여 파일 다운로드를 만들 수 있다.</li>
</ul>
<h2 id="네트워크-프로그래밍-용어-확인">네트워크 프로그래밍 용어 확인</h2>
<ul>
<li>UDP, TCP, HTTP, HTTPS</li>
<li>IP주소, 포트(port) 번호</li>
<li>서버, 클라이언트</li>
<li>소켓 프로그래밍</li>
<li>Listen, accept, connect*</li>
</ul>
<h2 id="안드로이드에서-네트워크-프로그래밍-방법">안드로이드에서 네트워크 프로그래밍 방법</h2>
<ul>
<li><p>자바 소켓 API</p>
</li>
<li><p>TCP, UDP*</p>
</li>
<li><p>TCP, UDP상에서 바로 하기보다는 <span style="background-color: #dcffe4">응용 프로토콜 라이브러리</span> 사용</p>
</li>
<li><p>HTTP(S)를 사용하는 경우, 대부분 이 경우에 해당됨</p>
</li>
<li><p>자바 HTTP URL Connection
Retrofit 라이브러리 (OkHttp 기반)
Volley, Cronet 등도 있음*</p>
</li>
<li><p>MQTT를 사용한다면</p>
</li>
<li><p>paho, HiveMQ 등 라이브러리 사용*</p>
</li>
</ul>
<h2 id="권한-설정">권한 설정</h2>
<ul>
<li>안드로이드에서 네트워크에 접근하기 위한 권한 필요</li>
<li>설치시 부여되는 일반 권한이므로 <span style="background-color: #dcffe4">동적 권한 확인 코드는 불필요</span></li>
</ul>
<p><strong><code>AndroidManifest.xml</code></strong></p>
<pre><code class="language-xml">&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
          xmlns:tools=&quot;http://schemas.android.com/tools&quot;&gt;

&lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;
&lt;uses-permission android:name=&quot;android.permission.ACCESS_NETWORK_STATE&quot; /&gt;

&lt;application ...</code></pre>
<h2 id="http-사용할-경우">HTTP 사용할 경우</h2>
<ul>
<li><p>HTTP와 HTTPS</p>
</li>
<li><p>HTTPS는 메시지를 <span style="background-color: #dcffe4">암호화</span>하여 전송*</p>
</li>
<li><p>HTTP는 메시지를 <span style="background-color: #dcffe4">그대로</span> 전송*</p>
</li>
<li><p>안드로이드에서 HTTP Url Connection API 사용할 때, <span style="background-color: #dcffe4">http는 기본적으로 <strong>금지</strong></span></p>
</li>
<li><p>http를 사용하기 위해서 Manifest의 application 태그 속성에</p>
</li>
<li><p><code>android:usesCleartextTraffic=&quot;true&quot;</code>를 추가*</p>
</li>
</ul>
<pre><code class="language-xml">&lt;application
   ...
   android:usesCeartextTraffic=&quot;true&quot;          
   ...
&gt;</code></pre>
<h2 id="네트워크-연결-상태">네트워크 연결 상태</h2>
<ul>
<li><p>현재 연결된 네트워크 종류나 인터넷 연결 가능 여부를 판단</p>
</li>
<li><p><code>ConnectivityManager.allNetworks</code>: <span style="background-color: #dcffe4">모든</span> 네트워크 <strong>IF</strong>를 리턴</p>
</li>
<li><p><code>ConnectivityManager.activeNetwork</code>: 현재 <span style="background-color: #dcffe4">활성화된</span> 네트워크 <strong>IF</strong></p>
</li>
</ul>
<pre><code class="language-kotlin">private fun isNetworkAvailable(): Boolean {
    val connectivityManager =
    getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val nw = connectivityManager.activeNetwork ?: return false // 현재 활성화된 네트워크
    val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false

    println(&quot;${actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)},
             ${actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)}, &quot;
             + &quot;${actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)}&quot;)

    return actNw.hasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    // 인터넷 가능 여부
}</code></pre>
<ul>
<li><p><code>hasTransport</code>: 네트워크의 <span style="background-color: #dcffe4">특정 전송 방식</span></p>
</li>
<li><p>Wi-Fi인지, 모바일 데이터인지*</p>
</li>
<li><p><code>hasCapabilities</code>: 네트워크의 <span style="background-color: #dcffe4">특정 기능</span></p>
</li>
<li><p>인터넷에 연결 가능한지*</p>
</li>
</ul>
<h2 id="자바-소켓-api">자바 소켓 API</h2>
<h3 id="networkrepository">NetworkRepository</h3>
<pre><code class="language-kotlin">class NetworkRepository {
    suspend fun fetchFromSocket(): String = withContext(Dispatchers.IO) {
        val sock = SocketFactory.getDefault().createSocket(&quot;google.com&quot;, 80)
        val istream = sock.getInputStream()
        val ostream = sock.getOutputStream()
        ostream.write(&quot;GET / \r\n&quot;.toByteArray())
        ostream.flush()
        val result = istream.readBytes().decodeToString()
        sock.close()
        result
    }
}</code></pre>
<p><code>withContext</code>(코루틴 컨텍스트, 코루틴)</p>
<ul>
<li>해당 코루틴 컨텍스트로 코루틴을 수행</li>
<li>여기에서는 <span style="background-color: #dcffe4">IO 담당 디스패쳐(스레드)</span>에서 수행하도록 함</li>
</ul>
<p><code>suspend</code></p>
<ul>
<li>네트워크나 파일 I/O같이 <span style="background-color: #dcffe4">블록 가능성</span>있는 API 호출할 때 
반드시 <span style="background-color: #dcffe4">비동기</span>로 호출하거나 <span style="background-color: #dcffe4">코루틴/스레드</span>에서 수행해야 함</li>
</ul>
<h3 id="viewmodel">ViewModel</h3>
<pre><code class="language-kotlin">class MyViewModel : ViewModel() {
    private val repository = NetworkRepository()

    private val _response = MutableStateFlow(&quot;&quot;)
    val response: StateFlow&lt;String&gt; = _response.asStateFlow()

    private val _responseBy = MutableStateFlow(&quot;&quot;)
    val responseBy: StateFlow&lt;String&gt; = _responseBy.asStateFlow()

    fun refreshJavaSocket() {
        viewModelScope.launch {
            try {
                val result = repository.fetchFromSocket()
                _responseBy.value = &quot;Java Socket : Succeeded to connect to the server&quot;
                _response.value = result
            } catch (e: Exception) {
                _responseBy.value = &quot;Java Socket&quot;
                _responseBy.value = &quot;Failed to connect to the server ${e.message}&quot;
            }
        }
    }
}</code></pre>
<h2 id="https-url-connection-api">HTTP(s) URL Connection API</h2>
<ul>
<li><p>java에서 제공하는 HttpURLConnection, HttpsURLConnection</p>
</li>
<li><p>HTTP(s) 프로토콜을 손쉽게 사용할 수 있는 API*</p>
</li>
<li><p>HTTP 사용 시 <code>Manifest</code> 파일의 <code>application</code> 태그에
<code>android:usesCleartextTraffic=&quot;true&quot;</code></p>
</li>
</ul>
<h3 id="networkrepository-1">NetworkRepository</h3>
<ul>
<li>이미지 URL의 데이터 수신 후 Bitmap으로 변환</li>
</ul>
<pre><code class="language-kotlin">class NetworkRepository {
    suspend fun fetchBitmapHttps(url: String):Bitmap? 
    = withContext(Dispatcher.IO) {
        val conn = URL(url).openConnection() as HttpsURLConnection
        try {
            val istream = conn.inputStream
            BitmapFactory.decodeStream(istream)
        } finally {
            conn.disconnect()
        }
    }
}</code></pre>
<h3 id="viewmodel-1">ViewModel</h3>
<pre><code class="language-kotlin">class MyViewModel : ViewModel() {
    private val repository = NetworkRepository()

    private val _response = MutableStateFlow(&quot;&quot;)
    val response: StateFlow&lt;String&gt; = _response.asStateFlow()

    private val _responseBy = MutableStateFlow(&quot;&quot;)
    val responseBy: StateFlow&lt;String&gt; = _responseBy.asStateFlow()

    private val _responseImg = MutableStateFlow&lt;Bitmap?&gt;(null)
    val responseImg: StateFlow&lt;Bitmap?&gt; = _responseImg.asStateFlow()

    fun refreshBitmapByHttpsLib(url: String) {
        viewModelScope.launch {
            try {
                val bitmap = repository.fetchBitmapHttps(url)
                _responseBy.value = &quot;HttpsURLConnection&quot;
                _response.value = &quot;Succeeded to download the image&quot;
                _responseImg.value = bitmap
            } catch(e: Exception) {
                _responseBy.value = &quot;HttpsURLConnection&quot;
                _responseBy.value = &quot;Failed to download the image: ${e.message}&quot;
            }
        }
    }
}</code></pre>
<h2 id="bitmap을-파일로-저장">Bitmap을 파일로 저장</h2>
<ul>
<li><code>ContentResolver</code>를 이용하여 MediaStore에 이미지 저장<pre><code class="language-kotlin">private fun saveBitmap(bitmap: Bitmap, fileName : String) {
  val collection = if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.Q) {
      MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERAL)
  } else {
      MediaStore.Images.Media.EXTERNAL_CONTENT_URI
  }
  val contentValues = ContentValues().apply {
      put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
  }
  val destUri = contentResolver.insert(collection, contentValues) ?: return
  contentResolver.openOutputStream(destUri)?.use {
      bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
  }
}</code></pre>
</li>
</ul>
<h2 id="retrofit">Retrofit</h2>
<ul>
<li><p>HTTP 연결을 통해 받은 JSON 등의 구조체 데이터를 
알아서 클래스 객체로 만들어줌</p>
</li>
<li><p>HTTP body 내용이 JSON, 일반 문자열, XML 등에 따라 
변환기를 선택 사용 가능*</p>
</li>
<li><p>프로그래머는 필요한 인터페이스와 데이터 클래스만 정의하면 
구현은 Retrofit이 알아서 함</p>
</li>
<li><p>build.gradle(모듈)</p>
<pre><code class="language-kotlin">dependencies {
  implementation(&quot;com.squareup.retrofit2:retrofit:3.0.0&quot;)
  implementation(&quot;com.squareup.retrofit2:converter-gson:3.0.0&quot;) 
  // Json용 변환기
}</code></pre>
</li>
</ul>
<h3 id="interface-정의">Interface 정의</h3>
<ul>
<li><p>Retrofit에서 자동으로 만들어주는 REST API 정의</p>
</li>
<li><p>@GET(경로명) annotation 사용</p>
</li>
<li><p>경로명에 {이름}으로 된 것은 메소드의 인자로 치환됨*</p>
</li>
<li><p>치환되는 인자의 앞에 @Path(&quot;이름&quot;) annotation 사용*</p>
</li>
<li><p>리턴 값은 </p>
</li>
<li><p><span style="background-color: #dcffe4">Call＜타입＞</span>을 리턴받아 콜백으로 응답을 받거나,</p>
</li>
<li><p><em><code>suspend</code>**를 붙여서 해당 타입 객체를 바로 리턴 받는 방법이 가능</em></p>
<pre><code class="language-kotlin">interface RestApi {
   @GET(&quot;users/{user}/repos&quot;)
   fun listReposCall(@Path(&quot;user&quot;) user: String): Call&lt;List&lt;Repos&gt;&gt;

   @GET(&quot;users{user}/repos&quot;)
   suspend fun listRepos(@Path(&quot;user&quot;)user: String): List&lt;Repo&gt;
}</code></pre>
<h3 id="data-class-정의">Data class 정의</h3>
<pre><code class="language-kotlin">data class Owner(val login: String)
data class Repo(val name: String, val owner: Owner, val url: String)
</code></pre>
</li>
</ul>
<p>interface RestApi {
    @GET(&quot;users/{user}/repos&quot;)
    suspend fun listRepos(@Path(&quot;user&quot;)user: String): List<Repo>
}</p>
<pre><code>
### JSON
- NetworkRepository
- RestApi 객체 생성
*JSON 변환기 지정*

```kotlin
class NetworkRepository {
    private val baseURL = &quot;https://api.github.com&quot;
    private val api: RestAi = with(Retrofit.Builder()) {
        baseUrl(baseURL)
        addConverterFactory(GsonConverterFactory.create()) // JSON 변환기 지정
        build()
    }.create(RestApi::class.java)

    suspend fun fetchRepos(userName: String): List&lt;Repo&gt; {
        return api.listRepos(userName)
    }
}</code></pre><h3 id="viewmodel-2">ViewModel</h3>
<pre><code class="language-kotlin">class MyViewModel : ViewModel() {
    private val repository = NetworkRepository()
    private val _response = MutableStateFlow(&quot;&quot;)
    val response: StateFlow&lt;String&gt; = _response.asStateFlow()
    private val _responseBy = MutableStateFlow(&quot;&quot;)
    val responseBy: StateFlow&lt;String&gt; = _responseBy.asStateFlow()

    fun refreshRetrofit(userName: String) {
        viewModelScope.launch {
            try {
                val repos = repository.fetchRepos(userName)
                _responseBy.value = &quot;retrofit : ${repos.size} repositories&quot;
                _response.value = StringBuilder().apply {
                    repos.forEach {
                        append(it.name)
                        append(&quot; - &quot;)
                        append(it.owner.login)
                        append(&quot;\n&quot;)
                    }
                }.toString()
            } catch (e: Exception)
        }
    }
}</code></pre>
<h2 id="download-manager">Download Manager</h2>
<ul>
<li>시스템에서 제공하는 다운로드 기능</li>
<li>URL과 다운로드 위치만 알려주면 알아서 알림도 표시하고 다운로드 수행</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/f8489dd2-2fe4-46fe-b780-e08ed84b7a86/image.png" alt=""></p>
<ul>
<li>다운로드 완료 방송 받는 방송 수신자
<code>DownloadManager.ACTION_DOWNLOAD_COMPLETE</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/f3e88214-03ad-4f2b-988d-5d1ca3d642bd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker]]></title>
            <link>https://velog.io/@hi_soap/Docker</link>
            <guid>https://velog.io/@hi_soap/Docker</guid>
            <pubDate>Wed, 03 Jun 2026 09:30:13 GMT</pubDate>
            <description><![CDATA[<h2 id="도커docker">도커(Docker)</h2>
<ul>
<li><p>애플리케이션과 <span style="background-color: #dcffe4"><strong>실행에 필요한 모든 요소</strong>를 </p>
</li>
<li><p><em>하나의 표준 단위(컨테이너)*</em>로 묶는 플랫폼</span></p>
</li>
<li><p>컨테이너 = 코드 + 런타임 + 라이브러리 + 시스템 도구 + 설정</p>
</li>
<li><p>리눅스 커널의 네임스페이스/cgroup 기술로 
프로세스를 격리해 가볍고 빠르게 실행</p>
</li>
<li><p><strong>핵심 이점</strong>
환경 일관성, 빠른 기동 시간, 높은 이식성, 대규모 운영의 자동화</p>
</li>
</ul>
<h3 id="도커의-필요성">도커의 필요성</h3>
<ul>
<li>개발 노트북, 테스트/운영 서버의 OS 버전, 의존성이 서로 다름</li>
<li>라이브러리 버전 충돌, 운영체제 의존 도구 누락, 시스템 환경변수 차이</li>
<li>도커는 애플리케이션 전체를 이미지로 고정 
→ <span style="background-color: #dcffe4">어디에서 실행해도 동일 환경 보장</span></li>
</ul>
<h3 id="가상-머신vm-vs-컨테이너">가상 머신(VM) vs 컨테이너</h3>
<p><strong>VM</strong></p>
<ul>
<li>게스트 OS 전체를 가상화 → 무겁고(GB), 부팅에 분 단위 소요</li>
</ul>
<p><strong>컨테이너</strong></p>
<ul>
<li>호스트 커널을 공유, 앱과 라이브러리만 격리 → 수십 MB, 수 초 내 기동</li>
</ul>
<hr>
<ul>
<li>동일 하드웨어에서 VM은 10대 수준, 컨테이너는 수백 개 까지 실행 가능</li>
<li>개발 속도, 자원 효율, 배포 빈도 등 모든 면에서 컨테이너가 유리</li>
</ul>
<h3 id="도커-아키텍처의-3대-요소">도커 아키텍처의 3대 요소</h3>
<p><strong>이미지(image)</strong>: 앱과 환경을 고려한 <span style="background-color: #dcffe4">읽기 전용 템플릿</span> - <strong>붕어빵 틀</strong></p>
<p><strong>컨테이너(container)</strong>: 이미지를 <span style="background-color: #dcffe4">실행 상태로 인스턴스화한 실제 프로세스</span> - <strong>붕어빵</strong></p>
<p><strong>레지스트리(Registry)</strong>: 이미지를 저장, 공유하는 원격 창고 (ex) 도커 허브)</p>
<p><strong>흐름</strong>: Dockerfile → build → 이미지 → push 
→ 레지스트리 → pull → run → 컨테이너</p>
<h3 id="실습-nodejs-예제-어플리케이션">실습: Node.js 예제 어플리케이션</h3>
<p><strong><code>index.js/package.json</code> - 두 파일을 같은 폴더에 준비</strong></p>
<pre><code class="language-javascript">// index.js — 3000번 포트에서 응답하는 최소 웹 서버
const http = require(&#39;http&#39;);
const server = http.createServer((req, res) =&gt; {
  res.end(&#39;Hello Docker World!&#39;);
});
server.listen(3000, () =&gt; console.log(&#39;3000번 포트 가동 중!&#39;));

// package.json — 시작 스크립트 정의
{
  &quot;name&quot;: &quot;docker-node-app&quot;,
  &quot;scripts&quot;: { &quot;start&quot;: &quot;node index.js&quot; }
}</code></pre>
<h3 id="도커파일dockerfile">도커파일(Dockerfile)</h3>
<ul>
<li><p><span style="background-color: #dcffe4">이미지를 어떻게 만들지</span> 단계별로 기술한 <span style="background-color: #dcffe4">평문 스크립트</span> - <strong>이미지의 설계도</strong></p>
</li>
<li><p>한 줄 한 줄이 <span style="background-color: #dcffe4">하나의 레이어(Layer)</span>로 쌓여 최종 이미지 구성</p>
</li>
<li><p>변경이 없는 레이어는 <span style="background-color: #dcffe4">캐시 재사용</span> → 빌드 시간이 극적으로 단축</p>
</li>
<li><p><span style="background-color: #dcffe4">공식 베이스 이미지(node, python, nginx 등)</span> 위에 내 앱을 얹는 방식이 일반적</p>
</li>
</ul>
<h4 id="dockerfile-상세-분석">Dockerfile 상세 분석</h4>
<pre><code class="language-yaml"># 1. 기반 이미지 설정 - Node.js 18이 설치된 리눅스 환경을 상속
FROM node:18
# 2. 작업 디렉토리 지정 - 컨테이너 내부 /app 폴더를 만들고 그리로 이동
WORKDIR /app
# 3. 소스 코드 복사 - 현재 폴더의 모든 파일을 /app 안으로 복사
COPY . .
# 4. 실행 명령어 정의 - 컨테이너 기동 시 자동으로 실행
CMD [&quot;npm&quot;, &quot;start&quot;]</code></pre>
<h4 id="주요-명령어-정리">주요 명령어 정리</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/be03db49-70af-475c-b125-5da820129f61/image.png" alt=""></p>
<h4 id="이미지-빌드---docker-build로-붕어빵-틀-만들기">이미지 빌드 - docker build로 붕어빵 틀 만들기</h4>
<ul>
<li><p><strong>명령</strong>: <code>docker build -t yourid/node-app</code> .</p>
</li>
<li><p><code>-t</code> 옵션: 이미지에 <code>[사용자/이미지이름: 태그]</code> 형태의 이름표 부착</p>
</li>
<li><p><code>.</code>: 빌드 컨텍스트 = Dockerfile이 위치한 현재 폴더</p>
</li>
<li><p><strong>확인</strong>: docker images로 방금 만든 이미지가 목록에 표시되는지 점검</p>
</li>
</ul>
<h4 id="도커-허브를-통한-전-세계-배포-워크플로우">도커 허브를 통한 전 세계 배포 워크플로우</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/6f87f03a-ff69-47b9-b733-8fc24673f757/image.png" alt=""></p>
<h3 id="도커-컴포즈docker-compose">도커 컴포즈(Docker Compose)</h3>
<ul>
<li><p>여러 개의 컨테이너를 하나의 설정으로 정의/실행하는
<span style="background-color: #dcffe4">다중 컨테이너 오케스트레이션 도구</span></p>
</li>
<li><p><strong>설정 파일</strong>: <code>docker-compose.yaml</code> (YAML 형식)</p>
</li>
<li><p>한 줄 명령 <code>docker-compose up</code>으로 <span style="background-color: #dcffe4">모든 서비스와 네트워크를 일괄 기동</span></p>
</li>
<li><p>각 서비스 간 의존 순서, 네트워크, 볼륨, 환경변수를 선언적으로 기술</p>
</li>
</ul>
<h4 id="도커-컴포즈가-필요한-이유">도커 컴포즈가 필요한 이유</h4>
<ul>
<li><p>도커 단독 사용 시, <span style="background-color: #dcffe4">컨테이너마다 <code>docker run</code>을 반복</span>하고
포트, 네트워크, 옵션을 매번 기억해야 함</p>
</li>
<li><p>수 개 ~ 수십 개 서비스가 얽힌 환경에서 <span style="background-color: #dcffe4">수동 실행</span>은 실수와 누락의 온상</p>
</li>
<li><p>컴포즈: 전체 토폴로지를 YAML로 버전 관리 
→ 팀원 모두 동일한 구성으로 재현</p>
</li>
<li><p>개발/테스트용 로컬 환경을 한 명령(<code>up</code>)으로 초기화하고
한 명령(<code>down</code>)으로 정리</p>
</li>
</ul>
<h4 id="실습-docker-composeyaml-작성">실습: docker-compose.yaml 작성</h4>
<p><strong>웹 서버 + 레디스 구성 예시</strong></p>
<pre><code class="language-yaml">version: &#39;3.8&#39;               # 1. 컴포즈 파일 규격 버전
services:                    # 2. 실행할 서비스들의 묶음
  web-app:                   # 3. 웹 서버 서비스 정의
    build: .                 # 4. 현재 폴더의 Dockerfile 로 빌드
    ports:                   # 5. 포트 연결 설정
      - &quot;8080:3000&quot;
    depends_on:              # 6. 실행 의존성 정의
      - redis-db             # 7. redis-db가 먼저 켜져야 함
  redis-db:                  # 8. 레디스 서비스 정의
    image: &quot;redis:alpine&quot;    # 9. 공식 경량 이미지 사용</code></pre>
<h4 id="라인별-해석">라인별 해석</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/fe8f2d4c-7632-43b5-b93d-687be7ffee9c/image.png" alt=""></p>
<h3 id="depends-on-실행-순서의-유무-비교">depends-on 실행 순서의 유무 비교</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/75778302-ed65-4b60-9dae-7e3f1e898987/image.png" alt=""></p>
<ul>
<li><code>depends-on</code>은 <span style="background-color: #dcffe4">컨테이너 기동 순서</span>를 보장하는 가이드라인</li>
<li>레디스 컨테이너가 먼저 올라온 뒤에야 웹 서비스가 실행됨</li>
<li>내부 애플리케이션이 <span style="background-color: #dcffe4">완전히 준비(ready) 된 상태까지는 보장하지 않음</span></li>
<li><strong>헬스체크 병행 권장*</strong></li>
</ul>
<h3 id="컴포즈는-서비스-이름만으로-통신함">컴포즈는 서비스 이름만으로 통신함</h3>
<pre><code class="language-javascript">// web-app 컨테이너 내부의 Node.js 코드
const redis = require(&#39;redis&#39;);
// 과거방식 컨테이너 IP를 직접 적어야 했던 시대
// const client = redis.createClient({ host: &#39;172.18.0.3&#39; });

// 컴포즈방식 yml에 적은 서비스 이름을 그대로 주소로
const client = redis.createClient({ host: &#39;redis-db&#39; });
// 도커 내부 DNS가 &#39;redis-db&#39;를 자동으로 해석해 연결</code></pre>
<h3 id="도커-컴포즈-핵심-명령어">도커 컴포즈 핵심 명령어</h3>
<p><strong><code>docker-compose up-d</code></strong>: 설계도대로 모든 서비스를 백그라운드로 기동</p>
<p><strong><code>docker-compose down</code></strong>: 컨테이너, 네트워크, 볼륨을 한 번에 정리</p>
<p><strong><code>docker-compose ps</code></strong>: 현재 실행 중인 서비스 상태 조회</p>
<p><strong><code>docker-compose logs -f [서비스명]</code></strong>: 실시간 로그 스트리밍으로 디버깅</p>
<p><strong><code>docker-compose build</code></strong>: 코드 변경 후 이미지만 다시 빌드</p>
<h3 id="마무리">마무리</h3>
<ul>
<li><p>도커는 앱과 환경을 <span style="background-color: #dcffe4">이미지</span>로 고정해 <span style="background-color: #dcffe4">어디서나 동일하게</span>를 보장함</p>
</li>
<li><p>도커파일의 <span style="background-color: #dcffe4">네 줄 레시피</span>만으로 나만의 실행 환경을 재현 가능하게 만듦</p>
</li>
<li><p><span style="background-color: #dcffe4"><code>빌드 → 푸시 → 풀 → 런</code> 4단계</span>로 전 세계 어디든 배포 가능</p>
</li>
<li><p>도커 컴포즈는 YAML 한 장으로 다중 컨테이너 시스템을 한 번에 지휘함</p>
</li>
<li><p>서비스 이름과 <code>depedns-on</code> 만으로 컨테이너 간 통신/실행 순서를 해결</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Playwright - E2E]]></title>
            <link>https://velog.io/@hi_soap/Playwright-E2E</link>
            <guid>https://velog.io/@hi_soap/Playwright-E2E</guid>
            <pubDate>Wed, 03 Jun 2026 08:46:29 GMT</pubDate>
            <description><![CDATA[<h2 id="playwright">Playwright</h2>
<ul>
<li><p>마이크로소프트가 개발한 <span style="background-color: #dcffe4">오픈소스 웹 자동화 라이브러리</span></p>
</li>
<li><p><strong>단일 코드 베이스</strong>
하나의 코드로 모든 브라우저 제어</p>
</li>
<li><p><strong>엔진 지원</strong>
크롬, 파이어폭스, 사파리 완벽 지원</p>
</li>
<li><p><strong>다국어 지원</strong>
TypeScript JavaScript, Python, Java, .NET</p>
</li>
</ul>
<h3 id="playwright-vs-selenium">Playwright vs Selenium</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/acd81365-9341-4a3c-92d7-8de9202ac291/image.png" alt=""></p>
<h2 id="e2e-test">E2E Test</h2>
<ul>
<li><p>사용자가 <span style="background-color: #dcffe4">실제로</span> 애플리케이션을 사용하는 처음부터 끝까지의 
<span style="background-color: #dcffe4">흐름 전체를 확인하는 테스트</span></p>
</li>
<li><p>개별 함수가 아닌 <span style="background-color: #dcffe4">사용자 관점</span>에서의 시나리오 검증</p>
</li>
<li><p>ex) 회원가입 → 로그인 → 상품 검색 → 장바구니 → 결제 완료</p>
</li>
<li><p>전체 서비스가 <span style="background-color: #dcffe4">실제 환경</span>에서 문제없이 동작하는지 보장</p>
</li>
</ul>
<h3 id="설치-및-프로젝트-시작">설치 및 프로젝트 시작</h3>
<p><strong>사전 요구사항</strong>: <code>Node.js 16</code> 버전 이상 설치</p>
<p><strong>버전 확인 명령어</strong>: <code>node -v</code></p>
<p><strong>설치 명령어</strong>: <code>npm init playwright@latest</code></p>
<p>설치 과정에서 <code>TypeScript/JavaScript</code> 선택, 예제 테스트 자동 생성</p>
<h3 id="프로젝트-구조-및-설정-파일">프로젝트 구조 및 설정 파일</h3>
<p><strong><code>tests/</code></strong>: 테스트 코드 파일을 저장하는 폴더</p>
<p><strong><code>playwright.config.ts</code></strong>: 전체 실행 환경 설정 파일</p>
<p><strong>핵심 옵션</strong></p>
<ul>
<li><strong>retries</strong>: 실패 시 재시도 횟수</li>
<li><strong>headless</strong>: 브라우저 창을 띄울지 여부</li>
</ul>
<p><strong>projects</strong>: 크롬, 파이어폭스, 웹킷 등 <span style="background-color: #dcffe4">브라우저 별 실행 설정</span></p>
<h3 id="사례-네이버-검색-테스트-코드">사례: 네이버 검색 테스트 코드</h3>
<p><strong>시나리오: 네이버 접속 → 검색창에 &quot;Playwright&quot; 입력</strong></p>
<pre><code class="language-javascript">import { test, expect } from &#39;@playwright/test&#39;;
test(&#39;  네이버 검색테스트&#39;, async ({ page }) =&gt; {
  // 1. 네이버 사이트로 이동
  await page.goto(&#39;https://www.naver.com&#39;);

  // 2. 검색창(#query) 에 &#39;Playwright&#39; 입력
  await page.fill(&#39;#query&#39;, &#39;Playwright&#39;);</code></pre>
<p><strong>시나리오: 엔터 키 입력 → 결과 페이지 제목 확인</strong></p>
<pre><code class="language-javascript">  // 3. 엔터 키 눌러서 검색 실행
  await page.press(&#39;#query&#39;, &#39;Enter&#39;);
  // 4. 페이지 제목에 &#39;Playwright&#39;   가 있는지 확인
  await expect(page).toHaveTitle(/Playwright/);
});</code></pre>
<h3 id="셀렉터selector와-html">셀렉터(Selector)와 HTML</h3>
<p><strong>셀렉터(selector)</strong>: 웹 페이지 내 특정 요소를 가리키는 주소</p>
<pre><code class="language-html">&lt;input id=&quot;query&quot; class=&quot;search_input&quot; name=&quot;q&quot;&gt;
&lt;button class=&quot;btn_search&quot;&gt;검색&lt;/button&gt;

// CSS 선택자
#query        // id  로 찾기
.btn_search   // class  로 찾기</code></pre>
<h3 id="playwright-로케이터locator">Playwright 로케이터(Locator)</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/582d7665-7e4a-4a56-89c5-ab6e0f4514c1/image.png" alt=""></p>
<h3 id="로케이터를-써야-하는-이유">로케이터를 써야 하는 이유</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/4ee47427-b287-4a2f-aef7-7e048fd9ab84/image.png" alt=""></p>
<ul>
<li><p>깨지기 쉬운 CSS 경로 대신 <span style="background-color: #dcffe4">사용자 관점 셀렉터 이용</span></p>
</li>
<li><p><strong>유지보수 용이</strong>: 구조가 바뀌어도 버튼의 역할과 이름은 그대로이기 때문</p>
</li>
<li><p><strong>가독성 향상</strong>: 코드만 봐도 무엇을 테스트하는지 알 수 있음</p>
</li>
<li><p><strong>웹 접근성(Accessibility)</strong> 준수</p>
</li>
</ul>
<h3 id="플래키flaky-테스트">플래키(Flaky) 테스트</h3>
<ul>
<li><p><span style="background-color: #dcffe4">성공과 실패를 오가는 <strong>불안정한 테스트</strong></span></p>
</li>
<li><p><strong>원인</strong>: 네트워크 지연, 타이밍 이슈, 비동기 렌더링, 데이터 의존성</p>
</li>
<li><p><strong>영향</strong>: 테스트 결과에 대한 <span style="background-color: #dcffe4">신뢰도 하락</span></p>
</li>
<li><p>개발자가 테스트를 신뢰하지 않아 결국 <span style="background-color: #dcffe4">자동화 포기</span></p>
</li>
</ul>
<h3 id="플래키-해결-1-자동-대기auto-waiting">플래키 해결: 1. 자동 대기(Auto-Waiting)</h3>
<ul>
<li>액션을 수행하기 전, <span style="background-color: #dcffe4">요소가 준비될 때 까지 자동으로 대기</span></li>
</ul>
<p><strong>체크 항목</strong></p>
<ul>
<li><strong>1) Visible</strong>: 요소가 화면에 보이는가?</li>
<li><strong>2) Stable</strong>: 애니메이션이 끝나 정지했는가?</li>
<li><strong>3) Enabled</strong>: 활성화되어 클릭 가능한가?</li>
<li><strong>4) Receives events</strong>: 이벤트가 처리될 준비가 됐는가?</li>
</ul>
<h3 id="플래키-해결-2-웹-퍼스트-어설션">플래키 해결: 2. 웹 퍼스트 어설션</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/d3f975f0-ad23-4e0d-ba60-a335b972520a/image.png" alt=""></p>
<ul>
<li><strong>고정 대기(waitForTimeout)</strong>는 <span style="background-color: #dcffe4">절대 금지</span></li>
<li>웹 퍼스트 어설션은 조건이 만족될 때 까지 <span style="background-color: #dcffe4">최대 5초간 확인</span></li>
<li>조건이 맞으면 다음 단계로 넘어가므로 <span style="background-color: #dcffe4">안정성과 속도</span> 모두 확보</li>
</ul>
<h3 id="테스팅-피라미드-전략">테스팅 피라미드 전략</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/152da3ad-1b0b-4b84-bfb8-808b1ab34a4d/image.png" alt=""></p>
<ul>
<li>아래로 갈수록 많이, 위로 갈수록 개수를 적게</li>
<li>유닛은 수천 개 단위로 빠르게, E2E는 핵심 흐름 위주로 <span style="background-color: #dcffe4">소수 정예</span></li>
</ul>
<h2 id="테스팅-비교">테스팅 비교</h2>
<h3 id="유닛-테스트unit-test">유닛 테스트(Unit Test)</h3>
<ul>
<li><span style="background-color: #dcffe4">함수, 메서드</span> 등 <span style="background-color: #dcffe4">코드의 가장 작은 단위</span>를 검증</li>
<li>초고속(ms) 실행, 실패 시 <span style="background-color: #dcffe4">문제 위치를 바로 특정</span></li>
<li>DB, 서버, 네트워크 등 <span style="background-color: #dcffe4">외부 의존 없음</span></li>
<li>ex) sum(1, 2) 함수가 3을 반환하는가</li>
</ul>
<h3 id="통합-테스트integration-test">통합 테스트(Integration Test)</h3>
<ul>
<li><span style="background-color: #dcffe4">여러 모듈</span>이 결합했을 때의 <span style="background-color: #dcffe4">상호작용 검증</span></li>
<li>DB 연동, API 호출과 응답, 외부 서비스 연계</li>
<li>유닛 테스트보다는 느리고, E2E 테스트보다는 빠름</li>
<li>ex) 회원가입 요청 시 <span style="background-color: #dcffe4">실제로 DB에 사용자 정보가 저장되는지</span> 확인</li>
</ul>
<h3 id="e2e-테스트">E2E 테스트</h3>
<ul>
<li><span style="background-color: #dcffe4">실제 사용자 관점</span>에서 <span style="background-color: #dcffe4">전체 흐름</span>을 시뮬레이션</li>
<li><strong>도구</strong>: Playwright, Selenium, Cypress 등</li>
<li><span style="background-color: #dcffe4">실제 사용자 경험</span>을 그대로 검증</li>
<li>느리고 관리가 어려움, <span style="background-color: #dcffe4">핵심 기능 위주로 작성</span></li>
</ul>
<h3 id="사례-네트워크-모킹">사례: 네트워크 모킹</h3>
<h4 id="사용-의도와-목록-조회">사용 의도와 목록 조회</h4>
<p><strong>1) 외부 서버에 의존하지 않는 독립적인 테스트 만들기</strong></p>
<pre><code class="language-javascript">test(&#39;가짜 데이터를 사용한 목록 조회&#39;, async ({ page }) =&gt; {
  await page.route(&#39;**/api/todos&#39;, async route =&gt; {
    await route.fulfill({
      status: 200,
      body: JSON.stringify([{ id: 1, text: &#39;모킹된 할일&#39; }]),
    });
  });
  await page.goto(&#39;http://localhost:5173&#39;);
  await expect(page.getByText(&#39;모킹된 할일&#39;)).toBeVisible();
});</code></pre>
<p><strong>2) 인위적으로 만들기 어려운 엣지 케이스를 강제 재현</strong></p>
<pre><code class="language-javascript">test(&#39;서버 에러 시 에러 메시지 표시&#39;, async ({ page }) =&gt; {
  await page.route(&#39;**/api/todos&#39;, async route =&gt; {
    await route.fulfill({ status: 500 }); // 500  에러 강제
  });
  await page.goto(&#39;http://localhost:5173&#39;);
  await expect(page.getByText(&#39;오류가 발생했습니다&#39;)).toBeVisible();
});</code></pre>
<h2 id="마무리">마무리</h2>
<ul>
<li>Playwright을 이용한 빠르고 안정적인 E2E 구현</li>
<li>로케이터로 깨지지 않는 견고한 셀렉터 작성</li>
<li>테스팅 피라미드 전략으로 테스트 효율 극대화</li>
<li>네트워크 모킹으로 외부 환경으로부터 독립성 확보</li>
<li>고정 대기 금지, 웹 퍼스트 어설션 사용 원칙 준수</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Testing Pyramid]]></title>
            <link>https://velog.io/@hi_soap/Testing-Pyramid</link>
            <guid>https://velog.io/@hi_soap/Testing-Pyramid</guid>
            <pubDate>Wed, 03 Jun 2026 06:17:15 GMT</pubDate>
            <description><![CDATA[<h2 id="테스팅-피라미드란">테스팅 피라미드란?</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/bc6b2e87-0673-4815-b4ec-405026ec992f/image.png" alt=""></p>
<h3 id="핵심-원칙">핵심 원칙</h3>
<ul>
<li><p>테스트 종류를 <span style="background-color: #dcffe4">계층별로 나누어</span> 비중을 조절하는 전략</p>
</li>
<li><p>아래로 갈수록 <span style="background-color: #dcffe4">많게</span>, 위로 갈수록 <span style="background-color: #dcffe4">적게</span></p>
</li>
<li><p>각 계층은 <span style="background-color: #dcffe4">서로 다른 속도, 커버리지, 특성</span>을 가짐</p>
</li>
</ul>
<h3 id="필요한-이유">필요한 이유</h3>
<p><strong>속도와 비용</strong></p>
<ul>
<li>하위 테스트일수록    <span style="background-color: #dcffe4">실행 속도가 빠르고 유지보수 비용 저렴</span></li>
</ul>
<p><strong>정확한 피드백</strong></p>
<ul>
<li>오류 발생 시 <span style="background-color: #dcffe4">버그의 위치를 즉시 파악 가능</span></li>
</ul>
<p><strong>신뢰성</strong></p>
<ul>
<li>상위 테스트를 통해 <span style="background-color: #dcffe4">실제 서비스 작동을 최종 보장</span></li>
</ul>
<p><strong>안티 패턴 경계</strong></p>
<ul>
<li><span style="background-color: #dcffe4">아이스크림 콘 구조</span></li>
<li>상위 테스트만 많다면 느리고 취약*</li>
</ul>
<h3 id="유닛unit-테스트와-코드-예시">유닛(Unit) 테스트와 코드 예시</h3>
<p><strong>함수·메서드의 독립적 로직 검증(Jest 스타일)</strong></p>
<pre><code class="language-javascript">// 대상 함수: 10% 할인 적용
function applyDiscount(price) {
    return price * 0.9;
}

// 단위 테스트 코드
test(&#39;할인 계산 로직검증&#39;, () =&gt; {
  const result = applyDiscount(10000);
  expect(result).toBe(9000);
});</code></pre>
<h3 id="통합integration-테스트와-코드-예시">통합(Integration) 테스트와 코드 예시</h3>
<p><strong>모듈 간의 협력과 데이터 흐름 검증</strong></p>
<pre><code class="language-javascript">test(&#39;회원가입 서비스 통합 검증&#39;, async () =&gt; {
  const newUser = { id: &#39;test_id&#39;, name: &#39;홍길동&#39; };
  // 1. 서비스 함수 실행 (DB  통신 발생)
  await userService.signUp(newUser);

  // 2. DB에서 데이터가 잘 저장되었는지 직접 조회
  const userInDb = await db.findUserById(&#39;test_id&#39;);
  expect(userInDb.name).toBe(&#39;홍길동&#39;);
});</code></pre>
<h3 id="e2e-테스트와-playwright-코드">E2E 테스트와 Playwright 코드</h3>
<p><strong>실제 사용자 관점의 전체 시나리오 보장</strong></p>
<pre><code class="language-javascript">test(&#39;로그인 성공 후 메인 페이지 이동&#39;, async ({ page }) =&gt; {
  await page.goto(&#39;https://naver.com/login&#39;);
  await page.fill(&#39;#query&#39;, &#39;Playwright&#39;);
  await page.press(&#39;#query&#39;, &#39;Enter&#39;);

  // 결과확인:페이지제목검증
  await expect(page).toHaveTitle(/Playwright/);
});</code></pre>
<h3 id="요약-비교">요약 비교</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/6f0e36dd-5dcf-4e73-a2ac-1feeba32f0ef/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[npm audit & Trivy]]></title>
            <link>https://velog.io/@hi_soap/npm-audit-Trivy</link>
            <guid>https://velog.io/@hi_soap/npm-audit-Trivy</guid>
            <pubDate>Wed, 03 Jun 2026 06:06:26 GMT</pubDate>
            <description><![CDATA[<h2 id="학습-목표">학습 목표</h2>
<ul>
<li>왜 보안 도구가 필요한가</li>
<li>npm audit 이해하기</li>
<li>Trivy 이해하기</li>
<li>두 도구 함께 사용하기</li>
</ul>
<h3 id="2021년-log4j-사태">2021년 Log4j 사태</h3>
<p><strong>Log4j</strong></p>
<ul>
<li><p>자바에서 거의 모든 회사가 쓰던 <span style="background-color: #dcffe4">로그 기록 라이브러리</span></p>
</li>
<li><p>해당 라이브러리에 해커가 한 줄의 메시지만 보내도 
서버를 통째로 빼앗을 수 있는 <span style="background-color: #dcffe4">치명적인 결함</span> 발견</p>
</li>
</ul>
<h2 id="npm-audit">npm audit</h2>
<p><strong>&quot;냉장고에 있는 식재료만 검사해주는 점원&quot;</strong>
<em>냉장고 안 식재료만 골라서 어떤 게 상했고, 리콜 됐는지 알려주는 점원</em></p>
<ul>
<li>JavaScript, Node.js 패키지만 골라서 검사</li>
</ul>
<h3 id="특징">특징</h3>
<ul>
<li><strong>내장형</strong>
<code>npm</code> 설치 시 자동 포함</li>
<li><strong>JavaScript 전용</strong>
<code>Node.js</code> 패키지만 검사</li>
<li><strong>자동 수정</strong>
<code>audit fix</code> 한 줄로 패치</li>
</ul>
<h3 id="동작">동작</h3>
<p><strong>1) package.json 읽기</strong></p>
<ul>
<li>재료 목록 확인<pre><code class="language-yaml">{
  &quot;dependencies&quot;: {
      &quot;express&quot;: &quot;4.17.1&quot;,
      &quot;lodash&quot;: &quot;4.17.20&quot; // 옛날 버전
  }
}</code></pre>
</li>
</ul>
<p><strong>2) 보안 DB 조회</strong></p>
<ul>
<li>공식 데이터베이스에 문의</li>
</ul>
<p><strong>3) 결과 보고</strong></p>
<ul>
<li>취약점 + 수정버전 안내</li>
</ul>
<h3 id="실제-실행-결과">실제 실행 결과</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/7fbc0893-831f-4472-b3b8-62ca0a5683f3/image.png" alt=""></p>
<p><strong><code>npm audit fix</code></strong> 를 통해 자동으로 수정 가능</p>
<h3 id="심각도severity-4단계">심각도(severity) 4단계</h3>
<p><strong>low</strong>: 위험도 낮음 - 약간 시들해진 채소
<strong>moderate</strong>: 보통 - 유통기한 임박
<strong>high</strong>: 심각 - 곰팡이 핀 빵
<strong>critical</strong>: 매우 위험 - 상한 고기</p>
<p><span style="background-color: #dcffe4"><em>high, critical은 즉시 수정이 필요하며,
low, moderate는 우선순위에 따라 처리</em></span></p>
<h2 id="trivy">Trivy</h2>
<p><strong>주방 위생검사원</strong>
<em>냉장고 뿐만 아니라 주방 전체 가스레인지, 조리기구, 도시락 통 <span style="background-color: #dcffe4">(컨테이너)</span> 까지
전부 점검하는 <span style="background-color: #dcffe4">종합 검사원</span></em></p>
<h3 id="특징-1">특징</h3>
<ul>
<li><p><strong>다중 언어 지원</strong></p>
</li>
<li><p>JavaScript, Python, GO, Java 등*</p>
</li>
<li><p><strong>컨테이너 지원</strong></p>
</li>
<li><p>Docker 이미지, OS 패키지 포함*</p>
</li>
<li><p><strong>시크릿 검사</strong></p>
</li>
<li><p>비밀번호, API 키 노출 탐지*</p>
</li>
</ul>
<h3 id="검사-대상">검사 대상</h3>
<p><strong>1) 컨테이너 이미지 - &quot;배달 도시락 통&quot;</strong></p>
<ul>
<li>Docker 이미지 안의 모든 것</li>
</ul>
<p><strong>2 소스 코드 - &quot;레시피 노트&quot;</strong></p>
<ul>
<li>GitHub 저장소</li>
</ul>
<p><strong>3) 설정 파일 - &quot;주방 설계도&quot;</strong></p>
<ul>
<li>Dockerfile, Kubernetes 설정</li>
</ul>
<p><strong>4) 비밀번호, 열쇠 - 금고 &quot;비밀번호 메모&quot;</strong></p>
<ul>
<li>코드에 실수로 적힌 API 키</li>
</ul>
<h4 id="사례-실수로-비밀번호를-코드에-적었다면">사례: 실수로 비밀번호를 코드에 적었다면?</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/f3ebb6f5-6f4a-42de-bf3b-20cab4c7a973/image.png" alt=""></p>
<ul>
<li>GitHub는 공개 저장소이기 때문에 <span style="background-color: #dcffe4">업로드 순간 전 세계에 공유</span></li>
</ul>
<h2 id="npm-audit-vs-trivy">npm audit vs Trivy</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/b80f1d98-96d2-4232-aa5b-4c89f8c0840c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/6f04cc32-444a-4047-b681-655924494b66/image.png" alt=""></p>
<p><code>npm audit</code>: 매일 점검
<code>Trivy</code>: 정기 종합검사</p>
<h2 id="마무리">마무리</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/d29c1362-7e4b-4004-adf2-8117cf8a4aa5/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ESLint]]></title>
            <link>https://velog.io/@hi_soap/ESLint</link>
            <guid>https://velog.io/@hi_soap/ESLint</guid>
            <pubDate>Wed, 03 Jun 2026 05:31:14 GMT</pubDate>
            <description><![CDATA[<h2 id="학습-목표">학습 목표</h2>
<ul>
<li>린트가 무엇인지</li>
<li>왜 필요한지</li>
<li>무엇을 잡는지</li>
<li>ESLint 사용법</li>
</ul>
<h2 id="lint">Lint</h2>
<p><strong>옷에 붙은 보푸라기</strong></p>
<ul>
<li>옷에 붙은 보푸라기처럼 떼어내야 할 <span style="background-color: #dcffe4">자잘한 결함</span>을 찾는 도구</li>
</ul>
<p><strong>코드의 맞춤법 검사기</strong></p>
<ul>
<li>실행하지 않고 <span style="background-color: #dcffe4">읽기만 해서</span> 검사</li>
</ul>
<h3 id="lint가-잡아주는-것">Lint가 잡아주는 것</h3>
<p><strong>1) 명백한 오류</strong></p>
<ul>
<li>실행하면 그냥 깨지는 것<pre><code class="language-js">console.log(undefinedVar)
// 선언하지 않은 변수 사용</code></pre>
</li>
<li><em>2) 잠재적 버그*</em></li>
<li>실행은 되지만 사고를 부르는 패턴<pre><code class="language-js">if (x == &quot;1&quot;) { ... }
// 타입까지 맞추지 않은 == 사용</code></pre>
</li>
</ul>
<p><strong>3) 코드 스타일</strong></p>
<ul>
<li>팀이 약속한 일관성</li>
<li>ex) 
들여쓰기 4칸 vs 2칸
세미콜론 유무 등*</li>
</ul>
<p><strong>4) 베스트 프랙티스</strong></p>
<ul>
<li>관습적으로 더 좋은 방식</li>
<li>ex)
<code>var</code> → <code>const/let</code>
사용하지 않는 변수 제거 등*</li>
</ul>
<h3 id="컴파일러-린터-사람">컴파일러, 린터, 사람</h3>
<h4 id="컴파일러인터프리터">컴파일러/인터프리터</h4>
<ul>
<li><span style="background-color: #dcffe4">문법 자체가 틀린 코드</span>를 잡아줌</li>
</ul>
<p><em>ex) <code>;</code>이 빠지거나, 괄호가 닫히지 않은 경우</em></p>
<p><strong><em>문법 100%</em></strong></p>
<hr>
<h4 id="린터">린터</h4>
<ul>
<li>문법은 맞지만 위험한 패턴</li>
</ul>
<p><em>ex) <code>==</code> 사용, 사용하지 않는 변수, 팀 스타일 위반</em></p>
<p><strong><em>습관·관습</em></strong></p>
<hr>
<h4 id="사람">사람</h4>
<ul>
<li>설계가 적절한지, 의도가 명확한지</li>
</ul>
<p><em>ex) 해당 함수는 책임이 너무 많음, 변수 이름이 뜻을 담지 않음</em></p>
<p><strong><em>의미·맥락</em></strong></p>
<hr>
<h2 id="eslint">ESLint</h2>
<h3 id="javascript-린터의-사실상-표준">JavaScript 린터의 사실상 표준</h3>
<h3 id="eslint의-핵심-강점">ESLint의 핵심 강점</h3>
<h4 id="1-플러그인-아키텍쳐">1) 플러그인 아키텍쳐</h4>
<ul>
<li>룰을 켜고 끄고, 새로 만들 수 있음</li>
</ul>
<h4 id="2-자동-수정">2) 자동 수정</h4>
<ul>
<li>간단한 문제는 <code>--fix</code> 옵션으로 즉시 수정</li>
</ul>
<h4 id="3-에디터-통합">3) 에디터 통합</h4>
<ul>
<li><code>VS Code</code> 등에서 빨간 줄로 실시간 표시</li>
</ul>
<h4 id="4-프레임워크-지원">4) 프레임워크 지원</h4>
<ul>
<li><code>React</code>, <code>Vue</code>, <code>TypeScript</code> 모두 공식 플러그인</li>
</ul>
<h3 id="설치">설치</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/b53f805c-9cfc-4dec-9814-a3a7c0e4f45d/image.png" alt=""></p>
<p><code>npm eslint src</code>: 검사만 진행
<code>npm eslint src --fix</code>: 자동 수정까지 진행</p>
<h3 id="eslintconfigjs">eslint.config.js</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/581fe2f7-bdfb-479f-a4a9-3ae318e2f2c0/image.png" alt=""></p>
<p><code>files</code></p>
<ul>
<li>검사할 파일 패턴을 글롭(<code>*</code>)으로 지정함</li>
</ul>
<p><code>languageOptions</code></p>
<ul>
<li><code>ECMAScript</code> 버전, 모듈 종류, 사용할 전역 변수</li>
</ul>
<p><code>rules</code></p>
<ul>
<li>룰 이름을 심각도로 매핑함
<code>off</code> → <code>warn</code> → <code>error</code></li>
</ul>
<h3 id="실무에서-거의-항상-켜두는-5가지">실무에서 거의 항상 켜두는 5가지</h3>
<p><code>no-undef</code>: 선언하지 않은 변수 사용 금지 - <strong>error</strong>
<code>no-unused-vars</code>: 선언만 하고 사용하지 않는 변수 경고 - <strong>warn</strong>
<code>eqeqeq</code>: <code>==</code> 대신 <code>===</code> 강제 - <strong>error</strong>
<code>no-console</code>: 콘솔 로그 잔재 경고 - <strong>warn</strong>
<code>prefer-const</code>: 재할당 하지 않는 변수는 <code>const</code> - <strong>warn</strong></p>
<h3 id="pr마다-린트를-자동으로-돌리는-이유">PR마다 린트를 자동으로 돌리는 이유</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/7fc752ea-2b9a-4b7a-8f3f-65cf024f9f41/image.png" alt=""></p>
<p><strong>1) 에디터 설정 의존 제거</strong></p>
<ul>
<li>같은 명령을 같은 환경에서 돌리기 때문에
&quot;내 컴퓨터에서는 안떴는데요?&quot; 를 차단</li>
</ul>
<p><strong>2) 리뷰의 짐 줄이기</strong></p>
<ul>
<li>컴퓨터가 잡을 건 컴퓨터가 잡고, <span style="background-color: #dcffe4">사람은 설계와 의도에 집중</span></li>
</ul>
<p><strong>3) 머지 차단</strong></p>
<ul>
<li>브랜치 보호와 함께 쓰면, lint가 빨개진 PR은 메인에 들어갈 수 없음</li>
</ul>
<h4 id="summary">Summary</h4>
<ul>
<li><p>린트는 코드의 맞춤법 검사</p>
</li>
<li><p>컴파일러도 사람도 잡지 못하는 <span style="background-color: #dcffe4">자잘한 결함을 자동으로 잡음</span></p>
</li>
<li><p>CI의 첫 단계</p>
</li>
<li><p>PR마다 자동, 사람 리뷰 전에 통과해야 함*</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[12. 대용량 저장 장치 관리]]></title>
            <link>https://velog.io/@hi_soap/12.-%EB%8C%80%EC%9A%A9%EB%9F%89-%EC%A0%80%EC%9E%A5-%EC%9E%A5%EC%B9%98-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@hi_soap/12.-%EB%8C%80%EC%9A%A9%EB%9F%89-%EC%A0%80%EC%9E%A5-%EC%9E%A5%EC%B9%98-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Tue, 02 Jun 2026 04:13:10 GMT</pubDate>
            <description><![CDATA[<h2 id="강의-목표">강의 목표</h2>
<ul>
<li><p><span style="background-color: #dcffe4">저장 장치의 특징</span>과 
<span style="background-color: #dcffe4">저장 장치가 시스템의 성능과 신뢰성에 미치는 영향</span>을 이해한다.</p>
</li>
<li><p><span style="background-color: #dcffe4">하드 디스크 장치의 구조와 입출력 과정</span>을 통해 
<span style="background-color: #dcffe4">디스크 입출력 성능을 결정하는 요소</span>에 대해 이해한다.</p>
</li>
<li><p><span style="background-color: #dcffe4">디스크 스케줄링 알고리즘</span>에 대해 이해하고 이들의 성능을 비교한다.</p>
</li>
<li><p>FCFS, SSTF, SCAN, C-SCAN, LOOK C-LOOK*</p>
</li>
<li><p><span style="background-color: #dcffe4">디스크의 저수준 포맷과 고수준 포맷</span>에 대해 이해한다.</p>
</li>
<li><p>최근에 많이 이용되는 
<span style="background-color: #dcffe4">SSD 저장 장치의 구조와 입출력 과정</span> 등에 대해 이해한다.</p>
</li>
</ul>
<h2 id="01-저장-장치-개요">01. 저장 장치 개요</h2>
<h3 id="11-저장-장치의-특징">1.1 저장 장치의 특징</h3>
<p><strong>주기억장치</strong></p>
<ul>
<li><p><span style="background-color: #dcffe4">현재 실행 중인 프로그램 코드와 데이터</span>를 적재</p>
</li>
<li><p><span style="background-color: #dcffe4">CPU가 직접 접근</span>하는 기억장치</p>
</li>
<li><p>RAM 반도체 메모리가 사용됨*</p>
</li>
</ul>
<p><strong>저장 장치</strong>
<img src="https://velog.velcdn.com/images/hi_soap/post/b65fbb9b-c1bc-4557-ba56-840fdd01c255/image.png" alt=""></p>
<ul>
<li><p><span style="background-color: #dcffe4">전원이 꺼져도</span> 프로그램과 데이터를 보조 저장할 수 있는 대용량 장치</p>
</li>
<li><p><span style="background-color: #dcffe4">CPU가 직접 접근하지 않고 입출력 전용 처리기</span>에 의해 입출력됨</p>
</li>
<li><p>하드 디스크, SSD, 자기테이프, CD, RAID, USB 스틱 등*</p>
</li>
<li><p><strong>대용량</strong>
몇 백 기가바이트(GB Giga Byte)에서 수십 테라바이트(Tera Byte)</p>
</li>
<li><p><strong>비휘발성(non-volatile)</strong>
컴퓨터의 교체 시기보다 긺</p>
</li>
<li><p><strong>가상 메모리의 스왑 공간으로 활용</strong>
메모리 계층 구조의 최하위단으로
데이터베이스나 파일 저장 및 가상 메모리의 스왑 공간으로 이용됨</p>
</li>
</ul>
<h3 id="12-저장-장치의-성능-및-신뢰성">1.2 저장 장치의 성능 및 신뢰성</h3>
<ul>
<li><strong>저장 장치의 입출력 병목(I/O bottleneck) 문제 - 시스템 성능에 영향</strong></li>
<li><strong>저장 장치의 데이터 신뢰성(data reliability) 문제 - 시스템 신뢰성에 영향</strong></li>
</ul>
<h4 id="저장-장치의-입출력-병목">저장 장치의 입출력 병목</h4>
<ul>
<li><p>저장 장치가 <span style="background-color: #dcffe4">입출력에 과부하가 걸려 있는 상태</span></p>
</li>
<li><p>여러 프로세스로부터 유발된 입출력 요청들로 인해,
<span style="background-color: #dcffe4">사용자나 프로세스는 입출력이 끝나기를 오래 기다리는 현상</span></p>
</li>
<li><p>CPU와 메모리의 처리 속도에 <span style="background-color: #dcffe4">저장 장치의 속도 향상은 미치지 못한 것이 원인</span></p>
</li>
<li><p><strong>주기억장치 메모리 늘리기</strong>
메모리를 늘려 <span style="background-color: #dcffe4">저장 장치로의 입출력 횟수를 줄이기</span></p>
</li>
<li><p><strong>디스크 캐시 늘리기</strong>
디스크 블록의 다음 블록들을 미리 디스크 캐시에 읽어두어
<span style="background-color: #dcffe4">순차 읽기 응용</span>의 입출력 응답 속도를 높인다.</p>
</li>
<li><p><strong>디스크 스케줄링</strong>
<span style="background-color: #dcffe4">디스크 암의 움직임을 최소화</span>함으로써 
<span style="background-color: #dcffe4">디스크 장치의 물리적인 입출력 시간을 단축</span>하는 기법</p>
</li>
<li><p><strong>SSD와 같은 빠른 저장 장치나 RAID와 같은 병렬 저장 장치 사용</strong>
<span style="background-color: #dcffe4">속도가 빠른 저장 장치</span>나  디스크를 <span style="background-color: #dcffe4">병렬로 동작시키는 
<strong>RAID(Redundant Array of Inexpensive Disks)</strong></span>를 이용하여 
<span style="background-color: #dcffe4">물리적인 입출력 성능 향상</span></p>
</li>
</ul>
<h4 id="데이터-신뢰성">데이터 신뢰성</h4>
<p><strong>디스크 미러링(disk mirroring)</strong></p>
<ul>
<li><span style="background-color: #dcffe4">2개의 디스크</span>에 항상 동일한 데이터가 기록되도록 구현하고
사용자에게는 <span style="background-color: #dcffe4">1개의 디스크</span>처럼 보이게 하는 기법</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/bb5b3cc9-aee0-4881-8a2a-cd30bff42d60/image.png" alt=""></p>
<ul>
<li><p>디스크 미러링은 RAID 기법 중 하나로 <span style="background-color: #dcffe4"><strong>RAID 레벨 1</strong></span>이라고 부름</p>
</li>
<li><p><strong>RAID</strong>는 디스크의 <span style="background-color: #dcffe4"><strong>입출력 병목</strong> 및 <strong>데이터의 신뢰성</strong>을 높이는 기법</span></p>
</li>
<li><p><strong>핫 스와핑(hot swapping)</strong>
시스템 동작 중 디스크가 고장나면, <span style="background-color: #dcffe4">시스템이 동작 중인 상태에서</span>
고장난 디스크를 제거 및 새 디스크를 삽입</p>
</li>
</ul>
<p><strong>RAID</strong></p>
<ul>
<li>디스크에 <span style="background-color: #dcffe4">패리티(parity)</span> 정보와 함께 <span style="background-color: #dcffe4">분산 기록</span>하여
<span style="background-color: #dcffe4">디스크가 손상될 때 자동으로 복구</span>하는 기법</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/517c1eef-81c5-4330-a7b5-99dac88c52cc/image.png" alt=""></p>
<ul>
<li><p><strong>RAID</strong> 기법 중 가장 많이 사용되는 <strong>레벨 5</strong>의 모형</p>
</li>
<li><p>3개의 디스크에 돌아가면서 배치하며,
그중 한 디스크에는 <span style="background-color: #dcffe4">패리티(parity) 블록</span>을 저장</p>
</li>
<li><p>작동 중 디스크 2가 고장이 나면 디스크 0, 1, 2를 통해 디스크 2를 복구</p>
</li>
</ul>
<h4 id="tip-패리티-비트">TIP 패리티 비트</h4>
<ul>
<li><p>데이터를 전송할 때, <span style="background-color: #dcffe4">오류를 검출하기 위해 추가되는 비트</span></p>
</li>
<li><p><strong>짝수/홀수 패리티 비트</strong>
데이터와 패리티 비트를 합쳐 
<span style="background-color: #dcffe4">1의 개수가 짝수(홀수)개가 되도록</span> 패리티 비트를 만듦</p>
</li>
<li><p>데이터 비트가 <code>001</code>일 때, 짝수 패리티 비트는 <code>1</code>, 홀수 패리티 비트는 <code>0</code>*</p>
</li>
<li><p>홀수 패리티 비트 생성 방법은
모든 데이터 비트들에 <span style="background-color: #dcffe4"><strong>XOR(exclusive OR, ⊕)</strong></span>
<code>홀수패리티 비트 p = b0 ⊕ b1 ⊕ b2</code>
<code>사용 중 B2 블록 손상 시, B2 = P0 ⊕ B0 ⊕ B1</code></p>
</li>
</ul>
<h2 id="02-하드-디스크-장치">02. 하드 디스크 장치</h2>
<h3 id="21-하드-디스크-장치의-구조">2.1 하드 디스크 장치의 구조</h3>
<p><strong>하드 디스크(HDD, Hard Disk Driver)</strong></p>
<ul>
<li>자성체(magnetic meterial)로 코팅된 여러 개의 원형 판(platter, 플래터)에
디지털 정보를 저장하고 읽어 내는 저장 장치</li>
</ul>
<h4 id="디스크-제어-모듈">디스크 제어 모듈</h4>
<ul>
<li><p><span style="background-color: #dcffe4">호스트로부터 명령을 받아 디스크 매체 모듈에 지시</span>하여
<span style="background-color: #dcffe4">디스크 캐시와 디스크 매체 사이에 입출력</span>이 이루어지도록 함</p>
</li>
<li><p><span style="background-color: #dcffe4">입출력 버스 인터페이스</span>를 통해 
<span style="background-color: #dcffe4">디스크 캐시에 저장된 데이터를 호스트로 전송</span> 또는
<span style="background-color: #dcffe4">호스트로부터 디스크 캐시로 데이터 수신</span></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/f499ec5e-c23a-4818-8f48-27fd321a9f0d/image.png" alt=""></p>
<p><strong>입출력 버스 인터페이스</strong></p>
<ul>
<li>입출력 버스를 통해 <span style="background-color: #dcffe4">호스트로부터 입출력 명령을 수신하고
데이터를 주고받는 하드웨어</span>
<em>PCI/SCSI/SATA/USB 버스 인터페이스 등 다양한 종류 존재</em></li>
</ul>
<p><strong>프로세서</strong></p>
<ul>
<li><p>운영체제로부터 전달받은 <span style="background-color: #dcffe4">디스크 입출력 명령을 해석</span>하여
기계 장치를 구동시켜 하드 디스크의 <span style="background-color: #dcffe4">암(arm)을 움직이거나 
디스크 헤드(disk head)</span>를 제어하여 플래터에서 읽거나 씀</p>
</li>
<li><p>내부에 <span style="background-color: #dcffe4">명령 큐</span>를 두고 호스트로부터 받은 <span style="background-color: #dcffe4">여러 개의 입출력 명령 저장</span></p>
</li>
<li><p><span style="background-color: #dcffe4">스케줄링</span>을 통해 <span style="background-color: #dcffe4">디스크 액세스 시간 단축 및 캐시 관리</span></p>
</li>
</ul>
<p><strong>디스크 캐시</strong></p>
<ul>
<li><p>입출력되는 데이터의 <span style="background-color: #dcffe4">임시 저장소 역할</span>을 하는 빠른 반도체 메모리</p>
</li>
<li><p>몇 십 MB 크기*</p>
</li>
<li><p>운영체제가 저장하기 위해 보낸 데이터는 <span style="background-color: #dcffe4">디스크 캐시에 먼저 저장</span> 후,
프로세서에 의해 플래터에 기록됨</p>
</li>
<li><p>프로세서는 <span style="background-color: #dcffe4">예측 읽기(prefetch)</span>를 통해 다음에 읽을 것으로 예측되는 데이터를 디스크 캐시에 읽어 놓아 <span style="background-color: #dcffe4">호스트의 응답 시간을 단축</span></p>
</li>
</ul>
<h4 id="디스크-매체-모듈">디스크 매체 모듈</h4>
<p><strong>플래터(platter)</strong></p>
<ul>
<li>완전한 원형 판의 자성체로 <span style="background-color: #dcffe4">디지털 정보가 기록되는 곳</span></li>
</ul>
<p><strong>디스크 헤드(disk head)</strong></p>
<ul>
<li><p>플래터 위를 움직이며 <span style="background-color: #dcffe4">디지털 정보를 읽거나 쓰는 장치</span></p>
</li>
<li><p>디스크 헤드는 플래터 표면 위에 <span style="background-color: #dcffe4">일정한 간격</span>을 유지한 채 정보를 읽거나 기록</p>
</li>
<li><p>플래터 표면에 닿게 되면 해당 부분이 손상되어 <span style="background-color: #dcffe4">불량 섹터(bad sector)</span> 가능성*</p>
</li>
</ul>
<p><strong>암(arm)</strong></p>
<ul>
<li>디스크 헤드를 원하는 위치로 움직이는 장치</li>
</ul>
<p><strong>구동기(actuator)</strong></p>
<ul>
<li>암들은 모두 <span style="background-color: #dcffe4">하나의 구동기</span>에 달려 있으며, 함께 이동한다.</li>
<li>그 중 하나의 플래터에서만 입출력을 진행함*</li>
</ul>
<h4 id="디스크-파킹disk-parking">디스크 파킹(disk parking)</h4>
<ul>
<li><strong><code>park</code></strong>
디스크 암을 플래터 바깥의 안전한 위치로 이동시키는 명령어</li>
<li>디스크 암이 플래터 위에 있는 상태에서 컴퓨터를 옮기면 플래터 훼손 가능성*</li>
</ul>
<h4 id="tip-최초의-하드-디스크-ibm-ramac-350">TIP 최초의 하드 디스크 IBM RAMAC 350</h4>
<h3 id="22-입출력-버스">2.2 입출력 버스</h3>
<ul>
<li>입출력 버스는 다음과 같이 여러 종류가 있으며, 하드 디스크 제조업체는 
<span style="background-color: #dcffe4">버스 인터페이스 회로를 포함하는</span> 하드 디스크를 만듦</li>
</ul>
<p><code>ATA, SATA(Serial ATA), SCSI, IEEE 1394, Fiber Channel, USB</code>
<img src="https://velog.velcdn.com/images/hi_soap/post/e38c36df-6c89-49c0-b0a9-c7e3010208ac/image.png" alt=""></p>
<p><strong>ATA(Advanced Technology Attachment)</strong></p>
<ul>
<li><span style="background-color: #dcffe4"><strong>하드 디스크</strong>나 <strong>CD-ROM 장치</strong>를 PC에 연결하는 표준 입출력 버스</span></li>
<li>상업적으로 IDE, EIDE 라는 이름으로 사용됨*</li>
</ul>
<p><strong>SATA</strong></p>
<ul>
<li>현대 컴퓨터에서 디스크 장치를 연결하는 <span style="background-color: #dcffe4">가장 보편적인 버스</span></li>
<li>PC나 노트북의 하드 디스크에 사용됨*</li>
</ul>
<p><strong>SCSI(Small Computer System Interface)</strong></p>
<ul>
<li>하나의 버스에 <span style="background-color: #dcffe4">하드 디스크, CD-ROM, DVD, 스캐너 장치</span> 등 
<span style="background-color: #dcffe4">여러 개의 장치들을 동시에 연결하는 입출력 버스</span></li>
<li>서버급 컴퓨터나 워크스테이션에서 사용됨*</li>
</ul>
<h3 id="23-디스크-저장-구조">2.3 디스크 저장 구조</h3>
<h4 id="섹터-트랙-실린더">섹터, 트랙, 실린더</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/9f681c60-88e3-4607-b24c-3060a300b8fc/image.png" alt=""></p>
<ul>
<li><p>하드 디스크의 모든 플래터들은 
<span style="background-color: #dcffe4">하나의 구동축(spindle, 스핀들)</span>에 연결되어 <span style="background-color: #dcffe4">동시에</span> 회전</p>
</li>
<li><p>언제나 동일한 속도인 <span style="background-color: #dcffe4">등각속도(CAV, Constant Angular Velocity)</span>로 회전</p>
</li>
</ul>
<p><strong>트랙(track)</strong></p>
<ul>
<li>플래터의 표면에는 여러 개의 동심원을 따라 정보가 저장되는데,
이 때의 <span style="background-color: #dcffe4">각 동심원</span></li>
</ul>
<p><strong>실린더(cylinder)</strong></p>
<ul>
<li><span style="background-color: #dcffe4">같은 동심원을 가진 트랙</span>들의 모임</li>
</ul>
<p><strong>섹터(sector)</strong></p>
<ul>
<li><p>디스크 장치가 저장하고 읽는 <span style="background-color: #dcffe4">최소 단위</span></p>
</li>
<li><p>전통적으로 512바이트 크기이지만, 
최근에는 <span style="background-color: #dcffe4">4KB 크기의 고급 포맷(Advanced Format)</span>을 사용</p>
</li>
</ul>
<h4 id="존-비트-레코딩zone-bit-recording">존 비트 레코딩(zone bit recording)</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/5fb05a15-1071-4058-b305-a199d512a50b/image.png" alt=""></p>
<p><strong>전통적인 디스크</strong></p>
<ul>
<li>전통적인 디스크에서는 <span style="background-color: #dcffe4">안쪽 트랙과 바깥쪽 트랙에 같은 개수의 섹터를 배치</span></li>
<li>안쪽 트랙에 비해 <span style="background-color: #dcffe4">바깥쪽 트랙의 저장 밀도가 낮음*</span></li>
</ul>
<p><strong>존 비트 레코딩</strong></p>
<ul>
<li><p>밀도가 낮은 바깥쪽 트랙에 <span style="background-color: #dcffe4">더 많은 섹터를 배치</span>하는 
<span style="background-color: #dcffe4">존 비트 레코딩(zone bit recording, ZBR)</span> 방식의 하드 디스크 등장</p>
</li>
<li><p>전체 트랙을 몇 개의 <span style="background-color: #dcffe4">존(zone, 영역)</span>으로 나눔</p>
</li>
<li><p>바깥 존에 더 많은 트랙 당 섹터를 배치하여
<span style="background-color: #dcffe4">디스크의 저장 용량을 늘리고 입출력 속도를 높임</span></p>
</li>
<li><p>바깥 존일수록 더 많은 섹터가 있으므로
<span style="background-color: #dcffe4">시간당 입출력되는 섹터가 많아, 입출력 속도가 더 높음</span></p>
</li>
</ul>
<h4 id="디스크-물리-주소">디스크 물리 주소</h4>
<ul>
<li><p>트랙과 섹터는 바깥에서 안으로 0부터 증가하는 순서로 번호가 매겨짐</p>
</li>
<li><p>플래터의 개수가 4개일 때, 헤드는 8개, 한 실린더는 8개의 트랙으로 구성*</p>
</li>
<li><p>실린더 번호도 바깥에서 안쪽으로 0번부터 매겨짐</p>
</li>
<li><p>섹터의 위치는 <span style="background-color: #dcffe4">CHS(Cylinder-Head-Sector) 물리 주소</span>로 표현됨
<code>CHS 물리 주소 = [실린더 번호, 헤드 번호, 섹터 번호]</code></p>
</li>
<li><p>운영체제는 디스크의 모든 섹터를 일차원으로 펼치고, 
여러 섹터를 <span style="background-color: #dcffe4">블록(disk block)</span>이라는 단위로 묶어 0번부터 블록에 번호를 매긴
<span style="background-color: #dcffe4">논리 블록 주소(LBA, Logical Block Address)</span>를 사용</p>
</li>
</ul>
<h4 id="하드-디스크의-회전-방향">하드 디스크의 회전 방향</h4>
<ul>
<li>하드 디스크의 모터는 <span style="background-color: #dcffe4">반시계 방향</span>으로 회전함</li>
<li>명확한 이유는 존재하지 않음*</li>
</ul>
<h4 id="하드-디스크를-세워-놓으면-고장-나지-않을까">하드 디스크를 세워 놓으면 고장 나지 않을까?</h4>
<ul>
<li>하드 디스크는 세워 놓든 눕혀 놓든 반대로 세워놓든 전혀 영향이 없음</li>
</ul>
<h3 id="24-디스크-용량">2.4 디스크 용량</h3>
<pre><code>디스크 용량 = 실린더 개수 x 실린더 당 트랙 수 x 트랙 당 섹터 수 x 섹터 크기</code></pre><ul>
<li>실린더 당 트랙 수 = 디스크 헤드의 개수</li>
</ul>
<h4 id="용량-계산-사례-1">용량 계산 사례 1</h4>
<pre><code>실린더: 1000개
실린더 당 트랙 수: 8개
트랙 당 섹터 수: 200개
섹터 크기: 512바이트

디스크 용량 = 1000 x 8 x 200 x 0.5KB = 약 800MB(819.2MB)</code></pre><h4 id="용량-계산-사례">용량 계산 사례</h4>
<pre><code>실린더: 4000개
실린더 당 트랙 수: 2개
트랙 당 섹터 수: 2000개
섹터 크기: 512바이트</code></pre><ul>
<li><p>(1) 디스크에는 총 몇 개의 트랙이 있는가?
<code>4000 x 2 = 8000개</code></p>
</li>
<li><p>(2) 트랙당 저장 용량은 얼마인가?
<code>2000 x 512바이트 = 약 1MB</code></p>
</li>
<li><p>(3) 디스크의 총 저장 용량은 얼마인가?
<code>8000 x 1MB = 8GB</code></p>
</li>
</ul>
<h4 id="tip-저장용량을-나타내는-단위-kb와-kib">TIP 저장용량을 나타내는 단위, KB와 KiB</h4>
<ul>
<li><code>KB</code>, <code>MB</code>, <code>GB</code>의 단위는 <code>1000</code>을 단위로 하고,
<code>KiB</code>, <code>MiB</code>, <code>GiB</code>의 단위는 <code>1024</code>를 단위로 함</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/96ecc44d-1820-42ea-9e99-73a5d3ef69b0/image.png" alt=""></p>
<h3 id="25-디스크-입출력-과정-및-성능-파라미터">2.5 디스크 입출력 과정 및 성능 파라미터</h3>
<ul>
<li><p>디스크 장치는 운영체제의 <span style="background-color: #dcffe4">디스크 드라이버</span>로부터 받은 입출력 명령을 실행</p>
</li>
<li><p>운영체제는 <span style="background-color: #dcffe4">논리 블록 주소</span>를 사용하기 때문에,
디스크 장치가 운영체제로부터 받는 명령의 구성은 다음과 같다
<code>읽기/쓰기, 논리블록번호(LBA), 호스트의 메모리 주소, 읽거나 쓰는 블록 수</code></p>
</li>
<li><p>디스크 입출력 명령이 내려지면, <span style="background-color: #dcffe4">디스크 장치 내 프로세서</span>가
논리 블록 번호 → CHS 물리 주소 후, 디스크 헤드를 목표 실린더로 이동</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/adfe51f3-e1a0-4311-899e-f9a22385f827/image.png" alt=""></p>
<ul>
<li><p><strong>1) 탐색(seek)</strong>
디스크 헤드를 목표 실린더로 이동</p>
</li>
<li><p><strong>2) 회전 지연(rotational latency)</strong>
플래터가 회전하여 목표 섹터가 디스크 헤드 밑에 도달할 때 까지 대기</p>
</li>
<li><p><strong>3) 전송(transfer)</strong>
<span style="background-color: #dcffe4">디스크 헤드와 호스트 사이</span>의 데이터 전송. <span style="background-color: #dcffe4">내부 전송과 외부 전송으로 나뉨</span></p>
</li>
<li><p><strong>4) 오버헤드(overhead)</strong>
프로세서가 호스트에서 명령을 받고 해석하는 등 부가 과정</p>
</li>
</ul>
<h4 id="탐색">탐색</h4>
<ul>
<li><p>디스크 장치 내 모터를 이용하여 
<span style="background-color: #dcffe4">디스크 헤드를 현재 실린더에서 목표 실린더로 이동</span>시키는 과정</p>
</li>
<li><p><span style="background-color: #dcffe4">이동하는 실린더 개수를 <strong>탐색 거리(seek distance)</strong></span>라고 함</p>
</li>
<li><p>탐색 시간은 탐색 거리에 선형적으로 비례*</p>
</li>
<li><p>탐색 거리가 <span style="background-color: #dcffe4">매우 짧은 경우</span> 탐색 시간이 많이 걸리는 현상이 일어남</p>
</li>
<li><p>모터가 <span style="background-color: #dcffe4">디스크 암을 가속시키는 탐색 초기</span>와 
<span style="background-color: #dcffe4">목표 실린더 앞에서의 감속</span>에 많은 시간이 걸리기 때문*</p>
</li>
<li><p>오늘날 상용 하드 디스크의 평균 탐색 시간은 <span style="background-color: #dcffe4">5ms 내외(1ms~10ms)</span></p>
</li>
</ul>
<h4 id="회전-지연">회전 지연</h4>
<ul>
<li><p>탐색 후 플래터가 회전하여 
<span style="background-color: #dcffe4">헤드 밑에 목표 섹터가 도달할 때 까지 걸리는 시간</span></p>
</li>
<li><p>평균 회전 지연 시간 = <span style="background-color: #dcffe4">1/2 회전 시간</span></p>
</li>
<li><p>디스크의 회전 속도는 <span style="background-color: #dcffe4">분당 회전수로 나타내며, 
단위는 <strong>RPM(Rotations Per minute)</strong></span></p>
</li>
<li><p>7200RPM인 경우</p>
<pre><code>1회전 시간 = 60초/7200 = 8.33ms
평균 회전 지연 시간 = 8.33ms / 2 = 4.17ms</code></pre></li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/71241926-d4e2-442a-b717-3cbc651257f7/image.png" alt=""></p>
<h4 id="전송">전송</h4>
<p><strong>내부 전송</strong></p>
<ul>
<li><p>디스크 헤더가 플래터 표면에서 <span style="background-color: #dcffe4">디스크 캐시로 데이터를 읽어오거나,</span>
반대로 <span style="background-color: #dcffe4">디스크 캐시에서 플래터 표면에 데이터를 기록하는 시간</span></p>
</li>
<li><p>한 트랙이 1000개의 섹터로 구성되고 섹터 크기는 512바이트(0.5KB)</p>
<pre><code>한 트랙의 크기 = 1000 x 0.5KB = 500KB
디스크의 회전 속도가 7200RPM일 때, 1회전 시간 = 8.3ms
→ 8.3ms에 500KB를 읽어 전송
</code></pre></li>
</ul>
<p>500KB/8.3ms = 약 60.24KB/ms = 60240KB/초 = 60MB/초</p>
<pre><code>
- 디스크의 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;회전 속도가 빠를수록 내부 전송 속도가 빠름&lt;/span&gt;

- 디스크 제조업체에서 공개하는 디스크의 데이터 전송률(data transfer rate)은
보통 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;내부 전송 속도&lt;/span&gt;를 의미함

- &lt;span style=&quot;background-color: #dcffe4&quot;&gt;바깥쪽 트랙&lt;/span&gt;을 액세스할 경우, &lt;span style=&quot;background-color: #dcffe4&quot;&gt;전송률이 더 높음&lt;/span&gt;

**외부 전송**
- &lt;span style=&quot;background-color: #dcffe4&quot;&gt;디스크 캐시와 호스트 컴퓨터 사이에 데이터가 전송되는 시간&lt;/span&gt;

- 디스크 장치와 호스트 컴퓨터가 연결되는 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;I/O 버스의 속도&lt;/span&gt;에 달려 있음
*일반적으로 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;외부 전송 시간 ＜ 내부 전송 시간*&lt;/span&gt;

#### 오버헤드 시간
- 호스트로부터 명령을 받고 해석하는 시간
- 디스크 헤드의 변경 시간(head switch time)
*이러한 시간들은 몇 마이크로초(us) 수준으로 매우 작기 때문에
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;디스크 입출력 시간에서 배제&lt;/span&gt;하기도 함*

#### 디스크 액세스 시간과 디스크 입출력 응답 시간
**디스크 액세스 시간(disk access time)**
- 디스크가 명령을 받은 후, &lt;span style=&quot;background-color: #dcffe4&quot;&gt;목표 섹터에 접근하여 읽거나 쓰기까지 걸리는 시간&lt;/span&gt;
`디스크 액세스 시간 = 탐색 시간 + 회전 지연 시간 + 내부 전송 시간`

**디스크 입출력 응답 시간(disk I/O response time)**
- 호스트나 응용프로그램 입장에서 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;디스크 입출력에 걸리는 전체 시간&lt;/span&gt;</code></pre><p>디스크 입출력 응답 시간 = 탐색 시간 + 회전 지연 시간 + 전체 전송 시간 + 오버헤드</p>
<pre><code>- 디스크의 캐시에 요청된 섹터가 있는 경우, 내부 전송 과정 없이
바로 디스크 캐시에서 호스트로 전송하기 때문에 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;가변적&lt;/span&gt;

#### 디스크 액세스 시간의 정의
- 디스크 액세스 시간에서 전송 시간을 뺀 
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;탐색 시간과 회전 지연 시간만으로&lt;/span&gt; 정의하기도 함

- 혹은 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;디스크 입출력 응답 시간&lt;/span&gt;을 단순히 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;디스크 액세스 시간&lt;/span&gt;이라고 부름

### 탐구 12-1 평균 디스크 액세스/응답 시간 계산
- 평균 디스크 탐색 시간: 5ms
- 디스크의 회전 속도: 10000rpm
- 섹터 크기: 512바이트
- 트랙당 섹터 수: 1000개
- 디스크 장치와 호스트 사이의 인터페이스 전송 속도: 100MB/s
- 디스크 장치의 오버헤드: 0.1ms

`평균 디스크 액세스 시간 = 평균 탐색 시간 + 평균 회전 지연 시간 + 내부 전송 시간`
</code></pre><p>디스크의 1회전 시간 = 60초 x 1000(ms 변환) / 10000 = 6ms
디스크의 평균 1회전 시간 = 6ms / 2 = 3ms</p>
<p>내부 전송 속도 = 1000 x 0.5KB / 6ms = 500KB/6ms
             = 83.3KB/ms = 83.3KB / 1000(MB 변환) x (1000 초 단위 변환) 
             = 83.3MB/초</p>
<p>1 섹터를 읽는데 걸리는 내부 전송 시간 = 0.5KB / (83.3MB/초) = 0.006ms</p>
<p>평균 디스크 액세스 시간 = 5ms + 3ms + 0.006ms = 8.006ms</p>
<pre><code></code></pre><p>평균 디스크 입출력 응답 시간 = 8ms + 0.5KB/(100MB/s) + 0.1ms
                         = 8ms + 0.005ms + 0.1ms
                         = 8.105ms</p>
<p>```</p>
<ul>
<li>해당 문제는 1섹터 기준의 문제이므로 값이 매우 작기 때문에
내부/외부 전송 시간을 무시 가능하지만, 수십 섹터가 되면 무시할 수 없다.</li>
</ul>
<h2 id="03-디스크-스케줄링-알고리즘">03. 디스크 스케줄링 알고리즘</h2>
<h3 id="31-디스크-큐와-디스크-스케줄링">3.1 디스크 큐와 디스크 스케줄링</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/f088ec3d-bb26-4df3-ab0f-a1eaa98cc8b2/image.png" alt=""></p>
<ul>
<li><p>디스크 큐에는 디스크 입출력 명령들이
여러 프로세스나 스레드로부터 <span style="background-color: #dcffe4">동시 다발적으로 도착</span>하거나,
하나의 프로세스로부터 <span style="background-color: #dcffe4">연속적으로 도착</span></p>
</li>
<li><p>후자의 경우 <span style="background-color: #dcffe4">한 파일을 순차적으로 읽는 경우</span>*</p>
</li>
<li><p>디스크 큐에 들어 있는 입출력 요청들의 요청 실린더는 
<span style="background-color: #dcffe4">중구난방</span>으로 예상되므로, 도착 순서대로 처리하기보다 
<span style="background-color: #dcffe4">디스크 헤드의 현재 위치와 목표 실린더의 위치를 고려하여</span> 처리</p>
</li>
<li><p>해당 방법으로 순서를 재조정하는 <span style="background-color: #dcffe4">디스크 스케줄링</span> 기법 필요*</p>
</li>
</ul>
<h3 id="32-디스크-스케줄링의-목표">3.2 디스크 스케줄링의 목표</h3>
<ul>
<li><p>디스크 스케줄링의 기본 목표는 
<span style="background-color: #dcffe4">디스크 암이 움직이는 평균 탐색 거리를 최소화</span>하는 것</p>
</li>
<li><p>평균 디스크 탐색 시간과 평균 디스크 액세스 시간을 줄이는 것*</p>
</li>
<li><p>디스크 장치 내 프로세서는 디스크 입출력 요청을 받으면
<span style="background-color: #dcffe4">CHS 물리 주소로 변환하여 디스크 큐에 저장</span></p>
</li>
<li><p><span style="background-color: #dcffe4">평균 탐색 거리</span>를 최소화하는 동시에 
<span style="background-color: #dcffe4">입출력 요청들의 응답 시간 편차</span>를 최소화하는 것도 고려</p>
</li>
</ul>
<h3 id="33-디스크-스케줄링-알고리즘">3.3 디스크 스케줄링 알고리즘</h3>
<ul>
<li>FCFS</li>
<li>SSTF</li>
<li>SCAN</li>
<li>LOOK</li>
<li>C-SCAN</li>
<li>C-LOOK</li>
<li>디스크 스케줄링 알고리즘을 평가하는 기준은 <span style="background-color: #dcffe4"><strong>평균 탐색 거리</strong></span></li>
<li>총 탐색 거리 / 입출력 요청의 개수*</li>
</ul>
<blockquote>
</blockquote>
<p>처음 디스크 헤드 위치: 실린더 30
요청 실린더들: 79, 68, 11, 74, 10, 89, 65, 87, 26, 15</p>
<h4 id="fcfsfirst-come-first-served">FCFS(First Come First Served)</h4>
<ul>
<li><p><span style="background-color: #dcffe4">디스크 큐에 도착한 순서대로</span> 요청들을 처리</p>
</li>
<li><p>디스크 큐 전체를 검색할 필요가 없어 
<span style="background-color: #dcffe4">구현이 쉽고, 기아가 발생하지 않으며 공평함</span></p>
</li>
<li><p>요청 실린더들의 <span style="background-color: #dcffe4">위치를 고려하지 않기 때문</span>에 성능이 나쁨</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/8b704bed-842a-4f63-9507-f754268be8bc/image.png" alt=""></p>
<h4 id="sstfshortest-seek-time-first">SSTF(Shortest Seek Time First)</h4>
<ul>
<li><p>현재 디스크 헤드가 있는 실린더에서 <span style="background-color: #dcffe4">방향에 관계없이 가장 가까운 요청 선택</span></p>
</li>
<li><p>기아가 발생할 수 있으며, <span style="background-color: #dcffe4">중간 범위의 실린더</span>에 요청이 많은 경우
양 끝쪽 실린더는 오래 대기할 수 있음</p>
</li>
<li><p>요청이 도착하여 응답하기까지의 편차가 큼</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/5ef619f7-e797-4fe8-98e5-44c07c2ece17/image.png" alt=""></p>
<h4 id="scan">SCAN</h4>
<ul>
<li><p>한쪽 실린더 끝에서 다른 쪽 실린더로 <span style="background-color: #dcffe4">방향을 정한 후</span>, 
그 방향으로 있는 요청들을 처리하면서 이동</p>
</li>
<li><p><span style="background-color: #dcffe4"><strong>엘리베이터(elevator)</strong> 알고리즘</span>이라고도 부름</p>
</li>
<li><p>SSTF에 비해 <span style="background-color: #dcffe4">입출력 요청이 균등하게 처리됨</span></p>
</li>
<li><p>높은 처리율보다 입출력 요청을 공평하게 스케줄링하고자 하는 경우*</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/aa70a3af-3cbd-4714-a093-082cc3533fcd/image.png" alt=""></p>
<ul>
<li>실린더 <span style="background-color: #dcffe4">끝까지 이동 후</span> 방향 전환</li>
</ul>
<h4 id="look">LOOK</h4>
<ul>
<li>SCAN과 같이 작동하지만, 현재 이동 방향으로 더 이상의 요청이 없는 경우
<span style="background-color: #dcffe4">실린더 끝까지 가지 않고 즉시 이동 방향을 바꿈</span></li>
<li>대기 중인 요청이 없음에도 불구하고 맨 끝 실린더까지 이동하는
<span style="background-color: #dcffe4">SCAN의 단점을 보완</span>*</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/59b274c0-d8d9-4d02-a776-efada2de02a8/image.png" alt=""></p>
<h4 id="c-scancircular-scan">C-SCAN(Circular SCAN)</h4>
<ul>
<li><p>SCAN의 수정 버전으로, <span style="background-color: #dcffe4">SCAN보다 더 균등한 서비스</span>를 위해 보완된 알고리즘 </p>
</li>
<li><p>맨 바깥쪽 실린더에서 맨 안쪽 실린더로
<span style="background-color: #dcffe4">한 방향으로만 이동하면서 요청을 처리</span></p>
</li>
<li><p>SCAN의 경우 <span style="background-color: #dcffe4">중간 실린더에 위치한 요청들</span>이 선택될 확률이 높음</p>
</li>
<li><p>요청 실린더 위치에 따라 서비스가 균등하지 않음*</p>
</li>
<li><p>바깥쪽으로 더 이상 요청이 없어도 
실린더 끝까지 헤드를 이동시키는 <span style="background-color: #dcffe4">비효율성</span></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/734f0c4b-04e9-4172-8850-0a5fac0f77c9/image.png" alt=""></p>
<h4 id="c-look">C-LOOK</h4>
<ul>
<li><p>LOOK + C-SCAN</p>
</li>
<li><p><span style="background-color: #dcffe4">요청이 없어도</span> 디스크 헤드를 끝 실린더로 이동시키는 C-SCAN의 단점 보완</p>
</li>
<li><p>이동하는 방향으로 요청이 없는 경우 
<span style="background-color: #dcffe4">바로 바깥쪽 실린더에서 가장 가까운 요청</span>으로 한 번에 이동</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/a4809370-b4d1-4438-ad12-1ea00da68c53/image.png" alt=""></p>
<h4 id="디스크-알고리즘-성능-비교">디스크 알고리즘 성능 비교</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/0dc125a4-7fa5-451e-85bd-d4265d8f451f/image.png" alt=""></p>
<h4 id="디스크-알고리즘-특성-비교">디스크 알고리즘 특성 비교</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/79ce34c1-c7d0-4a2d-ac46-4154559a9654/image.png" alt=""></p>
<h4 id="디스크-스케줄링에-대한-최근-경향">디스크 스케줄링에 대한 최근 경향</h4>
<ul>
<li><p>SSD(Solid State Disk)의 사용이 늘어나고 있어
<span style="background-color: #dcffe4">디스크 스케줄링은 과거보다 덜 중요해짐</span></p>
</li>
<li><p>SSD는 디스크 장치와 달리 
<span style="background-color: #dcffe4">헤드를 움직이는 모더와 기계 장치가 없는 반도체식 기억 장치</span></p>
</li>
<li><p>탐색과 회전 지연 시간이 없어 <span style="background-color: #dcffe4">디스크 스케줄링이 필요하지 않음</span>*</p>
</li>
</ul>
<h2 id="04-디스크-포맷">04. 디스크 포맷</h2>
<h3 id="41-저수준-포맷팅llf-low-level-formatting">4.1 저수준 포맷팅(LLF, Low Level Formatting)</h3>
<ul>
<li><p>하드 디스크의 플래터에 <span style="background-color: #dcffe4">트랙과 섹터를 구분하는 정보를 기록</span>하여
<span style="background-color: #dcffe4">디스크 헤드가 섹터와 트랙을 인식할 수 있게 하는 작업</span></p>
</li>
<li><p>초기에는 <span style="background-color: #dcffe4">하드 디스크 사용자가 직접 저수준 포매팅 진행</span></p>
</li>
<li><p>디스크 용량이 증가함에 따라, 디스크 밀도가 높아지고 
일반 사용자가 하기 힘들어지며 공장에서 저수준 포맷이 된 채로 출시됨</p>
</li>
<li><p>공장에서 저수준 포맷이 된 채로 출시되기 때문에
저수준 포맷을 하는 상용 소프트웨어들은 
<span style="background-color: #dcffe4">디스크에 기록된 정보를 모두 지우는 <strong>제로필(zero fill)</strong> 작업만 진행</span></p>
</li>
<li><p>저수준 포맷팅은 <span style="background-color: #dcffe4">섹터 크기에 따라</span>
<span style="background-color: #dcffe4"><strong>512 바이트 섹터 포맷</strong>과 <strong>4K 포맷(4096바이트 섹터 포맷)</strong></span>으로 나뉨</p>
</li>
</ul>
<h4 id="512바이트-섹터-포맷">512바이트 섹터 포맷</h4>
<ul>
<li>과거 플로피 디스크나 하드 디스크에서 전통적으로 사용</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/0d185009-fcc3-4651-ab0a-f3a3cd736557/image.png" alt=""></p>
<p><strong><code>GAP</code></strong>
다음 섹터를 읽기 전 플래터가 회전하는 동안
<span style="background-color: #dcffe4">디스크 헤드가 준비하는 약간의 시간을 벌기 위해 삽입된 공간</span></p>
<p><strong><code>SYNCH</code></strong>
디스크 헤드가 <span style="background-color: #dcffe4">GAP의 끝을 인식하도록 약속된 코드</span></p>
<p><strong><code>Address Mart</code></strong>
<span style="background-color: #dcffe4">섹터의 물리적 주소</span>가 새겨지는 공간
<em><code>[트랙 번호, 헤드 번호, 섹터 번호]</code></em></p>
<p><strong><code>512바이트의 섹터 공간</code></strong></p>
<p><strong><code>ECC(Error Correction Code)</code></strong>
읽거나 쓰는 과정에서 
<span style="background-color: #dcffe4">손상된 섹터의 복구나 교정을 위해 추가 기록되는 데이터</span></p>
<h4 id="4k-포맷">4K 포맷</h4>
<ul>
<li><p>기존의 8개의 섹터를 하나의 섹터로 만든 것</p>
</li>
<li><p><span style="background-color: #dcffe4">고급 포맷(Advanced Format)</span> 혹은 <span style="background-color: #dcffe4">4K 섹터 포맷</span></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/fc9d09f4-f122-4b6a-822b-d855952c254b/image.png" alt=""></p>
<p><strong>디스크의 <span style="background-color: #dcffe4">저장 효율</span> 향상</strong></p>
<ul>
<li>4KB당 405바이트의 저장 공간 이득을 볼 수 있음</li>
</ul>
<p><strong><span style="background-color: #dcffe4">오류 수정 능력</span>을 대폭 향상</strong></p>
<ul>
<li>ECC를 4KB당 하나만 두는 것으로 하는 대신 
기존 <span style="background-color: #dcffe4">50바이트의 ECC를 100바이트로 늘림</span></li>
</ul>
<p><strong>디스크 <span style="background-color: #dcffe4">입출력 성능 향상</span></strong></p>
<ul>
<li><p><span style="background-color: #dcffe4">현대의 응용프로그램의 크기가 커짐에 따라</span> 4KB 단위의 입출력이 효율적</p>
</li>
<li><p>가상 메모리 체제에서의 <span style="background-color: #dcffe4">페이지 크기가 4KB</span></p>
</li>
<li><p>가상 메모리 크기와 섹터 크기를 맞춤에 따라 <span style="background-color: #dcffe4">섹터 위치 계산이 편해짐*</span></p>
</li>
</ul>
<p><strong>과거부터 사용하던 512바이트 버퍼를 
4K 섹터 디스크 컴퓨터에서 실행 시 문제가 발생함</strong></p>
<ul>
<li><p>4K 섹터로 포맷된 디스크 장치들 중에는 
<span style="background-color: #dcffe4">물리적으로는 한 섹터를 4K 단위로</span> 읽고 쓰지만
<span style="background-color: #dcffe4">호스트에게는 한 섹터가 512바이트인 것처럼 보이도록 함-<strong>emulate</strong></span></p>
</li>
<li><p>이러한 4K 디스크를 <span style="background-color: #dcffe4">512 에뮬레이션 디스크 장치</span> 혹은 <span style="background-color: #dcffe4">512e</span>라고 부름</p>
</li>
<li><p>읽기 요청 시, <span style="background-color: #dcffe4">4K 섹터를 물리적으로 읽고 그 중 요청된 512 바이트를 전송</span>*</p>
</li>
</ul>
<h4 id="tip-불량-섹터bad-sector">TIP 불량 섹터(bad sector)</h4>
<ul>
<li>하드 디스크의 플래터에 결함이 발생하여 <span style="background-color: #dcffe4">읽고 쓸 수 없는 섹터</span></li>
</ul>
<p><strong>물리적 불량 섹터</strong></p>
<ul>
<li><p><span style="background-color: #dcffe4">디스크 헤드의 플래터 직접 접촉이 원인</span></p>
</li>
<li><p>SSD의 경우 <span style="background-color: #dcffe4">저장 셀(cell)</span>의 노후화</p>
</li>
<li><p>하드 디스크는 <span style="background-color: #dcffe4">제조 과정</span>에서 발생할 수 있음</p>
</li>
<li><p>물리적 불량 섹터는 <span style="background-color: #dcffe4">고칠 수 없음</span></p>
</li>
</ul>
<p><strong>논리적 불량 섹터</strong></p>
<ul>
<li><p>섹터를 기록하는 도중에 <span style="background-color: #dcffe4">갑작스럽게 컴퓨터가 꺼지는 경우 발생</span></p>
</li>
<li><p><span style="background-color: #dcffe4">섹터에 기록된 코드와 ECC(오류 정정 코드)의 불일치</span></p>
</li>
<li><p>디스크 장치가 불량 섹터로 처리*</p>
</li>
<li><p>기록된 정보의 손실은 일어날 수 있으나 <span style="background-color: #dcffe4">수리 과정을 통한 재사용 가능</span></p>
</li>
</ul>
<p><strong>불량 섹터의 관리 주체</strong></p>
<ul>
<li><p>1990년대 이전, 여분 섹터가 존재하지 않아, 
<span style="background-color: #dcffe4">운영체제가 파일 시스템을 통해 관리</span></p>
</li>
<li><p>운영체제의 파일 시스템에 의해 관리되지 <strong>않고,</strong>
<span style="background-color: #dcffe4">디스크 장치의 펌웨어</span>에 의해 관리됨</p>
</li>
<li><p>디스크 장치는 <span style="background-color: #dcffe4">불량 섹터 리스트, 여분의 온전한 섹터(spare sectors)</span>를 가짐</p>
</li>
<li><p>불량 섹터 발견 시, 스스로 여분 섹터로 매핑시켜
<span style="background-color: #dcffe4">운영체제는 불량 섹터에 대해 알지 못함</span></p>
</li>
</ul>
<h3 id="42-고수준-포맷팅hlf-high-level-formatting">4.2 고수준 포맷팅(HLF, High Level Formatting)</h3>
<ul>
<li><p>저수준 포맷된 하드 디스크를 여러 개의 <span style="background-color: #dcffe4">파티션(논리적인 공간)</span>으로 나누고,
<span style="background-color: #dcffe4">각 파티션에 파일 시스템을 구축하는 과정</span></p>
</li>
<li><p>디스크의 저장 공간을 <span style="background-color: #dcffe4">파티션(partition)으로 분할하는 과정부터 시작</span></p>
</li>
<li><p>디스크에는 <span style="background-color: #dcffe4">여러 개의 파티션</span>을 둘 수 있으며, 
<span style="background-color: #dcffe4">파티션마다 서로 다른 운영체제</span>를 선택할 수 있음</p>
</li>
<li><p>단, 부팅은 그 중 <span style="background-color: #dcffe4">한 파티션에서만</span> 이루어짐*</p>
</li>
<li><p>디스크를 포맷하고 파티션 나누는 방법은 두 방식이 있음</p>
</li>
<li><p>MBR/GPT 포맷*</p>
</li>
</ul>
<h4 id="mbr-포맷---전통-방식">MBR 포맷 - 전통 방식</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/33d3354c-a09a-44b2-9d13-b88b3aecde56/image.png" alt=""></p>
<ul>
<li>파티션에 관한 정보는 <span style="background-color: #dcffe4">부트 섹터</span>에 기록됨</li>
<li>이 부트 섹터를 <span style="background-color: #dcffe4">MBR(Master Boot Record)</span>이라고 하며, <span style="background-color: #dcffe4">크기는 512바이트*</span></li>
</ul>
<hr>
<p><strong>MBR의 구성</strong></p>
<ul>
<li><p><strong>부트 로더</strong> 
446바이트로 <span style="background-color: #dcffe4">부팅시 실행되는 프로그램</span></p>
</li>
<li><p><strong>파티션 테이블</strong>
64바이트로 최대 4개의 파티션 정보 기록. 각 파티션 정보는 16바이트</p>
</li>
<li><p><strong>매직 번호</strong>
2바이트 크기로 <strong><code>0xAA55</code></strong> 값 기록. <span style="background-color: #dcffe4"><strong><code>0xAA55</code></strong>가 아니면 MBR이 아니라고 판단</span></p>
</li>
</ul>
<hr>
<ul>
<li><p>파티션을 나누지 않았을 때에는 <span style="background-color: #dcffe4">파티션이 1개만 있는 것</span></p>
</li>
<li><p>파티션 테이블에도 1개만 유효함*</p>
</li>
<li><p>부팅이 시작되면 MBR에 저장된 <span style="background-color: #dcffe4">부트 로더</span> 프로그램이 메모리에 적재/실행됨</p>
</li>
<li><p>파티션 테이블을 검사하여 현재 부팅할 운영체제를 담고 있는
<span style="background-color: #dcffe4">활성 파티션(active partition)</span>을 찾음</p>
</li>
<li><p>이후 활성 파티션의 부트 섹터를 메모리에 적재시키고 코드를 실행*</p>
</li>
<li><p><span style="background-color: #dcffe4">파티션 개수가 제한적</span>이고 파티션 정보에서 <span style="background-color: #dcffe4">섹터 개수</span>를 나타내는 비트가 32</p>
</li>
<li><p>한 파티션이 2$^{32}$개의 섹터까지 가능*</p>
</li>
<li><p>한 섹터의 크기가 512바이트이면, 2$^{32}$ x 0.5K = 2TB가 파티션의 최대 크기*</p>
</li>
<li><p>4K 섹터 디스크의 경우, 섹터 크기가 8배이므로 
<span style="background-color: #dcffe4">파티션의 크기가 16TB까지 가능</span></p>
</li>
<li><p>그러나 MBR 포맷 사용 시, 
섹터 크기를 512바이트로 다루고 있기 때문에 <span style="background-color: #dcffe4">파티션의 크기가 2TB로 고정됨</span></p>
</li>
</ul>
<blockquote>
<p><strong>MBR 방식의 단점은 <span style="background-color: #dcffe4">파티션의 크기가 2TB로 제한되며 부팅 속도가 느림</span></strong></p>
</blockquote>
<h4 id="gptguid-partition-table-포맷---현대-방식">GPT(GUID partition table) 포맷 - 현대 방식</h4>
<ul>
<li><p><span style="background-color: #dcffe4"><strong>UEFI(Unfixed Extensible Firmware Interface) 펌웨어</strong>를 가진 
컴퓨터에서만 사용하는 포맷 방식</span></p>
</li>
<li><p>2TB 이상의 파티션을 만들 수 없는 <span style="background-color: #dcffe4">MBR의 문제점을 개선</span></p>
</li>
<li><p>최근에 나온 대부분의 컴퓨터는 UEFI 펌웨어를 내장하고 있기 때문에
<span style="background-color: #dcffe4">MBR 포맷이나 GPT 포맷 중 선택하여 사용할 수 있음</span></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/982246d6-b0ef-494d-9804-e6abd8f7c674/image.png" alt=""></p>
<p><strong>1) 보호 MBR 섹터가 첫 번째 섹터에 있는 이유</strong></p>
<ul>
<li><p>GPT 포맷 디스크를 <span style="background-color: #dcffe4">MBR 포맷 디스크로 보이게 하려는 목적</span></p>
</li>
<li><p>사용자가 GPT 포멧 디스크에 MBR 포맷만 다루는 유틸리티를 실행했을 때,
이 유틸리티가 현재 디스크에 <span style="background-color: #dcffe4">훼손이 발생한 것으로 오인</span>하여
<span style="background-color: #dcffe4">디스크를 포맷하거나 훼손하는 등의 오작동을 막기 위함</span>*</p>
</li>
<li><p>보호 MBR 섹터의 구조를 <span style="background-color: #dcffe4">MBR 포맷의 첫 섹터와 동일하게 구성</span></p>
</li>
<li><p>파티션 테이블의 첫 번째 항목의 <span style="background-color: #dcffe4">섹터 개수를 최대치(<code>0xFFFFFFFF</code>)로 기록</span></p>
</li>
<li><p>전체 디스크를 모두 사용하고 있다고 착각하게 만듦*</p>
</li>
<li><p>보호 MBR은 <span style="background-color: #dcffe4">MBR 포맷과의 호환성은 유지</span>하면서
MBR 포맷만 지원하는 유틸리티가 GPT 포맷 디스크를 잘못 인식하여
<span style="background-color: #dcffe4">덮어쓰는 것을 방지하기 위함</span></p>
</li>
</ul>
<p><strong>2) MBR 섹터의 파티션 테이블에서 
첫 번째 파티션 항목의 <span style="background-color: #dcffe4">파티션 타입 필드 값을 <code>0xEE</code>로 기록</span></strong></p>
<ul>
<li>GPT 포맷을 다루는 소프트웨어들에게 
현재 디스크가 GPT 포맷임을 나타내기 위해</li>
</ul>
<p><strong>3) 진짜 파티션 테이블은 GPT 헤더 다음에 구성됨</strong></p>
<ul>
<li><p><span style="background-color: #dcffe4">총 128개까지 파티션이 가능</span></p>
</li>
<li><p>한 파티션의 크기는 18엑사바이트(Exa Byte)까지 가능*</p>
</li>
<li><p><strong>GPT헤더의 구성 요소</strong></p>
</li>
<li><p>파티션 테이블의 개수
파티션 테이블이 시작하는 디스크의 위치
각 항목의 크기
파티션 테이블 항목 개수
파티션 테이블의 오류 확인을 위한 체크섬(checksum) 정보 등*</p>
</li>
</ul>
<p><strong>4) GPT 헤더와 파티션 테이블을 이중화</strong></p>
<ul>
<li>GPT 헤더와 파티션 테이블은 <span style="background-color: #dcffe4">디스크의 마지막 영역에 백업본을 둠</span></li>
<li>기본 GPT 헤더와 파티션 테이블이 손상될 때 복구 가능-<span style="background-color: #dcffe4">높은 신뢰도*</span></li>
</ul>
<h4 id="mbr-포맷-디스크의-부팅">MBR 포맷 디스크의 부팅</h4>
<ul>
<li>UEFI를 지원하지 않는 컴퓨터 디스크는 MBR 포맷으로 포맷팅되어야 하며,
<span style="background-color: #dcffe4">기존 BIOS 펌웨어에 의해 부팅됨</span></li>
</ul>
<p>*<em>1) *</em>전원이 켜지면 CPU는 <span style="background-color: #dcffe4">BIOS 펌웨어 코드의 실행을 시작</span>하여
<span style="background-color: #dcffe4">메모리나 기타 장치들을 테스트하고 초기화</span></p>
<p>*<em>2) *</em>BIOS 안에 작성된 <span style="background-color: #dcffe4">부트스트랩 코드를 실행</span></p>
<p><em><em>3) *</em>부트스트랩 코드는 하드 디스크의 첫 번째 섹터인
<span style="background-color: #dcffe4">MBR 섹터를 메모리로 읽어들이고 그곳으로 점프</span>
*MBR 섹터의 첫 번째 부분에 부트 로더가 작성되어 있음</em></p>
<p>*<em>4) *</em>CPU가 이 코드를 실행하면 <span style="background-color: #dcffe4">활성 파티션을 찾고</span> 
활성 파티션의 부트 섹터를 메모리로 읽어들이고 실행시킴</p>
<p>*<em>5) *</em>부트 섹터의 코드는 자신의 파티션에 설치된 <span style="background-color: #dcffe4">운영체제 커널을 메모리로 적재</span></p>
<p>*<em>6) *</em>커널로 제어를 넘기고, 커널 코드로 점프하여 
<span style="background-color: #dcffe4">커널 코드를 실행하면 필요한 프로세스가 생성됨</span></p>
<h4 id="gpt-포맷-디스크의-부팅">GPT 포맷 디스크의 부팅</h4>
<p>*<em>1) *</em>전원이 켜지면 컴퓨터에 장착된 <span style="background-color: #dcffe4">UEFI 펌웨어가 실행됨</span></p>
<p><em><em>2) *</em>UEFI 펌웨어에 저장된 EFI 변수를 읽음
*EFI 변수에는 부팅 순서, 부트 로더의 경로명 등 <span style="background-color: #dcffe4">부팅에 관한 정보</span>가 저장됨</em></p>
<p>*<em>3) *</em>기본 파티션 테이블에서 EFI 시스템 파티션 탐색</p>
<ul>
<li><p>EFI 파티션은 FAT32로 포맷된 특별한 파티션</p>
</li>
<li><p>권장 크기는 100~550MB*</p>
</li>
<li><p>이 파티션 안에 디스크에 설치된 
<span style="background-color: #dcffe4">모든 운영체제들의 부트 로더 프로그램</span>이 저장되어 있음</p>
</li>
</ul>
<p>*<em>4) *</em>UEFI 펌웨어는 EFI 변수에 지시된 부팅 순서에 따라
<span style="background-color: #dcffe4">EFI 파티션에서 부트 로드 프로그램을 찾아 실행</span></p>
<p>*<em>5) *</em>부트 로더 프로그램은 해당 운영체제를 메모리에 적재한 후
커널 코드로 점프하여 <span style="background-color: #dcffe4">커널 코드에 의해 프로세스 생성</span></p>
<h4 id="bios와-uefi-펌웨어">BIOS와 UEFI 펌웨어</h4>
<ul>
<li><p>BIOS(Basic Input Output System)나 UEFI는 
컴퓨터 마더보드(mother board 또는 main board)에 장착된 
ROM(BIOS의 경우)이나 플래시 메모리와 같은 
<span style="background-color: #dcffe4">비휘발성 메모리에 저장된 소프트웨어</span></p>
</li>
<li><p>전원이 켜지는 시점에 실행되는 <span style="background-color: #dcffe4">최초의 프로그램</span></p>
</li>
<li><p>컴퓨터 메모리나 하드웨어들을 <span style="background-color: #dcffe4">테스트하고 초기화</span>하며 컴퓨터를 부팅시킴</p>
</li>
<li><p>UEFI는 운영체제와 마더보드나 
주변 장치 내에 장착된 펌웨어 사이의 <span style="background-color: #dcffe4">매개 역할</span></p>
</li>
<li><p>실질적으로 장치들과 입출력 버스 등을 제어하는
마더보드 펌웨어 위에서 실행되는 <span style="background-color: #dcffe4">작은 운영체제</span></p>
</li>
<li><p>과거 BIOS를 <span style="background-color: #dcffe4">레거시 BIOS</span>라고 하는게 좋음</p>
</li>
<li><p>UFEI와 BIOS를 구분하지 않기 때문*</p>
</li>
</ul>
<h4 id="windows에서-파티션-나누고-사용하기">Windows에서 파티션 나누고 사용하기</h4>
<ul>
<li>D:, E: 등의 <span style="background-color: #dcffe4">논리 드라이브로 사용</span></li>
<li>복구 파티션(recovery partition)</li>
<li>진단 도구나 진단 데이터를 저장하는 파티션</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/99c4e15f-8872-4dcd-ac28-0229614c6795/image.png" alt=""></p>
<h4 id="리눅스에서-파티션-나누고-활용하기">리눅스에서 파티션 나누고 활용하기</h4>
<ul>
<li>리눅스, MacOS 등 Unix-like 운영체제에서 각 디스크 파티션에는
<span style="background-color: #dcffe4">파일 시스템이 구축</span>되거나 <span style="background-color: #dcffe4">파티션이 통째로 스왑 영역</span>으로 사용됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/871109da-c22e-4efb-8611-cc705f860045/image.png" alt=""></p>
<ul>
<li><p>많은 경우 파티션을 디렉터리에 연결하여 
<span style="background-color: #dcffe4">전체를 하나의 파일 시스템으로 사용</span></p>
</li>
<li><p>이 것을 <span style="background-color: #dcffe4">* <strong><em>마운트(mount)</em></strong> <em>라고 함</em></span></p>
</li>
<li><p>3개의 파일 시스템을 디렉터리에 마운트시켜
<span style="background-color: #dcffe4">전체를 하나의 파일 시스템처럼 사용</span></p>
</li>
<li><p>다른 파티션에 연결하기 위해 사용되는 <code>home</code>이나 <code>kitae</code> 디렉터리를
<span style="background-color: #dcffe4">마운트 포인트(mount point)</span>라고 함</p>
</li>
<li><p>마운트 포인트는 다른 파티션에 설치된 파일 시스템의 <span style="background-color: #dcffe4">루트 디렉터리</span>가 됨*</p>
</li>
</ul>
<h2 id="05-ssd-저장-장치">05. SSD 저장 장치</h2>
<ul>
<li><p>SSD(Solid State Drive, Solid State Disk)는 
플래시 메모리(flash memory)를 저장소로 사용한 <span style="background-color: #dcffe4">비휘발성 기억 장치</span></p>
</li>
<li><p>메모리 계층 구조의 <span style="background-color: #dcffe4">최하단에 위치</span>하는 보조 기억 장치</p>
</li>
<li><p>하드 디스크와 달리 회전하는 디스크나 모터, 움직이는 헤드 등
<span style="background-color: #dcffe4">기계 부품을 사용하지 않는 순수한 반도체 기억 장치</span></p>
</li>
<li><p>디스크보다 값이 비싸지만 <span style="background-color: #dcffe4">입출력 속도가 5~50배</span> 정도 빠름</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/c3480973-84af-4cf6-9d79-2c0ff8ffdd13/image.png" alt=""></p>
<h3 id="51-ssd-장치의-구조와-인터페이스">5.1 SSD 장치의 구조와 인터페이스</h3>
<ul>
<li>SSD는 4개의 하드웨어 요소로 구성됨</li>
<li>플래시 메모리
DRAM 캐시
호스트 인터페이스
SSD 제어기*</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/aac6fe3f-5a70-48f9-9fad-d1bbc7c3e89e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/7341b3ad-b2a5-454e-89e7-83372d7aa788/image.png" alt=""></p>
<h4 id="플래시-메모리">플래시 메모리</h4>
<ul>
<li><p>SSD의 <span style="background-color: #dcffe4">내부 저장소</span></p>
</li>
<li><p><span style="background-color: #dcffe4">플래시 제어기</span>에 의해 읽혀지고 기록됨</p>
</li>
</ul>
<h4 id="dram-캐시">DRAM 캐시</h4>
<ul>
<li><p><span style="background-color: #dcffe4">입출력 성능</span>을 높이기 위해 DRAM 캐시를 두고 있음</p>
</li>
<li><p>DRAM을 장착한 SSD는 상대적으로 <span style="background-color: #dcffe4">고가</span></p>
</li>
<li><p><span style="background-color: #dcffe4">디스크 캐시</span>와 비슷한 목적으로 사용되어 
<span style="background-color: #dcffe4">읽은 데이터나 쓸 데이터를 임시 저장</span></p>
</li>
<li><p>정전이 발생하면 DRAM 캐시에 들어 있는 <span style="background-color: #dcffe4">데이터를 모두 잃음</span></p>
</li>
<li><p>이 때문에 DRAM을 사용하지 않는 제조업체도 있음*</p>
</li>
</ul>
<h4 id="호스트-인터페이스-회로">호스트 인터페이스 회로</h4>
<ul>
<li><p><span style="background-color: #dcffe4">SSD 장치와 호스트를 연결하는 <strong>물리적인 인터페이스</strong></span></p>
</li>
<li><p>SATA(Serial ATA)
SAS(Serial attached SCSI)
PCIe(PCI express) 등의 여러 가지가 존재함</p>
</li>
<li><p>가장 많이 사용되는 타입은 <strong>PCIe</strong>이며, 
PCIe 인터페이스를 가진 SSD를 <strong>NVMe 드라이브</strong>라고 함</p>
</li>
</ul>
<h4 id="ssd-제어기ssd-controller">SSD 제어기(SSD Controller)</h4>
<ul>
<li><p>SSD 장치에서 <span style="background-color: #dcffe4">가장 중요한 부분</span></p>
</li>
<li><p>호스트로부터 받은 <span style="background-color: #dcffe4">데이터를 플래시 메모리에 기록</span>하고
플래시 메모리로부터 읽은 <span style="background-color: #dcffe4">데이터를 호스트로 전송</span></p>
</li>
<li><p>전반적인 작업을 제어함*</p>
</li>
<li><p>논리 블록 주소를 물리 블록 주소로 바꾸는 <span style="background-color: #dcffe4">매핑 테이블을 만들고 관리</span></p>
</li>
<li><p><span style="background-color: #dcffe4">논리 주소의 물리 주소 변환</span></p>
</li>
<li><p><strong>wear leveling</strong>와 같은 
플래시 메모리에서 <span style="background-color: #dcffe4">새로운 페이지를 저장할 장소 찾기</span>나</p>
</li>
<li><p><em>garbage collection*</em> 등의 기능을 수행함</p>
</li>
<li><p>SSD 제어기는 <span style="background-color: #dcffe4">하나의 칩(SoC, System on Chip)</span>으로 만듦</p>
</li>
<li><p>SSD 제어기 내부 <span style="background-color: #dcffe4">플래시 메모리 알고리즘</span>은 SSD 제조업체의 경쟁력</p>
</li>
<li><p>공개하지 않고 있음*</p>
</li>
</ul>
<h3 id="52-ssd-메모리의-논리-구조">5.2 SSD 메모리의 논리 구조</h3>
<h4 id="페이지와-블록">페이지와 블록</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/71194ddf-2542-4246-b57a-dc136ae22372/image.png" alt=""></p>
<ul>
<li>플래시 메모리는 <span style="background-color: #dcffe4">여러 개의 블록</span>들로 구성되며 
각 블록은 다시 <span style="background-color: #dcffe4">페이지(page)</span>들로 구성됨</li>
</ul>
<blockquote>
<p><em>SSD 장치 내에서 플래시 메모리에 읽고 쓰는 단위는</em> <strong><em>페이지</em></strong></p>
</blockquote>
<ul>
<li><p>플래시 메모리의 페이지는 
<span style="background-color: #dcffe4">운영체제의 가상 메모리에서 다루는 페이지와는 <strong>아무런 관계가 없음</strong></span></p>
</li>
<li><p>응용프로그램이 한 바이트를 읽거나 쓰고자 했어도, <span style="background-color: #dcffe4">페이지 단위</span>로 읽고 씀</p>
</li>
<li><p>페이지 크기는 보통 4KB<del>16KB, 블록 크기는 128KB</del>256KB</p>
</li>
<li><p>공정 기술이 발전함에 따라 크기도 커지는 추세*</p>
</li>
<li><p>하드 디스크와 달리, 
<span style="background-color: #dcffe4">블록이나 페이지의 위치에 따라 <strong>액세스 하는 시간이 다르지 않음</span></strong></p>
</li>
</ul>
<h4 id="주소-변환">주소 변환</h4>
<ul>
<li><p>운영체제의 파일 시스템은 SSD를 <span style="background-color: #dcffe4">디스크 장치로 인식</span></p>
</li>
<li><p>SSD에 대해서도 똑같이 액세스하고자 하는 데이터의 
<span style="background-color: #dcffe4">논리 블록 번호(LBA)</span> 발생</p>
</li>
<li><p>플래시 메모리에 저장된 각 논리 블록에 대해
<span style="background-color: #dcffe4">플래시 메모리의 블록 번호와 페이지 번호</span>를 나타내는 
<span style="background-color: #dcffe4">주소 변환 테이블</span>을 만들고 유지 및 관리</p>
</li>
<li><p>블록 읽기/쓰기 시 <span style="background-color: #dcffe4">주소 변환 테이블 참조</span>*</p>
</li>
</ul>
<h3 id="53-ssd의-플래시-변환-계층">5.3 SSD의 플래시 변환 계층</h3>
<ul>
<li><p>하드 디스크를 기반으로 하는 운영체제의 파일 시스템은
디스크를 <span style="background-color: #dcffe4">섹터</span> 단위로 인식함</p>
</li>
<li><p>SSD에서는 <span style="background-color: #dcffe4">페이지</span> 단위로 인식함</p>
</li>
</ul>
<p><strong>섹터를 기반으로 하는 기존 파일 시스템은 SSD를 읽고 쓸 수 없음</strong></p>
<ul>
<li><p>이를 해결하기 위해 SSD 제어기 내에 
<span style="background-color: #dcffe4">플래시 변환 계층(FTL, Flash Translation Layer)</span>이라는 <span style="background-color: #dcffe4">펌웨어</span>를 둠</p>
</li>
<li><p>플래시 변환 계층은 SSD 내부의 물리적 특성을 
<span style="background-color: #dcffe4">운영체제로(파일 시스템)부터 숨김</span></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/bcc4cb1a-ea66-45b1-92ba-b37121214b6a/image.png" alt=""></p>
<ul>
<li><p>플래시 변환 계층은 운영체제의 파일 시스템에게
<span style="background-color: #dcffe4">SSD가 섹터 기반의 저장소</span>로 보이게 함</p>
</li>
<li><p>하드 디스크와 달리 SSD에서는 
<span style="background-color: #dcffe4">논리 블록이 저장된 물리 페이지 주소와 함께 주소 변환 테이블도 계속 바뀜</span></p>
</li>
<li><p>특이한 페이지 쓰기 방식과 가비지 컬렉션 때문*</p>
</li>
<li><p>주소 변환 테이블은 
<span style="background-color: #dcffe4">SSD 제어기의 내부 메모리(RAM)</span>과 <span style="background-color: #dcffe4">SSD 플래시 메모리</span>에 저장됨</p>
</li>
<li><p>작동을 시작할 때 <span style="background-color: #dcffe4">플래시 메모리로부터 내부 메모리로</span> 읽어 들임*</p>
</li>
<li><p>SSD의 용량이 커지면 주소 변환 테이블도 같이 커지는 약점 존재*</p>
</li>
</ul>
<h3 id="54-ssd-입출력-동작">5.4 SSD 입출력 동작</h3>
<h4 id="페이지-읽기read">페이지 읽기(read)</h4>
<ul>
<li>플래시 메모리에서의 페이지 읽기는 단순히 <span style="background-color: #dcffe4">해당 페이지를 읽기만 하면 됨</span></li>
</ul>
<h4 id="페이지-쓰기프로그램writeprogram">페이지 쓰기/프로그램(write/program)</h4>
<ul>
<li><p>플래시 메모리에 쓰기를 <strong>프로그램(program)</strong>이라고 부름</p>
</li>
<li><p><span style="background-color: #dcffe4">동일한 페이지에 덮어쓰기는 불가능</span>하며,
오직 <span style="background-color: #dcffe4">빈 페이지</span>에만 쓸 수 있음</p>
</li>
<li><p>SSD가 빈 페이지를 찾고 그 곳에 데이터를 기록</p>
</li>
<li><p>빈 페이지를 만들기 위해서는 <span style="background-color: #dcffe4">지우기 연산</span>이 이루어져야 함</p>
</li>
<li><p>이미 기록된 페이지를 <span style="background-color: #dcffe4">수정하는 경우 <strong>read-modify-write</strong></span> 연산을 실행</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/64435b55-0c24-45fa-8c47-e7d5f874809b/image.png" alt=""></p>
<p><strong>1) 페이지 읽기(read)</strong> - SSD 제어기의 메모리로 페이지 읽어 들이기</p>
<p><strong>2) 페이지 수정(modify)</strong> - SSD 제어기의 메모리에서 페이지 수정</p>
<p><strong>3) 페이지 쓰기(write)</strong> - 수정된 페이지를 새로운 빈 페이지를 찾아 기록</p>
<p>*<em>4) *</em> 플래시 메모리의 이전 페이지를 <code>dirty</code> 또는 <code>stale</code>로 표시</p>
<ul>
<li><p>플래시 메모리는 <span style="background-color: #dcffe4">쓰기와 지우기 횟수에 제한</span>이 있기 때문에
특정 블록에 작업이 반복되면 <span style="background-color: #dcffe4">블록의 수명이 빨리 다하게 됨</span></p>
</li>
<li><p>각 페이지에 대해 비거나, <code>dirty/stale</code>, 유효한(<code>valid</code>)페이지 상태 정보는
<span style="background-color: #dcffe4">SSD 제어기</span>에 의해 따로 관리됨</p>
</li>
<li><p>SSD가 데이터를 읽거나 빈 페이지에 데이터를 쓰는 속도는 매우 빠르지만,
<span style="background-color: #dcffe4">페이지를 수정하는 속도는 매우 느림</span></p>
</li>
<li><p>페이지를 수정하여 다시 기록할 때마다 <span style="background-color: #dcffe4">주소 변환 테이블도 같이 수정됨</span>*</p>
</li>
<li><p>빈 페이지를 <span style="background-color: #dcffe4">동일한 블록에서 찾을 필요는 없음</span></p>
</li>
</ul>
<h4 id="블록-지우기erase">블록 지우기(erase)</h4>
<ul>
<li><p>플래시 메모리에서 <span style="background-color: #dcffe4">쓰기와 지우기는 서로 다른 기능</span></p>
</li>
<li><p>플래시 메모리는 <span style="background-color: #dcffe4">블록 단위로 데이터를 지움</span></p>
</li>
<li><p>높은 전압이 걸리면 주변 셀들의 값이 훼손될 수 있기 때문*</p>
</li>
<li><p>블록 지우기는 SSD가 <span style="background-color: #dcffe4">가비지 컬렉션</span>을 수행할 때
<span style="background-color: #dcffe4">빈 블록을 확보</span>하기 위해 내부에서 벌어지는 동작</p>
</li>
</ul>
<h4 id="가비지-컬렉션garbage-collection">가비지 컬렉션(garbage collection)</h4>
<ul>
<li><p>블록 내 페이지들의 수정이 계속되어 
<span style="background-color: #dcffe4"><code>dirty</code> 페이지가 많아지고 빈 페이지가 없을 때 
<strong>SSD 제어기</strong>에 의해 <strong>가비지 컬렉션</strong>이 수행됨</span></p>
</li>
<li><p><span style="background-color: #dcffe4"><strong>값이 기록된 페이지(<code>valid</code>)</strong>들을 다른 페이지들을 </p>
</li>
<li><p><em>다른 블록의 빈 페이지로 복사*</em></span>하고 <span style="background-color: #dcffe4"><strong>원본 블록들을 지우는 과정</strong></span></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/71d97a48-5677-4ad0-b30e-56f94f48bd52/image.png" alt=""></p>
<h4 id="웨어-레벨링wear-leveling-균등-쓰기-분배">웨어 레벨링(wear leveling, 균등 쓰기 분배)</h4>
<ul>
<li><p>플래시 메모리의 모든 블록에 <span style="background-color: #dcffe4">쓰기를 균등하게 분배</span>하여
<span style="background-color: #dcffe4">특정 블록에 대한 과도한 쓰기를 막음</span></p>
</li>
<li><p>플래시 메모리 블록은 <span style="background-color: #dcffe4">쓰거나 지우기 횟수에 비례하여 <strong>닳아(wear)</strong>감</span></p>
</li>
<li><p>읽기는 영향을 주지 않고 <span style="background-color: #dcffe4"><strong>쓰기</strong>가 영향을 줌</span></p>
</li>
<li><p>SSD 제어기는 플래시 메모리의 <span style="background-color: #dcffe4">각 블록마다 지우기 횟수를 기억</span>하고 
<span style="background-color: #dcffe4">쓰기 시, 지우기 횟수가 가장 적은 블록</span>을 선택함</p>
</li>
</ul>
<h4 id="55-ssd의-용도">5.5 SSD의 용도</h4>
<ul>
<li><p>SSD의 저장소 역할을 하는 NAND 플래시는 
오랜 시간 동안 전원이 공급되지 않으면 <span style="background-color: #dcffe4">전하(charge)가 누출</span>되어
<span style="background-color: #dcffe4">저장된 데이터가 지워짐</span></p>
</li>
<li><p>백업 저장 목적에는 적합하지 않음*</p>
</li>
<li><p>읽기가 많은 운영체제 코드나 프로그램을 설치하는 용도로는 적합하지만,
쓰기나 수정 작업이 많은 <span style="background-color: #dcffe4">스왑 영역</span>이나 
파일이 임시로 생성되었다가 지워지는 <span style="background-color: #dcffe4">임시 파일 시스템</span>에 적합하지 않음</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[브로드캐스트 리시버, 컨텐트 리졸버]]></title>
            <link>https://velog.io/@hi_soap/%EB%B8%8C%EB%A1%9C%EB%93%9C%EC%BA%90%EC%8A%A4%ED%8A%B8-%EB%A6%AC%EC%8B%9C%EB%B2%84-%EC%BB%A8%ED%85%90%ED%8A%B8-%EB%A6%AC%EC%A1%B8%EB%B2%84</link>
            <guid>https://velog.io/@hi_soap/%EB%B8%8C%EB%A1%9C%EB%93%9C%EC%BA%90%EC%8A%A4%ED%8A%B8-%EB%A6%AC%EC%8B%9C%EB%B2%84-%EC%BB%A8%ED%85%90%ED%8A%B8-%EB%A6%AC%EC%A1%B8%EB%B2%84</guid>
            <pubDate>Sat, 30 May 2026 04:56:08 GMT</pubDate>
            <description><![CDATA[<h2 id="학습목표">학습목표</h2>
<ul>
<li><span style="background-color: #dcffe4">브로드캐스트 리시버</span>의 개념을 이해하고 사용할 수 있다.</li>
<li><span style="background-color: #dcffe4">컨텐트 프로바이더와 리졸버</span> 개념을 이해하고 컨텐트 리졸버를 사용할 수 있다.</li>
<li><span style="background-color: #dcffe4">다중 동적 권한 요청</span>을 이해하고 사용할 수 있다.</li>
</ul>
<h2 id="브로드캐스트-리시버broadcast-receiver">브로드캐스트 리시버(Broadcast Receiver)</h2>
<p><strong>시스템이나 다른 앱이 방송하는 <span style="background-color: #dcffe4">메시지를 받는 리시버</span></strong></p>
<p><strong>시스템 이벤트시 방송</strong></p>
<ul>
<li>시스템 부트 완료, 충전 시작, 네트워크 연결 변경, 문자 수신 등의 이벤트</li>
<li>문자 수신과 같은 민감한 정보는 권한을 필요로 함</li>
</ul>
<p><strong>사용 방법</strong></p>
<ul>
<li><code>BroadcastReceiver</code> 상속한 클래스, <code>onReceive()</code> 재정의</li>
<li><code>ContextCompat.registerReceiver()</code>로 1번의 클래스 객체 등록</li>
<li>이 때 수신할 방송 종류를 <code>IntentFilter</code>로 정의하여 포함*</li>
<li>앱이 보내는 방송을 수신은 <code>ContextCompat.RECEIVER_EXPORTED</code> 플래그 설정*</li>
<li>더 이상 수신 할 필요가 없을 때 <code>unregisterReceiver()</code> 호출*</li>
</ul>
<h3 id="broadcastreceiver-클래스-상속">BroadcastReceiver 클래스 상속</h3>
<h4 id="onreceive-재정의"><code>onReceive()</code> 재정의</h4>
<pre><code class="language-kotlin">class MyBroadcastReceiver(private val onReceiveMessage:
(String, String) -&gt; Unit) : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) { ... }
}</code></pre>
<h3 id="수신자-등록해지-수신자에게서-메시지-받는-콜백">수신자 등록/해지, 수신자에게서 메시지 받는 콜백</h3>
<pre><code class="language-kotlin">private fun startBroadcastReceiver() {
    IntentFilter().also {
        it.addAction(Intent.ACTION_POWER_CONNECTED)
        it.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
        it.addAction(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) // 권한 필요
        ContextCompat.registerReceiver(this, broadcastReceiver, it, ContextCompat.RECEIVER_EXPORTED)
    }
}</code></pre>
<pre><code class="language-kotlin">override fun onStart() {
    super.onStart()
    startBroadcastReceiver()
}
override fun onStop() {
    super.onStop()
    unregisterReceiver(broadcastReceiver)
}</code></pre>
<h3 id="필요한-권한-등록-동적-권한-요청">필요한 권한 등록, 동적 권한 요청</h3>
<p><strong><code>Telephony.Sms.Intents.SMS_RECEIVED_ACTION</code>에 필요한 권한</strong></p>
<p><strong><code>AndroidManifest.xml</code>에 권한 표시</strong></p>
<pre><code class="language-xml">&lt;uses-feature android:name=
    &quot;android.hardware.telephony&quot; android:required=&quot;false&quot; /&gt;
&lt;uses-permission android:name=&quot;android.permission.RECEIVE_SMS&quot; /&gt;</code></pre>
<p><strong>동적 권한 요청</strong></p>
<ul>
<li><p>1개 권한 요청</p>
<pre><code class="language-kotlin">rememberLauncherForActivityResult(
  contract = ActivityResultContracts.RequestPermission(),
  onResult = 결과 받을 람다
).launch(요청할 권한)</code></pre>
</li>
<li><p>2개 이상 권한 요청</p>
<pre><code class="language-kotlin">rememberLauncherForActivityResult(
  contract = ActivityResultContracts.RequestMultiplePermission(),
  onResult = 결과 받을 람다
).launch(요청할 권한들의 배열)</code></pre>
</li>
</ul>
<h2 id="content-provider--resolver">Content Provider / Resolver</h2>
<ul>
<li>안드로이드에서 <span style="background-color: #dcffe4">다른 앱에 데이터를 제공해주기 위해 표준화된 인터페이스</span></li>
<li>적절한 <span style="background-color: #dcffe4">데이터 은닉과 보호 기능</span> 제공*
<img src="https://velog.velcdn.com/images/hi_soap/post/6b463bab-4a79-468e-a74e-13d2f699c317/image.png" alt=""></li>
</ul>
<h3 id="content-resolver">Content Resolver</h3>
<ul>
<li>Content Provider에 접근하기 위한 방법</li>
<li>CRUD - <code>Create</code>, <code>Retrieve</code>, <code>Update</code>, <code>Delete</code> 기능 제공</li>
<li>Content Provider에 따라 <span style="background-color: #dcffe4">접근 권한</span>이 필요한 경우가 있음</li>
<li><code>CallLog:READ_CALL_LOG</code>*</li>
</ul>
<h4 id="사용-방법데이터-읽기-retrieve">사용 방법(데이터 읽기, Retrieve)</h4>
<ul>
<li><strong>1)</strong> Context에서 ContentResolver 객체 가져오기</li>
<li><strong>2)</strong> query로 컨텐츠 URI와 조건 등을 명시해서 실행, cursor 리턴</li>
<li>Create, Update, Delete의 경우, query 대신 insert, update delete 사용*</li>
<li><strong>3)</strong> cursor를 사용하여 데이터 읽기</li>
</ul>
<h3 id="content-uri">Content URI</h3>
<ul>
<li>query, insert, update, delete 모두 <span style="background-color: #dcffe4">첫 번째 인자가 Content URI</span></li>
<li>Content URI는 접근하려는 <span style="background-color: #dcffe4">content provider의 주소</span></li>
</ul>
<pre><code class="language-kotlin">cursor = context.contentResolver.query(
    android.net.Uri.uri, // Content URI, ex) CallLog.Calls.CONTENT_URI, 
    String[] projection, // 가져올 테이블의 칼럼 이름
    String selection, // 조건, SQL의 WHERE 절과 유사
    String[] selectionArgs, // selection의 ?에 들어갈 인자
    String sortOrder // SQL의 ORDER BY에 해당
)</code></pre>
<pre><code class="language-kotlin">cursor.moveToNext()
cursor.getColumnIndex(.getString or .getLong) // 칼럼 인덱스</code></pre>
<h3 id="권한-확인">권한 확인</h3>
<pre><code class="language-kotlin">private fun hasPermission(perm: String) = 
    context.checkSelfPermission(perm) == PackageManager.PERMISSION_GRANTED</code></pre>
<h3 id="필요한-권한-등록">필요한 권한 등록</h3>
<pre><code class="language-xml">&lt;uses-permission android:name=&quot;android.permission.READ_CALL_LOG&quot; /&gt;

&lt;uses-permission
    android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot;
    android:maxSdkVersion=&quot;32&quot; /&gt;

&lt;uses-permission android:name=&quot;android.permission.READ_MEDIA_IMAGES&quot; /&gt;

&lt;uses-permission android:name=
                 &quot;android.permission.READ_MEDIA_VISUAL_USER_SELECTED&quot; /&gt;</code></pre>
<h2 id="broadcast와-contentresolver를-함께-응용">Broadcast와 ContentResolver를 함께 응용</h2>
<ul>
<li>SMS를 수신 후 발송자 이름을 주소록에서 조회</li>
<li>추가로 필요한 권한 <code>READ_CONTACTS</code><pre><code class="language-xml">&lt;uses-permission android:name=&quot;android.permission.READ_CONTACTS&quot; /&gt;</code></pre>
<pre><code class="language-kotlin">permissions += Manifest.permission.READ_CONTACTS</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CD 파이프라인 해설]]></title>
            <link>https://velog.io/@hi_soap/CD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%ED%95%B4%EC%84%A4</link>
            <guid>https://velog.io/@hi_soap/CD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%ED%95%B4%EC%84%A4</guid>
            <pubDate>Wed, 27 May 2026 08:12:54 GMT</pubDate>
            <description><![CDATA[<h2 id="cd-continuous-development">CD (Continuous Development)</h2>
<p><code>Job 1.</code>: Docker 빌드 + E2E 테스트
<code>Job 2.</code>: GHCR에 이미지 등록
<code>Job 3.</code>: GitHub Pages 배포</p>
<h4 id="핵심-흐름">핵심 흐름</h4>
<p>코드 push → 도커 이미지를 빌드해서 E2E 통과
→ 컨테이너 레지스트리 (GHCR)에 이미지 올리기
→ 정적 파일은 GitHub Pages로 웹 배포
<em>GitHub Pages: GitHub에서 제공하는 웹 서버</em></p>
<h3 id="용어-정리">용어 정리</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/e52df28e-9a0f-42a0-91e9-e455b8019605/image.png" alt=""></p>
<h3 id="part-1">Part 1.</h3>
<h4 id="워크플로우-메타데이터">워크플로우 메타데이터</h4>
<pre><code class="language-yaml">name: CD

on: 
  push:
    branches: [main]</code></pre>
<ul>
<li><code>name</code>: 워크플로우 이름. GitHub Actions 탭에 그대로 노출</li>
<li><code>on</code>: 트리거를 정의하는 키워드</li>
<li><code>push</code>: push 이벤트 발생 시 실행</li>
<li><code>branches</code>: main 브랜치에 push될 때만 실행</li>
</ul>
<h4 id="워크플로우-권한">워크플로우 권한</h4>
<pre><code class="language-yaml">permissions: 
  contents: read
  packages: write
  pages: write
  id-token: write</code></pre>
<p><strong>필요한 권한만 명시적으로 부여</strong></p>
<ul>
<li><code>contents: read</code>: 저장소 코드를 읽기만 함 (체크아웃에 필요)</li>
<li><code>packages: write</code>: GHCR에 도커 이미지를 업로드하므로 쓰기 권한 필요</li>
<li><code>pages: write</code>: GitHub Pages 배포에 필요한 쓰기 권한</li>
<li><code>id-token: write</code>: OIDC 토큰 발급용. 비밀번호 없이 신원을 증명</li>
</ul>
<h4 id="환경-변수">환경 변수</h4>
<pre><code class="language-yaml">env:
  IMAGE_NAME: ghcr.io/${{ github.repository }}</code></pre>
<p><strong>워크플로우 전체에서 쓸 환경 변수</strong></p>
<ul>
<li><code>IMAGE_NAME</code>: 도커 이미지의 풀네임</li>
<li><code>${{ github.repository }}</code>: GitHub이 자동으로 채워주는 사용자명/저장소</li>
</ul>
<h3 id="part-2">Part 2.</h3>
<h4 id="job-1-정의--outputs">Job 1. 정의 + outputs</h4>
<pre><code class="language-yaml">jobs:
  build-and-e2e:
    name: 1) Docker + E2E
    runs-on: ubuntulatest
    outpits:
      image-tag: ${{ steps.meta.outputs.tag }}</code></pre>
<ul>
<li><code>build-and-e2e</code>: 이 job의 고유 ID</li>
</ul>
<h4 id="체크아웃--커밋-태그-만들기">체크아웃 + 커밋 태그 만들기</h4>
<pre><code class="language-yaml">steps:
  - uses: actions/checkout@v4
  - id: meta
    run: echo &quot;tag=sha-${GITHUB_SHA::7}&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;</code></pre>
<ul>
<li><code>${GITHUB_SHA::7}</code>: 현재 커밋 해시의 앞 7자리만 잘라냄</li>
</ul>
<h4 id="docker-빌드--컨테이너-실행">Docker 빌드 + 컨테이너 실행</h4>
<pre><code class="language-yaml">- run: docker build -t calculator:test .

- run: docker run -d --rm --name calc -p 8080:80 calculator:test</code></pre>
<ul>
<li><code>-d</code>: detached 백그라운드 모드로 실행</li>
<li><code>-rm</code>: 컨테이너가 멈추면 자동 삭제</li>
<li><code>--name calc</code>: 컨테이너 이름을 calc로 지정</li>
<li><code>p 8080:80</code>: 호스트 8080 포트를 컨테이너 80포트에 연결</li>
</ul>
<h4 id="컨테이너-헬스체크">컨테이너 헬스체크</h4>
<pre><code class="language-yaml">- name: 
  run: |
    for i in {1..20}; do
      if curl -sf http://localhost:8080 &gt;dev/null; then echo OK; exit 0; fi
      sleep 1
    done
    exit 1</code></pre>
<ul>
<li>응답이 오면 exit 0 → 다음 step으로</li>
<li>20번 다 실패하면 exit 1 → 워크플로우 중단</li>
</ul>
<h4 id="node-환경--playwright-준비">Node 환경 + Playwright 준비</h4>
<pre><code class="language-yaml">- uses: actions/setup-node@v4
  with:
    node-version: &quot;20&quot;
    cache: &quot;npm&quot;
- run: npm ci
- run: npx playwright install --with-deps chromium</code></pre>
<h4 id="e2e-실행--컨테이너-정리">E2E 실행 + 컨테이너 정리</h4>
<pre><code class="language-yaml">- env:
    BASE_URL: http://localhost:8080
  run: npm run test:e2e

- if: always()
  run: docker stop calc || true</code></pre>
<ul>
<li><code>BASE_URL</code>: 코드가 어디로 접속할지 알려주는 환경 변수</li>
<li><code>if: always()</code>: 이전 step의 <span style="background-color: #dcffe4">성공/실패와 무관하게</span> 무조건 실행</li>
<li><code>||true</code>: stop이 실패해도 워크플로우 전체를 실패시키지 않도록 무시</li>
</ul>
<h3 id="part-3">Part 3.</h3>
<h4 id="job-2-정의--ghcr-로그인">Job 2. 정의 + GHCR 로그인</h4>
<pre><code class="language-yaml">push-image:
  name: 2) GHCR
  runs-on: ubuntu-latest
  needs: build-and-e2e
...
 - uses: docker/login-action@v3
   with:
     registry: ghcr.io
     username: ${{ github.actor }}
     password: ${{ secrets.GITHUB_TOKEN }}</code></pre>
<ul>
<li><code>needs: bulid-and-e2e</code>: 이전의 job이 성공해야만 실행 (안전장치)</li>
<li><code>secrets.GITHUB_TOKEN</code>: 매 실행마다 만들어지는 일회용 토큰</li>
</ul>
<p>... 등</p>
<h3 id="핵심-키워드">핵심 키워드</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/4c1888d5-add5-47a7-9fc5-ee9c79f23912/image.png" alt=""></p>
<h3 id="자주-발생하는-오류">자주 발생하는 오류</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/e46ef9d1-3736-4811-a418-fe7717613c3f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI 파이프라인 해설]]></title>
            <link>https://velog.io/@hi_soap/CI-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%ED%95%B4%EC%84%A4-uzhsfpyu</link>
            <guid>https://velog.io/@hi_soap/CI-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%ED%95%B4%EC%84%A4-uzhsfpyu</guid>
            <pubDate>Wed, 27 May 2026 06:50:59 GMT</pubDate>
            <description><![CDATA[<h2 id="ci-continuous-integration">CI (Continuous Integration)</h2>
<h3 id="역할">역할</h3>
<p><strong>코드 품질 검사</strong></p>
<ul>
<li>스타일 규칙 위반</li>
<li>보안 취약점</li>
<li>각 함수/모듈</li>
<li>모듈끼리의 연동</li>
</ul>
<h3 id="4가지-검사">4가지 검사</h3>
<h4 id="1-린트">1) 린트</h4>
<ul>
<li><code>ESLint</code>를 통한 <span style="background-color: #dcffe4">코딩 스타일 검사</span><h4 id="2-보안">2) 보안</h4>
</li>
<li><code>npm audit</code>를 통한 <span style="background-color: #dcffe4">보안 취약점</span> 검사 </li>
</ul>
<h4 id="3-단위-테스트">3) 단위 테스트</h4>
<ul>
<li><code>Jest</code>를 통한 <span style="background-color: #dcffe4">각 함수 검증</span></li>
</ul>
<h4 id="4-통합-테스트">4) 통합 테스트</h4>
<ul>
<li><code>Jest + jsdom</code>을 통한 <span style="background-color: #dcffe4">모듈 연동 검증</span></li>
<li><code>jsdom</code>: <code>Node.js</code>안에서 가상의 브라우저 DOM을 흉내내는 라이브러리*</li>
</ul>
<p><em>4개의 검사는 서로 <span style="background-color: #dcffe4">의존하지 않으며, 병렬로 실행됨</span></em>
<em>하나라도 실패하면 merge 불가능</em></p>
<h3 id="part-1">Part 1.</h3>
<h4 id="워크플로우-이름">워크플로우 이름</h4>
<pre><code class="language-yaml"># CI - PR이 열릴 때, 그리고 main에 push될 때 자동으로 실행
name: CI</code></pre>
<ul>
<li><code>#</code>: 주석으로 YAML이 무시하지만, 사람이 읽을 때 도움</li>
<li><code>name</code>: CI-workflow의 이름을 CI로 지정</li>
<li>GitHub의 Actions 탭에 name이 그대로 표시됨</li>
</ul>
<h4 id="두-가지-트리거">두 가지 트리거</h4>
<pre><code class="language-yaml">on: 
  pull_request:
    branches: [main]
  push:
    branches: [main]</code></pre>
<ul>
<li><p><code>main</code>에 대해 <code>push</code>, <code>PR</code> 이벤트 발생 시 실행됨</p>
</li>
<li><p><code>PR</code>: merge 전 검사
<code>push</code>: merge 후 검사 → main과 기능이 섞일 수 있기 때문*</p>
</li>
<li><p>CD는 <code>push</code>만 트리거</p>
</li>
</ul>
<h3 id="part-2">Part 2.</h3>
<h4 id="job-1-lint--checkout">Job 1. lint + checkout</h4>
<pre><code class="language-yaml">jobs:
  lint:
    name: 1) 린트 검사 (ESLint)
    runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4</code></pre>
<ul>
<li><code>lint</code>: 이 job의 <span style="background-color: #dcffe4">고유 ID</span></li>
<li><code>name</code>: UI에 보이는 이름 (한글도 가능)</li>
<li><code>runs-on: ubuntu-latest</code>: 최신 우분투 가상머신 사용</li>
<li><code>actions/checkout@v4</code>: 저장소 코드를 가상머신으로 다운로드</li>
</ul>
<h4 id="node-설치--의존성-설치">Node 설치 + 의존성 설치</h4>
<pre><code class="language-yaml">- uses: actions/setup-node@v4
  with:
    node-version: &quot;20&quot;
    cache: &quot;npm&quot;
- run: npm ci</code></pre>
<h4 id="lint-실행">Lint 실행</h4>
<pre><code class="language-yaml">- run: npm run lint</code></pre>
<ul>
<li>위반 사항 발견 시 0이 아닌 종료 코드 → step 실패 → job 실패</li>
<li>미사용 변수, 들여쓰기, 따옴표 스타일, 세미콜론 누락, <code>==</code> 대신 <code>===</code> 권장</li>
</ul>
<h3 id="part-3">Part 3.</h3>
<h4 id="job-2-security-정의--준비">Job 2. security 정의 + 준비</h4>
<pre><code class="language-yaml">security:
  name: 2) 보안 검사 (npm audit)
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
    with:
      node-version: &quot;20&quot;
      cache: &quot;npm&quot;
    - run: npm ci</code></pre>
<ul>
<li><p>의존성 패키지의 <span style="background-color: #dcffe4">보안 취약성</span>을 검사하는 작업</p>
</li>
<li><p>각 job은 완전히 새로운 가상 머신에서 실행하기 때문에
모든 준비를 처음부터 다시 해야 함</p>
</li>
<li><p>job 1.에서 설치한 것들을 다시 설치하는 이유*</p>
</li>
</ul>
<h4 id="보안-검사-실행">보안 검사 실행</h4>
<pre><code class="language-yaml">- run: npm run audit</code></pre>
<ul>
<li><p>내부적으로 <code>npm audit --audit-level=high</code> 같은 명령을 호출</p>
</li>
<li><p>심각도가 높은 취약점이 발견되면 0이 아닌 <span style="background-color: #dcffe4">종료 코드</span> → job 실패</p>
</li>
<li><p><code>npm run audit != npm audit</code></p>
</li>
<li><p>두 명령어는 서로 다른 명령임*</p>
</li>
</ul>
<h3 id="part-4">Part 4.</h3>
<h4 id="job-3-unit-test-정의--준비">Job 3. unit-test 정의 + 준비</h4>
<pre><code class="language-yaml">unit-test:
  name: 3) 단위 테스트 (Jest)
  runs-on: ubuntu-latest
  steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
    with:
      node-version: &quot;20&quot;
      cache: &quot;npm&quot;
  - run: npm ci</code></pre>
<h4 id="단위-테스트-실행">단위 테스트 실행</h4>
<pre><code class="language-yaml">- run: npm run test:unit</code></pre>
<p><strong>검증 대상</strong></p>
<ul>
<li><p>순수 함수의 입출력
<code>add(2, 3) == 5</code> 인지 확인</p>
</li>
<li><p>유틸리티 함수의 동작
<code>formatDate(&#39;2026-04-30&#39;)</code>의 결과 검증</p>
</li>
<li><p>예외 처리
<code>divide(1, 0)</code>이 에러를 던지는지 확인</p>
</li>
<li><p>경계값(edge case)
빈 배열, <code>null</code>, <code>undefined</code> 입력 시 동작</p>
</li>
</ul>
<p>*<span style="background-color: #dcffe4">DB, 네트워크, 파일 시스템은 사용하지 않음*</span>
<span style="background-color: #dcffe4"><em>빠르고 환경에 관계없이 같은 결과가 나옴</em></span></p>
<h3 id="part-5">Part 5.</h3>
<h4 id="job-4-integration-test-정의">Job 4. integration-test 정의</h4>
<pre><code class="language-yaml">integration-test:
  name: 4) 통합 테스트 (Jest + jsdom)
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: &quot;20&quot;
        cache: &quot;npm&quot;
    - run: npm ci</code></pre>
<h4 id="통합-테스트-실행">통합 테스트 실행</h4>
<pre><code class="language-yaml">- run: npm run test:integration</code></pre>
<ul>
<li><p><strong><code>jsdom</code></strong>은 <span style="background-color: #dcffe4">document나 윈도우 브라우저 객체를 가짜로 만들어줌</span></p>
</li>
<li><p>Node.js는 서버용이라 document나 윈도우 같은 브라우저 객체가 없음*</p>
</li>
<li><p>실제 브라우저를 띄우지 않고도 dom 조작 코드를 실행할 수 있게 해줌*</p>
</li>
<li><p>E2E 테스트(Playwright)보다 훨씬 빠르고, 단위 테스트보다 현실적</p>
</li>
<li><p>E2E 테스는 Docker 컨테이너를 실제로 띄워야 하기 때문에 무겁고 느림*</p>
</li>
<li><p>보통 CI에서는 <span style="background-color: #dcffe4">가볍고 빠른 검사</span>를 진행함*</p>
</li>
</ul>
<h4 id="단위-테스트-vs-통합-테스트-차이">단위 테스트 vs 통합 테스트 차이</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/38e629a2-2355-4e7e-82a3-e5f9b57f9d81/image.png" alt=""></p>
<h3 id="4개-job의-공통-패턴">4개 Job의 공통 패턴</h3>
<pre><code class="language-yaml"># 모든 job의 공통 패턴
steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
    with:
      node-version: &quot;20&quot;
      cache: &quot;npm&quot;
  - run: npm ci
  - run: npm run XXX</code></pre>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/cb0e35a4-d9b5-4874-b8e4-15822f534c50/image.png" alt=""></p>
<h3 id="핵심-키워드">핵심 키워드</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/87e445cc-cea5-4bdd-9254-377b409251c0/image.png" alt=""></p>
<h3 id="자주-만나는-오류">자주 만나는 오류</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/93f237c5-ba5d-4d62-a690-fc0fe2db5682/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Ralph Loop]]></title>
            <link>https://velog.io/@hi_soap/Ralph-Loop-7fiajttk</link>
            <guid>https://velog.io/@hi_soap/Ralph-Loop-7fiajttk</guid>
            <pubDate>Wed, 27 May 2026 05:54:13 GMT</pubDate>
            <description><![CDATA[<h2 id="ralph-loop">Ralph Loop</h2>
<ul>
<li>심슨 가족의 어린이 캐릭터 Ralph Wiggum에서 유래</li>
<li>화려한 프롬프트 엔지니어링이나 복잡한 에이전트 구조 없이
<span style="background-color: #dcffe4">같은 프롬프트를 단순히 반복 실행</span></li>
</ul>
<h3 id="4가지-원칙">4가지 원칙</h3>
<ul>
<li>같은 프롬프트를 반복한다.(<code>PROMPT.md</code>를 그대로 매번)</li>
<li>AI는 매번 <span style="background-color: #dcffe4">새 컨텍스트</span>로 시작한다.(이전 대화를 기억하지 않음)</li>
<li>모든 정보는 <span style="background-color: #dcffe4">파일에 저장</span>한다.(<code>SPEC.md</code>, <code>PROGRESS.md</code>)</li>
<li><span style="background-color: #dcffe4">한 번에 한 작업</span>만 한다.(작은 성공의 누적)</li>
</ul>
<h3 id="사용-분야">사용 분야</h3>
<p><strong>적합한 분야</strong></p>
<ul>
<li>완료 기준이 명확한 앱 (Todo, 가계부, 메모 등)</li>
<li>테스트로 검증 가능한 기능 단위</li>
</ul>
<p><strong>부적합한 분야</strong></p>
<ul>
<li>창의적 디자인</li>
<li>성능 최적화(전체 맥락을 필요로 하므로)</li>
</ul>
<h3 id="필요한-파일">필요한 파일</h3>
<p><strong><code>SPEC.md</code></strong></p>
<ul>
<li>무엇을 만들지 정의하는 파일</li>
<li><span style="background-color: #dcffe4">AI는 이 파일을 절대 수정하지 않음</span></li>
</ul>
<p><strong><code>PROMPT.md</code></strong></p>
<ul>
<li>매 반복마다 <span style="background-color: #dcffe4">AI에게 똑같이 전달할 지시문</span></li>
</ul>
<p><strong><code>PROGRESS.md</code></strong></p>
<ul>
<li><span style="background-color: #dcffe4">진행 상황 추적</span></li>
<li>매 반복마다 AI가 직접 갱신*</li>
</ul>
<p><strong><code>실행 스크립트</code></strong></p>
<ul>
<li>위 셋을 묶어서 반복</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 구축 with GitHub Actions]]></title>
            <link>https://velog.io/@hi_soap/CICD-%EA%B5%AC%EC%B6%95-with-GitHub-Actions</link>
            <guid>https://velog.io/@hi_soap/CICD-%EA%B5%AC%EC%B6%95-with-GitHub-Actions</guid>
            <pubDate>Wed, 27 May 2026 05:43:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hi_soap/post/d6bde77d-e63a-4a98-891d-b643aea12c27/image.png" alt=""></p>
<h3 id="github-actions">GitHub Actions</h3>
<ul>
<li>깃허브에서 제공하는 자동화 플랫폼</li>
<li>개발자가 코드를 올리거나 변경하는 <span style="background-color: #dcffe4">특정 이벤트 발생 시,
미리 정의해둔 작업들을 자동으로 실행</span></li>
<li>깃허브 액션을 실행시키는 주요 이벤트 트리거로는, 
<code>push</code>, <code>pull request</code>, <code>web hook</code> 등이 있음</li>
</ul>
<h3 id="cicd와-github-actions의-관계">CI/CD와 GitHub Actions의 관계</h3>
<ul>
<li><p>개발자가 코드를 수정하면, 
<span style="background-color: #dcffe4">자동으로 빌드하고 테스트 환경을 거쳐 코드의 품질을 검증</span><strong>* - CI*</strong></p>
</li>
<li><p>이후 <span style="background-color: #dcffe4">지속적 배포</span>를 통해 검증된 코드를 실제 서비스 환경인
프로덕션에 자동으로 배포 <strong>* - CD*</strong></p>
</li>
<li><p>GitHub Actions는 이 전체 파이프라인 과정을 자동화하여, 
개발자가 <span style="background-color: #dcffe4">코드 작성에만 집중</span>할 수 있게 도와주고 <span style="background-color: #dcffe4">휴먼 에러를 줄여줌</span></p>
</li>
</ul>
<h3 id="github-actions-workflow-분석">GitHub Actions workflow 분석</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/9c9c5f8c-d9a8-446b-85dd-b32011ba25bd/image.png" alt="">
<strong><code>name</code></strong>: 전체 자동화 작업의 이름을 정의</p>
<p><strong><code>on</code></strong>: 특정 이벤트가 발생했을 때, 이 작업이 시작될지 결정하는 설정
<em>예시에서는 <code>main</code> 브랜치에 <code>PR</code>, <code>Push</code>가 발생했을 때 작동</em></p>
<p><strong><code>jobs</code></strong>: 실제 수행할 작업들을 정의하는 구역
<em>여러 개를 설정하여 <span style="background-color: #dcffe4">병렬로 실행하거나 순차적으로 실행 가능</span></em></p>
<p><strong><code>runs-on</code></strong>: 이 작업을 실행할 <span style="background-color: #dcffe4">가상 머신 환경</span> 지정
<em>예시에서는 <code>ubuntu-latest</code> 최신 버전의 리눅스 환경 사용 중</em></p>
<p><strong><code>steps</code></strong>: 세부 실행 단계</p>
<ul>
<li><p><code>Checkout code</code>
<code>uses</code> 구문을 사용해서 <span style="background-color: #dcffe4">깃허브 저장소의 코드를 가상 환경으로 복사</span></p>
</li>
<li><p><code>Set up Node.js</code>
<code>with</code> 항목을 통해 특정 <span style="background-color: #dcffe4">노드 버전 지정</span></p>
</li>
<li><p><code>install dependencies</code>, <code>Run tests</code>
<code>run</code> 항목을 통해 <span style="background-color: #dcffe4">터미널 명령어를 직접 실행</span></p>
</li>
</ul>
<h2 id="cicd-구축을-위한-프롬프트">CI/CD 구축을 위한 프롬프트</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/d13308b5-7f07-4e51-ad0e-62cd4008bc21/image.png" alt=""></p>
<h3 id="ci">CI</h3>
<p><strong>정적 분석</strong></p>
<ul>
<li><span style="background-color: #dcffe4">코드 자체</span>에 문제가 없는지 점검</li>
</ul>
<p><strong>security scan</strong></p>
<ul>
<li><span style="background-color: #dcffe4">소스 코드 및 의존성의 보안 취약점</span>을 점검</li>
</ul>
<p><em>건물을 짓기 전, 설계도와 자재를 꼼꼼히 검사하는 것과 같음</em></p>
<p><strong>단위 테스트</strong></p>
<ul>
<li><span style="background-color: #dcffe4">비즈니스 로직</span>이 잘 작동하는지 확인</li>
</ul>
<p><strong>통합 테스트</strong></p>
<ul>
<li><span style="background-color: #dcffe4">여러 모듈이 서로 잘 연결</span>되는지 확인</li>
</ul>
<h3 id="cd">CD</h3>
<p><strong>도커 빌드</strong></p>
<ul>
<li>어떤 환경에서도 동일하게 작동할 수 있도록
<span style="background-color: #dcffe4">도커 이미지를 생성</span></li>
<li>이때 빌드 시간을 단축하고 효율성을 높이기 위해
<span style="background-color: #dcffe4"><strong>dependency</strong>와 <strong>도커 레이어 캐싱 전략</strong></span>을 반드시 적용해야 함*</li>
</ul>
<p><strong>container security scan</strong></p>
<ul>
<li>이미지가 만들어진 후, <span style="background-color: #dcffe4">이미지 레이어 자체에 보안 문제가 없는지 확인</span></li>
</ul>
<p><strong>E2E</strong></p>
<ul>
<li><span style="background-color: #dcffe4">실제 사용자 관점</span>에서 시스템이 전체적으로 잘 작동하는지 확인</li>
</ul>
<h3 id="github-pages-배포">GitHub Pages 배포</h3>
<ul>
<li><p>모든 테스트가 성공적으로 마무리되었을 때 진행하는 절차</p>
</li>
<li><p><span style="background-color: #dcffe4"><strong>upload pages artifacts, deploy pages</strong></span>와 같은 도구를 이용하여,
모든 절차를 <span style="background-color: #dcffe4">수동 작업 없이 자동화</span></p>
</li>
</ul>
<p><strong>smoke test</strong></p>
<ul>
<li><p>배포된 사이트가 실제로 살아있는지, 기본적인 기능이 응답하는지 확인</p>
</li>
<li><p>배포가 끝났다고 해서 모든 과정이 완료된 것이 아님*</p>
</li>
<li><p>해당 테스트로 <span style="background-color: #dcffe4">전체 파이프라인의 성공 여부 확정</span></p>
</li>
</ul>
<h2 id="ci-이슈-라벨-생성을-위한-프롬프트">CI 이슈 라벨 생성을 위한 프롬프트</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/4390ad3c-6d43-477b-86ec-c0199a5f52d6/image.png" alt=""></p>
<h3 id="label-data">Label Data</h3>
<p><strong><code>ci</code></strong>: <span style="background-color: #dcffe4">파이프라인 관련 이슈</span>나 <span style="background-color: #dcffe4">풀 리퀘스트를 식별</span>하는 데에 사용</p>
<p><strong><code>testing</code></strong>: <span style="background-color: #dcffe4">테스트 코드</span>나 <span style="background-color: #dcffe4">품질 검증 관련 작업</span>에 할당</p>
<p><strong><code>static-analysis</code></strong>: 코드의 <span style="background-color: #dcffe4">정적 분석 결과 추적</span></p>
<h3 id="실행-로직">실행 로직</h3>
<ul>
<li><p><span style="background-color: #dcffe4">GitHub CLI</span>를 통해 일관된 명령 수행</p>
</li>
<li><p>라벨이 존재하지 않을 때, <code>gh label create</code>: 라벨 생성
라벨이 이미 존재할 때, <code>gh label edit</code>: 색상과 설명 최신화</p>
</li>
<li><p>명령어 실행 중 특정 라벨에서 오류가 발생하더라도 
<span style="background-color: #dcffe4">전체 프로세스가 중단되지 않고 다음 라벨로 넘어가도록 설계</span></p>
</li>
<li><p><span style="background-color: #dcffe4"><strong>자동화의 안정성</strong></span> 확보를 위함*</p>
</li>
</ul>
<h2 id="cd-이슈-라벨-생성을-위한-프롬프트">CD 이슈 라벨 생성을 위한 프롬프트</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/46ebd635-14ba-4097-8224-462ee7eaa86e/image.png" alt=""></p>
<h3 id="label-data-1">Label Data</h3>
<p><strong><code>cd</code></strong>: <span style="background-color: #dcffe4">CD 파이프라인과 배포 전반</span> 담당
<strong><code>docker</code></strong>: <span style="background-color: #dcffe4">도커 이미지와 컨테이너화</span> 관련 이슈
<strong><code>e2e-testing</code></strong>: <span style="background-color: #dcffe4">playwright</span>을 기반으로 <span style="background-color: #dcffe4">실제 사용자 환경</span>에서의 동작을 검증
<strong><code>infrastructure</code></strong>: 인프라 및 배포 환경 설정 관리</p>
<h2 id="cicd-구축을-위한-프롬프트-1">CI/CD 구축을 위한 프롬프트</h2>
<h3 id="이슈-등록">[이슈 등록]</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/12ceba18-4c65-4ea0-8fb0-feb15ea8c24b/image.png" alt=""></p>
<ul>
<li><p><code>gh CLI</code> 깃허브 명령줄 도구를 이용하여 이슈 생성</p>
</li>
<li><p><code>Phase</code>, <code>description</code>, <code>Acceptance criteria</code>로 구성</p>
</li>
<li><p>한꺼번에 많은 요청을 보내면 <span style="background-color: #dcffe4">시스템 부하 가능성</span>이 있으므로,
이슈들을 하나씩 <span style="background-color: #dcffe4">순차적으로 생성</span>해야함</p>
</li>
</ul>
<h3 id="github-projects-칸반보드">GitHub Projects 칸반보드</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/c12ea6ae-0f11-4db1-89c8-b2df5840be47/image.png" alt=""></p>
<h3 id="구현-테스트-커밋">[구현-테스트-커밋]</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/a198ffe4-add8-440f-87bc-08473bb68460/image.png" alt=""></p>
<ul>
<li><p><code>gh project</code> 명령어로 <code>Todo</code> 열의 가장 위에 있는 이슈 번호와 제목 추출</p>
</li>
<li><p>해당 이슈의 상태를 <code>In Progress</code>로 변경 후 본인을 <code>assignee</code>로 할당*</p>
</li>
<li><p><code>gh issue view</code> 명령어로 내용 상세히 확인</p>
</li>
<li><p>이슈 번호와 요약 키워드를 조합한 피처 브랜치 생성*</p>
</li>
<li><p>실제 수용 기준인 <strong>AC</strong>를 바탕으로 실제 코드 구현</p>
</li>
<li><p>검증 통과 시 <code>PR</code> 생성</p>
</li>
<li><p>PR 본문에는 해당 이슈를 자동으로 닫아주는 <code>closes</code> 구문 추가*</p>
</li>
</ul>
<h2 id="ci-워크플로우">CI 워크플로우</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/6ec0e10c-0537-4e99-bd1c-2f24944911a7/image.png" alt=""></p>
<p><code>on:push</code>
이 모든 과정이 <span style="background-color: #dcffe4">코드가 저장소에 업로드되는 순간</span> 시작된다는 트리거를 의미</p>
<h3 id="첫-번째-단계-코드의-기초적인-결함-검사">첫 번째 단계: 코드의 기초적인 결함 검사</h3>
<ul>
<li><p><code>static analysis</code> - <code>ESLint</code>
<span style="background-color: #dcffe4">실제로 코드를 실행하지 않고도</span>, 문법적 오류나 코딩 컨벤션 위반을 검사</p>
</li>
<li><p><code>Security Scan</code> - <code>npm audit</code>
보안 스캔으로, 
프로젝트에서 사용하는 <span style="background-color: #dcffe4">외부 라이브러리에 알려진 취약점</span>이 없는지 검사</p>
</li>
<li><p>중앙의 연결선은 <span style="background-color: #dcffe4">의존성 관계</span>를 나타냄</p>
</li>
<li><p>기초적인 문법 오류나 보안 결함이 있는 코드에 대해
리소스가 많이 드는 테스트를 수행하지 않음</p>
</li>
<li><p><span style="background-color: #dcffe4">컴퓨팅 자원을 효율적으로 관리</span>하기 위함*</p>
</li>
</ul>
<p><em>두 과정은 병렬/그룹화되어 실행됨</em></p>
<p><em>첫 번째 작업의 모든 작업이 성공적으로 마무리되어야만 
두번째 단계로 넘어갈 수 있음</em></p>
<h3 id="두-번째-단계-본격적인-코드-검증">두 번째 단계: 본격적인 코드 검증</h3>
<ul>
<li><p>프론트엔드 환경에서 <code>Unit Tests(단위 테스트)</code>
각각의 함수나 컴포넌트가 <span style="background-color: #dcffe4">독립적으로 잘 작동하는지</span> 검증한다는 사실</p>
</li>
<li><p><code>Integration Tests(통합 테스트)</code> 및 커버리지 확인
이들이 <span style="background-color: #dcffe4">서로 연결되었을 때</span>도 비즈니스 로직에 맞게 돌아가는지 확인</p>
</li>
<li><p>커버리지: <span style="background-color: #dcffe4">전체 소스 코드에 대한 테스트 코드의 비율</span>을 수치화한 것 *</p>
</li>
</ul>
<h3 id="두-단계-정리">두 단계 정리</h3>
<ul>
<li><p>정적 분석과 보안 스캔을 통해 <span style="background-color: #dcffe4">코드의 무결성 확인</span></p>
</li>
<li><p>이를 통과한 코드에 한해서만 <span style="background-color: #dcffe4">단위 테스트와 통합 테스트</span>를 수행</p>
</li>
</ul>
<h2 id="ci-정적-분석">CI 정적 분석</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/0446d92a-b507-443b-9174-f43ff1165ed8/image.png" alt=""></p>
<ul>
<li>개발자가 작성한 코드의 안정성을 실시간으로 확인하는 <span style="background-color: #dcffe4">파이프라인 역할</span></li>
</ul>
<h3 id="워크플로우-트리거">워크플로우 트리거</h3>
<ul>
<li><code>main</code>, <code>develop</code> 브랜치에 코드가 <code>push</code> 또는 <code>PR</code> 생성 시,
해당 워크플로우가 자동으로 실행되도록 설계됨</li>
<li>개발자가 일일이 <span style="background-color: #dcffe4">수동으로 검사하지 않아도 되며</span>
코드에 변경 사항이 생길 때 마다 <span style="background-color: #dcffe4">즉각적인 피드백</span>을 받을 수 있음*</li>
</ul>
<h3 id="정적-분석-단계">정적 분석 단계</h3>
<ul>
<li><p><code>npm install</code>이 아닌 <code>npm ci</code>를 사용하는 이유는
<code>package-lock.json</code> 파일을 기준으로 의존성을 엄격하게 설치</p>
</li>
<li><p><span style="background-color: #dcffe4">개발 환경과 CI 환경 사이의 일관성 보장을 위함*</span></p>
</li>
<li><p>프론트엔드와 백엔드를 독립적으로 검사함으로 인해,
<span style="background-color: #dcffe4">각각의 영역에서 발생하는 결함을 보다 명확하게 분리해서 파악 가능</span></p>
</li>
</ul>
<h3 id="결과-요약-및-가시성">결과 요약 및 가시성</h3>
<ul>
<li><code>if: always()</code> 조건문을 사용함으로써
<span style="background-color: #dcffe4">앞선 단계들의 <strong>성공 여부에 관계없이</strong> <strong>무조건 결과를 출력</strong>하기 위함</span></li>
</ul>
<h3 id="security-scan">security scan</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/243a1f56-dd4b-4e70-84d0-0062cbfd53ae/image.png" alt=""></p>
<p><strong>1) 깃허브 저장소에 있는 최신 코드를 가상 환경으로 가져옴</strong></p>
<p><strong>2) 프론트엔드 스캔</strong>
<strong><code>npm audit</code></strong></p>
<ul>
<li><p><strong><code>--audit-level=high</code> 설정</strong>
보안 취약점 중 <span style="background-color: #dcffe4">심각도가 높은 항목이 발견될 때만</span> 빌드를 실패하게 만듦</p>
</li>
<li><p>개발자가 사소한 경고에 지치지 않게 하며, 치명적인 위험은 확실히 잡음*</p>
</li>
<li><p><strong><code>--omit=dev</code></strong>
<span style="background-color: #dcffe4">배포 시 포함되는 핵심 패키지</span>들의 안정성만 집중적으로 검증</p>
</li>
<li><p>실제 서비스 실행 시 포함되지 않는 <code>ESLint</code>나 <code>Jest</code>등의 개발 도구는 미포함*</p>
</li>
</ul>
<p><strong>3) 백엔드 스캔</strong></p>
<ul>
<li>프론트엔드 스캔과 같은 과정으로 진행됨</li>
</ul>
<p><em>프론트엔드와 백엔드를 나누어 검사함으로써
<span style="background-color: #dcffe4">어느 영역에 보안 결함이 발생했는지 즉각적인 파악 가능</span></em></p>
<h2 id="ci-보안-검사">CI: 보안 검사</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/5e86a881-a1e5-4ffe-9acf-feda609b61fc/image.png" alt=""></p>
<p><strong><code>Trivy</code></strong></p>
<ul>
<li><p>라이브러리 내의 보안 취약점 검사</p>
</li>
<li><p>파일 시스템을 뜻하는 <code>fs</code> 모드를 통해 프로젝트 전체를 검사</p>
</li>
<li><p><span style="background-color: #dcffe4"><strong>HIGH, CRITICAL</strong> 단계의 문제만 집중</span>하도록 만듦</p>
</li>
<li><p>해결 방법이 나오지 않은 문제는 <code>ignore-unfixed</code> 옵션으로 제외*</p>
</li>
<li><p>개발자가 <span style="background-color: #dcffe4">수정 가능한 실질적인 위협에만 집중</span>할 수 있도록 함*</p>
</li>
<li><p><strong><code>exit-code: 1</code></strong>
치명적인 보안 결함 발견 시, 빌드 과정 강제 종료</p>
</li>
<li><p>모든 검사 결과는 <code>Github Step Summary</code>를 통해 깔끔한 표 형태로 출력</p>
</li>
</ul>
<h2 id="ci-단위-테스트">CI 단위 테스트</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/76eb142e-5054-43f0-a41f-7127144c38ef/image.png" alt=""></p>
<p><strong><code>needs</code></strong></p>
<ul>
<li>앞 단계인 <code>static-analysis</code>와 
보안 스캔 단계인 <code>security scan</code>이 모두 통과되어야만 시작됨</li>
</ul>
<p><strong><code>Vitest</code></strong></p>
<ul>
<li><p><span style="background-color: #dcffe4">프론트엔드 테스트</span>에 사용하는 도구</p>
</li>
<li><p>웹 화면의 버튼이 잘 눌리는지, 카드가 제대로 표시되는지 등
<span style="background-color: #dcffe4">화면의 최소 단위 검사</span></p>
</li>
</ul>
<p><strong><code>Jest</code></strong></p>
<ul>
<li>카드 섞기 로직이나 데이터 처리 함수 같은 
<span style="background-color: #dcffe4">복잡한 수학적 계산</span>이 정확한지 꼼꼼하게 따져보는 역할</li>
</ul>
<p><em><code>Vitest</code>, <code>Jest</code>를 사용하기 위해 <strong><code>npm ci</code></strong>라는 명령어를 통해
<span style="background-color: #dcffe4">개발 환경을 완벽하게 복제하고 각각의 테스트를 실행</span></em></p>
<p><strong><code>if: always()</code></strong></p>
<ul>
<li>에러가 나서 <span style="background-color: #dcffe4">테스트가 실패하더라도</span> 결과 보고서는 반드시 만들어야 함</li>
<li>개발자가 무엇이 틀렸는지 알기 위해*</li>
</ul>
<h3 id="단위-테스트-사례-피셔-에이츠-셔플-알고리즘-검증">단위 테스트 사례: 피셔 에이츠 셔플 알고리즘 검증</h3>
<p><strong>카드를 무작위로 섞는 알고리즘</strong>
<img src="https://velog.velcdn.com/images/hi_soap/post/a6e0d015-c36f-45e4-8466-63383f3c7495/image.png" alt=""></p>
<h2 id="ci-통합-테스트">CI 통합 테스트</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/eafe6d38-e857-4c8e-8f86-b98d5d16d269/image.png" alt=""></p>
<ul>
<li>단위 테스트가 <span style="background-color: #dcffe4">개별 부품의 이상 유무 확인</span>을 했다면,
<span style="background-color: #dcffe4">그 부품들이 연결되었을 때</span> 이상이 없는지 점검</li>
</ul>
<p><strong><code>npm run test:integration</code></strong></p>
<ul>
<li><span style="background-color: #dcffe4">백엔드의 통합 테스트</span> 실행</li>
</ul>
<p><strong><code>coverage</code></strong></p>
<ul>
<li><strong>*&quot;전체 코드에 대해 몇 퍼센트를 테스트했는가&quot;*</strong> 를 나타내는 지표</li>
<li>설정한 임계값을 넘지 못하면 <code>exit-code 1</code>을 통한 품질 유지*</li>
</ul>
<p><strong><code>if: always()</code></strong></p>
<ul>
<li><span style="background-color: #dcffe4">성공 여부에 관계 없이</span> 보고서로 남겨짐</li>
</ul>
<h3 id="통합-테스트-사례-게임-api-연동-검증">통합 테스트 사례: 게임 API 연동 검증</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/d699f15f-0b64-453e-8c62-e8f26393fede/image.png" alt=""></p>
<ul>
<li>단위 테스트는 <span style="background-color: #dcffe4">내부 로직</span>에 집중하고, 통합 테스트는 <span style="background-color: #dcffe4">통신과 흐름</span>에 집중</li>
</ul>
<p><strong>데이터 규약 검증 (API Contract)</strong></p>
<ul>
<li>프론트엔드와 백엔드 사이의 데이터 약속(타입, 속성)이 지켜지는지 검증</li>
</ul>
<p><strong>성능 요구사항(Performance)</strong></p>
<ul>
<li><p><span style="background-color: #dcffe4">사용자 경험</span>을 보장하기 위함</p>
</li>
<li><p><span style="background-color: #dcffe4">비기능적 요구사항</span>까지 통합 테스트의 영역으로 끌어들임*</p>
</li>
<li><p><span style="background-color: #dcffe4">서버의 라우팅, 비즈니스 로직, 데이터 생성 과정</span>이 하나로 묶여
실제 서비스 환경에서 문제없이 작동할 것임을 보장</p>
</li>
</ul>
<h2 id="cd-1">CD</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/fde3646e-9e37-4f7d-a344-248212a48430/image.png" alt="">
<code>on: workflow_run</code>
CI 워크플로우가 성공적으로 완료되었을 때만 배포 과정이 시작되도록 설계됨</p>
<p><code>Docker Build and Push to~</code>
소스 코드를 <span style="background-color: #dcffe4">도커 이미지</span>의 독립적인 단위로 묶어 저장소에 업로드</p>
<p><code>Container Security Scan</code>
도커 이미지 내부의 <span style="background-color: #dcffe4">보안 취약점이나 악성 코드 검사</span></p>
<p><code>E2E Test</code>
<strong>playwright</strong>라는 도구를 사용해, <span style="background-color: #dcffe4">실제 사용자가 브라우저를 사용하는 것과 
  똑같은 환경</span>에서 기능들이 유기적으로 작동하는지 확인
<em>실제 사용자 경험을 시뮬레이션 하기 때문에 <span style="background-color: #dcffe4">가장 긴 시간이 소모됨</span></em></p>
<p><code>Deploy to GitHub Pages</code>
실제 서비스가 서버에 배포
<em>전 세계 사용자들이 접속할 수 있는 상태</em></p>
<p><code>Smoke Test</code>
배포 직후 서비스가 <span style="background-color: #dcffe4">최소한의 정상 작동</span>을 하는지 테스트</p>
<ul>
<li><p>이러한 선형적 구조는 각 단계가 <span style="background-color: #dcffe4">이전 단계의 성공을 보장받아야 실행되는</span>
강력한 <span style="background-color: #dcffe4">의존성 관계</span>를 보여줌</p>
</li>
<li><p>개발자의 <span style="background-color: #dcffe4">수동 개입 없이</span>
<span style="background-color: #dcffe4">빌드, 보안, 검증, 테스트, 배포, 최종 확인 과정이 모두 <strong>자동화</strong>됨</span></p>
</li>
</ul>
<h2 id="docker">Docker</h2>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/97d6bc9d-a1ae-42a3-b145-6b083add9b31/image.png" alt=""></p>
<ul>
<li>소프트웨어를 담는 <span style="background-color: #dcffe4">표준화된 컨테이너</span></li>
</ul>
<h3 id="docker-concepts">Docker Concepts</h3>
<ul>
<li>애플리케이션과 그 실행에 필요한 라이브러리를 하나로 묶어 <span style="background-color: #dcffe4">패키징</span>하는 도구</li>
<li>이렇게 패키징 된 결과물을 <code>docker image</code>라고 부름*</li>
</ul>
<p><strong>docker image</strong></p>
<ul>
<li><p>실행 가능한 <span style="background-color: #dcffe4">정적인 상태의 템플릿</span></p>
</li>
<li><p>도커 엔진 위에서 실행하면, <code>container</code>라는 <span style="background-color: #dcffe4">격리된 실행 객체</span>가 생성됨</p>
</li>
</ul>
<p><strong>container</strong></p>
<ul>
<li><p>기존 가상 머신보다 훨씬 <span style="background-color: #dcffe4">가볍고 빠름</span></p>
</li>
<li><p><span style="background-color: #dcffe4">독립적인 환경 제공</span></p>
</li>
<li><p><span style="background-color: #dcffe4">표준화된 소프트웨어 단위</span></p>
</li>
<li><p>어떤 환경에서도 동일하게 작동함</p>
</li>
</ul>
<h3 id="registry--ghcr">Registry &amp; GHCR</h3>
<ul>
<li><p>우리가 만든 이미지는 <code>Docker Hub</code>나 <code>GHCR(GitHub Container Registry)</code>
와 같은 <span style="background-color: #dcffe4">온라인 저장소</span>에 보관하고 관리함</p>
</li>
<li><p>이렇게 업로드된 이미지는 <span style="background-color: #dcffe4">환경에 관계없이</span> 어디서든 실행 가능함</p>
</li>
</ul>
<p><strong>GHCR</strong></p>
<ul>
<li>깃허브의 생태계 내에 있어, <span style="background-color: #dcffe4">깃허브 액션과 긴밀하게 통합됨</span></li>
<li>보안, 자동화 배포 파이프라인 구축 시 효율적*</li>
</ul>
<h3 id="dockerfile-structure">Dockerfile Structure</h3>
<ul>
<li>이미지를 빌드하기 위한 <span style="background-color: #dcffe4">명령어 집합</span></li>
</ul>
<p><strong><code>FROM</code></strong></p>
<ul>
<li>베이스 이미지를 지정하는 시작점</li>
</ul>
<p><strong><code>LABEL</code></strong></p>
<ul>
<li>이미지의 관리자나 버전 정보를 기록하는 <span style="background-color: #dcffe4">메타데이터</span></li>
</ul>
<p><strong><code>ENV</code></strong></p>
<ul>
<li>애플리케이션 안에서 사용할 <span style="background-color: #dcffe4">환경 변수</span></li>
</ul>
<p><strong><code>WORKDIR</code></strong></p>
<ul>
<li>컨테이너 내부의 <span style="background-color: #dcffe4">작업 디렉토리를 설정</span>하여
이후 명령어들이 실행될 위치를 잡음</li>
</ul>
<p><strong><code>COPY</code></strong></p>
<ul>
<li>로컬 컴퓨터의 파일들을 이미지 내부로 복사</li>
</ul>
<p><strong><code>RUN</code></strong></p>
<ul>
<li>빌드 과정 중 <span style="background-color: #dcffe4">필요한 패키지 설치</span> 또는 <span style="background-color: #dcffe4">업데이트 명령어 실행</span></li>
</ul>
<p><strong><code>EXPOSE</code></strong></p>
<ul>
<li>컨테이너가 <span style="background-color: #dcffe4">외부와 통신할 때 <strong>사용할 포트 번호</strong></span>를 알려줌</li>
</ul>
<p><strong><code>CMD</code></strong></p>
<ul>
<li>컨테이너가 실행되는 시점에 <span style="background-color: #dcffe4">기본으로 동작할 명령어</span>를 정의</li>
</ul>
<h3 id="cd-docker-이미지-빌드-및-ghcr-푸시">CD: Docker 이미지 빌드 및 GHCR 푸시</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/64354a27-a83e-444d-8371-5408bfaf91b5/image.png" alt=""></p>
<p><strong>1. 파이프라인 시작 &amp; 조건</strong></p>
<ul>
<li>CD 워크플로우는 메인 브랜치에서 <span style="background-color: #dcffe4">CI 작업이 성공적으로 끝났을 때만</span> 자동으로 시작되도록 설정됨</li>
</ul>
<p><strong>2. 빌드 환경 &amp; 도구</strong></p>
<ul>
<li><code>Buildx</code>
여러 환경에 맞춰 빌드 속도를 빠르게 해 주는 <span style="background-color: #dcffe4">레이어 캐싱 기능</span> 제공</li>
<li>배포 시간을 대폭 단축해 줌*</li>
</ul>
<p><strong>3. 인증 및 Frontend 빌드</strong></p>
<ul>
<li><p><code>GITHUB_TOKEN</code>을 사용하여 
<code>GHCR</code> 컨테이너 저장소에 자동 로그인을 통한 <span style="background-color: #dcffe4">보안성 확보</span> </p>
</li>
<li><p><code>nginx</code>
<span style="background-color: #dcffe4">웹 서버 설정 파일</span>로, 해당 파일을 포함하여 저장소로 푸시</p>
</li>
</ul>
<p><strong>4. Backend 빌드 &amp; 결과 리포트</strong></p>
<ul>
<li><p>컨텍스트를 최소화하여 <span style="background-color: #dcffe4">가벼운 이미지 생성</span></p>
</li>
<li><p><strong>GitHub Step Summary</strong> 기능을 통해
현재 빌드된 <span style="background-color: #dcffe4">이미지 태그와 상태를 시각화</span></p>
</li>
</ul>
<h3 id="cd-container-scan">CD: Container Scan</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/9dc76455-9645-4e9f-b4d4-dbd33fcf998f/image.png" alt=""></p>
<ul>
<li>소프트웨어를 실제 운영 환경에 배포하기 전 <span style="background-color: #dcffe4">안정성을 최종적으로 검증</span></li>
</ul>
<p><strong>1) 보안 파이프라인 시작 &amp; 조건</strong></p>
<ul>
<li><p><strong>도커 빌드(이미지 만들기)</strong>와 <strong>푸시(이미지를 저장소에 등록)</strong> 작업이
성공적으로 완료된 직후 실행되도록 설계</p>
</li>
<li><p>배포 전 보안 검사는 <span style="background-color: #dcffe4">선택이 아닌 필수 선행 작업</span></p>
</li>
<li><p>취약점이 포함된 이미지가 외부로 유출되는 것을 원천 차단</p>
</li>
</ul>
<p><strong>2) 인증 및 코드 접근</strong></p>
<ul>
<li><p>GHCR 저장소에 안전하게 접근하기 위해 <span style="background-color: #dcffe4">깃허브 토큰을 사용해 로그인</span></p>
</li>
<li><p>보안 도구가 프라이빗 저장소에 있는 이미지를 내려받아 검사할 수 있도록
<span style="background-color: #dcffe4">적절한 권한을 확보하는 과정</span>으로 <span style="background-color: #dcffe4">파이프라인의 신뢰성을 보장</span></p>
</li>
</ul>
<p><strong>3) 프론트엔드 이미지 보안 스캔</strong></p>
<ul>
<li><p><code>Trivy</code>라는 도구를 사용하여 시스템에 치명적인 영향을 줄 수 있는
<span style="background-color: #dcffe4">CRITICAL</span> 등급의 취약점을 집중적으로 점검</p>
</li>
<li><p>심각한 보안 결함 발생 시 <span style="background-color: #dcffe4">엑시트 코드 1번을 반환</span>하여 <span style="background-color: #dcffe4">전체 파이프라인 중단</span></p>
</li>
<li><p>결함이 있는 소프트웨어가 배포되는 것을 막음*</p>
</li>
</ul>
<p><strong>4) 백엔드 이미지 보안 스캔</strong></p>
<ul>
<li><span style="background-color: #dcffe4">패치가 존재하지 않는(해결 방법이 없는) 취약점</span>은
<span style="background-color: #dcffe4">결과에서 제외</span>하여 불필요한 경고인 <span style="background-color: #dcffe4">노이즈</span>를 줄임</li>
<li>개발자가 <span style="background-color: #dcffe4">당장 해결할 수 있는 문제에만 집중</span>하게 만듦*</li>
</ul>
<p><strong>5) 결과 리포팅 및 공유</strong></p>
<ul>
<li>모든 스캔이 완료되면 <code>GitHub Step Summary</code> 기능을 통해
각 이미지의 <span style="background-color: #dcffe4">보안 상태와 스캔 요약 결과를 시각적</span>으로 출력함</li>
<li>팀원 전체에게 공개되어 
<span style="background-color: #dcffe4">누구나 현재 배포된 소프트웨어의 품질과 안정성</span> 확인 가능*</li>
</ul>
<h3 id="cd-e2e-테스트">CD: E2E 테스트</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/8b0d72d3-8e01-4e3b-91e4-ad15834f505b/image.png" alt=""></p>
<ul>
<li><span style="background-color: #dcffe4">실제 사용자</span>가 우리 서비스를 이용하는 것과 <span style="background-color: #dcffe4">똑같은 환경</span>에서 
전체 시스템이 잘 돌아가는지 확인</li>
</ul>
<p><strong>1) 통합 검증 환경 구축</strong></p>
<ul>
<li>컨테이너 스캔까지 무사히 마친 후 실행되며,
파이프라인 내부에서 <span style="background-color: #dcffe4">프런트엔드와 백엔드 모두 구동</span>하여 전체 시스템을 구성</li>
</ul>
<p><strong>2) 의존성 설치 레이어</strong></p>
<ul>
<li><code>Node.js 20</code> 버전에서 <code>npm cache</code> 기능을 적극 활용하여
<span style="background-color: #dcffe4">매번 모든 패키지를 새로 받는 수고를 덜어냄</span></li>
<li>테스트 속도를 비약적으로 높임*</li>
</ul>
<p><strong>3) 브라우저 최적화 및 자동 실행</strong></p>
<ul>
<li><p>CD 속도를 최대한 끌어올리기 위해
여러 브라우저 대신 <span style="background-color: #dcffe4">Chronium</span> 하나만 설치해서 집중적으로 테스트</p>
</li>
<li><p>구글 크롬, 엣지, 웨일 등의 대부분 브라우저가 Chronium 기반임*</p>
</li>
<li><p>서버 환경에서 테스트를 돌릴 때, <span style="background-color: #dcffe4">브라우저 창을 직접 띄울 수 없음</span></p>
</li>
<li><p>이 때 사용하는 것이 <span style="background-color: #dcffe4">headless 모드*</span></p>
</li>
<li><p>이후 <code>Playwright</code> 라는 도구를 이용하여 
<span style="background-color: #dcffe4">웹 서버를 자동으로 띄우고 테스트를 실행하여 정밀한 검증 실행</span></p>
</li>
</ul>
<p><strong>4) 실패 분석 및 결과 보관</strong></p>
<ul>
<li>테스트가 <span style="background-color: #dcffe4">실패할 경우에만</span> <code>HTML</code> 형태의 리포트를 <span style="background-color: #dcffe4">아티팩트 형태로</span> 업로드</li>
<li>해당 기록은 <span style="background-color: #dcffe4">7일</span>동안 기록됨*</li>
</ul>
<h4 id="playwright-e2e-test-scenarios-card-matching-game">playwright E2E Test Scenarios: Card Matching Game</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/8ed902c9-e561-4587-9f88-49ffac52f686/image.png" alt=""></p>
<h3 id="cd-smoke-테스트">CD: Smoke 테스트</h3>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/0c3ac734-054d-4f82-9aba-c28308692ab0/image.png" alt=""></p>
<ul>
<li><p>하드웨어 수리 분야에서 유래됨
기기를 수리한 뒤 처음 전원을 켰을 때, 연기가 나지 않으면 큰 문제는 없는 것</p>
</li>
<li><p>전체 기능을 상세히 점검하기 전
<span style="background-color: #dcffe4">가장 핵심적인 서비스가 죽지 않고 살아있는지 확인</span></p>
</li>
<li><p><span style="background-color: #dcffe4">최소한의 가용성 테스트</span>*</p>
</li>
</ul>
<p><strong><code>curl</code> 명령어를 통해 <code>GitHub Pages</code> 사이트로 실제 접속 시도</strong></p>
<ul>
<li><p><span style="background-color: #dcffe4">소프트웨어 공학의 회복 탄력성 원칙이 적용된 <strong>재시도 로직</strong></span>으로
리트라이 횟수를 5회로 설정, 각 시도 사이에 10초의 지연 시간을 가짐</p>
</li>
<li><p><span style="background-color: #dcffe4">CDN(Content Delivery Network)</span> 전파가 전 세계적으로 완료되는데 걸리는
<span style="background-color: #dcffe4">물리적인 시간을 고려한 설정</span></p>
</li>
<li><p>웹 서버가 서울에만 있다면, 미국 사용자는 데이터를 받기까지 오랜 시간 대기,
CDN을 사용하면 미국에 있는 거점 서버에서 바로 데이터 전송*</p>
</li>
<li><p><span style="background-color: #dcffe4">일시적인 네트워크 지연으로 인한 테스트 실패 현상 방지</span>*</p>
</li>
<li><p>접속 거부 시에도 재시도하도록 설정하고,
최대 대기 시간을 30초로 제한하여 <span style="background-color: #dcffe4">파이프라인의 무한 대기를 방지</span></p>
</li>
<li><p><code>HTTP 200</code> 응답 수신 시 스모크 테스트 통과 메시지 출력과 함께 작업 종료</p>
</li>
</ul>
<p><strong>Health Check API 활용</strong></p>
<ul>
<li><p>단순히 메인 페이지가 열리는 것을 넘어, 서버 내부의 데이터베이스 연결이나
캐시 시스템이 정상적으로 작동하는지 확인</p>
</li>
<li><p>겉으로는 멀쩡해 보이지만, 실제 기능은 마비된 상태인 <span style="background-color: #dcffe4">좀비 서버</span>를 미리 감지</p>
</li>
</ul>
<p><strong>핵심 UI 요소의 렌더링 확인</strong></p>
<ul>
<li>HTTP 응답 코드가 200이라고 해서 화면에 내용이 제대로 보인다는 보장 없음</li>
<li><code>Playwright</code>와 같은 도구를 이용하여 
페이지 제목, 로그인 버튼 등의 <span style="background-color: #dcffe4">돔 트리 점검*</span></li>
</ul>
<p><strong>외부 연동 서비스의 연결성 점검</strong></p>
<ul>
<li>서비스와 연결된 외부 게이트웨이나 인증 API 서버와의 통신이 원활한지 확인</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[11. 파일 시스템 관리]]></title>
            <link>https://velog.io/@hi_soap/11.-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@hi_soap/11.-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Mon, 25 May 2026 09:42:46 GMT</pubDate>
            <description><![CDATA[<h2 id="강의-목표">강의 목표</h2>
<ul>
<li>응용프로그램과 저장 장치 사이의 <span style="background-color: #dcffe4">파일 입출력 과정</span>을 안다.</li>
<li><span style="background-color: #dcffe4">디렉터리와 파일의 계층 구조</span>에 대해 이해한다.</li>
<li><span style="background-color: #dcffe4">파일 메타 정보와 파일 시스템 메타 정보</span>에 대해 이해한다.</li>
<li><span style="background-color: #dcffe4">FAT 파일 시스템의 저장 구조</span>에 대해 이해한다.</li>
<li><span style="background-color: #dcffe4">Unix 파일 시스템의 저장 구조</span>에 대해 이해한다.</li>
<li><span style="background-color: #dcffe4">파일 입출력 연산</span>이 이루어지는 과정을 이해한다.</li>
<li>파일 찾기, 파일 열기, 파일 읽기, 파일 쓰기, 파일 닫기*</li>
</ul>
<h2 id="01-파일-시스템과-저장-장치">01 파일 시스템과 저장 장치</h2>
<h3 id="11-파일과-저장-장치">1.1 파일과 저장 장치</h3>
<p><strong>파일(file)</strong></p>
<ul>
<li><p>사용자나 응용프로그램에게 <span style="background-color: #dcffe4">정보를 저장하고 관리하는 논리적 단위</span></p>
</li>
<li><p>내용과 형식은 파일을 만드는 <span style="background-color: #dcffe4">사용자나 응응프로그램에 의해 결정</span></p>
</li>
<li><p>컴퓨터 시스템은 0/1로 다룸*</p>
</li>
<li><p>파일이 생성되고 기록되고 읽혀지는 모든 과정은 <span style="background-color: #dcffe4">운영체제에 의해서만 통제됨</span></p>
</li>
<li><p>저장 장치를 통제하는 것은 <span style="background-color: #dcffe4">운영체제 본연의 기능이기 때문</span>이며,
저장 장치에 관한 정보(빈 공간, 파일 저장 위치)는 <span style="background-color: #dcffe4">운영체제에 의해서 통제됨</span>*</p>
</li>
</ul>
<p><strong>비휘발성 영구 저장 장치(non-volatile)</strong></p>
<ul>
<li><p>전원이 꺼져도 <span style="background-color: #dcffe4">정보가 지워지지 않는</span> 저장 장치</p>
</li>
<li><p><em>예)
하드 디스크(HDD, Hard Disk Drive)
USB 플래시 드라이버(USB Flash Drive)
SSD(Solid-State Drive)
테이프 저장 장치</em></p>
</li>
<li><p>*<span style="background-color: #dcffe4">램 디스크(RAM Disk)</span>와 같이 메모리의 일부분을 저장 장치로 활용하기도 함*</p>
</li>
<li><p>이는 전원이 꺼지면 파일이 모두 사라지므로 <span style="background-color: #dcffe4">영구 저장 장치는 아님</span>*</p>
</li>
</ul>
<h3 id="12-디스크-장치-개요">1.2 디스크 장치 개요</h3>
<ul>
<li>운영체제의 파일 시스템은 <span style="background-color: #dcffe4">저장 장치의 종류나 구조에 무관하게 설계됨</span></li>
</ul>
<p><strong>하드 디스크</strong></p>
<ul>
<li><p><span style="background-color: #dcffe4">자성체(magnetic material)</span>로 코팅된 여러 개의 <span style="background-color: #dcffe4">원판(플래터, platter)</span>에
디지털 정보를 저장하고 읽어 내는 장치</p>
</li>
<li><p>디스크 장치는 <span style="background-color: #dcffe4">디스크 매체 모듈과 디스크 제어 모듈</span>로 구성됨</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/826fc11f-4e97-4061-99b9-c66b88b1aeac/image.png" alt=""></p>
<h4 id="디스크-매체-모듈">디스크 매체 모듈</h4>
<p><strong>플래터(platter)</strong></p>
<ul>
<li><p>정보가 기록되는 <span style="background-color: #dcffe4">저장소</span>로 <span style="background-color: #dcffe4"><strong>스핀들(spindle)</strong></span>에 연결되어 함께 회전</p>
</li>
<li><p><span style="background-color: #dcffe4"><strong>디스크 헤드(disk head)</strong></span>는 플래터에서 <span style="background-color: #dcffe4">정보를 읽거나 기록</span></p>
</li>
<li><p><span style="background-color: #dcffe4">아래 윗면 모두 정복 저장됨</span>
플래터 당 <span style="background-color: #dcffe4">2개의 디스크 헤드</span>가 존재*</p>
</li>
<li><p>모든 <strong><span style="background-color: #dcffe4">암(arm)</span></strong>들은 하나의 <span style="background-color: #dcffe4"><strong>구동기(actuator)</strong></span>에 달려 있어 안팎으로 움직임</p>
</li>
</ul>
<h4 id="디스크-제어-모듈">디스크 제어 모듈</h4>
<ul>
<li><p><span style="background-color: #dcffe4"><strong>프로세서(processor)</strong></span>가 위치하는 곳으로, <span style="background-color: #dcffe4">운영체제로부터 명령을 받고 해석</span> 후
<span style="background-color: #dcffe4">디스크 매체 모듈을 제어</span>하여 <span style="background-color: #dcffe4">물리적인 디스크 액세스</span> 진행</p>
</li>
<li><p>입출력 데이터를 임시 저장하는 <span style="background-color: #dcffe4"><strong>디스크 캐시</strong></span>를 두고 있음</p>
</li>
<li><p>1MB~몇 십 MB 크기의 빠른 반도체 메모리*</p>
</li>
<li><p>운영체제가 전달한 데이터는 <span style="background-color: #dcffe4">디스크 캐시</span>에 가장 먼저 저장되며,
프로세서에 의해 디스크 캐시 → <span style="background-color: #dcffe4">플래터</span>로 저장됨</p>
</li>
<li><p>읽기 요청을 받은 경우, 
프로세서는 플래터 → 디스크 캐시로 이동 후 호스트로 전송</p>
</li>
<li><p>디스크 캐시는 운영체제나 응용프로그램에게 <span style="background-color: #dcffe4"> 디스크 입출력 시간 단축</span></p>
</li>
</ul>
<h4 id="호스트host">호스트(host)</h4>
<ul>
<li><p>원격 네트워크에서 사용자가 연결하여 사용하는 장치를 <span style="background-color: #dcffe4">터미널(terminal)</span></p>
</li>
<li><p>원격에 있는 컴퓨터를 <span style="background-color: #dcffe4">호스트 또는 호스트 컴퓨터</span>라고 함</p>
</li>
<li><p>디스크장치의 경우 입출력 버스를 통해 메인 컴퓨터와 연결되는데,
<span style="background-color: #dcffe4">메인 컴퓨터를 호스트</span>라고 함*</p>
</li>
</ul>
<h4 id="트랙-섹터-실린더">트랙, 섹터, 실린더</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/021f251c-0a65-4a8b-b66d-496f20cbe81b/image.png" alt=""></p>
<p><strong>섹터(sector)</strong></p>
<ul>
<li>디스크에서 읽고 쓰는 <span style="background-color: #dcffe4">최소 단위</span></li>
<li>512바이트 혹은 4096바이트*</li>
</ul>
<p><strong>트랙(track)</strong></p>
<ul>
<li>플래터에 정보가 저장되는 하나의 동심원</li>
<li>여러 개의 섹터로 구성됨*</li>
</ul>
<p><strong>실린더(cylinder)</strong></p>
<ul>
<li>모든 플래터를 통틀어 <span style="background-color: #dcffe4">같은 반지름의 트랙 그룹</span></li>
</ul>
<p><em>4장의 플래터를 가진 디스크는 헤드가 8개이므로,
실린더는 8개의 트랙으로 구성됨</em></p>
<h4 id="섹터와-블록">섹터와 블록</h4>
<p><strong>섹터(sector)</strong></p>
<ul>
<li>디스크가 입출력하는 <span style="background-color: #dcffe4">물리적인 최소 단위</span></li>
</ul>
<p><strong>블록(block)</strong></p>
<ul>
<li><p>운영체제가 파일 데이터를 다루는 <span style="background-color: #dcffe4">논리적인 단위</span></p>
</li>
<li><p>운영체제는 파일을 블록 단위로 나누어 
하드 디스크에 <span style="background-color: #dcffe4">분산 배치</span>하고, 블록 단위로 읽고 씀</p>
</li>
<li><p>블록의 크기는 <span style="background-color: #dcffe4">운영체제마다 다르며, <strong>몇 개의 섹터</strong></span>로 구성됨</p>
</li>
</ul>
<h3 id="13-파일-입출력-주소">1.3 파일 입출력 주소</h3>
<h4 id="디스크-물리-주소와-논리-블록-주소">디스크 물리 주소와 논리 블록 주소</h4>
<ul>
<li><strong>응용프로그램</strong> - 파일 내 바이트 주소 </li>
<li><strong>운영체제</strong> - 논리 블록 주소</li>
<li><strong>디스크 장치</strong> - 디스크 물리 주소</li>
</ul>
<p><strong>디스크 물리 주소</strong></p>
<ul>
<li>디스크 장치는 플래터에서 <span style="background-color: #dcffe4">섹터 단위</span>로 읽고 씀<pre><code>CHS(Cylinder-Head-Sector) 물리 주소 = [실린더 번호, 헤드 번호, 섹터 번호]</code></pre></li>
</ul>
<p><strong>논리 블록 주소(Logical Block Address)</strong></p>
<ul>
<li><p>디스크의 모든 블록들을 <span style="background-color: #dcffe4">일차원 배열</span>로 나열하고 번호를 매긴 주소</p>
</li>
<li><p>블록 번호는 맨 바깥쪽 실린더 → 안쪽 실린더 순서
맨 위의 트랙 → 아래 트랙으로 이동</p>
</li>
<li><p>디스크의 물리적인 구조와 무관함</p>
</li>
</ul>
<p><strong>바이트 번호</strong></p>
<ul>
<li>파일 내 바이트의 위치, 즉 <span style="background-color: #dcffe4">옵셋</span></li>
</ul>
<h4 id="파일-주소-변환">파일 주소 변환</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/43e94f72-a819-4ef0-b80e-a267c1b91a10/image.png" alt=""></p>
<ul>
<li><p>사용자나 응용프로그램은 파일 데이터가 
바이트 단위로 <span style="background-color: #dcffe4">연속해서 저장</span>된다고 생각함</p>
</li>
<li><p>운영체제는 파일을 블록 크기로 분할하고, 각 블록을 디스크에 <span style="background-color: #dcffe4">분산 저장</span></p>
</li>
<li><p>파일 블록 배치 정보(파일의 각 블록이 저장된 디스크 블록 번호)를 
별도로 저장 및 관리</p>
</li>
<li><p>응용프로그램에서 파일 내 바이트 주소의 변환 과정</p>
<pre><code>파일 내 바이트 주소 
→ 논리 블록 주소(운영체제에 의해) 
→ CHS 물리 주소(디스크 장치에 의해)</code></pre></li>
</ul>
<h4 id="주소-계층화-의미">주소 계층화 의미</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/a7d29f45-b929-4c56-81c3-efb6a19ba095/image.png" alt=""></p>
<ul>
<li><p>응용프로그램, 운영체제, 디스크 장치 사이에서 
파일 데이터를 바라보는 시각은 <span style="background-color: #dcffe4">계층화</span>되어있음</p>
</li>
<li><p>계층 구조로 인해 사용자나 응용프로그램, 운영체제, 그리고 디스크 장치가
<span style="background-color: #dcffe4">상호 독립적</span>으로 정의된 기능 수행 가능</p>
</li>
<li><p>응용프로그램 작성자는 <span style="background-color: #dcffe4">저장 매체의 하드웨어 구조나 특성에 대한 지식 없이</span>
파일 입출력을 가능하게 함</p>
</li>
<li><p>운영체제는 저장 장치의 종류나, 실린더 수, 헤드 수, 트랙당 섹터 수 등
<span style="background-color: #dcffe4">저장 장치의 하드웨어와 무관하게 개발</span>될 수 있음</p>
</li>
<li><p>SSD와 같은 실린더나 트랙 등 디스크 포맷을 가지지 않는 저장 매체도
동일한 방식으로 입출력 가능*</p>
</li>
</ul>
<h3 id="14-파일-시스템의-정의와-범위">1.4 파일 시스템의 정의와 범위</h3>
<p><strong>파일 시스템</strong></p>
<ul>
<li>저장 매체에 파일을 생성·저장·읽기·쓰기의 기능을 가진 운영체제를 통칭
<img src="https://velog.velcdn.com/images/hi_soap/post/c6a2d230-faf6-495e-bd95-78185962e789/image.png" alt=""></li>
</ul>
<p><strong>파일 시스템을 구성하는 4가지 요소</strong></p>
<ul>
<li>파일 시스템의 논리 구조</li>
<li>저장 장치에 파일 시스템 구축</li>
<li>커널 내 파일 입출력 구현</li>
<li>응용프로그램을 위한 파일 시스템 인터페이스</li>
</ul>
<h4 id="파일-시스템의-논리-구조">파일 시스템의 논리 구조</h4>
<ul>
<li>운영체제는 여러 파일을 다루기 위해,
디렉터리와 파일들로 이루어지는 <span style="background-color: #dcffe4">트리 계층 구조</span>로 파일 시스템을 구성</li>
</ul>
<h4 id="저장-장치에-파일-시스템-구축">저장 장치에 파일 시스템 구축</h4>
<ul>
<li>파일들을 저장 매체 속에 <span style="background-color: #dcffe4">블록 단위로 분산 저장·관리</span>하기 위한 체계</li>
<li>저장 매체 속 사용/미사용 블록에 관한 정보 등의 설계*</li>
</ul>
<h4 id="커널-내-파일-입출력-구현">커널 내 파일 입출력 구현</h4>
<p><strong>1) 파일 생성</strong></p>
<ul>
<li>저장 매체의 빈 공간에 물리적으로 파일 이름과 속성 등을 기록</li>
</ul>
<p><strong>2) 파일 열기</strong></p>
<ul>
<li>파일을 읽고 쓰기 전, <span style="background-color: #dcffe4">파일의 존재, 접근 권한 등을 확인</span>하는 작업과 함께
파일을 읽고 쓰고 공유할 수 있도록 <span style="background-color: #dcffe4">커널 내 구조 형성</span></li>
</ul>
<p><strong>3) 파일 읽기</strong></p>
<ul>
<li>파일 블록이 <span style="background-color: #dcffe4">저장된 위치</span>를 알고 파일 데이터 읽기</li>
</ul>
<p><strong>4) 파일 쓰기</strong></p>
<ul>
<li>저장 매체에 파일 데이터를 기록하는 기능</li>
<li>이미 존재하는 파일 데이터를 갱신하는 경우엔 데이터를 덮어 쓰고,
새로운 파일 데이터를 기록하는 경우 저장 매체의 빈 공간을 할당받아 기록*</li>
</ul>
<p><strong>5) 파일 닫기</strong></p>
<ul>
<li>파일 열기 시에 형성된 커널 내 자료 구조 해제</li>
</ul>
<p><strong>6) 파일 삭제</strong></p>
<ul>
<li>저장 매체에서 파일이 저장된 영역을 빈 영역 리스트에 반환</li>
</ul>
<p><strong>7) 파일 메타 정보 읽기/변경</strong></p>
<ul>
<li>파일의 속성 등 메타 정보를 읽거나 변경</li>
</ul>
<h4 id="응용프로그램을-위한-파일-시스템-인터페이스시스템-호출">응용프로그램을 위한 파일 시스템 인터페이스(시스템 호출)</h4>
<ul>
<li>커널 내에 구현된 파일 입출력 기능을 활용할 수 있도록
<code>open()</code>, <code>close()</code>, <code>read()</code>, <code>write</code>, <code>seek()</code> 등 다양한 시스템 호출 제공</li>
<li>응용프로그램은 <span style="background-color: #dcffe4">시스템 호출을 통해서만</span> 파일 시스템 사용 가능*</li>
</ul>
<h3 id="15-파일-시스템-입출력-계층">1.5 파일 시스템 입출력 계층</h3>
<ul>
<li>파일 시스템은 파일이 입출력되는 <span style="background-color: #dcffe4">모든 과정에서 관여</span></li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/7c17f9cf-1b3d-4db3-ada7-7d4446a3e03b/image.png" alt=""></p>
<ul>
<li>응용프로그램은 일반적으로 <span style="background-color: #dcffe4">파일 입출력 라이브러리</span>를 이용하여 작성됨</li>
</ul>
<p><strong>라이브러리</strong></p>
<ul>
<li>필요에 따라 <span style="background-color: #dcffe4">시스템 호출</span>을 이용하여 
<span style="background-color: #dcffe4">운영체제에게 파일 입출력을 요청</span></li>
</ul>
<p><strong>파일 입출력 계층</strong></p>
<ul>
<li><span style="background-color: #dcffe4">요청이 정상인지 검사</span>하고
요청된 파일 데이터의 <span style="background-color: #dcffe4">바이트 주소 → 논리 블록 주소(LBA)</span>로 변환</li>
</ul>
<p><strong>논리 블록 입출력 계층</strong></p>
<ul>
<li><p>논리 블록 주소에 해당하는 디스크 블록이 
<span style="background-color: #dcffe4">커널 내의 버퍼(버퍼 캐시)</span>에 있는지 확인</p>
</li>
<li><p><strong>있으면</strong> <span style="background-color: #dcffe4">버퍼 캐시에서 읽거나 쓰기</span> 작업으로 완료</p>
</li>
<li><p><strong>없으면</strong> 디스크 장치 드라이버(disk driver)를 통해 
디스크 장치로 논리 블록 주소를 보내 입출력 지시</p>
</li>
</ul>
<p><strong>물리 저장소 입출력 계층</strong></p>
<ul>
<li><p>저장 장치에서 구현되는 계층으로, 
<span style="background-color: #dcffe4">논리 블록 주소 → 저장 장치의 물리 주소</span>로 변환하여 데이터를 액세스</p>
</li>
<li><p>저장 장치가 
하드 디스크 → CHS 물리 주소
SSD → 플래시 메모리 상의 블록 주소</p>
</li>
</ul>
<h3 id="16-파일-읽기-사례">1.6 파일 읽기 사례</h3>
<ul>
<li>C 표준 라이브러리 함수 <code>fread()</code>를 이용하여 파일에서 <code>n</code>바이트 읽기<pre><code class="language-c">char buf[SIZE]
FILE* fp = fopen(...);
fread(fp, buf, n);</code></pre>
<img src="https://velog.velcdn.com/images/hi_soap/post/6607f8c6-b8c6-4b17-ae82-d46083c38ce4/image.png" alt=""></li>
</ul>
<p><strong>파일 데이터의 복사 경로</strong>
<code>디스크 캐시 → 커널의 버퍼 캐시 → C 라이브러리의 버퍼 → 응용프로그램의 buf[]배열</code></p>
<h4 id="파일-읽기-과정을-통한-주목-사항">파일 읽기 과정을 통한 주목 사항</h4>
<p><strong>1) 파일 읽기의 과정은 <span style="background-color: #dcffe4">계층 구조</span>로 이루어지고 그 역할이 잘 구분됨</strong></p>
<p><strong>2) 운영체제 커널의 역할은 사용자나 응용프로그램이 파일이 저장되는 
저장 장치의 종류나 구조, 위치 등 <span style="background-color: #dcffe4">물리적인 특성에 무관</span>하게 입출력 지원</strong></p>
<p><strong>3) <span style="background-color: #dcffe4">디스크 드라이버에 의해</span> 파일에 대한 <span style="background-color: #dcffe4">논리적 구조와 물리적 저장 공간을 분리</span></strong></p>
<p><strong>4) 파일 데이터는 <span style="background-color: #dcffe4">여러 번의 복사</span>를 거쳐 이동</strong></p>
<ul>
<li><span style="background-color: #dcffe4">버퍼</span>들로 인해 많은 시간이 소요되기도 하지만, </li>
<li>동일한 파일 블록 액세스, 
여러 스레드에 의해 동일한 파일이 공유되는경우
입출력 성능 향상*</li>
</ul>
<h2 id="02-파일-시스템의-논리-구조">02. 파일 시스템의 논리 구조</h2>
<h3 id="21-파일-시스템-구조">2.1 파일 시스템 구조</h3>
<ul>
<li>오늘날 운영체제는 대부분 
<span style="background-color: #dcffe4">트리 계층 구조(tree hierarchical structure)</span>로 파일 시스템을 구성</li>
<li>파일이 몇 십 개 수준이라면 계층 구조 없이 일차원적으로 저장*</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/530cde30-a7f2-4a91-b684-a706028cdfd5/image.png" alt=""></p>
<h4 id="디렉터리와-파일의-계층-구조">디렉터리와 파일의 계층 구조</h4>
<p><strong>디렉터리(directory)</strong></p>
<ul>
<li><p>파일과 서브 디렉터리를 담기 위한 컨테이너</p>
</li>
<li><p><strong>루트 디렉터리(root directory)</strong>
파일 시스템 계층 구조의 <span style="background-color: #dcffe4">최상위 디렉터리</span></p>
</li>
<li><p><strong>서브 디렉터리(sub directory)</strong>
<span style="background-color: #dcffe4">루트 디렉터리의 하부</span>에 존재하는 디렉터리</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/cc4a8bc8-169f-4ff6-8ef0-ec2bb19975ef/image.png" alt=""></p>
<ul>
<li><p>대부분의 파일 시스템에서는 
<span style="background-color: #dcffe4"><strong>파일 블록의 저장 위치</strong>는 디렉터리에 두지 않고 <strong>다른 곳</strong>에 저장함</span></p>
</li>
<li><p>FAT 파일 시스템의 경우 FAT 테이블에,
유닉스나 리눅스 파일 시스템의 경우 i-node*</p>
</li>
<li><p>디렉터리는 <span style="background-color: #dcffe4"><strong>서브 디렉터리</strong>나 <strong>파일들의 목록</strong>을 저장한 파일</span></p>
</li>
</ul>
<h4 id="디렉터리와-폴더">디렉터리와 폴더</h4>
<ul>
<li><p><strong>디렉터리</strong>는 <span style="background-color: #dcffe4">파일을 담는 <strong>물리적인 컨테이너</strong></span></p>
</li>
<li><p><strong>폴더</strong>는 파일뿐 아니라 네트워크 환경, 내 컴퓨터, 제어판 등
<span style="background-color: #dcffe4">파일 개념이 아닌 여러 요소들</span>도 담을 수 있는 <span style="background-color: #dcffe4"><strong>논리적인 컨테이너</strong></span></p>
</li>
<li><p>디렉터리는 폴더의 일종이지만, 디렉터리와 폴더는 같은 개념으로 사용됨</p>
</li>
</ul>
<h4 id="파일-이름과-경로명">파일 이름과 경로명</h4>
<ul>
<li><p>파일 이름은 <code>a.jpg</code>, <code>main.cpp</code> 등 <span style="background-color: #dcffe4">이름(name)과 확장자(extension)으로 구성</span></p>
</li>
<li><p>확장자는 없을 수도 있음*</p>
</li>
<li><p><strong>파일의 경로명(pathname)</strong>은 루트 디렉터리에서 
<span style="background-color: #dcffe4">계층구조를 포함하는 완전한 파일 이름</span></p>
</li>
<li><p><code>gun.exe</code>
리눅스: <code>/Programs/Apps/gun.exe</code>
Windows: <code>C:\Programs\Apps\gun.exe</code></p>
</li>
</ul>
<h3 id="22-파일-시스템-메타-정보와-파일-메타-정보">2.2 파일 시스템 메타 정보와 파일 메타 정보</h3>
<p><strong>운영체제는 파일 시스템을 다루기 위해 다음 2개의 매타 정보를 만들고 활용함</strong></p>
<ul>
<li>파일 시스템 메타 정보</li>
<li>파일 메타 정보</li>
</ul>
<h4 id="파일-시스템-메타-정보">파일 시스템 메타 정보</h4>
<ul>
<li><p><span style="background-color: #dcffe4">파일 시스템 전체에 대한 정보</span>로, 운영체제나 파일 시스템 종류마다 다름</p>
</li>
<li><p>공통적으로는 다음과 같다</p>
</li>
<li><p><strong>파일 시스템 전체 크기
저장 장치에 구축된 파일 시스템의 현재 사용 크기
저장 장치에 구축된 파일 시스템의 비어 있는 크기
저장 장치 상에 비어 있는 블록들의 리스트*</strong></p>
</li>
<li><p>저장 매체 속 <span style="background-color: #dcffe4">예약된 특별한 위치에 저장</span>하여,
파일이나 디렉터리와 <span style="background-color: #dcffe4">섞이지 않도록 하고</span>
운영체제가 <span style="background-color: #dcffe4">쉽게 읽고 쓸 수 있도록 함</span></p>
</li>
</ul>
<h4 id="파일-메타-정보">파일 메타 정보</h4>
<ul>
<li><p><span style="background-color: #dcffe4">파일에 관한 여러 정보</span>로서 <span style="background-color: #dcffe4">파일 데이터(실제 파일 내용)는 포함되지 않음</span></p>
</li>
<li><p>파일마다 메타 정보는 <span style="background-color: #dcffe4">별도로 관리</span>되며 운영체제에 따라 조금씩 다름</p>
</li>
<li><p>공통적으로 다음과 같다</p>
</li>
<li><p><strong>파일 이름
파일 크기
파일이 만들어진 시간
파일이 수정된 시간
파일이 가장 최근에 액세스된 시간
파일을 만든 사용자(소유자)
파일 속성(접근 권한)
파일이 저장된 위치(파일 블록 배치 정보)*</strong>
<img src="https://velog.velcdn.com/images/hi_soap/post/84d650b1-c386-466a-9cbe-58e0d4a5abfa/image.png" alt=""></p>
</li>
<li><p>파일에는 파일 데이터만 저장되고 <span style="background-color: #dcffe4">파일 메타 정보는 다른 곳에 저장됨</span></p>
</li>
</ul>
<h4 id="파일-메타-정보는-어디에-저장되는가">파일 메타 정보는 어디에 저장되는가?</h4>
<ul>
<li>파일 메타 정보는 <span style="background-color: #dcffe4">파일 데이터와 분리하여 따로 저장됨</span></li>
</ul>
<h4 id="tip-파일-메타-정보-중-파일-속성file-attributes">TIP 파일 메타 정보 중 파일 속성(file attributes)</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/24724d94-8d4e-4b6b-9a14-bca194dbd37a/image.png" alt=""></p>
<h2 id="03-파일-시스템-구축">03. 파일 시스템 구축</h2>
<h3 id="31-파일-시스템-종류와-구현-이슈">3.1 파일 시스템 종류와 구현 이슈</h3>
<p><strong>파일 시스템 종류</strong></p>
<ul>
<li>FAT(File Allocation Table) - MS-DOS</li>
<li>UFS(Unix File System) - Unix</li>
<li>ext2, ext3, ext4 - Linux</li>
<li>HFS(Hierarchical File System) - Mac</li>
<li>NTFS(New Technology File System) - 윈도우즈 3.1~, FAT를 개선, 리눅스 지원</li>
<li>모두 <span style="background-color: #dcffe4">트리 구조의 계층적 파일 시스템</span>이지만, <span style="background-color: #dcffe4">저장 장치에서 구성 방식은 다름</span>*</li>
</ul>
<p><strong>파일 시스템 구현 이슈</strong></p>
<ul>
<li>디스크 장치에 비어 있는 블록들의 리스트를 어떻게 관리할 것인가?</li>
<li>파일 블록들을 디스크의 어느 영역에 분산 배치할 것인가?</li>
<li>파일 블록들이 저장된 디스크 내 위치들을 어떻게 관리할 것인가?</li>
</ul>
<h3 id="32-fat-파일-시스템">3.2 FAT 파일 시스템</h3>
<ul>
<li>1980년대 PC의 개인용 운영체제인 MS-DOS의 파일 시스템으로 개발</li>
<li>파일 개수와 크기가 작았던 당시에 적합하도록 설계</li>
<li>진화된 모습으로 지금도 사용</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/bab7e0c3-e2ca-40c6-ad60-a6bd64b242f7/image.png" alt=""></p>
<h4 id="fat-파일-시스템의-구조">FAT 파일 시스템의 구조</h4>
<ul>
<li>디스크의 맨 첫번째 섹터인 <span style="background-color: #dcffe4">부트 섹터(boot sector)</span>에서 
루트 디렉터리까지의 공간은 FAT 파일 시스템을 구축할 때(포맷할 때)
<span style="background-color: #dcffe4">고정 크기</span>로 설정됨</li>
</ul>
<h4 id="부트-섹터">부트 섹터</h4>
<ul>
<li>한 섹터 크기로 <span style="background-color: #dcffe4">파일 시스템 메타 정보 및 디스크에 관련된 정보</span>들과
<span style="background-color: #dcffe4">컴퓨터가 부팅할 때  실행되는 코드가 저장</span>되는 영역</li>
</ul>
<pre><code>DOS 버전, 섹터 당 바이트 수, 블록 당 섹터 수, FAT 개수, 루트 디렉터리 항목 개수,
전체 섹터 수, FAT 당 섹터 수, 트랙 당 섹터 수, 디스크 헤드 개수 등</code></pre><ul>
<li><p>FAT 테이블 항목들을 조사하면 현재 파일 시스템 내에서
<span style="background-color: #dcffe4">사용 중인 블록들과 비어 있는 블록들</span>을 알 수 있음</p>
</li>
<li><p>MS-DOS 운영체제의 커널 코드는 <code>IO.SYS</code>와 <code>MSDOS.SYS</code> 파일에 들어 있으며, 
이들은 모두 루트 디렉터리(<code>C:/</code>)에, 
<code>hidden</code>, <code>read-only</code>, <code>system</code> 속성으로 저장됨</p>
</li>
<li><p>부팅이 시작되면 <span style="background-color: #dcffe4">부트 섹터</span>에 들어있는 코드가 메모리에 적재되고 실행됨</p>
</li>
</ul>
<h4 id="fat1과-fat2">FAT1과 FAT2</h4>
<ul>
<li><p>파일 시스템의 전체 파일에 대해 <span style="background-color: #dcffe4">파일이 저장된 디스크 블록들의 번호 저장</span></p>
</li>
<li><p>FAT가 훼손되면 파일을 찾을 수 없기 때문에 <span style="background-color: #dcffe4">FAT2를 복사본으로 둠</span></p>
</li>
</ul>
<h4 id="루트-디렉터리">루트 디렉터리</h4>
<ul>
<li><p>루트 디렉터리는 <span style="background-color: #dcffe4">FAT2 바로 뒤에 구성됨</span></p>
</li>
<li><p>파일을 찾을 때 루트 디렉터리에서부터 시작하므로,
루트 디렉터리를 찾기 쉽도록 위치를 고정시키기 위함*</p>
</li>
<li><p>루트 디렉터리의 크기는 고정되어 있으므로
루트 디렉터리에 생성되는 <span style="background-color: #dcffe4">파일이나 서브 디렉터리의 개수도 고정됨</span></p>
</li>
<li><p>루트 디렉터리에는 분산된 파일 블록들 중 <span style="background-color: #dcffe4">시작 블록 번호를 저장</span></p>
</li>
<li><p>루트 디렉터리의 바로 아래에 있는 디렉터리의 메타데이터 저장</p>
</li>
</ul>
<h4 id="데이터-블록-영역">데이터 블록 영역</h4>
<ul>
<li><p>루트 디렉터리를 제외한 <span style="background-color: #dcffe4">모든 파일의 데이터 블록들이 저장되는 영역</span></p>
</li>
<li><p>각 파일은 <span style="background-color: #dcffe4">블록 단위</span>로 데이터 블록 영역 내에 <span style="background-color: #dcffe4">분산 저장</span>됨</p>
</li>
</ul>
<h4 id="디렉터리">디렉터리</h4>
<ul>
<li><p>파일의 목록을 담은 특별한 파일</p>
</li>
<li><p>루트 디렉터리나 서브 디렉터리 모두 구조는 동일함</p>
</li>
<li><p><span style="background-color: #dcffe4">파일 하나당 하나의 디렉터리 항목</span>이 생기므로, 
<span style="background-color: #dcffe4">디렉터리 항목의 개수는 디렉터리에 존재하는 파일 개수와 동일함</span></p>
</li>
<li><p>FAT 파일 시스템에서 디렉터리 항목은 하나의 파일 메타 정보를 모두 저장</p>
</li>
<li><p>가장 중요한 것은 <span style="background-color: #dcffe4">시작 블록 번호</span>로 파일이 저장된 첫 번째 디스크 블록 번호*</p>
</li>
</ul>
<blockquote>
</blockquote>
<p><strong>정리</strong>
<code>/usr/usrs/a.txt</code>에서
<code>usr</code>, <code>usrs</code>는 디렉터리로 이 곳에 저장되는 데이터는 
하위 디렉터리/파일의 메타데이터이고
<code>a.txt</code>는 파일로 실제 데이터가 저장됨</p>
<blockquote>
</blockquote>
<p>즉, 데이터 블록에서
usr에는 usrs의 메타데이터가 저장되고,
usrs에는 a.txt의 메타데이터가 저장됨
a.txt에는 실제 데이터가 저장됨</p>
<h4 id="255개-문자의-긴-파일-이름long-file-name-lfn">255개 문자의 긴 파일 이름(Long File Name, LFN)</h4>
<h4 id="파일-블록-배치file-allocation">파일 블록 배치(File Allocation)</h4>
<ul>
<li>파일 데이터를 <span style="background-color: #dcffe4">블록 단위</span>로 디스크에 <span style="background-color: #dcffe4">분산 저장</span>하고
저장된 위치는 <span style="background-color: #dcffe4">FAT(File Allocation Table)</span> 라고 불리는 테이블에 기록</li>
<li>이 테이블의 항목들은 <span style="background-color: #dcffe4">연결 리스트(Linked List)</span>로 연결됨*</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/0f153c65-2b05-42cd-a765-55b41e224420/image.png" alt=""></p>
<ul>
<li><p>FAT 테이블의 항목별 의미
<code>-1</code>: 파일의 마지막 블록
<code>0</code>: 사용 가능한 자유 블록
<code>2~</code>: 파일의 다음 블록 번호</p>
</li>
<li><p>FAT 파일 시스템은 구축 시 모두 <code>0</code>으로 초기화되며,
운영체제는 블록을 할당할 때 FAT 항목이 <code>0</code>인 블록을 찾아 할당함</p>
</li>
<li><p>FAT 테이블의 항목 크기는 12, 16, 32비트로 진화함에 따라
파일 시스템 이름도 FAT12/16/32로 바뀌어 옴</p>
</li>
<li><p>FAT 테이블의 항목 크기가 16비트일 때,</p>
</li>
<li><p>한 항목이 가질 수 있는 주소의 경우의 수 = 2$^{16}$개
디스크 블록 크기가 4KB라면 파일 시스템이 저장할 수 있는 데이터의 양은
2$^{16}$ x 2$^{12}$ = 2$^{20}$ = 256MB*</p>
</li>
</ul>
<h4 id="fat-파일-시스템의-장단점">FAT 파일 시스템의 장단점</h4>
<p><strong>장점</strong></p>
<ul>
<li>단순하여 구현이 쉽고 <span style="background-color: #dcffe4">외부 단편화가 없음</span></li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>파일 당 ½ 블록 크기로 <span style="background-color: #dcffe4">내부 단편화 발생</span></li>
<li>하나의 파일을 순차적으로 읽는 경우, 디스크 전체에 걸쳐 블록을 읽느라 디스크 헤드를 움직이는 <span style="background-color: #dcffe4">탐색 시간(seek time)</span>이 큼</li>
<li><span style="background-color: #dcffe4">FAT 테이블 영역이 손상</span>되면 <span style="background-color: #dcffe4">파일 시스템 전체를 읽을 수 없음</span></li>
</ul>
<h3 id="33-unix-파일-시스템">3.3 Unix 파일 시스템</h3>
<h4 id="unix-파일-시스템의-구조">Unix 파일 시스템의 구조</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/452b4fe0-1c3e-43bf-97c4-3352d58b60e8/image.png" alt=""></p>
<h4 id="부트-블록boot-block">부트 블록(boot block)</h4>
<ul>
<li><p><span style="background-color: #dcffe4">부팅이 진행될 때 처음에 메모리에 적재</span>되는 디스크 블록</p>
</li>
<li><p><span style="background-color: #dcffe4">운영체제를 적재하는 코드와 부팅 시 필요한 정보</span>가 저장됨</p>
</li>
<li><p><span style="background-color: #dcffe4">하나의 디스크를 여러 개의 파티션</span>으로 나누고 
<span style="background-color: #dcffe4">각 파티션마다 파일 시스템을 구축</span>할 수 있음</p>
</li>
<li><p>부팅에 참여하지 않는 파일 시스템의 부트 블록은 <span style="background-color: #dcffe4">비어 있음</span></p>
</li>
</ul>
<h4 id="수퍼-블록super-block">수퍼 블록(super block)</h4>
<ul>
<li><p>파일 시스템의 유지 관리를 위한 <span style="background-color: #dcffe4">파일 시스템 메타 정보</span>가 기록되는 공간</p>
</li>
<li><p><strong>파일 시스템 크기와 상태 정보
블록 크기
자유 블록 수
자유 블록 리스트
자유 블록 리스트에서 요청시 할당할 다음 블록 인덱스
i-node 리스트의 크기
자유 i-node 수
자유 i-node 리스트
자유 i-node 리스트에서 요청시 할당할 다음 자유 i-node 인덱스
루트 디렉터리의 i-node 번호
수퍼 블록이 갱신된 최근 시간*</strong></p>
</li>
<li><p>파일이 생성되고 읽고 쓰는 동안 자유 i-node를 찾거나 자유 블록을 찾는 등
<span style="background-color: #dcffe4">커널에 의해 자주 액세스됨</span></p>
</li>
<li><p>디스크의 입출력을 줄이기 위해 부팅 초기에 <span style="background-color: #dcffe4">메모리에 적재됨</span>*</p>
</li>
<li><p>메모리에 적재된 수퍼 블록은 <span style="background-color: #dcffe4">파일 입출력 동안 계속 갱신</span>하기 때문에
<span style="background-color: #dcffe4">주기적으로 디스크의 수퍼 블록에 기록</span>되어야 함</p>
</li>
<li><p>메모리 적재 후 갱신되었을 때, 디스크에 기록되지 못한 채 컴퓨터 종료 시
<span style="background-color: #dcffe4">파일 시스템이 깨지는</span> 상황 발생*</p>
</li>
<li><p>디스크에 저장된 수퍼 블록 손상 시 심각한 문제 발생
→ 파일 시스템마다 디스크에 <span style="background-color: #dcffe4"><strong>백업 수퍼 블록(backup super block)</strong></span>을 만들고,
<span style="background-color: #dcffe4"><strong>기본 수퍼 블록(primary super block)</strong></span>과 같은 상태 유지</p>
</li>
<li><p>기본 수퍼 블록이 망가지면 백업 수퍼 블록을 이용해 복구*</p>
</li>
</ul>
<h4 id="i-node와-i-node-리스트">i-node와 i-node 리스트</h4>
<p><strong>i-node(index node)</strong></p>
<ul>
<li><span style="background-color: #dcffe4">파일 메타 정보가 기록되는 구조체</span>로서 <span style="background-color: #dcffe4">파일마다 한 개씩 사용</span>됨</li>
</ul>
<p><strong>i-node 리스트</strong></p>
<ul>
<li><span style="background-color: #dcffe4">i-node들의 테이블</span>로서 <span style="background-color: #dcffe4">수퍼 블록 다음</span>에 저장되며,
파일 시스템이 구축될 때 <span style="background-color: #dcffe4">크기가 고정</span>되므로 <span style="background-color: #dcffe4">i-node개수도 고정</span></li>
</ul>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/2add29e8-b7f3-4b33-a406-9959c04c2b9c/image.png" alt=""></p>
<ul>
<li><p>파일이 만들어지면 운영체제는 <span style="background-color: #dcffe4">i-node 리스트에서 
자유 i-node를 하나 할당받아 파일 메타 정보들을 기록·관리</span></p>
</li>
<li><p>i-node 리스트에서 <span style="background-color: #dcffe4">i-node를 다 사용하게 되면 파일을 생성할 수 없음</span></p>
</li>
<li><p>i-node 리스트의 크기와 자유 i-node에 관한 정보는 <span style="background-color: #dcffe4">수퍼 블록에 기록됨</span></p>
</li>
<li><p>각 i-node는 리스트에서 <span style="background-color: #dcffe4">인덱스</span>로 구분됨</p>
</li>
<li><p>i-node 번호 = i-node 리스트 인덱스와 동일*</p>
</li>
<li><p>i-node의 번호는 0부터 시작되지만 
<span style="background-color: #dcffe4">사용할 수 있는 첫 i-node는 파일 시스템마다 다름</span></p>
</li>
<li><p>Unix는 1번, 리눅스는 2번부터 사용 가능
0번은 <span style="background-color: #dcffe4">오류 처리</span>를 위해 예약되어 있음*</p>
</li>
</ul>
<h4 id="데이터-블록들">데이터 블록들</h4>
<ul>
<li>파일과 디렉터리가 저장되는 공간</li>
<li>파일은 블록 단위로 <span style="background-color: #dcffe4">분산 저장</span>됨</li>
<li>블록의 크기는 <span style="background-color: #dcffe4">수퍼 블록</span>에 기록되어 있으며, 보통 4KB</li>
</ul>
<h4 id="디렉터리-1">디렉터리</h4>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/89a6fafd-f4c6-43d1-888d-e4a6fa15ba9a/image.png" alt="">
<img src="https://velog.velcdn.com/images/hi_soap/post/288154be-aecc-4d38-ad6c-73dbc336d1cd/image.png" alt=""></p>
<ul>
<li>FAT 파일 시스템이 각 파일의 메타 정보를 <span style="background-color: #dcffe4">디렉터리</span>에 두는 것과 달리,
Unix 파일 시스템은 파일 메타 정보를 <span style="background-color: #dcffe4">i-node</span>에 둔다.</li>
</ul>
<h4 id="탐구-11-1-unix-파일-시스템의-qa">탐구 11-1 Unix 파일 시스템의 Q&amp;A</h4>
<p><strong>Q1. Unix 파일 시스템을 사용할 때 만들 수 있는 파일 개수는?</strong></p>
<ul>
<li>파일 하나당 하나의 i-node가 필요하므로 
i-node 리스트에 있는 <span style="background-color: #dcffe4">i-node의 개수</span>와 같음</li>
</ul>
<p><strong>Q2. 수퍼 블록이 메모리에 적재된 채 사용되어야 하는 이유는?</strong></p>
<ul>
<li><p>커널은 파일이 생성될 때마다 <span style="background-color: #dcffe4">자유 i-node</span>를 찾아야 하고, 
파일이 삭제될 때마다 <span style="background-color: #dcffe4">i-node를 반환</span>하기 위해 
수퍼 블록을 읽고 쓰는 작업이 발생하기 때문에, 
<span style="background-color: #dcffe4">커널 코드의 실행을 빨리 하기 위해</span> 수퍼 블록을 메모리에 적재하여 사용</p>
</li>
<li><p>메모리에 적재된 수퍼 블록은 <span style="background-color: #dcffe4">주기적으로 디스크의 수퍼 블록에 저장됨</span></p>
</li>
</ul>
<p><strong>Q3. Unix 파일 시스템에서 
파일 시스템 메타 정보와 파일 메타 정보는 어디에 기록되는가?</strong></p>
<ul>
<li>파일 시스템 메타 정보는 <span style="background-color: #dcffe4">수퍼 블록</span>에 저장되고, 
파일 메타 정보는 <span style="background-color: #dcffe4">i-node</span>에 기록된다.
파일 이름은 <span style="background-color: #dcffe4">디렉터리 항목(데이터 블록에 위치)</span>에 기록된다.</li>
</ul>
<h4 id="tip-파일의-i-node-번호-보기">TIP 파일의 i-node 번호 보기</h4>
<p><code>ls -ial</code> 명령어를 통해 현재 디렉터리에 저장된 파일들의 
<span style="background-color: #dcffe4">i-node 번호와 i-node에 들어 있는 파일 메타 정보</span> 출력
<img src="https://velog.velcdn.com/images/hi_soap/post/4be416d3-fd57-46b3-bdf7-76d243aa2dae/image.png" alt=""></p>
<h4 id="파일-블록-배치file-allocation-1">파일 블록 배치(File Allocation)</h4>
<ul>
<li>파일을 <span style="background-color: #dcffe4">블록 단위</span>로 디스크의 여러 블록에 <span style="background-color: #dcffe4">분산 저장</span>하고,
<span style="background-color: #dcffe4">i-node에 15개의 인덱스</span>를 통해 <span style="background-color: #dcffe4">파일이 저장된 디스크 블록들의 번호</span>를 기억
```
12개의 직접 인덱스 </li>
<li>1개의 간접 인덱스 </li>
<li>1개의 이중 간접 인덱스 </li>
<li>1개의 3중 간접 인덱스<pre><code></code></pre></li>
</ul>
<p><strong>12개의 직접 인덱스(direct index)</strong></p>
<ul>
<li><p>파일이 저장된 <span style="background-color: #dcffe4">처음 12개의 디스크 블록 번호를 가리킴</span></p>
</li>
<li><p>파일 생성 시, 데이터 블록들이 저장되는 곳에서 자유 블록 1개를 할당받고,
블록 번호를 12개의 인덱스 중 첫 인덱스에 기록</p>
</li>
<li><p>블록 크기가 4KB라고 할 때, 파일 크기가 4KB를 넘어서게 되면
다시 자유 블록을 할당받고 블록 번호를 2번째 인덱스에 기록</p>
</li>
<li><p>12개의 직접 인덱스만으로 <span style="background-color: #dcffe4">파일이 저장된 블록을 가리킬 수 있음</span></p>
</li>
<li><p>블록 크기가 4KB일 때</p>
<pre><code>12개의 직접 인덱스로 가리킬 수 있는 파일 블록 수 = 12개
12개의 직접 인덱스로 가리킬 수 있는 파일 크기 = 12 x 4KB = 48KB</code></pre></li>
</ul>
<p><strong>1개의 간접 인덱스(single indirect index)</strong></p>
<ul>
<li><p>파일이 12개의 블록을 넘어서 커지게 되면, <span style="background-color: #dcffe4">i-node에 있는 간접 인덱스 사용</span></p>
</li>
<li><p>1개의 디스크 블록을 할당받아 간접 인덱스로 가리키게 하고
이 디스크 블록을 <span style="background-color: #dcffe4">파일 블록에 대한 인덱스들로 사용</span>
<img src="https://velog.velcdn.com/images/hi_soap/post/6e1cc12d-697d-458b-b3b1-4eb9c9fb089e/image.png" alt=""></p>
</li>
<li><p>블록 크기가 4KB이고 블록 번호가 4바이트(32비트)일 때,</p>
</li>
<li><p>= 블록 번호를 표현하는 데 4바이트(32비트)를 사용할 때*</p>
<pre><code>간접 인덱스로 가리킬 수 있는 파일 블록 수 = 4KB/4바이트 = 1024
간접 인덱스로 가리킬 수 있는 파일 크기 = 1024 x 4KB = 4MB
인덱스로 사용되는 디스크 블록 수 = 1개
</code></pre></li>
</ul>
<pre><code>
**1개의 2중 간접 인덱스(double indirect index)**
- 파일이 1034(12 + 1024)개의 블록을 넘어 커지게 되면, 
i-node에 있는 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;2중 간접 인덱스&lt;/span&gt;가 사용됨

- 2중 간접 인덱스가 가리키는 1개의 블록은 
1024(4KB/4바이트)개의 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;인덱스&lt;/span&gt;로 사용되고, 
이 1024개의 인덱스는 또 다시 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;1024개의 블록 번호들을 가리킴&lt;/span&gt;</code></pre><p>2중 간접 인덱스로 가리킬 수 있는 파일 블록 수 = 1024 x 1024 블록
2중 간접 인덱스로 가리킬 수 있는 파일 크기 = 1024 x 1024 x 4KB = 4GB
인덱스로 사용되는 디스크 블록 수 = 1 + 1024 = 1025개</p>
<pre><code>
**1개의 3중 간접 인덱스(triple indirect index)**
- 파일이 더 커지게 되면 
i-node에 있는 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;3중 간접 인덱스&lt;/span&gt;를 이용하여 파일 블록을 가리킴</code></pre><p>3중 간접 인덱스로 가리킬 수 있는 파일 블록 수 = 1024 x 1024 x 1024
3중 간접 인덱스로 가리킬 수 있는 파일 크기 = 1024 x 1024 x 1024 x 4KB = 4TB
인덱스로 사용되는 디스크 블록 수 = 1 + 1024 + 1024 x 1024개</p>
<pre><code>
**블록 번호가 32비트(4바이트)이고, 한 블록의 크기가 4KB일 때,
Unix 파일 시스템에서 파일의 최대 크기**
`48KB + 4MB + 4GB + 4TB`
![](https://velog.velcdn.com/images/hi_soap/post/2adda4c8-b914-4094-b9fc-31e74da78eb5/image.png)



#### 파일의 i-node를 찾는 과정
`/usr/source/main.c`를 찾는 과정
![](https://velog.velcdn.com/images/hi_soap/post/66cb2f45-d4fe-4088-bf15-f3d9c50689ae/image.png)

#### 리눅스에서 긴 파일 이름을 위한 디렉터리 항목
![](https://velog.velcdn.com/images/hi_soap/post/9cd8529b-6224-4e89-98d7-161fff2bba0c/image.png)
- `파일 이름`: 널문자(`&#39;\0&#39;`)로 끝나는 문자열로 최대 255문자까지 가능

- `파일 이름 길이`: 파일 이름의 실제 길이가 기록됨

- `파일 타입`: 파일인지, 디렉터리인지, FIFO인지, 네트워크 소켓인지, 링크인지 등 인지를 나타내는 값이 들어감

- `i-node 번호`: 크기도 4바이트로 늘었으며, 값이 0이면 사용되지 않는 디렉터리

## 04. 파일 입출력 연산
- 파일 입출력은 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;커널에 의해서만&lt;/span&gt; 이루어지므로,
응용프로그램에게 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;파일 입출력 관련 시스템 호출 함수&lt;/span&gt;들이 제공됨</code></pre><p>open(), read(), write(), close(), chmode(), create, mount(), unmount() 등</p>
<pre><code>
### 4.1 파일 찾기
- 파일 찾기의 과정은 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;**파일의 경로명**으로부터 파일의 **i-node**를 찾는 과정&lt;/span&gt;으로 
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;커널에 의해&lt;/span&gt; 이루어짐

- i-node를 찾아야 해당 파일 데이터가 담겨 있는 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;블록들의 번호&lt;/span&gt;도 알 수 있고,
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;파일 타입&lt;/span&gt;과 이 파일에 대한 현재 프로세스의 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;접근 권한&lt;/span&gt; 등을 확인 가능

### 4.2 파일 열기, open()
#### 파일을 여는 이유
- 파일이 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;존재&lt;/span&gt;하는지 확인
- 현재 프로세스가 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;파일 연산(읽기/쓰기)&lt;/span&gt;할 수 있는지
파일에 대한 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;접근 권한&lt;/span&gt;을 확인
*파일이 존재하지 않거나, 접근 권한이 없다면 `-1`을 리턴*
- 연이어 파일을 읽거나 쓰기 위한 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;커널 내에 자료 구조 형성&lt;/span&gt;

![](https://velog.velcdn.com/images/hi_soap/post/f1a195d7-8312-4b50-97f6-249029950d26/image.png)

- 파일이 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;존재&lt;/span&gt;하는지 확인
- 파일이 존재한다면 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;디스크에서 메모리로 i-node를 읽어들임&lt;/span&gt;
- 파일에 대한 접근이 가능한지 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;권한 여부 판단&lt;/span&gt;
- 파일을 읽고 쓰기 위한 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;커널 자료 구조 형성&lt;/span&gt;

#### 파일 입출력을 위한 커널 자료 구조
**메모리 i-node 테이블**
- 현재 열린 파일들에 대해 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;디스크에서 i-node를 읽어 메모리에 형성한 테이블&lt;/span&gt;
*&lt;span style=&quot;background-color: #dcffe4&quot;&gt;시스템에 1개 존재&lt;/span&gt;*

- i-node는 파일을 읽고 쓰는 과정에서 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;계속 액세스&lt;/span&gt;되므로, 
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;디스크 입출력 시간&lt;/span&gt;을 줄이기 위해 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;메모리에 올려놓고&lt;/span&gt; 사용

**오픈 파일 테이블(open file table)**
- &lt;span style=&quot;background-color: #dcffe4&quot;&gt;열려 있는 모든 파일에 관한 정보&lt;/span&gt;를 기록해둔 테이블

- &lt;span style=&quot;background-color: #dcffe4&quot;&gt;시스템에 1개&lt;/span&gt;만 있기 때문에
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;**시스템 파일 테이블(system file table), 전역 파일 테이블(global file table)**&lt;/span&gt;

- &lt;span style=&quot;background-color: #dcffe4&quot;&gt;파일이 열릴 때 마다&lt;/span&gt; 오픈 파일 테이블의 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;새 항목이 사용됨&lt;/span&gt;

- `열기 모드(R/W)`
`파일 옵셋(offset)`
파일 내에 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;다음에 읽거나 쓸 바이트 위치를 나타내는 정수값&lt;/span&gt; 
*커널에 의해 파일을 읽은 만큼 또는 쓴 만큼 옵셋을 증가시켜,
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;다음에 액세스할 파일 내 바이트 위치를 가리키게 함*&lt;/span&gt;
`메모리 i-node의 메모리 주소`
등으로 구성

- 오픈 파일 테이블의 항목 개수는 
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;이중 링크드 리스트(doubly linked list)&lt;/span&gt;로 구성되어
오픈 파일 테이블의 항목들을 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;동적으로 할당&lt;/span&gt;함
*무한정 많은 파일이 열리는 것은 허용하지 않음*


**프로세스별 오픈 파일 테이블(per-process open file table)**
- &lt;span style=&quot;background-color: #dcffe4&quot;&gt;**프로세스 당** 하나씩 존재&lt;/span&gt;하며, 
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;프로세스가 파일을 열 때마다&lt;/span&gt; 테이블에 1개의 항목이 사용됨

- 

- &lt;span style=&quot;background-color: #dcffe4&quot;&gt;오픈 파일 테이블에 대한 메모리 주소&lt;/span&gt;가 기록됨
*프로세스가 열어 놓은 모든 파일에 대해
오픈 파일 테이블에 대한 주소가 기록된 배열*

- 프로세스가 생성될 때 생성되고, 종료하면 소멸됨

- 프로세스의 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;모든 스레드에 의해 공유됨&lt;/span&gt;

- 운영체제에 따라 다르지만 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;PCB에 저장됨&lt;/span&gt;

**버퍼 캐시(buffer cache)**
- 파일을 읽고 쓰는 과정에서 
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;파일 블록들을 일시적으로 저장&lt;/span&gt;하는 메모리 공간

- &lt;span style=&quot;background-color: #dcffe4&quot;&gt;커널 공간&lt;/span&gt;에 만들어짐

- 어떤 프로세스 혹은 어떤 파일의 블록인지 표시되지 않고,
&lt;span style=&quot;background-color: #dcffe4&quot;&gt;오직 디스크 블록 번호로만 관리됨&lt;/span&gt;

- 프로세스가 파일을 읽을 때 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;버퍼 캐시에서 바로 읽어갈 수 있으며,&lt;/span&gt;
파일 쓰기 시에는 &lt;span style=&quot;background-color: #dcffe4&quot;&gt;버퍼 캐시에 블록을 쓰는 것으로 파일 쓰기를 마침&lt;/span&gt;
*버퍼 캐시에 쓰여진 블록들은 커널에 의해 적절한 시점에 디스크에 기록됨*

#### 파일 열기 과정
```c
int fd = open(&quot;/usr/source/main.c&quot;, O_RDONLY);
// /usr/source/main.c를 읽기 모드로 여는 코드</code></pre><p><strong>(1) 파일 이름으로 i-node 번호 알아내기</strong></p>
<p><strong>(2) 디스크 i-node를 메모리 i-node 테이블에 적재</strong></p>
<ul>
<li>메모리 i-node 테이블에서 <span style="background-color: #dcffe4">비어 있는 i-node를 할당</span>받은 후, 적재</li>
<li>메모리 i-node 테이블에는 동일한 i-node가 여러 개 저장되지 않음</li>
<li>동일한 파일이 여러 번 열리면 <span style="background-color: #dcffe4">메모리 i-node는 <strong>공유</strong>됨</span></li>
<li>파일이 수정될 때마다 메모리 i-node가 수정되고
적당한 시점에 디스크 i-node에 저장됨</li>
</ul>
<p><strong>(3) 오픈 파일 테이블에 새 항목 만들기</strong></p>
<ul>
<li>오픈 파일 테이블에 새 항목을 만들고 <span style="background-color: #dcffe4">메모리 i-node의 주소를 기록</span></li>
<li>파일 열기 모드(<code>O_RDONLY</code>)정보를 기록</li>
<li><code>offset</code> 값을 <code>0</code>으로 초기화</li>
<li><code>offset</code> 값은 <span style="background-color: #dcffe4">다음에 읽을 파일 내 바이트 위치</span>를 나타냄
커널은 <span style="background-color: #dcffe4">파일을 읽은 바이트만큼 offset을 증가</span>시킴*</li>
</ul>
<p><strong>(4) 프로세스별 오픈 파일 테이블에 새 항목 만들기</strong></p>
<ul>
<li>프로세스별 오픈 파일 테이블에 새 항목을 만들고
<span style="background-color: #dcffe4">오픈 파일 테이블 항목에 대한 주소 기록</span></li>
</ul>
<p><strong>(5) 프로세스별 오픈 파일 테이블의 항목 번호를 리턴</strong></p>
<ul>
<li>프로세스별 오픈 파일 테이블에 <span style="background-color: #dcffe4">방금 생성한 항목 번호 리턴</span></li>
<li>이 번호는 <span style="background-color: #dcffe4">정수 값</span>으로 응용프로그램에 선연된 변수 <code>fd</code>에 저장됨</li>
<li>리눅스, MacOS 등 Unix 계열의 운영체제에서는 이 정수를
<span style="background-color: #dcffe4"><strong>파일 디스크립터(file descriptor, fd)</strong></span>
Windows 계열에서는 <span style="background-color: #dcffe4"><strong>파일 핸들(handle)</strong></span>이라고 부름</li>
</ul>
<h4 id="파일-디스크립터file-descriptor">파일 디스크립터(file descriptor)</h4>
<ul>
<li><p><span style="background-color: #dcffe4">프로세스별 오픈 파일 테이블의 인덱스로서</span>
열린 파일마다 매겨진 <span style="background-color: #dcffe4">고유한 정수 번호</span></p>
</li>
<li><p>응용프로그램이 열어 놓은 파일을 대변하는 값*</p>
</li>
<li><p><span style="background-color: #dcffe4">파일을 연 응용프로그램</span>에게 반드시 전달해야 함</p>
</li>
<li><p><code>open()</code>의 리턴값</p>
</li>
</ul>
<h4 id="tip-파일-디스크립터-0-1-2">TIP 파일 디스크립터 0, 1, 2</h4>
<p><code>0</code>: 표준 입력 장치(키보드)
<code>1</code>: 표준 출력 장치(디스플레이)
<code>2</code>: 표준 오류 장치(디스플레이)</p>
<ul>
<li>장치들은 운영체제에서 <span style="background-color: #dcffe4">파일로 다루어짐</span><pre><code class="language-c">#include &lt;stdio.h&gt;
int main() {
  char c;
  read(0, &amp;c, 1); // 0번 파일 디스크립터(키보드)에서 문자 1개를 읽어 변수 c에 저장
  write(1, &amp;c, 1); // 1번 파일 디스크립터(디스플레이)에 변수 c의 문자 출력
  write(2, &quot;hello\n&quot;, 6); // 2번 파일 디스크립터(디스플레이)dp &quot;hello\n&quot; 출력
}
</code></pre>
</li>
</ul>
<p>// 출력 결과
// a
// ahello</p>
<pre><code>
```c
#include &lt;unistd.h&gt; // 이곳에 상수로 선언되어 있기 때문에 가능함

read(STDIN_FILENO, &amp;c, 1);
write(STDOUT_FILENO, &amp;C, 1);
write(STDERR_FILENO, &quot;hello\n&quot;, 6);

// 출력 결과
// a
// ahello

// 또는

#include &lt;stdio.h&gt;

fscanf(stdin, &quot;%c&quot;, &amp;c);
fprintf(stdout, &quot;%c&quot;, c);
fprintf(stderr, &quot;hello\n&quot;);

// 출력 결과
// a
// hello // stderr에 출력하면 버퍼를 거치지 않고 바로 출력됨
// a</code></pre><ul>
<li>처음부터 <code>printf()</code>나 <code>scanf()</code>를 사용하여 입출력을 할 수 있는 것은 운영체제가 프로세스를 생성할 때, <span style="background-color: #dcffe4">프로세스별 오픈 파일 테이블에 
파일 디스크립터 0, 1, 2 항목을 만들어 두었기 때문</span></li>
</ul>
<h3 id="43-파일-읽기-read">4.3 파일 읽기, read()</h3>
<pre><code class="language-c">char buf[1024];
int fd;
fd = open(&quot;/usr/source/main.c&quot;, O_RDONLY); // 파일 열기 후 fd는 3이라고 가정
read(fd, buf, 1024); // main.c 파일에서 1024 바이트를 읽어 buf에 저장</code></pre>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/0a6da4e9-e6e6-4453-83ae-3697f06ffefc/image.png" alt=""></p>
<h3 id="44-파일-쓰기-write">4.4 파일 쓰기, write()</h3>
<pre><code class="language-c">char buf[230];
int fd;
fd = open(&quot;/usr/source/calc/c&quot;, O_RDWR);</code></pre>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/5304300d-d1e3-4213-9d36-dc2c824464b4/image.png" alt=""></p>
<h3 id="45-파일-닫기-close">4.5 파일 닫기, close()</h3>
<pre><code class="language-c">close(fd); // fd가 가리키는 calc.c 파일 닫기</code></pre>
<p><img src="https://velog.velcdn.com/images/hi_soap/post/3cc92977-7526-49f1-abc3-bead2a0881b4/image.png" alt=""></p>
<h4 id="오픈-파일-테이블-항목의-참조카운트">오픈 파일 테이블 항목의 참조카운트</h4>
<ul>
<li><p>오픈 파일 테이블의 항목은 서로 같은/다른 프로세스에 의해 <span style="background-color: #dcffe4">공유될 수 있음</span></p>
</li>
<li><p>공유되는 개수를 기억하기 위해 
<span style="background-color: #dcffe4">참조카운트(reference count) 필드</span>를 두고 있음</p>
</li>
<li><p>파일이 닫힐 때 참조카운트를 1감소, 0이 되면 항목 제거*</p>
</li>
</ul>
<h4 id="탐구-11-2-동일한-파일을-동시에-여는-경우">탐구 11-2 동일한 파일을 동시에 여는 경우</h4>
<ul>
<li>두 프로세스 중 <span style="background-color: #dcffe4">누가 먼저 실행되느냐에 따라 결과가 달라짐</span>
<img src="https://velog.velcdn.com/images/hi_soap/post/037d38ef-52f7-4fe1-aafa-bef22022b7eb/image.png" alt=""></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>