<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jindaram</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sat, 14 Sep 2024 09:08:18 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. jindaram. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jindaram-stu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[JSCODE 운영체제 스터디 후기]]></title>
            <link>https://velog.io/@jindaram-stu/JSCODE-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@jindaram-stu/JSCODE-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sat, 14 Sep 2024 09:08:18 GMT</pubDate>
            <description><![CDATA[<h3 id="jscode-모의면접을-신청하고-시작하게된-계기">JSCODE 모의면접을 신청하고 시작하게된 계기</h3>
<p>유튜브를 통해 JSCODE를 알게 되었는데, 스터디를 모집한다고 하여 참여하게 되었다. 평소 운영체제에 대한 학습이 미흡하다고 생각하여, 모의 면접을 목적으로 준비한다긴 보단 <em>(회사를 다니는 입장이다 보니 그렇게 면접이 급하진 않았다.)</em> 운영체제 지식을 넓히고 틀을 잡는다는 생각으로 참여하게 되었다. </p>
<h3 id="jscode-모의면접-스터디를-마무리하여-개인적으로-성장한-점-느낀점-작성">JSCODE 모의면접 스터디를 마무리하여 개인적으로 성장한 점, 느낀점 작성</h3>
<p>운영체제에 대해 조금 더 체계적으로 공부할 수 있는 기회였기 때문에 그동안 두루뭉실하게 알고있던 파편화 된 지식을 구성에 맞게 다듬을 수 있는 계기가 되었다. 평소라면 조금 느슨하게 공부를 진행했을수도 있었지만, 강제성이 더해지고, 시간에 대한 제약이 생기니 더 빠르고 심화적으로 공부를 할 수 있었던 것 같다. </p>
<p>면접에서 무리없이 나의 운영체제에 대한 지식을 어필 할 수 있으려면 물어볼 법한 내용을 달달 외워 정리된 말로 대답을 하는 것보다는 <em>(내용이 너무 많아 외우는 것 조차도 무리다.)</em> 이러한 체계와 내용들을 깊게 이해하고 나만의 언어로 설명할 수 있어야 한다고 생각한다. 그러기 위해선 체계적인 공부가 필요하다고 생각이 드는데, JSCODE 운영체제 스터디는 운영체제라는 막연한 주제에 대해 핵심적인 질문과 공부 내용을 제시하기 때문에 운영체제에 대해 큰 틀을 잡아주고, 효과적이고 효율적으로 공부할 수 있었던 게 좋았다. 그리고 이러한 공부법이 내게 맞는 공부법이라고 느끼게 되었다.</p>
<p>그 외에도 면접 시 나의 면접 태도나 모습에 대해 부족한 점을 피드백 받을 수 있었다. 면접 경험이 많이 없기 때문에 면접 시에 객관적으로 내 모습을 확인할 수 있는 기회가 적었는데, JSCODE 운영체제 모의 면접 스터디를 통해 같은 스터디원들끼리 면접 태도에 대해 피드백을 받을 수 있어서, 외적인 점에 대해 고쳐야 할 것들을 알 수 있었다.</p>
<p>글을 쓰는 능력도 조금 향상된 것 같다. 보통 공부 내용을 기록한다고 한다라면 책 혹은 검색 내용에 있는 것을 그대로 옮기게 되는 경우가 다반사다. 나도 그랬고, 많은 공부 블로그의 내용들도 그렇다. 하지만 그건 정말 <code>기록</code>을 목적으로 하는 일이다. 그런 내용은 기록을 한다고 하더라도 내 언어로 쓰지 않으면 기억에 남지 않고, 내 것이 되지 않는다. 물론 나도 모든 내용을 나만의 언어로 적은 것은 아니다. 하지만 부분적으로 내가 잘 이해하고 있다는 생각이 드는 내용은 나만의 언어로 쓰려고 노력했던 것 같다. 그리고 그 결과 조금 더 글을 유연하고 자신있게 쓸 수 있던 것 같다.</p>
<p>나는 강제성 없이도 나만의 공부를 잘 해간다고 생각했었다. 하지만 JSCODE 운영체제 스터디를 하고 나서 생각이 달라졌다. 강제성 없이도 공부를 잘 해가는 사람들도 있겠지만, 나의 입장에서 그리고 공부의 효율성을 생각해봤을 때, 나는 강제성이 더해져야 공부 효율이 더 오른다고 느꼈다. 더불어 내가 공부한 내용을 면접이라는 매개체로 다시 점검 하다보니, 더욱 더 꼼꼼하게 공부한 부분도 있었던 것 같다.</p>
<h3 id="난-이런-것들을-잘했다">난 이런 것들을 잘했다.</h3>
<p>우선 공부를 하면서 나만의 언어로 기록과 공부를 하려고 노력했던 게 잘했던 것 같다. 이런 공부법이 시간은 더  오래 걸릴 수 있겠지만, 딱딱하게 정의된 내용을 억지로 머리에 집어넣으려고 하면 오히려 이해하는 데 드는 시간이 더 걸리게 된다. 그래서 처음 접하는 개념을 공부할 때, 그림을 통해 그 구조를 이해하고 그 뒤에 정의된 내용을 읽으면서 그림과 내용을 대조 시키며 공부를 했던 게 효과적이었고, 그런 글과 그림의 구조를 머리 속으로 이해한 후, 원활하게 나만의 언어로 기록할 수 있었다.</p>
<h3 id="난-이런-것들이-부족했다">난 이런 것들이 부족했다.</h3>
<p>조금 더 심화적인 내용들을 공부하는 것이 부족했던 것 같다. 모의면접 때 내가 예상할 수 없었던 깊이의 심화 질문을 몇 개 받아보니 <code>&#39;아 이런 부분을 더 생각해볼걸&#39;</code> 이라는 생각이 들었다. 공부하는 시점에 조금 더 심화된 내용이나, 발생할 수 있는 문제에 대해 생각해보면 실제 면접 시에도 민첩하게 잘 대처할 수 있을 것 같다.
질문에 대한 답을 하면, 대답한 내용과 이전에 이야기한 다른 내용이 서로 충돌하거나, 문제가 생길 수 있다. 그렇다면 면접자 입장에서는 당연히 그런 부분에 대해 물어보기 마련이다. 여기서 생각이 들었던 게, 그런 심화적인 질문들을 내 답변에 의해서 나온다는 것이다. 그렇기에 내 답변을 잘 생각하고 말하고, 그런 것에 대해 공부한다면 조금 더 유연하게 대처할 수 있을 것 같다.</p>
<h3 id="난-이런-것들을-더-잘하고-싶다">난 이런 것들을 더 잘하고 싶다.</h3>
<p>운영체제 공부 내용을 정리했던 것처럼, 다른 CS 내용과 트러블 슈팅 경험 등에 대한 내용을 체계적으로 기록하고 싶다. 운영체제를 제외하고도, 네트워크, 알고리즘 등 개발자로 진로를 정하면서 공부할 내용들은 앞으로 계속해서 쌓여있다. 운영체제를 통해 내가 공부한 내용을 나만의 언어로 작성하고, 기록하는 것들에 대한 감을 잡았기 때문에 다른 컴퓨터 공학 지식이나, 새로운 기술, 겪었던 문제와 그 해결방법들을 체계있게 기록하고 싶다.</p>
<hr>
<p>채용 시장이 많이 힘들다고 하는데, 모두 열심히 준비해서 원하는 결과를 받았으면 좋겠다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[운영체제 5주차 스터디 - 메모리 관리]]></title>
            <link>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-5%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-5%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Thu, 12 Sep 2024 09:07:22 GMT</pubDate>
            <description><![CDATA[<h2 id="주소-지정">주소 지정</h2>
<p>CPU에서 연산을 하기 위해서는 데이터를 가지고 해야 하는데, 이러한 데이터의 위치가 필요하다. 따라서 명령어는 피연산자 필드를 이용하여 데이터의 위치 정보를 제공한다. 주소 지정 방식은 피연산자 필드를 이용하여 데이터의 유효주소를 결정하는 방법이다.</p>
<h3 id="절대-주소-지정">절대 주소 지정</h3>
<p>절대 주소 지정 방식은 실제 메모리 주소, 즉 물리적 주소를 직접 사용하여 데이터를 참조하는 방식이다. 해당 데이터가 메모리 상에서 정확히 어디에 있어야 하는지 알 수 있어야 합니다. 주소 계산을 따로 하지 않기 때문에 접근 속도가 빠르고, 메모리 관리가 단순해질 순 있으나 프로그램이 고정된 메모리 주소에만 접근해야 하므로 재배치성이 떨어지고, 매모리 출동 가능성이 높아지게 됩니다.</p>
<h3 id="상대-주소-지정">상대 주소 지정</h3>
<p><code>상대 주소 지정 방식</code>은 기준 주소를 기반으로 Offset 을 더하거나 빼서 메모리의 특정 위치를 참조하는 방식입니다. 이는 프로그램이 메모리의 어디에 위치하든, 코드 내 주소가 상대적이기 때문에 재배치성이 뛰어나다. 또한 메모리 보호와 효율적인 사용이 가능하고 다양한 프로세스가 실행되는 환경에서도 메모리 충돌을 방지할 수 있습니다. 단, 절대 주소 지정 방식에 비해 주소 계산이 추가로 필요하기 때문에 성능이 조금 떨어질 수 있습니다.</p>
<h3 id="절대-주소-지정-방식과-상대-주소-지정-방식의-차이점">절대 주소 지정 방식과 상대 주소 지정 방식의 차이점</h3>
<p><code>절대 주소 지정 방식</code>은 메모리의 주소를 직접적으로 가지고 있기 때문에 메모리 접근이 직관적이고, 디버깅이 쉽지만 메모리 주소가 고정되어 있기 때문에 프로그램의 위치가 변경되면 코드 수정이 필요할 수 있기에 유연성이 떨어진다. 하지만 <code>상대 주소 지정 방식</code>은 명령어 내에 포함된 주소가 PC 값에 상대적으로 저장되기 때문에 명령어의 메모리 주소가 유동적으로 지정된다. 이는 프로그램의 위치가 변경되어도 주소가 자동으로 계산되기 때문에 코드의 유연성이 높아진다. 하지만 한번 더 메모리를 탐색하기 때문에 절대 주소 지정 방식에 비해 상대적으로 속도가 느립니다.</p>
<blockquote>
<p>유효 주소 (Effective Address)
유효주소란 메모리 접근을 위해 사용하는 실제 메모리 주소입니다. </p>
</blockquote>
<h2 id="메모리">메모리</h2>
<h3 id="메모리-분할">메모리 분할</h3>
<p>메모리 분할이란 운영체제가 메모리를 효율적으로 관리하고 여러 프로그램들이 동시에 실행될 수 있도록 메모리를 나누는 기법입니다. 주기억장치(RAM)를 여러 개의 구역으로 나누어 각 프로그램이나 프로세스가 자신만의 메모리 영역을 가지도록 하는 방법이다. </p>
<h3 id="메모리-배치-기법">메모리 배치 기법</h3>
<p>메모리 배치 기법은 운영체제가 메모리에 프로그램이나 프로세스가 사용할 메모리를 할당하는 방법이다. 이는 프로세스가 실행되기 위해 메모리에 올라갈 때, 운영체제는 적절한 메모리 공간을 찾는데, 이 때 메모리 배치 기법을 통해 메모리 공간을 선택합니다. </p>
<h3 id="내부-단편화">내부 단편화</h3>
<p>내부 단편화는 할당된 메모리 내에서 사용하지 않는 공간이 발생하는 문제다. 고정 크기의 메모리가 할당할 때, 프로그램의 크기가 할당된 메모리 블록보다 작을 경우에 발생한다. 할당된 블록의 남은 공간은 다른 프로세스에서 사용할 수 없기 때문에 메모리가 비효율적으로 사용되는 문제이다.</p>
<h3 id="외부-단편화">외부 단편화</h3>
<p>외부 단편화는 메모라 공간이 여러 개의 작은 빈 공간으로 나뉘어져 있어, 충분한 전체 빈 공간이 있음에도 불구하고 새로운 프로세스를 적재할 수 없는 문제이다. 메모리 공간이 할당되고 해제되는 것을 반복하면서 작은 조각들로 나뉜 메모리 공간이 효율적으로 사용되지 않는 상태입니다. </p>
<h3 id="colaescing-통합">Colaescing (통합)</h3>
<hr>
<p><code>Colaescing</code> 기법은 메모리 관리에서 외부 단편화 문제를 해결하기 위해 사용되는 기법으로, <strong>빈 공간의 메모리 공간들을 결합하여 하나의 큰 메모리 공간으로 만드는 기법입니다.</strong> 이 기법을 통해 메모리에 남아있는 조각난 작은 빈 공간들을 하나의 큰 공간으로 합칠 수 있어, 새로운 프로세스가 적재될 수 있는 충분한 메모리 공간을 제공한다. </p>
<h3 id="compaction-압축">Compaction (압축)</h3>
<hr>
<p><code>Compaction</code> 기법은 주로 동적 메모리 할당 시스템에서 메모리 단편화를 해결하기 위해서 사용하는 메모리 배치 기법입니다. 이는 메모라 블록들을 연속적으로 배치하여 메모리 단편화를 줄이고, 빈 공간을 확보하여 새로운 메모리 요청을 더 효율적으로 처리할 수 있도록 합니다.
메모리에서 사용 중인 블록들을 한쪽으로 이동시켜서 연속적인 메모리 공간들 만듭니다. 메모리의 빈 공간이 연속적으로 합쳐지게 되어, 큰 메모리 블록을 요구하는 프로그램이나 프로세스가 할당될 수 있습니다.</p>
<h3 id="버디-시스템-buddy-system">버디 시스템 (Buddy System)</h3>
<hr>
<p>버디 시스템 (Buddy System)은 메모리 블록을 2의 제곱 단위로 관리하는 시스템을 의미한다.  만약 프로세스 메모리 적재에 대해 요청이 들어왔을 떄, 가장 큰 메모리 블록으로부터 시작해서 Binary로 절반씩 쪼개면서 요청된 프로세스의 매모리 크기 k 에 대해 <code>2^(n-1) &lt; k &lt;= 2^n</code> 를 만족시키는 n 을 찾고 2^n 만큼의 공간에 프로세스를 적재한다. 쉽게 이야기해서 1000 Kb 짜리 프로세스 메모리에 대해서 요청이 들어왔다면 512KB &lt; 1000KB &lt; 1024KB 이기 떄문에 1024KB 짜리 메모리 공간에 해당 프로세스를 적재하는 것이다. </p>
<p>이해가 잘 안된다면 그림으로 이해해보자.
<img src="https://velog.velcdn.com/images/jindaram-stu/post/753f639a-8eca-47a5-8e3b-27a6ff5505d6/image.png" alt=""></p>
<ol>
<li>가장 큰 메모리 블록 1Mb 짜리가 존재한다. </li>
<li>그 후 100kb 짜리 요청이 들어온다. 공식에 의하면 64 kb &lt; 100kb &lt; 128kb 이기 때문에 128kb 메모리 공간에 100kb 짜리 프로세스가 적재된다. 이 떄 1MB 짜리 메모리는 요청에 대해 적합한 메모리 공간을 찾기 위해 2^n 단위로 쪼개졌다.
(<code>1mb</code>-&gt; <code>512kb</code> | <code>512kb</code> -&gt; <code>256kb</code>  | <code>256kb</code> | <code>512kb</code> -&gt; <code>128kb</code> | <code>128kb</code> | <code>256kb</code> | <code>512kb</code>)</li>
<li>그 뒤로 요청된 프로세스의 메모리 크기에 따라 메모리 블록이 쪼개어지고, 할당된다.</li>
<li>이 때, 메모리 해제가 되면 같은 Parent를 갖는 Buddy 공간이 비어있다면 병합이 된다. Release C 부분을 보게 되면 64kb 공간을 할당 받아 사용 중인 프로세스 C가 해제되면서 [ 64kb | 64kb ] 가 아닌 [ 128kb ] 가 되었다. 이 처럼 메모리 할당 시에 같은 Parent를 갖는 Buddy 공간이 비어있다면 병합이 된다. 그 다음 Release E가 되면서 다시 512kb 가 되고 Release D 후 메모리가 처음 1Mb 블록으로 변한 것을 볼 수 있다.</li>
</ol>
<h3 id="페이징-paging">페이징 (Paging)</h3>
<hr>
<p><code>페이징 (Paging)</code> 은 메모리 관리 기법 중 하나로 물리적 메모리를 일정한 크기의 고정된 블록으로 나누고, 프로세스의 주소 공간도 동일한 크기의 고정된 블록으로 나누어 관리하는 방식이다. 각각의 페이지는 물리 메모리에 매핑이 되기 때문에 연속적으로 배치되지 않아 외부 단편화 문제가 발생하지 않는다.</p>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/c0c5d1a3-4422-40db-954d-1c8793382720/image.png" alt=""></p>
<p><strong>Page Table</strong>
각각의 프로세스에서 사용하는 메모리 데이터들이 어느 프레임에 적재되어 있는지 CPU는 알기 어렵다. 이를 위해서 물리 주(프레임)에 불연속적으로 배치되더라도, CPU가 접근하는 논리 주소에 연속적으로 배치되도록 하기 위해 <code>Page Table</code> 을 이용한다.</p>
<blockquote>
<p><strong>PTBR (Page Table Base Register)</strong>
 프로세스마다 Page Table이 존재하고 CPU 내의 Page Table Base Register 는 각 프로세스의 페이지 테이블이 적재된 주소를 가리키고 있다. 하지만 페이지 테이블이 메모리에 저장되어 있다면 메모리 접근 시간이 2배로 늘어나게 된다.</p>
</blockquote>
<blockquote>
<p><strong>TLB (Translation Lookaside Buffer)</strong>
CPU 곁의 페이지 테이블 캐시 메모리이다. 페이지 테이블의 일부를 가져와 저장하는 캐쉬 메모리로서, 페이지 테이블의 접근 시간 2배 문제를 해결해준다. 
<code>TLB Hit</code> : CPU가 접근하려는 논리 주소가 TLB에 있는 경우
<code>TLB Miss</code> : CPU가 접근하려는 논리 주소가 TLB에 없는 경우 )</p>
</blockquote>
<h2 id="세그멘테이션-segmentation">세그멘테이션 (Segmentation)</h2>
<p><code>세그멘테이션 (Segmentation)</code> 은 프로세스를 논리적인 단위(Segment) 로 나누어 메모리에 할당하는 방식입니다. 페이징과 달리 세그멘테이션 방식은 메모리를 고정된 크기로 나누는 것이 아닌 프로그램의 논리적 구조에 따라 가변 크기 블록으로 나누어 집니다.</p>
<blockquote>
<p> <strong>Segment</strong>
세그멘테이션에서 프로그램이 논리적으로 나누어 질 수 있는 독립적인 단위이다. 예를 들어 코드, 데이터, 스택 등은 각기 다른 목적을 가진 논리적인 정보이기 때문에 각각의 Segment로 나누어질 수 있습니다. </p>
</blockquote>
<blockquote>
<p><strong>Segment Table</strong>
세그먼트 번호와 세그먼트의 기본 주소 (Base Address) 및 길이를 저장하는 데이터 구조입니다. 이 테이블은 가상 주소를 물리 주소로 변환하는데 사용됩니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/403cab43-d81c-45a9-81ab-3a10df0173d7/image.png" alt=""></p>
<p>프로세스는 여러 개의 세그먼트로 나뉘게 됩니다. 각 Segment는 고정 크기가 아닌, 프로그램의 요구에 따라 가변적인 크기를 가집니다. 
각 프로세스는 Segment Table을 가지고 있고, 해당 테이블에는 Segment의 시작 주소와 세그먼트의 최대 크기가 기록됩니다. 가상 주소는 Segment Number와 Offset 으로 이루어져 있으며, Segment Number를 이용해서 Segment Table에서 해당 Segment의 기본 주소를 찾은 후, 거이에 Offset 을 더해 실제 물리 메모리 주소를 계산합니다.</p>
<p>Segment는 논리적 구조가 반영할 수 있고, 각 Segment는 독립적으로 관리되므로, 특정 Segment에 대해 R/W 권한을 따로 설정할 수 있습니다. ( 예를 들어 코드 영역은 Read만 하고 싶다 라고 할 때, Segmentation 기법은 코드 Segment만 권한 설정을 하면 되지만, Paging 기법의 경우, 그러한 데이터가 고정된 크기로 분할되었기 때문에 어디서 부터 어디까지가 Code 영역의 정보인지 알 수 없다. ) 또한 여러 프로세스가 같은 Segment를 공유할 수 있습니다. 또한 Segment의 크기가 가변적이기 때문에 필요한 만큼의 메모리를 할당할 수 있습니다. 이는 고정 크기의 페이지를 사용하는 Paging 기법보다 메모리를 보다 효율적으로 사용할 수 있습니다.</p>
<p>단, Segment의 크기가 가변적이기 때문에, 메모리 내에서 사용하지 않는 작은 조작들이 발생할 수 있다. 여러 번 메모리를 할당하고 해제하다 보면, 메모리 내에서 큰 연속된 빈 공간을 확보하기 어려워지는 <code>외부 단편화</code> 문제가 발생할 수 있다. 또한 페이징과 대비하여 <code>Segmentation</code> 기법은 테이블을 관리하는데 들어가는 비용이 더 들어가고, 주소 변환 과정이 더 복잡해지는 오버헤드가 발생할 수 있다. </p>
<blockquote>
<p><strong>지금은 <code>Paging</code> vs <code>Segmentation</code> 기법 중 어떤 메모리 배치 기법이 많이 사용될까?</strong>
현대 시스템에서는 Paging 기법이 더 많이 쓰인다. Segmentation 기법은 외부 단편화 문제와 복잡한 관리 때문에 거의 사용되지 않는다고 한다. 페이징의 단순성, 외부 단편화 문제 해결, 가상 메모리의 지원 등의 장점이 현대 시스템서 더 적합하기 때문이다. 일부 시스템에서는 Paging-Segmentation 기법을 사용하기도 했으나, 현대의 시스템의 주류는 페이징 기법이다.</p>
</blockquote>
<h2 id="가상-메모리">가상 메모리</h2>
<hr>
<p>가상 메모리는 실제 메모리보다 많아 보이게 하는 기술로, 어떤 프로세스가 실행될 때 메모리에 해당 프로세스 전체가 올라가지 않더라도 실행이 가능하다는 점에 착안하여 고안됨. 
빠르고 작은 주기억장치인(RAM) 과 크고 느린 보조기억장치(디스크)와 병합하여, 하나의 크고 빠른 기억장치(가상 메모리)처럼 동작하게 하는 것이다.
가상 메모리를 구현하기 위해서는 MMU 라는 특수 메모리 관리 하드웨어를 갖추고 있어야 함.</p>
<blockquote>
<p>MMU는 가상 주소를 물리 주소로 변환하고, 메모리를 보호하는 기능을 수행한다. </p>
</blockquote>
<h3 id="가상-주소">가상 주소</h3>
<p>가상 주소는 프로세스가 참조하는 메모리 주소이다. 각 프로세스는 독립된 가상 메모리 공간을 가지며, 자신의 메모리 주소를 가상 주소로 인식한다. 프로그램이 실행되는 동안 CPU는 명령어를 수행하며, 참조하는 데이터나 명령어의 위치를 가상 주소를 통해 찾습니다. 그러나 실제 데이터는 물리 메모리에 저장되어 있기 때문에, 이 가상 주소는 물리 주소로 변환되어야만 실제로 메모리 접근이 가능합니다.</p>
<h3 id="물리-주소">물리 주소</h3>
<p>물리 주소는 실제 메모리에 저장된 데이터의 주소입니다. CPU가 실제로 데이터를 읽거나 쓸 때에는 가상 주소가 물리 주소로 변환된 후, 물리 주소를 기반으로 메모리에 접근하게 됩니다.</p>
<h3 id="가상-주소에서-물리-주소는-어떻게-변환될까">가상 주소에서 물리 주소는 어떻게 변환될까?</h3>
<p>가상 주소는 CPU의 MMU(Memory Management Unit)에 의해 물리 주소로 변환된다. MMU는 가상 주소의 페이지 번호를 이용해 페이지 테이블을 참조하고, 해당 페이지가 메모리의 어느 물리 주소에 있는지 찾아낸다.</p>
<p>가상 주소는 페이지(page) 라는 작은 블록으로 나누어지며, 각 페이지가 물리 메모리 특정 페이지 프레임에 대응된다. MMU는 이 매핑 정보를 담고 있는 페이지 테이블을 참조하여 가상 주소를 물리 주소로 변환한다. 이 떄 가상 주소는 두 부분으로 나뉜다.</p>
<ul>
<li><code>Page Number</code>: 가상 메모리 공간 내의 특정 페이지를 가리킴</li>
<li><code>Offset</code>: 페이지 내에서의 위치를 나타냄
MMU는 페이지 테이블에서 가상 주소의 페이지 번호에 해당하는 물리 메모리의 프레임 번호를 찾아낸다. 이 떄 TLB 라는 캐시 정보를 통해서 자주 찾는 데이터라면 TLB에서 해당 매핑 정보를 먼저 찾고, 찾지 못하면 (TLB Miss) 페이지 테이블을 참조한다. 즉 TLB에 없으면 추가 비용이 든다.</li>
</ul>
<p>페이지 번호를 통해 찾은 물리 메모리의 프레임에 Offset을 더하여 가상 주소와 실제 물리 주소를 계산한다. 그리고 최종 변환된 물리 주소를 통해 메모리 접근이 이루어 진다. </p>
<p>이 떄 MMU가 요청한 가상 주소에 해당하는 페이지가 물리 메모리에 없을 경우, Page Fault가 발생한다. 이 때 운영체제는 디스크에 있는 해당 페이지를 물리 메모리로 로드하는 과정을 거치게 된다.</p>
<h2 id="swapping">Swapping</h2>
<hr>
<p><strong>Swapping</strong>은 메모리에서 사용되지 않는 일부 프로세스의 메모리 데이터를 스왑 공간으로 보내는 것을 말합니다. 이 과정을 통해 메모리 공간을 확보하고, 프로세스를 실행할 수 있는 환경을 마련합니다. Swapping은 가상 메모리 시스템에서 활용되며, 물리 메모리에 더 많은 프로세스를 수용하기 위한 중요한 기법 중 하나입니다.</p>
<h3 id="과정">과정</h3>
<p>메모리가 부족한 상황이 발생하게 되면 운영체제는 메모리에 있는 프로세스 중 가장 덜 중요한 프로세스나 현재 실행 중이지 않은 프로세스 (대기상태) 를 선택하여 해당 프로세스의 전체 메모리 혹은 일부 페이지가 하드 디스크의 스왑 공간으로 옮겨집니다. 이를 <code>스왑 아웃 (Swap Out)</code> 이라고 합니다.
스왑 아웃으로 확보된 공간을 새로운 프로세스나 중요한 프로세스가 그 공간을 사용하여 실행됩니다. 이후에 스왑 아웃된 프로세스가 다시 실행되어야 할 때, 해당 프로세스는 디스크에서 <code>스왑 인 (Swap In)</code> 되어 메모리로 다시 로드 됩니다. 이 과정은 동적으로 반복됩니다.</p>
<p>이러한 <strong>Swapping</strong>은 효율적인 메모리 사용이 가능하게 하고, 물리 메모리가 부족할 때에도 더 많은 프로세스를 실행할 수 있다. 또한 현재 즉시 사용되지 않는 비활성 프로세들을 스왑 아웃하여, 실행 중인 중요한 프로세스가 더 많은 메모리 자원을 활용할 수 있게 한다.</p>
<p>단 디스크는 RAM보다 속도가 느리기 때문에 프로세스를 <code>스왑 인/아웃</code>하는 데에 상대적으로 시간이 많이 소요된다. 디스크 I/O가 빈번해지면 시스템 성능이 급저하 될 수 있다. 거기에 여러 프로세스가 계속해서 스왑 인/아을 반복하게 되면 시스템의 자원이 모두 Swapping 에만 사용되어 실제 작업이 거의 진행되지 않는 상황인 <code>스레싱 (Thrashing)</code> 이 발생할 수 있다. </p>
<h2 id="페이지-교체">페이지 교체</h2>
<p>페이지 교체는 가상 메모리 시스템에서 <strong><code>페이지 부재 (Page Fault)</code></strong> 가 발생했을 때, 메모리에 새로운 페이지를 불러오기 위해 기존에 메모리에 있던 페이지를 <code>보조 기억장치(디스크)</code>로 보내고 새 페이지를 메모리에 올리는 과정입니다. 이 떄 <code>페이지 교체 알고리즘</code>을 통해 어떤 페이지를 교체할 지 결정합니다. </p>
<h3 id="페이지-교체-알고리즘">페이지 교체 알고리즘</h3>
<p>Page Fault가 발생할 때, 페이지 교체가 일어나는데, 이때 어떠한 페이지를 교체하는 지에 대한 결정 방법이다. 어느 알고리즘을 선택하느냐에 따라 시스템 성능에 큰 영향을 끼칠 수 있다.</p>
<ul>
<li><h3 id="fifo-first-in-fist-out">FIFO (First In Fist Out)</h3>
<p>해당 알고리즘은 단순하게 가장 먼저 메모리에 올라온 페이지를 가장 먼저 교체하는 방식입니다. Queue 형태로 페이지가 관리되며 가장 오래된 페이지가 교체됩니다. 이 방법은 구현이 간단하고 이해하기 쉬우나 오래된 페이지가 자주 사용될 수 있음에도 교체될 가능성이 높기 때문에 <code>Belady의 모순 현상</code> 이 발생할 수도 있다.</p>
<blockquote>
<p><strong><em>Belady의 모순 현상</em></strong>
Page Fault가 자주 발생할 경우 Page Frame 증가시키면, 즉 사용 가능한 메모리를 증가 시키면 직관적으로는 Page Fault 발생 빈도가 감소할 것으로 예상되나, 실제로는 그렇지 않는 현상을 의미한다.</p>
</blockquote>
</li>
<li><h3 id="lru-least-recently-used">LRU (Least Recently Used)</h3>
<p>가장 최근에 사용된 적이 없는 페이지를 교체하는 방식입니다. 즉, 가장 오랫동안 사용되지 않은 페이지를 선해 교체합니다. 이는 최근 자주 사용된 페이지를 메모리에 상주하므로 성능적으로 효율적이지만, 페이지 사용 시간을 기록해야 하기 때문에 구현이 상대적으로 복잡하고 오버헤드가 큽니다.</p>
</li>
<li><h3 id="lfu-least-frequently-used">LFU (Least Frequently Used)</h3>
</li>
<li><p><em>가장 적게 사용된 페이지를 교체하는 방식입니다.*</em> 페이지 사용 횟수를 기록하고, 그 중 가장 사용빈도가 낮은 페이지를 교체합니다. 자주 사용 되지 않는 페이지를 교체하여 효율성을 높일 수 있으나, 페이지가 한 번 많이 사용 되었더라도 이후에 사용 되지 않으면 오랫동안 교체되지 않는 문제가 있습니다. 
예를 들어 몇 시간 전에 빈번하게 접근하여 사용했던 페이지가 최근에는 접근하는 횟수가 낮은데, 이전에 많이 사용된 페이지기에 교체가 안 이루어지는 것 입니다.</p>
</li>
<li><h3 id="클럭-알고리즘-clock-algorithm">클럭 알고리즘 (Clock Algorithm)</h3>
<p><code>클럭 알고리즘 (Clock Algorithm)</code> 은 페이지 테이블을 원형 큐처럼 다루며, 각 페이지에 대한 참조 비트를 검사합니다. 이 과정은 시계 방향으로 진행된다. </p>
<p>페이지 테이블의 각 페이지는 참조 비트를 가지고 있고, 참조 비트는 아래와 같은 값에 대한 의미를 가진다.</p>
<p><strong>참조 비트</strong> 
<strong>∙</strong> <code>0</code> -&gt; 최근에 참조되지 않음
<strong>∙</strong> <code>1</code> -&gt; 최근에 참조됨</p>
<p>이 때, 페이지 테이블을 원형 큐처럼 순회하는 <code>클럭 핸들러 (Clock Handler)</code>가 있는데, 이 클럭 핸들러는 현재 검사 중인 페이지의 참조 비트를 검사한다.</p>
</li>
<li><p><em>참조 비트 -&gt; 1*</em>
 페이지가 최근에 참조된 페이지로 간주하고, 참조 비트를 0으로 설정 후 클럭 핸들러의 다음 페이지로 이동 시킴. ( 해당 페이지는 교체 대상에서 제외 )</p>
</li>
<li><p><em>참조 비트 -&gt; 0*</em>
 페이지가 최근에 참조되지 않는 것으로 간주하며, 이 페이지를 교체 대상으로 선택한다. 페이지를 디스크로 스왑 아웃하고, 새로운 페이지를 메모리에 로드한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] 운영체제 3주차 스터디 - CPU 스케줄링]]></title>
            <link>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-3%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-CPU-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81</link>
            <guid>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-3%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-CPU-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81</guid>
            <pubDate>Thu, 29 Aug 2024 13:25:37 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="cpu-스케줄링">CPU 스케줄링</h1>
<hr>
<p>CPU 스케줄링은 운영체제가 CPU를 효율적으로 분배하는 방법입니다. 여러 프로세스나 스레드가 동시에 실행되려 할 때, CPU는 어떤 프로세스나 스레드에 CPU를 할당할지를 결정해야 합니다. 그리고 그 방법을 CPU 스케줄링이라고 합니다.</p>
<h2 id="스케줄러의-종류">스케줄러의 종류</h2>
<hr>
<h3 id="장기-스케줄러-long-term-scheduler-">장기 스케줄러 (Long-Term Scheduler )</h3>
<p><code>Long-Term Scheduler</code> 는 새로운 프로세스가 생성되거나, 준비 상태로 전환될 때, 프로세스를 메모리에 올려 CPU가 프로세스를 처리할 수 있도록 합니다. 메모리에 적재되기 전에 대기 상태에 있는 프로세스 중에서 시스템의 메모리 사용을 효율적으로 하기 위해 프로세스의 수를 조절하여 어떤 것을 메모리에 적재할 지 결정합니다.</p>
<h3 id="중기-스케줄러-medium-term-scheduler-수정-해야함">중기 스케줄러 (Medium-Term Scheduler) (수정 해야함)</h3>
<p>중기 스케줄러는 메모리에 너무 많은 프로세스가 존재해 메모리가 부족해지면 메모리에 있는 프로세스를 디스크와 같은 보조 기억 장치로 내보내고, 필요 시 다시 불러오는 작업을 수행합니다.</p>
<h3 id="단기-스케줄러-short-term-scheduler">단기 스케줄러 (Short-Term Scheduler)</h3>
<p>현재 실행중인 프로세스와 대기 상태인 프로세스들 사이에서 CPU를 할당하는 역할을 합니다. 프로세스의 상태를 <code>Running</code> , <code>Ready</code> , <code>Waiting</code> 등으로 전환하며 CPU를 가장 적절한 프로세스에게 할당합니다.
단기 스케줄러는 매우 자주 실행되며 프로세스가 CPU를 사용할 때마다 결정됩니다. </p>
<h2 id="선점-여부에-따른-스케줄링">선점 여부에 따른 스케줄링</h2>
<h3 id="선점형-스케줄링-preemptive-scheduling">선점형 스케줄링 (Preemptive Scheduling)</h3>
<hr>
<p><code>선점형 스케줄링 (Preemptive Scheduling)</code> 이란 운영체제에서 프로세스 스케줄링을 수항핼 때, 현재 처리 중인 프로세스를 중단하고 다른 프로세스를 처리할 수 있도록 하는 스케줄링 방식입니다. 이는 프로세스 간의 공정성을 보장하고, 시스템 자원을 효율적으로 활용하는 데에 중요한 역할을 합니다.</p>
<h3 id="비선점형-스케줄링-non-preemptive-schedulimg">비선점형 스케줄링 (Non-preemptive Schedulimg)</h3>
<hr>
<p><code>비선점 스케줄링 (Non-preemptive Schedulimg)</code> 은 한번 CPU를 할당받은 프로세스는 그 자체로 종료되거나, I/O 작업이 있을 때까지 CPU는 해당 프로세스의 작업만 처리합니다. 이 과정에서 다른 프로세스는 해당 프로세스의 CPU를 강제로 빼앗을 수 없습니다. 
일반적으로 선점형 스케줄링 보다 호출빈도가 낮고, <code>Context Switching Overhead</code>가 적다. 이는 어떠한 프로세스가 작업을 마치고 자발적으로 대기 상태로 들어가거나, 종료되는 경우 다른 프로세스가 실행된다.</p>
<h2 id="선입선출-스케줄링-fcfs">선입선출 스케줄링 (FCFS)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/f3c65fbd-7e84-4649-83e9-37b09416f011/image.png" alt=""></p>
<p><strong>선입선출 스케줄링 (First-Come First-Served)</strong> 이란 프로세스들이 도착한 순서대로 CPU를 할당받아 실행됩니다. 즉 먼저 도착한 프로세스가 먼저 실행되며, 실행이 끝날 때까지 다른 프로세스는 대기 상태로 남게 됩니다. 이는 <strong>비선점형 스케줄링</strong>의 특징이며, 선입선출 스케줄링 역시 비선점형 스케줄링의 방식 중 하나입니다.</p>
<p>간단하고 직관적이며, 프로세스들이 도착한 순서대로 CPU를 할당받아 작업을 처리하기 때문에 모든 프로세스들이 공정하게 CPU를 할당받게 됩니다. </p>
<p>하지만 긴 작업이 처리 해야하는 프로세스가 가장 먼저 도착하게 되면 그 뒤로 도착한 프로세스들이 긴 시간 동안 대기해야 합니다. (Convoy Effect) 이 때문에 뒤에 밀려있는 프로세스들은 응답 시간이 지연되고, I/O 작업이 많은 프로세스가 CPU를 점유할 경우 
<code>FCFS</code> 방식은 다른 프로세스를 처리하기 위해 CPU가 디른 프로세스로 할당되지 않기 때문에 CPU 자원을 비효율적으로 사용하게 됩니다.</p>
<h2 id="최단-작업-우선-스케줄링-sjf">최단 작업 우선 스케줄링 (SJF)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/e5cd67c0-20a3-4a0a-818f-171b8af04b8f/image.png" alt=""></p>
<p><code>최단 작업 우선 스케줄링 (Shortest Job First, SJF)</code> 은 <code>전체 실행 시간</code> 이 가장 짧은 프로세스를 우선적으로 실행하는 방식입니다. 이 알고리즘은 작업의 실행 시간을 기준으로 스케줄링을 결정하며 평균 대기 시간을 완화할 수 있다는 장점이 있습니다. </p>
<p>하지만 아래 그림과 같은 현상이 발생할 수 있습니다.
<img src="https://velog.velcdn.com/images/jindaram-stu/post/ec675098-7816-416c-b880-4b7c375fc4ea/image.png" alt=""></p>
<p>SJF 스케줄링 방식은 비선점형 스케줄링 방식이기 때문에 긴 작업 시간을 가지고 있는 <code>A 프로세스</code> 가 먼저 도착하고, CPU를 할당 받아, <code>A 프로세스의</code> 작업을 처리하던 도중 <code>B 프로세스</code> , <code>C 프로세스</code> 가 <code>Ready Queue</code>에 도착하게 되면, 대기 상태에 이르게 됩니다. 그리고 <code>B 프로세스</code>와 <code>C 프로세스</code> 는 긴 작업 시간을 가지는 <code>A 프로세스</code> 가 끝나야만 CPU를 할당받아 작업을 처리할 수 있습니다. </p>
<p>이렇게 발생하는 Convoy Effect는 프로세스 도착시간에 따라 영향을 많이 받으며 응답 시간이 긴 시간의 작업 시간을 갖는 프로세스에 따라 길어질 수 있습니다.</p>
<p>그리고 이를 해결하기 위한 <code>SRTF(Shortest Remaining Time First)</code> 스케줄링 방식이 고안되었습니다.</p>
<p><code>SJF 스케줄링</code> 방식은 평균 대기 시간을 완화하는데 효과적인 알고리즘이긴 하나, 프로세스의  실행 시간을 정확히 예측하여야 하고, 해당 정보가 불충분하거나 정확하지 않으면 효율적인 구현이 어렵습니다. 또한 긴 작업 시간을 가진 작업이 짧은 작업 시간을 가진 프로세스에 밀려 긴 시간 동안 <code>기아 상태 (Starvation)</code>에 이르게 되는 문제가 생길 수 있습니다.</p>
<h2 id="최소-잔류시간-우선-스케줄링-srtf">최소 잔류시간 우선 스케줄링 (SRTF)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/2e9ece9f-ef15-4164-b1b9-36d1b2527bbb/image.png" alt=""></p>
<p><code>최소 잔류시간 우선 스케줄링(SRTF, Shortest Remaining Time First)</code> 은 CPU 스케줄링 알고리즘 중 하나로 프로세스가 도착할 때 마다 현재 실행중인 프로세스와 도착한 프로세스의 잔여 실행 시간을 비교하여, 잔여 실행 시간이 더 짧은 프로세스가 우선적으로 CPU를 사용하도록 하는 방식이다. 
해당 스케줄링 방식은 선점형 스케줄링 방식 중 하나이며, <code>SJF(Shortest Job First)</code> 의 선점형 버전이다.</p>
<p>SJF 방식에 비해 SRTF 방식은 선점이 가능하다는 특징이 있다. 하지만 작업 시간이 짧은 프로세스에 대한 작업 처리 요청이 빈번히 들어오는 경우에는 동일하게 <code>Starvation</code> 문제가 발생할 수 있습니다.</p>
<blockquote>
<p><strong>최단 작업 스케줄링(SJF)과 최소 잔류시간 우선 스케줄링 (SRTF)의 차이점</strong>
SJF와 SRTF의 가장 큰 차이점은 선점형과 비선점형 그리고 전체 실행 시간과 남은 실행 시간의 차이이다. 먼저 <strong>SJF는 비선점형 방식으로써, CPU가 중간에 해제되어 다른 프로세스를 처리할 수 없고, 프로세스의 남은 작업 시간이 아닌, 전체 실행 시간을 기준으로 프로세스를 스케줄링 한다.</strong> 반면 <strong>SRTF 방식은 선점형으로써, CPU가 동적으로 해제되어 다른 프로세스를 처리할 수 있고, 전체 실행 시간 기준이 아닌 남은 작업 시간을 기준으로 스케줄링 한다.</strong>
상대적으로 SRTF 방식이 선점형의 특징에 따라 짧은 응답시간을 갖고, SJF는 긴 작업을 처리하고 있는 경우 다른 프로세스는 처리할 수 없기 때문에 응답시간이 늦어질 수 있다.</p>
</blockquote>
<h2 id="우선순위-스케줄링">우선순위 스케줄링</h2>
<hr>
<p><code>우선순위 스케줄링(Priority Scheduling)</code> 은 프로세스 스케줄링 알고리즘 중 하나로, 각 프로세스에 우선순위를 부여하고, Reday Queue에 프로세스가 도착하면 이 우선순위를 기반으로 프로세스의 처리 순서를 결정하는 방식이다. CPU는 가장 높은 우선순위를 가진 프로세스에게 먼저 할당된다.
우선순위는 시스템에서 정적으로 결정될 수도 있고, 동적으로 변화할 수도 있습니다.</p>
<h2 id="round-robin-스케줄링">Round Robin 스케줄링</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/7f69f954-f36c-4772-830c-3a5a086a26af/image.png" alt=""></p>
<p><code>Round Robin(RR)</code> 스케줄링은 <code>선입선출(First-Come, First-Served, FCFS)</code> 방식과 유사하지만 <strong><code>시간 할당량(TimeSlice)</code></strong> 이라는 개념을 추가한 방식입니다. 
RR 스케줄링 방식은 각 프로세스에 할당될 시간 할당량이 설정됩니다. 이 시간 동안 프로세스에 CPU가 할당되어 작업을 처리합니다.
모든 준비 상태의 프로세스들은 Ready Queue에 들어가게 되고, Ready Queue는 선입선출 방식으로 관리되며, 가장 먼저 들어온 프로세스가 CPU를 먼저 할당받습니다. 그리고 시간 할당을 받은 만큼 작업을 처리하게 되며, 작업이 끝난다면 해당 프로세스는 큐에서 제거되어, 다음 프로세스가 CPU를 할당받습니다. 
만약 시간 할당량 만큼 작업을 하고도 프로세스가 종료되지 않았다면, CPU는 해당 프로세스로부터 회수되고, 그 프로세스는 큐의 맨 끝으로 이동합니다. 그리고나서 다음 프로세스가 CPU를 할당받습니다. RR 스케줄링 방식은 이 과정을 반복하게 됩니다.</p>
<p>Round Robin 스케줄링은 모든 프로세스가 동일한 시간 할당량을 가지기 때문에 CPU가 공평하게 작업을 처리할 수 있다는 특징이 있습니다. 그렇기 때문에 어느 한 프로세스가 오랜 시간 대기 상태에 머물러 있는 기아 상태에 다른 스케줄링 방식보다 덜 노출될 수 있다는 장점이 있습니다. 하지만 RR 스케줄링 방식은 다른 스케줄링 방식보다 <code>Context Switching</code> 이 자주 발생할 수 있기에 오버 헤드가 더 발생 하고 작업 완료에 대한 시간은 다른 방식보다 더 늦어질 수 있다는 단점이 있습니다.</p>
<blockquote>
<p><strong>FCFS(First-Come First-Served) 와 RR(Round-Robin) 의 차이점</strong>
FCFS는 어떤 프로세스가 큐에 들어오면 CPU는 해당 프로세스의 모든 작업 처리가 끝날 때까지 해당 프로세스에 할당됩니다. 이에 비해 RR 방식은 FCFS 방식과 마찬가지로 큐에 들어 온 순서대로 CPU를 할당받아 작업을  처리하지만, 각 프로세스마다 시간 할당량을 분배하여 그만큼 작업을 하고, 작업하던 프로세스를 큐의 맨 뒤로 넘기고 (프로세스의 작업이 남아있다면) 다음 프로세스에 CPU를 할당하여 작업을 이어 나갑니다. 
이러한 특징에서 알 수 있듯이 FCFS는 비선점형 스케줄링 방식이고, RR 방식은 선점형 스케줄링 방식입니다.</p>
</blockquote>
<h2 id="멀티-레벨-큐-스케줄링">멀티 레벨 큐 스케줄링</h2>
<hr>
<p>멀티 레벨 큐 스케줄링 방식은 프로세스를 우선순위의 정도에 따라 여러 개의 큐로 나누어 관리하는 스케줄링 방식입니다. 쉽게 예시를 들자면 놀이공원이나 워터파크에 입장할 때, 입장객들이 많으면 일반적으로 줄을 서게 된다. 하지만 해당 놀러간 곳에 회원권이나, 특정 이벤트 당첨자의 경우 줄을 기다리지 않고, 바로 입장할 수 있는 시스템이 존재하는 곳도 있다. 
이처럼 프로세스도 우선순위가 높은 작업들은 먼저 처리하는 줄(Queue) 을 따로 만들고, 그 뒤로 우선순위에 따라 여러 개의 큐를 만들어 프로세스들을 각각의 큐에 넣어 프로세스를 스케줄링 하는 방식이다. 그리고 그 각각의 큐는 서로 다른 Ready Queue이기 때문에 다른 스케줄링 방식을 사용할 수 있습니다.
멀티 레벨 큐 스케줄링은 각 큐에 대한 스케줄링 정책을 독립적으로 사용할 수 있어 시스템을 설계하고 이해하기가 용이하다는 장점이 있다. 또한 다양한 프로세스 특성에 맞게 큐를 나누고, 각 큐에 맞는 스케줄링 정책을 사용함으로써 유연하게 스케줄링을 할 수 있다.
하지만 시스템의 상태에 따라 기아 상태가 발생할 수 있고, 큐 간 불균형 문제가 발생할 수도 있습니다.</p>
<h2 id="멀티-레벨-피드백-큐-스케줄링-mlfq">멀티 레벨 피드백 큐 스케줄링 (MLFQ)</h2>
<p><code>멀티 레벨 피드백 큐(MLFQ, Multi-Level Feedback Queue)</code> 는 멀티 레벨 큐 스케줄링 방식과 유사하지만 차이점이 있습니다. </p>
<p><code>멀티 레벨 피드백 큐</code>는 <strong>작업마다 고정된 우선순위를 부여하는 것이 아닌, 각 작업의 특성에 따라 작업 패턴을 보고 우선순위를 부여합니다.</strong></p>
<p>프로세스는 처음 높은 우선순위 큐에 할당되지만, CPU를 긴 시간 동안 사용하게 되면(CPU Bound) 낮은 우선순위 큐로 이동합니다. 반대로 프로세스가 짧은 시간 동안 CPU를 사용하고 빠르게 대기 상태에 이르는 경우에는 우선순위가 상승할 수 있습니다. 이 경우에는 I/O 작업을 빈번하게 수행하는 프로세스일 경우가 많습니다. (I/O Bound)</p>
<p>예를 들어 텍스트 에디터나 웹 브라우저는 실시간 응답이 중요한 프로세스이기 때문에 사용자의 입력(I/O)에 대해 민감하게 반응해야 합니다. 이러한 프로세스들은 짧은 <code>TimeSlice</code>를 필요로 하며, 이를 통해 빠르게 반응하여 사용자에게 피드백을 제공할 수 있기 때문에 보통 높은 우선순위 큐에 위치합니다.</p>
<p>이러한 스케줄링 방식은 프로세스의 우선순위를 유동적으로 변경할 수 있기 때문에, CPU 자원의 효율적 배분이 가능합니다. </p>
<p>우선순위가 높은 큐는 짧은 TimeSlice를 할당받고, 우선순위가 낮은 큐는 긴 시간의 TimeSlice를 할당 받습니다.   반대로 낮은 우선순위의 큐의 프로세스가 승급되기도 합니다. 이러한 스케줄링 방식은 프로세스의 우선순위를 유동적으로 변경할 수 있어, CPU 자원의 효율적 배분이 가능합니다.</p>
<blockquote>
<p>MLFQ는 각 작업마다 고정된 우선순위를 부여하는 것이 아닌 각 작업의 특성에 따라 동적으로 우선순위를 부여한다. 예를 들어 어떤 한 작업이 긴 시간 동안 CPU를 집중적으로 사용하면 해당 작업의 우선순위를 낮추고, 어떤 작업이 반복적으로 CPU를 반납하게 되면 해당 작업은 우선순위를 높게 유지한다.</p>
</blockquote>
<h1 id="기아-상태">기아 상태</h1>
<hr>
<p>CPU 스케줄링에서 <code>기아 상태 (Starvation)</code>는 특정 프로세스가 오랫동안 CPU를 할당받지 못해 실행되지 못하는 상태를 의미합니다. 해당 문제는 스케줄링 알고리즘에 의해 발생되고, 우선순위 기반 스케줄링에서 자주 나타날 수 있습니다.</p>
<h2 id="원인">원인</h2>
<hr>
<ol>
<li>우선순위 스케줄링 (Priority Scheduling) :<ul>
<li>우선순위가 높은 프로세스가 먼저 CPU를 할당받는 방식. <strong><code>우선순위가 높은 프로세스</code></strong>가 CPU를 계속 할당받게 되면, <strong><code>우선순위가 낮은 프로세스</code></strong>는 CPU 할당을 받지 못해 무기한 대기 상태에 놓일 수 있음.</li>
</ul>
</li>
<li>비선점 스케줄링 (Non-preemptive Scheduling) :<ul>
<li>한 번 CPU를 할당받은 프로세스는 자체적으로 종료되거나, 입출력 작업을 시작하기 전까지 CPU를 계속 사용. 이 경우, 짧은 작업이 계속해서 들어오면 긴 작업들이 CPU를 할당받을 기회를 얻지 못해 기아 상태에 빠질 수 있음.</li>
</ul>
</li>
<li>자원 독점 (Resource Contention) :<ul>
<li>특정 자원을 독점적으로 사용하는 프로세스가 있을 경우, 다른 프로세스들이 해당 자원을 기다리느라 CPU를 할당받지 못하게 되어 기아 상태에 빠질 수 있음.</li>
</ul>
</li>
</ol>
<p><code>기아 상태 (Starvation)</code> 에 놓이게 되면 시스템 자원을 비효율적으로 사용하게 되고, 시스템 성능을 저하 시킬 수 있음. 또한 기아 상태에 놓인 프로세스는 정상적인 작업 처리가 어렵다.</p>
<h2 id="해결-방법">해결 방법</h2>
<hr>
<h3 id="에이징-aging">에이징 (Aging)</h3>
<p>프로세스가 시스템에서 대기하는 시간이 길어질수록 해당 프로세스의 우선순위를 점진적으로 눂여주는 기법입니다. 이는 기아 상태를 효과적으로 예방할 수 있으며, 우선순위 스케줄링의 공정성을 높여줍니다.</p>
<h3 id="타임-슬라이스-조정">타임 슬라이스 조정</h3>
<p>낮은 우선순위 프로세스에게 더 긴 시간의 타임 슬라이스를 할당하거나, 높은 우선순위 프로세스에게 더 짧은 타임 슬라이스를 할당하는 방법입니다. 우선순위가 낮은 프로세스라도 할당된 시간 동안 충분히 자원을 사용할 수 있도록 합니다.</p>
<blockquote>
<p>Time Slice 란?
Time Slice는 CPU 스케줄링에서 프로세스가 CPU를 할당받아 실행되는 동안 사용할 수 있는 최대 시간을 의미합니다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] 운영체제 2주차 스터디 (2) - 쓰레드]]></title>
            <link>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-2-%EC%93%B0%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-2-%EC%93%B0%EB%A0%88%EB%93%9C</guid>
            <pubDate>Thu, 22 Aug 2024 09:36:53 GMT</pubDate>
            <description><![CDATA[<h1 id="thread">Thread</h1>
<hr>
<p>쓰레드(Thread)란 프로세스 내에서 독립적으로 실행되는 단위로써, 실제로 작업을 수행하는 주체를 의미하고, 하나의 프로세스는 여러 개의 쓰레드를 가질 수 있다.</p>
<ul>
<li><strong>병렬 처리</strong>
쓰레드는 각각의 독립적인 실행 주체이기 때문에 병렬 처리가 가능하다. 이 때문에 한 개의 쓰레드를 사용하는 방법보다 더욱 더 빨리 작업을 처리 할 수 있다.</li>
<li><strong>자원 공유</strong>
같은 프로세스 내의 쓰레드는 프로세스의 Stack을 제외한 메모리와 프로세스의 자원을 공유한다. 이는 쓰레드 간의 데이터 교환에 굉장히 효율적이고, 비용이 적다.</li>
<li><strong>경량</strong>
쓰레드의 생성은 프로세스 생성에 비해 가볍고, 쓰레드 간의 전환도 더 빠르다.</li>
</ul>
<p>웹 서버나, 데이터 베이스 등 실시간 애플리케이션에서는 쓰레드를 활용하여 높은 성능과 응답성을 제공한다. <code>Spring Boot</code> 에서 기본적으로 사용하는 <code>Tomcat</code> 서버도 기본적으로 여러 개의 쓰레드를 활용하여 클라이언트의 요청을 처리한다.</p>
<h2 id="thread의-메모리">Thread의 메모리</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/ded4fea5-8a20-4fec-bc30-d743e91c06f3/image.png" alt=""></p>
<p>좌측의 이미지는 일반적인 프로세스 메모리 공간의 모습이고, 우측의 이미지는 다수의 쓰레드가 생성된 프로세스 메모리 공간의 모습이다.
<strong>Thread</strong> 는 독립된 실행단위이기 때문에 각각의 <code>Stack</code>과 <code>기타 레지스터</code> 들을 각각의 <strong>Thread</strong> 마다 가지고 있다. </p>
<p>반면 <code>Heap</code>, <code>Data(Static)</code>, <code>Code</code> 와 같은 영역은 그 어느 <strong>Thread</strong> 에서도 접근이 가능하도록 공유 되어있다.
이처럼 <strong>Thread</strong>는 프로세스 내의 <code>Heap</code>, <code>Data(Static)</code>, <code>Code</code> 영역을 모두 공유하고 있어서, 언제 어디서든 각각의 영역에 접근이 가능하다.</p>
<p>그렇기 때문에 각각의 Thread에서 프로세스가 가지고 있는 Code 데이터를 이용해서 함수를 자유롭게 호출할 수 있다. Heap, Data 영역도 역시 공유되기 때문에 IPC 없이도
쓰레드 간의 통신이 가능하다. 이는 다수의 쓰레드가 통신으로 주고받고자 하는 데이터를 Heap 영역에 할당하여 각각의 쓰레드가 자유롭게 접근하여 사용한다고 생각하면 된다.</p>
<h2 id="tcb-thread-control-block">TCB (Thread Control Block)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/bdcfc16f-6df8-47aa-b459-9ced44cca49b/image.jpeg" alt=""></p>
<p><code>쓰레드 제어 블록(Thread Control Block 이하 TCB)</code> 이란 운영 체제에서 쓰레드를 관리하기 위해 사용하는 데이터 구조이다. TCB는 각 쓰레드의 상태와 제어 정보를 저장하여 쓰레드의 실행과 관리에 필요한 모든 정보를 포함한다.
<code>PCB(Process Control Block)</code>에 비해 적은 데이터를 저장하며, 이 때문에 <code>Context Switching</code> 시 <code>Process</code> 보다 더 빠르고 가볍다는 장점이 있다.</p>
<p>TCB에는 다음과 같은 정보가 포함 되어 있다.</p>
<ul>
<li><code>쓰레드 ID</code></li>
<li><code>쓰레드 상태</code></li>
<li><code>CPU 정보</code></li>
<li><code>우선 순위</code></li>
<li><code>PCB를 가리키는 포인터</code></li>
<li><code>스레드 생성 스레드를 가리키는 포인터</code></li>
<li><code>Stack 포인터</code>
등이 있다.</li>
</ul>
<p><code>Context Switching</code>이 발생하면, 현재 실행 중인 위와 같은 스레드의 정보를 TCP에 저장하고, 다른 스레드의 TCB에 저장된 정보를 불러와 복원하여 실행한다.</p>
<blockquote>
<p><strong>Context Switching 시, TCB가 PCB보다 빠른 이유</strong>
PCB는 프로세스 전체의 상태를 관리하는 데이터가 포함되어 있으므로, 상대적으로 TCB에 저장되는 데이터보다 양이 많다. 스레드는 프로세스 안에서 작은 실행 단위이기 때문에 그렇다. 또한 스레드는 다른 스레드와 자원을 일부 공유하기 때문에 TCB에 관리할 데이터가 더 적다. 하지만 <code>Process Context Swiching</code> 시에는 사용하는 메모리 공간 자체가 아예 다르기 때문에 메모리 공간을 전환해야 하는 등 <code>Thread Context Switching</code> 보다 오버헤드가 더 발생한다고 이야기 할 수 있다.</p>
</blockquote>
<h2 id="user-level-thread-와-kernal-level-thread">User Level Thread 와 Kernal Level Thread</h2>
<p>먼저 쓰레드는 2가지의 쓰레드로 크게 나뉜다.</p>
<ul>
<li>User Level Thread</li>
<li>Kernal Level Thread</li>
</ul>
<blockquote>
<p><strong>User Level Thread</strong></p>
</blockquote>
<p>먼저 <code>User Level Thread</code>는 커널에서 직접 생성하고 제어하는 것이 아니라, <code>User Level</code>에서 생성되고 관리된다. <code>User Level Thread</code>는 커널에서 해당 <code>Thread</code>를 인식하지 못하기 때문에 사용자가 <code>Thread</code> 관련
라이브러리를 구현하여 스케줄링하고 생성에 관여한다. 하지만 <code>User Level Thread</code>는 I/O 작업이 생길 경우, 다른 <code>Thread</code> 모두 대기 상태가 된다. 왜냐하면 커널은 <code>User Level Thread</code>에 대해서 인식하지 못하기 때문에
해당 I/O 작업이 일어난 <code>Process</code>를 <code>대기(Blocked)</code> 상태로 변경한다. 결국 해당 프로세스 내에 존재하는 모든 <code>Thread</code>가 대기 상태에 이르게 된다.</p>
<p><code>User Level Thread</code>는 커널에 독립적으로 <code>Thread</code>를 스케줄링 할 수 있기 때문에 모든 운영체제에 적용할 수 있는 이식성이 높고, 스케줄링이나 동기화를 위해 커널 영역의 기능을 쓰지 않아, 커널 전환 시 발생하는 오버헤드가 줄어듭니다.
다만 커널이 <code>User Level Thread</code>들의 알지 못하면서 생기는 부수효과들이 문제가 될 수 있습니다. (e.g. I/O 작업 시 모든 Thread Blocked)</p>
<blockquote>
<p><strong>Kernal Level Thread</strong></p>
</blockquote>
<p><code>Kernal Level Thread</code>는 커널이 Thread 생성과 스케줄링 등 관련된 모든 작업을 관리하는 방식이다. <code>User Level Thread</code>와 <code>Kernal Level Thread</code>가 One to One 매핑이 되고, 커널이 직접 스케줄링하고 관리하기 때문에 관리에 대해 많은 지원을 받을 수 있다.
하지만 오버헤드도 그만큼 늘어나게 된다. 또한 커널이 각 <code>Thread</code>를 개별적으로 관리하므로, 어떠한 <code>Thread</code> 에서 I/O 작업이 일어나도, 다른 쓰레드들이 대기 상태에 이르지 않고 작업을 계속 처리할 수 있다.</p>
<blockquote>
<p>JVM의 Thread 구조는 어떻게 되어있을까?
<a href="https://letsmakemyselfprogrammer.tistory.com/98">https://letsmakemyselfprogrammer.tistory.com/98</a></p>
</blockquote>
<h2 id="multi-thread-programming">Multi-Thread Programming</h2>
<p><strong><code>멀티 쓰레드 프로그래밍(Multi-Thread Programming)</code> 은 하나의 프로그램이 여러 쓰레드를 사용해 동시에 여러 작업을 수행하는 프로그래밍 방식이다.</strong> 여러 작업을 동시에 처리할 수 있기 때문에 프로그램의 성능이 향상되지만 여러 쓰레드가 같은 메모리 자원에 접근하면서 생길 수 있는
<em>동기화 문제</em>로 인해 데이터가 깨질 수 있다. 이를 해결하기 위해 Lock 과 같은 동기화 메커니즘을 사용할 수 있다. 하지만 이 때문에 코드가 복잡해지고 <code>데드락(DeadLock)</code> 문제가 발생할 수 있다.</p>
<ul>
<li><p><strong>장점</strong></p>
<ul>
<li>멀티 쓰레드 환경에서 각각의 쓰레드는 메모리 공유가 가능하여 효율성이 증대한다. 이로 인해 데이터 복사 비용이 줄어들고, 통신 비용이 낮아진다.</li>
<li>작업에 대해 병렬 처리가 가능하며, CPU를 조금 더 효율적으로 사용하여 애플리케이션의 응답성과 처리량과 성능을 향상 시킬 수 있다.</li>
</ul>
</li>
<li><p><strong>단점</strong></p>
<ul>
<li>단일 스레드 프로그래밍 방식보다 코드가 더 복잡해진다.</li>
<li>스레드 간의 동기화 문제가 발생하여, 데이터의 정합성이 꺠질 수 있다. 이러한 경우 일관성을 유지하기 위한 <code>Lock(락)</code>과 같은 동기화 메커니즘을 사용하여 데이터를 일관성 있게 유지해야 한다.</li>
<li>이 때문에 잘못 사용하게 되면 오히려 애플리케이션의 성능을 떨어리거나 안정성을 해칠 수 있다.</li>
</ul>
</li>
</ul>
<p>다음과 같은 코드를 통해 Java에서 멀티 쓰레드를 사용할 수 있다.</p>
<pre><code class="language-java">class MyThread extends Thread {
  public void run() {
      for (int i=0; i&lt;5; i++) {
        System.out.println(Thread.currentThread().getName() + &quot;-&quot; + i);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
      }
  }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        thread1.start();
        thread2.start();
    }
}</code></pre>
<blockquote>
<p><strong>멀티 프로세스가 있는데, 멀티 쓰레드를 사용하는 이유</strong></p>
</blockquote>
<ul>
<li><code>멀티 프로세스</code>는 각 프로세스가 독립적인 메모리 공간을 갖는데에 비해, <code>멀티 쓰레드</code> 방식은 각 쓰레드가 <code>Stack</code>과 <code>Register</code>를 제외한 공간은 모두 공유한다. 그렇기 때문에 메모리 공간을 더 효율적으로 사용함으로써 <strong><code>메모리 효율성</code></strong>을 높일 수 있다.</li>
<li><code>멀티 프로세스</code>는 <code>Context Switching</code> 비용이 <code>멀티 쓰레드</code> 방식에 비해 높기 때문에 <code>멀티 쓰레드</code> 방식을 사용하게 되면 <strong><code>Context Switching 시 발생하는 오버헤드</code></strong>가 적고, <code>Context Switching</code> 속도가 빠르다.</li>
<li>각 쓰레드 간에 데이터를 교환할 때에는 프로세스 처럼 <code>IPC</code>가 아니라 공유된 메모리를 통해 데이터를 공유할 수 있기 때문에 데이터 <strong><code>복사 및 통신 비용</code></strong> 또한 줄어들게 되고, 이로인한 <strong><code>코드 복잡성</code></strong> 역시 줄어들게 된다.</li>
<li>멀티코어 환경에서 <code>멀티 프로세스</code>, <code>멀티 쓰레드</code> 모두 병렬 작업 처리가 가능하지만, 앞서 이야기한 멀티 프로세스 방식에서는 오버헤드가 멀티 쓰레드 방식보다 더 발생하기 때문에 자원을 더 효율적으로 활용하는 데에 있어서는 멀티 쓰레딩 프로그래밍이 더 적합하다.</li>
</ul>
<blockquote>
<p><strong>멀티 쓰레딩 프로그래밍 시 주의할 점</strong></p>
</blockquote>
<ul>
<li>멀티 쓰레딩 프로그래밍 문제의 핵심은 바로 하나의 자원에 대해 여러 쓰레드가 접근이 가능하다는 점이다. 만약 불변해야 하는 데이터가 여러 쓰레드 중 하나의 쓰레드에 의해 변경이 되었다면 그 값을 읽어서 사용하는 다른 쓰레드들은 모두 영향을 받거나, 최악의 경우 작업을 정상적으로 처리할 수 없게 된다. 이러한 동기화 문제를 해결하기 위해서 Lock(락)과 같은 동기화 메커니즘을 도입해서 해결할 수 있는데, 이 역시도 DeadLock과 같은 문제점을 야기할 수 있기 때문에 멀티 쓰레딩 프로그래밍 시 교착 상태에 빠지지 않게 하거나, 동기화 문제를 주의하며 애플리케이션을 개발해야 한다.</li>
</ul>
<blockquote>
<p><strong>Thread-Safe 란?</strong>
Thread-Safe란 위에서 제시한 <code>멀티 쓰레딩 프로그래밍 시 주의할 점</code>의 문제를 해소한 상태이다. 즉, 멀티 쓰레드 프로그래밍에서 공유된 자원에 대해 동시에 접근이 이루어졌을 때, 프로그램 실행에는 아무 문제가 없는 상태를 말한다. 이 상태에서는 두 개 이상의 쓰레드가 Race Condition에 빠지지 않고, 결과에 대한 정합성이 보장 되었음을 나타낸다.
이에 대해 동기화에 대한 범위를 최소화하거나, 자원을 얻는 순서나 타임 아웃에 대해 설정하고, 가능하면 불변 객체를 사용하여 접근하는 자원에 대해 그 어떤 쓰레드에서도 자원을 변경할 수 없도록 설정한다면 Thread-Safe 하고 안전한 멀티 쓰레드 프로그래밍을 할 수 있다.</p>
</blockquote>
<h3 id="참고">참고</h3>
<p><a href="https://mooneegee.blogspot.com/2015/01/os-thread.html">https://mooneegee.blogspot.com/2015/01/os-thread.html</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] 운영체제 2주차 스터디 (1) - 프로세스]]></title>
            <link>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-1-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</link>
            <guid>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-1-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</guid>
            <pubDate>Thu, 22 Aug 2024 09:36:44 GMT</pubDate>
            <description><![CDATA[<h1 id="프로세스">프로세스</h1>
<hr>
<h2 id="프로그램-program">프로그램 (Program)</h2>
<p>영단어 _<code>Program</code>_은 <code>무언가의 진행 목록이나 순서</code> 를  의미하는 영단어이다. 이처럼 컴퓨터에서의 프로그램 (Program)은 어떤 문제를 해결하기 위한 처리 방법과 순서를 기술한 명령문 집합체의 의미를 갖는다. 프로그램 이야기하는 것은 주로 명령에 의한 진행 절차와 그 짜임을 더 중요시 하는 의미의 말이다.</p>
<p>그렇기 때문에 <code>소프트웨어</code> 와 <code>프로그램</code> 은 유사한 의미를 갖지만, 서로 강조하는 바가 다르다고 볼 수 있다. 정리하자면 프로그램은 *<em><code>컴퓨터가 어떠한 동작이나 문제를 해결하기 위해 작성된 실행 가능한 코드</code> *</em>라고 할 수 있다.</p>
<h2 id="프로세스-process">프로세스 (Process)</h2>
<p>프로세스란 컴퓨터에서 실행중인 프로그램의 인스턴스를 의미합니다. 일반적으로 컴퓨터에서 프로그램을 실행하면 해당 프로그램을 실행하는데 필요한 자원을 할당받아 프로세스가 생성됩니다.
프로세스는 다음과 같이 이루어져 있습니다.</p>
<ul>
<li><strong>코드(Text) 영역</strong> : 실행할 프로그램의 코드가 저장된 영역</li>
<li><strong>데이터(Data) 영역</strong> : 전역 변수와 정적 변수가 저장된 영역</li>
<li><strong>힙(Heap) 영역</strong> : 동적으로 할당된 메모리가 저장되는 영역 (프로그램 실행 중에 크기가 변할 수 있다)</li>
<li><strong>스택(Stack) 영역</strong> : 함수 호출과 관련된 영역 (매개변수, 반환 주소, 지역 변수 등이 저장되는 영역)</li>
<li><strong>프로세스 제어 블록(PCB) <code>커널</code></strong> : 운영체제가 프로세스를 관리하기 위해서 사용하는 자료구조로써 프로세스의 상태, 프로그램 카운터, CPU 레지스터 값, 메모리 관련 정보, I/O 상태 정보, 계정 정보 등이 포함됩니다</li>
</ul>
<blockquote>
<p><strong>프로그램(Program)과 프로세스(Process)의 차이점</strong>
프로그램은 앞서 말한 대로 바이트 수준의 의미를 갖습니다. 어떠한 작업에 대한 명령문들이 대한 집합체를 의미합니다. 하지만 프로세스는 해당 프로그램이 메모리에 적재되어 실행되고 있는 상태를 말합니다. 프로그램은 상태를 가지지 않지만 프로세스는 다양한 상태를 가질 수 있고, 프로그램은 코드와 데이터만 취급하는 반면 프로세스 이를 포함한 Stack, Heap, 레지스터, PCB 등을 다룹니다.</p>
</blockquote>
<h3 id="프로세스의-상태">프로세스의 상태</h3>
<ul>
<li><p><code>New</code>
프로세스가 생성 중인 일시적인 상태입니다. PCB와 같은 프로세스 자료구조와 같은 것들은 생성되었지만, 아직 메모리 획득을 하지 못한 상태입니다.</p>
</li>
<li><p><code>Ready</code>
프로세스가 실행되기 위해 준비되었으나, CPU 자원이 할당되지 않은 상태입니다. 준비는 되어있지만, 운영체제의 스케줄러가 프로세스를 선택하여 실행할 때까지 대기합니다. 이 때 프로세스는 Ready Queue에 적재됩니다.</p>
</li>
<li><p><code>Running</code>
프로세스가 현재 CPU 자원을 할당 받아 실행 중인 상태입니다. 프로세스는 CPU를 사용하여 명령어를 실행하고 있고, 프로그램 카운터(PC)가 현재 실행 중인 명령어를 가리키고 있는 상태입니다.</p>
</li>
<li><p><code>Blocked</code>
프로세스가 I/O 작업이나 다른 자원에 대해 대기 중인 상태입니다. 자원이 준비될 때까지 프로세스는 실행되지 않으며, 해당 자원이 준비되면 다시 대기 상태로 돌아오게 됩니다. I/O 작업 대기 중인 프로세스나 네트워크 응답을 대기 중인 프로세스의 상태를 예로 들 수 있습니다. </p>
</li>
<li><p><code>Waiting</code>
프로세스가 특정 이벤트나 조건이 발생할 때까지 대기하는 상태입니다. <code>Blocked</code>과 상태는 비슷하지만, 특정 이벤트가 발생하기를 기다리는 상태입니다.</p>
</li>
<li><p><code>Terminated</code>
프로세스가 실행을 완료하고 종료된 상태입니다. 프로세스의 실행이 끝나거나, 에러로 인해 종료되며 운영체제는 프로세스의 자원을 해제하고, PCB를 정리합니다.</p>
</li>
<li><p><code>Suspended State</code>
프로세스가 물리적인 메모리에서 내보내져 메모리 밖에 있는 상태를 의미합니다. </p>
</li>
</ul>
<blockquote>
<p>프로세스 상태 흐름
사용자가 프로그램을 실행하게 되면 커널에 해당 프로세스의 PCB가 할당되고 상태가 <code>New</code> 상태로 설정됩니다. 그 후 메모리에 적재되고 CPU를 할당받기를 기다리는 상태인 <code>Ready</code> 상태로 전이됩니다. 이 때 CPU 스케줄링에 의해 CPU를 할당받게 되면 프로세스는 <code>Running</code> 상태가 되어 작업을 처리 합니다. 그리고 CPU에 할당된 시간이 종료되어 <code>Timeout Interrupt</code>가 발생하면 CPU를 다른 프로세스에 양도하고 <code>Running</code> 에서 다시 <code>Ready</code> 상태로 전이된다. 만약 프로세스가 CPU를 할당받아 작업을 처리하던 중, I/O 작업 요청이 들어오게 되면 해당 프로세스는 <code>Blocked</code> 상태가 되고, CPU는 다른 프로세스에게 양도된다. 해당 작업을 완료하게 되면 다시 <code>Ready</code> 상태로 접어들어, CPU 할당을 기다린다.</p>
</blockquote>
<h3 id="프로세스-문맥-context">프로세스 문맥 (Context)</h3>
<p>현대의 운영체제는 CPU가 시간을 분할하여 작업을 처리하는 시분할 시스템으로 구성되어 있다. 이 때 CPU가 각 프로세스를 번갈아 가며 작업을 처리하게 되는데, 한 개의 프로세스를 여러번 작업 할 때, 이전 작업이 끝난 시점부터 이어서 작업을 하여야 하기 때문에 정확한 수행 시점과 상태를 재현할 수 있는 정보가 필요하게 된다. 그 정보를 <strong><code>프로세스 문맥 (Process Context)</code></strong> 라고 한다.</p>
<p>또한 프로세스 문맥은 다음과 같이 3가지 정도로 나뉘게 된다.</p>
<ul>
<li><strong>하드웨어 문맥</strong>
하드웨어 수준에서 말하는 프로세스의 문맥은 바로 <code>PC(Program Count)</code> 등을 비롯한 레지스터 값들을 의미한다. </li>
<li><strong>프로세스 주소공간</strong>
<code>Code</code>, <code>Data</code>, <code>Heap</code>, <code>Stack</code>에 저장되는 데이터들을 의미한다.</li>
<li><strong>커널 수준 문맥</strong>
프로세스의 메타데이터가 저장되는 <strong><code>PCB(Process Control Block)</code></strong>을 관리하고 취급하는 것을 의미한다.</li>
</ul>
<h3 id="context-switching">Context Switching</h3>
<p><strong>문맥 교환</strong> 즉, <code>Context Switching</code>은 운영체제에서 CPU가 하나의 프로세스에서 다른 프로세스로 전환할 때 발생하는 과정이다. 해당 과정에서 실행중인 프로세스의 상태를 저장하고, 다음에 실행될 프로세스의 상태를 불러오는 작업을 의미한다.</p>
<p>Context Switching이 중요한 이유는 우리가 여러 프로그램을 동시에 실행하는 것처럼 느끼게 하는 요소의 핵심 메커니즘이다. 또한 Context Switching을 통해 시스템 자원을 효율적으로 관리하고 우선 순위에 따라 CPU 시간을 프로세스에 더 할당 할 수 있다. 또한 각각의 프로세스마다 PCB에 해당하는 프로세스의 정보를 저장하기 때문에 프로세스 간의 간섭을 받지 않고 안정적인 시스템 운영이 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/073abb2c-6345-42f3-9989-3f4c534eda9c/image.png" alt=""></p>
<blockquote>
<p><strong>Context Switching 발생 과정</strong></p>
</blockquote>
<p><strong>Context Switching은 기본적으로 프로세스 A 에서 프로세스 B로 전환될 때 발생하는 작업이다.</strong> 이 작업이 발생할 수 있는 경우는 여러 프로세스가 운영체제의 스케줄러에 의해 번갈아 수행될 때나, Interrupt가 발생하여 프로세스가 변경될 때 발생할 수 있다. </p>
<p>먼저 현재 프로세스의 상태를 저장하게 된다. 프로세스의 레지스터 값 (PC, SP 등..)과 프로세스의 메모리 상태, 프로세스 식별 정보, 스케줄링 정보 등을 커널의 메모리 영역에 있는 PCB 등에 저장한다. 이 때 해당 프로세스의 상태는 <code>Blocked</code> 또는 <code>Ready</code> 상태로 업데이트 됩니다.</p>
<p>그런 후 다음으로 처리 할 프로세스의 PCB를 읽어들여 복원한 후 해당 프로세스의 상태를 <code>Running</code> 으로 설정한 후 실행을 재개합니다.</p>
<h3 id="프로세스의-메모리">프로세스의 메모리</h3>
<p>프로세스의 메모리 공간은 다음과 같이 이루어져 있습니다.</p>
<ul>
<li><strong><code>Code</code></strong>
Code 영역에서는 실행할 프로그램의 코드가 저장됩니다. CPU에서 이 영역에 있는 명령어들을 가져와서, 실행합니다. </li>
<li><strong><code>Stack</code></strong>
함수 호출 시에 지역변수 및 매개변수, 리턴 주소를 저장하는 영역입니다. 해당 영역에서는 함수 호출 시에 메모리가 할당되고, 함수 종료 시 메모리가 해제됩니다.</li>
<li><strong><code>Heap</code></strong>
프로세스 실행 중에 동적으로 할당되는 메모리 공간입니다. 해당 영역은 런타임 시에 필요에 따라 메모리를 동적으로 할당하고 해제합니다. <code>malloc</code> , <code>calloc</code> , <code>realloc</code> 와 같은 함수를 통해 메모리를 할당하며, <code>free</code> 함수를 통해 메모리를 해제합니다.</li>
<li><strong><code>Data</code></strong>
Data 영역에서는 이미 초기화 된 전역 변수와 정적(static) 변수가 저장됩니다. 해당 영역의 데이터들은 프로그램이 시작될 때 초기화되며, 프로그램 종료까지 유지됩니다. <blockquote>
<p>BSS(Block Started by Symbol) 라고 하는 영역에는 초기화 되지 않은 전역 변수와 정적 변수가 저장되는데, 시작 시엔 0으로 초기화됩니다. </p>
</blockquote>
</li>
</ul>
<h3 id="pcb-process-control-block">PCB (Process Control Block)</h3>
<p><code>프로세스 제어블록(이하 PCB)</code>는 운영체제가 프로세스를 관리하기 위해 사용하는 데이터 구조이다. 프로세스의 상태, 자원, 속성 등을 추적하는 데 필요한 정보를 포함하고, 프로세스 생성 및 프로세스 스케줄링에 필수적인 역할을 한다.</p>
<p>PCB에는 프로세스의 상태 (e.g. <code>New</code>,<code>Running</code> 등), 프로세스 식별자(PID), 프로그램 카운터(PC), 레지스터, 메모리 관리 정보나 CPU 스케줄링 정보 등이 포함된다. PCB는 Context Switching이 발생할 때, 현재 프로세스의 위와 같은 데이터들의 정보가 저장되고, 다음 프로세스의 PCB의 해당 정보들을 불러와 복원하여 실행을 이어 갈 수 있다.</p>
<p>또한 PCB는 중요한 정보를 가지고 있기 때문에 아무나 접근할 수 없도록 보호된 커널 메모리 영역에 존재하고 Linked List 형태로 Process Table을 통해 각 프로세스에 대한 PCB 데이터를 관리한다.</p>
<h3 id="멀티-프로세스-multi-process">멀티 프로세스 (Multi Process)</h3>
<p>멀티 프로세스는 각각 독립된 메모리 공간을 가진 여러 프로세스가 동시에 실행되는 방식입니다. 멀티 프로세스는 하나의 프로세스가 실패해도 다른 프로세스에 영향을 주지 않기 떄문에 안정성이 높다. 이는 각각의 프로세스가 서로 다른 메모리 공간을 가지기 때문이다. 하지만 이에 따라 메모리 사용량이 증가하게 되고 각각의 프로세스가 서로 통신을 하게 된다면 IPC 비용이 발생한다.</p>
<blockquote>
<p><strong>멀티 프로세스를 사용하는 예시</strong></p>
</blockquote>
<ul>
<li>Nginx 웹 서버의 경우 웹 서버에 대한  멀티 프로세스 + 싱글 스레드를 통해 웹 서버에 대한 요청을 처리한다.</li>
<li>크롬 브라우저나 웨일 브라우저의 경우 각각의 탭을 프로세스로 관리하기 때문에 어떤 탭에서 문제가 발생하더라도 다른 탭이나 브라우저 자체에 영향을 주지 않는다. </li>
</ul>
<h3 id="ipc-inter-process-communication">IPC (Inter Process Communication)</h3>
<p>서로 다른 프로세스가 협력을 하려면 서로 데이터를 주고 받을 수 있어야 한다. 하지만
프로세스들은 각각 독립된 메모리 영역을 가지고 있기 때문에 서로에 메모리 영역에 접근할 수 없다. 이를 해소하기 위해 IPC란 기술이 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/151fa5e2-be5a-4c77-ba5e-02d824ec1f46/image.png" alt=""></p>
<p><strong>IPC 모델</strong>에는 크게 다음 2가지로 구분된다.</p>
<ul>
<li><strong>공유 메모리</strong>
협력하고자 하는 프로세스 간의 공유하는 메모리 영역을 만들어 해당 영역에 데이터를 Read/Write하여 공유하는 방식입니다. 동일한 영역의 데이터를 읽고 쓰기 때문에 동기화 문제에 대해 주의하여야 합니다. </li>
<li><strong>Message Passing</strong>
공유 메모리 방식과 같이 동일한 메모리 공간을 공유하지 않고도 프로세스 간 통신을 할 수 있는 모델입니다. 운영체제에서 프로세스 간의 통신 방법을 제공하고, 대신 전달해줍니다. <strong>Message Passing</strong>에 해당하는 방법에는 <code>소켓 (Socket)</code>, <code>파이프(Pipe)</code>, <code>메시지큐(Message Queue)</code> 등등이 있다.</li>
</ul>
<h3 id="fork">fork()</h3>
<p>운영체제에서 자식 프로세스를 생성하는데, <code>fork()</code> 시스템 콜을 통해 자식 프로세스를 생성합니다. 해당 시스템 콜이 발생하면 커널모드로 변경되고 이후 커널에서는 자식 프로세스에 대한 PCB를 할당하고, 부모 프로세스의 PCB 데이터를 자식 프로세스의 PCB에 복사합니다. 그런 후 자식 프로세스는 <code>Ready Queue</code> 에 적재되고 다시 CPU는 부모 프로세스의 작업을 처리하게 됩니다.</p>
<p>다음 코드를 살펴보자.</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

int main(void) {
  int pid;
  printf(&quot;Start!\n&quot;);
  pid = fork();
  printf(&quot;Who are you?\n);

  if (pid == 0) {
    printf(&quot;Child Process\n&quot;);
  } else {
    printf(&quot;Parent Process\n&quot;);
  }
}</code></pre>
<p>해당 코드의 실행 결과를 예상해보면</p>
<pre><code>Start!
Who are you?
Child Process 또는 Parent Process</code></pre><p>라고 짐작할 수 있을 것이다. 하지만 실제 결과는 다음과 같다.</p>
<pre><code>Start!
Who are you?
Parent Process
who are you?
Child Process</code></pre><p>어떤 근거로 위와 같은 결과가 나오는 것일까? </p>
<p><code>fork()</code> 시스템 콜을 호출하면 자식 프로세스를 생성하기 위해 커널모드가 변경됩니다. 자식 프로세스의 PCB가 할당되고, fork()를 호출한 부모 프로세스의 PCB 데이터를 자식 프로세스 PCB에 복사하게 됩니다. 그런 후 다시 부모 프로세스로 돌아와서 남은 작업을 처리합니다.</p>
<pre><code>Start!
Who are you?
Parent Process</code></pre><p>여기까지 위와 같은 결과로 출력될 것 입니다. Parent Process까지 호출하게 되면 부모 프로세스는 종료가 됩니다. 시간이 지나 <code>Ready Queue</code>에 적재되어 있던 자식 프로세스에 CPU가 할당되면 자식 프로세스를 생성할 당시의 부모 프로세스의 PCB 데이터를 기반으로 작업을 처리합니다. 당시 부모 프로세스 PCB의 PC(Program Count) 주소가 다음 수행할 코드의 주소를 <code>printf(&quot;who are you?\n&quot;);</code> 로 가지고 있기 때문에 <code>printf(&quot;who are you?\n&quot;);</code> 코드부터 실행하게 됩니다. 그 후 코드의 진행은 부모 프로세스와 똑같습니다.</p>
<pre><code>Start!
Who are you?
Parent Process
who are you?
Child Process</code></pre><p>결과적으로 위와 같은 출력 결과가 나온다는 것을 이해할 수 있습니다. <code>fork()</code> 는 영단어의 의미처럼 부모 프로세스를 복제하여 자식 프로세스를 생성한다는 것을 알 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] 운영체제 1주차 스터디]]></title>
            <link>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EB%9E%80</link>
            <guid>https://velog.io/@jindaram-stu/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EB%9E%80</guid>
            <pubDate>Thu, 15 Aug 2024 16:53:17 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/3a0f6507-e668-477b-ac7f-8d74eb870b20/image.png" alt=""></p>
<h1 id="운영체제란">운영체제란?</h1>
<hr>
<p>흔히 <strong>OS (Operating System)</strong> 이라고 부르는 운영체제는 하드웨어 바로 위에서 실행되는 소프트웨어로써, 사용자 및 컴퓨터가 관리하는 모든 소프트웨어와 하드웨어를 관리하고 연결한다. 이는 우리가 흔히 아는 <code>Windows</code>, <code>Mac OS</code> 뿐만이 아니라, 모바일에 사용되는 <code>Android</code>, <code>iOS</code> 등도 운영체제에 포함됩니다. </p>
<p>운영체제의 핵심은 하드웨어와 소프트웨어 간의 상호작용을 관리하고 제어하는 것이다. 또한 컴퓨터 자원을 소프트웨어에 할당하여 어떠한 소프트웨어가 자원을 과점유하거나, 부족하게 사용하지 않게 적절하게 할당하고 자원을 관리한다.</p>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/90f9d899-9f28-4234-a1c1-533015c124eb/image.png" alt=""></p>
<h2 id="운영체제의-역할">운영체제의 역할</h2>
<p>좁은 의미의 운영체제는 <code>커널</code>을 의미한다. <code>커널</code>은 운영체제의 일부이지만, 그 역할과 중요성이 너무 크기 때문에, 좁은 의미의 운영체제 라고도 부른다. 
이 외에도 운영체제에는 각종 시스템을 위한 유틸리티들을 광범위하게 포함하여 넓은 의미의 운영체제라고 부른다. 보통 운영체제라고 하면 <code>커널</code>을 의미하는 경우가 많다 그만큼 커널이 운영체제의 요소 중 핵심이기 때문이다.</p>
<p>운영체제의 종류에 따라 사용자 인터페이스를 제공하기도 하며, 기본적으로 하드웨어 자원 관리 및 제어, 프로세스 및 스케줄링 관리 및 제어 등과 같은 전반적인 하드웨어와 소프트웨어 사이의 필요한 일련의 작업들을 처리합니다. 또한 어떤 프로그램에서도 메모리에 직접 접근할 수 없도록 보안 기능을 제공하고 이는 악성 코드가 다른 프로세스나 시스템 자원에 접근하는 것을 방지합니다. </p>
<p>그리고 시스템 자원에 대한 권한을 제어하여 허가된 자원에만 접근할 수 있도록 합니다. 이는 파일, 디렉토리, 장치 등에서 읽기, 쓰기, 실행 권한을 설정하여 불법적인 접근을 막습니다. </p>
<p>이 외에도 시스템 콜 인터페이스, 커널 모드와 유저 모드 구분과 같은 컴퓨터의 전반적인 관리에 대한 기능을 제공합니다.</p>
<h2 id="운영체제의-분류">운영체제의 분류</h2>
<hr>
<h3 id="1-시분할-시스템">1. 시분할 시스템</h3>
<p><strong>시분할(Time sharing) 시스템</strong>은 여러 작업을 수행할 때 컴퓨터 처리 능력을 일정한 시간 단위로 분할하여 사용하는 시스템이다. CPU 스케줄링과 다중 프로그래밍을 이용해서 각 사용자 들에게 컴퓨터 자원을 시간적으로 분할하여 사용할 수 있게 해준다. <code>일괄 처리 시스템</code>에 비해 짧은 응답 시간을 가지고 <code>Interactive</code> 한 방법이다. </p>
<blockquote>
<p><strong>일괄처리시스템</strong> 이란?
작업 요청의 일정량을 모아서 한꺼번에 처리하는 시스템이다. 일괄 처리 시스템은 작업이 완전히 종료될 때 까지 기다려야 하기 때문에 응답 속도가 늦다. 현대 운영체제에서 찾아보기 힘든 시스템이고</p>
</blockquote>
<h3 id="2-다중-프로그래밍-시스템">2. 다중 프로그래밍 시스템</h3>
<p><strong>다중 프로그래밍 시스템 (Multi Programming System)</strong>은 메모리에 사용자의 프로그램이 2개 이상 적재되어 있고, 이를 번갈아가며 실행하다, 어느 한 프로그램에서 입출력 발생하게 되면 해당 입출력이 처리되는 동안 다른 프로그램을 처리하는 방식이다. 입출력 이벤트를 처리하게 되면 CPU는 그 시간 동안 대기를 하게 되지만, 다중 프로그래밍 시스템을 통해 입출력 명령을 처리하면서 다른 프로그램을 수행할 수 있다.</p>
<h3 id="3-대화형-시스템">3. 대화형 시스템</h3>
<p><strong>대화형 시스템 (Interactive System)</strong>은 위에서 설명한 일괄처리 시스템은 응답 시간이 느리기 때문에 바로바로 응답을 확인할 수 없다. 하지만 대화형 시스템은 사용자와 컴퓨터가 대화하는 것처럼 응답을 거의 즉시 확인할 수 있도록 소통을 원활하게 하고 효율성을 높입니다. </p>
<h3 id="4-다중-처리기-시스템">4. 다중 처리기 시스템</h3>
<p><strong>다중 처리기 시스템(Multi Processor System)</strong> 은 여러 개의 CPU로 구성된 시스템이다. 
단일 처리기 시스템 (Single Processor System)에 비해 처리할 수 있는 CPU의 갯수가 많기 때문에 상대적으로 성능이 좋다. 
또한 단일 처리기 시스템은 하나의 CPU가 고장 나게 되면, 전체적인 시스템이 중지되지만, 다중 처리기 시스템은 여러 개의 CPU가 존재하기 때문에 하나의 CPU가 중지되더라도 처리 속도만 느려질 뿐 시스템이 중지되지는 않는다.</p>
<h1 id="커널">커널</h1>
<hr>
<p>운영체제의 핵심 요소로써, 하드웨어와 소프트웨어 간의 중재 역할을 수행하며 시스템 자원을 관리합니다. 우리가 흔히 운영체제라고 부르는 것은 대체로 이 커널을 의미합니다. </p>
<h2 id="커널의-역할">커널의 역할</h2>
<ul>
<li><strong>자원 관리</strong>
커널은 시스템의 물리적 메모리를 효율적으로 관리하고, 할당합니다. 또한 프로세스가 동시에 실행할 수 있도록 프로세스의 생성, 스케줄링과 종료에 대해 관리하고 하드웨어 장치 드라이버를 통해 입출력에 대한 작업을 처리합니다. </li>
<li><strong>시스템 콜</strong>
위에서 이야기 하였듯이 커널은 응용 프로그램에서의 시스템 콜을 처리합니다. 응용 프로그램이 하드웨어에 접근할 때, 시스템 콜을 호출하고, 그 시스템 콜은 커널이 처리하게 됩니다.</li>
<li><strong>하드웨어 추상화</strong>
커널은 하드웨어 자원을 추상화하여 응용 프로그램이 하드웨어의 세부사항에 의존하지 않게 합니다. 이를 통해 어떤 하드웨어를 지니고 있는 컴퓨터라도 운영체제 단계의 추상화에 의해 하드웨어에 따라 응용프로그램을 수정하거나, 어느 플랫폼에서도 동일하게 동작하게 합니다.</li>
</ul>
<p>이 외에도 보안 및 접근제어, 네트워크, 인터럽트 처리 등 하드웨어와 응용 소프트웨어 사이에서 전반적인 관리와 제어를 담당합니다. 커널의 안정성과 효용성은 시스템의 전반적인 운영체제의 성능에 큰 영향을 끼칩니다.</p>
<h2 id="운영체제의-실행모드">운영체제의 실행모드</h2>
<p>운영체제의 실행모드에는 총 2가지가 있다. </p>
<ul>
<li>커널 모드</li>
<li>유저 모드<h3 id="커널-모드">커널 모드</h3>
</li>
<li><em>커널 모드는 운영체제의 커널이 실행되는 모드입이다.*</em> 커널 모드에서는 전체 하드웨어 자원과 시스템 메모리 전체에 직접적인 접근이 가능합니다. 커널 모드에서 실행되는 코드는 시스템 전체에 대한 완전한 제어 권한을 가지기 때문에 프로세스 스케줄링, 메모리 관리, 시스템 콜, 입출력 작업 등 시스템 자원에 대한 기본적인 작업을 수행할 때 사용하는 모드입니다.</li>
</ul>
<h3 id="유저-모드">유저 모드</h3>
<p><strong>유저 모드는 응용 프로그램이 실행될 때 사용되는 모드입니다.</strong> 제한된 접근 권한을 가지기 때문에 커널 모드 처럼 자유롭게 하드웨어에 접근하거나, 시스템 메모리에 접근할 수 없습니다. 이는 애플리케이션의 시스템 공간만큼 접근할 수 있습니다.
또한 에러가 발생했을 때, 시스템 전체에 영향을 최소화합니다. 애플리케이션에서 발생하는 오류는 애플리케이션에만 영향을 미치기 때문에 시스템 전체의 안정성을 보장합니다. 만약 하드웨어에 대한 접근이 필요하면 시스템 콜을 통해 커널 모드로 전환하여 필요한 작업을 처리하고 그 결과를 유저 모드로 반환받습니다.</p>
<h2 id="시스템-콜-system-call">시스템 콜 (System Call)</h2>
<p><strong>응용 프로그램들은 직접 하드웨어에 접근할 수 없고, 운영체제를 통해 하드웨어에 접근할 수 있는데, 이렇게 하드웨어를 다룰 수 있도록 하는 것이 인터페이스이다</strong>. 이는 응용 프로그램이 직접 하드웨어 자원에 직접 접근하거나 커널의 중요한 자원에 접근하는 것을 방지하기 위해 모든 하드웨어의 자원에 대한 접근은 운영체제를 통해서만 가능합니다. </p>
<pre><code class="language-java">public main void(String[] args) {
    File file = new File(&quot;/example.txt&quot;);
    try (FileWriter writer = new FileWriter(file)) {
        writer.write(&quot;Hello, World!&quot;);
    } ...
}</code></pre>
<p>이런식으로 파일에 접근하여 내용을 수정하거나, 새로운 파일을 생성하고자 할 때, Java에서 추상화 한 시스템 콜을 호출하여 커널에서 해당 명령을 처리합니다.</p>
<h2 id="polling">Polling</h2>
<p><strong>폴링 (Polling)은 CPU가 주기적으로 특정 장치의 상태를 확인하여 해당 장치가 데이터나 처리를 요구하는지에 대한 여부를 점검하는 방식입니다.</strong> 구현이 간단하기 때문에 복잡하지 않다는 것이 장점이지만, CPU의 자원을 계속해서 사용해서 상태를 확인하고, 폴링하는 주기에 따라 지연이 발생할 수 있다는 것을 단점으로 꼽을 수 있습니다.</p>
<h2 id="interrupt">Interrupt</h2>
<p><strong>인터럽트 (Interrupt)는 하드웨어나 소프트웨어가 즉각적으로 CPU의 주의를 끌어야 할 필요가 있을 때 사용됩니다.</strong> 즉 CPU가 현재 작업을 멈추고 더 중요한 작업을 처리할 수 있도록 하는 신호입니다.
인터럽트가 발생하면 CPU는 현재 실행 중인 작업을 멈추고 인터럽트의 요청을 처리합니다. CPU는 인터럽트가 발생하면 해당 인터럽트에 맞는 인터럽트 핸들러를 호출합니다. 그리고 해당 인터럽트가 끝나면 CPU는 중단되있던 작업을 재개합니다.</p>
<blockquote>
<p>예를 들어 사용자가 키보드를 입력했을 때, CPU는 현재 작업을 일시 중지하고 키보드 입력을 처리하는 인터럽트 핸들러를 실행합니다. 이 핸들러는 사용자가 입력한 문자를 읽고, 화면에 표시하거나 내부적으로 처리하게 됩니다.</p>
</blockquote>
<h2 id="dma">DMA</h2>
<p>DMA는 Direct Memory Access의 약자로 CPU의 개입 없이 메모리와 입출력 장치 간의 데이터 전송을 직접 처리하는 기술입니다. DMA는 데이터 전송의 효율을 높이고 CPU의 부담을 줄여주는 중요한 역할을 합니다. 
이는 CPU가 많은 일을 하기 때문에 생겨났는데, 예를 들어 
DMA를 통해 작업이 끝났다면 DMA는 CPU에게 인터럽트를 발생시켜 작업을 끝났음을 알리게 됩니다.</p>
<blockquote>
<p><strong>DMA가 필요한 이유</strong>
하드 디스크에서 파일을 읽어 메모리에 적재하려고 하면 메모리에는 CPU만이 접근할 수 있기 때문에 CPU에 인터럽트가 발생하게 되고, 하드 디스크의 데이터를 메모리에 적재하는 일도 반드시 CPU를 통해야만 한다.
처리량이 적당한 CPU라면 괜찮겠지만, 그렇지 않다면 해당 작업도 부담을 주기 때문에 CPU가 온전히 본인의 기능의 집중할 수 있도록 DMA가 생기게 되었다.</p>
</blockquote>
<h2 id="동기식-io와-비동기식-io">동기식 I/O와 비동기식 I/O</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/122cc206-d7be-400a-bc9f-af3c75a28d0d/image.png" alt=""></p>
<h3 id="동기식-io">동기식 I/O</h3>
<p><strong>동기식 I/O는 작업이 완료할 때 까지 다른 작업을 처리하지 않고, 기다리는 방식을 말합니다.</strong> 흐름이 직관적이고 구현이 쉽지만 I/O 작업이 완료할 때 까지 대기하므로 CPU를 비효율적으로 사용하게 됩니다. 그렇기 때문에 I/O 작업이 지연되면 시스템 전체의 성능에 영향을 줄 수 있습니다. </p>
<h3 id="비동기식-io">비동기식 I/O</h3>
<p><strong>비동기식 I/O는 동기식 I/O와 반대로 작업이 완료되기를 기다리지 않고 CPU가 다른 작업을 계속하는 방식입니다</strong>. I/O 작업이 완료되면 콜백이나, 완료 알림을 통해 결과를 처리합니다. 동기식 I/O에 비해 CPU가 놀지 않아 좀 더 효율적으로 사용할 수 있으며 전체 시스템 성능에 미치는 영향을 줄일 수 있습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[넘블] 회고 (웹소설 클론코딩 챌린지)]]></title>
            <link>https://velog.io/@jindaram-stu/%EB%84%98%EB%B8%94-%ED%9A%8C%EA%B3%A0-%EC%9B%B9%EC%86%8C%EC%84%A4-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9-%EC%B1%8C%EB%A6%B0%EC%A7%80</link>
            <guid>https://velog.io/@jindaram-stu/%EB%84%98%EB%B8%94-%ED%9A%8C%EA%B3%A0-%EC%9B%B9%EC%86%8C%EC%84%A4-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9-%EC%B1%8C%EB%A6%B0%EC%A7%80</guid>
            <pubDate>Sun, 16 Apr 2023 08:18:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/448746c3-b6a2-4496-8e6e-979f839993c3/image.png" alt=""></p>
<h1 id="넘블이란">넘블이란?</h1>
<p>넘블은 쉽게 말해서 코딩 챌린지 플랫폼이다. 호스트 주관으로 챌린지가 하나씩 개설되는데, 예를 들어 웹소설 클론코딩 챌린지가 열리면, 유명 회사에서 일하는 개발자분들의 코드 리뷰를 받을 수 있는 기회가 주어진다. 단 결과물이 상위에 들어야 받을 수 있다. 하지만 상위 결과물이 받은 코드 리뷰를 같이 볼 수 있기 때문에 도움이 되는 건 마찬가지이다.</p>
<h2 id="내가-도전한-넘블-챌린지">내가 도전한 넘블 챌린지</h2>
<p>내가 도전한 챌린지는 네이버, 카카오, 문피아 같은 웹 소설 플랫폼을 백엔드 기반으로 클론코딩 해보는 것이었다. 아무 주제 없이 클론코딩을 해보는 것도 도움이 되겠지만, 특별히 호스트께서 여러가지 집중해볼만한 주제를 던져주셨다. 
<img src="https://velog.velcdn.com/images/jindaram-stu/post/1b725e31-9824-4f84-aec3-bde1c92e840c/image.png" alt="">
특히나 대용량 트래픽 환경에서 생길 수 있는 장애에 대해 집중할 수 있는 시간이었다. 이 외에 주어진 문제를 풀 수 있을만한 고민을 하는 시간도 갖게 되었다.</p>
<h2 id="정책">정책</h2>
<p>큰 틀만 제공되고, 실제로 세세한 정책에 대해서는 스스로 정해봐야만 했다. </p>
<ul>
<li>소설 등록 시 20,000자 이내이어야 합니다.<ul>
<li>많은 이용자의 무분별한 긴 양의 소설 등록을 제한하도록 20,000자로 제한</li>
<li>추후 작품에 대한 인기가 많아진다면, 해당 소설의 작자에 한하여 글자 수 제한 해제.</li>
</ul>
</li>
<li>랭킹은 전체 인기순, 무료 인기순, 유료 인기순으로 나열하고, 각 인기에 대한 기준은 조회수로 적용한다.</li>
<li>무료 소설은 비로그인, 로그인 사용자 모두 열람할 수 있다.</li>
<li>유료 소설은 로그인 사용자 중 결제 이력이 있는 사용자만 열람할 수 있다.</li>
</ul>
<h2 id="요구사항-및-문서">요구사항 및 문서</h2>
<ul>
<li><p>ERD 
<img src="https://velog.velcdn.com/images/jindaram-stu/post/fa0b46d1-179e-44c9-989a-c8b5e61f105c/image.png" alt=""></p>
</li>
<li><p>[요구사항 및 구현한 기능]
(<a href="https://www.notion.so/jinsb/ec78c7b166cc4bcea1453ef009630d9f">https://www.notion.so/jinsb/ec78c7b166cc4bcea1453ef009630d9f</a>)</p>
</li>
</ul>
<h2 id="내가-고민했던-문제들">내가 고민했던 문제들</h2>
<p>글이 길어 링크로 첨부합니다.</p>
<ol>
<li><p><a href="https://jinsb.notion.site/2ff1b00a58b340458c693e98e5c9e9f7">동시 요청이 여러개 들어왔을 때 하나만 처리하기</a></p>
</li>
<li><p><a href="https://jinsb.notion.site/a40bd22c30b94970b8355eb0b2009263">랭킹 캐시 성능 개선기</a></p>
</li>
<li><p>[소설 내용은 어떻게 관리해야 할까?]
(<a href="https://jinsb.notion.site/21aa346d88334751a1724d6ac8c805fb">https://jinsb.notion.site/21aa346d88334751a1724d6ac8c805fb</a>)</p>
</li>
<li><p>[조회수는 매번 올려야 할까?]
(<a href="https://jinsb.notion.site/228cdcfbe9824aab96f1ac42370e3dba">https://jinsb.notion.site/228cdcfbe9824aab96f1ac42370e3dba</a>)</p>
</li>
<li><p>[선호작 기능을 효율적으로 구현하는 방법]
(<a href="https://jinsb.notion.site/78a2aef8230c42448831353ba5e5be4d">https://jinsb.notion.site/78a2aef8230c42448831353ba5e5be4d</a>)</p>
</li>
</ol>
<h2 id="아쉬웠던-점">아쉬웠던 점</h2>
<ol>
<li>Replication을 적용해보지 못했다.</li>
<li>성능 테스트 툴을 다양하게 사용해보지 못하고 테스를 깊게 해보지 못했다.</li>
<li>테스트 코드를 작성하지 못했다.</li>
<li>고민했던 문제들에 대해 어느정도 해결했지만, 궁극적인 해결 방식은 아니었다. 분산락을 통해 동시성 처리를 했지만, 추후엔 Message Broker를 통해 구현하는 방식도 해볼 것이다.</li>
<li>예외 처리 및 응답 메시지 부족</li>
<li>제출 문서 준비 부족</li>
</ol>
<h2 id="후기">후기</h2>
<p>먼저 새로운 접근을 할 수 있었던 점이 너무 좋았다. 처음에는 쉽게 프로젝트를 완성 시킬 줄 알았지만, 결과물을 보니 공부했던 부분들과 부족했던 부분들이 많았다. 테이블 설계부터 동시성 처리, 비즈니스 정책 확립 등 미흡한 부분이 많았다. 퇴근 후 학교까지 다니기 때문에 해당 프로젝트에 할애할 수 있는 시간이 그리 많지 않았다. 그래도 백엔드 개발을 하면서 성능이란 것에 대해 깊게 고민하고, 조금이라도 해결하는 유익한 시간이었던 것 같다. 결론적으로 내가 앞으로 무엇을 더 공부해야 하는지를 알 수 있어서 좋았다. 결과를 떠나서 다른 분들은 내가 고민한 부분을 어떻게 구현했는지를 통해 더 넓은 지식을 알아갈 수 있는게 좋은 것 같다. 실제로 대용량 트래픽 환경에서 개발을 한다면 신경써야할 부분이 한 두가지가 아닌 것 같았다. 이렇게 작은 프로젝트를 만드는데도 여러가지 상황을 고려하면서 개발했어야 하기 때문에 실제 업무라면 내가 지나친 작은 기능도 회사에 크리티컬한 피해를 입힐 수 있다는 점이 조금 무서운 것 같다. 
앞으로도 꾸준히 공부하면서 많은 사람들이 이용해도 안정적인 서비스를 개발할 수 있는 개발자로 성장하고 싶다. 더 나아가 앞으로도 더 많은 넘블 챌린지에 참여하면서 끊기지 않는 토이프로젝트를 하고싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스5기] 🌉 다리 건너기 게임]]></title>
            <link>https://velog.io/@jindaram-stu/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A45%EA%B8%B0-%EB%8B%A4%EB%A6%AC-%EA%B1%B4%EB%84%88%EA%B8%B0-%EA%B2%8C%EC%9E%84</link>
            <guid>https://velog.io/@jindaram-stu/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A45%EA%B8%B0-%EB%8B%A4%EB%A6%AC-%EA%B1%B4%EB%84%88%EA%B8%B0-%EA%B2%8C%EC%9E%84</guid>
            <pubDate>Tue, 22 Nov 2022 15:25:26 GMT</pubDate>
            <description><![CDATA[<p>드디어 4주차까지 마무리를 했다. 시작할 때 4주가 길게 느껴졌지만, 막상 지나고 보니 정말 짧게 느껴졌다. 정말 어떻게 시간이 흘러갔는지 모르겠다. </p>
<p>이번 주차에는 조금 엄격한(?) 요구사항이 추가되었다. 메소드도 10라인 이하로 제한하고 우테코에서 지정한 클래스를 활용하여 개발을 진행했어야 했었다. 개인적으로 메소드의 라인 제한보다 우테코에서 지정한 클래스를 활용해서 개발하는 부분이 더 힘들었던 것 같다.</p>
<h3 id="🚩-dependency-injection">🚩 Dependency Injection</h3>
<p>먼저 의존성 주입에 대해서 이야기 하고 싶다. 간단하게 개발하려면 static을 활용해서 모든 로직을 실행할 수 있겠지만, 이번에 주어진 클래스들을 보면 수정제한이 걸린 부분에 static을 걸 수가 없는 부분이 있다. 3주차 숫자야구 미션에서도 DI를 활용해서 의존성을 주입해주었지만, 이번 주차 때의 우테코에서 제공한 클래스를 보며 DI를 적용하면 되겠구나 라는 생각이 들었다.</p>
<pre><code class="language-java">
public class BridgeGameContainer {

    private static BridgeGameContainer bridgeGameContainer;

    private BridgeGameContainer() {
    }

    public static BridgeGameContainer getInstance() {
        if (bridgeGameContainer == null) {
            bridgeGameContainer = new BridgeGameContainer();
        }
        return bridgeGameContainer;
    }

    public BridgeGameSystem bridgeGameSystem() {
        return new BridgeGameSystem(bridgeMaker(), inputView(), outputView());
    }

    private BridgeMaker bridgeMaker() {
        return new BridgeMaker(bridgeNumberGenerator());
    }

    private BridgeNumberGenerator bridgeNumberGenerator() {
        return new BridgeRandomNumberGenerator();
    }

    private InputView inputView() {
        return new InputView();
    }

    private OutputView outputView() {
        return new OutputView();
    }
}</code></pre>
<p>의존성 주입을 위해 먼저 <code>Container</code> 객체를 만들었다. <code>Container</code> 객체는 싱글톤으로 동작하며 <code>Application.java</code> 에서 <code>BridgeGameSystem</code>의 의존성을 주입하여 실행한다.</p>
<pre><code class="language-java">public class Application {

    public static void main(String[] args) {
        BridgeGameContainer bridgeGameContainer = BridgeGameContainer.getInstance();
        try {
            BridgeGameSystem bridgeGameSystem = bridgeGameContainer.bridgeGameSystem();
            bridgeGameSystem.play();
        } catch (IllegalArgumentException e) {
            System.out.println(ExceptionMessage.PREFIX + e.getMessage());
        }

    }
}</code></pre>
<p>위와 같이 <code>bridgeGameContainer.bridgeGameSystem()</code> 메소드를 통해서 의존성을 주입받는다. 이를 활용해서 static 없이 주어진 클래스의 요구사항을 만족할 수 있었다!</p>
<h3 id="🛠-refactoring">🛠 Refactoring</h3>
<p>메소드 10줄 제한 때문에 길이 길이 긴 메소드를 작성할 수 없었다. 다리 건너기 게임의 흐름을 담당하는 <code>BridgeGameSystem</code> 클래스는 게임의 로직을 순차적으로 수행하기 위해서 하드코딩 한다면 10줄 기본으로 넘어갈 거 같아 작게 메소드 단위로 구분했다.</p>
<pre><code class="language-java">    public void play() {
        Moving moving;
        GameStatus gameStatus;
        do {
            moving = move();
            gameStatus = getGameStatus(moving);
        } while (gameStatus.equals(GameStatus.NEXT) || gameStatus.equals(GameStatus.RETRY));
        outputView.printResult(bridgeGame);
    }

    private Moving move() {
        Moving moving = bridgeGame.move(inputView.readMoving());
        outputView.printMap(moving);
        return moving;
    }

    private GameStatus getGameStatus(Moving moving) {
        if (bridgeGame.getResult() == Result.SUCCESS) {
            return GameStatus.SUCCESS;
        }
        if (moving.getResult() == Result.FAIL) {
            GameCommand gameCommand = inputView.readGameCommand();
            return bridgeGame.getGameStatusByGameCommand(gameCommand);
        }
        return GameStatus.NEXT;
    }</code></pre>
<p>가장 큰 틀은 <code>play()</code> 메소드에서 진행이 되고, <code>move()</code>는 다리는 건너는 행위 그리고 <code>getGameStatus()</code>는 다리는 건너는 행위에 의한 결과를 처리하는 메소드이다. 만약 <code>getGameStatus()</code>가 <code>SUCCESS</code>(다리 한칸을 건너는 것을 성공), <code>RETRY</code>(실패했지만 재시도)를 반환한다면 반복은 종료되고 결과를 반환하게 된다. 이렇게 3가지 메소드로 분리해서 메인 로직을 작성했다. 뭔가 쉽게 설명하자면 다음과 같이 분리를 한 것과 마찬가지이다.</p>
<pre><code class="language-text">플레이 -&gt; [ 다리 건너기, 결과에 따른 로직 수행하기, 결과 출력 ]</code></pre>
<p>이런식으로 잘게 쪼개서 해결하면 모든 메소드를 10줄 라인 아래로 작성하며 코드를 작성할 수 있다. </p>
<h3 id="아쉽거나-부족했던-점">아쉽거나 부족했던 점</h3>
<p>이번 미션을 진행하면서 조금 부족했다고 느꼈던 부분들이 있다. 가장 먼저 고민이 들었던 것은 어느정도의 퀄리티가 완성되어야 커밋을 해야하나 라는 점이었다. 클린코드고 뭐고 일단 기능이 굴러가기만 하면 그대로 커밋을 진행해도 되는건지 아니면 어느정도 리팩토링을 하고 커밋을 진행해야 하는지 경험이 부족해서 감이 잘 잡히지 않았다. 커밋 내역에 깔끔한 코드만 남기고 싶은 마음은 아직 초보이기에 그런 것일까 고민에 휩싸였다. 하지만 우테코에서 의도한 바는 처음부터 깔끔한 코드를 작성하는 것이 아닌 것 같았다. 이번 주차의 주요 내용은 <code>클래스의 분리</code>와 <code>리팩토링</code>이었다. 그런 점을 미루어 보아 처음부터 깔끔한 코드를 작성하는 것은 힘드니까 점차 코드를 수정해가며 좋은 코드로 바꾸어보아라 같은 의미를 내포하는 것 같다. 하지만 갈피를 잡지 못해 결국 점차 리팩토링 되었던 나의 코드는 그저 한 개의 커밋으로 퉁 쳐버렸다. 그런 뒤에 커밋 내역을 보니 내가 리팩토링을 하며 개선해나갔던 부분, 고민했던 부분들이 보이지 않았다. 우테코에서는 그런 부분을 커밋 내역에 한번 포함시켜보라는 의미였을지도 모르는데 말이다. 잘 진행해왔던 1~3주차 였다고 생각하지만 가장 마음에 들지 않게 4주차를 마무리했다. </p>
<p>더 나아가 클래스 및 메소드에 책임과 역할에 관한 내용이다. 많은 글들과 클린코드를 보면 메소드와 클래스는 한 가지의 일만 해야한다는 이야기들이 많이 있다. 이는 굉장히 추상적인 내용이지만 핵심은 많은 일을 하면 안된다는 것이다.
하지만 코드를 작성하다보면 자연스레 하나의 메소드와 클래스에 많은 책임과 역할이 담기게 된다. 매우 자연스럽게 말이다. 그래서 헷갈리는 부분이 있었다. 사용자의 값을 입력받고, 이를 검증하고 변환하는 부분을 만들려고 할 때, 입력을 받는 로직을 그대로 작성하고 검증과 변환의 로직을 메소드로 분리해서 입력을 받는 메소드 안에 해당 메소드를 호출해도 
입력을 받는 메소드는 많은 책임을 지고 있는 건지, 아니면 분리 했기 때문에 책임이 덜 해지는 건지에 대해 의문이 생겼다. 이에 대해서는 Discussion에 질문을 남겨볼 생각이다.</p>
<h3 id="😀-소감">😀 소감</h3>
<p>이번 시간에는 클래스 분리에 대해 집중적으로 생각하며 개발했습니다. 또한 메소드 제한이 10줄이기 때문에 정말 많이 분리하려고 노력했습니다. indent에 관한 제한은 2 이지만 자체적으로 1이라는 제한을 걸고 개발에 임했습니다.
게임 로직 자체는 많이 어렵지 않았지만, 늘어난 요구사항을 모두 충족하며 개발하려다보니 처음엔 힘든 부분이 있었습니다.
하지만 메일로 받은 내용처럼 처음엔 조금 길에 늘여뜨려 작성하다가 점점 리팩토링을 통해 개발하다보니, 꽤나 간단하게 분리할 수 있는 부분도 있었습니다.</p>
<p>매주 미션을 하며 느끼는 부분이지만, 정말 좋은 클린코드를 작성하려면 무엇보다도 코드의 구조가 중요하다고 생각했습니다. 아무리 좋은 코드를 짜려고 해도 구조가 보수적이고 서로 많이 얽혀있다면 그 코드의 깨끗함은 한계가 존재한다고 생각이 들었습니다. 4주 동안 애플리케이션의 로직보다 눈에 보이는 코드의 구조를 신경 쓰는데 많이 집중하고 주목하게 된 시간이었습니다. 언뜻보면 정말 짧은 시간이었지만, 매주 매주 미션을 수행한 뒤 친구들과 함께했던 프로젝트의 코드나 토이 프로젝트로 진행한 코드를 보면 참 많이 부족했구나 라는 생각이 들었습니다.</p>
<p>늘 개발을 하면서 깔끔하게 작성하고 싶다. 한 눈에 들어오게 잘 정리된 코드를 작성하고 싶다는 생각을 하며 살았는데, 이를 연습하기는 쉽지 않았습니다. 하지만 우아한 테크코스 프리코스를 참여하면서 코치님이 내주신 문제에 요구사항을 잘 생각하고 실제 프로그램 개발에 적용하면서 그 중요성과 저만의 작은 노하우를 터득하게 되었습니다. 아직 한참 부족할지는 모르겠지만 앞으로 클린코드를 작성하는데에 기초 체력 정도는 얻었다고는 생각하고 있습니다.</p>
<p>Tecoble 및 우테코 유튜브도 정말 많은 도움이 되었습니다. 제가 고민하고 있던 부분들을 선배 개발자 분들께서 잘 풀어주시고 이해가 잘 되도록 예제와 함께 설명을 해주시니 정말 감사할 따름이었습니다. 아직은 제가 작성한 프로그램이 남들이 보기에도 잘 짜여지고 깨끗하고 깔끔한지는 모르겠습니다. 하지만 4주전과 지금의 저를 비교해봤을 때 정말 많이 발전한 느낌을 받습니다. 이를 계기로 공부 방향에 대해서도 많은 고민을 하게 되었고, 개발자로서의 덩치만 키우는 것이 아닌 실력을 빈틈을 계속해서 메워나가 탄탄한 개발자가 될 수 있는 초석을 마련한 것 같습니다.</p>
<p>4주 동안 많은 인원의 학생들을 관리하시느라 정말 고생 많으셨고, 결과에 상관없이 정말 좋은 값진 시간이었습니다. 앞으로도 우테코를 통해 많은 학생들이 저와 같은 동기부여와 노하우를 터득하는 시간을 가졌으면 좋겠습니다. 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스5기] ⚾ 숫자야구 게임]]></title>
            <link>https://velog.io/@jindaram-stu/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A45%EA%B8%B0-%EC%88%AB%EC%9E%90%EC%95%BC%EA%B5%AC-%EA%B2%8C%EC%9E%84</link>
            <guid>https://velog.io/@jindaram-stu/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A45%EA%B8%B0-%EC%88%AB%EC%9E%90%EC%95%BC%EA%B5%AC-%EA%B2%8C%EC%9E%84</guid>
            <pubDate>Wed, 09 Nov 2022 05:18:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/73011a98-34bd-44cb-af05-ae34fdd290cb/image.png" alt=""></p>
<h2 id="개요"><strong>개요</strong></h2>
<p>2주차 미션은 숫자야구 게임을 여러 요구사항에 맞춰서 개발하는 것이었다. 확실히 1주차 미션 때보다 더욱 더 많은 조건들이 붙어있었다. 주의깊게 본 부분은 다음과 같다.</p>
<h2 id="추가된-요구사항-및-주의사항">추가된 요구사항 및 주의사항</h2>
<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/a82a2f36-ee37-4ddc-ab41-20bbe58e3e77/image.png" alt=""></p>
<p>가장 눈에 띄는 요구사항은 인덴트와 한 가지 일만 하는 메소드 그리고 테스트 코드였다. 1주차 미션 때, 인덴트가 많은 코드를 작성했던 기억이 있다. 이걸 어떻게 줄여야 하지 하는 고민이 있었는데, 이번 주차는 기능 목록을 잘 정의하여 메소드 분리를 잘 해야겠다고 다짐했다. 그리고 알곤 있었지만, 자세하게는 모르던 JUnit과 AssertJ를 활용한 테스트 코드 작성에도 심혈를 기울였다. 추가적으로 자바 코드 컨벤션에 대해서 지켜야 했다.</p>
<h2 id="기능-목록">기능 목록</h2>
<h4 id="📌-컴퓨터상대방-숫자-생성">📌 컴퓨터(상대방) 숫자 생성</h4>
<ul>
<li>컴퓨터의 숫자 3자리를 생성합니다.</li>
<li>숫자가 중복되지 않도록 검증합니다.<h4 id="📌유저사용자-숫자-생성">📌유저(사용자) 숫자 생성</h4>
</li>
<li>사용자로부터 숫자를 입력받습니다.</li>
<li>[3자리, 숫자, 중복되지않음] 조건을 만족하는 지 검증합니다.</li>
<li>만족하지 않는다면 IllegalArgumentException 발생시킵니다.<h4 id="📌-count스트라이크볼-결과-관리">📌 Count(스트라이크/볼) 결과 관리</h4>
</li>
<li>컴퓨터 숫자와 유저 숫자를 바탕으로 스트라이크와 볼을 판별하고 관리합니다.</li>
<li>판별 결과를 바탕으로 힌트(Hint) 문자열을 생성합니다.<h4 id="📌-inning-관리-기능">📌 Inning 관리 기능</h4>
</li>
<li>야구에서 사용하는 용어 Inning(이닝) 을 의미하며, 한 번의 숫자 야구 게임을 관리합니다.</li>
<li>입/출력 기능, 컴퓨터와 유저 숫자 생성 및 판별 결과 관리를 포함합니다.<h4 id="📌-game-관리-기능">📌 Game 관리 기능</h4>
</li>
<li>게임 자체를 관리합니다.</li>
<li>사용자가 컴퓨터의 숫자를 맞춘다면 게임 재시작 여부를 물어 재시작하거나 종료합니다.<h4 id="📌-입출력-기능">📌 입/출력 기능</h4>
</li>
<li>사용자에게 게임 진행에 도움을 주는 문자열을 출력을 담당합니다.</li>
<li>사용자로부터 게임에 사용될 숫자나, 게임 재시작 여부를 입력받습니다.</li>
</ul>
<p>기능은 우선 이 정도로 정의했다. 그리고 이에 맞춰서 클래스들을 작성했다.</p>
<h2 id="indent-줄이기">Indent 줄이기</h2>
<p><code>indent</code>를 줄이기 위해선 메소드를 분리하는 것이 중요하다. 그리고 메소드 분리를 용이하게 잘 하려면 로직의 구조를 잘 짜는 것 역시 중요하다. 숫자 야구 게임을 개발하다보면, 많은 이들이 반복문 두 개를 중첩시켜서 개발하였을 것이다. 이를 분리하여 개발하지 않는다면 많은 <code>indent</code>가 생성될 것이다.</p>
<p>이번 주차에서는 <code>indent</code>를 줄이기 위해서 메소드와 클래스를 잘 분리하여 작성했다고 생각했지만, 피어 리뷰에서 간과한 부분이 있었다는 것을 깨달았다.</p>
<pre><code class="language-JAVA">while (true) {
            List&lt;Integer&gt; computerNumber = ComputerNumber.generate();
            InningManager.play(computerNumber);
            if (!isRegame(BaseballGameInput.getRegameStatus())) {
                break;
            }
        }</code></pre>
<p>해당 코드를 살펴보면 if문이 들어가 있어 <code>indent</code>가 2인 것을 확인할 수 있다. 이 if문은 무한 반복문을 종료시키는 코드인데, 아래와 같이 바꾸면 <code>indent</code>를 줄일 수 있다는 피드백을 받았다.</p>
<pre><code class="language-java">while (isRegame(BaseballGameInput.getRegameStatus()) {
            List&lt;Integer&gt; computerNumber = ComputerNumber.generate();
            InningManager.play(computerNumber);
        }</code></pre>
<p>하지만 이렇게 바꾸게 되면 게임을 최초 시작했을 때, 게임 재시작 여부를 물어보기 때문에 로직을 조금 수정해야 했다. 그래도 <code>indent</code>를 줄여 가독성을 높일 수 있어 충분히 시도해볼만한 방법이었다.</p>
<h2 id="테스트-코드">테스트 코드</h2>
<p>처음에는 로직을 중심으로 프로그램을 작성했지만, 테스트를 해보려고 할 때 막상 테스트 하기 용이하지 않은 부분이 있었다. 예를 들어 <code>UserNumber</code>는 유저의 숫자를 생성하고 검증한다. 생성할 때에는 사용자의 입력을 받는데, 테스트 시에 입력을 받기보단 값을 넣어 케이스 별로 검증을 하고 싶었다. 다음 코드를 같이 살펴보자.</p>
<pre><code class="language-java">public class UserNumber {
    public static List&lt;Integer&gt; generate() {
        String userNumber = Console.readLine();
        // 검증 및 리스트 변환 로직
    }
}</code></pre>
<p>여기서 generate() 메소드를 테스트 하고 싶을 때, 테스트 코드를 구현하기가 만만치 않다. 나는 다음과 같이 코드를 테스트 해보고싶었다.</p>
<pre><code class="language-java">@Test
void 세자리가_아닌_숫자_입력() {
    String userNumber = &quot;1234&quot;;

    assertThrownBy(() -&gt; UserNumber.generate(userNumber))
            .isInstanceOf(IllegalArgumentException.class);
}</code></pre>
<p>이런식으로 테스트 코드를 작성하려면 메소드의 구조를 바꾸어야 했다.</p>
<pre><code class="language-java">public class UserNumber {
    public static List&lt;Integer&gt; generate(String userNumber) {
        // 검증 및 리스트 변환 로직
    }
}</code></pre>
<p>이런식으로 구성한 후 호출하는 부분에서 <code>UserNumber.generate(Console.readLine())</code> 이렇게 호출하게 되면 위에 작성한 테스트 코드로 테스트를 진행할 수 있고, 비즈니스 로직도 정상적으로 수행된다. 이 외에도 테스트가 용이하게끔 클래스 및 메소드 구조를 설계하여 작성하려고 노력했다.</p>
<h2 id="소감">소감</h2>
<p><code>Discussion</code>에 공유된 다른 분들의 코드를 보며 참 많이 배웠다. 개발은 공부보단 직접 해봐야 는다고 하지만 남의 코드를 보는 것도 많이 늘게 되는 것 같다. 이번 주차에서는 로직보단 코드의 가독성 및 테스트 즉 요구사항을 중점적으로 생각하며 미션 풀이를 했다. 하루하루 지나면서 코드의 품질을 신경 쓰는 시간이 늘어갔다. 그리고 TDD에 관심이 생겨 켄트 백의 &#39;테스트 주도 개발&#39; 이라는 책을 구매했다. 등하교 시간에 조금씩 조금씩 읽어볼 예정이다. 또한 피어리뷰를 처음으로 받아봤는데, 꽤나 많이 신경쓰고 작성했던 코드도 다른 분들이 피드백을 남겨주시니까 아직 한참 부족하다고 생각했다. 하지만 앞으로 배울 점이 많이 남아있다는 사실이 설레기도 한다. 이제 곧 3주차가 시작하는데, 남은 미션들을 수행하면서 어제보다 나은 개발자가 되기를 기대한다 😊</p>
<blockquote>
<p>참고자료
<a href="https://tecoble.techcourse.co.kr/post/2020-06-10-one-level-of-indentation-per-method/">https://tecoble.techcourse.co.kr/post/2020-06-10-one-level-of-indentation-per-method/</a>
<a href="https://tecoble.techcourse.co.kr/post/2020-05-10-single-job-method/">https://tecoble.techcourse.co.kr/post/2020-05-10-single-job-method/</a>
<a href="https://da-nyee.github.io/posts/tecoble-unit-test-vs-integration-test-vs-acceptance-test/">https://da-nyee.github.io/posts/tecoble-unit-test-vs-integration-test-vs-acceptance-test/</a>
<a href="https://tecoble.techcourse.co.kr/post/2020-05-07-appropriate_method_for_test_by_parameter/">https://tecoble.techcourse.co.kr/post/2020-05-07-appropriate_method_for_test_by_parameter/</a>
<a href="https://www.youtube.com/watch?v=3LMmPXoGI9Q">https://www.youtube.com/watch?v=3LMmPXoGI9Q</a></p>
</blockquote>
<h4 id="이번-주차-때-주의해야-할-점">이번 주차 때 주의해야 할 점!</h4>
<p>✅ 자바 코드 컨벤션 지키기!
✅ 좋은 테스트 코드 작성하기!
✅ indent를 줄여 가독성 높이기!
✅ 메소드 작성 시 한가지 일만 시키기!
✅ 스스로 생각하고 풀어보기!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스] 착한 문제(?)에 그렇지 못한 코드 (1)]]></title>
            <link>https://velog.io/@jindaram-stu/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-%EC%B0%A9%ED%95%9C-%EB%AC%B8%EC%A0%9C%EC%97%90-%EA%B7%B8%EB%A0%87%EC%A7%80-%EB%AA%BB%ED%95%9C-%EC%BD%94%EB%93%9C-1</link>
            <guid>https://velog.io/@jindaram-stu/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-%EC%B0%A9%ED%95%9C-%EB%AC%B8%EC%A0%9C%EC%97%90-%EA%B7%B8%EB%A0%87%EC%A7%80-%EB%AA%BB%ED%95%9C-%EC%BD%94%EB%93%9C-1</guid>
            <pubDate>Sat, 05 Nov 2022 11:09:08 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jindaram-stu/post/1a257b31-7a5f-4a9a-a401-dc1247012abb/image.png" alt=""></p>
<h2 id="소감">소감</h2>
<hr>
<p>1주차 온보밍 미션 기간이 끝이 났다.. 하지만 곧바로 2주차 START~
돌이켜보면 후회가 가장 많이 남은 한 주 였다. 문제의 난이도보다는 클린코드를 작성하기 위한 몸부림을 많이 쳤던 거 같다. 뭐 다소 까다로운 문제들도 있었지만, 모두 재미있는 문제들이었다. 이번 회고록에서는 각 문제에 대한 나의 느낌과 소감을 공유해보려고 한다.</p>
<p><em><strong>문제 풀이에 관한 코드 Github</strong></em>
<a href="https://github.com/jindaram-stu/java-onboarding/tree/jindaram-stu/src/main/java/onboarding">https://github.com/jindaram-stu/java-onboarding/tree/jindaram-stu/src/main/java/onboarding</a></p>
<h3 id="📢-1번-문제">📢 1번 문제</h3>
<p>가장 먼저 친숙한 이름들이 나와 반가웠다. 가끔가다 코딩테스트 관련 문제를 살펴보면, 되게 딱딱한 문제들도 있지만, 1번 문제처럼 친숙한 소재를 가지는 문제들이 많은 걸 느끼곤 한다. 그런 부분들이 해당 문제와 조금 가깝게 느끼게 해주는 거 같다. 포비와 크롱.. 기억하겠어 😂
문제 난이도는 그닥 높진 않았지만, 예외 케이스를 한도 끝도 없이 생각하다보니 코드가 길어지는 걸 느꼈다. 조건문을 반복해서 사용해야 할 일이 있었는데, 한 줄로 표현 가능한 IF문을 작성할까 했었지만, 많은 사람들이 쓰는 IF문 형식을 유지해야 가독성이 좋을 거 같아서, 일부러 늘어뜨려 작성했다. 잘한 건지는 모르겠지만, 나만 보는 코드가 아닌 같이 보는 코드라는 생각을 하며 첫 문제 풀이를 시작했다. </p>
<h3 id="📢-2번-문제">📢 2번 문제</h3>
<p>개인적으로 조금 난이도가 있었던 문제였다. 문제를 잘 사펴보니 문자들을 삭제하는 과정을 설명해주신 것이 큰 도움이 되었다. 처음엔 두 문자만 처리하는 줄 알았지만, 슬랙에 도는 흉흉한 소문들을 확인해보니 두 문자 이상이 반복되는 모든 경우에 대해서 처리를 하는 것이었다. 나는 Stack을 활용하여 문제를 풀었다. Stack의 peek() 값과 현재 위치한 인덱스의 문자가 같으면 반복 카운트를 +1 하여 Stack의 peek()과 현재 위치한 인덱스 문자가 다를 때 반복 카운트 만큼 Stack을 제거하는 로직으로 풀었다. 2번 문제에서 코드를 간단하게 구성하는 것이 중요하지만 그렇게 하기 위해선 로직도 간단해야 한다는 것을 느꼈다. 로직이 복잡하다면 메소드로 분리하기도 애매하고, 분리했을 때 파라미터가 너무 많아진다는 것을 느꼈다. 가장 간단한 로직을 작성해야 클린하게 코드를 짜기 편한 걸 깨달았다!</p>
<h3 id="📢-3번-문제">📢 3번 문제</h3>
<p>3번 문제는 조금 쉽게 풀 수 있었다. 숫자를 문자열로 파싱한 뒤에 3,6,9가 각각 들어가는 부분을 계산하면 쉽게 문제를 풀 수 있다! 코드가 간단해서 첨부해보겠다!</p>
<pre><code class="language-java">public class Problem3 {
    public static int solution(int number) {
        int resultClap = 0;
        for (int i=1; i&lt;=number; i++) {
            String stringNumber = String.valueOf(i);
            int threeCount = count(stringNumber,&quot;3&quot;);
            int sixCount = count(stringNumber,&quot;6&quot;);
            int nineCount = count(stringNumber,&quot;9&quot;);

            resultClap += threeCount + sixCount + nineCount;
        }
        return resultClap;
    }

    public static Integer count(String number, String clapCondition) {
        return number.length() - number.replace(clapCondition,&quot;&quot;).length();
    }
}</code></pre>
<h3 id="📢-4번-문제">📢 4번 문제</h3>
<p>문제를 보며 가장 먼저 청개구리는 역순으로 변환을 하니, 스펠링에 늘어뜨려 놓은 문자열을 선언한 뒤, 뒤에서부터 계산하면 되겠다는 생각이 떠올랐다. 그리고 알파벳인 경우에만 위 로직을 적용해야 하므로, 변환 전에 추출한 Character가 문자인지도 확인하는 메소드도 추가했다. 비교적 로직이 간단해서 메소드를 분리하기 편했고 깔끔하게 작성한(?) 문제라고 생각한다. (아마두) 😊</p>
<h3 id="📢-5번-문제">📢 5번 문제</h3>
<p>어디서 많이 본 것 같은 문제다. 나의 경우 Cash에 해당하는 [50,000원, 10,000원, 5,000원, 1,000원, 500원, 100원, 50원, 10원, 1원] 을 상수로 정의해두고, 반복을 통해 money를 계산했다. 각각의 몫을 ArrayList에 추가했당! 개인적으로 제일 간단하게 풀 수 있었던 문제!</p>
<h3 id="📢-6번-문제">📢 6번 문제</h3>
<p>갑자기 팍 올라간 문제의 난이도..! 문제를 보자 2글자씩 나눈 뒤 어딘가에 저장하여 매번 검사해야 한다는 사실을 떠올렸으나, 실제 구현에 애를 먹었다. 2글자씩 나눈 값이 저장한 공간에 존재하지 않는다면, 넘어가고 존재한다면 존재하는 crew의 이메일을 List에 추가하려 했으나, 그렇다면 기존 crew가 추가되지 않는다는 사실을 깨달았다. 문제를 해결하려 깊게 고민하다보니, Map의 key, value로 해결할 수 있었다. Map의 containsKey() 메소드로 닉네임을 2글자로 분리한 것이 있는지 없는지 확인할 수 있었고, value에는 나눈 유저의 이메일을 넣어두었다. 그래서 다음 코드와 같이 해결할 수 있었다. 😊</p>
<pre><code class="language-java">if (segments.containsKey(word)) {
    emails.add(segments.get(word));
    emails.add(crewEmail);
}
if(!segments.containsKey(word)) {
    segments.put(word,crewEmail);
}</code></pre>
<p>그렇게 잘 풀리나 싶었지만.. 테스트케이스를 공유하는 슬랙 채널을 확인해보니 &quot;두근두근&quot;과 같이 동일한 닉네임에서 반복하는 결과에 대한 테스트케이스를 발견했다. 결국 해당 테스트케이스에 대해서는 예외처리를 진행하지 못했다. 앞으로는 어떤 값들이 들어올 수 있는지 문제성향과 잘 파악하며 풀이를 진행해야겠다.. 😂</p>
<h3 id="📢-7번-문제">📢 7번 문제</h3>
<p>6번 문제를 보고 잔뜩 쫄았지만 은근(?) 어렵지 않게 구현할 수 있었던 문제였다. 먼저 다음과 같은 순서도를 생각했다.</p>
<ol>
<li>user와 친구인 부분과 친구가 아닌 부분 분리하기</li>
<li>②에서 분리한 친구 관계 중 user의 친구가 한 명만 존재한다면 나머지 친구에게 +10점 부여</li>
<li>방문한 이들 중 user의 친구를 제외한 이들에게 +1점 부여</li>
<li>Comparator을 이용한 커스텀 정렬 후 리턴
그리고 이를 유의하면서 구현하다보니 금방 구현할 수 있었다. </li>
</ol>
<hr>
<h3 id="회고">회고</h3>
<p>1주차가 무사히(?) 끝났다. 다소 코딩테스트와 같은 느낌이 있었지만, 시간이 너무나도 넉넉하여 못 푼 문제는 없었다. 하지만 시간이 많은 만큼 코드의 가독성과 성능에 대한 타협점을 찾느라 애를 많이 먹었던 시간이었던 것 같다. 그런만큼 개발에 대한 시각을 나만 하는 것이 아닌, 남과 같이 한다는 시각을 가지게 되었고 그런만큼 코드의 성능보다 유지보수와 가독성이 좋은 코드도 중요하다는 것을 깨달을 수 있는 한 주였다. 어떤 미션이 앞으로 출제될지는 모르겠지만 남은 3번의 미션동안 이 점을 유의하여 미션을 수행해야겠다! 좋은 결과를 낳지 않더라도, 최선을 다한 만큼 후회없는 시간이 되었으면 좋겠다. 👍 그리고 4주동안 같이 진행하는 분들도 열심히 힘냈으면 좋겠다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BOJ] 5568 : 카드놓기]]></title>
            <link>https://velog.io/@jindaram-stu/BOJ-5568-%EC%B9%B4%EB%93%9C%EB%86%93%EA%B8%B0</link>
            <guid>https://velog.io/@jindaram-stu/BOJ-5568-%EC%B9%B4%EB%93%9C%EB%86%93%EA%B8%B0</guid>
            <pubDate>Wed, 06 Oct 2021 16:18:57 GMT</pubDate>
            <description><![CDATA[<h4 id="출처--httpswwwacmicpcnetproblem5568">출처 : <a href="https://www.acmicpc.net/problem/5568">https://www.acmicpc.net/problem/5568</a></h4>
<hr>
<h2 id="💬-설명">💬 설명</h2>
<hr>
<p>상근이는 카드 n(4 ≤ n ≤ 10)장을 바닥에 나란히 놓고 놀고있다. 각 카드에는 1이상 99이하의 정수가 적혀져 있다. 상근이는 이 카드 중에서 k(2 ≤ k ≤ 4)장을 선택하고, 가로로 나란히 정수를 만들기로 했다. 상근이가 만들 수 있는 정수는 모두 몇 가지일까?</p>
<p>예를 들어, 카드가 5장 있고, 카드에 쓰여 있는 수가 1, 2, 3, 13, 21라고 하자. 여기서 3장을 선택해서 정수를 만들려고 한다. 2, 1, 13을 순서대로 나열하면 정수 2113을 만들 수 있다. 또, 21, 1, 3을 순서대로 나열하면 2113을 만들 수 있다. 이렇게 한 정수를 만드는 조합이 여러 가지 일 수 있다.</p>
<p>n장의 카드에 적힌 숫자가 주어졌을 때, 그 중에서 k개를 선택해서 만들 수 있는 정수의 개수를 구하는 프로그램을 작성하시오.</p>
<h2 id="💻-입력출력">💻 입력/출력</h2>
<hr>
<p><img src="https://images.velog.io/images/jindaram-stu/post/0f2d2765-e48d-4891-bcff-c746e6739f45/11313.PNG" alt=""></p>
<h2 id="📝-풀이">📝 풀이</h2>
<hr>
<p>본 문제의 설명은 재귀 카테고리에 있는 문제이며, 내가 생각하는 문제의 요점은
바로 중복이 허용 된다는 것이다. 즉 5개의 카드 {1,1,2,3,4} 에서 3개를 뽑는다고 하면 [1,1,2],[1,2,1]
이런식으로 순서가 바뀌어도 해당 값은 정수가 만들어지기 때문에 허용이 된다. 또한 한 개의 정수가 만들어진다 하더라도 그 방법은 다양할수도 있기 때문에 그런 중복은 허용이 되지 않는다 즉, 정수의 갯수만 파악하여야 한다. </p>
<p>위 문제를 풀기 위해선 가장 먼저 정수가 어떤 방식으로 만들어질지 생각해봐야 한다.
위와 같은 예시에서 3개의 카드를 뽑는 경우의 수는</p>
<p>[1,1,2] [1,1,3] [1,1,4] [1,2,3] [1,2,4] [1,2,1] [1,3,4] [1,3,1] [1,3,2] ···
이런식으로 진행되게 된다. 이러한 패턴을 파악하기 쉽게 구성한다면</p>
<pre><code>1
    1
           2 [1,1,2]
        3 [1,1,3]
        4 [1,1,4]
    2
        3 [1,2,3]
        4 [1,2,4]
        1 [1,2,1]
    3
        4 [1,3,4]
        1 [1,3,1]
        2 [1,3,2]
    4
        1 [1,4,1]
        2 [1,4,2]
        3 [1,4,3]
2

       ·
       ·
       ·
       ·
       ·</code></pre><p>각 정수의 자릿수를 level[0,1,2] 라고 한다면
0 level의 반복수는 n번
1 level은 n-1번
2 level은 n-2번이다.</p>
<p>우리는 n=5인 경우를 살펴봤으니, 세 단계에서 각각 5번 4번 3번을 반복하여 정수를 추출해내는 것이다. 그리고 재귀 함수 맨 앞에 level이 1의 자리 정수를 구할 때, String으로 해당 정수를 추가시켜서 ArrayList.contains() 메소드를 통해 있는 지 확인한 수 false라면 해당 정수를 추가시킨다. </p>
<p>내가 제시한 기준에서 나는 10의 자리수가 바뀌는 과정을 배열의 재조정을 통해 풀었다.
코드를 통해 알아보자</p>
<h2 id="📋-코드">📋 코드</h2>
<hr>
<pre><code class="language-Java">public class card {

    static int k = 0;
    static int n = 0;
    static ArrayList&lt;String&gt; jungsuList = new ArrayList&lt;String&gt;();

    public static void main(String[] args) throws NumberFormatException, IOException {
        ArrayList&lt;Integer&gt; arr = new ArrayList&lt;Integer&gt;();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        n = Integer.parseInt(br.readLine());
        k = Integer.parseInt(br.readLine());
        for (int q=0; q&lt;n; q++) {
            arr.add(Integer.parseInt(br.readLine()));
        }

        pushBlock(arr,&quot;&quot;,0);

        System.out.println(jungsuList.size());

    }

    public static void pushBlock(ArrayList&lt;Integer&gt; a,String number, int count) {
        String abc = number;

        if (count == k-1) {
            for (int k=count; k&lt;n; k++) {
                number += String.valueOf(a.get(k));
                if (!jungsuList.contains(number)) { // arraylist에 같은 수가 있는지 확인
                    jungsuList.add(number);
                }
                number = abc;
            }
            return;
        }

        for (int i=count; i&lt;n; i++) {
            number += String.valueOf(a.get(count));
            pushBlock(a,number,count+1);
            int temp = a.get(count);
            a.remove(count);
            a.add(temp);

            number = abc;
        }
    }

}
</code></pre>
]]></description>
        </item>
    </channel>
</rss>