<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>whatSup.log</title>
        <link>https://velog.io/</link>
        <description>AI Engineer : Lv 0</description>
        <lastBuildDate>Fri, 03 Mar 2023 11:38:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>whatSup.log</title>
            <url>https://images.velog.io/images/whattsup_kim/profile/3e1f476a-3997-4f86-867c-e7b69d4ce8aa/케빈.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. whatSup.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/whattsup_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[빅데이터를 지탱하는 기술] CH02. 빅데이터의 탐색]]></title>
            <link>https://velog.io/@whattsup_kim/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-CH02.-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@whattsup_kim/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-CH02.-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-%ED%83%90%EC%83%89</guid>
            <pubDate>Fri, 03 Mar 2023 11:38:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 글은 <a href="http://www.yes24.com/Product/Goods/66277191">빅데이터를 지탱하는 기술</a>을 읽고 정리한 내용입니다.</p>
</blockquote>
<h1 id="2-1-크로스-집계의-기본">2-1 크로스 집계의 기본</h1>
<h2 id="트랜잭션-테이블-크로스-테이블-피벗-테이블">트랜잭션 테이블, 크로스 테이블, 피벗 테이블</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/d4124b53-6c11-4e20-9e31-a01dbcce22a3/image.png" alt=""></p>
<ul>
<li>크로스 테이블 : 어떤 기간에 어떤 상품의 판매 ‘집계’ 등을 테이블로 만든 것<ul>
<li>사람들이 보기 편함</li>
<li>그러나 데이터베이스에서는 다루기 어려운 형식</li>
</ul>
</li>
<li>트랜잭션 테이블 : DB의 record에 해당하는 데이터를 저장하는 테이블.<ul>
<li>데이터베이스 등에서 다뤄지는 형식</li>
<li>새로운 데이터는 새로운 행이 추가되는 식으로 기록됨</li>
</ul>
</li>
<li>크로스 집계 : 트랙잰션 테이블에서 크로스 테이블로 변환하는 과정<ul>
<li>스프레드시트의 피봇 테이블</li>
<li>Pandas의 <code>pivot_table()</code></li>
<li>SQL의 집계 합수를 이용(대량의 데이터)</li>
</ul>
</li>
</ul>
<h2 id="데이터-집계-→-데이터-마트-→-시각화">데이터 집계 → 데이터 마트 → 시각화</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/62618e6e-f5e8-4091-a97a-b787b1a3af9e/image.png" alt=""></p>
<ul>
<li>일반적으로 데이터 마트가 작을수록 시각화하는 것이 간단하지만, 동시에 원래 데이터에 포함된 정보를 잃어버리게 되어 시각화의 프로세스에서 할 수 있는 것이 적어질 수 있음.</li>
<li>반대로 데이터 집계에서 정보를 많이 남기게 되면, 데이터 마트가 거대화되어 좋은 시각화를 할 수 없게 될 우려가 있음.</li>
<li>이들은 trade-off 관계에 있으며, 이 ‘데이터 마트의 크기’에 따라 시스템 구성이 결정됨.</li>
</ul>
<h1 id="2-2-열-지향-스토리지에-의한-고속화">2-2 열 지향 스토리지에 의한 고속화</h1>
<p>메모리에 다 올라가지 않는 정도의 대량의 데이터를 집계하려면, 미리 데이터를 집계에 적합한 형태로 변환하는 것이 필요함. 2-2에서는 집계효율이 높은 데이터베이스의 구조를 살펴본다.</p>
<h2 id="데이터베이스의-지연을-줄이기">데이터베이스의 지연을 줄이기</h2>
<ul>
<li><p>데이터 양이 증가함에 따라 집계에 걸리는 시간은 길어짐</p>
<ul>
<li><p>따라서 초 단위로 데이터를 집계하려면 이를 위한 시스템을 설계하여야 한다.</p>
</li>
<li><p>주로 다음과 같은 3계층의 시스템을 만듦</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/1c80d311-476e-4709-b6ce-a35a99337033/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p>원 데이터는 용량적인 제약이 적고, 대량의 데이터를 처리할 수 있는 데이터 레이크(혹은 데이터 웨어하우스)에 저장한다. 여기서 원하는 데이터를 추출하여 데이터 마트를 구축하고, <strong>데이터 마트에서는 항상 초 단위의 응답을 얻을 수 있도록 한다</strong>.</p>
</li>
</ul>
<h3 id="데이터-처리의-지연--지연이-적은-데이터-마트-작성을-위한-기초-지식">데이터 처리의 지연 : 지연이 적은 데이터 마트 작성을 위한 기초 지식</h3>
<ul>
<li>지연을 해결하기 위한 가장 간단한 방법은 모든 데이터를 메모리에 올리는 것이다.<ul>
<li>모든 데이터를 메모리에 올릴 수 있는 정도의 양이라면, MySQL이나 PostgreSQL과 같은 일반적인 RDB가 데이터마트에 적합하다. (RDB는 지연이 적고 많은 클라이언트의 동시 접속을 감당할 수 있음)</li>
</ul>
</li>
<li>그러나, RDB는 메모리가 부족하면 급격히 성능이 저하됨.<ul>
<li>수억 레코드를 초과하는 집계에서는 항상 디바이스 I/O가 발생하게 됨.</li>
<li>이를 어떻게 효율화할 것인지가 중요한 key.</li>
</ul>
</li>
</ul>
<h3 id="압축과-분산에-의해-지연-줄이기---mpp-기술">‘압축’과 ‘분산’에 의해 지연 줄이기 - MPP 기술</h3>
<ul>
<li>데이터를 가능한 한 작게 압축하고, 그것을 여러 디스크에 분산함으로써 데이터의 로드에 따른 지연을 줄일 수 있다.</li>
<li>분산된 데이터를 읽어들이려면 멀티 코어를 활용하면서 디스크 I/O를 병렬처리하는 것이 효과적이다.<ul>
<li>이러한 아키텍처를 MPP(Massive Parallel Processing: 대규모 병렬 처리)라고 부른다.</li>
<li>MPP 아키텍처는 데이터 분석을 위해 데이터베이스에서 널리 사용되고 있다.</li>
<li>ex) Amazon Redshift, Google BigQuery, …</li>
</ul>
</li>
</ul>
<h2 id="열-지향-데이터베이스-접근">열 지향 데이터베이스 접근</h2>
<p>빅테이터로 취급되는 데이터의 대부부은 디스크에 있기 때문에 쿼리에 필요한 최소한의 데이터를 가져옴으로써 지연이 줄어들게 된다. 이를 위해 사요오디는 방법이 ‘칼럼 단위로의 데이터 압축’이다.</p>
<h3 id="행-지향-데이터베이스---각-행이-디스크-상에-일련의-데이터로-기록됨">행 지향 데이터베이스 - 각 행이 디스크 상에 일련의 데이터로 기록됨</h3>
<ul>
<li>행 지향 데이터베이스에서는 테이블의 각 행을 하나의 덩어리로 디스크에 저장한다.<ul>
<li>이렇게 하면, 새 레코드를 추가할 때 파일의 끝에 데이터를 쓸 뿐이므로 빠르게 데이터를 추가할 수 있다.</li>
<li>즉, 대량의 트랜잭션을 지연 없이 처리하기 위한 데이터 쓰기 작업을 효율적으로 할 수 있음</li>
</ul>
</li>
<li>행 지향 데이터베이스에서는 데이터 검색을 고속화하기 위해 인덱스(index)를 사용한다.<ul>
<li>만약 인덱스가 없다면 검색을 위해 모든 데이터를 로드해야하므로 많은 디스크 I/O가 발생하여 성능이 저하됨.</li>
<li>❗️반면 데이터 분석에서는 어떤 칼럼이 사용되는지 미리 알 수 없기 때문에 인덱스를 사용하여도 큰 도움이 되지 않음. 따라서 디스크I/O를 효율화하기 위한 다른 고속화 기술이 필요함</li>
</ul>
</li>
</ul>
<h3 id="열-지향-데이터베이스---칼럼마다-데이터를-모아두기">열 지향 데이터베이스 - 칼럼마다 데이터를 모아두기</h3>
<ul>
<li>열 지향 데이터베이스에서는 데이터를 미리 칼럼 단위로 정리해둠으로써 필요한 칼럼만을 로드하여 디스크I/O를 줄임<ul>
<li>예를 들어, 점포의 총 매출액을 알고 싶을 땐 고객의 정보는 필요 없음. 행 지향 데이터베이스에서는 레코드 단위로 저장되기 때문에 디스크로부터 필요 없는 열까지 로드하게 됨.</li>
</ul>
</li>
<li>또한 열 지향 데이터베이스는 데이터의 압출 효율도 우수함.<ul>
<li>같은 칼럼에는 종종 유사한 데이터가 나열됨.</li>
<li>특히 같은 문자열의 반복은 매우 작게 압축할 수 있음</li>
<li>열 지향 데이터베이스는 압축되지 않은 행 지향 데이터베이스와 비교하면 (대략) 1/10 이하로 압축할 수 있음</li>
</ul>
</li>
</ul>
<h2 id="mpp-데이터베이스의-접근-방식">MPP 데이터베이스의 접근 방식</h2>
<ul>
<li><p>행 지향 데이터베이스에서는 보통 하나의 쿼리는 하나의 스레드에서 실행됨.</p>
<ul>
<li>행 지향 데이터베이스의 경우, 각 쿼리는 충분히 짧은 시간 안에 끝나는 것으로 가정하므로, 하나의 쿼리를 분산 처리하는 상황은 가정하지 않음.</li>
</ul>
</li>
<li><p>반면, 열 지향 데이터베이스는 한 번의 쿼리도 실행 시간이 길어짐. 또한 압축된 데이터의 전개 등으로 CPU 리소스를 필요로하므로 멀티 코어를 활용하여 고속화하는 것이 좋음</p>
</li>
<li><p>MPP에서는 하나의 쿼리를 다수의 작은 테스크로 분해하고 이를 가능한 병렬로 실행함</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/5fec8b46-5609-49b9-80ef-5921ef0d69ad/image.png" alt=""></p>
</li>
</ul>
<h3 id="mpp-데이터베이스와-대화형-쿼리-엔진">MPP 데이터베이스와 대화형 쿼리 엔진</h3>
<ul>
<li>쿼리가 잘 병렬화할 수 있다면, MPP를 사용한 데이터의 집계는 CPU 코어 수에 비례해 고속화됨<ul>
<li>단, 디스크로부터의 로드가 병목 현상이 발생하지 않도록 데이터가 고르게 분산되어 있어야 함</li>
</ul>
</li>
<li>MPP는 구조상, 고속화를 위해 CPU와 디스크 모두를 균형 있게 늘려야 함.<ul>
<li>이처럼 하드웨어 수준에서 데이터 집계에 최적화된 DB를 ‘MPP 데이터베이스’라고 함</li>
</ul>
</li>
<li>MPP의 아키텍처는 Hadoop과 함께 사용되는 대화형 쿼리 엔진으로도 채택되고 있음.<ul>
<li>그러나, 데이터를 열 지향으로 압축하지 않는 한 MPP 데이터베이스와 동등한 성능은 되지 못함.(하둡 상에서 열 지향 스토리지를 만들기 위해 여러 라이브러리가 개발되고 있음)</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>집계 시스템 종류</th>
<th>스토리지의 종류</th>
<th>최적의 레코드 수</th>
</tr>
</thead>
<tbody><tr>
<td>RDB</td>
<td>행 지향</td>
<td>~수 천만</td>
</tr>
<tr>
<td>MPP 데이터베이스</td>
<td>열 지향(하드웨어 일체형)</td>
<td>수억~</td>
</tr>
<tr>
<td>대화형 쿼리 엔진</td>
<td>열 지향(분산 스토리지에 보관)</td>
<td>수억~</td>
</tr>
</tbody></table>
<h1 id="2-3-애드-훅-분석과-시각화-도구">2-3 애드 훅 분석과 시각화 도구</h1>
<h2 id="jupyter-notebook에-의한-애드-훅-분석">Jupyter Notebook에 의한 애드 훅 분석</h2>
<p>-생략-</p>
<h2 id="대시보드-도구---정기적으로-집계-결과를-시각화하기">대시보드 도구 - 정기적으로 집계 결과를 시각화하기</h2>
<ul>
<li>대시보드 도구와 BI 도구의 차이는 그다지 엄밀하진 않음<ul>
<li>대시보드는 새로운 그래프를 쉽게 추가할 수 있는 것이 중시된다면</li>
<li>BI 도구는 보다 대화형 데이터 탐색이 중요시 됨.</li>
<li>ex) 그래프를 클릭하여 상세한 표시로 전환하거나, 집계에 기반이 되는 로우 데이터를 표시하는 등 시간을 들여 차분히 데이터를 보고 싶은 경우 BI 도구가 더 적합함</li>
</ul>
</li>
<li>대시보드 도구에서는 최신의 집계 결과를 즉시 확인할 수 있길 기대한다.<ul>
<li>따라서 정해진 지표의 일상적인 변화를 모니터링하고 싶은 경우에는 대시보드가 적합함</li>
</ul>
</li>
<li>대표적인 오픈소스 대시보드(혹은 실시간 시각화) 도구는 다음이 있다.<ul>
<li>Redash : 다수의 데이터 소스에 대응하는 파이썬 기반 대시보드 도구<ul>
<li>장점<ul>
<li>SQL에 의한 쿼리의 실행 결과를 그대로 시각화하는 데 적합</li>
<li>대시보드의 작성이 직관적임<ol>
<li>데이터 소스 등록</li>
<li>쿼리를 실행하여 표와 그래프를 만듦</li>
<li>그래프를 대시보드에 추가함</li>
</ol>
</li>
<li>쿼리는 정기적으로 실행되어 그 결과가 Redash 자신의 데이터베이스에 저장된다. 따라서 별도 데이터 마트를 만들 필요가 없음</li>
</ul>
</li>
<li>단점<ul>
<li>BI 도구만큼 대량의 데이터를 처리할 수 없음</li>
<li>Redash에서 그래프의 수만큼 쿼리를 실행하게 되고, 대시보드가 증가함에 따라 백앤드 DB의 부하가 높아짐</li>
</ul>
</li>
</ul>
</li>
<li>Superset : 대화형(interactive) 대시보드를 작성하기 위한 파이썬 기반 웹 어플리케이션.<ul>
<li>장점<ul>
<li>시계열 데이터에 대응한 열 지향 스토리지인 ‘Druid’를 표준으로 지원하며, 스트리밍 형의 데이터 전송과 조합시킴으로써 실시간 정보를 취급할 수 있음.</li>
</ul>
</li>
<li>단점<ul>
<li>내장 스토리지 시스템을 갖고 있지 않아 데이터의 집계는 외부 데이터 저장소에 의존함.</li>
<li>BI 도구와 마찬가지로 시각화를 위한 데이터 마트를 먼저 만들어두어야 함</li>
</ul>
</li>
</ul>
</li>
<li>Kibana : 자바스크립트로 만들어진 대화식 시각화 도구(특히 실시간 대시보드를 만들 목적으로 자주 사용됨.).<ul>
<li>Kibana는 Elasticsearch 이외의 데이터 소스에는 대응하고 있지 않아 시각화하려는 데이터는 모두 Elasticsearch에 저장해야 함.<ul>
<li>Elasticsearch는 ‘전체 텍스트 검색’에 대응한 데이터 스토어임. 따라서 키워드로 텍스트 데이터를 검색하려는 경우 특히 그 힘을 발휘함.</li>
</ul>
</li>
<li>시각화를 위한 데이터 스토어로 Elasticsearch를 채용하는 경우 최선의 선택이 될 수 있음.<ul>
<li>차분히 시간을 들여 데이터를 탐색하는 것보다는 검색 조건에 맞는 데이터를 빠르게 시각화하는 데 적합한 도구임</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="bi-도구---대화적인-대시보드">BI 도구 - 대화적인 대시보드</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/8ca159c4-cacd-420e-bc3e-61cc681cd979/image.png" alt=""></p>
<ul>
<li>몇 개월 단위의 장기적인 데이터의 추이를 시각화하거나, 집계의 조건을 세부적으로 바꿀 수 있는 대시보드를 만들려면, BI 도구를 사용하는 것이 적합할 수 있음.</li>
<li>BI 도구에서는 이미 있는 데이터를 그대로 가져올 뿐 아니라 , 시간을 들여 데이터를 분석하기 쉽도록 가공하는 일이 자주 있음. 따라서 시각화에 적합한 데이터 마트를 만들어 읽고 쓰는 것을 전제로 함</li>
<li>대화형 대시보드를 만들기 위해선 그 바탕이 되는 데이터를 모두 포함하는 하나의 테이블을 작성하고, 이 테이블을 사용해 다수의 대시보드를 만든다.<ul>
<li>알고 싶은 것이 늘어날 때마다 데이터 마트에 테이블을 만들고, 거기에서 파생된 다수의 대시보드가 생겨나는 것이 BI 도구의 시각화 과정임</li>
</ul>
</li>
</ul>
<h1 id="2-4-데이터-마트의-기본-구조">2-4 데이터 마트의 기본 구조</h1>
<h2 id="시각화에-적합한-데이터-마트-만들기---olap">시각화에 적합한 데이터 마트 만들기 - OLAP</h2>
<ul>
<li>BI 도구에 있어 핵심적인 개념 중 하나로 OLAP(Online Analytical Processing)라는 구조가 있음</li>
</ul>
<h3 id="다차원-모델과-olap-큐브">다차원 모델과 OLAP 큐브</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/043f16b7-bbf6-4f3f-8c9a-36b71876ca6e/image.png" alt=""></p>
<ul>
<li>OLAP는 데이터 집계를 효율화하는 접근 방법 중 하나임.</li>
<li>일반적으로 RDB는 표 형식으로 모델링된 데이터를 SQL로 집계하는 반면, OLAP에서는 ‘다차원 모델’의 데이터 구조를 ‘MDX(MultiDimensional eXpressions)’ 등의 쿼리 언어로 집계함.<ul>
<li>데이터 분석을 위해 만들어진 다차원의 데이터를 ‘OLAP 큐브’라고 부르며, 이것을 집계하는 구조가 OLAP임</li>
</ul>
</li>
<li>이전에는 컴퓨터 성능이 높지 않아, 데이터 편집에 많은 시간이 걸렸으므로 OLAP를 고속화하려면 여러 아이디어가 필수적이었음<ul>
<li>ex) 크로스 집계의 모든 조합을 미리 계산하여 캐싱해두기</li>
</ul>
</li>
<li>BI 도구는  본래 OLAP 구조를 사용하여 데이터를 집계하기 위한 것이었고, 따라서 이전에는 데이터 마트도 OLAP 큐브로 작성되었음</li>
</ul>
<h3 id="mpp-데이터베이스와-비정규화-테이블">MPP 데이터베이스와 비정규화 테이블</h3>
<ul>
<li>최근에는 MPP 데이터베이스 등의 보급으로 BI와 MPP 데이터베이스를 조합하여 크로스 집계하는 경우가 증가하고 있음 (미리 계산 x).</li>
<li>BI 도구로 생각한대로의 그래프를 만들기 위해서는 이미 존재하는 테이블을 그대로 시각화하려고 하는 것이 아니라, 만들고 싶은 그래프에 맞춰 ‘다차원 모델’을 설계함.<ul>
<li>그러나 MPP 데이터베이스에는 다차원 모델의 개념이 없기 때문에 이를 대신해 ‘비정규화 테이블’을 준비함</li>
<li>비정규화 테이블을 활용하여 BI 도구에서 OLAP와 동등한 시각화를 실현할 수 있음</li>
</ul>
</li>
</ul>
<h2 id="테이블을-비정규화하기">테이블을 비정규화하기</h2>
<ul>
<li><p>데이터베이스 설계에서는 종종 테이블을 ‘마스터’와 ‘트랜잭션’으로 구분함 → 관계형 모델!</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/8368ce3e-1947-416a-b943-f129c01da73b/image.png" alt=""></p>
<ul>
<li>트랜잭션 : 시간과 함께 생성되는 데이터를 기록한 것<ul>
<li>한 번 기록되면 변화하지 않음</li>
</ul>
</li>
<li>마스터 : 트랜잭션에서 참고되는 각종 정보<ul>
<li>상황에 따라 다시 쓰일 수 있음</li>
</ul>
</li>
</ul>
</li>
<li><p>데이터 분석에서는 이러한 정규화된 관계형 모델에서 출발해서 그와는 반대의 작업을 실행함</p>
</li>
</ul>
<h3 id="팩트-테이블과-디멘전-테이블">팩트 테이블과 디멘전 테이블</h3>
<ul>
<li>데이터 웨어하우스의 세계에서는 트랜잭션처럼 사실이 기록된 것을 ‘<strong>팩트 테이블</strong>’이라고 하고, 거기에 참고되는 마스터 데이터 등을 ‘<strong>디멘전 테이블</strong>’이라고 함<ul>
<li>팩트 테이블 : 집계의 기반이 되는 숫자 데이터(ex. 판매액 등)</li>
<li>디멘전 테이블 : 테이블을 분류하기 위한 속성값</li>
</ul>
</li>
</ul>
<h3 id="스타-스키마">스타 스키마</h3>
<ul>
<li><p>데이터 마트를 만들 땐, 팩트 테이블을 중심으로 여러 디멘전 테이블을 결합하는 것이 좋다 (스타 스키마)</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/81b96abd-30fa-4abc-8fdd-d198f832de54/image.png" alt=""></p>
</li>
<li><p>디멘전 테이블을 작성하려면 정규화에 의해 분해된 테이블을 최대한 결합하여 하나의 테이블로 정리한다 (비정규화)</p>
<ul>
<li>그 결과로 데이터가 중복되어도 괜찮음(팩트 테이블만 정규화가 되어 있음)</li>
</ul>
</li>
<li><p>스타 스키마의 장점</p>
<ul>
<li>스타 스키마와 같은 팩트/차원 모델은 <strong>단순</strong>하므로 이해하고 구현하기가 쉬움.</li>
<li>팩트 테이블은 실시간 데이터이므로 시간이 지날수록 매우 많아진다. 따라서 디맨젼 테이블을 최대한 늘림과 동시에 팩트 테이블의 사이즈를 최소화하는 것이 디스크 I/O를 줄일 수 있는 방법이다.</li>
</ul>
</li>
</ul>
<h3 id="비정규화-테이블">비정규화 테이블</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/97d37504-876b-4d08-ad61-a2fe3b580060/image.png" alt=""></p>
<ul>
<li>MPP 데이터베이스와 같은 열 지향 스토리지를 갖는 시스템이 보급됨에 따라, 칼럼의 수가 아무리 늘어나도 성능에 영향을 주지 않게 됨.<ul>
<li>또한 많은 계산을 요구하는 조인을 피하려고 하므로 정규화된 데이터에 비해 <strong>쿼리 성능이 향상됨.</strong></li>
</ul>
</li>
<li>따라서 처음부터 펙트 테이블에 모든 칼럼을 포함해두고, 쿼리의 실행 시에는 테이블 결합을 하지 않는 ‘비정규화 테이블’을 사용하는 것이 대부분의 경우 가장 단순하며 효율적인 방법임.</li>
</ul>
<blockquote>
<p><strong>데이터 웨어하우스와 스타 스키마</strong></p>
<ul>
<li>데이터 마트가 아니라 ‘데이터 웨어하우스’의 데이터 구조로는 스타 스키마가 우수함</li>
<li>따라서 보통 데이터를 축적하는 단계에서는 펙트 테이블과 디멘전 체이블로 분리해두고, 이를 분석하는 단계가 된 후에 결합해 비정규화 테이블을 만ㄷ름</li>
</ul>
</blockquote>
<h2 id="다차원-모델-시각화에-대비하여-테이블을-추상화하기">다차원 모델 시각화에 대비하여 테이블을 추상화하기</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/3ce0322a-2ef0-4c4c-9f1b-99a006a7cf92/image.png" alt=""></p>
<ul>
<li><p>비정규화 테이블을 준비했다면 그것을 ‘다차원 모델&#39;에 의해 추상화한다.</p>
</li>
<li><p>다차원 모델의 칼럼은 ‘디멘전’과 ‘측정값’으로 분류한다.</p>
<ul>
<li>디멘전은 주로 날짜 및 문자열의 값이 되며, 크로스 집계의 행이나 열로서 사용된다.</li>
<li>측정값은 주로 숫자값이  되고, sum()과 max()와 같은 집계 함수와 함께 사용된다.</li>
</ul>
</li>
<li><p>다차원 모델에 의한 데이터의 집계에서는 디멘전과 측정값을 사용하여 SQL의 쿼리가 자동으로 생성된다.</p>
<ul>
<li><p>ex) Tableau Puble에서의 디멘전과 측정값</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/77cbbe3d-a2cd-4ef5-8fb6-eb879ecc5441/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 기초]]></title>
            <link>https://velog.io/@whattsup_kim/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@whattsup_kim/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 13 Jan 2023 09:10:08 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터베이스-개요">데이터베이스 개요</h1>
<h2 id="데이터베이스-개념">데이터베이스 개념</h2>
<ul>
<li>방대한 데이터를 효율적으로 관리하기 위해 컴퓨터에 통함-저장 한 것</li>
<li>특정 조직의 여러 사용자가 <strong>공유</strong>하여 사용할 수 있도록 <strong>통합</strong>해서 <strong>저장</strong>한 <strong>운영</strong> 데이터의 집합</li>
<li>데이터베이스 관리 시스템(DBMS)라는 프로그램을 이용하여 관리</li>
</ul>
<h2 id="데이터베이스에-저장된-데이터의-특징">데이터베이스에 저장된 데이터의 특징</h2>
<p><strong>1. 공유 데이터(Shared)</strong></p>
<p>: 특정 조직의 여러 사용자가 함께 소유하고 이용할 수 있는 공용 데이터</p>
<p><strong>2. 통합 데이터(Intergrated)</strong></p>
<p>: 최소의 중복과 통제 가능한 중복만 허용하는 데이터</p>
<p><strong>3. 저장 데이터(Stored)</strong></p>
<p>: 컴퓨터가 접근할 수 있는 매체에 저장된 데이터</p>
<p><strong>4. 운영 데이터(Operational)</strong></p>
<p>: 조직의 주요 기능을 수행하기 위해 지속적으로 필요한 데이터</p>
<h2 id="데이터베이스의-특징">데이터베이스의 특징</h2>
<p><strong>1. 실시간 접근(Real-time accessibility)</strong></p>
<p>: 사용자의 데이터 요구에 실시간으로 응답</p>
<p><strong>2. 계속 변화(Continuous evolution)</strong></p>
<p>: 데이터의 계속적인 삽입, 삭제, 수정을 통해 현재의 정확한 데이터를 유지</p>
<p><strong>3. 동시 공유(Concurrent sharing)</strong></p>
<p>: 서로 다른 데이터의 동시 사용뿐 아니라 같은 데이터의 동시 사용 지원</p>
<p><strong>4. 내용 기반 참조(Content reference)</strong></p>
<p>: 데이터가 저장된 주소나 위치가 아닌 내용으로 참조(ex. 성적이 70점 이상인 학생)</p>
<h2 id="파일-처리-시스템">파일 처리 시스템</h2>
<p>이전에는 응용 프로그램마다 필요한 데이터를 별도의 파일로 관리했음</p>
<ul>
<li>파일 처리 시스템 : 데이터를 파일로 관리하기 위해 파일을 생성, 삭제, 수정, 검색하는 기능을 제공하는 소프트웨어</li>
</ul>
<h3 id="파일-처리-시스템의-문제점">파일 처리 시스템의 문제점</h3>
<ol>
<li><strong>데이터 중복성</strong><ul>
<li>같은 내용의 데이터가 여러 파일에 중복 저장될 수 있음</li>
<li>따라서 저장 공간의 낭비는 물론, 데이터의 일관성과 무결성을 유지하기 어려움</li>
</ul>
</li>
<li><strong>데이터 종속성</strong><ul>
<li>응용 프로그램이 데이터 파일에 종속적</li>
<li>따라서 사용하는 파일의 구조를 변경하면 응용프로그램도 함께 변경해야 함</li>
</ul>
</li>
<li><strong>데이터 파일에 대한 동시 공유, 보안, 회복 기능 부족</strong><ul>
<li>하나의 파일을 동시에 공유해서 사용하기 어렵고</li>
<li>누가 접근해서 사용하는지 보안 관리가 없으며</li>
<li>시스템 문제가 발생하여 파일의 내용이 사라져도 회복이 어려움</li>
</ul>
</li>
<li><strong>응용 프로그램 개발이 쉽지 않음</strong><ul>
<li>파일에 대한 처리 및 관리를 응용 프로그램에서 일부 해주어야 하므로 개발 자체가 쉽지 않음</li>
</ul>
</li>
</ol>
<h2 id="데이터베이스-관리-시스템database-management-system-dbms">데이터베이스 관리 시스템(DataBase Management System, DBMS)</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/4ac978a0-641a-40bc-bd39-3490b9ccb811/image.png" alt=""></p>
<ul>
<li>기존 파일 시스템의 무넺를 해결하기 위해 제시된 소프트웨어</li>
<li>조직에 필요한 데이터를 데이터베이스에 통합하여 저장하고 관리함</li>
<li>사용자와 응용 프로그램에 편리하고 효율적인 데이터베이스 사용환경을 제공하는 소프트웨어</li>
</ul>
<h2 id="데이터베이스-관리-시스템의-주요-기능">데이터베이스 관리 시스템의 주요 기능</h2>
<p><strong>1. 정의 기능</strong></p>
<p>: 데이터베이스 구조를 정의하거나 수정 가능</p>
<ul>
<li>데이터 정의 언어(Data Definition Language, DDL) : 데이터 저장 구조, 데이터 접근 방법, 데이터 형식 등의 정의 가능</li>
</ul>
<p><strong>2. 조작 기능</strong></p>
<p>: 데이터를 삽입, 삭제, 수정, 검색하는 연산 가능</p>
<ul>
<li>데이터 조작 언어(Data Manipulation Language, DML) : 데이터베이스에 저장된 데이터를 검색, 수정, 삽입, 삭제할 떄 사용</li>
</ul>
<p><strong>3. 제거 기능</strong></p>
<p>: 데이터를 항상 정확하고 안전하게 유지</p>
<ul>
<li>데이터 제어 언어(Data Control Language, DCL) : 데이터베이스의 무결성 유지, 보안 및 접근 제어, 시스템 장애로부터의 복구, 병행 수행 제어 기능 등을 수행</li>
</ul>
<h2 id="데이터베이스-관리-시스템의-장점">데이터베이스 관리 시스템의 장점</h2>
<ul>
<li>데이터의 중복과 불일치 감소</li>
<li>데이터 독립성 확보 (파일시스템 등에 대해서)</li>
<li>데이터의 공유와 동시 접근이 가능</li>
<li>데이터의 보안 향상 (스토리지에 있는 데이터에 직접 접근하는 것이 아니라, DBMS를 거치고 접근)</li>
<li>데이터 무결성 향상</li>
<li>표준화 용이</li>
<li>시스템의 융통성 향상</li>
<li>응용 프로그램 개발 및 유지 비용 감소(데이터와 응용프로그램 개발을 분리)</li>
<li>사용자에게 더 나은 서비스 제공(보안 &amp; 회복, …)</li>
<li>시스템 고장으로부터 데이터베이스 복구 가능</li>
<li>데이터 중심의 중앙 집중 관리</li>
</ul>
<h1 id="데이터-모델링">데이터 모델링</h1>
<h2 id="데이터-모델링이란">데이터 모델링이란</h2>
<p>현실 세계에 존재하는 데이터를 컴퓨터 세계의 데이터베이스로 옮기는 변환 과정 (데이터베이스 설계의 핵심 과정)</p>
<ul>
<li>개념적 데이터 모델 : 사람의 머리로 이해할 수 있도록 현실 세계를 개념적 모델링하여 데이터베이스의 개념적 구조로 표현하는 도구(ex. 개체-관계 모델)</li>
<li>논리적 데이터 모델 : 개념적 구조를 논리적 모델링하여 데이터베이스의 논리적 구조로 표현하는 도구(ex. 관계 데이터 모델)</li>
</ul>
<h2 id="개체-관계-모델entity-relationship-model-e-r-model">개체-관계 모델(Entity-Relationship model, E-R model)</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/2db2ed7b-4048-4e5f-a570-2a215450181e/image.png" alt=""></p>
<ul>
<li>피터 첸(Peter Chen)이 제안한 개념적 데이터 모델</li>
<li>개체와 개체 간의 관계를 이용해 현실 세계를 개념적 구조로 표현</li>
<li>핵심 요소<ul>
<li>개체(Entity)<ul>
<li>현실 세계에서 사람이나 사물과 같이 구별되는 모든 것</li>
<li>저장할 가치가 있는 중요 데이터를 가지고 있는 사람이나 사물, 개념, 사건 등</li>
<li>다른 개체와 구별되는 이름을 가지고 있고, 각 개체만의 고유한 특성이나 상태. 즉, 속성을 하나 이상 가지고 있음.</li>
</ul>
</li>
<li>속성(Attribute)<ul>
<li>개체나 관계가 가지고 있는 고유의 특성</li>
<li>의미 있는 데이터의 가장 작은 논리적 단위</li>
<li>파일 구조의 필트(field)와 대응됨</li>
<li>E-R 다이어그램에서 타원으로 표현하고, 타원 안에 이름을 표기</li>
</ul>
</li>
<li>개체 타입(Entity Type)<ul>
<li>개체를 고유의 이름과 속성들로 정의한 것</li>
<li>파일 구조의 레코드 타입(record type)에 대응됨</li>
</ul>
</li>
<li>개체 인스턴스(Entity Instance)<ul>
<li>개체를 구성하고 있는 속성이 실제 값을 가짐으로써 실체화된 개체</li>
<li>entity occurrence라고도 함</li>
<li>파일 구조의 레코드 인스턴스(record instance)에 대응됨</li>
</ul>
</li>
<li>개체 집합<ul>
<li>특정 개체 타입에 대한 개체 인스턴스들을 모아놓은 것</li>
</ul>
</li>
</ul>
</li>
<li>개체-관계 다이어그램(E-R diagram) : 개체-관계 모델을 이용해 현실 세계를 개념적으로 모델링한 결과물을 그림으로 표현한 것</li>
</ul>
<h2 id="관계-데이터-모델">관계 데이터 모델</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/7687f8ae-143c-48d2-af99-f194391c85ff/image.png" alt=""></p>
<ul>
<li>개념적 구조를 논리적 구조로 표현하는 논리적 데이터 모델</li>
<li>하나의 개체에 대한 데이터를 하나의 릴레이션에 저장</li>
</ul>
<h3 id="관계-데이터-모델-용어">관계 데이터 모델 용어</h3>
<ul>
<li>릴레이션(relation)<ul>
<li>하나의 개체에 관한 데이터를 2차원 테이블의 구조로 저장한 것</li>
<li>파일 관리 시스템 관점에서 파일(file)에 대응</li>
</ul>
</li>
<li>속성(attribute)<ul>
<li>릴레이션의 열(애트리뷰트)</li>
<li>파일 관리 시스템 관점에서 필드(field)에 대응</li>
</ul>
</li>
<li>튜플(tuple)<ul>
<li>릴레이션의 행</li>
<li>파일 관리 시스템 관점에서 레코드(record)에 대응</li>
</ul>
</li>
<li>도메인(domain)<ul>
<li>하나의 속성이 가질 수 있는 모든 값의 집합</li>
<li>속성 값을 입력 및 수정할 때 적합성 판단의 기준이 됨</li>
<li>일반적으로 속성의 특성을 고려한 데이터 타입으로 정의</li>
</ul>
</li>
<li>널(null)<ul>
<li>속성 값을 아직 모르거나, 해당하는 값이 없음을 표현</li>
</ul>
</li>
<li>차수(degree)<ul>
<li>하나의 릴레이션에서 속성(열) 전체의 개수</li>
</ul>
</li>
<li>카디널리티(Cardinality)<ul>
<li>하나의 릴레이션에서 튜플(행)의 전체 개수</li>
</ul>
</li>
</ul>
<h3 id="릴레이션-구성">릴레이션 구성</h3>
<ul>
<li><p>릴레이션 스키마(relation schema)</p>
<ul>
<li><p>릴레이션의 논리적 구조</p>
</li>
<li><p>릴레이션의 이름과 릴레이션에 포함된 몯느 속성 이름으로 정의</p>
<ul>
<li>ex) 고객(고객아이디, 이름, 나이, 등급, 직업, …)</li>
</ul>
</li>
<li><p>릴레이션 내포(intension)라고도 함</p>
</li>
<li><p>정적인 특징이 있음</p>
<p>→ 릴레이션 스키마의 모음 = 데이터베이스 스키마</p>
</li>
</ul>
</li>
<li><p>릴레이션 인스턴스(relation instance)</p>
<ul>
<li><p>어느 한 시점에 릴레이션에 존재하는 튜플들의 집합</p>
</li>
<li><p>릴레이션 외연(relation extension)이라고도 함</p>
</li>
<li><p>동적인 특징이 있음</p>
<p>→ 릴레이션 인스턴스의 모음 = 데이터베이스 인스턴스</p>
</li>
</ul>
</li>
</ul>
<h3 id="릴레이션-특성">릴레이션 특성</h3>
<ol>
<li><strong>튜플의 유일성</strong><ul>
<li>하나의 릴레이션에는 동일한 튜플이 존재할 수 없음</li>
</ul>
</li>
<li><strong>튜플의 무순서</strong><ul>
<li>하나의 릴레이션에서 튜플 사이의 순서는 무의미</li>
</ul>
</li>
<li><strong>속성의 무순서</strong><ul>
<li>하나의 릴레이션에서 속성의 순서는 무의미</li>
</ul>
</li>
<li><strong>속성의 원자성</strong><ul>
<li>속성 값으로 원자 값만 사용할 수 있음</li>
</ul>
</li>
</ol>
<h3 id="키key">키(key)</h3>
<ul>
<li>릴레이션에서 튜플들을 유일하게 구별하는 속성 또는 속성들의 집합</li>
<li>키의 특성<ul>
<li><strong>유일성(uniqueness)</strong> : 하나의 릴레이션에서 모든 튜플은 서로 다른 키 값을 가져야 함</li>
<li><strong>최소성(minimality)</strong> : 꼭 필요한 최소한의 속성들로만 키를 구성</li>
</ul>
</li>
<li>키의 종류<ul>
<li><strong>슈퍼 키(super key)</strong> : 유일성을 만족하는 속성 또는 속성들의 집합<ul>
<li>ex) 고객 릴레이션의 슈퍼키 : 고객 아이디, 고객 고유 번호, 고객 이름, … 등</li>
</ul>
</li>
<li><strong>후보 키(candidate key) :</strong> 유일성과 최소성을 만족하는 속성 또는 속성들의 집합<ul>
<li>ex) 고객 릴레이션의 슈퍼키 : 고객 아이디, 고객 고유 번호, … 등</li>
</ul>
</li>
<li><strong>기본 키(primary key) :</strong> 후보키 중에서 기본적으로 사용하기 위해 선택한 키<ul>
<li>ex) 고객 릴레이션의 슈퍼키 : 고객 아이디</li>
</ul>
</li>
<li><strong>대체 키(alternate key) :</strong> 기본키로 선택되지 못한 후보키<ul>
<li>ex) ex) 고객 릴레이션의 슈퍼키 : 고객 고유 번호</li>
</ul>
</li>
<li><strong>외래 키(foreign key) :</strong> 다른 릴레이션의 기본키를 참조하는 속성 또는 속성들의 집합 → 릴레이션들 간의 관계를 표현</li>
</ul>
</li>
</ul>
<h3 id="무결성-제약조건integrity-constraint">무결성 제약조건(integrity constraint)</h3>
<p>: 데이터의 무결성을 보장하고 일관된 상태로 유지하기 위한 규칙 (무결성 : 데이터를 결함이 없는 상태. 즉, 정확하고 유효하게 유지하는 것)</p>
<ul>
<li><strong>개체 무결성 제약조건(entity integrity constraint)</strong><ul>
<li>기본키를 구성하는 모든 속성은 널(null) 값을 가질 수 없음</li>
</ul>
</li>
<li><strong>참조 무결성 제약조건(referential integrity constraint)</strong><ul>
<li>외래키는 참조할 수 없는 값을 가질 수 없는 규칙</li>
</ul>
</li>
</ul>
<h1 id="관계-데이터-연산">관계 데이터 연산</h1>
<ul>
<li>관계 데이터 모델의 연산</li>
<li>원하는 데이터를 얻기 위해 릴레이션에 필요한 처리 요구를 수행하는 것</li>
<li>관계 대수 : 원하는 결과를 얻기 위해 데이터의 처리 과정을 순서대로 기술</li>
<li>관계 해석 : 원하는 결과를 얻기 위해 원하는 데이터가 무엇인지 기술</li>
</ul>
<h2 id="관계-대수relational-algebra의-개념">관계 대수(relational algebra)의 개념</h2>
<ul>
<li><p>절차 언어 : 원하는 결과를 얻기 위해 릴레이션의 처리 과정을 순서대로 기술하는 언어</p>
</li>
<li><p>폐쇄 특성(closure property) : 피연산도 릴레이션이고 연산의 결과도 릴레이션임</p>
</li>
<li><p>릴레이션 처리 연산자 : 일반 집합 연산자와 순수 관계 연산자로 분류</p>
<ul>
<li><p>집합 연산자</p>
<ul>
<li><p>합집합(union)</p>
</li>
<li><p>교집합(intersection)</p>
</li>
<li><p>차집합(difference)</p>
</li>
<li><p>카티션 프로덕트(cartesian product) :두 릴레이션의 조합 가능한 모든 경우의 수를 구하기 위한 집합 연산. 두 릴레이션에 속상 각 튜플들을 모두 연결하여 만들어진 새로운 튜플로 결과 릴레이션을 구성</p>
<p>  → 집합 연산자는 피연산자가 2개 필요함</p>
<p>  → 합집합, 교집합, 차집합은 두 릴레이션이 합병가능해야 함</p>
<p>  → 합병가능 조건 : 1. 두 릴레이션의 차수가 같아야 함  2. 두 릴레이션에서 서로 대응되는 속성의 도메인이 같아야 함</p>
</li>
</ul>
</li>
<li><p>관계 연산자</p>
<ul>
<li>셀렉트(select) : 릴레이션 $R$에서 조건을 만족하는 튜플들을 반환 (<code>릴레이션 where 조건식</code>)</li>
<li>프로젝트(project) : 릴레이션 $R$에서 주어진 속성들의 값으로만 구성된 튜플들을 반환 (<code>릴레이션[속성1, …]</code>)</li>
<li>조인(join) : 공통 속성을 이용해 릴레이션 $R$과 $S$의 튜플들을 연결하여 만들어진 새로운 튜플들을 반환</li>
<li>디비전(division)($R\div{S}$) : 릴레이션 $S$의 모든 튜플과 관련이 있는 릴레이션 $R$의 튜플들을 반환</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="join의-종류">JOIN의 종류</h3>
<ul>
<li><p>INNER JOIN</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/293a1bc1-71c8-40c6-9c2a-3561cf8f3ba7/image.png" alt=""></p>
<ul>
<li>내부 조인은 2개의 릴레이션의 컬럼 값을 결합함하는 것으로, 마치 교집합 연산과 같음(양쪾 데이터 집합에서 공통적으로 존재하는 데이터만 조인)</li>
<li>내부 조인은 기본 조인 형식으로 간주된다.</li>
</ul>
</li>
<li><p>LEFT OUTER JOIN</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/5e5c532d-7d63-48f3-9293-6c309d0da535/image.png" alt=""></p>
<ul>
<li>LEFT OUTER JOIN은 조인 키 컬럼 값이 양쪽 릴레이션에서 공통적으로 존재하는 데이터와, 왼쪽 릴레이션에 명시된 테이블에만 존재하는 데이터를 결과로 추출하게 된다.</li>
</ul>
</li>
<li><p>RIGHT OUTER JOIN</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/085ec2f6-346c-43bf-9cc5-7072c2ae21e1/image.png" alt=""></p>
<ul>
<li>RIGHT OUTER JOIN은 조인 키 컬럼 값이 양쪾 릴레이션에서 공통적으로 존재하는 데이터와, 오른쪽 릴레이션에 명시된 테이블에만 존재하는 데이터를 결과로 추출하게 된다.</li>
</ul>
</li>
<li><p>FULL OUTER JOIN</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/6f50cd73-9841-400a-a1c6-3b6fb1348c18/image.png" alt=""></p>
<ul>
<li>FULL OUTER JOIN은 조인 키 컬럼 값이 양쪽 릴레이션에서 공통적으로 존재하는 데이터와, 한 쪽 릴레이션에만 존재하는 데이터도 모두 결과 데이터 집합으로 추출하게 된다.</li>
</ul>
</li>
</ul>
<h2 id="관계-해석relational-calculus">관계 해석(relational calculus)</h2>
<ul>
<li>비절차 언어(nonprocedural language) : 처리를 원하는 데이터가 무엇인지만 기술하는 언어</li>
<li>수학의 프레디킷 해석(predicate calculus)에 기반을 두고 있음</li>
<li>분류<ul>
<li>튜플 관계 해석(tuple relational calculus)</li>
<li>도메인 관계 해석(domain relational calculus)</li>
</ul>
</li>
</ul>
<h1 id="sqlstructured-query-language-기본">SQL(Structured Query Language) 기본</h1>
<ul>
<li>관계형 데이터베이스의 조작과 관리에 사용되는 데이터베이스 질의용 언어</li>
<li>원하는 데이터가 무엇인지만 기술하는 비절차적 언어임</li>
</ul>
<h2 id="sql-분류">SQL 분류</h2>
<ul>
<li><strong>데이터 정의어(DDL)</strong> : 테이블을 생성하고 변경-제거하는 기능을 제공</li>
<li><strong>데이터 조작어(DML)</strong> : 테이블에 새 데이터를 삽입하거나, 테이블에 저장된 데이터를 수정-삭제-검색하는 기능을 제공</li>
<li><strong>데이터 제어어(DCL)</strong> : 보안을 위해 데이터에 대한 접근 및 사용 권한을 사용자별로 부여하거나 취소하는 기능을 제공</li>
</ul>
<h2 id="데이터-정의어ddl">데이터 정의어(DDL)</h2>
<h3 id="create-table">CREATE TABLE</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/cfaff3f2-7c78-49d3-84fe-2278854258cb/image.png" alt=""></p>
<ul>
<li>테이블을 생성할 때 사용됨</li>
<li>[ ]내용은 생략 가능</li>
<li>SQL 질의문은 세미콜론(;)으로 문장의 끝을 표시</li>
<li>SQL 질의문은 대소문자를 구분하지 않음(그</li>
<li>CREATE TABLE문은 기본 제약사항, 기본키, 대체키, 외래키, 데이터 무결성을 위한 제약조건 정의를 포함</li>
<li>속성 데이터 타입<ul>
<li>INT(INTEGER) : 정수</li>
<li>SMALLINT : INT보다 작은 정수</li>
<li>CHAR(n) or CHARACTER(n) : 길이가 n인 고정 길이의 문자열</li>
<li>VARCHAR(n) or CHARACTER VARYING(n) : 최대 길이가 n인 가변 길이의 문자열</li>
<li>NUMERIC(p, s) or DECIMAL(p, s) : 고정 소수점 실수. p는 소수점을 제외한 전체 숫자의 길이 / s는 소수점 이하 숫자의 길이</li>
<li>FLOAT(n) : 길이가 n인 부동 소수점 실수</li>
<li>REAL : 부동 소수점 실수</li>
<li>DATE : 연, 월, 일로 표현되는 날짜</li>
<li>TIME : 시, 분, 초로 표현되는 시간</li>
<li>DATETIME : 날짜와 시간</li>
</ul>
</li>
<li>키의 정의<ul>
<li>PRIMARY KEY : 기본키를 지정하는 키워드<ul>
<li>ex) PRIMARY KEY (고객아이디)</li>
</ul>
</li>
<li>UNIQUE<ul>
<li>대체키를 지정하는 키워드</li>
<li>대체키로 지정되는 속상의 값은 유일성을 가지며, 기본키와 달리 널(null) 값이 허용됨</li>
</ul>
</li>
<li>FOREIGN KEY<ul>
<li>외래키가 어떤 테이블의 무슨 속성을 참조하는지 REFERENCES 키워드 다음에 제시</li>
<li>참조 무결성 제약조건 유지를 위해 참조되는 테이블에서 튜플 삭제 시 처리방법을 지정하는 옵션도 있음<ul>
<li>ON DELETE NO ACTION : 튜플을 삭제하지 못하게 함</li>
<li>ON DELETE CASCADE : 관련 튜플을 함께 삭제</li>
<li>ON DELETE SET NULL : 관련 튜플의 외래키 값을 NULL로 변경</li>
<li>ON DELETE SET DEFAULT : 관련 튜플의 외래키 값을 기본 값을 ㅗ변경</li>
</ul>
</li>
<li>ex) FOREIGN KEY (소속부서) REFERENCES 부서 (부서번호) ON UPDATE CASCADE → 부서번호가 변경되면, 소속부서 외래키 값도 함께 변경됨</li>
</ul>
</li>
</ul>
</li>
<li>무결성 제약조건 정의(CHECK)<ul>
<li>테이블에 정확하고 유효한 데이터를 유지하기 위해 특정 속성에 대한 제약조건을 지정</li>
<li>CONSTRAINT 키워드와 함꼐 고유의 이름을 부여할 수도 있음</li>
<li>ex) CHECK (재고량 &gt;= 0)</li>
<li>ex) CONSTRAINT CHK_CPY CHECK(제조업체=’오뚜기’) → CHK_CPY라는 이름으로 제약조건을 생성한 것임</li>
</ul>
</li>
</ul>
<h3 id="alter-table">ALTER TABLE</h3>
<ul>
<li><p>새로운 속성 추가</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/10b2a896-7381-4edd-b8e6-1a8452ef0f58/image.png" alt=""></p>
</li>
<li><p>기존 속성 삭제</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/30adb40f-2dbd-4272-abf3-ae623cfb8d38/image.png" alt=""></p>
<ul>
<li>만약 삭제할 속성과 관련된 제약조건이 존재하면, 속성 삭제가 안 됨(관련된 제약조건을 먼저 삭제해야 함)</li>
</ul>
</li>
<li><p>새로운 제약조건 추가</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/dd048b7c-8337-4c7f-8542-dc9decc35696/image.png" alt=""></p>
</li>
<li><p>기존 제약조건 삭제</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/b16341d6-3499-46e0-b4c6-0e7c01b3dab3/image.png" alt=""></p>
</li>
</ul>
<h3 id="drop-table">DROP TABLE</h3>
<ul>
<li><p>테이블 삭제</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/f35444ed-ea9b-4fcd-844d-60219f4a13e7/image.png" alt=""></p>
<ul>
<li>만약 삭제할 테이블을 참조하는 테이블이 있다면, 테이블 삭제가 수행되지 않음(관련된 외래키 제약조건을 먼저 삭제해야 함)</li>
</ul>
</li>
</ul>
<h2 id="데이터-조작어dml"><strong>데이터 조작어(DML)</strong></h2>
<h3 id="select">SELECT</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/fec30575-cb81-4a73-8fb0-ad877eadea30/image.png" alt=""></p>
<ul>
<li><p>FROM 키워드와 함꼐 검색하고 싶은 속성이 있는 테이블의 이름을 나열</p>
</li>
<li><p>ALL : 결과 테이블이 튜플의 중복을 허용하도록 지정(생략 가능)</p>
</li>
<li><p>DISTINCT : 결과 테이블이 튜플의 중복을 허용하지 않도록 지정</p>
</li>
<li><p>AS 키워드를 이용해 결과 테이블에서 속성의 이름을 바꾸어 출력 가능</p>
<ul>
<li>새로운 이름에 공백에 포함되면 큰따옴표나 작은따옴표로 묶어주어야 함</li>
<li>생략 가능</li>
</ul>
</li>
<li><p>SELECT는 산술식을 이용한 검색이 가능</p>
<ul>
<li>SELECT 키워드와 함꼐 산술식 제시</li>
<li>속성의 값이 실제로 변경되는 것은 아니고, 결과 테이블에서만 계산된 값이 출력됨</li>
</ul>
</li>
<li><p>SELECT는 조건을 만족하는 데이터만 검색이 가능</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/da103072-a765-460c-8541-43922da84070/image.png" alt=""></p>
<ul>
<li>WHERE 키워드와 함께 비교 연산자와 논리 연산자를 이용한 검색 조건 제시</li>
<li>숫자뿐 아니라 문자나 날짜 값을 비교하는 것도 가능(문자나 날짜는 작은 따옴표로 묶어서 표현)</li>
</ul>
</li>
<li><p>LIKE 키워드를 이용해 부분적으로 일치하는 데이터를 검색할 수 있음</p>
<ul>
<li>% 기호 : 문자의 내용과 개수는 상관 없음</li>
<li>_ 기호 : 문자의 내용은 상관 없음</li>
<li>ex)<ul>
<li>LIKE ‘data%’ : data로 시작하는 문자열(길이 상관 x)</li>
<li>LIKE ‘%data’ : data로 끝나는 문자열(길이 상관 x)</li>
<li>LIKE’%data%’ : data가 포함된 문자열(길이 상관 x)</li>
<li>LIKE’data____’ : data로 시작하는 8자리 문자열</li>
<li>LIKE’____data’ : data로 끝나는 9자리 문자열</li>
</ul>
</li>
</ul>
</li>
<li><p>IS NULL 혹은 IS NOT NULL 키워드를 통해 널 값을 비교할 수 있음</p>
<ul>
<li>널 값은 다른 값과 크기를 비교할 순 없음</li>
</ul>
</li>
<li><p>정렬 검색</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/f5fda744-2faa-41de-88b0-7e2d8ead45e4/image.png" alt=""></p>
<ul>
<li>ORDER BY 키워드를 이용해 결과 테이블 내용을 사용자가 원하는 순서로 출력할 수 있음</li>
<li>오름차순(기본) : ASC / 내림차순 : DESC</li>
</ul>
</li>
<li><p>집계 함수를 이용하여 검색을 할 수 있음(COUNT, MAX, MIN, SUM, AVG)</p>
<ul>
<li>집계함수는 WHERE절에서는 사용할 수 없고, SELECT 절이나 HAVING 절에서만 사용 가능</li>
</ul>
</li>
<li><p>그룹별 검색</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/b698fdcf-931e-4d14-8c0b-7d2abe997f79/image.png" alt=""></p>
<ul>
<li>GROUP BY 키워드를 이용해 특정 속성의 값이 같은 튜플을 모아 그룹을 만들고, 그룹별로 검색</li>
<li>GROUP BY 키워드로 그룹을 나누는 기준이 되는 속성을 지정</li>
<li>HAVING 키워드로 그룹에 대한 조건을 작성</li>
</ul>
</li>
<li><p>여러 테이블에 대한 조인 검색</p>
<ul>
<li>조인 속성 : 조인 검색을 위해 테이블을 연결해주는 속성<ul>
<li>연결하려는 테이블 간 조인 속성의 이름은 달라도 되지만, 도메인은 같아야 함</li>
<li>일반적으로 외래키를 조인 속성으로 이용함</li>
</ul>
</li>
<li>FROM절에 필요한 모든 테이블을 나열</li>
<li>WHERE절에 조인 속성의 값이 같아야 함을 의미하는 조인 조건을 제시</li>
</ul>
</li>
<li><p>SELECT문 안에 또 다른 SELECT문을 포함하는 부속 질의문을 이용할 수 있음</p>
<ul>
<li>상위(주) 질의문 : 다른 SELECT문을 포함하는 SELECT문</li>
<li>부속(서브) 질의문 : 다른 SELECT문 안에 들어 있는 SELECT문<ul>
<li>부속 질의문은 괄호로 묶어서 작성되며 ORDER BY절을 사용할 수 없음</li>
</ul>
</li>
<li>부속 질의문을 먼저 수행하고, 그 결과를 이용해 상위 질의문을 수행</li>
<li>부속질의문과 상의 질의문을 연결하는 연산자가 필요<ul>
<li>IN : 부속 질의문의 결과 값 중 일치하는 것이 있으면 검색 조건이 참</li>
<li>NOT IN : 부속 질의문의 결과 값 중 일치하는 것이 없으면 검색 조건이 참</li>
<li>EXISTS : 부속 질의문 결과 값이 하나라도 존재하면 검색 조건이 참</li>
<li>NOT EXISTS : 하나라도 존재하지 않으면 검색 조건이 참</li>
<li>ALL : 부속 질의문의 결과 값 모두와 비교한 결과가 참이면 검색 조건 만족(비교 연산자와 함께 사용)</li>
<li>ANY 또는 SOME : 부속 질의문의 결과 값 중 하나라도 비교한 결과가 참이면 검색 조건 만족(비교 연산자와 함께 사용)</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="insert">INSERT</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/fbc9d2c7-c101-4e32-82c6-5759b0be5676/image.png" alt=""></p>
<ul>
<li><p>INTO 키워드와 함께 튜플을 삽입할 테이블의 이름과 속성의 이름을 나열</p>
<ul>
<li>속성 리스트를 생략하면 테이블을 정의할 때 지정한 속성의 순서대로 값이 삽입됨</li>
<li>그러나, 웬만하면 속성 리스트를 지정해주는게 안전함</li>
</ul>
</li>
<li><p>VALUES 키워드와 함께 삽입할 속성 값들을 나열</p>
<ul>
<li>INTO절의 속성 이름과 VALUES절의 속성 값은 순서대로 일대일 대응되어야 함</li>
</ul>
</li>
<li><p>SELECT문을 이용해 다른 테이블에서 검색한 데이터를 삽입할 수도 있음</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/1a5d983e-3270-43af-8582-347db88b089a/image.png" alt=""></p>
</li>
</ul>
<h3 id="update">UPDATE</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/c5fa13c3-e1ca-4595-a578-66cf97caa378/image.png" alt=""></p>
<ul>
<li>테이블에 저장된 튜플에서 특정 속성의 값을 수정할 때 사용</li>
<li>SET 키워드 다음에 속성 값을 어떻게 수정할 것인지를 지정</li>
<li>WHERE절에 제시된 조건을 만족하는 튜플에 대해서만 속성 값을 수정(❗️생략하면 모든 튜플을 대상으로 수정)</li>
</ul>
<h3 id="delete">DELETE</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/435c408d-e222-4752-b1dc-63c98fdb9a10/image.png" alt=""></p>
<ul>
<li>테이블에 지정된 데이터를 삭제할 때 사용</li>
<li>WHERE 절에 제시한 조건을 만족하는 튜플만 삭제(❗️생략하면 모든 튜플을 삭제해 빈 테이블이 됨)</li>
</ul>
<h2 id="뷰view"><strong>뷰(VIEW)</strong></h2>
<ul>
<li>뷰는 사용자에게 접근이 허용된 자료만을 제한적으로 보여주기 위해 하나 이상의 기본 테이블로부터 유도된, 이름을 가지는 가상 테이블임.</li>
<li>데이터를 실제로 저장하지 않고 논리적으로만 존재하는 테이블이지만, 일반 테이블과 동일한 방법으로 사용함</li>
<li>다른 뷰를 기반으로 새로운 뷰를 만드는 것도 가능함</li>
<li>뷰를 통해 기본 테이블의 내용을 쉽게 검색할 수는 있지만, 기본 테이블의 내용을 변화시키는 작업은 제한적으로 이루어짐<ul>
<li>기본 테이블 : 뷰를 만드는 데 기반이 되는 물리적인 테이블</li>
</ul>
</li>
<li>뷰의 장점<ul>
<li>질의문을 좀 더 쉽게 작성 가능 → 미리 특정 뷰를 만들어놓고, 간단히 검색이 가능</li>
<li>데이터 보안 유지에 도움이 됨(제한된 자료만을 보여줌)</li>
<li>데이터를 좀 더 편리하게 관리 가능 → 제공된 뷰와 관련이 없는 다른 내용에 대해 사용자가 신경 쓸 필요가 없음</li>
</ul>
</li>
</ul>
<h3 id="create-view">CREATE VIEW</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/68ce3d5d-07d4-4059-a33f-ef8cbf9deeef/image.png" alt=""></p>
<ul>
<li>뷰 생성</li>
<li>CREATE VIEW 키워드와 함께 생성할 뷰의 이름과 뷰를 구성하는 속성의 이름을 나열</li>
<li>AS 키워드와 함께 기본 테이블에 대한 SELECT문 작성</li>
<li>WITH CHECK OPTION : 뷰에 삽입이나 수정 연산을 할 떄 SELECT문에서 제시한 뷰의 정의 조건을 위반하면 수행되지 않도록 하는 제약조건을 지정</li>
</ul>
<h3 id="select-view">SELECT VIEW</h3>
<ul>
<li>SELECT VIEW로는 일반 테이블과 같은 방법으로 원하는 데이터를 검색할 수 있음</li>
<li>뷰에 대한 SELECT문이 내부적으로 기본 테이블에 대한 SELECT문으로 변환되어 수행함</li>
</ul>
<h3 id="insert-update-delete-view">INSERT, UPDATE, DELETE VIEW</h3>
<ul>
<li>뷰에 대한 삽입-수정-삭제 연산은 실제로 기본 테이블에 수행되므로 결과적으로 기본 테이블이 변경됨<ul>
<li>따라서 뷰에 대한 삽입-수정-삭제 연산은 제한적으로 수행됨(변경 불가능한 뷰)</li>
</ul>
</li>
<li>변경 불가능한 뷰의 특징<ul>
<li>기본 테이블의 기본키를 구성하는 속성이 포함되어 있지 않은 뷰</li>
<li>기본 테이블에 있던 내용이 아닌 집계 함수로 새로 계산된 내용을 포함하는 뷰</li>
<li>DISTICNT 키워드를 포함하여 정의한 뷰</li>
<li>GROUP BY 절을 포함하여 정의한 뷰</li>
<li>여러 개의 테이블을 조인하여 정의한 뷰는 변경이 불가능한 경우가 많음</li>
</ul>
</li>
</ul>
<h3 id="drop-view">DROP VIEW</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/2af06b96-1d29-4fc6-828e-87ac3a82fcec/image.png" alt=""></p>
<ul>
<li>뷰를 삭제해도 기본 테이블은 영향을 받지 않음</li>
<li>만약, 삭제할 뷰를 참조하는 제약 조건이 존재하면, 뷰 삭제가 수행되지 않음(제약 조건을 먼저 삭제해야 함)</li>
</ul>
<h1 id="데이터베이스-발전과-종류">데이터베이스 발전과 종류</h1>
<h2 id="1세대--계층-dbms-네트워크-dbms">1세대 : 계층 DBMS, 네트워크 DBMS</h2>
<ul>
<li><p>계층 DBMS : 데이터베이스를 트리형태로 구성한 것</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/56674341-3caa-48c2-be87-8dfa7a7e1824/image.png" alt=""></p>
<ul>
<li>ex) IMS(Information Management System)</li>
</ul>
</li>
<li><p>네트워크 DBMS : 데이터베이스를 그래프 형태로 구성한 것</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/385a366f-401f-4114-8a01-405ddddcb634/image.png" alt=""></p>
<ul>
<li>ex) IDS(Intergrated Data Store)</li>
</ul>
</li>
</ul>
<h2 id="2세대--관계-dbms">2세대 : 관계 DBMS</h2>
<ul>
<li>관계 DBMS : 데이터베이스를 테이블 형태로 구성</li>
<li>ex) 오라클, MySQL, 액세스(Access), ….</li>
</ul>
<h2 id="3세대--객체-지향-dbms-객체-관계-dbms">3세대 : 객체 지향 DBMS, 객체 관계 DBMS</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/4f296404-3863-4012-8158-0a54e911d59c/image.png" alt=""></p>
<ul>
<li>객체지향 DBMS : 객체를 이용해 데이터베이스를 구성<ul>
<li>ex) 오투(O2), 젬스톤(GemStone), …</li>
</ul>
</li>
<li>객체관계 DBMS ; 객체 DBMS + 관계 DBMS</li>
</ul>
<h2 id="4세대--nosql">4세대 : NoSQL</h2>
<ul>
<li>NoSQL : 비정형 데이터를 처리하는 데 적합하고 확장성이 뛰어난 데이터베이스<ul>
<li>안정성과 일관성 유지를 위한 복잡한 기능을 포기</li>
<li>데이터 구조를 미리 정해두지 않는 유연성</li>
<li>확장성이 뛰어나 여러 대의 서버 컴퓨터에 데이터를 분산하여 저장하고 처리하는 환경에서 주로 사용</li>
<li>ex) MongoDB, HBase, Cassandra, Redis, ….</li>
</ul>
</li>
<li>배경 및 장점<ul>
<li>관계 데이터베이스를 대신할 새로운 대안의 필요성</li>
<li>정형화된 데이터를 주로 처리하는 관계 데이터베이스는 빠른 속도로 대량 생산되는 다양한 유형의 비정형 데이터를 저장 및 관리하는데 적합하지 않음</li>
<li>단일 컴퓨터 환경에서 주로 사용되는 관계 데이터베이스는 확장성 측면에서 비효율적임</li>
<li>빠른 속도로 생성되는 대량의 비정형 데이터를 저장하고 처리하기 위해 ACID(원자성, 일관성, 격지성, 지속성)를 위한 트랜잭션 기능을 제공하지 않는 대신 저렴한 비용으로 여러 대의 컴퓨터에 데이터를 분산-처리-저장하는 것이 가능한 데이터베이스</li>
<li>스키마 없이 동작하기 떄문에 데이터 구조를 미리 정의할 필요가 없고 수시로 그 구조를 바꿀 수 있어 비정형 데이터를 저장하기에 적합</li>
<li>대부분 오픈 소스로 제공</li>
</ul>
</li>
</ul>
<h3 id="rdbms-vs-nosql">RDBMS vs NoSQL</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>관계 데이터베이스</th>
<th>NoSQL</th>
</tr>
</thead>
<tbody><tr>
<td>처리 데이터</td>
<td>정형 데이터</td>
<td>정형 데이터, 반정형 데이터, 비정형 데이터</td>
</tr>
<tr>
<td>대용량 데이터</td>
<td>대용량 처리 시 성능 저하</td>
<td>대용량 데이터 처리 지원</td>
</tr>
<tr>
<td>스키마</td>
<td>미리 정해진 스키마 존재</td>
<td>스키마가 없거나 변경이 자유로움</td>
</tr>
<tr>
<td>트랜잭션</td>
<td>트랜잭션을 통해 일관성 유지를 보장</td>
<td>트랜잭션을 지원하지 않아 일관성 유지를 보장하기 어려움</td>
</tr>
<tr>
<td>검색 기능</td>
<td>조인 등의 복잡한 검색 기능 제공</td>
<td>단순한 데이터 검색 기능 제공</td>
</tr>
<tr>
<td>확장성</td>
<td>클러스터 환경에 적합하지 않음</td>
<td>클러스터 환경에 적합</td>
</tr>
<tr>
<td>라이선스</td>
<td>고가의 라이선스 비용</td>
<td>오픈 소스</td>
</tr>
<tr>
<td>예시</td>
<td>Oracle, MySQL, MS SQL 서버, …</td>
<td>Cassandra, MongoDB, HBase, …</td>
</tr>
</tbody></table>
<h3 id="nosql-종류">NoSQL 종류</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/12e9ca4b-f002-4bae-b12f-3a2b2ae2c740/image.png" alt=""></p>
<ul>
<li><strong>key-value store(kvs)</strong> : 다수의 키와 값을 관련지어 저장하는 데이터베이스<ul>
<li>ex) Riak, Redis, …</li>
</ul>
</li>
<li><strong>document store</strong> : JSON과 같은 복잡한 데이터 구조를 저장하는 데이터베이스<ul>
<li>ex) MongoDB, CouchDB, …</li>
</ul>
</li>
<li><strong>wide-column store</strong> : 여러 키를 사용하여 높은 확장성을 제공하는 데이터베이스<ul>
<li>ex) Cassandra, …</li>
</ul>
</li>
<li><strong>graph-based store</strong> : 노드에 데이터를 저장하고 간선으로 데이터 간 관계를 표현하는 그래프 형태<ul>
<li>ex) Neo4J, OrientDB, …</li>
</ul>
</li>
</ul>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PL7ZVZgsnLwEEMDG02R-ThBc1cDTdT97z6">https://www.youtube.com/playlist?list=PL7ZVZgsnLwEEMDG02R-ThBc1cDTdT97z6</a></li>
<li><a href="https://itwiki.kr/w/ER_%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8">https://itwiki.kr/w/ER_다이어그램</a></li>
<li><a href="https://sparkdia.tistory.com/17">https://sparkdia.tistory.com/17</a></li>
<li><a href="https://learn.microsoft.com/en-us/dotnet/architecture/cloud-native/relational-vs-nosql-data">https://learn.microsoft.com/en-us/dotnet/architecture/cloud-native/relational-vs-nosql-data</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Interview for AI Engineer : Computer Science]]></title>
            <link>https://velog.io/@whattsup_kim/cs-review-for-al-enginner</link>
            <guid>https://velog.io/@whattsup_kim/cs-review-for-al-enginner</guid>
            <pubDate>Mon, 09 Jan 2023 09:29:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>AI Engineer로서 알아야 할 기본적인 CS지식들을 스스로 간단히(직관적으로) 리뷰하는 공간입니다.</p>
</blockquote>
<h1 id="operation-system">Operation System</h1>
<h3 id="프로그램--프로세스--스레드"><a href="https://habitual-pint-c5d.notion.site/vs-d7842923aa194dae8f61b663c3f86e7f">프로그램 &amp; 프로세스 &amp; 스레드</a></h3>
<h3 id="멀티-프로세스--멀티-스레드"><a href="https://habitual-pint-c5d.notion.site/Multi-Process-Multi-Thread-3ed55beb28a24a7489a3f12933c625a0">멀티 프로세스 &amp; 멀티 스레드</a></h3>
<h3 id="캐시-메모리"><a href="https://www.notion.so/f3e5f1891c044c56a21a078c9adab8a1">캐시 메모리</a></h3>
<h3 id="thread-safe"><a href="https://habitual-pint-c5d.notion.site/Thread-safe-ee6c366dd61b44e29b3bc9fe6646f71c">Thread-safe</a></h3>
<h3 id="스케줄러단기중기장기"><a href="https://habitual-pint-c5d.notion.site/27c84084334e44d1b615571a6d61ce9a">스케줄러(단기/중기/장기)</a></h3>
<h3 id="스케줄러스케줄러-종류"><a href="https://habitual-pint-c5d.notion.site/cpu-8e2a00443ebc4bb9a4bec8eae74bc3d3">스케줄러(스케줄러 종류)</a></h3>
<h3 id="동기--비동기"><a href="https://habitual-pint-c5d.notion.site/vs-4d42029255f34f4d8811435cfca11fdd">동기 &amp; 비동기</a></h3>
<h3 id="메모리-관리-전략"><a href="https://habitual-pint-c5d.notion.site/c9ae503891db419cb7dfa3a8cac8a75e">메모리 관리 전략</a></h3>
<h3 id="교착상태"><a href="https://habitual-pint-c5d.notion.site/DeadLock-ecba22d3b72c4982a49184a1e4de9808">교착상태</a></h3>
<h3 id="스레드-종류사용자-수준-커널-수준-혼합형"><a href="https://habitual-pint-c5d.notion.site/ffadd615bb6b4198b9cbd2f10fc02fc5">스레드 종류(사용자 수준, 커널 수준, 혼합형)</a></h3>
<h3 id="context-switchingfeat-pcb"><a href="https://habitual-pint-c5d.notion.site/Context-Switching-bf9643955651416995eb0bfa680f13c2">Context Switching(feat. PCB)</a></h3>
<h1 id="network">Network</h1>
<h3 id="tcpip-모델과-osi-모델"><a href="https://habitual-pint-c5d.notion.site/TCP-IP-OSI-46796ae7cd0b4ca0b2a99f8d2a2a3d51">TCP/IP 모델과 OSI 모델</a></h3>
<h3 id="패킷과-데이터-캡슐화"><a href="https://habitual-pint-c5d.notion.site/Packet-Frame-Packet-datagram-Segment-0469b6bdffc1470d841baa62848de80e">패킷과 데이터 캡슐화</a></h3>
<h3 id="라우터-스위치-허브"><a href="https://habitual-pint-c5d.notion.site/2121ef932efc4a3ab550a20fa2871317">라우터, 스위치, 허브</a></h3>
<h3 id="mac주소--이더넷-프로토콜"><a href="https://habitual-pint-c5d.notion.site/MAC-ethernet-3dcb40f968c145dabee662a3b2feba01">Mac주소 &amp; 이더넷 프로토콜</a></h3>
<h3 id="arp-프로토콜"><a href="https://habitual-pint-c5d.notion.site/ARP-fbffc9c9d87e44d7b0869e209835a785">ARP 프로토콜</a></h3>
<h3 id="ip-프로토콜"><a href="https://habitual-pint-c5d.notion.site/IP-IPv4-IPv6-7ad2e6e411c74538ba13ae87f961a480">IP 프로토콜</a></h3>
<h3 id="icmp-프로토콜"><a href="https://habitual-pint-c5d.notion.site/ICMP-d6c00a449c6a459a84502fcb3c3757f2">ICMP 프로토콜</a></h3>
<h3 id="사설-ip와-서브넷팅"><a href="https://habitual-pint-c5d.notion.site/IP-63ce3c66140e43a099f7a02d48508790">사설 IP와 서브넷팅</a></h3>
<h3 id="nat와-포트포워딩"><a href="https://habitual-pint-c5d.notion.site/NAT-33702e02ff9c420890e547768b69f145">NAT와 포트포워딩</a></h3>
<h3 id="소켓과-포트"><a href="https://habitual-pint-c5d.notion.site/Socket-Port-8f1ff2ae80794a37bfb2d55eeb51e994">소켓과 포트</a></h3>
<h3 id="tcp-vs-udp"><a href="https://habitual-pint-c5d.notion.site/TCP-vs-UDP-12362d5475d24ef9a68b1de8a386f991">TCP vs UDP</a></h3>
<h3 id="tcp의-3way-handshake와-4way-handshake"><a href="https://habitual-pint-c5d.notion.site/TCP-3-way-handshake-4-way-handshake-a3874dcfae56421f907d7bb54a27572f">TCP의 3way-handshake와 4way-handshake</a></h3>
<h3 id="http와-https-프로토콜"><a href="https://habitual-pint-c5d.notion.site/HTTP-HTTPS-a41d3a1cc6c24306928bad04e8e099a4">HTTP와 HTTPS 프로토콜</a></h3>
<h3 id="http와-https-통신과정"><a href="https://habitual-pint-c5d.notion.site/HTTP-HTTPS-2454a1b2121744459985c81951734d0c">HTTP와 HTTPS 통신과정</a></h3>
<h3 id="httpget-vs-post"><a href="https://habitual-pint-c5d.notion.site/HTTP-GET-POST-38e8fcf2413d44a9ad9efc5e9d04fce1">HTTP(GET vs POST)</a></h3>
<h3 id="쿠키--세션"><a href="https://habitual-pint-c5d.notion.site/410f9a93802445528a0980faebd21f5b">쿠키 &amp; 세션</a></h3>
<h3 id="dns"><a href="https://habitual-pint-c5d.notion.site/DNS-cda26769742140cdbb1700da8f86d3d1">DNS</a></h3>
<h3 id="rest--rest-api"><a href="https://habitual-pint-c5d.notion.site/Rest-Rest-API-811a830926094b5cb2c023dd5d217a2f">Rest &amp; Rest API</a></h3>
<h3 id="브라우저에-wwwgooglecom을-치면-일어나는-일"><a href="https://habitual-pint-c5d.notion.site/www-google-com-7ecb822c3c884a4e87f8d838f1656099">브라우저에 &#39;www.google.com&#39;을 치면 일어나는 일</a></h3>
<h1 id="database">DataBase</h1>
<h3 id="데이터베이스-풀"><a href="https://habitual-pint-c5d.notion.site/0d215f0cb3354b9796b96e67cb116fbe">데이터베이스 풀</a></h3>
<h3 id="정규화1차-2차-3차-bncf"><a href="https://habitual-pint-c5d.notion.site/1-2-3-BNCF-c7cae7ec94f04796b476d6e820c77bf5">정규화(1차, 2차, 3차, BNCF)</a></h3>
<h3 id="트랜잭션이란"><a href="https://habitual-pint-c5d.notion.site/7c7a79772e264053a4055753eecd4433">트랜잭션이란</a></h3>
<h3 id="join"><a href="https://habitual-pint-c5d.notion.site/Join-03027684cf084c1ead6c7220bdcb4fbf">JOIN</a></h3>
<h3 id="index"><a href="https://habitual-pint-c5d.notion.site/Index-f0dfd3abda704420a2389a2b1414f724">Index</a></h3>
<h3 id="sql-vs-nosql"><a href="https://habitual-pint-c5d.notion.site/SQL-vs-NoSQL-80f30fa3b6c341cdba21c8ac903a199f">SQL vs NoSQL</a></h3>
<h3 id="분산-dbclustering-replication-sharding"><a href="https://habitual-pint-c5d.notion.site/DB-Clustering-Replication-Sharding-2fd7b249b24a4577a84ddccd09c90add">분산 DB(Clustering, Replication, Sharding)</a></h3>
<hr>
<h4 id="reference">Reference</h4>
<ul>
<li><a href="https://boostdevs.gitbook.io/ai-tech-interview/">https://boostdevs.gitbook.io/ai-tech-interview/</a></li>
<li><a href="https://github.com/WeareSoft/tech-interview#5-design-pattern">https://github.com/WeareSoft/tech-interview#5-design-pattern</a></li>
<li><a href="https://theheydaze.tistory.com/603">https://theheydaze.tistory.com/603</a></li>
<li><a href="https://github.com/gyoogle/tech-interview-for-developer/tree/master/Web">https://github.com/gyoogle/tech-interview-for-developer/tree/master/Web</a></li>
<li><a href="https://www.fun-coding.org/DS&amp;AL1-2.html">https://www.fun-coding.org/DS&amp;AL1-2.html</a></li>
<li><a href="https://ddarahakit.tistory.com/?page=2">https://ddarahakit.tistory.com/?page=2</a></li>
<li><a href="https://www.fun-coding.org/">https://www.fun-coding.org/</a></li>
<li><a href="https://ddarahakit.tistory.com/?page=2">https://ddarahakit.tistory.com/?page=2</a></li>
<li><a href="https://jiyoungtt.tistory.com/47">https://jiyoungtt.tistory.com/47</a></li>
<li><a href="https://buddev.tistory.com/71">https://buddev.tistory.com/71</a></li>
<li><a href="https://hoyeonkim795.github.io/posts/normalization-course/">https://hoyeonkim795.github.io/posts/normalization-course/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[빅데이터를 지탱하는 기술] CH01. 빅데이터 기초 지식]]></title>
            <link>https://velog.io/@whattsup_kim/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@whattsup_kim/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Thu, 29 Dec 2022 13:56:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 글은 <a href="http://www.yes24.com/Product/Goods/66277191">빅데이터를 지탱하는 기술</a>을 읽고 정리한 내용입니다.</p>
</blockquote>
<h1 id="1-1-빅데이터의-정착">1-1 빅데이터의 정착</h1>
<h2 id="분산-시스템에-의한-데이터-처리의-고속화">분산 시스템에 의한 데이터 처리의 고속화</h2>
<h3 id="데이터의-취급이-어려웠던-이유">데이터의 취급이 어려웠던 이유</h3>
<ul>
<li>데이터의 분석 방법을 모름 (데이터로부터 가치를 찾을 수 없음)</li>
<li>데이터 처리에 수고와 시간이 걸림 (데이터 처리에 비용이 많이 듦)</li>
</ul>
<h3 id="빅데이터-기술-등장---hadoop과-nosql">빅데이터 기술 등장 - Hadoop과 NoSQL</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/0b7819d0-03a3-4378-a1af-33d3ddd940be/image.png" alt=""></p>
<ul>
<li><strong>Hadoop : 다수의 컴퓨터에서 대량의 데이터를 처리하기 위한 시스템</strong><ul>
<li>원래 구글에서 개발된 분산 처리 프레임워크인 ‘MapReduce’를 참고하여 제작됨</li>
<li>초기에는 Hadoop에서 MapReduce를 동작시키기 위해 Java로 프로그래밍을 해야했음(누구나 간단히 사용하지 못함). 따라서 SQL과 같은 쿼리를 Hadoop에서 실행하기 위한 소프트웨어로 Hive가 개발되어 2009년에 출시됨<ul>
<li>Hive를 이용하여 프로그래밍 없이 데이터를 집계할 수 있게 됨</li>
</ul>
</li>
</ul>
</li>
<li><strong>NoSQL 데이터베이스 : 빈번한 읽기/쓰기 및 분산 처리가 강점</strong><ul>
<li>NoSQL이란 : 전통적인 RDB의 제약을 제거하는 것을 목표로 한 데이터베이스의 총칭</li>
<li>기존의 RDB 대비 고속의 읽기, 쓰기가 가능하며, 분산 처리에 뛰어남</li>
<li>NoSQL 데이터베이스의 종류<ul>
<li><strong>key-value store(kvs)</strong> : 다수의 키와 값을 관련지어 저장하는 데이터베이스<ul>
<li>ex) Riak, Redis, …</li>
</ul>
</li>
<li><strong>document store</strong> : JSON과 같은 복잡한 데이터 구조를 저장하는 데이터베이스<ul>
<li>ex) MongoDB, CouchDB, …</li>
</ul>
</li>
<li><strong>wide-column store</strong> : 여러 키를 사용하여 높은 확장성을 제공하는 데이터베이스<ul>
<li>ex) Cassandra, …</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>Hadoop + NoSQL : 현실적인 비용으로 대규모 데이터 처리 실현</strong><ul>
<li>NoSQL에 기록 &amp; Hadoop으로 분산 처리</li>
</ul>
</li>
</ul>
<h2 id="분산-시스템의-비즈니스-이용-개척">분산 시스템의 비즈니스 이용 개척</h2>
<ul>
<li><p>일부 기업에서는 이전부터 데이터 분석을 기반으로 하는 엔터프라이즈 <strong>데이터 웨어하우스</strong>를 도입하여 운용했음.</p>
<ul>
<li>대량의 데이터를 축적하고, 분석함으로써 업무 개선과 경영 판단의 근거로 활용함</li>
</ul>
</li>
<li><p>분산 시스템의 발전에 따라, 기존에 데이터 웨어하우스가 사용되는 경우(case)에도 Hadoop(or Hive)을 사용하는 경우가 증가함 → “빅데이터” 키워드 대두</p>
<ul>
<li><p>비교적 작은 데이터 또는 중요한 데이터는 데이터 웨어하우스를 사용하고, 확장성이 필요한 업무 등에는 Hadoop을 사용하는 등 데이터 웨어하우스의 부하를 줄임</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/bba5d249-4b40-44ca-b9ae-610c8deb83be/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p>빅데이터 기술이 기존의 데이터 웨어하우스와 다른 점은 다수의 분산 시스템을 조합하여 확장성이 뛰어난 데이터 처리 구조를 만든다는 점임</p>
</li>
</ul>
<h2 id="직접-할-수-있는-데이터-분석-폭-확대">직접 할 수 있는 데이터 분석 폭 확대</h2>
<ul>
<li>비슷한 시기부터 클라우드 서비스에 의한 빅데이터 활용이 증가함<ul>
<li>“여러 컴퓨터를 분산”하는 환경과 클라우드는 잘 맞는 한 쌍임</li>
<li>클라우드 서비스 예시<ul>
<li>Amazon Elastic MapReduce (클라우드를 위한 Hadoop)</li>
<li>구글 BigQuery (데이터 웨어하우스)</li>
<li>Azure HDInsight (클라우드를 위한 Hadoop)</li>
<li>Amazon Redshift (데이터 웨어하우스)</li>
</ul>
</li>
</ul>
</li>
<li>비슷한 시기 <strong>데이터 디스커버리(BI 도구)</strong> 등장<ul>
<li>데이터 디스커버리 : 데이터 웨어하우스에 저장된 데이터를 대화형으로 시각화하여 가치있는 정보를 찾고자 하는 프로세스</li>
<li>BI(Business Intelligence) : 이전부터 데이터 웨어하우스와 조합되어 사용되던 경영자용 시각화 시스템</li>
</ul>
</li>
</ul>
<h1 id="1-2-빅데이터-시대의-데이터-분석-기반">1-2 빅데이터 시대의 데이터 분석 기반</h1>
<h2 id="빅데이터-기술---서브-시스템들">빅데이터 기술 - 서브 시스템들</h2>
<h3 id="데이터-파이프라인---데이터-수집부터-워크플로-관리까지">데이터 파이프라인 - 데이터 수집부터 워크플로 관리까지</h3>
<ul>
<li>데이터 파이프라인 : 데이터가 흘러가는 시스템</li>
<li>파이프라인에 하고싶은 일이 증가됨에 따라 시스템은 점차 복잡해지고, 그것을 어떻게 조합시킬지가 문제가 됨</li>
</ul>
<h3 id="데이터-수집---벌크-형과-스트리밍-형의-데이터-전송">데이터 수집 - 벌크 형과 스트리밍 형의 데이터 전송</h3>
<ul>
<li><p>데이터 파이프라인은 데이터를 모으는 부분부터 시작됨</p>
</li>
<li><p>데이터는 여러 장소에서 발생하고 각기 다른 형태를 보일 수 있음</p>
</li>
<li><p>데이터 전송(data transfer)의 방법은 크게 두 가지가 있음</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/85581b12-f6b7-4698-b388-6bd98814751b/image.png" alt=""></p>
<ul>
<li><strong>벌크(bulk) 형</strong> : 어딘가에 존재하는 데이터를 정리해 추출하는 방법<ul>
<li>데이터베이스와 파일 서버 등에서 정기적으로 데이터를 수집하는 데 사용됨.</li>
</ul>
</li>
<li><strong>스트리밍(streaming) 형</strong> : 차례차례로 생성되는 데이터를 끊임없이 계속해서 보내는 방법.<ul>
<li>모바일 앱, 임베디드 기계 등에서 발생하는 데이터를 수집하는 데 사용됨</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="스트림stream-처리와-배치batch-처리">스트림(stream) 처리와 배치(batch) 처리</h3>
<ul>
<li><strong>스트림 프로세싱</strong> : 스트림 처리는 데이터가 생성되는 즉시 <strong>실시간</strong>으로 데이터를 처리하는 것을 의미<ul>
<li>ex) 고객이 이전에 구매한 내역을 즉시 반영하여 상품 추천</li>
</ul>
</li>
<li><strong>배치 프로세싱</strong> : 배치 처리는 특정 시간 범위 내에서 <strong>대량의 데이터</strong>를 일괄 처리하는 것을 말한다<ul>
<li>ex) 하루에 한 번씩 고객들의 구매 내역을 가져와서 ‘요즘 트렌드’ 상품을 추천한다.</li>
</ul>
</li>
</ul>
<h3 id="분산-스토리지---객체-스토리지-nosql-데이터베이스">분산 스토리지 - 객체 스토리지, NoSQL 데이터베이스</h3>
<ul>
<li>수집된 데이터는 분산 스토리지(distribute storage)에 저장됨<ul>
<li><strong>분산 스토리지(distribute storage)</strong> : 여러 컴퓨터와 디스크로부터 구성된 스토리지 시스템</li>
<li>분산 스토리지 저장 방법<ul>
<li><strong>객체 스토리지(object storage)</strong> : 한 덩어리로 모인 데이터에 이름을 부여해서 파일로 저장<ul>
<li>ex) Amazon S3</li>
</ul>
</li>
<li><strong>NoSQL 데이터베이스</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="분산-데이터-처리---쿼리-엔진-etl-프로세스">분산 데이터 처리 - 쿼리 엔진, ETL 프로세스</h3>
<ul>
<li><p>분산 데이터 처리의 주 역할은 나중에 분석하기 쉽도록 <strong>데이터를 가공</strong>해서 그 결과를 외부 데이터베이스에 <strong>저장</strong>하는 것임</p>
</li>
<li><p>분산 스토리지에 저장된 데이터를 처리하는 데는 <strong>분산 데이터 처리 프레임워크</strong>가 필요함</p>
<ul>
<li>MapReduce 등</li>
</ul>
</li>
<li><p>빅데이터를 SQL로 집계하기 위한 두 가지 방법</p>
<ul>
<li><p><strong>쿼리 엔진(query engine) 도입 :</strong> ex) Hive, 대화형 쿼리 엔진(interactive query engine), …</p>
</li>
<li><p>외부의 <strong>데이터 웨어하우스</strong> 제품을 이용하는 것 : ex) AWS Redshift, Google BigQuery, …</p>
<ul>
<li><p>이를 위해서는 분산 스토리지에서 추출한 데이터를 데이터 웨어하우스에 적합한 형식으로 변환해야 함.</p>
</li>
<li><p>이러한 절차를 <strong>ETL(Extract - Transform - Load)(or ELT) 프로세스</strong>라고 함.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/1b78dd5d-2c91-412d-b57b-e5fd8f400883/image.png" alt=""></p>
<ul>
<li>데이터 추출, 데이터 가공, 데이터 로드</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="워크플로-관리">워크플로 관리</h3>
<ul>
<li>전체 데이터 파이프라인의 동작을 관리하기 위해서 <strong>워크플로 관리 기술</strong>을 사용함<ul>
<li>ex) 매일 정해진 시간에 배치 처리를 실행</li>
<li>ex) 특정한 순서대로 파이프라인을 동작</li>
<li>ex) 오류가 발생한 경우, 관리자에게 통지</li>
</ul>
</li>
</ul>
<h2 id="데이터-웨어하우스와-데이터-마트---데이터-파이프라인-기본형">데이터 웨어하우스와 데이터 마트 - 데이터 파이프라인 기본형</h2>
<ul>
<li>데이터 웨어하우스는 웹 서버 등에 사용하는 일반적인 RDB와는 달리 ‘대량의 데이터를 장기 보존하는 것’에 최적화되어 있음. 따라서 <strong>정리된 대량의 데이터를 한 번에 전송하는 것은 뛰어나지만, 소량의 데이터를 자주 쓰고 읽는 데는 적합하지 않음.</strong></li>
<li>데이터 마트(data mart) : 데이터 분석과 같은 목적에 사용하는 경우에는 데이터 웨어하우스에서 필요한 데이터만을 추출하여 ‘데이터 마트’를 구축함. (웨어하우스와 마찬가지로 SQL 형태)</li>
<li>데이터 웨어하우스를 중심으로 하는 데이터 파이프라인<ol>
<li>[저장소] 데이터 소스(source) : 업무 시스템을 위한 RDB나 로그 등을 저장하는 파일 서버들. (raw 데이터)</li>
<li>[처리] ETL</li>
<li>[저장소] 데이터 웨어하우스</li>
<li>[처리] 필요한 데이터 추출(자주 읽고 쓰며 사용할 데이터)</li>
<li>[저장소] 데이터 마트(data mart) → 분석</li>
</ol>
</li>
</ul>
<h2 id="데이터-레이크---데이터를-그대로-축적">데이터 레이크 - 데이터를 그대로 축적</h2>
<ul>
<li><strong>데이터 레이크 :</strong> 대규모의 다양한 원시 데이터(raw data)를 기본 형식으로 저장하는 데이터 저장소 (분산 스토리지)<ul>
<li>정제되지 않은 데이터들을 있는 그대로 저장하는 공간임</li>
<li>대부분 CSV나 JSON 등으로 저장됨</li>
</ul>
</li>
<li>데이터 웨어하우스를 데이터 레이크로 치환하면, ETL 프로세싱이 완료된 데이터들을 데이터 마트에 넣고 관리하게 된다.</li>
<li>데이터 레이크를 중심으로 하는 데이터 파이프라인<ol>
<li>[저장소] 데이터 소스(source) : 업무 시스템을 위한 RDB나 로그 등을 저장하는 파일 서버들. (raw 데이터)</li>
<li>[저장소] 데이터 레이크 : 수집한 로우 데이터를 그대로 보존</li>
<li>[처리] ETL</li>
<li>[저장소] 데이터 마트(data mart) → 분석</li>
</ol>
</li>
</ul>
<h2 id="데이터-분석-기반을-단계적으로-발전시키기---팀과-역할-분담-스몰-스타트와-확장">데이터 분석 기반을 단계적으로 발전시키기 - 팀과 역할 분담, 스몰 스타트와 확장</h2>
<h3 id="애드-혹-분석ad-hoc-analysis">애드 혹 분석(ad hoc analysis)</h3>
<ul>
<li><strong>애드 혹 분석(ad hoc analysis) :</strong> 일회성 데이터 분석<ul>
<li>SQL 쿼리를 직접 작성하여 실행하거나 스프레드시트 등을 이용하여 그래프를 만드는 등의 수작업</li>
</ul>
</li>
<li>애드 혹 분석은 데이터 마트를 만들지 않은 채 데이터 레이크나 웨어하우스에 직접 연결하는 경우가 많음</li>
</ul>
<h3 id="데이터-마트와-워크플로-관리">데이터 마트와 워크플로 관리</h3>
<ul>
<li>복잡한 데이터 분석에서는 먼저 데이터 마트를 구축한 후에 분석하거나 시각화하는 것이 효율적일 수 있음.</li>
<li>데이터 마트 구축은 배치 처리로 자동화되는 경우가 많기 떄문에 그 실행 관리를 위해 워크플로 관리 도구를 사용함<ul>
<li>워크플로 관리가 도입되면, 데이터 분석보다는 엔지니어링 작업이 많아지기 때문에 이에 대한 비용이 발생함</li>
<li>그러나 데이터 처리를 자동화하여 장기적으로 운용하기 위해선 안정된 워크플로 관리가 필수적임</li>
</ul>
</li>
</ul>
<p>→ 머신러닝 파이프라인에서는 데이터 워크플로의 성숙한 자동화를 위해서 데이터 통계량의 변화 등을 잘 살피는 것도 중요한 이슈일 수 있음 (모니터링 도구 등)</p>
<h2 id="확증적-데이터와-탐색적-데이터-분석">확증적 데이터와 탐색적 데이터 분석</h2>
<ul>
<li>확증적 데이터 분석 (confirmatory data analysis) : 가설을 세우고 그것을 검증하는 분석<ul>
<li>통계학적 모델링에 의한 분석 (통계 분석, 머신러닝 등)</li>
</ul>
</li>
<li>탐색적 데이터 분석(exploratory data analysis) : 데이터를 먼저 보면서 그 의미를 읽어내려고 하는 분석<ul>
<li>시각화 등을 활용하여 사람의 힘으로 의미를 읽는 것</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[주기적 실행 작업 관리 : cron (cron job) & logrotate]]></title>
            <link>https://velog.io/@whattsup_kim/cron-logrotate</link>
            <guid>https://velog.io/@whattsup_kim/cron-logrotate</guid>
            <pubDate>Sun, 18 Dec 2022 06:05:00 GMT</pubDate>
            <description><![CDATA[<h2 id="cron이란">cron이란</h2>
<ul>
<li>Linux 배포판을 포함한 Unix 계열 컴퓨터 운영 체제의 시간 기반 작업 스케줄러<ul>
<li>쉽게 말해, 정의된 스케줄에 따라 명령을 실행하는 도구</li>
</ul>
</li>
<li>cron 작업을 수행하는 데몬은 시스펨 시작 시 구동되고(systemd), 계속 실행 됨</li>
<li>스케줄 설정 : cron table (crontab) 파일 생성</li>
</ul>
<h3 id="cron-서비스의-job-관리">cron 서비스의 job 관리</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/589782d4-856d-408f-9a35-2004bc9023c8/image.png" alt=""></p>
<ul>
<li><p>cron service는 사용자 정의 cron job와 시스템 정의 cron job으로 나누어 짐</p>
</li>
<li><p>시스템이 정의한 crontab 구성에는</p>
<ul>
<li><p>일, 시간, 일, 주, 월별 주기 작업을 위한 설정이 포함되어 있음</p>
</li>
<li><p>해당 디렉토리에 포함된 스크립트를 실행</p>
</li>
<li><p>ex) /etc/cron.daily 디렉토리의 작업을 매일 6:00에 실행</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/613fe85f-40a8-4227-89c6-cd987962a7e3/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<h3 id="crontab-커맨드">crontab 커맨드</h3>
<pre><code class="language-bash"># 기존 crontab 항목 편집 (사용자 정의 cron job)
crontab -e
# root 사용자는 다른 사용자의 정보도 확인 가능
crontab -e &lt;USER_ID&gt;

# crobtab 리스트 출력 
crontab -l

# crontab 삭제
crontab -r</code></pre>
<h3 id="crontab-설정">crontab 설정</h3>
<ul>
<li><strong>분</strong>(0<del>59)  /  <strong>시</strong>(0</del>24)  /  <strong>일</strong>(1<del>31)  /  <strong>월</strong>(1</del>12)  /  <strong>요일</strong>(0~6)</li>
<li><code>*</code>은 모든 것과 일치함을 의미</li>
<li><code>,</code>은 반복 실행을 의미</li>
<li><code>-</code>는 범위 실행을 의미</li>
<li><code>/</code>은 간격 실행을 의미</li>
</ul>
<pre><code>
# 특정 시간
* * * * * &lt;CMD&gt; : 매 1분 마다 명령을 실행
0 * * * * &lt;CMD&gt; : 매 1시간 마다 명령을 실행 (1:00, 2:00, ...)
0 0 * * * &lt;CMD&gt; : 매 1일 마다 명령을 실행 (00:00)
0 2 * * * &lt;CMD&gt; : 매일 새벽 2시에 명령을 실행
0 2 * * 6 &lt;CMD&gt; : 매주 토요일 새벽 2시에 명령을 실행

0,10,20 * * * * &lt;CMD&gt; : 매 시간 0분, 10분, 20분마다 명령을 실행
0-30 0 * * * &lt;CMD&gt; : 매일 00:00부터 00:30까지 명령을 실행
*/10 * * * * &lt;CMD&gt; : 매 10분마다 명령을 실행</code></pre><h2 id="logrotate">logrotate</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/42641595-b378-44fc-9d57-5dc9fa95ef6f/image.png" alt=""></p>
<p>로그 파일 관리를 도와주는 도구</p>
<ul>
<li>로그 압축, 제거, 특정기간 단위로 로그 분리, 메일 전송, ….</li>
<li>모든 리눅스 배포판의 표준 로그 관리 유틸리티임</li>
<li><code>/etc/cron.daily</code> 디렉토리 내 logrotate를 실행하는 shell script가 존재</li>
</ul>
<p><code>/etc/logrotate.conf</code></p>
<ul>
<li>logrotate의 default 설정이 명시되어 있는 곳(전반적인 설정을 함)</li>
</ul>
<p><code>/etc/logrotate.d/*</code></p>
<ul>
<li>rotate를 돌릴 각 프로그램의 설정</li>
<li>여기에 logrotate 설정 파일만 추가하면 로그 관리 기능을 구현할 수 있음</li>
<li>cron에 의해 매일 1회 수행됨</li>
</ul>
<h3 id="logrotate-작성-가이드"><strong>logrotate 작성 가이드</strong></h3>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
<th>예시/비고</th>
</tr>
</thead>
<tbody><tr>
<td>rotate [숫자]</td>
<td>log파일이 [숫자]개 이상이면 삭제</td>
<td>rotate 5</td>
</tr>
<tr>
<td>maxage [숫자]</td>
<td>log파일이 [숫자]일 이상이면 삭제</td>
<td>maxage 30</td>
</tr>
<tr>
<td>size</td>
<td>지정된 용량보다 클 경우 로테이트 실행</td>
<td>size +100k</td>
</tr>
<tr>
<td>create [권한][유저][그룹]</td>
<td>로테이트 되는 log파일 권한 지정</td>
<td>create 644 root root</td>
</tr>
<tr>
<td>notifempty</td>
<td>로그 내용이 없으면 로테이트 하지 않음</td>
<td></td>
</tr>
<tr>
<td>ifempty</td>
<td>로그 내용이 없어도 로테이트</td>
<td></td>
</tr>
<tr>
<td>monthly</td>
<td>월 단위 로테이트 진행</td>
<td></td>
</tr>
<tr>
<td>weekly</td>
<td>주 단위 로테이트 진행</td>
<td></td>
</tr>
<tr>
<td>daily</td>
<td>일 단위 로테이트 진행</td>
<td></td>
</tr>
<tr>
<td>compress</td>
<td>로테이트 되는 log파일 압축</td>
<td></td>
</tr>
<tr>
<td>nocompress</td>
<td>로테이트 되는 log파일 압축 하지 않음</td>
<td></td>
</tr>
<tr>
<td>missingok</td>
<td>log파일이 발견되지 않은 경우 에러처리 하지 않음</td>
<td></td>
</tr>
<tr>
<td>dateext</td>
<td>백업파일의 이름에 날짜가 들어가도록 함</td>
<td></td>
</tr>
<tr>
<td>copytruncate</td>
<td>로그 데이터를 새로운 파일로 카피하고, 기존 파일을 0으로 만들어서, 새 로그 저장용으로 사용(즉, 저장될 파일은 같음)</td>
<td>장점 : 앱이 로그 파일을 새로 열 필요가 없음. / 단점 : 카피를 하기 때문에 로그내용이 많은 경우 오랜 시간이 걸릴 수 있음</td>
</tr>
<tr>
<td>postrotate</td>
<td>로그 로테이션이 완료된 후에 실행될 스크립트</td>
<td>ex) 앱에 시그널을 보내서 로그 저장용 파일을 새로 여는 것</td>
</tr>
<tr>
<td>lastaction-endscript</td>
<td>logrotate output을 생성하고 실행</td>
<td></td>
</tr>
</tbody></table>
<h3 id="logrotate-예시">logrotate 예시</h3>
<ul>
<li><p>MongoDB 로그 관리</p>
<ul>
<li><p>설정 파일 생성</p>
<pre><code class="language-bash">  cd /data/log/logrotate
  vim docker</code></pre>
<pre><code class="language-bash">  /var/log/mongodb/mongod.log {
      su root root
      daily
      size 300M
      rotate 7
      missingok
      compress
      delaycompress
      notifempty
      create 664 ubuntu ubuntu
      sharedscripts
      postrotate
      sudo /bin/kill -SIGUSR1 `ps -ef | grep mongod | grep 
  -v grep | awk &#39;{print $2}’`
      endscript
  }</code></pre>
</li>
<li><p>log 실행(루트 권한)</p>
<pre><code class="language-bash">  logrotate -f /etc/logrotate.d/[rotate_name]</code></pre>
</li>
</ul>
</li>
<li><p>nginx 로그 설정</p>
<pre><code>  /var/log/nginx/*.log {
      daily
      missingok
      rotate 14
      compress
      delaycompress
      notifempty
      create 0640 222-data adm
      sharedscripts
      prerotate
                      if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                                      run-parts /etc/logrotate.d/httpd-prerotate;
                      fi \
      endscript
      postrotate
                      invoke-rc.d nginx rotate &gt;/dev/null 2&gt;&amp;1
      endscript
  }</code></pre><ul>
<li><code>invoke-rc.d nginx rotate &gt;/dev/null 2&gt;&amp;1</code>는 <code>sudo service nginx rotate</code> 명령어와 같음. (즉, nginx log 파일을 새로 오픈한 것)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Interview for AI Engineer : Data Science]]></title>
            <link>https://velog.io/@whattsup_kim/ds-review-for-al-enginner</link>
            <guid>https://velog.io/@whattsup_kim/ds-review-for-al-enginner</guid>
            <pubDate>Thu, 15 Dec 2022 11:52:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>AI Engineer로서 알아야 할 기본적인 DS지식들을 스스로 간단히(직관적으로) 리뷰하는 공간입니다.</p>
</blockquote>
<h1 id="ml-basic">ML Basic</h1>
<h3 id="metric분류-회귀"><a href="https://habitual-pint-c5d.notion.site/metric-ex-RMSE-MAE-recall-precision-0c99eb391f9144b68ce82c9e392bc0ca">Metric(분류, 회귀)</a></h3>
<h3 id="데이터-스케일링정규화"><a href="https://habitual-pint-c5d.notion.site/e748c10bfad645b5a1532832208677e9">데이터 스케일링(정규화)</a></h3>
<h3 id="local-minima와-global-minima"><a href="https://habitual-pint-c5d.notion.site/Local-Minima-Global-Minima-23f4bc3619e84d2092133866322ea5b6">Local Minima와 Global Minima</a></h3>
<h3 id="차원의-저주"><a href="https://habitual-pint-c5d.notion.site/0aa345057553482dbbcff9ba3e0c95c6">차원의 저주</a></h3>
<h3 id="ldalatent-dirichlet-allocation"><a href="https://habitual-pint-c5d.notion.site/616ebc0f5e774fb997324e911e4359f9">LDA(Latent Dirichlet allocation)</a></h3>
<h3 id="svmsupport-vector-machine"><a href="https://habitual-pint-c5d.notion.site/SVM-SVM-4773a49ea5f2454f81169b8c0ffe06eb">SVM(support vector machine)</a></h3>
<h3 id="나이브-베이즈naive-bayes"><a href="https://habitual-pint-c5d.notion.site/naive-bayes-d0b157299b294391a793bb7a41932f44">나이브 베이즈(naive bayes)</a></h3>
<h3 id="차원-축소-기법"><a href="https://habitual-pint-c5d.notion.site/dimension-reduction-PCA-SVD-6b48eb30b21444e48d9dcb2f7d578a57">차원 축소 기법</a></h3>
<h3 id="markov-chain"><a href="https://habitual-pint-c5d.notion.site/Markov-Chain-73dce96c21d647cfa47a8aae27124ab3">Markov Chain</a></h3>
<h3 id="경사하강법"><a href="https://habitual-pint-c5d.notion.site/5c54fcd0e6b747fabfd49b7d6f7e6b7d">경사하강법</a></h3>
<h3 id="optimizer"><a href="https://habitual-pint-c5d.notion.site/Optimizer-dc76931b9cf34de99271a8afd2dcbed3">Optimizer</a></h3>
<h3 id="norml1--l2--regularization"><a href="https://habitual-pint-c5d.notion.site/L1-L2-norm-L1-L2-Regularization-7326d2ca4fae4fc2a2b6760869f0b242">Norm(L1 &amp; L2) &amp; Regularization</a></h3>
<h3 id="batch-norm"><a href="https://habitual-pint-c5d.notion.site/Batch-Norm-19c71b3c343b40639c94ea9cc6b9780d">Batch Norm</a></h3>
<h3 id="선형회귀회귀-분석의-4가지-가정"><a href="https://habitual-pint-c5d.notion.site/Linear-Regression-732d375992fa4bbbaeb6304baa913a46">선형회귀(&amp;회귀 분석의 4가지 가정)</a></h3>
<h3 id="로지스틱-회귀"><a href="https://habitual-pint-c5d.notion.site/75d4bb6a15c945f8a511e727757f9734">로지스틱 회귀</a></h3>
<h3 id="앙상블-기법"><a href="https://habitual-pint-c5d.notion.site/f1409c4c4c264655980df3a593e23060">앙상블 기법</a></h3>
<h3 id="decision-tree"><a href="https://habitual-pint-c5d.notion.site/Decision-Tree-3057dbdb20c14c1db79feaf48d6411b2">Decision Tree</a></h3>
<h3 id="random-forest"><a href="https://habitual-pint-c5d.notion.site/RamdomForest-b694443bca824b29ae67dffe34f3b116">Random Forest</a></h3>
<h3 id="adaboost"><a href="https://habitual-pint-c5d.notion.site/AdaBoost-d0ace1035efb4ba5b7babf8aa6c1cbdb">AdaBoost</a></h3>
<h3 id="gradient-boosting"><a href="https://habitual-pint-c5d.notion.site/Gradient-Boosting-fcf110ba2786420e8952420cb76564ac">Gradient Boosting</a></h3>
<h3 id="embedding"><a href="https://habitual-pint-c5d.notion.site/Embedding-89fc73f872284ac2a7c308e0c45d887f">Embedding</a></h3>
<h3 id="cnn"><a href="https://habitual-pint-c5d.notion.site/CNN-2efff8db6c5d427d923a298dbe7d6113">CNN</a></h3>
<h3 id="rnn"><a href="https://habitual-pint-c5d.notion.site/RNN-f2b3cdf4b9ee4595b3c2dbdece39a04d">RNN</a></h3>
<h3 id="lstm--gru"><a href="https://habitual-pint-c5d.notion.site/LSTM-GRU-f598fde5113040a993494442263662a6">LSTM &amp; GRU</a></h3>
<h3 id="transformer"><a href="https://habitual-pint-c5d.notion.site/Transfomer-f2b5aacaebec4797a422964f59a763b5">Transformer</a></h3>
<h1 id="recsys">RecSys</h1>
<h3 id="association-rule"><a href="https://habitual-pint-c5d.notion.site/Association-Rule-Support-Confidence-Lift-65c022068dde43df98dcd750dc91b99d">Association Rule</a></h3>
<h3 id="content-based-recommendation"><a href="https://habitual-pint-c5d.notion.site/Content-based-969573a8c3a747279301f5737a8b02dc">Content-based Recommendation</a></h3>
<h3 id="collaborative-filtering"><a href="https://habitual-pint-c5d.notion.site/Collaborative-Filtering-4018ad02af3a4a5098dca917ae9bd8b8">Collaborative Filtering</a></h3>
<h3 id="유사도-측정"><a href="https://habitual-pint-c5d.notion.site/78c3c98ee8dd478abbc16d32eb41ca6a">유사도 측정</a></h3>
<h3 id="latent-factor-model"><a href="https://habitual-pint-c5d.notion.site/Latent-Factor-Model-acf264bf4dd84bc495c5814896495f77">Latent factor model</a></h3>
<h3 id="matrix-factorization"><a href="https://habitual-pint-c5d.notion.site/Matrix-Factorization-93c9dfead30f43bc979241e995246364">Matrix Factorization</a></h3>
<h3 id="matrix-factorization-for-implicit-feedback"><a href="https://habitual-pint-c5d.notion.site/MF-for-Implicit-Feedback-bcf302fbeff84b04a3ef0b836be123f4">Matrix Factorization for Implicit Feedback</a></h3>
<h3 id="bayesian-personalized-ranking"><a href="https://habitual-pint-c5d.notion.site/Bayesian-Personalized-Ranking-7bd21a5515c540f4bfaf4b24beafbc19">Bayesian Personalized Ranking</a></h3>
<h3 id="item2vec"><a href="https://habitual-pint-c5d.notion.site/Item2Vec-306d2ecf934948e8aa06429bc01e55de">Item2Vec</a></h3>
<h3 id="ann"><a href="https://habitual-pint-c5d.notion.site/ANN-Approximate-Nearest-Neighbor-429eef5779614707ae4d056981ddb712">ANN</a></h3>
<h3 id="neural-collaborative-filtering"><a href="https://habitual-pint-c5d.notion.site/Neural-Collaborative-Filtering-99ac494a414b40308f385de9dc5a6010">Neural Collaborative Filtering</a></h3>
<h3 id="gnn"><a href="https://habitual-pint-c5d.notion.site/GNN-based-68ce59ff9cce40bebf7b1f6369f47c20">GNN</a></h3>
<h3 id="context-aware-recommendation"><a href="https://habitual-pint-c5d.notion.site/Context-aware-Recommendation-a95b907ed652448ca68cd8e7a6e2c6b5">Context-aware Recommendation</a></h3>
<h3 id="multi-armed-bandit"><a href="https://habitual-pint-c5d.notion.site/Multi-Armed-Bandit-0b3174696baf469697899bf7bcf6e170">Multi-Armed Bandit</a></h3>
<hr>
<h4 id="reference">Reference</h4>
<ul>
<li><a href="https://boostdevs.gitbook.io/ai-tech-interview/">https://boostdevs.gitbook.io/ai-tech-interview/</a></li>
<li><a href="https://github.com/SeongBeomLEE/Machine-Learning-Engineer-Interview">https://github.com/SeongBeomLEE/Machine-Learning-Engineer-Interview</a></li>
<li><a href="https://brunch.co.kr/@gimmesilver#articles">https://brunch.co.kr/@gimmesilver#articles</a></li>
<li><a href="https://wooono.tistory.com/m/category/AI/Machine%20Learning">https://wooono.tistory.com/m/category/AI/Machine Learning</a></li>
<li><a href="https://angeloyeo.github.io/2019/07/27/PCA.html">https://angeloyeo.github.io/2019/07/27/PCA.html</a></li>
<li><a href="https://www.youtube.com/c/%ED%98%81%ED%8E%9C%ED%95%98%EC%9E%84">https://www.youtube.com/c/혁펜하임</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[PKI 인증서와 TLS(SSL)]]></title>
            <link>https://velog.io/@whattsup_kim/PKI-%EC%9D%B8%EC%A6%9D%EC%84%9C%EC%99%80-TLSSSL</link>
            <guid>https://velog.io/@whattsup_kim/PKI-%EC%9D%B8%EC%A6%9D%EC%84%9C%EC%99%80-TLSSSL</guid>
            <pubDate>Mon, 07 Nov 2022 15:24:15 GMT</pubDate>
            <description><![CDATA[<h2 id="pkipublic-key-infrastructure란">PKI(<strong>Public Key Infrastructure)</strong>란</h2>
<p>PKI는 <strong>비대칭 암호화</strong> 기술을 이용한 <strong>공개키 기반의 인증 체계임.</strong></p>
<ul>
<li><p>활용 사례 : HTTPS, SSH, 인터넷 뱅킹 등</p>
</li>
<li><p>대표적인 인증서 표준 포멧 : <strong>X.509</strong></p>
<p>  → 쿠버네티스에서는 <strong>X.509</strong>를 이용하여 사용자의 신원을 <strong>인증</strong>하는 목적으로 사용할 수 있음.
  (참고로 이는 쿠버네티스의 많은 인증 방법들 중 하나임 - HTTP Authentication, OpenID Connect, Webhook, Proxy Auth, ..)</p>
</li>
</ul>
<h3 id="public-private-키"><strong>Public-Private 키</strong></h3>
<ul>
<li><strong>X.509</strong>의 근간이 되는 <strong>암호화</strong> 기술</li>
<li>Public 키와 Private 키가 존재</li>
<li>Public 키는 누구나 가질 수 있으며, Private 키는 오직 Public, Private 키페어 소유자만 가지고 있음</li>
<li>Public key는 Private key로만 복호화가 가능</li>
</ul>
<h3 id="pki-인프라-구조순서"><strong>PKI 인프라 구조(순서)</strong></h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/d2102f06-da24-4354-915d-0adb920be437/image.png" alt=""></p>
<h3 id="인증서-내용구조"><strong>인증서 내용(구조)</strong></h3>
<p>: 사용자 정보 + Public key + 공개 기관(Certificate Authority)의 서명</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/319fd21d-cc51-49bf-a161-e8806547dd22/image.png" alt=""></p>
<ul>
<li>인증서에는 해당 인증서를 발급해준 발급 기관 (Issuer) 정보를 포함하고 있다.</li>
<li>발급 기관 정보를 이용하여 지금 보고 있는 인증서가 아무에게서나 발급된 인증서가 아니라 공인된 인증 기관에서 발급된 인증서라는 것을 확인할 수 있다. 이것을 <strong>Certificate Authority(인증 기관)</strong>이라 부르고 짧게 CA라고도 한다.</li>
<li>이 CA 또한 인증서로 이루어져 있어 해당 Public 키로 인증기관의 유효성을 동일한 메커니즘으로 검사할 수 있다. CA도 마찬가지로 CA의 인증서를 발급한 인증 기관(CA의 CA)이 존재하며 이러한 연결고리를 <strong>Certificate Chain</strong> (인증 체인)이라고 부른다.</li>
<li>인증 체인의 가장 끝에는 <strong>Root CA</strong>라고 하는 인증기관이 있다. 즉, <strong>Root CA</strong>를 이용하여 다른 CA를 검증하고 해당 CA가 최종 Certificate을 인증하는 것이다.<ul>
<li>이 <strong>Root CA</strong>는 따로 인증 기관이 존재하지 않으며 스스로가 스스로를 인증한다.</li>
</ul>
</li>
</ul>
<h3 id="pki-동작-예시">PKI 동작 예시</h3>
<p>다음 <a href="https://sy34.net/public-private-key-pair/">블로그</a>의 글의 예시를 참고하였습니다. </p>
<p><strong>예시 1) 상대방에게 메세지를 암호화하여 전달</strong></p>
<ul>
<li>W의 암호화 열쇠 : [<code>W_Public Key</code> / <code>W_Private Key</code>]</li>
<li>S의 암호화 열쇠 : [<code>S_Public Key</code> / <code>S_Private Key</code>]<ul>
<li>이때, W가 S에게 “hello S”라는 메세지를 모내고 싶은 경우 다음과 같이 진행된다.<ol>
<li>W는 S의 공개된 열쇠인 <code>S_Public Key</code>를 이용하여 &quot;hello S&quot; 라는 메세지를 암호화 한다. 암호화 했더니 &quot;asj29dfns3aju1—&quot; 라는 메세지로 변환되었다.</li>
<li>암호화 된 메세지를 S에게 전송한다.</li>
<li>S는 자신만이 알고 있는 사적 열쇠인 <code>S_Private Key</code>를 이용하여 메세지를 해독하여 &quot;hello S&quot; 라는 내용을 받는다.</li>
</ol>
</li>
<li>이렇듯 상대방의 공개 열쇠를 이용하여 메세지를 암호화 한 후 전송하면, 그 상대방만이 메세지를 해독할 수 있다.</li>
<li>2번 과정에서 다른 이가 메세지를 가로채도 &quot;asj29dfns3aju1—&quot; 라는 메세지를 해독하는 것은 (거의) 불가능하다.</li>
</ul>
</li>
</ul>
<p><strong>예시 2) 나 자신을 증명(신원 확인) - 디지털 서명(digital signature)</strong></p>
<ul>
<li>위 예시에서 반대로 W가 보낸 메세지를 S가 확인하고 싶은 상황이라고 가정해보자.</li>
<li>S는 이 메세지가 정말 W가 보낸 것인지 확인 할 방법이 없다.</li>
<li>이럴 경우, W는 <code>W_Private Key</code>를 통해 메세지르 암호화하여, S에게 보낼 수 있다.</li>
<li>S는 이전에 W에게 전달받은 <code>W_Public Key</code>으로 해당 메세지가 정말 W가 보낸 것이 맞는지 검증할 수 있다.</li>
</ul>
<h2 id="sslsecure-sockets-layer과-tlstransport-layer-security">SSL(<strong>Secure Sockets Layer)</strong>과 TLS(Transport Layer Security)</h2>
<h3 id="보안서버란">보안서버란</h3>
<p>인터넷 상에서 사용자 PC와 웹 서버 사이에 송수신되는 정보를 암호화하여 전송하는 서버를 의미</p>
<ul>
<li>웹 브라우저와 웹 서버 간 전송되는 데이터의 암/복호화를 통해 보안 채널을 형성하여 안전한 전자 거래를 보장함</li>
</ul>
<p>보안 서버 구축 필요성</p>
<ul>
<li>정보 유출 방지(sniffing : 서버로 전송되는 고객의 정보 해킹)</li>
<li>위조 사이트 방지(phishing : 기업을 사칭하여 고객의 정보를 뺴가는 등의 사기수법)</li>
</ul>
<h3 id="sslsecure-socket-layer와-tlstransport-layer-security">SSL(Secure Socket Layer)와 TLS(Transport Layer Security)</h3>
<p>SSL이란 웹 표준 암호화 방식으로, 웹 서버와 웹 브라우저 사이에 몯느 정보를 암호화 해주는 방식을 의미</p>
<ul>
<li>SSL 통신은 http가 아닌 https 통신채널을 사용하며, 모든 웹 서버와 웹 브라우저가 SSL을 지원함</li>
<li>SSL은 서버 인증(Server Authentication), 클라이언트 인증(Client Authenitication), 데이터 암호화(Data Encryption) 기능을 제공함</li>
</ul>
<p>TLS 1.0은 SSL 3.0을 계승한다.</p>
<ul>
<li>TLS은 SSL 3.0을 기반으로 한 업그레드된 프로토콜임</li>
</ul>
<p>SSL/TLS의 주요 기능은 다음과 같다.</p>
<ul>
<li>신원 확인<ul>
<li>SSL 서버 인증서는 회사에 대한 방문조사 후 발급되기 때문에 고개들은 서버 인증서를 확인하여 회사의 웹 사이트가 실제로 존재하고, 회사의 소유임을 확인할 수 있다.</li>
</ul>
</li>
<li>메시지 비밀 보장<ul>
<li>SSL로 웹 서버와 고객 간 교환된 정보를 하나의 Session 키로 암호화한다.</li>
<li>이 Session 키를 안전하게 고객에게 전달하기 위해 회사의 공캐키로 암호화하여 보냄</li>
<li>이때, 각 Session에 한 고객에게 하나의 키가 사용되므로, 권한이 없는 제3자는 정보를 가로채도 볼 수 없음</li>
</ul>
</li>
<li>메시지의 무결성<ul>
<li>사용자의 브라우저로부터 Web Server까지 전달되는 동안 누군가에 의해 데이터가 변경되지 않도록 보장한다.</li>
</ul>
</li>
</ul>
<h3 id="대칭symmetric-encryption-키-vs-비대칭asymmetric-encryption-키">대칭(Symmetric Encryption) 키 vs 비대칭(Asymmetric Encryption) 키</h3>
<p>대칭키 : 암호화-복호화에 동일한 키를 사용하는 것</p>
<ul>
<li>빠른 속도를 가지지만, 안전하지 않음</li>
</ul>
<p>비대칭 키 : 암호화할 때와 복호화할 때의 키가 서로 다른 것</p>
<ul>
<li>안전한 키 교환을 할 수 있지만, 속도가 느림</li>
</ul>
<p>→ 다음과 같이 상호보완한 방법도 있음 : 용량이 큰 정보는 대칭키로 암호화하고, 암호화에 사용된 대칭키는 공개키로 암호화하여 대상에게 전달하는 방식</p>
<h3 id="ssl-동작방법">SSL 동작방법</h3>
<p>본 섹션은 다음 <a href="https://www.crocus.co.kr/1387">블로그(1)</a>, <a href="http://wiki.gurubee.net/display/SWDEV/SSL+%28TLS%29">블로그(2)</a>를 참고하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/e9e54bab-164f-4c19-8f1b-5b43b9171154/image.png" alt=""></p>
<ol>
<li>인터넷 사이트는 자신의 정보와 공개 키를 인증기관에 제출한다.</li>
<li>인증기관은 검증을 거친후 사이트 정보와 공개 키를 인증기관의 개인 키로 암호화한다(=사이트 인증서). </li>
<li><strong>인증기관</strong>은 인터넷 사이트에게 암호화한 인증서를 제공한다.</li>
<li><strong>인증기관</strong>은 웹 브라우저에게 자신의 공캐 키를 제공한다.</li>
<li>사용자가 웹 브라우저로 사이트에 접속을 요청한다(Handshake: <strong>Client Hello</strong>). 사용자는 이때 서버에 다음과 같은 정보들을 보낸다.<ul>
<li>클라이언트 측에서 생성한 랜덤한 데이터</li>
<li>클라이언트가 지원하는 암호화 방식들 : 클라이언트가 전달한 암호화 방식 중에서 사이트 쪽에서도 사용할 수 있는 암호화 방식을 선택하기 위한 작업</li>
<li>세션 아이디 : 이전에 HandShake를 했다면, 비용과 시간을 절약하기 위해 기존의 세션을 재활용하게 되는데, 이 때 사용할 연결에 대한 식별자를 사이트 측으로 전송한다.</li>
</ul>
</li>
<li>사이트는 이에 대한 대답으로 다음과 같은 정보들을 보낸다(Handshake: <strong>Server Hello</strong>).<ul>
<li>사이트 측에서 생성한 랜덤한 데이터</li>
<li>사이트가 선택한 웹 브라우저의 암호화 방식 : 이를 통해 암호화 방식 선택을 완료한다.</li>
<li>인증서(이 인증서에는 인증기관의 개인 키로 암호화한 사이트의 정보와 사이트의 공개키가 들어있다.)</li>
</ul>
</li>
<li>웹 브라우저는 인증기관의 공개 키로 사이트 인증서를 해독하여 검증한다.<ul>
<li>웹 브라우저에 내장된 CA 리스트를 확인(CA 리스트에 없다면 경고 메세지를 출력)</li>
<li>웹 브라우저 측에서 생성한 랜덤 데이터와 사이트 측에서 생성한 랜덤 데이터를 조합하여 <code>pre master secret</code>라는 대칭키를 생성</li>
</ul>
</li>
<li>웹 브라우저는 위에서 얻은 사이트 공개 키로 대칭키를 암호화해서 사이트로 보낸다.</li>
<li>사이트는 자신의 개인 키로 암호문을 해독하여 대칭 키를 얻는다. </li>
<li>이제 대칭 키로 암호문을 주고받을 수 있다. <ul>
<li>(9)까지의 과정을 통해 웹 브라우저와 사이트 모두 동일한 pre master secret을 가졌다. 이를 일련의 과정을 거쳐서 master secret 값으로 만든 뒤 이를 통해 Session key를 생성한다.</li>
<li>이 Session key 값을 이용하여 사이트와 웹 브라우저는 데이터를 대칭 키 방식으로 암호화 한 후에 주고받는다.</li>
</ul>
</li>
<li>이후 데이터의 전송이 끝나면 SSL 통신이 끝났음을 서로에게 알린 후, 통신에 사용한 Session key를 폐기한다.</li>
</ol>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://coffeewhale.com/kubernetes/authentication/x509/2020/05/02/auth01/">https://coffeewhale.com/kubernetes/authentication/x509/2020/05/02/auth01/</a></li>
<li><a href="https://sy34.net/public-private-key-pair/">https://sy34.net/public-private-key-pair/</a></li>
<li><a href="http://wiki.gurubee.net/display/SWDEV/SSL+%28TLS%29">http://wiki.gurubee.net/display/SWDEV/SSL+(TLS)</a></li>
<li><a href="https://www.crocus.co.kr/1387">https://www.crocus.co.kr/1387</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[kubernetes] 쿠버네티스란?]]></title>
            <link>https://velog.io/@whattsup_kim/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4kubernetes%EB%9E%80</link>
            <guid>https://velog.io/@whattsup_kim/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4kubernetes%EB%9E%80</guid>
            <pubDate>Sat, 29 Oct 2022 10:43:54 GMT</pubDate>
            <description><![CDATA[<h2 id="배경">배경</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/6f371c9e-8fc1-49ba-9752-50e179f63ce5/image.png" alt=""></p>
<p><strong>전통적인 배포 시대</strong></p>
<ul>
<li>전통적인 배포 때에는 애플리케이션을 물리 서버에서 실행했습니다.</li>
<li>그러나 한 물리 서버에서 여러 애플리케이션의 한계를 정의할 방법이 없었게 때문에 이에 대대 리소스 할당 문제가 발생했습니다.<ul>
<li>ex) 물리 서버 하나에서 무거운 인스턴스를 돌리게 되면, 다른 인스턴스에 대한 성능은 저하될 수 있습니다.</li>
</ul>
</li>
<li>따라서 당시 개발자들은 서로 다른 여러 물리 서버에서 각 애플리케이션을 실행하며 이러한 문제를 해결하였습니다.</li>
<li>그러나 이는 많은 물리 서버를 유지해야 하기 때문에 (남는 서버 공간은 그대로 방치됨) 조직에게 많은 비용을 요구하였죠. 즉, 안정적이지만 비효율적인 구조였던 것입니다.</li>
</ul>
<p><strong>(하이퍼바이저 기반) 가상화된 배포 시대</strong></p>
<ul>
<li>이러한 해결책으로 ‘가상화된 배포 시대’가 도래하엿습니다.</li>
<li>가상화된 배포 시대에는 단일 물리 서버의 CPU에서 여러 가상 시스템(Virtual Machine)을 실행할 수 있게 하였습니다.</li>
<li>그러나 하나의 Host OS 위에 OS를 여러 개 실행시킨다는 점에서 VM은 리소스를 많이 잡아먹기 때문에 무겁다는 단점이 있었죠.</li>
</ul>
<p><strong>컨테이너 개발 시대</strong></p>
<ul>
<li>컨테이너는 VM과 유사지만, 가상화를 좀 더 경량화된 프로세스의 개념으로 만든 기술을 의미합니다.<ul>
<li>호스트 OS 위에 컨테이너 엔진을 설치하고, 애플리케이션 작동에 필요한 바이너리, 라이브러리 등을 하나로 모아 각자가 별도의 서버인 것처럼 사용하는 환경입니다.</li>
</ul>
</li>
<li>컨테이너는 호스트 OS와 커널을 공유하므로, 이전보다 빠르고 가볍게 가상화를 구현할 수 있게 되었으며, 유연하고 자유로운 마이크로서비스를 관리하기 용이하게 해 주었습니다.<ul>
<li>마이크로 서비스: 앱이 작고 독립적인 단위로 쪼개져서 동적으로 배포되고 관리되는 것</li>
</ul>
</li>
</ul>
<h2 id="탄력적인-컨테이너-운용을-위한-솔루션-쿠버네티스">탄력적인 컨테이너 운용을 위한 솔루션: 쿠버네티스</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/a01ec621-8836-41d9-b1bf-b321da7da549/image.png" alt=""></p>
<p>제목에서 설명하듯, 쿠버네티스는 탄력적인 컨테이너 운용을 위한 솔루션입니다.</p>
<ul>
<li>만약, 서비스 중에 컨테이너가 다운되어 다른 컨테이너를 띄어야 하는 상황이 온다면 어떻게 해야 할까요?</li>
<li>개발자가 24시간 대기하면서 서비스 상황을 체크하지 않는 이상(심지어 그렇다고 하더라도) 문제를 바로 해결하는 것은 정말 힘든 문제입니다.</li>
<li>그런데 만약 컨테이너가 다운되면, 시스템이 복제해두었던 다른 컨테이너를 다시 실행하는 방식으로 이러한 문제를 해결한다면 어떨까요?</li>
</ul>
<p>이것이 컨테이너 오케스트레이션 도구가 등장한 이유입니다. </p>
<ul>
<li>컨테이너 오케스트레이션 : 일반적으로 애플리케이션은 의도에 따라 애플리케이션이 실행되게 하기 위해 네트워킹 수준에서 정리가 필요한 개별적으로 컨테이너화된 구성 요소(주로 마이크로 서비스로 칭함)로 구성됩니다. 이러한 방식으로 다수의 컨테이너를 정리하는 프로세스를 컨테이너 오케스트레이션이라고 합니다.</li>
</ul>
<p>쿠버네티스는 이러한 컨테이너 오케스트레이션 도구 중 가장 널리 사용되는 오픈소스 툴입니다.</p>
<ul>
<li>같은 역할을 하는 도구로서 도커 스웜(Docker Swarm), 아파치 메소스(Apache Mesos), 노마드(Nomad) 등이 대규모 컨테이너의 효율적 제어라는 동일한 목적 아래 발전되어 왔으나, 2022년 현재는 쿠버네티스가 <strong>컨테이너 기반 인프라 시장에서 사실상의 표준</strong>으로 자리 잡은 상태입니다.</li>
</ul>
<h2 id="쿠버테티스-기능">쿠버테티스 기능</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/6dffe3ab-bd65-4299-90c8-cfa185b22bcc/image.png" alt=""></p>
<p><strong>서비스 디스커버리와 로드 밸런싱</strong></p>
<ul>
<li>쿠버네티스는 (별도의 DNK 구성 없이) DNS 이름을 사용하거나 자체 IP 주소를 사용하여 컨테이너를 노출할 수 있습니다.</li>
<li>트래픽이 많아지면, 쿠버네티스는 자동으로 네트워크 트래픽을 로드밸런싱하여, 배포가 안정적으로 이루어질 수 있도록 합니다.</li>
</ul>
<p><strong>스토리지 오케스트레이션</strong></p>
<ul>
<li>쿠버네티스를 사용하면 로컬 저장소, 공용 클라우드 등과 같이 원하는 저장소 시스템을 자동으로 탑재할 수 있습니다.</li>
</ul>
<p><strong>자동화된 롤아웃과 롤백</strong></p>
<ul>
<li>배포된 컨테이너의 원하는 상태를 서술(선언, Desired State)할 수 있으며, 상태를 원하는 상태로 설정한 속도에 따라 변경할 수 있습니다.</li>
<li>장애 시 애플리케이션의 롤백도 지원합니다.</li>
</ul>
<p><strong>자동화된 빈 패킹(bin packing)</strong></p>
<ul>
<li>컨테이너화된 작업을 실행하는데 사용할 수 있는 쿠버네티스 클러스터 노드를 제공합니다.<ul>
<li>각 컨테이너가 필요로 하는 CPU와 메모리를 쿠버네티스에 지시하면, 쿠버네티스는 컨테이너를</li>
</ul>
</li>
</ul>
<p><strong>시크릿(secret)과 구성(config) 관리</strong></p>
<ul>
<li>시크릿과 애플리케이션 구성을 안전하게 배포하고 업데이트할 수 있습니다.</li>
<li>시크릿된 정보들은 암호화되어 저장됩니다.</li>
</ul>
<p><strong>자가 치유</strong></p>
<ul>
<li>오류가 발생하거나 노드가 죽었다면, 컨테이너를 재시작하고 다시 스케쥴링 해줍니다.</li>
<li>즉, 사용자가 정의한 상태에 따라 서비스를 준비하고 제공합니다.</li>
</ul>
<p><strong>배치 실행</strong></p>
<ul>
<li>배치(실시간으로 처리하는 것이 아니라, 일괄적으로 모아서 한 번에 처리하는 것) 단위 작업을 실행할 수 있도록 하며, 주기적인 배치 작업도 실행할 수 있습니다.</li>
</ul>
<p><strong>오토 스케일링</strong></p>
<ul>
<li>자동으로 애플리케이션의 스케일을 넓히거나 줄일 수 있습니다(Horizontal Scailing)</li>
</ul>
<h2 id="쿠버네티스-핵심-컨셉">쿠버네티스 핵심 컨셉</h2>
<h3 id="선언형-인터페이스와-desired-state">선언형 인터페이스와 Desired State</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/6b8e79be-52e9-4175-b37c-7f64d8bb4743/image.png" alt=""></p>
<ul>
<li>쿠버네티스에서는 명령형 인터페이스가 아닌 <strong>선언형 인터페이스</strong>를 사용합니다.<ul>
<li>어떤 동작을 지시하는 것이 아니라 원하는 상태를 선언하는 것.</li>
<li>이러한 방식을 “쿠버네티스 네이티브”하다고도 함.</li>
<li>ex) “우리집 온도가 25도로 유지됐으면 좋겠다.”</li>
</ul>
</li>
<li>쿠버네티스는 <strong>현재 상태</strong>와 <strong>선언된 상태(desired state)</strong>가 <strong>일치하는지를 지속적으로 체크</strong>압니다. 만약 두 상태가 다르다면, 선언된 상태에 맞게 복구될 수 있도록 필요한 조치를 취하게 됩니다.</li>
<li>조금 더 자세하게 설명하면, 쿠버네티스의 모든 것은 <strong>Objects</strong>와 <strong>Controller</strong>를 중심으로 돌아갑니다.<ul>
<li>쿠버네티스는 <strong>클러스터의 상태</strong>를 나타내기 위해 이 <strong>Objects</strong> 이용합니다(클러스터의 상태를 나타내는 단위입니다.).<ul>
<li>즉, Objects는 사용자의 의도를 담은 레코드입니다. → Objects를 생성함으로써 클러스터의 워크로드를 어떤 형태로 보이고 싶은지를 효과적으로 쿠버네티스에게 전달할 수 있습니다.<ul>
<li>ex) 어떤 컨테이너화된 애플리케이션이 동작 중인지, 어떤 상태인지, 그 애플리케이션이 이용할 수 있는 리소스, 어떤 의도로 작성되었는지 등</li>
</ul>
</li>
<li>주요 Objects로는 Pod, ReplicaSet, Deployments, Service, Volume 등이 있습니다.<ul>
<li><strong>Pod</strong> : 쿠버네티스에서 배포할 수 있는 <strong>가장 작은 단위</strong>로, 한 개 이상의 컨테이너와 스토리지, 네트워크 속성을 가집니다. Pod에 속한 컨테이너는 <strong>스토리지</strong>와 <strong>네트워크</strong>를 <strong>공유</strong>하고, 서로 localhost로 접근할 수 있습니다.</li>
<li><strong>ReplicaSet</strong> : <strong>Pod</strong>를 한 개 이상 <strong>복제하여 관리</strong>하는 Object입니다. ReplicaSet은 복제할 개수, 개수를 체크할 라벨 선택자, 생성할 Pod의 설정 값 등을 가지고 있습니다. 이를 직접적으로 사용하기보다는 <strong>Deployments 등 다른 오브젝트에 의해 사용</strong>되는 경우가 많습니다.</li>
<li><strong>Service</strong> : <strong>네트워크</strong>와 관련된 오브젝트입니다. Pod를 <strong>외부 네트워크와 연결</strong>해주고, 여러 개의 Pod를 바라보는 <strong>내부 로드밸런서를 생성</strong>할 떄 사용합니다. 내부 DNS에 서비스 이름을 도메인으로 등록하기 떄문에 <strong>서비스 디스커버리</strong> 역할도 합니다.</li>
<li><strong>Volume</strong> : <strong>저장소</strong>와 관련된 오브젝트입니다. 호스트 디렉토리를 그대로 이용하거나 클라우드 스토리지를 동적으로 생성하여 사용할 수 있습니다.</li>
</ul>
</li>
<li>Objects에 대한 명세(Spec)은 주로 YAML로 정의합니다.<ul>
<li>오브젝트의 종류와 원하는 상태를 입력합니다.</li>
<li>이러한 명세는 생성, 조회, 삭제로 관리할 수 있기 때문에 REST API로 쉽게 노출할 수 있습니다.</li>
</ul>
</li>
<li>애플리케이션을 배포하기 위해선 원하는 상태(<strong>desired state</strong>)를</li>
</ul>
</li>
<li>반면에 <strong>Controller</strong>는 클러스터의 <strong>실제 상태와 원하는 상태를 관찰하는 무한 루프</strong>입니다. 이 두 상태가 벌어지면 컨트롤러는 클러스터의 현재 상태를 원하는 상태에 더 가깝게 만들기 위해 변경을 시작합니다.</li>
</ul>
</li>
</ul>
<h3 id="클러스터와-마스터-노드">클러스터와 마스터-노드</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/0a3cc61e-fde4-4afb-8943-63e2c1d819ff/image.png" alt=""></p>
<ul>
<li>쿠버네티스에서는 전체 물리 리소스를 클러스터 단위로 추상화하여 관리합니다.<ul>
<li>사용자는 이 클러스터 단위로 쿠버네티스를 사용하여, 물리적으로는 여러 대의 서버가 분리되어 있어도, 사용자의 입장에서는 하나의 서버를 사용하는 것처럼 사용할 수 있게 됩니다.</li>
<li>쿠버네티스 클러스터는 즉, 컨테이너화된 애플리케이션을 실행하는 <a href="https://kubernetes.io/ko/docs/concepts/architecture/nodes/">노드</a>(워커)들의 집합입니다.</li>
<li>모든 클러스터는 최소 한 개의 워커 노드를 가집니다.</li>
</ul>
</li>
<li>API 서버는 json 또는 protobuf 형식을 사용하여 http 통신을 지원하지만, 편하게 사용하기 위해 주로 <code>kubectl</code>이라는 CLI 도구를 사용합니다.</li>
<li>클러스터 내부에는 클러스터의 구성 요소들에 대해 제어 권한을 가진 컨트롤 플레인(Control Plane) 역할의 마스터 노드(Master Node)를 두게 되며, 관리자는 이 마스터 노드를 이용하여 클러스터 전체를 제어합니다.<ul>
<li>모든 명령은 마스터의 API 서버를 호출하고, 노드는 마스터와 통신하면서 필요한 작업을 수행합니다.</li>
<li>따라서 마스터 노드에는 엄격한 보안 설정이 필요하며, 고가용성을 위해 여러 대를 구성하기도 합니다.</li>
</ul>
</li>
<li>Comtrol Plane의 구성은 다음과 같습니다.<ul>
<li><strong>API</strong> : kubectl 요청 뿐 아니라 내부 노드까지의 모든 요청을 처리하는 모듈입니다.<ul>
<li>실제로는 원하는 상태(desire state)를 key-value 저장소에 저장하고, 저장된 상태를 조회하는 일을 합니다.</li>
<li>권한을 체크하여 요청을 거부할 수 있습니다.</li>
<li>디버거 역할도 수행합니다.</li>
<li>❗️Pod을 할당하고 상태를 체크하는 것은 다른 모듈이 합니다.</li>
</ul>
</li>
<li><strong>etcd</strong> : RAFT 알고리즘을 이용한 <strong>key-value 저장소</strong>입니다.<ul>
<li>여러 개로 분산하여 복제함으로써 안정성을 높였으며, 속도도 빠릅니다.</li>
<li>클러스터의 모든 설정, 상태 데이터는 여기에 저장됩니다. 따라서 etcd만 잘 백업하면 언제든지 클러스터를 복구할 수 있습니다.</li>
<li>etcd는 오직 API 서버와 통신합니다.</li>
</ul>
</li>
<li><strong>Scheduler</strong> : 할당되지 않은 Pod를 여러가지 조건(자원, 라벨)에 따라 적절한 노드 서버에 할당해주는 모듈입니다.</li>
<li><strong>CM(Controller Manager)</strong> : CM(Controller Manager) :  현재 상태를 desired 상태로 유지하기 위해 쿠버네티스에 있는 거의 모든 <strong>오브젝트의 상태</strong>를 관리합니다. CM은 내부에 아주 다양한 컨트롤러들을 포함하고 관리하는 데몬이며, 이들 컨트롤러들은 오브젝트별로 철저히 분업화되어 있습니다.</li>
<li><strong>CCM(Cloud Controller Manager)</strong> : 클라우드(AWS, Azure, GCP)에 특화된 모듈입니다. 클라우드별 제어 로직을 포함하고 있으며, 각 클라우드 업체에서 자체 모듈을 만들어서 제공하고 있습니다.</li>
</ul>
</li>
<li>Node의 구성은 다음과 같습니다.<ul>
<li><strong>kubelet</strong> : Control Plane의 API 서버가 전달해준 명령을 받고, 본인 노드의 현재 상태를 다시 API에 전달하는 역할입니다.<ul>
<li>노드에 할당된 Pod의 생명주기를 관리합니다(컨테이너 생성 등).</li>
</ul>
</li>
<li><strong>kube-proxy</strong> : kublet이 Pod를 관리하면, proxy는 Pod로 연결되는 네트워크를 관리합니다.<ul>
<li>여러 개의 Pod를 라운드 로빈(RR) 형태로 묶어 서비스를 제공할 수 있습니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="pod-생성-과정">Pod 생성 과정</h2>
<p>이제 쿠버네티스의 핵심 컨셉을 바탕으로, Pod가 생성될 떄 이것들이 어떻게 작동하는 지를 시퀀스 다이아그램을 통해 알아보도록 하겠습니다. </p>
<p>[다이아그램 출처 - <a href="https://blog.heptio.com/core-kubernetes-jazz-improv-over-orchestration-a7903ea92ca">Core Kubernetes: Jazz Improv over Orchestration</a>]</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/fac8ccb2-a07c-4031-9cf0-4170e93c5843/image.png" alt=""></p>
<ol>
<li>사용자의 요청 <code>create Pod</code>이 API Server로 왔습니다. </li>
<li>API는 요청을 etcd에 기록합니다.</li>
<li>API 서버를 통해 이를 지켜보고 있던 Scheduler는 해당 Pod를 배치할 노드를 선택하고,</li>
<li>해당 정보를 다시 API 서버를 통해 etcd에 기록합니다.</li>
<li>kublet은 API Server를 통해 etcd에 새로운 이벤트가 있다는 것을 감지하고, 이에 맞게 컨테이너를 구동합니다.</li>
<li>마지막 API Server를 통해 구동된 컨테이너 정보를 다시 etcd에 기록합니다.</li>
</ol>
<h2 id="replicaset-생성-과정">ReplicaSet 생성 과정</h2>
<p>이번에는 쿠버네티스의 핵심 컨셉을 바탕으로, ReplicaSet이 생성될 떄 이것들이 어떻게 작동하는 지를 시퀀스 다이아그램을 통해 알아보도록 하겠습니다. </p>
<p>[다이아그램 출처 - <a href="https://www.containerlabs.kubedaily.com/Kubernetes/beginner/Sequential-Breakdown-of-the-Process.html#sequential-breakdown-of-the-process">Sequential Breakdown of the Process</a>]</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/c479ede7-eefd-4687-8ce2-dfa53e13ffd5/image.png" alt=""></p>
<p>(현재 3개의 Pod가 운영중이라고 가정합니다.)</p>
<ol>
<li>Kubernetes 클라이언트는 API Server로 <code>replicaset.yaml</code>파일에 정의된 ReplicaSet생성을 요청합니다.</li>
<li>해당하는 컨트롤러는(ReplicaSet Controller) 관련 정보를 모니터링 하다가 새로운 ReplicaSet 객체가 있음을 감지하였습니다.</li>
<li>파일의 replica 복제본 값이 (예를 들어) 5라고 구성되었기 때문에, 컨트롤러는 Pod를 5개로 정의합니다.</li>
<li>API 서버를 통해 이를 지켜보고 있던 Scheduler는 할당되지 않은 두 개의 Pod가 있음을 감지하였습니다.</li>
<li>Scheduler는 Pod를 할당할 노드를 결정하고, 해당 정보를 Api Server에 보냅니다.</li>
<li>이를 주시하던 kublet은 API Server를 통해 두 개의 Pod가 새롭게 할당되었음을 감지합니다.</li>
<li>Kubelet은 Docker에 해당하는 컨테이너 생성을 요청합니다.</li>
<li>마지막으로 Kubelet은 API 서버에 업데이트된 Pod 상태를 보냅니다.</li>
</ol>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://kubernetes.io/ko/docs/concepts/overview/what-is-kubernetes/">https://kubernetes.io/ko/docs/concepts/overview/what-is-kubernetes/</a></li>
<li><a href="https://seongjin.me/kubernetes-core-concepts/">https://seongjin.me/kubernetes-core-concepts/</a></li>
<li><a href="https://iximiuz.com/en/posts/kubernetes-operator-pattern/">https://iximiuz.com/en/posts/kubernetes-operator-pattern/</a></li>
<li><a href="https://www.hpe.com/kr/ko/what-is/container-orchestration.html">https://www.hpe.com/kr/ko/what-is/container-orchestration.html</a></li>
<li><a href="https://www.sharedit.co.kr/posts/12040">https://www.sharedit.co.kr/posts/12040</a></li>
<li><a href="https://subicura.com/2019/05/19/kubernetes-basic-1.html?utm_source=subicura.com&amp;utm_medium=referral&amp;utm_campaign=k8s">https://subicura.com/2019/05/19/kubernetes-basic-1.html?utm_source=subicura.com&amp;utm_medium=referral&amp;utm_campaign=k8s</a></li>
<li><a href="https://blog.heptio.com/core-kubernetes-jazz-improv-over-orchestration-a7903ea92ca">https://blog.heptio.com/core-kubernetes-jazz-improv-over-orchestration-a7903ea92ca</a></li>
<li><a href="https://www.containerlabs.kubedaily.com/Kubernetes/beginner/Sequential-Breakdown-of-the-Process.html#sequential-breakdown-of-the-process">https://www.containerlabs.kubedaily.com/Kubernetes/beginner/Sequential-Breakdown-of-the-Process.html#sequential-breakdown-of-the-process</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Ops] CI/CD란?]]></title>
            <link>https://velog.io/@whattsup_kim/CI-CD%EB%9E%80</link>
            <guid>https://velog.io/@whattsup_kim/CI-CD%EB%9E%80</guid>
            <pubDate>Fri, 28 Oct 2022 10:52:30 GMT</pubDate>
            <description><![CDATA[<h2 id="cicd의-이해">CI/CD의 이해</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/5bf2ae81-e293-4712-a6ed-9a2d93293996/image.png" alt="">
*<em>CI/CD의 목표 *</em></p>
<ul>
<li>반복 작업의 자동화 및 피드백 루프 단축 등을 통해 소프트웨어 릴리스 프로세스의 속도를 개선하는 것이다.</li>
<li>이는 짧은주기의 개발단위를 반복하며, 많은 협력과 피드백을 필요로 하는 애자일의 원칙을 실현하는 데 핵심적인 역할을 한다.</li>
</ul>
<h3 id="cicontinuous-integration-지속적-통합란">CI(Continuous Integration, 지속적 통합)란</h3>
<ul>
<li><p><strong>Continuous Integration</strong>이란 자동화된 빌드 및 테스트를 수행하고, 코드 변경사항을 중안 리포지토리에 정기적으로 병합하는 것
  → <strong>자동화된 구성</strong>(CI &amp; 빌드 서비스) + <strong>문화적 구성</strong>(빈번하게 통합하도록 하는 것)</p>
</li>
<li><p>커밋된 변경사항이 자동으로 빌드 &amp; 테스트되어 공유 리포지토리에 병합(빌드)됨으로써 MSA 환경에서 작업 시 기능 충돌 발생 방지 등을 할 수 있음</p>
</li>
</ul>
<h3 id="cdcotinuous-delivery--deployment-지속적-전달--배포란">CD(Cotinuous Delivery &amp; Deployment, 지속적 전달 &amp; 배포)란</h3>
<ul>
<li><strong>Continous Delivery</strong>란 프로덕션에 릴리스하기 위한 코드 변경이 자동으로 준비되는 소프트웨어 개발 방식을 의미<ul>
<li>단순한 유닛 테스트 외에도 다양한 테스트를 자동화함</li>
<li>테스트 : UI 테스트, 로드 테스트, 통합 테스트, API 안정성 테스트 등 (클라우드에서는 테스트 환경 구성이 용이함)</li>
</ul>
</li>
<li><strong>Continous Deployment</strong>는 명시적 승인 없이 자동으로 배포까지 진행된는 것<ul>
<li>Continous Delivery에서 실제 배포는 선택적이나, Continous Deployment에서는 그렇지 않음</li>
<li>따라서 높은 수준의 테스트에 대한 확신이 필요하며, 추가적으로 모니터링 도구가 필요할 수 있음</li>
</ul>
</li>
</ul>
<h2 id="aws에서-제공하는-cicd도구들">AWS에서 제공하는 CI/CD도구들</h2>
<p>AWS Code Series를 사용하여 AWS에서 CI/CD를 자동화할 수 있다.</p>
<ul>
<li>AWS Code Series : AWS의 CodeCommit, CodeBuild, CodeDeploy, CodePipeline을 통칭하는 단어</li>
</ul>
<h3 id="aws-code-series를-사용하여-마이크로서비스용-cicd-파이프라인-및-amazon-ecs-클러스터-자동-구축-예시">AWS Code Series를 사용하여 마이크로서비스용 CI/CD 파이프라인 및 Amazon ECS 클러스터 자동 구축 예시</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/f83658dd-174d-4c14-82f5-f33776911a78/image.png" alt=""></p>
<ol>
<li>개발자는 <strong>CodeCommit</strong> 리포지토리에 코드를 커밋</li>
<li>code pipeline이 trigger됨</li>
<li><strong>Code Build</strong>는 컨테이너 이미지 빌드 &amp; 간단한 유닛 테스트 &amp; ECR에 이미지 저장  </li>
<li>CodePipeline은 비프로덕션 ECS 클러스터의 기존 Fargate 서비스에 새 이미지를 배포함 // CI 끝 &amp; CD 시작</li>
<li>ECS는 ECR 리포지토리에서 비프로덕션 Fargate 서비스로 이미지를 가져옴</li>
<li>dev환경에 배포 → 각종 QA &amp; Testing 진행  (비프로덕션 URL을 사용하여 수행)</li>
<li>테스트가 완료되었으면, 관리자의 릴리스 승인을 기다리게 됨</li>
<li>승인이 되면, CodePipeline은 프로덕션 ECS 클러스터의 기존 Fargate 서비스에 새 이미지를 배포</li>
<li>ECS는 ECR 리포지토리에서 프로덕션 Fargate 서비스로 이미지를 가져옴 </li>
<li>프로덕션 사용자는 프로덕션 URL을 사용하여 기능에 엑세스함</li>
</ol>
<blockquote>
<p><strong>VPC(Virtual Private Cloud)란?</strong></p>
<ul>
<li>가상 사설망</li>
<li>VPC를 적용하면 VPC별로 네트워크를 구성할 수 있고, 각 VPC별로 다르게 네트워크 설정을 줄 수 있음. 또한 각 VPC는 완전히 독립된 네트워크처럼 동작함.</li>
</ul>
</blockquote>
<h2 id="devsecops">DevSecOps</h2>
<h4 id="배경">배경</h4>
<ul>
<li>전통적으로, 소프트웨어 보안 운영은 소프트웨어를 만드는 데 필요한 다른 프로세스와는 별개로 수행되었다.<ul>
<li>개발자는 코드를 작성하고나서 보안에 대해 별로 생각하지 않고 코드를 배포하였고, 소프트웨어를 만들고 프로덕션 환경에 배치한 후에야 보안 엔지니어가 코드 안에서 또는 코드를 호스팅하는 환경 안에서 잠재적 취약점을 확인하였음.</li>
</ul>
</li>
<li>이러한 환경은 보안과 DevOps의 효율을 떨어뜨렸고, 이에 소프트웨어 전달 프로세스의 모든 단계에 보안을 통합함으로써 이러한 문제를 해결하고자 한 것이 바로 DevSecOps인 것이다.<h4 id="devsecops란">DevSecOps란</h4>
</li>
<li>DevOps의 핵심 개념을 <strong>보안까지 포함하도록 확장</strong>하는 것이다.</li>
<li>효과적인 DevSecOps는 DevOps를 수용하고 전체 CI/CD 개발 파이프라인에 <strong>보안을 통합</strong>하는 것을 의미한다.</li>
<li>즉, DevSecOps는 소프트웨어 <strong>보안</strong>을 전체 <strong>소프트웨어 전달 프로세스</strong>의 <strong>핵심 부분</strong>으로 만드는 개념을 말한다. (DevOps + Security)</li>
</ul>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://aws.amazon.com/ko/devops/continuous-delivery/">https://aws.amazon.com/ko/devops/continuous-delivery/</a></li>
<li><a href="https://aws.amazon.com/ko/devops/continuous-delivery/">https://aws.amazon.com/ko/devops/continuous-delivery/</a></li>
<li><a href="https://www.jetbrains.com/ko-kr/teamcity/ci-cd-guide/continuous-integration-vs-delivery-vs-deployment/">https://www.jetbrains.com/ko-kr/teamcity/ci-cd-guide/continuous-integration-vs-delivery-vs-deployment/</a></li>
<li><a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/automatically-build-ci-cd-pipelines-and-amazon-ecs-clusters-for-microservices-using-aws-cdk.html">https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/automatically-build-ci-cd-pipelines-and-amazon-ecs-clusters-for-microservices-using-aws-cdk.html</a></li>
<li><a href="https://www.paloaltonetworks.co.kr/cyberpedia/what-is-devsecops">https://www.paloaltonetworks.co.kr/cyberpedia/what-is-devsecops</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[기본적인 웹 아키텍처 개념들]]></title>
            <link>https://velog.io/@whattsup_kim/%EA%B8%B0%EB%B3%B8%EC%A0%81%EC%9D%B8-%EC%9B%B9-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B0%9C%EB%85%90%EB%93%A4-bclu6cjr</link>
            <guid>https://velog.io/@whattsup_kim/%EA%B8%B0%EB%B3%B8%EC%A0%81%EC%9D%B8-%EC%9B%B9-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B0%9C%EB%85%90%EB%93%A4-bclu6cjr</guid>
            <pubDate>Wed, 26 Oct 2022 08:33:37 GMT</pubDate>
            <description><![CDATA[<h2 id="기본적인-웹-아키텍처-개념들">기본적인 웹 아키텍처 개념들</h2>
<p><a href="https://medium.com/storyblocks-engineering/web-architecture-101-a3224e126947">Web Architecture 101</a>에서 저자는 다음과 같이 11가지의 아케틱처 개념들을 설명하고 있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/b44eba0f-3b09-47b0-bc1f-e39f4a9c8ef1/image.png" alt=""></p>
<p>위 다이어그램은 저자가 속한 <a href="https://www.storyblocks.com/">Storyblocks</a>의 아키텍처를 나타낸 것이다. 예시를 바탕으로 위 다이어그램을 따라가보자.</p>
<ol>
<li>사용자가 구글에 어떤 사진을 검색하면 Storyblocks 사이트의 사진이 노출된다. 사용자가 해당 사진을 클릭하면, 사용자의 브라우저는 내부적으로 Storyblocks에 접속하는 방법을 물어보기 위해 <strong>DNS</strong>서버에 요청을 보낸 다음 Storyblocks에 접속 요청을 한다.</li>
<li>접속 요청은 이를 처리하기 위해 <strong>로드밸런서</strong>에 도착하고, 로드밸런서는 당시 동작중인 10여 개의 서버 중 하나를 랜덤하게 선택하여 요청을 처리한다. <strong>웹 서버</strong>는 <strong>캐싱 서비스</strong>에서 이미지에 대한 일부 정보를 가져오고, <strong>데이터베이스</strong>에서 해당 이미지에 대한 나머지 데이터를 가져온다. 이미지의 color profile이 아직 계산되지 않았으므로 color profile job을 <strong>잡 큐</strong>에 보낸다. <strong>잡 서버</strong>는 이를 비동기적으로 처리하여 결과를 데이터베이스를 업데이트한다.</li>
<li>이제, 사용자는 사진 제목을 입력으로 사용하여 <strong>Full text search service</strong>에 요청을 보내 유사한 사진을 찾으려고 한다. 사용자가 현재 회원으로 로그인했기 떄문에 계정 <strong>서비스</strong>에서 계정 정보를 조회한다. 마지막으로 <strong>Data firehose</strong>에 페이지 뷰 이벤트를 발생시켜서 <strong>클라우드 스토리지</strong>에 기록하고, 분석가들이 사용할 수 있도록 <strong>Data warehouse</strong>에 로드한다.</li>
<li>이제 <strong>서버</strong>는 view를 HTML로 렌더링하고, <strong>로드 밸런서</strong>를 통해 사용자의 브라우저로 보낸다. 페이지는 Javascript와 CSS 파일을 포함하고 있고, 이들은 CDN에 연결되어 있다. 사용자 브라우저는 CDN을 통해 이런 콘텐츠를 받고, 최종적으로 사용자가 볼 수 있게 페이지를 렌더링한다.</li>
</ol>
<h3 id="1-dns">1. DNS</h3>
<p>DNS(Domain Name Server)는 월드 와이드 웹을 가능하게 해주는 기술이다. 가장 기본적인 수준의 DNS는 도메인 이름(google.com)에서 해당하는 IP주소로의 키/값 조회를 제공한다. 즉, 컴퓨터의 요청을 해당 키에 맞는 적절한 서버 IP로 보내주는 것이다. </p>
<h3 id="2-로드-벨런서">2. 로드 벨런서</h3>
<p>로드 밸런싱에 대해 자세히 알아보기 전에, 수평적 vs 수직적 애플리케이션 확장(scaling)에 관해서 얘기해보자. StackOverflow의 답변에 따르면 수평적 확장은 더 많은 장치를 새로 추가하는 것이고, 수직적 확장은 이미 사용하고 있던 장치의 성능을 업그레이드 하는 것이다. 이때, 웹 개발에서는 대부분 수평적 확장을 원한다. 이유는 다음과 같다.</p>
<ol>
<li>서비스 중단을 막기 위해서이다. 서버는 언제든지 고장날 수 있고, 네트워크 속도는 느려질 수 있으며, 데이터 센터에 화재가 발하여 오프라인 상태가 될 수 있다. 이럴 때 서버가 두 개 이상 있으면 서비스가 계속 실행되도록 할 수 있다.</li>
<li>수평적 확장은 백엔드 앱(웹 서버, DB, …)들을 각각 다른 서버에서 실행함으로써 서로가 최소한으로만 부딪힐 수 있도록 한다. </li>
<li>수직적인 확장에는 한계가 있다. Storyblocks를 예로 들자, 서비스를 위해 150 ~ 400 개의 AWS EC2 인스턴스를 동시에 실행한다. 수직 확장을 통해 이러한 성능을 제공하는 것은 어려울 것이다.</li>
</ol>
<p>로드 벨런서는 이러한 수평 확장을 가능하게 하는 기술이다. 로드 밸런서는 들어오는 요청을 복제/미러링된 많은 서버 중 하나로 연결하고 서버의 응답을 다시 클라이언트로 보내는 역할을 한다. 이 떄, 각 서버에 과부하가 걸리지 않도록 요청을 적절히 분배해주는 일을 하는 것이다. </p>
<h3 id="3-웹-애플리케이션-서버">3. 웹 애플리케이션 서버</h3>
<p>웹 애플리케이션 서버는 사용자들의 요청을 처리하고 결과를 HTML에 담아 사용자의 브라우저로 다시 보내는 핵심 비즈니스 로직을 실행한다. 이를 위해 DB, 캐시, 잡큐 등 다양한 백엔드 인프라와 데이터를 주고받아야 한다.</p>
<p>앱 서버 구현을 위해서는 특정 언어(Node.sj, Ruby, PHP, Scalal, Java, ..)와 해당 언어에 대한 웹 MVC 프레임워크(Express for Node.js, Ruby on Rail, Play for Scala, Laravel for PHP, …)를 선택해야 한다. </p>
<h3 id="4-데이터베이스-서버">4. 데이터베이스 서버</h3>
<p>모든 최신 웹 애플리케이션은 하나 이상의 데이터베이스를 활용하여 정보를 저장한다. 데이터베이스는 데이터 구조를 정의하고, 데이터를 삽입/찾기/수정/삭제/연산 등의 역할을 한다. </p>
<h3 id="5-캐싱-서비스">5. 캐싱 서비스</h3>
<p>캐싱 서비스는 정보를 거의 O(1) 시에 정보를 저장하고 조회할 수 있는 간단한 키/값 형태의 데이터 저장소를 제공한다. 프로그램은 이 캐싱 서비스를 활용하여 비용이 많이 드는 계산 결과를 저장함으로써 다음 번 검색에서의 효율을 높인다. </p>
<p>애플리케이션은 데이터베이스 쿼리, 외부 서비스 호출 결과, 지정된 URL의 HTML 등을 캐시에 저장한다. 실무에서는 다음과 같이 사용된다.</p>
<ul>
<li>구글은 사용자들이 자주 검색하는 검색 결과를 캐시에 저장한다.</li>
<li>페이스북은 게시물 데이터, 친구 목록 등과 같이 로그인할 때 표시되는 많은 데이터들을 캐싱한다.</li>
<li>Storyblocks에서는 React 서버 사이드 렌더링으로 생성된 HTML, 검색 결과, 검색어 입력 자동완성 결과 등을 캐싱한다.</li>
</ul>
<p>가장 널리 사용되는 캐싱 서버 스택은 Redis와 Memcache이다.</p>
<h3 id="6-잡-큐job-queue--서버">6. 잡 큐(job queue) &amp; 서버</h3>
<p>대부분의 웹 애플리케이션은 사용자 요청에 대한 응답과는 직접적인 관련이 없는 작업을 백그라운드에서 비동기적으로 실행할 필요가 잆다. 예를 들어, 구글의 검색 엔진은 비동기적으로 (정기적으로) 웹을 크롤링하고 있으며, 누군가 검색을 요청하면, 이러한 결과를 보여준다.</p>
<p>비동기 작업에 가장 널리 사용되는 것이 잡 큐 아키텍처이다. 간단하게 설명하면, 잡 서버는 큐에서 할 일이 있는지 확인하고, 있다면 큐에서 잡을 뽑아내어 실행한다.</p>
<h3 id="7-전체-텍스트-검색-서비스">7. 전체 텍스트 검색 서비스</h3>
<p>많은 웹 앱에서는 사용자가 텍스트를 입력(쿼리)하면, 검색을 하고 가장 관련있는 결과를 보여주는 기능을 제공한다. 이 기능이 바로 전체 텍스트 검색 서비스이다. 전체 텍스트 검색에서는 쿼리 키워드를 포함하는 문서를 빨리 찾기 위해 inverted index를 활용한다.(아래 그림을 보면, <code>in</code> <code>the</code> <code>with</code>와 같은 것들은 포함되지 않는다.)</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/260dfd3c-d0c7-42cd-8736-a2e68135953e/image.png" alt=""></p>
<p>오늘날 가장 인기 있는 검색 플랫폼은 Elasticsearch이지만, Sphinx 또는 Apache Solr와 같은 많은 선택지도 있다.</p>
<h3 id="8-서비스">8. 서비스</h3>
<p>앱이 특정 규모에 도달하면, 별도의 애플리케이션으로 실행되도록 ‘서비스’가 생기게 된다. 서비스는 외부에 바로 노출되진 않지만, 다른 앱들과 연동된다.</p>
<ul>
<li>예를 들어, 결제 서비스는 고객이 카드로 결제할 수 있는 인터페이스를 제공한다.</li>
</ul>
<h3 id="9-데이터">9. 데이터</h3>
<p>오늘날 거의 모든 앱들은 일정 규모에 도달하면 데이터 파이프라인을 활용하여 데이터 수집, 저장 및 분석을 할 수 있도록 한다. 일반적으로 데이터 파이프라인은 3단계를 거친다.</p>
<ol>
<li>앱은 사용자 상호작용에 대한 이벤트를 firehose로 보낸다. firehose는 데이터 처리를 위한 스트리밍 인터페이스를 제공한다. 대표적은 플랫폼으로는 Kafka가 있다.</li>
<li>원시 데이터와 전처리된 데이터들은 클라우드 스토리지에 저장된다. </li>
<li>전처리된 데이터는 종종 분석을 위해 데이터 웨어하우스에 로드된다. 대표적인 플랫폼으로는 RedShift가 있다. 만약 데이터가 충분히 있다면 분석을 위해 Hadoop 등과 같은 기술이 분석을 위해 필요하게 된다.</li>
</ol>
<p>위에서 제시한 다이어그램에서 표시되지 않은 또 다른 단계는 바로 서비스의 운영 데이터베이스에서 데이터 웨어하우스로 데이터를 로드하는 과정이다. </p>
<ul>
<li>예를 들어, Storyblocks에서는 Video Blacks, AudioBlocks, … 등의 데이터베이스를 매일 밤 Redshift로 불러온다.</li>
</ul>
<h3 id="10-클라우드-스토리지">10. 클라우드 스토리지</h3>
<p>AWS에서는 클라우드 스토리지를 인터넷을 통해 데이터를 저장, 접근, 공유할 수 있는 단순하고 확장성 있는 방법이라고 말한다. RESTful API와 HTTP를 사용하여 언제든지 클라우드에 접근하고 저장할 수 있다.</p>
<p>대표적인 플랫폼으로는 아마존 S3가 있다.</p>
<h3 id="11-cdn">11. CDN</h3>
<p>CDN(Content Delivery Network)는 지리적으로 분산된 여러 개의 서버이다. 웹 콘텐츠를 사용자와 가까운 곳에서 전송함으로써 전송 속도를 높인다. HTML, CSS, javascript, image와 같은 정적인 데이터를 수 많은 엣지(edge) 서버에 분산시키는 형태로 동작한다.</p>
<ul>
<li>사용자는 데이터를 원본 서버 대신 가장 가까운 엣지 서버에서 다운로드한다.</li>
</ul>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://medium.com/storyblocks-engineering/web-architecture-101-a3224e126947">https://medium.com/storyblocks-engineering/web-architecture-101-a3224e126947</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[애플리케이션 아키텍처란]]></title>
            <link>https://velog.io/@whattsup_kim/%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%9E%80</link>
            <guid>https://velog.io/@whattsup_kim/%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%9E%80</guid>
            <pubDate>Wed, 26 Oct 2022 08:31:29 GMT</pubDate>
            <description><![CDATA[<p>본 글에서는 애플리케이션 아키텍처와 관련된 개념들을 간략하게나마 정리한다.</p>
<h2 id="애플리케이션-아키텍처란">애플리케이션 아키텍처란?</h2>
<p>애플리케이션 아키텍처는 애플리케이션을 설계하고 <strong>구축</strong>하는 데 사용하는 <strong>패턴</strong>과 기술을 설명한다.</p>
<ul>
<li>아키텍처는 애플리케이션을 구축할 때 따라야 할 로드맵과 모범 사례를 제공하여 체계적으로 구성된 애플리케이션을 완성할 수 있게 해준다.</li>
<li>여기서 <strong>패턴</strong>은 문제에 대한 반복 가능한 솔루션을 의미한다.</li>
<li>애플리케이션 아키텍처 종류로는 N계층, 웹-큐 작업자, 마이크로 서비스, 이벤트 기반 아키텍처, 빅 데이터(빅 컴퓨팅) 등이 있다.</li>
</ul>
<h3 id="n-tier-아키텍처">N-tier 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/0e553724-60cb-42fb-baff-72d0a470fc8a/image.png" alt=""></p>
<ul>
<li>N계층 아키텍처는 애플리케이션을 <strong>논리적 레이어</strong>와 <strong>물리적 계층</strong>으로 나눈다.</li>
<li>레이어는 책임을 구분하고 종속성을 관리하는 방법이다.<ul>
<li>레이어마다 특정 책임이 있음</li>
<li>상위 레이어는 하위 레이어의 서비스를 사용할 수 있지만 하위 레이어는 상위 레이어의 서비스를 사용할 수 없음</li>
</ul>
</li>
<li>N계층 아키텍처는 <strong>폐쇄형 레이어 아키텍처</strong> 또는 <strong>개방형 레이어 아키텍처</strong>를 사용할 수 있다.<ul>
<li>폐쇄형 : 자신과 맞닿아있는 하위 레이어로만 접속이 가능</li>
<li>개방형 : 하위에 있는 모든 레이어로 접속이 가능</li>
</ul>
</li>
<li>계층은 물리적으로 분리되어 별도의 시스템에서 실행된다.<ul>
<li>계층은 다른 계층을 직접 호출하거나 비동기 메시징(메시지 큐)을 사용할 수 있음.</li>
<li>계층을 물리적으로 분리하면 확장성과 복원력이 향상되지만 추가 네트워크 통신으로 인해 대기 시간도 증가함.</li>
<li>예를 들어, 3계층 애플리케이션에는 프레젠테이션 계층, 중간 계층(비즈니스 논리 처리), 데이터베이스 계층이 있음(중간 계층은 선택 사항).</li>
</ul>
</li>
<li>전통적인 비즈니스에서 많이 사용되던 아키텍처이지만, 모노로틱(내부 요소 간 의존성이 강함)한 특성때문에 최근에는 조금 지양되고 있다.</li>
</ul>
<h3 id="web-queue-worker-아키텍처">Web-Queue-Worker 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/972b8ea9-8d33-414d-94d4-45f1ca5d689b/image.png" alt=""></p>
<ul>
<li>애플리케이션의 웹 프론트엔드는 HTTP 요청을 처리하고 백엔드 작업자(worker)는 CPU 집약적인 작업이나 장기 실행 작업을 수행한다. 이때, 프론트엔드는 비동기 메시지 큐(Queue)를 통해 작업자(worker)와 통신한다.</li>
<li>Web-Queue-Worker 아키텍처는 보통 다음과 같은 구성을 갖고 있다.<ul>
<li>하나 이상의 데이터베이스</li>
<li>빠른 읽기를 위해 데이터베이스의 값을 저장하는 캐시</li>
<li>정적 콘텐츠를 바로 제공하기 위한 CDN(Content Delivery Network)</li>
<li>원격 서비스(ex. 전자 메일 또는 SMS 서비스)</li>
<li>인증을 위한 Identity Provider</li>
</ul>
</li>
<li>웹과 워커는 둘 다 상태 비저장이다.</li>
<li>세션 상태는 분산된 캐시에 저장할 수 있다.</li>
<li>모든 장기 실행 작업은 작업자에 의해 비동기적으로 수행된다.<ul>
<li>작업자는 큐의 메시지에 의해 동작(trigger)되거나, 일정에 따라 실행된다.</li>
<li>(장기 실행 작업이 없는 경우 작업자를 생략할 수 있음)</li>
</ul>
</li>
<li>프론트엔드는 웹 API 형태로 구성될 수 있다.</li>
</ul>
<h3 id="마이크로-서비스-아키텍처">마이크로 서비스 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/218ea71a-2899-4135-9fe8-e59c8f80046f/image.png" alt=""></p>
<ul>
<li>모든 요소를 하나의 애플리케이션에 구축하는 전통적인 모놀리식 접근 방식 대신, 마이크로 서비스는 작고, 독립적이며, 느슨하게 결합되어 있다.</li>
<li>각 서비스는 소규모의 집중 개발 팀에서 구축할 수 있으며, 개별 서비스를 배포할 때 팀 간의 조정이 거의 필요 없으므로 업데이트를 자주 수행할 수 있다(서비스를 독립적으로 배포할 수 있음).</li>
<li>서비스가 잘 정의된 API를 사용하여 서로 통신하며, 각 서비스의 내부 구현 세부 정보는 다른 서비스에서 볼 수 없다.</li>
<li>다중저장소 프로그래밍을 지원한다(모든 서비스가 동일한 기술 스택을 공유할 필요가 없음).</li>
<li>마이크로 서비스는 빠른 릴리스, 빠른 개발, 빠른 혁신, 복원력이 있는 아키텍처이다. 하지만 마이크로 서비스 아키텍처는 N-tier 아키텍처 또는 Web-Queue-Worker 보다 빌드 및 관리 방법이 좀 더 복잡함.<ul>
<li>성숙한 개발 및 DevOps 문화가 필요함</li>
<li>관리/오케스트레이션, API 게이트웨이와 같은 요소가 추가적으로 필요</li>
</ul>
</li>
</ul>
<h3 id="이벤트-기반-아키텍처">이벤트 기반 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/7410d868-606e-4826-b5c9-02b1a49bfc87/image.png" alt=""></p>
<ul>
<li>이벤트 기반 아키텍처는 이벤트 스트림을 생성하는 <strong>이벤트 생성자</strong> 와 이벤트 를 수신하는 <strong>이벤트 소비자로 구성된다.</strong><ul>
<li>생산자는 소비자와 독립적 관계이며, 각 소비자는 서로 독립적 관계이다.</li>
<li>생산자는 수신자가 받던 안 받던 관계없이, 일단 이벤트를 채널에 보내둠.</li>
<li>이벤트가 발생하면, 해당 이벤트를 구독하는 사용자가 이벤트를 수신함(pub/sub 패턴).  <blockquote>
<p><strong>pub/sub 패턴이란?</strong></p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/ea25578c-3f52-4bb0-8f56-735cb5c98c30/image.png" alt=""></p>
<ul>
<li><strong>pub/sub 패턴은</strong> 비동기식 메세징 패턴으로,<ul>
<li><strong>Publisher(발신자)</strong>는 Subscriber(수신자)에 대한 정보를 몰라도 그냥 일단 메세지를 <strong>채널</strong>에 보내놓는다</li>
<li>이 때 메세지에 맞는 <strong>Topic</strong>으로 보내놓으면, 해당 Topic을 구독중인 <strong>Subscriber</strong>에게만 메세지가 가게 된다</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li>이벤트 기반 아키텍처는 지연 시간이 매우 짧은(실시간 처리) 어플리케이션 혹은 이벤트 요청에 다양한 종류의 작업을 처리해야 할 경우 유용하다.</li>
</ul>
<h3 id="빅데이터-아키텍처">빅데이터 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/6702e98e-016c-44bd-b42e-99eb0d660194/image.png" alt=""></p>
<ul>
<li>빅 데이터 아키텍처는 기존의 데이터베이스 시스템에 비해 훨씬 크고 복잡한 데이터의 수집, 처리 및 분석 등을 수행하도록 디자인되었다.</li>
<li>빅데이터 아키텍처는 MLOps에 대해 정리한 <a href="https://velog.io/@whattsup_kim/MLOps%EB%9E%80#mlops-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C">블로그</a>에서 더 자세히 확인할 수 있다.</li>
</ul>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/">https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/</a></li>
<li><a href="https://www.redhat.com/en/topics/cloud-native-apps/what-is-an-application-architecture">https://www.redhat.com/en/topics/cloud-native-apps/what-is-an-application-architecture</a></li>
<li><a href="https://honglab.tistory.com/61">https://honglab.tistory.com/61</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker Compose] Compose Commands]]></title>
            <link>https://velog.io/@whattsup_kim/Docker-Compose-Compose-Commands</link>
            <guid>https://velog.io/@whattsup_kim/Docker-Compose-Compose-Commands</guid>
            <pubDate>Fri, 21 Oct 2022 12:43:28 GMT</pubDate>
            <description><![CDATA[<h1 id="compose-설치">Compose 설치</h1>
<p><a href="https://docs.docker.com/compose/install/">docs</a>를 참고하여 설치를 진행한다.</p>
<pre><code class="language-bash">sudo apt-get update
sudo apt-get install docker-compose-plugin</code></pre>
<h1 id="compose-commands">Compose Commands</h1>
<h2 id="overview">Overview</h2>
<p>본 명령어는 Compose V2를 기준으로 작성함.</p>
<ul>
<li>docker compose 사용법 : <code>docker compose COMMAND</code></li>
</ul>
<p><code>docker compose —help</code></p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/e8f21b82-0717-4495-98da-2f6f9b1570de/image.png" alt=""></p>
<h3 id="-f-옵션">-f 옵션</h3>
<p><code>-f</code>옵션을 사용하여 Compose 파일을 직접 지정할 수 있다.</p>
<ul>
<li><code>-f</code>옵션을 사용하지 않으면, Compose는 <code>compose.yml</code>파일을  찾기 위해 작업 디렉토리와 부모 티렉토리를 탐색함.</li>
<li><code>-f</code>옵션을 여러 번 사용하여 여러 Compose파일을 제공할 수 있음<ul>
<li>이때, Compose 파일을 제공한 순서대로 빌드를 진행함.</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db</code></pre>
<ul>
<li>모든 경로는 현재 작업 디렉토리를 기준으로 상대경로를 사용함.</li>
<li><code>-f</code>옵션 뒤에 절대경로를 지정하여 사용할 수 있음</li>
</ul>
<h3 id="--profile옵션">--profile옵션</h3>
<p><code>—profile</code>을 통해 Compose 파일에서 profile이 등록된 서비스를 활성화시킬 수 있다. </p>
<ul>
<li>등록이 안 된 서비스들은 자동으로 활성화됨</li>
<li>여러 프로필을 활성화할 수 있음</li>
</ul>
<pre><code class="language-bash">docker compose --profile frontend --profile debug up</code></pre>
<h2 id="pull">pull</h2>
<p><code>docker compose pull [OPTIONS] [SERVICE...]</code></p>
<p>docker-compose.yml 내에 명시한 서비스의 이미지를 pull 해온다.</p>
<ul>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--ignore-pull-failures</td>
<td>pull이 실패할 경우 무시</td>
</tr>
<tr>
<td>--include-deps</td>
<td>종속성으로 선언된 이미지를 가져옴</td>
</tr>
<tr>
<td>--quiet , -q</td>
<td>STDOUT 없이 빌드함</td>
</tr>
<tr>
<td>- ex) 서비스 이름이 <code>db</code>일 경우, <code>docker compose pull db</code>를 입력하면 해당하는 이미지를 pull해옴</td>
<td></td>
</tr>
</tbody></table>
<h2 id="build">build</h2>
<p><code>docker compose build [OPTIONS] [SERVICE...]</code></p>
<p>docker-compose.yml 내에 명시한 서비스를 빌드하거나 re-build함</p>
<ul>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--build-arg</td>
<td>build에서 사용하는 arg를 설정</td>
</tr>
<tr>
<td>--no-cache</td>
<td>빌드 시 캐시를 사용하지 않음</td>
</tr>
<tr>
<td>--pull</td>
<td>항상 최신 버전의 이미지를 가져옴</td>
</tr>
<tr>
<td>--quiet , -q</td>
<td>STDOUT 없이 빌드함</td>
</tr>
</tbody></table>
<h2 id="up">up</h2>
<p><code>docker compose up [OPTIONS] [SERVICE...]</code></p>
<p>Docker Compose에 정의되어 있는 모든 서비스 컨테이너를 한 번에 생성하고 실행한다.</p>
<ul>
<li>up프로세스 순서<ol>
<li>서비스 네트워크 설정</li>
<li>(볼륨 생성) &amp; 볼륨 연결</li>
<li>필요한 이미지 pull</li>
<li>필요한 이미지 build</li>
<li>의존성에 따라 서비스 실행</li>
</ol>
</li>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>Default</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--attach</td>
<td></td>
<td>서비스 출력에 연결</td>
</tr>
<tr>
<td>--attach-dependencies</td>
<td></td>
<td>종속 컨테이너에 연결</td>
</tr>
<tr>
<td>--build</td>
<td></td>
<td>컨테이너 시작 전에 이미지를 빌드함</td>
</tr>
<tr>
<td>--detach , -d</td>
<td></td>
<td>백그라운드 모드 적용</td>
</tr>
<tr>
<td>--force-recreate</td>
<td></td>
<td>이미지나 구성이 변경되지 않았더라도 다시 create함</td>
</tr>
<tr>
<td>--no-build</td>
<td></td>
<td>이미지가 없어도, 빌드하지 않음</td>
</tr>
<tr>
<td>--no-deps</td>
<td></td>
<td>link된 서비스를 시작하지 않음</td>
</tr>
<tr>
<td>--no-recreate</td>
<td></td>
<td>컨테이너가 이미 있는 경우 다시 만들지 않음 (—force-recreate와 같이 사용 못함)</td>
</tr>
<tr>
<td>--no-start</td>
<td></td>
<td>서비스를 만들고, 시작하지 않음</td>
</tr>
<tr>
<td>--pull</td>
<td>missing</td>
<td>running하기 전에 이미지를 pull 할 지 결정 (&quot;always&quot;</td>
</tr>
<tr>
<td>--quiet-pull</td>
<td></td>
<td>조용히 pull함</td>
</tr>
<tr>
<td>--remove-orphans</td>
<td></td>
<td>compose 파일에 정의되지 않은 서비스의 컨테이너 제거</td>
</tr>
<tr>
<td>--timeout , -t</td>
<td>10</td>
<td>컨테이너의 타임아웃을 지정(기본 10초)</td>
</tr>
</tbody></table>
<h2 id="ps">ps</h2>
<p><code>docker compose ps [OPTIONS] [SERVICE...]</code></p>
<p>Compose의 컨테이너 목록을 표시한다.</p>
<ul>
<li>모든 컨테이너를 확인하고 싶다면 뒤에 service를 명시하지 않으면 됨</li>
<li>컨테이너 목록은 docker 명령어로도 확인할 수 있음</li>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>Default</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--all , -a</td>
<td></td>
<td>중지된 서비스까지 모두 표시</td>
</tr>
<tr>
<td>--format</td>
<td>pretty</td>
<td>출력 형식을 지정</td>
</tr>
<tr>
<td>--quiet , -q</td>
<td></td>
<td>컨테이너 id만 출력</td>
</tr>
<tr>
<td>--services</td>
<td></td>
<td>서비스들을 보여줌</td>
</tr>
<tr>
<td>--status</td>
<td></td>
<td>상태별로 필터링함. 상태: [paused</td>
</tr>
<tr>
<td>- 예시</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<pre><code class="language-bash">docker compose ps --format json | jq .

# 아래는 출력
[
  {
    &quot;ID&quot;: &quot;1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a&quot;,
    &quot;Name&quot;: &quot;example-bar-1&quot;,
    &quot;Command&quot;: &quot;/docker-entrypoint.sh nginx -g &#39;daemon off;&#39;&quot;,
    &quot;Project&quot;: &quot;example&quot;,
    &quot;Service&quot;: &quot;bar&quot;,
    &quot;State&quot;: &quot;exited&quot;,
    &quot;Health&quot;: &quot;&quot;,
    &quot;ExitCode&quot;: 0,
    &quot;Publishers&quot;: null
  },
  {
    &quot;ID&quot;: &quot;f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0&quot;,
    &quot;Name&quot;: &quot;example-foo-1&quot;,
    &quot;Command&quot;: &quot;/docker-entrypoint.sh nginx -g &#39;daemon off;&#39;&quot;,
    &quot;Project&quot;: &quot;example&quot;,
    &quot;Service&quot;: &quot;foo&quot;,
    &quot;State&quot;: &quot;running&quot;,
    &quot;Health&quot;: &quot;&quot;,
    &quot;ExitCode&quot;: 0,
    &quot;Publishers&quot;: [
      {
        &quot;URL&quot;: &quot;0.0.0.0&quot;,
        &quot;TargetPort&quot;: 80,
        &quot;PublishedPort&quot;: 8080,
        &quot;Protocol&quot;: &quot;tcp&quot;
      }
    ]
  }
]</code></pre>
<h2 id="logs">logs</h2>
<p><code>docker compose logs [OPTIONS] [SERVICE...]</code></p>
<p>명시한 서비스의 로그를 출력한다.</p>
<ul>
<li>모든 컨테이너를 확인하고 싶다면 뒤에 service를 명시하지 않으면 됨</li>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>Default</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--follow , -f</td>
<td></td>
<td>logs를 계속적으로 follow한다.</td>
</tr>
<tr>
<td>--no-color</td>
<td></td>
<td>흑백으로 출력</td>
</tr>
<tr>
<td>--since</td>
<td></td>
<td>명시한 시간 이후의 로그를 출력(e.g. 2013-01-02T13:23:37Z : 해당하는 시간부터 출력) (e.g. 42m : 42분 전부터 출력)</td>
</tr>
<tr>
<td>--until</td>
<td></td>
<td>명시한 시간까지의 로그를 출력(—since와 같은 양식)</td>
</tr>
<tr>
<td>--tail</td>
<td>all</td>
<td>컨테이너 로그 끝에서부터 표시할 로그의 개수</td>
</tr>
<tr>
<td>--timestamps , -t</td>
<td></td>
<td>timestamp를 출력함</td>
</tr>
</tbody></table>
<h2 id="run">run</h2>
<p><code>docker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]</code></p>
<p>새로운 서비스 컨테이너를 실행하고, 특정 명령어를 일회성으로 실행한다. </p>
<ul>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>Default</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--detach , -d</td>
<td></td>
<td>백그라운드에서 실행</td>
</tr>
<tr>
<td>--entrypoint</td>
<td></td>
<td>entrypoint를 재정의</td>
</tr>
<tr>
<td>--env , -e</td>
<td></td>
<td>환경변수를 설정</td>
</tr>
<tr>
<td>--interactive , -i</td>
<td>true</td>
<td>interactive한 모드 적용</td>
</tr>
<tr>
<td>--label , -l</td>
<td></td>
<td>label을 재정의</td>
</tr>
<tr>
<td>--name</td>
<td></td>
<td>컨테이너에 이름을 할당</td>
</tr>
<tr>
<td>--publish , -p</td>
<td></td>
<td>포트를 지정</td>
</tr>
<tr>
<td>--quiet-pull</td>
<td></td>
<td>필요할 경우 조용히 pull을 진행</td>
</tr>
<tr>
<td>--rm</td>
<td></td>
<td>exits될 경우 컨테이너를 제거</td>
</tr>
<tr>
<td>--tty , -t</td>
<td>true</td>
<td>tty를 활성화함</td>
</tr>
<tr>
<td>--user , -u</td>
<td></td>
<td>지정 유저로 명령을 실행</td>
</tr>
<tr>
<td>--volume , -v</td>
<td></td>
<td>볼륨을 마운트시킴</td>
</tr>
<tr>
<td>--workdir , -w</td>
<td></td>
<td>작업공간을 명시</td>
</tr>
</tbody></table>
<h2 id="start">start</h2>
<p><code>docker compose start [SERVICE...]</code></p>
<p>내려가있는 특정 컨테이너를 시작시킴</p>
<ul>
<li><code>docker compose up</code>을 해주어도 됨</li>
</ul>
<h2 id="stop">stop</h2>
<p><code>docker compose stop [OPTIONS] [SERVICE...]</code></p>
<p>컨테이너를 제거하지 않고 실행만 중지함</p>
<ul>
<li>그러나 실행중인 모든 프로세스를 kill함(메모리 제거)<ul>
<li>메모리를 유지하고 싶으면 <code>pause</code>를 사용해야 함</li>
</ul>
</li>
</ul>
<h2 id="restart">restart</h2>
<p><code>docker compose restart [OPTIONS] [SERVICE...]</code></p>
<p>서비스 컨테이너를 재시작함</p>
<ul>
<li>모든 서비스를 재시작하려면 Service를 명시하지 않으면 됨</li>
</ul>
<h2 id="pause--unpause">pause / unpause</h2>
<p><code>docker compose pause [SERVICE...]</code></p>
<p><code>docker compose unpause [SERVICE...]</code></p>
<p>서비스를 일시중지하거나 일시중지를 해제한다.</p>
<ul>
<li><code>stop</code>과 달리 실행중인 프로세스를 kill하지 않고 유지(메모리가 유지됨)</li>
</ul>
<h2 id="cp">cp</h2>
<p>서비스 컨테이너와 로컬 파일 시스템 간에 파일이나 폴더 복사함.</p>
<ul>
<li>from container</li>
</ul>
<p><code>docker compose cp [OPTIONS] SERVICE_container:SRC_PATH DEST_PATH</code> </p>
<ul>
<li>to container</li>
</ul>
<p><code>docker compose cp [OPTIONS] SRC_PATH SERVICE_container:DEST_PATH</code> </p>
<ul>
<li>container → container</li>
</ul>
<p><code>docker compose cp [OPTIONS] SERVICE_container:SRC_PATH SERVICE_container:DEST_PATH</code> </p>
<h2 id="exec">exec</h2>
<p><code>docker compose exec [OPTIONS] SERVICE COMMAND [ARGS...]</code></p>
<p>실행중인 서비스 컨테이너에서 명령을 실행한다. (실행되어있는 컨테이너에서 실행)</p>
<ul>
<li>기본적으로 <code>docker exec</code>와 동일함</li>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>Default</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--detach , -d</td>
<td></td>
<td>백그라운드에서 명령 실행</td>
</tr>
<tr>
<td>--env , -e</td>
<td></td>
<td>환경변수 설정</td>
</tr>
<tr>
<td>--interactive , -i</td>
<td>true</td>
<td>interactive 한 모드에서 실행</td>
</tr>
<tr>
<td>--privileged</td>
<td></td>
<td>프로세스에 확장된 권한 부여</td>
</tr>
<tr>
<td>--tty , -t</td>
<td>true</td>
<td>tty를 활성화함</td>
</tr>
<tr>
<td>--user , -u</td>
<td></td>
<td>지정 유저로 명령을 실행</td>
</tr>
<tr>
<td>--workdir , -w</td>
<td></td>
<td>명령을 실행할 작업공간을 명시</td>
</tr>
</tbody></table>
<h2 id="config">config</h2>
<h2 id="kill">kill</h2>
<p><code>docker compose kill [OPTIONS] [SERVICE...]</code></p>
<p>실행중인 컨테이너에 신호를 보내어 강제로 정지시킨다.</p>
<ul>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>Default</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--remove-orphans</td>
<td></td>
<td>Compose 파일에 정의되지 않은 서비스의 컨테이너를 제거</td>
</tr>
<tr>
<td>--signal , -s</td>
<td>SIGKILL</td>
<td>보낼 신호를 명시</td>
</tr>
</tbody></table>
<h2 id="rm">rm</h2>
<p><code>docker compose rm [OPTIONS] [SERVICE...]</code></p>
<p>중지된 서비스 컨테이너를 제거함. </p>
<ul>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--force , -f</td>
<td>강제로 제거(제거 확인 메세지를 출력하지 않음)</td>
</tr>
<tr>
<td>--stop , -s</td>
<td>컨테이너를 제거하기 전에 중지시킨다.</td>
</tr>
<tr>
<td>--volumes , -v</td>
<td>컨테이너에 연결된 모든 익명으로된 볼륨을 제거함</td>
</tr>
</tbody></table>
<h2 id="down">down</h2>
<p><code>docker compose down [OPTIONS]</code></p>
<p>모든 서비스 컨테이너를 중지하고, 리소스(컨테이너, 볼륨, 네트워크, <code>up</code>으로부터 만들어진 이미지)를 삭제한다.</p>
<ul>
<li>external 리소스는 삭제되지 않음.</li>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>Default</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>--rmi</td>
<td></td>
<td>“all”은 모든 이미지를 삭제하지만, “local”로 하면 태그가 없는 이미지만 삭제함</td>
</tr>
<tr>
<td>--volumes , -v</td>
<td></td>
<td>Compose 파일의 볼륨 섹션에서 정의했던 볼륨을 삭제</td>
</tr>
</tbody></table>
<h4 id="reference">Reference</h4>
<p><a href="https://docs.docker.com/compose/reference/">https://docs.docker.com/compose/reference/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker Compose] Compose Specification]]></title>
            <link>https://velog.io/@whattsup_kim/Docker-Compose-Compose-Specification</link>
            <guid>https://velog.io/@whattsup_kim/Docker-Compose-Compose-Specification</guid>
            <pubDate>Fri, 21 Oct 2022 12:39:24 GMT</pubDate>
            <description><![CDATA[<p>본 글은 <a href="https://docs.docker.com/compose/compose-file/">Compose file format 3.8+</a> 버전을 보고 작성하였습니다.</p>
<h1 id="docker-compose">Docker Compose</h1>
<p>Docker Compose는 다중 컨테이너로 구성된 애플리케이션을 관리하기 위한 오케스트레이션(Orchestration) 도구이다.</p>
<ul>
<li>Compose에서는 <code>YAML</code>파일을 사용하여 애플리케이션 서비스를 구성(명세)함</li>
<li>Compose는 다음과 같이 3단계 프로세스로 진행됨<ol>
<li><code>Dockerfile</code>에 애플리케이션을 작성. 이를 통해 어디에서나 재현가능한 환경을 제공함</li>
<li>애플리케이션 서비스들을 <code>compose.yml</code>에 정의하여 함꼐 실행될 수 있도록 함.</li>
<li>docker compose up을 통해 전체 앱을 실행</li>
</ol>
</li>
</ul>
<h1 id="compose-specification">Compose Specification</h1>
<p>Compose를 사용하기 전에 Compose 파일에 대해 먼저 알아보자.</p>
<p>Compose 파일은 Docker 애플리케이션의 서비스, 네트워크, 볼륨 등을 정의(명세) 하는 YAML 파일이다. 이를 통해 플랫폼에 구애받지 않는, 재현가능한 컨테이너 기반 애플리케이션을 정의할 수 있다.</p>
<ul>
<li>Compose 파일의 기본 양식은 <code>compose.yaml</code> 혹은 <code>compose.yml</code> 이다.<ul>
<li>이전 버전에서 사용하던 <code>docker-compose.yaml</code>도 호환되지만, 두 파일이 같이 있는 경우 <strong>기본양식이 선호</strong>됨</li>
</ul>
</li>
</ul>
<p>Compose 파일의 최상위 Level에는 다음과 같은 구조를 포함할 수 있다.</p>
<ul>
<li>Version (Deprecated): 지원 버전을 확인.</li>
<li><strong>Service</strong> : 독립된 컨테이너에서 돌아가는 애플리케이션의 구성요소<ul>
<li>쉽게 이야기해서 Compose에서 돌아갈 Container에 대한 설정을 의미.</li>
<li>메인 애플리케이션부터 기반 시스템까지 모두 이 Service에 포함됨</li>
</ul>
</li>
<li><strong>Network</strong> : Service는 Network를 통해 서로 통신함<ul>
<li>기본적으로 Compose는 Default 네트워크에 모든 컨테이너 서비스들을 연결함</li>
</ul>
</li>
<li><strong>Volume</strong> : 서비스는 영구적으로 유지해야 할 데이터를 Volume에 저장하고 공유함.<ul>
<li>마치 Docker의 volume과 같음</li>
</ul>
</li>
<li>Config : 애플리케이션 서비스에서 필요한 설정 값 등을 명시하기 위해 config가 사용됨.</li>
<li>Secret : 애플리케이션에서 사용할 민감한 정보를 저장 시 사용됨</li>
</ul>
<p>이들이 동작되는 <strong>예시</strong>를 살펴보자. 다음과 같은 구조를 가진 서비스가 있다고 해보자.</p>
<pre><code>(External user) --&gt; 443 [frontend network]
                            |
                  +--------------------+
                  |  frontend service  |...ro...&lt;HTTP configuration&gt;
                  |      &quot;webapp&quot;      |...ro...&lt;server certificate&gt; #secured
                  +--------------------+
                            |
                        [backend network]
                            |
                  +--------------------+
                  |  backend service   |  r+w   ___________________
                  |     &quot;database&quot;     |=======( persistent volume )
                  +--------------------+        \_________________/</code></pre><p>이들의 구성은 다음과 같다.</p>
<ul>
<li>service : webapp, database</li>
<li>secret : frontend 서비스에서 사용할 HTTPS 인증서</li>
<li>config : frontend 서비스에서 사용할 HTTP config</li>
<li>volume : backend와 연결된 volume</li>
<li>networks : 2개의 network들</li>
</ul>
<p>이들을 위한 Compose file은 다음과 같이 구성될 것임.</p>
<pre><code class="language-yaml">services:
  frontend:
    image: awesome/webapp
    ports:
      - &quot;443:8043&quot;
    networks:
      - front-tier
      - back-tier
    configs:
      - httpd-config
    secrets:
      - server-certificate

  backend:
    image: awesome/database
    volumes:
      - db-data:/etc/data
    networks:
      - back-tier

volumes:
  db-data:
    driver: flocker
    driver_opts:
      size: &quot;10GiB&quot;

configs:
  httpd-config:
    external: true

secrets:
  server-certificate:
    external: true

networks:
  # The presence of these objects is sufficient to define them
  front-tier: {}
  back-tier: {}</code></pre>
<p>이제 Compose의 top-Level component들 중에서 가장 자주사용하는 services, volumess, networks 대해서 자세히 알아보자.  </p>
<h2 id="service">service</h2>
<p>본 글에서는 자주 사용할 것 같은 명령어만 정리한다. 나머지는 <a href="https://docs.docker.com/compose/compose-file/">공식 문서</a>를 참고하자.</p>
<h3 id="profiles">profiles</h3>
<p><strong>Profiles</strong>은 선택적으로 서비스를 활성화할 수 있도록 도와주는 속성이다.</p>
<ul>
<li>각 서비스를 프로필에 매핑하면, 해당 프로필이 활성화 된 경우에만 서비스가 시작된다.</li>
<li>서비스를 프로필에 매핑시키지 않았다면, 해당 서비스는 항상 활성화된다.</li>
</ul>
<p>예를 들어보자.</p>
<pre><code class="language-yaml">services:
  foo:
    image: foo
  bar:
    image: bar
    profiles:
      - test
  baz:
    image: baz
    depends_on:
      - bar
    profiles:
      - test
  zot:
    image: zot
    depends_on:
      - bar
    profiles:
      - debug</code></pre>
<ul>
<li>위 서비스를 그냥 실행시키면 profiles 속성이 없는 <code>foo</code>는 항상 활성화됨</li>
<li>만약 <code>test</code> 프로필만 활성화된다면, 해당하는 프로필에 속해있는 <code>bar</code>와 <code>baz</code>도 실행됨(<code>foo</code>는 고정적으로 실행되는 중..)</li>
<li>만약 <code>debug</code> 프로필만 활성화되면, <code>zot</code>이 실행되어야 하지만, <code>bar</code>에 종속되어있으므로 실행되지 않음</li>
<li><code>debug</code>과 <code>test</code>가 모두 활성화되면, 위에 있는 모든 서비스가 활성화됨.</li>
<li>만약 Compose가 그냥 <code>bar</code>를 <strong>명시적</strong>으로 실행하면, profiles을 활성화하지 않아도 해당 <strong>서비스</strong>(<code>bar</code>)와 해당 <strong>프로필</strong>(<code>test</code>)이 <strong>활성화됨</strong></li>
<li>만약 Compose가 그냥 <code>baz</code>를 <strong>명시적</strong>으로 실행하면, 해당 서비스와 해당 프로필이 활성화되며, depends_on이므로 <code>bar</code>도 실행됩니다.</li>
<li>만약 Compose가 그냥 <code>zot</code>을 <strong>명시적</strong>으로 실행하면, 해당 서비스와 해당 프로필이 활성화됩니다. 하지만 depends_on에 의해 <code>bar</code>를 실행하려 할 때 해당 profile(<code>test</code>)이 <code>zot</code>의 profile(<code>debug</code>)다르므로, 실행될 수 없습니다. 따라서 결국 <code>zot</code>은 실행되지 않습니다.<ul>
<li>이러한 경우, <code>bar</code>의 profiles에 test를 추가로 포함시켜 해결할 수 있습니다.</li>
</ul>
</li>
</ul>
<h3 id="image">image</h3>
<p>image는 컨테이너를 시작할 이미지를 지정한다.</p>
<ul>
<li>docker에서 build할 때와 마찬가지로, 로컬 환경에 없다면 hub에서 다운로드하여 사용한다.</li>
</ul>
<pre><code class="language-yaml">services:
    # 이미지 지정은 다음과 같은 양식(도커 빌드할 때와 같음)으로 할 수 있다.
  image: redis
  image: redis:5
  image: redis@sha256:0ed5d5928d4737458944eb604cc8509e245c3e19d02ad83935398bc4b991aac7
  image: library/redis
  image: docker.io/library/redis
  image: my_private.registry:5000/redis</code></pre>
<h3 id="build">build</h3>
<p>(image가 없다면) build는 컨테이너 이미지를 생성하기 위한 명령어이다. 해당 명령어를 통해 dockerfile을 자동으로 빌드하여 컨테이너를 생성하고, 이를 사용할 수 있다.</p>
<pre><code class="language-yaml">services:
  frontend:
    image: awesome/webapp
    build: ./webapp

  backend:
    image: awesome/database
    build:
      context: ../backend
      dockerfile: Dockerfile
            args:
                &lt;abc&gt;:123</code></pre>
<ul>
<li><code>awesome/webapp</code>도커 이미지는 <code>./webapp</code>이라는 하위폴더로부터 이미지를 생성한다. 해당 폴더에는 반드시 dockerfile이 있어야 한다.<ul>
<li>위와 같이 build에 경로를 바로 지정하여 사용할 수 있다.</li>
</ul>
</li>
<li><code>awesome/database</code>도커 이미지는<code>backend</code>라는 상위폴더로부터 이미지를 생성한다.<ul>
<li><strong>context</strong>에 있는 폴더(또는 git리포지토리 URL)에서 검색을 함(상대 혹은 절대경로)</li>
<li><strong>dockerfile</strong>: (이름이 다를경우) 도커파일의 이름을 명시해줄 수 있음</li>
<li><strong>args :</strong> 도커 이미지를 빌드할 때 사용할 args를 명시할 수 있음</li>
</ul>
</li>
<li>build된 이미지는 이미지 레지스트리에 push된다.</li>
</ul>
<h3 id="depends_on">depends_on</h3>
<p>depends_on은 서비스 간 종속성을 나타냅니다.</p>
<pre><code class="language-yaml">services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres</code></pre>
<ul>
<li>Compose는 종속성 순서대로 서비스를 생성합니다.<ul>
<li>위 예시에서는 <code>redis</code>&amp;<code>db</code> → <code>web</code></li>
</ul>
</li>
<li>Compose는 종속성 순서대로 서비스를 제거합니다.<ul>
<li>위 예시에서는 <code>web</code>→ <code>redis</code>&amp;<code>db</code></li>
</ul>
</li>
</ul>
<h3 id="containier_name">containier_name</h3>
<p>container_name은 컨테이너 이름을 지정해줄 수 있다. (지정하지 않는다면 default이름으로 지정됨)</p>
<pre><code class="language-yaml">container_name: my-web-container</code></pre>
<h3 id="cgroup_parent">cgroup_parent</h3>
<p>cgroup_parent는 컨테이너에 대해 상위 cgroup을 지정해줄 수 있음</p>
<ul>
<li><code>cgroup_parent: m-executor-abcd</code></li>
</ul>
<h3 id="deploy-배포--리소스-관리">deploy (배포 &amp; 리소스 관리)</h3>
<p>depoly는 서비스에 대한 런타임 요구사항들을 정의할 수 있다. </p>
<p>(간략하게 설명하고 넘어가는 component들은 <a href="https://docs.docker.com/compose/compose-file/deploy/#definitions">docs</a>를 참고)</p>
<ul>
<li>endpoint_mode : 서비스에 연결하려는 (외부) 클라이언트에 대한 검색 방법(Virtual IP, DNS)을 지정한다.</li>
</ul>
<pre><code class="language-yaml">services:
  frontend:
    image: awesome/webapp
    ports:
      - &quot;8080:80&quot;
    deploy:
      mode: replicated
      replicas: 2
      endpoint_mode: vip</code></pre>
<ul>
<li>mode : 서비스를 실행하는 데 사용되는 복제 모델을 정의함<ul>
<li><code>replicated</code>(default) : 복제 모델을 정의함. 이를 통해 동일한 코드를 실행하는 여러 컨테이너를 가질 수 있음.<ul>
<li><code>replicas</code>로 복제할 개수를 설정함</li>
</ul>
</li>
<li><code>global</code> : 단 하나의 모델만 정의함</li>
</ul>
</li>
</ul>
<pre><code class="language-yaml">services:
  frontend:
    image: awesome/webapp
    deploy:
      mode: replicated
      replicas: 6</code></pre>
<ul>
<li>resources : 컨테이너들에 대한 리소스를 관리할 수 있음<ul>
<li><code>limits</code> : 여기에 해당하는 만큼 리소스를 제한할 수 있음</li>
<li><code>reservations</code> :  여기에 해당하는 만큼의 리소스 이상을 할당한다는 것을 보장함.</li>
</ul>
</li>
</ul>
<pre><code class="language-yaml">services:
  frontend:
    image: awesome/webapp
    deploy:
      resources:
        limits:
          cpus: &#39;0.50&#39;
          memory: 50M
          pids: 1
        reservations:
          cpus: &#39;0.25&#39;
          memory: 20M</code></pre>
<ul>
<li>resources 관련 컴포턴트들<ul>
<li>cpus : 컨테이너가 사용할 수 있는 CPU 코어<ul>
<li>퍼센트로 표시</li>
</ul>
</li>
<li>memory : 컨테이너가 할당할 수 있는 메모리 양<ul>
<li>byte values 형식을 사용<ul>
<li><code>2b</code>, <code>1024kb</code>, <code>2048k</code>, <code>30m</code>, <code>1gb</code></li>
</ul>
</li>
</ul>
</li>
<li>pids : pid 제한을 조정<ul>
<li>정수로 표시</li>
</ul>
</li>
<li>device : 컨테이너가 사용할 수 있는 장치를 구성함<ul>
<li><code>capabilities</code> 에는 사용할 기능을 문자열로 입력 ([gpu] 혹은 [tpu], …) → 필수로 입력해야 하는 필드임(이것만 입력하면 모든 gpu 사용함)</li>
<li><code>count</code> 에는 사용할 장치 개수(int) 혹은 all 입력</li>
<li><code>device_ids</code> 에는 호스트의 장치 id를 입력]<ul>
<li>ex. 호스트에서 nvidia-smi를 출력하고, 장치 ID를 찾은 후 입력할 수 있음</li>
</ul>
</li>
<li><code>driver</code> 에는 문자열로 지정된 값을 넣음<ul>
<li>ex. ‘nvidia’</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>device 예시</li>
</ul>
<pre><code class="language-yaml"># 1
services:
  test:
    image: nvidia/cuda:10.2-base
    command: nvidia-smi
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
# 2
services:
  test:
    image: tensorflow/tensorflow:latest-gpu
    command: python -c &quot;import tensorflow as tf;tf.test.gpu_device_name()&quot;
    deploy:
      resources:
        reservations:
          devices:
          - driver: nvidia
            device_ids: [&#39;0&#39;, &#39;3&#39;]
            capabilities: [gpu]</code></pre>
<ul>
<li>restart_policy : 컨테이너가 종료될 때 다시 시작할지 여부와 방법을 작성<ul>
<li>condition : <code>none</code>, <code>on-failure</code>, <code>any</code>(default)</li>
<li>delay : 재시작을 시도들 사이에 대기할 시간(default : 0)</li>
<li>max_attempts : 재시작 시도 횟수(int) (defult : 무한)</li>
<li>windows : 재시작 성공 여부를 결정하기 전 기다리는 시간</li>
</ul>
</li>
</ul>
<pre><code class="language-yaml">deploy:
  restart_policy:
    condition: on-failure
    delay: 5s
    max_attempts: 3
    window: 120s</code></pre>
<ul>
<li>그 외<ul>
<li>roliback_config</li>
<li>update_config</li>
</ul>
</li>
</ul>
<h3 id="command">command</h3>
<p>comman는 Dockerfile에서의 CMD 명령을 재정의(overrides)함.</p>
<ul>
<li><code>command: bundle exec thin -p 3000</code></li>
<li><code>command: [ &quot;bundle&quot;, &quot;exec&quot;, &quot;thin&quot;, &quot;-p&quot;, &quot;3000&quot; ]</code></li>
</ul>
<h3 id="entrypoint">entrypoint</h3>
<p>entrypoint는 Dockerfile에서의 ENTRYPOINT 명령을 재정의(override)함.</p>
<ul>
<li>Docker image의 ENTRYPOINT와 CMD는 모두 지움</li>
<li><code>entrypoint: /code/entrypoint.sh</code></li>
</ul>
<h3 id="environment">environment</h3>
<p>environment는 컨테이너에 설정된 환경변수를 정의함. 작성 형식은 다음과 같이 두 가지가 있음</p>
<pre><code class="language-yaml"># 1
environment:
  RACK_ENV: development
  SHOW: &quot;true&quot;
  USER_INPUT:

# 2
environment:
  - RACK_ENV=development
  - SHOW=true
  - USER_INPUT</code></pre>
<h3 id="env_file">env_file</h3>
<p>env_file은 파일 내용을 기반으로 환경 변수를 추가함(만약 <code>environment</code>와 함께 있다면, <code>environment</code>가 우선순위를 가짐).</p>
<ul>
<li><code>env_file: .env</code></li>
</ul>
<h3 id="ports">ports</h3>
<p>ports는 컨테이너 포트를 노출한다.. (<code>network_mode: host</code>와 함께 사용하면 안 됨)</p>
<pre><code class="language-yaml">ports:
  - &quot;3000&quot;
  - &quot;3000-3005&quot;
  - &quot;8000:8000&quot;
  - &quot;9090-9091:8080-8081&quot;
  - &quot;49100:22&quot;
  - &quot;127.0.0.1:8001:8001&quot;
  - &quot;127.0.0.1:5000-5010:5000-5010&quot;
  - &quot;6060:6060/udp&quot;</code></pre>
<h3 id="expose">expose</h3>
<p>expose는 호스트 머신에 대한 포트는 공개하지 않고, 포트를 노출하고 싶은 경우(ex. links 기능 사용)에 사용한다.</p>
<pre><code class="language-yaml">expose:
  - &quot;3000&quot;
  - &quot;8000&quot;</code></pre>
<h3 id="links">links</h3>
<p>links는 다른 서비스의 컨테이너에 접근할 수 있도록 별명을 설정할 수 있다.</p>
<p>(사용하지 않더라도 한 네트워크 안에 있는 서비스끼리는 해당 서비스 이름으로 통신이 가능함)</p>
<pre><code class="language-yaml">web:
  links:
    - db
    - db:database  # [service:alias]를 통해 별칭으로도 접근할 수 있음
    - redis</code></pre>
<h3 id="tty">tty</h3>
<p>TTY와 함께 실행되도록 서비스 컨테이너를 구성한다.</p>
<pre><code class="language-yaml">service:
  service_name:
    tty: true</code></pre>
<h3 id="logging">logging</h3>
<p>컨테이너와 서비스의 로깅에 관한 설정을 한다.</p>
<ul>
<li><code>drive</code>에는 드라이버 종류를 명시하고, <code>option</code>s에서는 옵션을 키-값 쌍으로 설정합니다.</li>
</ul>
<pre><code class="language-yaml">logging:
  driver: syslog
  options:
    syslog-address: &quot;tcp://192.168.0.42:123&quot;</code></pre>
<h3 id="restart">restart</h3>
<p>restart는 컨테이너가 종료될 시 재시작 여부와 방법에 대해 지정한다.</p>
<pre><code class="language-yaml">restart: &quot;no&quot;  # 재시작하지 않음
restart: always  # 항상 재시작
restart: on-failure  # exit code가 error를 나타내는 경우만 재시작
restart: unless-stopped  # 서비스가 중지되거나 제거되기 전까지 항상 재시작</code></pre>
<h3 id="working_dir">working_dir</h3>
<p>working_dir은 Dockerfile의 WORKDIR로 지정된 컨테이너의 작업 디렉토리를 재정의(override)한다.</p>
<pre><code class="language-yaml">service:
  service_name:
    working_dir: /home/user/workspace</code></pre>
<h2 id="network">Network</h2>
<p>네트워크는 서비스가 서로 통신할 수 있도록 하는 layer이다. </p>
<ul>
<li>네트워크는 <strong>top-level 섹션</strong>에서 (이름을 지정하여) <strong>생성</strong>되며, <strong>Service의 하위 섹션</strong>에서 네트워크 이름을 명시함으로써 네트워크에 <strong>연결</strong>할 수 있다.</li>
</ul>
<pre><code class="language-yaml"># 예를 들어 다음과 같이 fromt-tier와 back-tier를 생성했다면
# service 하위 섹션에 이 이름을 명시하여 연결할 수 있음
services:
  frontend:
    image: awesome/webapp
    networks:
      - front-tier
      - back-tier

networks:
  front-tier:
  back-tier:
</code></pre>
<ul>
<li>기본적으로 Compose에서는 애플리케이션에 대한 default 단일 네트워크를 생성한다. 서비스의 각 컨테이너는 기본 네트워크에 연결되며, 이를 통해 서로 통신할 수 있다.<ul>
<li>default 네트워크는 애플리케이션이 생성될 때 <strong>만들어졌다가</strong>, 애플리케이션이 내려갈 때 <strong>삭제</strong>됨.</li>
<li>default 네트워크의 이름은 <code>compose.yaml</code>파일이 있는 디렉토리의 이름을 기반으로 생성됨.</li>
<li>ex) .yml파일이 my_app에 있다면, 네트워크 이름은 <code>my_app_default</code>가 됨</li>
</ul>
</li>
<li>각 컨테이너는 <strong>호스트명</strong>으로 <strong>네트워크를 검색</strong>할 수 있다.<ul>
<li>호스트명으로 접근하면, 적절한 컨테이너 IP 주소를 얻어서 접근하게 됨</li>
<li>이때, 주의해야 할 점은 ports(<code>&lt;host_port&gt;:&lt;container_port&gt;</code>)이다.<ul>
<li>만약 같은 네트워크에서 접근한다면 <code>container_port</code>를 사용해야 하며, 호스트에서 접속할 때에는 <code>host_port</code>를 사용해야 한다. <code>host_port</code>를 사용할 경우 스웜 밖에서도 접근이 가능하다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="driver">driver</h3>
<p>driver는 새로 생성한 네트워크에서 사용할 드라이버를 지정함</p>
<ul>
<li>네트워크 driver 종류는 이전에 작성한 <a href="https://velog.io/@whattsup_kim/Docker-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC">글</a>을 참고</li>
</ul>
<pre><code class="language-yaml">networks:
  mynet1:  # 네트워크 별칭
    driver: overlay

  mynet2:
    driver: bridge</code></pre>
<h3 id="attachable">attachable</h3>
<p>만약 attachable이 true라면, 다른 독립 실행된 컨테이너에서도 이 네트워크에 접근할 수 있다. 독립 실행된 컨테이너가 이 네트워크에 연결되면, 네트워크에 연결된 서비스와 통신할 수 있다.</p>
<pre><code class="language-yaml">networks:
  mynet1:
    driver: overlay
    attachable: true</code></pre>
<h3 id="internal">internal</h3>
<p>internal이 True면, 외부로부터 격리된 네트워크를 생성할 수 있다.</p>
<h3 id="external">external</h3>
<p>external이 True이면, 외부에서 생성된 network를 사용한다는 의미이다. 만약, 해당하는 network가 없다면 오류를 뱉는다.</p>
<pre><code class="language-yaml">services:
  frontend:
    image: awesome/webapp
    networks:
      - outside_network  # 외부에 있는 outside_network라는 이름의 네트워크 사용

networks:
  outside_network:
    external: true</code></pre>
<h2 id="volume">Volume</h2>
<p>volume은 영구적으로 데이터를 저장하기 위한 layer이다.</p>
<ul>
<li>네트워크와 마찬가지로 top-level 섹션에서 (이름을 지정하여) 생성되며, Service의 하위 섹션에서 이를 사용할 수 있다.</li>
</ul>
<pre><code class="language-yaml">services:
  backend:
    image: awesome/database
    volumes:
      - db-data:/etc/data

  backup:
    image: backup-service
    volumes:
      - db-data:/var/lib/backup/data

volumes:
  db-data:</code></pre>
<h3 id="external-1">external</h3>
<p>external이 True이면, 외부에서 생성된 volume를 사용한다는 의미이다. 만약, 해당하는 volume가 없다면 오류를 뱉는다.</p>
<pre><code class="language-yaml">services:
  frontend:
    image: awesome/webapp
    networks:
      - outside_volume  # 외부에 있는 outside_network라는 이름의 volume 사용

volumes:
  outside_volume:
    external: true</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[컨테이너를 위한 리눅스 기능: cgroup, namespace, union mount]]></title>
            <link>https://velog.io/@whattsup_kim/Linux-Kernel-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%A6%AC%EB%88%85%EC%8A%A4%EC%9D%98-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B2%A9%EB%A6%AC-%EA%B8%B0%EB%8A%A5-cgroup-namespace-union-mount</link>
            <guid>https://velog.io/@whattsup_kim/Linux-Kernel-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%A6%AC%EB%88%85%EC%8A%A4%EC%9D%98-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B2%A9%EB%A6%AC-%EA%B8%B0%EB%8A%A5-cgroup-namespace-union-mount</guid>
            <pubDate>Mon, 17 Oct 2022 12:44:45 GMT</pubDate>
            <description><![CDATA[<p><strong>컨테이너를 구성하는 대표적인 리눅스 기술 세 가지를 알아보자.</strong></p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/184dd31a-5dc8-4677-a7af-d2048b08d3a0/image.png" alt=""></p>
<ul>
<li><strong>Control groups :</strong> 리소스 사용량 결정</li>
<li><strong>Namespaces :</strong> 자원을 격리(공개 범위를 결정)</li>
<li><strong>Union mount</strong> file system : 컨테이너를 효율적으로 관리</li>
</ul>
<h2 id="cgroupcontrol-group">Cgroup(Control group)</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/8d44fcf1-73a2-47bc-93ca-bd96292634fe/image.png" alt=""></p>
<p><strong>Cgroup</strong>은 프로세스들이 사용하는 <strong>시스템의 자원의 사용 정보를 수집하고, 제한시키고, 격리시키는 리눅스 커널 기능</strong>(모든 프로세스에 대해 리소스 사용 정보를 수집함)을 한다.</p>
<ul>
<li><strong>제한 가능한 자원</strong><ul>
<li><strong>CPU</strong> : 스케줄러를 사용하여 해당 cgroup에 속한 프로세스 CPU 사용 시간을 제어함</li>
<li><strong>memory</strong> : 해당 cgroup에 속한 프로세스의 메모리 사용량을 제어함<ul>
<li>초과 시 oom 발생(oom_control로 관리할 수 있음)</li>
</ul>
</li>
<li><strong>freezer</strong> : cgroup의 작업을 일시중지하거나 다시 시작함<ul>
<li>마치 도커의 pause/unpause와 같은 역할</li>
</ul>
</li>
<li><strong>blkio</strong> : cgroup의 BlockI/O(Block device(SSD, USB, HDD 등)에 대한 제한을 설정</li>
<li><strong>net_cls</strong> : 네트워크 패킷을 클래스 식별자(classid)로 태그하여 Linux 트래픽 컨트롤러 (<strong>tc</strong>
)가 특정 cgroup에서 발생하는 패킷들을 식별할 수 있게 함</li>
<li><strong>cpuset</strong> : 개별 CPU 및 메모리 노드를 cgroup에 바인딩 하기 위한 서브시스템. 리눅스의 <code>testset</code> 명령과 유사하게 CPU 코어를 할당 할 수 있는 서브시스템임.</li>
<li><strong>cpuacct</strong> : cgroup이 사용한 CPU 자원에 대한 보고서를 생성</li>
<li><strong>devices</strong> : cgroup 작업 단위로 장치에 대한 엑세스를 허용하거나 거부</li>
<li><strong>ns</strong> : namespace 서브시스템</li>
<li>…</li>
</ul>
</li>
<li><strong>활용 사례</strong><ul>
<li>runc, YARN (Hadoop), Android 등</li>
<li><strong>ex)</strong> Android에서는 cgroup을 이용해서 애플리케이션을 foreground / background로 나누고 background의 CPU 점유율을 낮추고 있음.</li>
<li><strong>ex)</strong> 페이스북에서는 워크로드를 core workload, non-core services 등으로 나누고 cgroup을 지정하여 리소스를 관리(가장 중요한 core workload에 영향을 최소화시킴)</li>
</ul>
</li>
</ul>
<h3 id="cgroup-v1-vs-v2">Cgroup v1 vs v2</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/4fc85855-b5d5-4c18-9528-1c16d55edeae/image.png" alt=""></p>
<p>Cgroup은 두 가지 버전으로 나눠지며, 두 버전은 위 그림처럼 계층 구조가 다르다.</p>
<ul>
<li>cgroup<strong>v1</strong> : control 대상이 되는 <strong>리소스들을 기준</strong>으로 control 그룹들을 나눔</li>
<li>cgroup<strong>v2 :</strong> control 대상이 되는 <strong>워크로드들을 기준</strong>으로 control 그룹들을 나눔</li>
</ul>
<h3 id="cgroup-실습해보기--cpu-사용량-제한">Cgroup 실습해보기 : CPU 사용량 제한</h3>
<p>Cgroup으로 CPU 사용량을 제한하는 실습을 해보자.</p>
<p>(본 실습에는 cgroupv2가 사용됨)</p>
<ul>
<li>먼저 <code>stress</code> 실험을 위해 stress 패키지를 설치해주었다.</li>
</ul>
<pre><code class="language-bash">sudo apt update &amp;&amp; apt install -y stress</code></pre>
<ul>
<li>다음 명령어로 내가 어떤 cgroup 버전을 가졌는지 확인할 수 있다.</li>
</ul>
<pre><code class="language-bash">grep cgroup /proc/filesystems

# 만약 시스템이 cgroupv2를 지원한다면, 다음과 같이 출력이 됩니다.
# 만약 시스템이 cgroupv1만 지원한다면, 아래 출력에서 &#39;cgroup2&#39; 부분은 보이지 않습니다.</code></pre>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/9b505386-4000-43d9-a332-e4259728fb54/image.png" alt=""></p>
<ul>
<li>현재 쉘의 pid가 어떤 cgroup인지 먼저 확인해보자.</li>
</ul>
<pre><code class="language-bash">cat /proc/$$/cgroup</code></pre>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/54f94349-70f2-4ca4-b863-2fbf40cb5364/image.png" alt=""></p>
<ul>
<li><p>이제 <code>/sys/fs/cgroup</code>경로로 가서 시스템에 설치된 cgroup 목록을 확인해보자.</p>
<ul>
<li>cgroup목록은 (이전에 배운 proc처럼)실제 파일은 아니지만, 파일처럼 보임</li>
</ul>
</li>
<li><p>이제 test용 부모 디렉토리를 만들어보자.</p>
</li>
</ul>
<pre><code class="language-bash">mkdir test_cgroup_parent &amp;&amp; cd test_cgroup_parent
ls

# cgroup 디렉토리와 마찬가지로 cgroup.controllers, cgroup.subtree_control 등의 파일이 보임</code></pre>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/18479635-ceff-48c5-b8b5-12afe586f426/image.png" alt=""></p>
<ul>
<li>(<code>cat cgroup.controllers</code><strong>명령어로 어떤 것들을 컨트롤할 수 있는지 확인할 수 있다.)</strong><ul>
<li>(출력 -&gt;) cpuset cpu io memory hugetlb pids rdma misc</li>
</ul>
</li>
<li>이제, cpu를 <strong>subtree</strong>이 추가하여 컨트롤 할 수 있도록 설정한다.<ul>
<li><code>+</code>는 추가를, <code>-</code>는 삭제를 의미한다.</li>
<li>현재 디렉토리는 <strong>부모</strong> 디렉토리이다.</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">echo &quot;+cpu&quot; &gt;&gt; /sys/fs/cgroup/test_cgroup_parent/cgroup.subtree_control</code></pre>
<ul>
<li>이제, <strong>cpu.max</strong>를 통해 제한을 걸어본다.<ul>
<li>cpu.max의 <strong>첫 번쨰 값</strong>은 허용된 시간(마이크로초) 할당량임. 이 시간에는 하위 그룹의 모든 프로세스를 전체적으로 실행할 수 있음. <strong>두 번째 값</strong>은 총 기간 길이를 지정함.</li>
<li>이번 실습에서는 1000000마이크로초(1초) 중에 100000 마이크로초만 실행되게끔 제한을 걸어보자(1/10만 실행하도록 설정)</li>
<li>현재 디렉토리는 <strong>부모</strong> 디렉토리이다.</li>
<li><strong>optional) cpu.weight</strong>로는 테스크별 가중치를 조절할수도 있다.<ul>
<li>cpu.weight 컨트롤러의 파일 값은 백분율이 아니라 절대값임.</li>
<li>예를 들어, task1이 100이고, task2가 200이라면, task2의 가중치가 두 배 더 높음</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">echo 100000 1000000 &gt; /sys/fs/cgroup/test_cgroup_parent/cpu.max

# cat으로 잘 설정되었는지 확인할 수 있다.</code></pre>
<ul>
<li>실험을 하기에 앞서, CPU에 부하를 걸어본다.</li>
</ul>
<pre><code class="language-bash">stress -c 1

# 다른 쉘에서 사용량을 확인해본다.
top

# cpu가 100% 사용중</code></pre>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/68855d54-12e2-443b-ae9f-9cf3870e248d/image.png" alt=""></p>
<ul>
<li>이제 test용 자식 디렉토리를 생성하고, pid를 추가하여 제한을 걸어본다.<ul>
<li>pid는 <strong>자식</strong> 디렉토리에서 추가한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-bash"># 부모(test_cgroup_parent) 위치에서 자식 생성 &amp;&amp; 자식으로 이동
mkdir test_cgroup_child &amp;&amp; cd test_cgroup_child

# 현재 쉘의 pid를 cgroup.procs에 추가
# 현재 pid는 echo $$ 를 통해 알 수 있음
echo $$ &gt; /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child/cgroup.procs</code></pre>
<ul>
<li>이제 다시 (해당 쉘에서) CPU에 부하를 걸어보자.</li>
</ul>
<pre><code class="language-bash">stress -c 1

# 다른 쉘에서 사용량을 확인해본다.
top

# cpu가 10%만 사용중임을 확인할 수 있다.</code></pre>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/de2a3048-296f-4829-9b12-b7a96d2db97c/image.png" alt=""></p>
<ul>
<li>마지막으로 현재 쉘 pid가 어떤 cgroup에 속하는지 다시 한 번 확인해보자.</li>
</ul>
<pre><code class="language-bash">cat /proc/$$/cgroup</code></pre>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/e7539946-1914-482b-ae07-4c4d594f98b3/image.png" alt=""></p>
<ul>
<li>실험이  완료되었으니 cgroup을 삭제해주자. cgroup을 삭제할 때에는 깊은 곳부터 순차적으로 삭제해준다.</li>
</ul>
<pre><code class="language-bash">sudo rmdir /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child
sudo rmdir /sys/fs/cgroup/test_cgroup_parent</code></pre>
<blockquote>
<p><strong>cgroupv1일 경우</strong></p>
<p>: cgroup폴더 내 cpu 폴더에서 위와 비슷하게 진행하면 된다.</p>
<ul>
<li>단, <code>cpu.max</code>가 아니라 <code>cpu.cfs_quota_us</code>(허용 시간)와 <code>cpu.cfs_period_us</code>(총 시간)를 사용한다.</li>
</ul>
</blockquote>
<h2 id="namespace">Namespace</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/51f542f0-d452-4e6b-9fbc-645fca989799/image.png" alt=""></p>
<p><strong>Namespace</strong>는 <strong>프로세스별로 별도의 커널 자원을 분할</strong>하는 리눅스 커널의 기능이다.</p>
<ul>
<li><strong>namespace 종류</strong><ul>
<li><strong>Process ID(</strong>pid) : pid 정보를 격리함. 네임스페이스 외 다른 프로세스에 접근이 불가.</li>
<li><strong>Network</strong>(net) : 네트워크 장치, IP주소, 포트, 라우팅 테이블 등 <strong>네트워크 리소스</strong>를 격리하고 가상 네트워크 장치를 할당함.</li>
<li><strong>Filesystem/mount(</strong>mnt) : 프로세스별로 마운트되는 파일시스템을 격리함.</li>
<li><strong>inter-proc comms</strong>(ipc) : inter-process communication을 격리. 다른 프로세스의 접근이나 제어를 방지함.</li>
<li><strong>UTS</strong> : 호스트명, 도메인명을 격리</li>
<li><strong>User</strong> : 프로세스 별로 UID, GID 정보를 격리</li>
</ul>
</li>
<li><strong>namespace vs cgroup</strong><ul>
<li>cgroup은 해당 <strong>프로세스가 쓸 수 있는 사용량</strong>을 제한한다.</li>
<li>namespace는 해당 <strong>프로세스가 볼 수 있는 범위</strong>를 제한한다.</li>
</ul>
</li>
</ul>
<h3 id="namespace-실습해보기--네임스페이스-pid-확인해보기">Namespace 실습해보기 : 네임스페이스 PID 확인해보기</h3>
<p>네임스페이스를 생성하여 PID를 격리해보자. 마치 하나의 시스템에서 PID가 2개인 것처럼 프로세스를 만들어보는 것이다.</p>
<ul>
<li><p>unshare 명령어로 새로운 네임스페이스를 생성해보고, 쉘을 실행하여 PID를 확인해보자.</p>
<ul>
<li><p>unshare은 (부모로부터) unshared한 namespace를 생성하는 커맨드이다.</p>
<p>  <code>unshare [options] [&lt;program&gt; [&lt;argument&gt;...]]</code></p>
</li>
<li><p>옵션</p>
<ul>
<li><code>-p</code> , <code>—pid</code>: pid 생성</li>
<li><code>-m</code>, <code>—mount</code> : mount 생성</li>
<li><code>-i</code>, <code>—ipc</code> : ipc 생성</li>
<li><code>-f</code>, <code>—fork</code> : 자식 프로세스 생성</li>
<li><code>-u</code> , <code>—uts</code>: 호스트명과 도메인 명을 격리할 수 있음</li>
<li><code>-U</code>, <code>—user</code> : 유저 namespace 격리</li>
<li>…</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-bash"># 유저 격리 &amp; PID 격리
# --map-root-user : 루트 사용자를 네임스페이스에 매핑(새 네임스페이스 안에서 루트 권한이 있음)
# 프로세스 분기(bash)
unshare --user --pid --map-root-user --mount-proc --fork bash
ps -ef

# 다음과 같이 process들이 격리됨을 확인할 수 있다.</code></pre>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/4303e832-3010-44c6-b91e-8a3713adddcf/image.png" alt=""></p>
<h2 id="union-mount-filesystem">Union mount filesystem</h2>
<p><strong>Union mount filesystem</strong>은 하나의 디렉토리 위치에 여러 개의 디렉토리를 마운트하여도, 하나의 통합된 디렉토리처럼 보이게 하는 방법임</p>
<ul>
<li>원래는 기존 디렉토리 위치에 새로운 파일 시스템을 마운트하면 새롭게 마운트된 내용만 보이게 됨</li>
<li>UFS에서 사용하는 주요 개념으로 <strong>Image Layer</strong>와 <strong>CoW</strong>가 있음.</li>
<li>도커는 UFS(Union File System)기반의 <strong>Storage driver</strong>를 사용하여 컨테이너와 이미지를 관리함.</li>
</ul>
<h3 id="image-layer">Image Layer</h3>
<ul>
<li><p>도커를 통해 Image Layer의 구조를 알아보자.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/e1461d72-ec84-4cf6-8ace-3ae70b665abc/image.png" alt=""></p>
<ul>
<li>위 그림에서 <strong>하단</strong>에는 <strong>Read Only</strong>의 이미지 레이어가 존재함.</li>
<li>위 그림에서 <strong>상단</strong>에는 <strong>Writable</strong>한 컨테이너 레이어가 존재함.</li>
</ul>
</li>
<li><p>이러한 <strong>Image Layer</strong>는 <strong>효율적으로</strong> 하나의 도커 이미지로 여러 개의 <strong>컨테이너를 생성</strong>하고, 각 컨테이너를 사용자의 입맛에 맞게 <strong>관리</strong>할 수 있게 해줌.</p>
<ul>
<li>Read Only의 이미지 레이어와 Writable한 컨테이너 레이어와의 Union으로 최종 컨테이너들을 생성하는 방식</li>
</ul>
</li>
<li><p>즉, 이미지를 레이어 형식으로 쌓는 Image Layer 구조를 가짐</p>
</li>
<li><p>이떄 효울적인 Write를 위해 CoW 전략을 사용함</p>
</li>
</ul>
<h3 id="cowcopy-on-write-rowredirect-on-write-전략">CoW(Copy on Write), RoW(Redirect on Write) 전략</h3>
<p><a href="https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/9_VirtualMemory.html">이미지 출처</a></p>
<ul>
<li><p>다음 이미지와 같이 Process1은 physical memory로부터 page A, B, C를 사용하고 있다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/b91f4975-8b76-481b-bb7e-b4e40dd9548b/image.png" alt="https://velog.velcdn.com/images/whattsup_kim/post/b91f4975-8b76-481b-bb7e-b4e40dd9548b/image.png"></p>
</li>
<li><p>이때, page A, B, C에 대해 읽기 작업을 수행하는 새로운 Process 2가 생성된다면, 다음 그림처럼 Process 2는 그저 physical memory에 접근하여 파일들의 내용을 그저 읽으면 된다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/0f1db320-6bdd-47cb-b188-fb2d58afe699/image.png" alt="https://velog.velcdn.com/images/whattsup_kim/post/0f1db320-6bdd-47cb-b188-fb2d58afe699/image.png"></p>
</li>
<li><p>그러나 process가 기존 파일들에 쓰기 작업을 해야 할 경우, 상황이 조금 달라짐. 원본 파일을 유지하면서 쓰기를 저장할 수 있어야 하기 때문이다.</p>
</li>
<li><p>이를 해결하기 위해, 쓰기 요청을 수행해야 할 경우에는 다음 그림처럼 physical memory에서 원본 파일을 복사(Read &amp; Write)한 뒤 요청을 반영하게 됨. 이것이 바로 Copy on Write 전략이다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/338a7c24-52a1-4552-8316-de3d021a650f/image.png" alt="https://velog.velcdn.com/images/whattsup_kim/post/338a7c24-52a1-4552-8316-de3d021a650f/image.png"></p>
</li>
<li><p>RoW는 CoW와 비슷하지만, 쓰기 작업 시 Copy(Read &amp; Write)를 하는 것이 아니라, 변경점만을 저장(Write)한다는 차이가 있음. 따라서 CoW와는 다르게 한 번의 쓰기 작업만 일어난다.</p>
</li>
<li><p>이제 위에서 살펴봤던 도커 storage driver의 구조를 다시 한 번 봐보자.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/852c9807-dab1-4436-b214-81484f449a48/image.png" alt="https://velog.velcdn.com/images/whattsup_kim/post/852c9807-dab1-4436-b214-81484f449a48/image.png"></p>
<ul>
<li>도커에서 아래 Image Layer들은 위 예시에서 &#39;원본 파일&#39;에 해당한다..</li>
<li>만약 새로운 컨테이너를 생성하는 등 변경사항이 생긴다면 <strong>Container Layer</strong>에 <strong>변경점을 저장</strong>하게 된다.</li>
<li>만약 변경된 내용으로 새로운 이미지를 build하게 된다면, <code>원본 파일 + 변경점</code>이 하나의 원본 파일(layer)이 될 것이다.</li>
</ul>
</li>
<li><p>도커는 드라이버에 따라 <strong>CoW</strong> 또는 <strong>RoW</strong> 개념을 사용한다. 이제 도커에서 사용하는 <strong>Union FileSystem</strong>을 지원하는 대표적인 드라이버(<strong>AUFS</strong>, <strong>OverlayFS</strong>)에 대해서 알아보자.</p>
<ul>
<li>더 많은 드라이버에 대한 정보 👉 <a href="https://docs.docker.com/storage/storagedriver/select-storage-driver/">공식 문서</a></li>
</ul>
</li>
</ul>
<h3 id="aufs-driver">AUFS Driver</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/927b0b15-5a26-42fc-85f5-a755182a9840/image.png" alt="https://velog.velcdn.com/images/whattsup_kim/post/927b0b15-5a26-42fc-85f5-a755182a9840/image.png"></p>
<ul>
<li>AUFS는 Container에서 원본 파일을 변경해야 한다면, 컨테이너 레이어로 전체 파일을 복사하고, 이 파일을 변경함으로써 변경사항을 반영한다.</li>
<li>이미지 레이어는 계층형태로 되어있으며, 복사할 파일을 찾기 위해 가장 위의 레이어부터 찾기 시작하게 됩니다. 따라서 복사할 파일이 이미지 레이어의 아래쪽에 있다면, 시간이 더 오래걸릴 수 있다.</li>
</ul>
<h3 id="overlayfs-driver">OverlayFS Driver</h3>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/f569f58c-243a-4635-9201-0266701db742/image.png" alt="https://velog.velcdn.com/images/whattsup_kim/post/f569f58c-243a-4635-9201-0266701db742/image.png"></p>
<ul>
<li><p>OverlayFS는 AUFS와 비슷한 원리로 동작하지만, 이미지 레이어에 계층이 없는 구조로 되어있다.
(overlay2는 현재 지원되는 모든 Linux배포판에 대해 선호되는 스토리지 드라이버임)</p>
</li>
<li><p>AUFS와 유사하게 (파일 변경점에 대해)<strong>lowedir</strong>(이미지 레이어)에 존재하는 file을 <strong>upperdir</strong>에 복사하여 사용한다.</p>
<ul>
<li>그러나 AUFS와는 다르게 계층화된 레이어 구조가 아니기 때문에 복사할 파일을 찾는 과정이 AUFS보다 빠름</li>
<li>upperdir에는 container에서 발생한 변경 사항을 담고 있음(overlayfile 파일이 존재).</li>
</ul>
</li>
<li><p><code>docker diff</code> 명령어를 통해, lowerdir로부터 upperdir에 어떤 변화가 있었는지를 확인할 수 있음</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/eb3c4efa-abc8-4576-88aa-3c669b7e4d22/image.png" alt="https://velog.velcdn.com/images/whattsup_kim/post/eb3c4efa-abc8-4576-88aa-3c669b7e4d22/image.png"></p>
<ul>
<li>A: 추가됨</li>
<li>C: 변경됨</li>
<li>D: 삭제됨</li>
</ul>
</li>
</ul>
<h3 id="union-mount-filesystem-실습해보기--docker에서-overlayfs를-출력해보기">Union mount filesystem 실습해보기 : docker에서 OverlayFS를 출력해보기</h3>
<p>도커 이미지의 overlayFS 정보를 확인해보자.</p>
<pre><code class="language-bash">cat /proc/mounts | grep overlay

# 다음과 같이 overlay 타입, lowerdir, upperdir, wordir(fs에서 관리 목적으로 사용) 등을 확인할 수 있다.</code></pre>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/d7deadbb-9eba-460d-9c3c-0f92a14c9c18/image.png" alt=""></p>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://www.nextplatform.com/2018/07/13/microsofts-container-strategy-continues-to-evolve/">https://www.nextplatform.com/2018/07/13/microsofts-container-strategy-continues-to-evolve/</a></li>
<li><a href="https://medium.com/some-tldrs/tldr-understanding-the-new-control-groups-api-by-rami-rosen-980df476f633">https://medium.com/some-tldrs/tldr-understanding-the-new-control-groups-api-by-rami-rosen-980df476f633</a></li>
<li><a href="https://www.lightnetics.com/topic/17326/what-are-control-groups-in-linux">https://www.lightnetics.com/topic/17326/what-are-control-groups-in-linux</a></li>
<li><a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/using-cgroups-v2-to-control-distribution-of-cpu-time-for-applications_managing-monitoring-and-updating-the-kernel#proc_controlling-distribution-of-cpu-time-for-applications-by-adjusting-cpu-bandwidth_using-cgroups-v2-to-control-distribution-of-cpu-time-for-applications">redhat_docummentation</a></li>
<li><a href="https://bikramat.medium.com/namespace-vs-cgroup-60c832c6b8c8">https://bikramat.medium.com/namespace-vs-cgroup-60c832c6b8c8</a></li>
<li><a href="https://velog.io/@whattsup_kim/DockerStorage-driver-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B4%80%EB%A6%AC">https://velog.io/@whattsup_kim/DockerStorage-driver-데이터-관리</a></li>
<li><a href="https://www.nginx.com/blog/what-are-namespaces-cgroups-how-do-they-work/">https://www.nginx.com/blog/what-are-namespaces-cgroups-how-do-they-work/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux Kernel] Memory Management]]></title>
            <link>https://velog.io/@whattsup_kim/Linux-Kernel-Memory-Management</link>
            <guid>https://velog.io/@whattsup_kim/Linux-Kernel-Memory-Management</guid>
            <pubDate>Sun, 16 Oct 2022 10:13:02 GMT</pubDate>
            <description><![CDATA[<p>이제 [Linux Kernel] 시리즈의 마지막 챕터로, Memory Management에 대해 알아보자.</p>
<h1 id="memory-management">Memory Management</h1>
<p>컴퓨터 시스템에서는 항상 물리적으로 존재하는 것보다 더 많은 양의 메모리를 필요해왔다. 이러한 물리적인 메모리의 한계를 극복하기 위해 여러 기법들이 개발되었는데, 이 중 <strong>가상 메모리(virtual memotry)</strong> 기법이 가장 성공적인 방법이라고 말할 수 있다. 가상 메모리는 다음과 같은 기능들을 제공한다.</p>
<ul>
<li><strong>넓은 주소공간</strong> : 운영체제는 시스템이 실제로 가진 것(물리)보다 훨씬 많은 양의 메모리를 가지고 있는 것처럼 보이게 한다.<ul>
<li>따라서 가상 메모리는 물리적 메모리모다 훨씬 더 클 수 있다.</li>
</ul>
</li>
<li><strong>보호</strong> : 시스템의 각 프로세스는 각자 독립된 가상 주소공간을 갖는다.<ul>
<li>프로세스들은 각자 완벽하게 분리되어 있어서, 각자가 다른 것에 영향을 줄 수 없다.</li>
</ul>
</li>
<li><strong>메모리 매핑</strong> : 메모리 매핑은 파일을 프로세스 주소공간에 매핑하기 위해 사용된다.<ul>
<li>파일의 내용은 프로세스 가상 주소 공간에 매핑된다.</li>
</ul>
</li>
<li><strong>공유 가상 메모리</strong> : 만약, 프로세스들이 메모리를 공유하는 것이 필요하다면 이를 가능하게 해줄 수도 있다.<ul>
<li>두 개 이상의 프로세스에게 공통적인 메모리를 줌으로써 프로세스 간 통신(IPC) 메커니즘으로 사용될 수 있음</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/e78875fb-821c-4003-9bc5-72bbb08bd90f/image.png" alt=""></p>
<h1 id="process-address-space">Process Address space</h1>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/3e0ca0b9-9628-4b72-89fe-0ed0554a7ba2/image.png" alt=""></p>
<ul>
<li><p>코드를 컴파일하여 실행하게 되면, 메모리에 위 그림과 같이 올라가게 되고, 주소가 주어진다. 이러한 주소 영역들을 <strong>Process Address Space(프로세스 주소 공간)</strong>라고 부르며, 각 주소 영역을 <strong>memory areas(= VMA. virtual memory address)</strong>라고 부른다.</p>
<ul>
<li>해당 주소는 말그대로 <strong>가상 주소</strong>이다.</li>
</ul>
</li>
<li><p>실제 Address Mapping은 다음과 같이 되어있음.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/524e82dc-b3cc-47ab-a6b9-95a5d2b6c78d/image.png" alt=""></p>
</li>
<li><p>이러한 매핑된 주소의 정보는 <strong>PCB</strong>의 6가지 리소스 중 <strong>mm 구조체</strong>에 들어있음</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/387b960d-f083-4a39-bad1-11963a24f4df/image.png" alt=""></p>
<ul>
<li>mm에 포인터가 있고, 이를 따라가보면 실제 <strong>메모리 관련 구조</strong>들을 찾을 수 있음. 따라서 mm에는 <strong>address space</strong>에 대한정보도 들어있음.</li>
<li>이를 통해 현 프로세스에서 사용중인 VMA 주소들(vm_area_struct)에 접근할 수 있음.</li>
</ul>
</li>
</ul>
<h1 id="vmavirtual-memory-address">VMA(virtual memory address)</h1>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/cccc8890-20de-4468-9dff-076ebcb399f9/image.png" alt=""></p>
<p>Process Address space 내용을 복습해보면, 현재 실행중인 프로세스에는 각각 <strong>PCB</strong>(task_struct)가 존재하고, PCB의 <strong>mm_struct</strong>의 <strong>mmap</strong> 필드를 따라가다 보면, <strong>vm_area_struct</strong>가 나온다. 그리고 여기에서 현재 프로세스가 사용중인 가상 메모리의 정보를 알 수 있다.</p>
<ul>
<li><strong>PCB’s mm_struct → mmap field → vm_area_struct</strong></li>
<li><strong>VMA들</strong>은 다음과 같은 내용(의 주소)들을 가지고 있다.<ul>
<li>Code : 기계어 (코드를 컴파일하여 기계어로 만들고 디스어셈블을 하여 어셈블리어로 바꾸어 code 영역에 넣는다(instruction))</li>
<li>data : 전역변수, static 변수의 할당의 위해 존재하는 공간</li>
<li>heap : 프로그래머의 동적 할당을 위해 존재하는 공간</li>
<li>stack : 지역 변수가 저장되는 공간</li>
<li>…</li>
</ul>
</li>
</ul>
<p><strong>vm_area_struct</strong>들은 <strong>리스트로</strong> 연결되어 있으몀, 각 vm_area_struct는 다음과 같이 구성되어 있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/36301c04-ae03-4380-acb3-dc6e9a689476/image.png" alt=""></p>
<ul>
<li>vm_start : 시작 주소</li>
<li>vm_end : 끝 주소</li>
<li>vm_ops : read, write 등을 하는 operation<ul>
<li>이 영역에는 <strong>page mapping table</strong>과 관련된 정보도 들어있음</li>
</ul>
</li>
<li>vm_mm : 다시 mm_struct를 가리킴</li>
<li>vm_next : 다음 vma 리스트를 가리킴</li>
<li>vm_file : vma에 해당하는 파일(어떤 디스크)을 가리킴</li>
</ul>
<h1 id="페이징paging">페이징(Paging)</h1>
<p>위에서 우리는 실제 컴퓨터 메모리는 한정적이기에, 넓은 주소를 가지는 가상 메모리가 등장하게 되었고, 가상 메모리는 실제 물리적 메모리보다 훨씬 더 클 수 있다고 배웠다. 그렇다면 실제 물리적인 (메인)메모리에 자신보다 큰 가상 메모리가 어떻게 (실제 메모리에) 올라올 수 있는 것일까?</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/885caafe-6b3a-4575-8ebd-330b2b7a8da2/image.png" alt=""></p>
<ul>
<li><p>먼저, 가상 메모리 시스템에서 가상 주소들은 모두 물리적 주소가 아니라 가상 주소이다. 이 가상 주소들은 운영체제가 관리하는 <strong>테이블들</strong>에 저장된 정보를 바탕으로 <strong>프로세서에</strong> 의해 <strong>물리적 주소</strong>로 변환된다.</p>
<ul>
<li>물리적 주소로 변환되는 작업은 CPU 내 <strong>MMU(Memory Management Unit)</strong>라고 불리는 하드웨어가 수행한다.</li>
<li><strong>TLB</strong>에는 가장 최근에 변환한 페이지 테이블 엔트리를 정보가 들어있다.</li>
<li><strong>MMU</strong>는 변환하려는 가상주소를 <strong>TLB</strong>에서 먼저 검색한다. 올라와 있는 엔트리가 존재하면, 바로 물리 메모리로 주소 변한 후 원하는 데이터를 가져올 수 있다. 만약 TLB내에 올라와 있는 엔트리가 없다면 페이지 테이블을 참조해서 변환 과정이 일어난다.</li>
</ul>
</li>
<li><p>이때, 이 변환을 쉽게 하기 위해 가상 메모리와 물리적 메모리는 작은 조각으로 나뉘게 된다.</p>
<ul>
<li>왜 작은 조각으로 나눌까?
  → 만약 a.out의 <code>main()</code>부터 실행한다고 하면, 전체 조각 중에서 이 <code>main()</code>이 있는 조각만을 먼저 가져와서 참조할 수 있다. 즉, <strong>현재 실행하려는 조각만 디스크에서 메모리로 가져오는 것</strong>이다.</li>
</ul>
</li>
<li><p>그런데 만약 조각들의 크기가 천차만별이라면, 비효율적으로 메모리가 사용되게 되고 다음과 같은 단편화가 발생하게 될 수 있다.</p>
<ul>
<li><p><strong>내부 단편화</strong> : 메모리 할당 시 프로세스보다 더 큰 공간을 할당받아서, 그 나머지 공간이 낭비되는 것</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/c02cc993-728d-49cd-b800-2dcb47d5073d/image.png" alt=""></p>
</li>
<li><p><strong>외부 단편화</strong> : 메모리 할당 시 공간들이 연속적으로 붙어있지 않아 빈 공간(낭비)이 생기는 것</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/4ecacd7b-32a0-4457-b4c7-8b736f8dda50/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p>이러한 <strong>단편화 문제를 해결</strong>하기 위해 작은 조각들의 크기를 <strong>고정분할 방식으로 분할</strong>하는 것을 <strong>페이징(paging)</strong>기법이라고 말한다.</p>
<ul>
<li><strong>페이지(page)</strong> : <strong>가상 메모리</strong>에서는 하나의 분할된 영역을 페이지라고 함</li>
<li><strong>프레임(frame)</strong> : <strong>실제 메모리</strong>에서는 하나의 분할된 영역을 프레임이라고 함</li>
</ul>
</li>
<li><p>메모리에는 이러한 페이지를 관리하기 위한 <strong>table(page table)</strong>이 존재한다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/41d2c7e7-c661-4471-aa9b-1b6a60725649/image.png" alt=""></p>
<ul>
<li><strong>page table</strong>은 다시 말해, <strong>가상 주소를 물리적 주소로 변환시켜주기 위한 정보</strong>를 가지고 있는 테이블이다.</li>
</ul>
</li>
</ul>
<h2 id="요구-페이징">요구 페이징</h2>
<p>위에서 메모리를 작은 조각(페이지)로 나누는 이유는 실제로 가상 메모리보다 훨씬 적은 물리적 메모리만 있기 때문에 <strong>현재 실행하려는 조각만 디스크에서 메모리로 가져와서 실행</strong>시키고자 하는 것이라고 이야기했었다. 이렇게 가상 메모리들이 접근되는 경우에만 메모리에 읽어들이는 기법을 <strong>요구 페이징(demand paging)</strong>이라고 말한다.</p>
<ul>
<li>프로세스가 현재 메모리에 없는 가상 주소를 접근하려고 하면, 프로세서는 참조된 가상 페이지에 대한 페이지 테이블 엔트리를 찾을 수 없을 것이다.</li>
<li>이 시점에서 프로세서는 운영체제에게 <strong>page fault</strong>가 발생했다고 통보한다.</li>
<li><strong>page fault</strong>가 발생하면  운영체제는 해당하는 페이지를 디스크의 이미지로부터 메모리에 가져온다.<ul>
<li>가져온 페이지는 <strong>빈 물리적 페이지 프레임에 기록</strong>된다.</li>
<li>가상 페이지 프레임 번호를 위한 엔트리가 프로세스의 <strong>페이지 테이블에 추가</strong>된다.</li>
</ul>
</li>
<li>이후 page fault가 실행됐던 시점으로 돌아가서 나머지 일이 진행된다.</li>
</ul>
<h2 id="스와핑swapping">스와핑(swapping)</h2>
<p>프로세스가 가상 페이지를 물리적 (메인)메모리에 가져와야 하는데, 비어 있는 물리적 페이지가 없다면, 운영체제는 물리적 메모리에서 다른 페이지를 제거하여, 가져올 페이지를 위해 공간을 마련해야 한다.</p>
<ul>
<li>리눅스에서는 시스템에서 제거될 페이지를 공정하게 선택하기 위해 <strong>LRU 알고리즘</strong>을 사용한다.<ul>
<li>** <strong>LRU</strong>(least, recently, uses - 최근최소사용) : 필요한 것만 ram으로 가져오고 오래 안쓰는건 하드디스크에 내려놓는다.</li>
</ul>
</li>
<li>만약 물리적 메모리에서 제거될 페이지가 이미지나 데이터 파일에서 온 것이고, 이 페이지에 쓰여진 것이 없다면, <strong>페이지의 내용을 저장할 필요가 없다</strong>. 대신 그냥 제거를 하고, <strong>나중에 다시</strong> 필요하게 되면 이미지나 데이터 파일로부터 다시 <strong>메모리에 읽어들이면 된다.</strong></li>
<li>그러나, 페이지가 변경되었다면, 운영체제는 페이지의 내용을 나중에 다시 사용할 수 있도록 보존해야 한다. 이런 페이지를 <strong>더티 페이지(dirty page)</strong>라고 하며, 이를 메모리에서 제거하기 전에 <strong>스왑 파일(swap file)</strong>이라는 특별한 파일에 저장한다.</li>
</ul>
<h2 id="page-table">page table</h2>
<p>page table에 대해서 좀 더 살펴보자.</p>
<ul>
<li>페이지 사이즈가 4KB라고 가정해보자.</li>
<li>32bit 시스템에서는 4KB의 페이지에 접근하기 위해선, (2^12이 4096이므로) 대략 12bit가 필요하다. 그럼 나머지 20bit(32 - 12)는 현재 찾으려는 page가 어디있는지에 사용되게 된다.<ul>
<li>동일하게, 64bit시스템에서는 52bit(64-12)가 현재 찾으려는 page가 어디있는지에 사용되게 된다.</li>
</ul>
</li>
<li>다시 말해, (32bit의 경우) 12bit로는 실제 페이지의 특정 바이트를 가져오고, 20bit로는 페이지 주소를 지칭한다.</li>
<li>즉, 32bit 시스템에서는 <strong>page table</strong>을 위해 20bit(2^20)의 페이지(엔트리)를 거느릴 수 있다.<ul>
<li>64bit 시스템에서는 <strong>page table</strong>을 위해 52bit(2^52)의 페이지(엔트리)를 거느릴 수 있다.</li>
</ul>
</li>
<li>그러나 이러한 엔트리로 각각의 프로세스를 관리하는 것은 너무 큰 낭비이다.<ul>
<li>사용되는 부분을 제외하면 나머지는 다 낭비되고 있기 때문</li>
</ul>
</li>
</ul>
<p>리눅스에서는 위와 같은 문제를 해결하기 위해 다음과 같은 방식을 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/e11d7e0c-9e77-4fab-800e-09fdabaa72fb/image.png" alt=""></p>
<ul>
<li><p>32bit라고 했을 떄, 2^20의 엔트리를 가진다고 했었다.</p>
<ul>
<li>즉 2^20 사이즈의 배열임</li>
</ul>
</li>
<li><p>이떄, 20bit로 사용되던 부분을 절반으로 나누고, 나눈 10bit를 특별한 용도로 사용한다.</p>
<ul>
<li>이것이 위 그림에서 Dir_no(10)이다.</li>
<li>10bit이므로, 1024 개의 엔트리를 가지는 테이블에 실제 사용되고 있는 엔트리에만 해당 세그먼트의 정보를 담는다.</li>
</ul>
</li>
<li><p>즉, 사용하는 세크먼트들만 2^10개의 엔트리를 할당해주고, 사용하지 않는 엔트리는 생성하지 않아 낭비를 줄이게 된다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/42c6964e-4b12-4b57-afb9-db8e3d4c4cfa/image.png" alt=""></p>
</li>
</ul>
<h1 id="🔥-메모리-관리흐름-총-정리">🔥 메모리 관리(흐름) 총 정리</h1>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/0d178f72-f5c8-4fd7-b646-b7c0893f6a76/image.png" alt=""></p>
<ul>
<li>프로세스마다 PCB가 존재하고, 그 PCB는 6개로 구성된다. 이중 <strong>mm 구조체</strong>에는 메모리와 관련된 정보가 들어있다.<ul>
<li><strong>mm구조체</strong>에는 여러 필드들이 있는데, 그 중 <strong>mmap</strong> 필드를 따라가다 보면 <strong>vm_area_struc</strong>t가 나온다.<ul>
<li>이 <strong>vm_area_struct</strong>에서는 현재 프로세스가 사용중인 <strong>가상 메모리 정보</strong>를 알 수 있다.</li>
<li><strong>vm_area_struct</strong>의 필드에는 시작 주소, 끝 주소, 권한, 파일명 등의 정보가 들어있다.</li>
</ul>
</li>
<li>또한 <strong>mm구조체</strong>에는 <strong>pgd</strong>(page directory. 가상메모리 -&gt; 물리메모리 매핑에 사용되는 페이지 테이블의 주소.)가 있다.<ul>
<li>pgd에는 2^10(32bit 기준) 사이즈의 엔트리를 갖는 디렉토리가 있다.</li>
<li>pgd의 디렉토리의 비어있지 않은 세그먼트에는 각각 2^10의 엔트리를 갖는 page table 엔트리가 있다.</li>
</ul>
</li>
</ul>
</li>
<li>프로세스가 현재 메모리에 없는 가상 주소를 접근하려고 하면, 프로세서는 참조된 가상 페이지에 대한 페이지 테이블 엔트리를 찾을 수 없을 것이다. 이를 <strong>page faul</strong>t가 났다고 한다.<ul>
<li>page fault 시, 물리적 메모리 공간이 있으면, 운영체제는 해당하는 페이지를 디스크의 이미지로부터 메모리로 가져온다.<ul>
<li>가져온 페이지는 빈 물리적 페이지 <strong>프레임에</strong> 기록된다.</li>
<li>가상 페이지 <strong>프레임 번호</strong>를 위한 엔트리가 프로세스의 <strong>테이블에</strong> 추가된다.</li>
</ul>
</li>
<li>만약, 물리적 메모리에 공간이 없다면 <strong>LRU 알고리즘</strong>으로 공간을 마련한다.<ul>
<li>이떄, 페이지가 변경되어 페이지의 내용을 나중에 다시 사용할 수 있도록 보존해야 한다면, <strong>스왑 파일</strong>이라는 특별한 파일로 디스크에 저장한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0">https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0</a></li>
<li><a href="https://jeongzero.oopy.io/category/kernel">https://jeongzero.oopy.io/category/kernel</a></li>
<li><a href="https://wayhome25.github.io/cs/2017/04/13/cs-15-1/">https://wayhome25.github.io/cs/2017/04/13/cs-15-1/</a></li>
<li><a href="https://junghyun100.github.io/%EB%A9%94%EB%AA%A8%EB%A6%AC%EB%8B%A8%ED%8E%B8%ED%99%94/">https://junghyun100.github.io/메모리단편화/</a></li>
<li><a href="https://developer.ibm.com/articles/l-kernel-memory-access/">https://developer.ibm.com/articles/l-kernel-memory-access/</a></li>
<li><a href="https://jeongzero.oopy.io/359eaa11-b6e6-466c-a066-9ae582c886d4">https://jeongzero.oopy.io/359eaa11-b6e6-466c-a066-9ae582c886d4</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux Kernel] File System of Linux]]></title>
            <link>https://velog.io/@whattsup_kim/Linux-Kernel-Linux-File-System</link>
            <guid>https://velog.io/@whattsup_kim/Linux-Kernel-Linux-File-System</guid>
            <pubDate>Sun, 16 Oct 2022 10:06:16 GMT</pubDate>
            <description><![CDATA[<p>이제 본격적으로 Linux의 File System에 대해 알아보자. </p>
<h1 id="virtual-file-systemvfs">Virtual File System(VFS)</h1>
<p>유닉스 파일 시스템에는 다음과 같이 4가지로 구성되어 있다고 배웠다.</p>
<ul>
<li>Boot Block : 파일시스템에 UNIX 커널을 적재시키기 위해 부팅 시 필요한 코드를 저장하고 있는 영역</li>
<li>Super Block : 파일시스템에 있는 총 블록의 수, 블록 크기, 비어 있는 블록을 가리키는 포인터 등에 대한 정보(전체 파일시스템에 관한 메타데이터)를 가지고 있다.</li>
<li>i-node Block : 각 파일에 대한 대부분의 정보를 가지고 있는 레코드(각 파일에 관한 메타데이터)</li>
<li>Data Block : 실제 데이터가 저장되는 공간</li>
</ul>
<p>이러한 파일시스템은 각 회사마다 다르기 때문에, 다른 회사의 파일시스템끼리는 호환성이 없다. 즉, <strong>옛날 유닉스 환경</strong>에서는 <strong>오로지 유닉스 파일시스템만 마운트</strong>가 가능했었다.</p>
<p>하지만 리눅스는 다르다. <strong>리눅스는 다른 종류의 파일시스템도 마운트가 가능</strong>하다. 이렇게 각기 다른 파일 시스템을 서포팅하기 위한 시스템이 바로 <strong>VFS Layer</strong>이다. </p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/bdf0bf66-373b-4130-97db-ff03214ebb15/image.png" alt=""></p>
<ul>
<li>리눅스에서는 어떤 파일시스템이 사용되든 간에 VFS Layer를 통하게 되어있다.</li>
<li><strong>VFS란, 파일시스템 인터페이스를 유저 공간 프로그램에 제공하기 위해 구현된 커널의 서브 시스템(표준 인터페이스)</strong>이라고 볼 수 있다.</li>
<li><strong>VFS</strong>에서는 서로 다른 디바이스의 서로 다른 파일 시스템에 대해 <strong>읽고 쓰는 작업을 하기 위해 표준 시스템 콜을 사용</strong>할 수 있게 해준다.<ul>
<li>standard operation : read(), write(), open(), …</li>
<li>standard data structure : superblock, i-node, …</li>
</ul>
</li>
<li><strong>VFS</strong>는 <strong>추상화 계층</strong>이므로, 실제 구현은 <strong>Actual physical File System Layer</strong>에서 진행한다. 여기에는 각 파일시스템(Ext2, Solaris, Windows, …)에 따라 <strong>각기 다른 구현 로직이 들어있다</strong>.</li>
</ul>
<h2 id="vfs-standard-objects">VFS Standard Objects</h2>
<p>리눅스는 파일시스템이 어떤 회사의 것이든 상관없이 다음 4가지의 Objects들을 정의한다.</p>
<ol>
<li>superblock object : 파일 시스템 전체에 대한 structure (<strong>file system</strong> control block)</li>
<li>inode object : 각 파일에 대한 structure (<strong>file</strong> control block)</li>
<li>file object : 실제 데이터 (<strong>offset</strong> and interaction between [open_file - precess])</li>
<li>dentry object : path_name에 매핑된 i-node 정보가 담김 (pathname → inode)</li>
</ol>
<p>1 ~ 3은 유닉스 파일시스템에서 배웠으니, dentry object에 대해서만 배워보도록 하자.</p>
<h2 id="dentry-object">dentry object</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/2417bc9c-d40c-4a22-b830-bd85bf3c3e4e/image.png" alt=""></p>
<ul>
<li>(위 그림) 현재 <code>/a/b/c/d/e</code>를 읽으려고 한다.</li>
<li>그럼 디스크에서 루트부터 시작해서 루트 inode를 가져오고, inode의 data block을 보고, 또 a inode를 가져오고, …. 를 반복하여 결국 e에 대한 inode를 얻게 된다.<ul>
<li>이러한 과정을 <strong>Path Component</strong>s라고 한다.</li>
</ul>
</li>
<li>이러한 과정(반복)은 많은 오버헤드(수 많은 disk I/O)를 발생시켰기 때문에 엔지니어들은 이러한 단점을 해결해야 했다.<ul>
<li>예를 들어, 상위 루트로부터 path component를 하게 되면(그림에서 2번) ,</li>
<li>이전에 / → /a → /ab → /abc라는 path conponent 작업을 했음에도, 다음 /a/b/c/d로 접근하기 위해서 또 / → /a → /a/b → /a/b/c → /a/b/c/d의 <strong>inode에 접근하는 과정을 반복</strong>해야한다.</li>
</ul>
</li>
<li>따라서 <strong>dentry structure</strong>가 등장하게 된다.</li>
</ul>
<p><strong>dentry structure</strong>는 쉽게 말하면, 효율을 위해 Dentry Cache를 저장하는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/ff931a65-da3c-4b30-87f5-001619c3abfe/image.png" alt=""></p>
<ul>
<li><p>dcache(Dentry Cache)는 directory-entry (= dentry)의 cache를 말하는 것이다.</p>
<ul>
<li>(dentry는 기본적으로 디렉터리 내의 요소들, 즉 파일 (및 하위 디렉터리의) 이름을 나타내는 것임)</li>
<li>이름 외의 파일에 대한 정보는 inode 상에 저장된다. 물론 dentry에는 주어진 이름을 통해 해당하는 inode를 알아낼 수 있도록 inode 번호를 함께 저장한다.</li>
<li>이러한 것은 기본적으로 파일 시스템 상의 디스크 블록에 저장되어 있게 되지만, 성능 향상을 위해 이를 <strong>메모리에 저장해 두는 것</strong>이 바로 <strong>dcache</strong>가 된다.</li>
</ul>
</li>
<li><p>dcache는 부모 dentry와 파일 이름을 키로 하는 <strong>hash table</strong>로 구현되어 있다.</p>
</li>
<li><p>시스템의 가용 메모리 크기에 따라 동적으로 크기를 조정하기 위해 <strong>LRU 리스트</strong>를 별도로 유지한다.</p>
</li>
<li><p>예를 들어, /a/b/c/d를 접근하는 과정을 했다고 가정한다면, 다음 그림과 같이 5개의 dentries가 만들어지는 것이고, 이후 /a/b/c에 접근 할 때에는 해당 dentry를 이용하여 빠르게 접근할 수 있게 되는 것이다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/7996c9c1-5615-4786-bfd6-9597f8333e4f/image.png" alt=""></p>
</li>
</ul>
<h1 id="proc-file-system">/proc file system</h1>
<p>이번에는 VFS 하위에 존재하는 <strong>proc</strong>라는 파일 시스템에 대하여 설명한다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/8c7ee9e7-32e2-41c2-af84-b7f57a9e68bc/image.png" alt=""></p>
<ul>
<li><strong>/proc</strong> 파일 시스템은 소프트웨어적으로 생성되는 <strong>dynamic</strong>한 (특별한) 파일시스템이다.</li>
<li>즉, 일종의 <strong>가상 파일 시스템</strong>이다(이 파일들은 실제 물리적인 디스크에 존재하는 것이 아닌, 메모리에 저장되어 있는 것이며, 특정 커널 함수를 호출시키는 파일임)</li>
<li>proc파일 시스템은 <strong>운영체제의 각종 정보</strong>(프로세스 정보, 다른 시스템 정보, ..)를 커널모드가 아닌 유저모드에서 쉽게 접근할 수 있도록 만들어 줌으로 시스템 정보를 일반 프로그래머가 쉽게 접근 할 수 있도록 도와준다.</li>
<li>Proc의 목적은 <strong>커널 자원과 컴포넌트를 쉽게 보여주는데에 있으며, 현재 시스템에서 실행 중인 프로세스 정보도 확인 가능하다.</strong> 특히 따로 API를 호출하지 않고 시스템 정보를 가져와 읽기 쉬운 형태로 나타내어 주는 점이 특징이다(파일시스템의 오버헤드를 줄일 수도 있음)</li>
</ul>
<p>proc 파일에는 다음과 같은 종류가 있다.</p>
<table>
<thead>
<tr>
<th align="center">파일</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center">/proc/[PID]/maps</td>
<td>프로세스가 mapping 된 메모리 주소 공간. 모든 프로세스에는 각자 주소 공간이 있으며, 이 주소 공간은 가상 메모리 관리자(VMM)가 제공하고 관리</td>
</tr>
<tr>
<td align="center">/proc/[PID]/cmdline</td>
<td>프로세스 인수(argv) 전체를 포함. Command Line에서 넘어온 argumnet를 포함하여 프로세스가 질생된 방식을 정확하고 신속하게 파악하는 수단으로 사용</td>
</tr>
<tr>
<td align="center">/proc/[PID]/coredump_filter</td>
<td>메모리 유형의 비트마스크를 포함하며 프로세스의 어떤 메모리 세그먼트를 덤프시킬 것인지 설정</td>
</tr>
<tr>
<td align="center">/proc/[PID]/cwd/</td>
<td>프로세스가 사용중인 디렉토리나 파일</td>
</tr>
<tr>
<td align="center">/proc/[PID]/environ</td>
<td>프로세스의 현재 환경을 저장. 프로세스 map에서 가장 아랫부분, 즉 커널이 프로세스 환경 정보를 저장하는 메모리 위치를 직접 가리키는 링크</td>
</tr>
<tr>
<td align="center">/proc/[PID]/exe</td>
<td>실행중인 프로그램 이름</td>
</tr>
<tr>
<td align="center">/proc/[PID]/fd/proc/[PID]/fdinfo</td>
<td>프로세스가 사용중인 File Descriptor 링크와 정보 저장</td>
</tr>
<tr>
<td align="center">/proc/[PID]/limits</td>
<td>프로세스에 적용된 resource 제한 사항</td>
</tr>
<tr>
<td align="center">/proc/[PID]/loginuid</td>
<td>해당 프로세스를 실행하는 login UID</td>
</tr>
<tr>
<td align="center">/proc/[PID]/mem</td>
<td>프로세스가 사용중인 메모리 상태</td>
</tr>
<tr>
<td align="center">/proc/uptime</td>
<td>시스템 가동 시간에 대한 정보를 기록한다.</td>
</tr>
<tr>
<td align="center">/proc/meminfo</td>
<td>물리적 메모리 및 스왑 메모리 정보가 들어 있는 파일이다.</td>
</tr>
<tr>
<td align="center">/proc/cmdline</td>
<td>부팅 시에 실행되는 커널 관련 옵션에 대한 정보를 담고 있다.</td>
</tr>
<tr>
<td align="center">/proc/loadavg</td>
<td>최근 1분, 5분. 15분 동안의 평균 부하율을 기록하는 파일이다.</td>
</tr>
<tr>
<td align="center">/proc/modules</td>
<td>현재 모듈로 로딩된 모듈 목록, lsmod 했을때 나오는 정보</td>
</tr>
<tr>
<td align="center">/proc/mounts</td>
<td>마운트된 파일시스템에 대한 정보</td>
</tr>
<tr>
<td align="center">/proc/partitions</td>
<td>현재 시스템의 파티션 정보</td>
</tr>
<tr>
<td align="center">/proc/stat</td>
<td>CPU, 인터럽트, 컨텍스트 스위치 등 일반적인 시스템 통계 정보</td>
</tr>
</tbody></table>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0">https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0</a></li>
<li><a href="https://jeongzero.oopy.io/category/kernel">https://jeongzero.oopy.io/category/kernel</a></li>
<li><a href="http://egloos.zum.com/studyfoss/v/5469672">http://egloos.zum.com/studyfoss/v/5469672</a></li>
<li><a href="https://www.joinc.co.kr/w/Site/system_programing/proc/ProcFsPrograming">https://www.joinc.co.kr/w/Site/system_programing/proc/ProcFsPrograming</a></li>
<li><a href="https://secmem.tistory.com/150">https://secmem.tistory.com/150</a></li>
<li><a href="https://itwiki.kr/w/%EB%A6%AC%EB%88%85%EC%8A%A4_proc">https://itwiki.kr/w/리눅스_proc</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux Kernel] File System of Unix]]></title>
            <link>https://velog.io/@whattsup_kim/Linux-Kernel-File-System-of-Unix</link>
            <guid>https://velog.io/@whattsup_kim/Linux-Kernel-File-System-of-Unix</guid>
            <pubDate>Sun, 16 Oct 2022 10:01:34 GMT</pubDate>
            <description><![CDATA[<p>Linux File System을 공부하기에 앞서 근간이 되는 Unix File System을 알아보자.</p>
<h1 id="fcbfile-control-block">FCB(File Control Block)</h1>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/dcd6f844-c678-40fd-b10b-f485f004eff1/image.png" alt=""></p>
<p>이전에 배웠듯이, 커널(분홍색 영역)은 하드웨어 자원들과 각종 Process들을 관리한다.</p>
<ul>
<li>커널은 이러한 자원들을 관리하기 위한 Data Structure를 각 하드웨어와 프로세스들 마다 가지고 있다.<ul>
<li>ex) 위 그림에서 <code>mem</code>(Data Structure)에는 메모리의 총 크기가 어느정도이며, 어디서부터 어디까지 사용되고 있는지 등에 대한 정보가 담겨있음</li>
</ul>
</li>
<li>이때, 프로세스들을 관리하기 위한 Data Structure는 (이전에 배웠던) <strong>PCB(Process Control Block)</strong>라고 불렀음.</li>
<li>또한, 파일들을 관리하기 위한 Data Structure는 <strong>FCB(File Control Block)</strong>라고 부름.</li>
</ul>
<p><strong>File Control Block</strong>은 이름을 보면 알 수 있듯, File 하드웨어를 위한 <strong>메타데이터</strong>이다. 여기에는 다음과 같은 정보들이 들어있다.</p>
<ul>
<li>Owner : 파일 소유자가 누구인지 (eg. Clinton)</li>
<li>Protection : 파일의 퍼미션을 무엇인지 (eg. rwx r-- r--)</li>
<li>Device : 파일이 어디에 있는지 (eg. disk)</li>
<li>Content : 파일 내용은 어디서 찾을 수 있는지 (eg. sector address)</li>
<li>Device driver routines : 파일이 들어있는 디바이스의 어떤 부분을 읽어야 함수를 호출할 수 있는지(eg. read(), open() )</li>
<li>Accessing where now : 현재 파일의 어디를 읽고 있는지 (eg. offset)</li>
</ul>
<p>이제 파일 저장부터 알아보도록 하자.</p>
<h1 id="file-store">File store</h1>
<p>파일을 disk에 저장할 때에는 다음과 같은 방식들을 사용할 수 있다.</p>
<ul>
<li><p><strong>Contiguous allocation (variable size)</strong></p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/47994c55-2a6c-4f70-a0a9-474ecc6676ae/image.png" alt=""></p>
<ul>
<li><strong>Contiguous allocation</strong>은 간단하게 설명하면, 차례대로(연속적으로) 파일을 저장하는 방식이다.</li>
<li>하지만 Contiguous allocation는 중간에 파일이 삭제되고 나면, <strong>hole이 생기는 단점</strong>을 가지고 있다.<ul>
<li>만약 새로 저장할 파일의 사이즈가 구멍의 사이즈보다 크게 된다면, 단편화 문제가 생기게 된다.<ul>
<li>** 외부 단편화 : 메모리(디스크) 할당 시 공간들이 연속적으로 붙어있지 않아 빈 공간이 생기는 것</li>
<li>** 내부 단편화 : 메모리(디스크) 할당 시 프로세스보다 더 큰 공간을 할당받아서 그 나머지 공간이 낭비되는 것</li>
</ul>
</li>
<li>(디스크 조각 모음이 바로 이 hole을 메꾸는 작업이다.)</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Scattered allocation (fixed size)</strong></p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/39b7cb3e-edfe-4637-9e58-91683b3e2038/image.png" alt=""></p>
<ul>
<li><strong>Scattered allocation</strong>은 디스크에 파일을 저장할 때 그대로 저장하는 것이 아니라, 동일한 크기로 조각을 낸 후, 이를 디스크의 섹터에 저장시키는 것이다.</li>
<li>이러한 방법을 사용하면 Contiguous allocation에서 보았던 단점(hole)을 보완할 수 있다.</li>
<li>그러나 디스크에서 원하는 조각들을 찾기 위해 <code>lseek()</code>같은 함수로 파일 포인터를 옮겨다닐 때 딜레이가 생길 수 있다.<ul>
<li>반면, <strong>Contiguous allocation</strong>는 첫 포인터만 찾으면 연속적으로 빨리 파일을 읽을 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>두 방법에는 서로 장단점이 존재하므로, OS에서는 두 방법을 동시에 사용한다. 디스크 파티션 중 일부는 <strong>Contiguous allocation</strong>를 사용하고, 나머지 일부는 <strong>Scattered allocation(대부분은 이 방법을 사용)</strong>를 사용하는 것이다.</p>
<h1 id="file-open">File open</h1>
<p>파일들이 디스크에 저장되어 있는 상태에서 이들을 read할 때에는, 그 <strong>파일들의 섹터들의 주소</strong>들을 다 알아야 한다. 이 섹터들의 주소는 <strong>(FCB에 있는)File 메타데이터</strong>에 저장이 되어있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/e35e3311-beb5-4a7b-b3d1-44bfc6979f44/image.png" alt=""></p>
<ul>
<li>우리는 open을 위해 FCB에 존재하는 <strong>메타데이터</strong>들만 우리 메모리로 가져오고, 메모리에서는 파일의 데이터 섹터를 접근하기 위해 복사해 온 <strong>메타데이터 안의 정보를 사용</strong>하면 된다.</li>
<li>따라서 데이터 섹터 자체가 올라오는 것이 아니라, 필요할 때마다 메타데이터를 보고 필요한 데이터 섹터에 접근하여 가져올 수 있다.</li>
</ul>
<p>그런데 만약 여러 개의 프로세스들이 동일한 파일을 사용하게 된다면, 세 프로세스 모두 동일한 메타데이터를 복사해와야 할 것이고, 중복이 발생하게 된다. 이를 방지하기 위해서는 <strong>메타데이터를 공유해서 사용</strong>하기 위한 방법도 생각해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/3cd1ccf2-3aba-4822-902b-0442c6276555/image.png" alt=""></p>
<ul>
<li><p>따라서 중복으로 인해 낭비가 발생하는 정보들은 프로세스끼리 공유해서 사용하도록 한다.</p>
</li>
<li><p>그러나, 반드시 공유되면 안 되는 메타데이터 정보가 하나 있는데, 그것은 바로 <strong>offset</strong>이다.</p>
<ul>
<li>파일의 어떤 부분을 읽고 있는지에 대한 정보는 프로세스별로 나눠져야 한다.</li>
</ul>
</li>
<li><p>따라서 file의 메타데이터는 두 개의 구조체로 구분된다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/b4c818a4-cf5b-451c-9576-ea3afe077a6a/image.png" alt=""></p>
<ul>
<li><strong>inode struct</strong> : 모든 프로세스들이 <strong>공유</strong>하는 메타데이터</li>
<li><strong>file struct</strong> : 각 프로세스가 <strong>개별</strong>적으로 가지고 있는 메타데이터 (offset)</li>
</ul>
</li>
</ul>
<h2 id="inode-struct">inode struct</h2>
<p><strong>inode</strong> 구조체에는 다음과 같은 다양한 정보가 들어있다. </p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/00ae6bfc-41fb-4b0b-86eb-55455872489b/image.png" alt=""></p>
<ul>
<li>전부 <code>i_</code>로 시작하는 필드임</li>
<li><code>i_addr[8]</code>에 실제 파일의 <strong>섹터 주소</strong>가 들어있음</li>
</ul>
<p>디스크 공간에는 이러한 inode들이 모여 있는 <strong>inode block</strong>이 다음과 같이 따로 존재한다(전체 디스크의 1%정도를 차지).</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/1a04d72d-3c0c-4d95-b066-3fc8abf11a60/image.png" alt=""></p>
<ul>
<li><p>(data block에는 실제 데이터가 들어가있다.)</p>
</li>
<li><p>각 inode는 숫자를 가지고 있으며, 이를 <strong>inode number</strong>라고 한다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/ac71ce08-cfc8-417b-9603-a664cc552b97/image.png" alt=""></p>
<ul>
<li>이 inode number로 해당하는 inode로 접근하고, 여기서 content로 접근하게 된다.</li>
</ul>
</li>
</ul>
<h2 id="file-struct">file struct</h2>
<p><strong>file struct</strong>에는 다음과 같은 필드로 구성되어져 있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/83f5daf8-8224-47d7-a1a6-8279350d20e0/image.png" alt=""></p>
<ul>
<li>전부 <code>f_</code>로 시작하는 필드임.</li>
<li><code>f_offset</code>에는 inode table을 가리키는 인덱스가 들어있음.</li>
</ul>
<h2 id="device-switch-table">Device switch table</h2>
<p><strong>Device switch table</strong>에는 어떠한 디바이스에서 어떠한 동작을 수행할 것인지에 대한 정보가 2차원 배열 형태로 들어있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/b5a755ec-55a6-4d7e-9399-fad7447bad42/image.png" alt=""></p>
<ul>
<li><p>이러한 정보를 <strong>device driver routine</strong>이라고 부른다.</p>
</li>
<li><p>이는 <strong>devswtab[] 배열 형태</strong>로 되어있다.</p>
</li>
<li><p>예를 들어 위 그림에서 [device: printer, operation: open]과 같이 접근한다면, 프린터의 <strong>open operation driver routine의 시작 주소</strong>를 얻을 수 있다.</p>
</li>
<li><p>(사실 <strong>devswtab[] 배열</strong>은 다음과 같이 1차원 배열의 구조로 되어있다.)</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/5dc03261-e7dd-43dc-a871-7079f050c19f/image.png" alt=""></p>
</li>
<li><p>“Unix에서는 모든 것이 file로 관리된다.”</p>
<ul>
<li>이처럼 Unix에서는 일반 정규 파일부터 디렉토리, 소켓, 파이프 등 모든 객체들을 파일로 관리함</li>
</ul>
</li>
</ul>
<h2 id="file-open-과정">File open 과정</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/f9227863-8dac-4b3e-9f91-4e2e21f82c7e/image.png" alt=""></p>
<ul>
<li>컴퓨터가 부팅이 되면, 가장 먼저 커널 프로그램이 올라오면서, 각종 하드웨어 Data Structure역시 올라온다.<ul>
<li>FCB가 올라오며 가장 앞에 존재하는 inode 0이 올라옴</li>
<li>이는 root directory file로 <code>‘/&#39;</code>루트임.</li>
</ul>
</li>
<li>이제, 만약 유저가 <code>/a/b</code> 파일을 open 해 달라고 시스템 콜을 요청한다고 가정하자(위 그림).<ol>
<li>우선 file table에서 <code>/a/b</code>파일의 inode(으로)를 inode table로 가져와야 한다. <ul>
<li>현재 우리가 아는건 inode 0. 즉, <code>‘/&#39;</code>(루트) 밖에 없으므로, 해당 inode의 포인터를 따라가서 가져와야 한다.</li>
</ul>
</li>
<li>inode 0에 연결된 포인터에는 <code>‘/&#39;</code>(루트)의 content가 들어있다. 여기서 원하는 inode를 가져온다.<ul>
<li><code>‘/&#39;</code>(루트)의 content에는 실제 root directory file에 들어있는 data들이 존재하는데, 현재(그림)는 <code>a</code>, <code>bin</code>, <code>x</code> 3개의 file이 들어있고, 이 파일들은 각각 i-number가 존재한다.</li>
</ul>
</li>
<li>우리는 <code>/a</code> 파일로 가야하기에, 디스크에서 해당 inode number인 7을 찾아서 가져온다.<ul>
<li>이제 inode table에는 <code>/</code>와 <code>a</code> inode가 들어있다.</li>
</ul>
</li>
<li>위와 동일한 방식으로 <code>a</code> inode를 따라가서 해당 content가 들어있다. 여기서 원하는 inode를 가져온다.<ul>
<li><code>a</code> inode의 content를 보면, <code>b</code>, <code>usr</code>, <code>y</code>가 있다. 이 중 b의 inode number가 3이므로, 디스크에서 inode-3을 찾아서 가져온다.</li>
</ul>
</li>
<li>이제 <code>/a/b</code>를 open하기 위한 준비가 끝났다. file 구조체에서 offset을 0으로 하여 생성한 뒤, inode <code>b</code>를 가르키게 한다. </li>
<li>이제 마지막으로 실제 프로세스의 <strong>PCB</strong>에 존재하는 open한 파일들을 관리하는 배열(<strong>u_ofile[]</strong>)에 생성한 <strong>file→offset 주소</strong>를 넣는다.</li>
</ol>
</li>
<li>결국 유저에게 최종적으로 return되는 <strong>fd(file descriptor(open file table))</strong>는 <strong>u_ofile[]의 인덱스</strong>이다. 우리는 여기에 <code>read(4, buf, size)</code>와 같이 해당 파일에다가 쓰기 작업을 하게 되는 것이다.</li>
</ul>
<p><strong>fd table</strong>에 대해 좀 더 자세히 알아보자</p>
<h1 id="fdfile-descriptor-table--open-file-table">FD(File descriptor) table (= open file table)</h1>
<p>FD table의 특징은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/ee3a8486-11e3-4db0-ac63-97e04468f118/image.png" alt=""></p>
<ul>
<li>PCB에 존재</li>
<li>각 프로세스마다 <strong>open한 파일들의 정보</strong>가 담겨있음</li>
<li><code>open(path_name)</code>으로 call하면, 커널이 해당 파일을 open하여 <strong>fd를 리턴</strong>해줌<ul>
<li>시스템은 사용하지 않는 fd 중 가장 작은 값(0과 음수가 아닌 정수값)을 할당해줌.</li>
<li>0, 1, 2는 예약된 fd임(순서대로 stdin/out/err). 따라서 유저가 open했을 떈 fd=3부터 할당됨.</li>
</ul>
</li>
<li>프로세스가 시스템 콜을 통해 파일들에 접근할 땐 이 fd를 사용(파일을 지칭).</li>
</ul>
<h1 id="file-read--write">File read &amp; write</h1>
<p>파일을 열었으니, 이제 Read하고 Write하는 과정에 대해 알아보자.</p>
<h2 id="accessing-file-with-fd">Accessing File with fd</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/e8cb35f5-8bae-4f97-b612-f869899f2e8c/image.png" alt=""></p>
<ul>
<li>그림과 같이 <code>read(4, var, count)</code>라고 요청을 하면, 우선 유저 프로세스의 PCB에 들어있는 <strong>fd table</strong>을 순회한다.</li>
<li>요청한 인덱스 4를 찾았으면, <code>fd_table[4]</code>에 들어있는 <strong>file→offset 주소</strong>를 확인한다.</li>
<li>이후 file table에서 <strong>offset→inode</strong>로 주소를 확인한다.</li>
<li>이후 inode table의 (해당하는) inode에서 실제 데이터의 섹터 주소를 얻을 수 있다.</li>
<li>inode에는 디바이스 정보도 들어있으므로, 만약 disk라면 disk의 핸들러 중 read를 찾고 실제 루틴이 시행된다.<ul>
<li>이는 위에서 설명한 Device switch table임.</li>
</ul>
</li>
</ul>
<p>결국 과정을 보면, open 할 때에만 <code>path_name</code>을 사용하고, read/write/close를 할 때에는 전부 open으로 얻은 fd(file descriptor)로만 관리한다.</p>
<ul>
<li>open할 때 수행되었던 로직들을 반복하여 수행하는 것은 비효율적이므로 이러한 방식을 사용함.</li>
<li>fd로만 접근하면, file struct, inode struct, device switch로만 메모리에 access하여 원하는 정보를 다 얻어올 수 있기 때문에 효율적임</li>
</ul>
<h1 id="balance-tree">Balance tree</h1>
<p>현재 내가 가진 디스크의 용량이 10GB이고, 섹터의 사이즈가 1K라고 해보자. </p>
<ul>
<li><p>내가 현재 사용할 수 있는 섹터는 10,000,000,000 / 1000 = 10,000,000 sectors 이다.</p>
</li>
<li><p>만약 1000개의 섹터를 가르키는 포인터를 만드려면, 각 섹터 포인터는 24bits가 필요하다.</p>
</li>
<li><p>각 섹터들이 가지고 있는 주소도 어딘가에 저장해야 하는데, 이 저장위치도 섹터이다. 따라서 많은 정보를 저장하기 위해서는 수 많은 섹터들이 필요할 것이다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/88d35bb8-1d45-4488-9840-81f8edafa6ce/image.png" alt=""></p>
<ul>
<li><p>위 그림처럼 각 섹터들을 50(m)개 단위로 묶는다면, 이 50(m)개를 관리하기 위한 또 다른 섹터가 필요하고, 이러한 관리 섹터가 많아진다면 이 관리 섹터를 관리하기 위한 관리 섹터도 필요하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/b9ae950b-90b6-4c31-8ccf-c3542deba527/image.png" alt=""></p>
</li>
<li><p>결국 이 같은 일이 반복되다 보면 위 그림처럼 점점 큰 트리 형태가 만들어질 것이다.</p>
</li>
</ul>
</li>
</ul>
<p>일반적인 이진 트리인 경우 양쪽의 depth가 서로 다를 수 있지만, 섹터가 트리 형태로 관리될 땐 양쪽의 depth가 모두 동일하다. 따라서 이러한 tree를 <strong>Balanced Tree(B-Tree)</strong>라고 부른다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/7f57acab-b6ca-4cae-9a4d-2d59b2c05068/image.png" alt=""></p>
<ul>
<li>B-Tree에서 우리는 용량이 큰 데이터를 저장하고, 이용할 떈 제일 최상위에 존재하는 하나의 inode만 알고 있으면 된다.<ul>
<li>루트 inode를 말면, 여기에 연결되어 있는 자식 노드에 접근해서 데이터를 얻어오면 되기 떄문이다.</li>
<li>제일 최상단에 존재하는 노드를 마스터 인덱스라고 부른다.</li>
</ul>
</li>
</ul>
<h1 id="file-system-in-disk">File System in Disk</h1>
<h2 id="super-block">super block</h2>
<p>File system은 각 data 섹터들을 관리하기 위한 inode와 실제 content가 담긴 data block으로 구성되어 있다고 배웠다. 이 inode와 data의 관리가 또 필요하기 때문에 유닉스에는 <strong>super block</strong>이라는 영역이 존재한다. </p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/5f2d5325-ae90-43ec-b787-5cb24dc50e8e/image.png" alt=""></p>
<ul>
<li><p><strong>superblock</strong>의 주 역할은 inode나 data의 <strong>섹터들이 사용 후 delete 되었을 때 이를 관리</strong>하기 위함이 가장 크다.</p>
</li>
<li><p>따라서 <strong>inode hole sector(free inodes)</strong>와 <strong>data hole sector(free data blocks)</strong>를 가리키는 포인터들이 존재한다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/c8b060e9-ca0a-4e83-96d8-5fc781b29b96/image.png" alt=""></p>
</li>
<li><p>추가적으로는 <strong>global 정보</strong>가 들어있다.</p>
<ul>
<li>현재 inode, data block의 <strong>사이즈</strong>는 몇 인지, <strong>접근 권한</strong>은 뭔지 등에 대한 정보</li>
</ul>
</li>
</ul>
<p>따라서, 결국 <strong>File System은 SuperBlock, inode, data 영역으로 구성</strong>되어 있는 것이다. 이들을 도식화하면 다음과 같이 나타낼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/c16a5344-0bd0-4cbc-9ac9-fffdeb118783/image.png" alt=""></p>
<ul>
<li>(여기서 M은 inode의 사이즈를, N은 data block의 사이즈를 의미한다)</li>
</ul>
<h2 id="boot-block">boot block</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/0393cc6b-43bd-4fad-84a6-a04fd961e675/image.png" alt=""></p>
<p>추가로, 리눅스에는 <strong>boot block</strong>도 존재한다. boot block은 이름 그대로 <strong>부팅 시에 로딩되기 위해 필요한 정보</strong>가 저장되어 있다.</p>
<h2 id="mount">Mount</h2>
<p>이렇게 유닉스의 <strong>File System</strong>은 Bootblock, Superblock, I-node, Datablock 으로 구성되어 있따. 이때 이러한 <strong>File System</strong>은 disk에만 존재하는 것이 아니라, USB 같은 곳에도 들어있으며, 하드가 여러 개라면, 각 하드마다 하나씩 존재한다. 그렇다면 이렇게 <strong>여러 개의 File System</strong>이 있을 때, 어느 것이 <strong>root</strong>가 되어야 할까?</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/a883a8a3-097c-4abe-98fa-b34198ffbb89/image.png" alt=""></p>
<ul>
<li><p>우선, 유닉스에서는 여러 File System 중에서 하나를 무조건 root로 하여 부팅을 해야한다.</p>
</li>
<li><p>만약 위 그림처럼 FS_1을 root로 부팅했다면, 부팅 시 화면에 FS_1이 루트로 세팅되어 올라오게 된다.</p>
</li>
<li><p>그리고 FS_2나 FS_3에 접근할 때에는 <strong>마운트(Mount)(시스템 콜)</strong>를 이용하여 접근하게 된다</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/7b04fbb3-1058-41fa-a4d8-6614719a8aa0/image.png" alt=""></p>
</li>
<li><p>위 그림처럼, 만약 FS_1의 root로 부팅을 했고, FS_1의 하위폴더 중 <code>/usr</code>에다가 FS_3(3번 디스크)를 마운트시키면, <code>/usr</code>를 통해 <code>/dev/dsk3</code>의 FS에 접근할 수 있게 된다. 즉, <code>/usr/</code>은 다음 그림처럼 <code>/dev/dsk3</code>의 <strong>root</strong>가 되어버리는 것이다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/9bb10a8e-ac8d-4e7c-b30b-127eb3ba5117/image.png" alt=""></p>
</li>
</ul>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0">https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0</a></li>
<li><a href="https://jeongzero.oopy.io/category/kernel">https://jeongzero.oopy.io/category/kernel</a></li>
<li><a href="https://www.computerhope.com/jargon/f/file-descriptor.htm">https://www.computerhope.com/jargon/f/file-descriptor.htm</a></li>
<li><a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=jvioonpe&amp;logNo=220377775960">https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=jvioonpe&amp;logNo=220377775960</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux Kernel] interrupt]]></title>
            <link>https://velog.io/@whattsup_kim/Linux-Kernel-interrupt</link>
            <guid>https://velog.io/@whattsup_kim/Linux-Kernel-interrupt</guid>
            <pubDate>Sun, 16 Oct 2022 09:49:34 GMT</pubDate>
            <description><![CDATA[<h1 id="리눅스의-시간-관리">리눅스의 시간 관리</h1>
<h2 id="용어---hz-juffies">용어 - HZ, juffies</h2>
<p><strong>HZ</strong></p>
<ul>
<li>HZ란 1초동안 몇 번 째깍거렸나를 나타내는 지표임</li>
<li>ex) 1초에 천 번 쨰깍거린다면, 1000HZ(Hertz)가 될 것임</li>
<li>이를 컴퓨터적인 해석으로 표현하면 다음과 같다<br>  <code>#define HZ 1000</code></li>
<li>위 표현의 의미는 <strong>1초에 1000번 인터럽트(interrupt)가 걸린다는 의미</strong>이다.<ul>
<li>대부분의 경우 HZ는 100을 걸어둔다(너무 많은 인터럽트는 오버헤드를 증가시키기 때문).</li>
</ul>
</li>
</ul>
<p><strong>jiffies</strong></p>
<ul>
<li>시스템이 부팅된 후, 몇 번 째깍거렸는지를 <strong><code>jiffies</code></strong>라고 함</li>
<li><code>jiffies</code>는 global 변수이며, 카운터의 역할을 함</li>
<li>이 <code>jiffies</code>를 설정된 HZ로 나누면, 몇 초가 흘렀는지 알 수 있음<ul>
<li><code>jiffies</code>가 3000이고 HZ가 100이라면, 부팅된 시간은 (3000/100 →) 30초 전이라는 것을 알 수 있다.</li>
</ul>
</li>
</ul>
<p>그렇다면 <strong>왜 HZ가 필요한걸까?</strong> 필요할 떄(입력이 생겼을 때)만 인터럽트를 하면 안 되는 걸까?</p>
<ul>
<li>시스템에 시간 단위를 도입한 이유는 특정 시간마다 <strong>반복이 필요한 일</strong>들을 처리하려면, <strong>시스템이 시간의 개념을 알아야</strong> 하기 때문임</li>
<li>또한 시스템이 <strong>스케줄링</strong> 등을 할 때 필요하게 됨<ul>
<li>전 시간에 설명한 timeslice를 생각해보자. 각 프로세스들은 CPU 사용 시간을 할당받기 위해 <strong>timeslice</strong>를 가지고 있다. 이 때 할당받는 <strong>시간의 체크 단위를 HZ로</strong> 하게 되는 것이다.</li>
</ul>
</li>
</ul>
<p>→ 즉, <strong>HZ 단위로 계속해서 프로세스들이 돌아가며</strong>, 작업을 할 수 있게 하기 위해  <strong>특정 시간마다 인터럽트(interrupt)를 걸어주는 것</strong>이다.</p>
<h2 id="harware-clock-and-timers">Harware Clock and Timers</h2>
<p>시스템 시간은 크게 <strong>Timer</strong>와 <strong>Real-Time Clock</strong>(RTC)로 나눌 수 있다.</p>
<ul>
<li><strong>Timer는 주기적으로 CPU에게 인터럽트(interrupt)를 거는 역할</strong>을 함</li>
<li><strong>Real-Time Clock</strong>(RTC)은 <strong>현실 세계의 시간을 표현</strong>하며, ****PC의 전원을 꺼두어도 보조 배터리를 통해 계속해서 현재 시간을 측정한다.</li>
</ul>
<p>그렇다면, <strong>Timer</strong> 인터럽트는 어떻게 구동되고 있는걸까?</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/f4fadc60-1677-4bbd-b558-9955f195c79f/image.png" alt=""></p>
<ul>
<li>앞서 말했든 1번 째깍거릴 때마다 인터럽트가 걸리게 된다.<ul>
<li>(1초에 100번 째깍거린다면, 1초에 100번 인터럽트가 걸린다는 의미)</li>
</ul>
</li>
<li>이렇게 인터럽트가 걸리면 인터럽트 핸들러에 의해 <code>do_timer()</code>함수가 안에서 <strong>jiffies</strong>를 1만큼Tlr 증가시킨다.</li>
<li>이후 <code>update_process_times()</code>함수가 호출되는데, 현재 동작하고 있는 프로세스의 PCB 정보를 토대로 Kernel-mode인지, User-mode인지에 따라 각 모드의 count를 증가시킨다.<ul>
<li>이 두 모드의 count를 합치면, 해당 프로세스가 얼마만큼 CPU를 사용했는지를 알 수 있음 → 해당하는 정보는 PCB에 존재함</li>
</ul>
</li>
</ul>
<p>그렇다면, 지금까지 여러번 나오고 있는 Interrupt는 도대체 무엇일까?</p>
<h1 id="interrupt-구조">Interrupt 구조</h1>
<h2 id="interrupt란">interrupt란?</h2>
<p><strong>인터럽트(Interrupt)</strong>란 <strong>CPU가 프로그램을 실행하고 있을 떄, 입출력 하드웨어나 예외상황 등이 발생해 작업 처리가 필요할 경우에 CPU에게 알려서 이를 처리해달라고 요청하는 것</strong>임</p>
<h2 id="단일-디바이스의-interrupt">단일 디바이스의 Interrupt</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/dec7a503-eac3-4219-9751-7fe9f3b3a5aa/image.png" alt=""></p>
<ul>
<li>위 그림을 보면, CPU는 사이클을 돌면서 fetch, decode, execution 등의 루틴이 돌아가게 된다. 이떄, 하나의 사이클이 끝나면 다음 명령어를 수행하기 위해 <strong>PC</strong>가 증가한다.</li>
<li>이때 만약 위 그림처럼 <strong>disk</strong>가 중간에 인터럽트를 걸었다고 생각해보자.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/ade4ffd0-2f3d-4e4b-9548-f3fc296c32f2/image.png" alt=""></p>
<ul>
<li>이렇게 되면 CPU 내부에 Interrupt request bit가 설정됨.</li>
<li>Interrupt request bit가 설정되어 있으면, 작업을 계속 돌지 않고, PC에 인터럽트 핸들러(interrupt handler) 주소가 들어가게 됨.</li>
<li>그리고 다시 진행을 하면 해당 주소가 fetch되며, <strong>disk를 서비스해주는 인터럽드 핸들러 루틴이 실행됨.</strong></li>
</ul>
<blockquote>
<p><strong>참고:</strong> 만약 Interrupt request bit를 disable시킨다면, 인터럽트를 당하지 않게 만들수도 있음</p>
</blockquote>
<blockquote>
<p><strong>참고:</strong> preemption은 인터럽트 후에 발생하는 것임. <strong>그러나 인터럽트가 항상 preemption만을 유발하는 것은 아님</strong></p>
</blockquote>
<h2 id="멀티-디바이스의-interrupt">멀티 디바이스의 interrupt</h2>
<p>그렇다면, 하나의 디바이스가 아니라 한 번에 많은 디바이스들이 인터럽트를 요청하면 어떻게 될까?</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/8e3366ba-4eb3-4558-93bd-2c250cb2f4a1/image.png" alt=""></p>
<ul>
<li>이럴 경우 각 디바이스가 Interrupt request bit를 설정시키는 방식이 아니라 중간에 <strong>Interrupt Controller</strong>가 이들을 통제하게 됨.</li>
<li>이 <strong>Interrupt Controller</strong>를 <strong>PIC(Programmable Interrupt Controller)</strong>라고 부름</li>
</ul>
<p><strong>PIC</strong>에서 인터럽트를 처리하는 과정은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/ec315ac4-a959-4d41-ab18-00e867699f5a/image.png" alt=""></p>
<ul>
<li><p>요청들을 한 device들은 <strong>Interrupt Request Line</strong>(<strong>IRQ</strong> Line)에 연결되게 됨</p>
</li>
<li><p>이들은 <strong>Interrupt</strong> <strong>Mask Registe</strong>r를 통해 <code>0</code>일경우 <strong>차단</strong>이 되고, 아닐경우 통과가 되어 <strong>Iterrupt Request Register</strong>에 들어가게 됨</p>
<ul>
<li>소프트웨어적으로 차단(<code>0</code>)이 될 장치를 설정할 수 있음</li>
</ul>
</li>
<li><p>이후 <strong>Priority Register</strong>에서 전달된 요청들 중 우선순위를 체크하고, 우선순위가 높은 요청(진행되어야 할 요청)들은 <strong>In Service Register</strong>에 등록되게 됨</p>
</li>
<li><p>마지막으로 위 요청이 <strong>INTR</strong>에 전달되어 Interrupt request bit이 enable되게 됨</p>
<ul>
<li><p>INRT로 요청이 전달될 때 어떤 디바이스가 요청했는지에 대한 정보는 vector에 담아서 보내게 됨</p>
</li>
<li><p>여기서 INTR은 CPU그림에서 보았던 interrupt request를 의미</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/05909d35-e3d8-437a-b9a0-d0922670e622/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p>이렇게 요청이 처리되고 있을 땐, 다른 <strong>PIC와 장치들은 차단되어져</strong> 있음</p>
<ul>
<li>CPU는 인터럽트 요청을 마친 후 <strong>ACK</strong> 신호를 보내게 되고, 신호를 받으면 PIC는 다음 인터럽트 요청을 처리하게 됨</li>
</ul>
</li>
</ul>
<p>이 과정 전체를 도식화하면 다음과 같음.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/8207c3ef-0fe8-4f18-9b4a-186ae94cdfcc/image.png" alt=""></p>
<h2 id="멀티-프로세서-환경에서-멀티-디바이스의-interrupt">멀티 프로세서 환경에서 멀티 디바이스의 interrupt</h2>
<p>이번에는 여러 디바이스들의 요청들이 <strong>멀티 프로세서 환경</strong>에서는 어떻게 처리되는 지 알아보자.</p>
<h3 id="local-apic--multi-apic">Local-APIC &amp; multi-APIC</h3>
<p>멀티 프로세서 환경에서는 PIC가 다음과 같은 구조로 되어있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/c909a587-3237-43ec-a616-98c06349f56d/image.png" alt=""></p>
<ul>
<li>위 그림과 같이 CPU가 두 개가 있는 환경이라고 해보자.</li>
<li>두 CPU는 bus에 연결되어 있으며, bus는 <strong>(multi) APIC(Advanced PIC)</strong>에 연결되어 있다. 그리고 디바이스들은 이 (<strong>multi) APIC</strong>에 연결되어 있다.<ul>
<li>** APIC : 멀티 프로세스를 위한 Advanced PIC</li>
<li>CPU에는 <strong>local APIC</strong>도 존재하며, 이는 정기적으로 인터럽트를 걸어주는 Timer와 연결되어 있다.</li>
</ul>
</li>
</ul>
<h3 id="bus--io-interface">Bus &amp; I/O interface</h3>
<p>본격적으로 interrupt를 설명하기 이전에 잠시 컴퓨터 구조를 살펴보면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/9df9bfcb-09b6-4923-8bb7-6bb0fa0e7347/image.png" alt=""></p>
<ul>
<li><strong>CPU</strong>가 <code>0000000~7777XXX</code>번 메모리를 <strong>메모리 관리 유닛(MMU)</strong>에게 보내면 bus를 타고 좌측 Memory쪽으로 가게 되고,</li>
<li><code>7777XXX~7777777</code>번 메모리를 <strong>MMU</strong>에 보내게 되면 <strong>*<em>bus를 타고 *</em>I/O Interface</strong>쪽으로 가게 되어있다.</li>
<li><strong>I/O bus</strong>들에는 실제로 각 I/O 장치들이 연결되어 있는데, 이 장치들은 컴퓨터를 보면 있는 각종 연결할 수 있는 장치, 즉 <strong>I/O Interface card</strong>들로 연결이 되거나, 혹은 (그림 우측) PIC를 거쳐서 연결이 될 수도 있다.</li>
</ul>
<h3 id="smp와-amp">SMP와 AMP</h3>
<p>본격적으로 멀티 프로세싱에 대해 알아보자. 멀티 프로세싱은 크게 2가지 종류가 있다.</p>
<ol>
<li><p><strong>SMP(Symmetric Multiprocessing) : 대칭형 멀티 프로세싱</strong></p>
<p> <img src="https://velog.velcdn.com/images/whattsup_kim/post/4cf7bd1d-872a-40c9-8b1d-185ac05015f4/image.png" alt=""></p>
<ul>
<li><strong>SMP</strong>에서는 모든 프로세서가 하나의 메모리, I/O 디바이스, 인터럽트 등의 자원을 공유하며 하나의 운영체제가 모든 프로세스를 관리한다.</li>
<li>이러한 방식에서 디바이스가 I/O 인터페이스 카드에 연결되어 요청을 보내면, <strong>APIC</strong>가 받아서 처리를 하는데, <strong>CPU</strong> 간 차이가 없는 대칭형이기 때문에 어느 CPU에 요청을 전달 할 지는 다음과 같은 두 가지 방식을 사용한다.<ul>
<li><strong>Static Distribution</strong> 방식<ul>
<li>정적으로 정해진 곳에 보낸다.</li>
<li>이미 만들어진 <strong>Static Table</strong>을 통해 결정을 하게 된다</li>
</ul>
</li>
<li><strong>Dynamic Distribution</strong> 방식<ul>
<li><strong>동적 IRO 분배</strong> 알고리즘으로 보낸다.<ul>
<li><strong>동적 IRO 분배 알고리즘의 목표</strong>는 <strong>우선순위가 가장 낮은 프로세스를 돌리고 있는 CPU에게 IRQ를 주는 것</strong>이다.</li>
</ul>
</li>
<li>만약 running 중인 프로세스의 우선순위가 동일한 CPU가 존재하면, <strong>Arbitration</strong> 알고리즘을 적용한다.<ul>
<li>Arbitration : 모든 CPU가 카운터를 가진다. 현재 인터럽트 요청을 처리하는 CPU의 카운터는 0으로 만들고, 나머지는 카운터를 1씩 증가시킨다. 카운터가 높을수록 인터럽트를 제대로 수행하지 않은 놈이므로, 그에게 분배한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>SMP</strong>에서는 각 CPU들이 자원을 공유하기 때문에 <strong>상호배제의 원칙(Mutual Exlusion)</strong>이 철저하게 보장되어야 한다. 따라서 상대적으로 구현이 어렵다.</li>
</ul>
</li>
<li><p><strong>AMP(Asymmetric Multiprocessing) : 비 대칭형 멀티 프로세싱</strong></p>
<p> <img src="https://velog.velcdn.com/images/whattsup_kim/post/b46066b3-1a39-48dc-85ec-ffa83c4340fb/image.png" alt=""></p>
<ul>
<li><strong>AMP</strong>에서는 각 프로세스가 특정한 업무를 맡아서 한다. Master-Slave 형태로 되어 있으며, 주 프로세스가 전체 시스템을 통제하고, 다른 프로세스들은 주 프로세스의 통제 하에 동작된다.<ul>
<li>Master CPU는 본인만의 메모리를 가지고 있는데, 여기 OS 커널이 들어있음</li>
<li>즉, Master CPU만이 I/O를 처리할 수 있기 때문에 다른 CPU가 I/O를 처리하기 위해선 <strong>Master CPU</strong>에게 요청을 해야함.</li>
</ul>
</li>
<li><strong>AMP</strong>는 SMP 대비 아키텍처 디자인이 <strong>간단하다는 장점</strong>이 있다.</li>
</ul>
</li>
</ol>
<h2 id="data-structure-for-interrupt-handling">Data Structure for Interrupt Handling</h2>
<p>Interrupt의 구조를 다시 상기해보면, IRQ Lines에는 많은 디바이스들이 물려있고, 이들의 요청을 컨트롤하기 위해 PLC가 존재한다. </p>
<p>이때, 각각의 IRQ Line에는 다음과 같은 4개의 정보가 담겨져 있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/a4210291-0e49-4e9c-ab65-fee9bed0883e/image.png" alt=""></p>
<ul>
<li>Status<ol>
<li>IRQ_DISABLES : 인터럽트가 마스킹되어있는지(허용x)에 대한 상태</li>
<li>IRQ_WATING : 인터럽트가 마스킹되어있진 않지만 아직 interrupt 요청이 오지 않은 대기 상태</li>
<li>IRQ_PENDING : 인터럽트 요청이 왔지만, 아직 커널이 이를 서비스해주지 않은 상태</li>
<li>IRQ_INPROGRESS : 커널이 인터럽트 서비스 루틴을 수행하는 상태(ISR)</li>
</ol>
</li>
<li>Handler : 인터럽트 요청이 어떤 PIC(local PIC? Multi PIC?)로부터 왔는지 확인해줌</li>
<li>Lock : 공유 자원을 이용할 때(<strong>SMP</strong>) 상호배제를 위해 존재</li>
<li>Action : 요청이 어떤 IRQ Line에 있는 어떤 디바이스로부터 왔는가에 대한 정보가 담겨져 있음.<ul>
<li>따라서 Action 필드를 따라가다 보면 ISR이 리스트로 쭉 연결되어 있음</li>
</ul>
</li>
</ul>
<p>만약 IRQ Line이 여러 개라면, 다음과 같이 위 4개의 정보를 하나의 구조체로 하여 배열 형태로 IRQ Lines의 정보가 관리된다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/0be01fa4-54c8-4eb4-a832-bfd277d41d0b/image.png" alt=""></p>
<ul>
<li>만약 IRQ Line이 3개라면 위 그림처럼 IRQ Lines가 존재할 것이다.<ul>
<li>이 3개의 IRQ Line은 <code>irq_desc</code>라는 배열 형태로 관리된다.</li>
<li><code>irq_desc</code>배열은 Shared variable임</li>
</ul>
</li>
<li>이 중 action 필드는 ISR과 연결되어 있는 것을 확인할 수 있다.<ul>
<li>해당 포인터를 따라가서 요청이 어떤 디파이스로부터 왔는가를 체킹한다.</li>
</ul>
</li>
</ul>
<p>이 구조를 전체 구조와 연결지어보면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/010db224-4d99-4c47-b1fc-249489cb6caa/image.png" alt=""></p>
<h1 id="interrupt-진행">Interrupt 진행</h1>
<h2 id="function-for-interrupt">Function for interrupt</h2>
<p>실제로 인터럽트가 걸렸을 때 어떤 코드가 실행되는지를 알아보자.</p>
<ul>
<li><p>제일 먼저 <code>IRQn_interrupt()</code>가 호출된다.</p>
<ul>
<li>해당 함수는 어셈블러 함수임.<ul>
<li>** 어셈블러 : 기계어와 1:1대응이 되는 컴퓨터 프로그래밍 저급 언어</li>
</ul>
</li>
</ul>
</li>
<li><p><code>IRQn_interrupt()</code>함수가 호출되면, 간단한 동작을 한 후 바로 <code>do_IRQ(n)</code> 함수를 호출함.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/14835c6d-8ec2-48e5-ba58-72b6d1f6ebde/image.png" alt=""></p>
<ol>
<li><code>do_IRQ()</code>함수는 struct pt_regs라는 자료형으로, regs 변수를 하나 가지고 들어온다. 이후 reg.irg_eax &amp; 0xff 연산을 통해 <code>irq line number</code>를 뽑아낸다.<ul>
<li>해당 <code>irq line number</code>가 바로 <code>irq_desc[]</code>배열의 인덱스이다.</li>
</ul>
</li>
<li>이후 <code>irq_desc + irq;</code> 연산을 통해 irq_desc[]의 특정 인덱스에 있는 구조체 주소(<code>desc</code>)를 가져오게 된다.</li>
<li>이후 <code>spin_lock(desc→lock)</code>함수를 통해 상호배제를 확인<ul>
<li>자원이 사용중이지 않으면, lock을 건 후 자신이 요청을 함</li>
<li>자원이 사용중이면 계속 기다리게 됨</li>
</ul>
</li>
<li>요청이 된 후에는, <code>desc → handler</code>를 참조하여 어떤 PIC(local? multi?)가 요청하였는지 찾고, 해당 PIC에 ack 신호를 보냄(다른 인터럽트 처리를 위해)</li>
<li>이후 <code>desc → status</code>를 가져와서 irq_lines의 IRQ_WATING을 없애고, 처리를 기다린다는 상태인 IRQ_PENDING을 설정함.<ul>
<li>이 때 <code>action = NULL</code>로 설정하여 서로 다른 CPU들이 동일한 ISR(Interrupt Service Routine)을 하지 못하도록 방지함</li>
</ul>
</li>
<li>이후 (사진 우측) for문을 돌면서 <code>desk → lock</code>을 unlock 시킴<ul>
<li><code>irq_desc[]</code>에서 들어온 요청을 이미 찾았기 때문에 이제 critical_section에 접근할 필요가 없기 떄문</li>
</ul>
</li>
<li>이후 <code>handle_IRQ_event(...)</code>을 호출함<ul>
<li>이 함수 안에서 실제로 <code>action →</code>에 연결된 요청 작업을 수행함<ul>
<li>** Action : 요청이 어떤 IRQ Line에 있는 어떤 디바이스로부터 왔는가에 대한 정보가 담겨져 있음.</li>
</ul>
</li>
<li>이 함수는 do-while()로 되어 있기 때문에 action 필드가 NULL일 때까지 모든 (해당)IRQ Lines에 연결되어 있는 디바이스의 인터럽트 요청을 처리함</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="interrupt-routine이-진행중인-irq-라인에서-또-다른-요청이-생기는-경우">Interrupt Routine이 진행중인 IRQ 라인에서 또 다른 요청이 생기는 경우</h2>
<p>Interrupt Routine이 진행중인 IRQ 라인에서 또 다른 요청이 생기는 경우 다음과 같이 실행된다.</p>
<p>(현재 <strong>APIC</strong>가 <strong>CPU0</strong>에게 <strong>IRQ$_m$라인에 대한</strong> 처리를 요청한 상태라고 가정한다)</p>
<ol>
<li>만약 현재 요청 들어온 <strong>IRQ$_m$</strong>라인에 일을 해주고 있는 CPU가 없는 상태이다. 이제 <strong>CPU0</strong>이 이를 해주기 시작한다.</li>
<li><strong>CPU0</strong>이 <strong>IRQ$_m$</strong> 라인을 처리하고 있는 도중 동일한 <strong>IRQ$_m$</strong> 라인에서 요청이 와서 <strong>CPU1</strong>이 처리해주려고 한다. <strong>IPQ$_m$</strong> 라인의 status는 현재<strong>IRQ_INPROGRESS</strong>가 설정되어 있기 때문에 <strong>CPU1</strong>은 이를 확인하고 <strong>IRQ_PENDING</strong> 값을 <strong>추가로 세팅</strong>해준 다음 종료한다.</li>
<li><strong>CPU0</strong>이 수행하고 있는 <code>handle_IRQ_event(...)</code>함수가 끝나면 for문에서 <strong>다시 한 번</strong> <strong>IRQ_PENDING을 체크</strong>함. 로직에서 분명 IRQ_PENDING을 껐었지만, 이게 다시 세팅되어있다면, 동일한 라인에서 또 요청이 들어왔다는 것이고, 이는 <strong>다른 CPU가 나에게 처리부탁을 요청했다는 의미</strong>이다. 따라서 다시 <code>handle_IRQ_event(...)</code>함수를 실행한다.</li>
</ol>
<h2 id="interrupt-흐름-정리">interrupt 흐름 정리</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/42b5bf07-3482-4ede-bd32-310ea66ea7ed/image.png" alt=""></p>
<ol>
<li>(multi)APIC의 IRQ Lines에는 여러 디바이스가 물려있음. 따라서 IRQ$_m$ 라인을 통해 특정 디바이스가 인터럽트 요청을 보내게 됨.</li>
<li>APIC에서는 이 요청을 여러 CPU 중 (위 그림에서는) CPU$_i$을 선택하였음.<ul>
<li>따라서 CPU$_i$의 counter는 0이 됨. (Arbitration)</li>
</ul>
</li>
<li>CPU$_i$는 irq_desc[m]의 status를 IRQ_PENDING으로 업데이트함.</li>
<li>이제 어떤 CPU가 해당 인터럽트를 처리하게 될 지 선택함<ul>
<li>첫 번째 케이스 : 어떠한 CPU도 IRQ$_m$을 처리하고 있지 않다면, CPU$_i$가 바로 처리함</li>
<li>두 번째 케이스 : 만약 다른 CPU(예를 들어. CPU$_k$)가 이미 IRQ$_m$을 처리하고 있으면, Status에 IRQ_PENDING 값을 추가로 세팅해줌으로써 요청을 CPU$_k$에게 넘김</li>
</ul>
</li>
</ol>
<h2 id="전반부-처리top-half와-후반부-처리bottom-half">전반부 처리(Top Half)와 후반부 처리(Bottom Half)</h2>
<p>인터럽트를 위한 <code>do_IRQ()</code>함수의 로직은 많은 민폐를 끼침</p>
<ul>
<li>요청에 대한 ACK가 오기 전까지 PLC는 block됨</li>
<li>공유 메모리를 사용하기 때문에 lock이 걸리면 다른 CPU는 사용하지 못함</li>
</ul>
<p><code>do_IRQ()</code>함수의 로직을 들여다보면, 다음과 같은 특징이 있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/93abb854-af3d-483e-be8f-4b476da1597a/image.png" alt=""></p>
<ul>
<li>민폐를 끼치는 영역은 위와 같이 <strong>Critical Top-Half</strong> 영역에서 주로 발생한다.<ul>
<li>따라서 해당 영역은 매우 신속한 처리를 필요로 한다.</li>
</ul>
</li>
<li>Critical Top-Half 영역이 아닌 <strong>Non-Critical Top-Half</strong> 영역은 보다 민폐를 끼지는 상황이 덜하지만, 존재하긴 함</li>
</ul>
<p>그렇다면, 만약 인터럽트의 작업이 굉장히 무거워서, 신속한 처리를 하지 못한다면 어떻게 될까? </p>
<ul>
<li>리눅스에서는 이와 같은 문제를 해결하기 위해 작업을 나눠서 처리한다.</li>
<li>또한 한 번에 처리하지 못하는 작업을 이후에 다시 처리할 수 있도록 <strong>soft-irq이라는 bit</strong>를 설정한다.<ul>
<li>해당 bit가 설정되어 있다면, <code>do_softirq()</code>함수를 통해 남은 작업을 다시 수행하도록 소프트웨어적으로 구현되어 있다.</li>
<li>이러한 매커니즘을 <strong>Bottom Half</strong> 라고 부른다.</li>
<li>즉, <strong>Top Half(전반부 처리)</strong>는 하드웨어에 의해, <strong>Bottom Half(후반부 처리)</strong>는 소프트웨어에 의해 실행되는 로직을 갖는다.</li>
</ul>
</li>
</ul>
<p>네트워크 장치의 경우에서 Top Half와 Bottom Half이 동작되는 예시를 생각해보자.</p>
<ul>
<li><p>현재 NLC(네트워크 인터페이스 카드)에서 인터럽트 요청이 들어왔다.</p>
<ul>
<li>네트워크 전송량과 지연시간을 최적화하고, 타임아웃을 막으려면 즉시 이 작업을 처리해야 함.</li>
<li>그러므로 즉시 인터럽트를 발생시켜서 커널에게 새로운 패킷이 왔다는 것을 알린다.</li>
<li>커널은 이에 반응해 네트워크 장치에 등록된 인터럽트를 실행시킴.</li>
</ul>
</li>
<li><p>PIC는 특정 CPU를 선택하고, CPU에서는 <code>do_IRQ()</code>를 호출하여 Top Half를 수행함.</p>
<ul>
<li><p>Top Half를 수행하면서 ACK를 보내고, 새로 수신한 네트워크 패킷을 주 메모리에 복사한 다음, 네트워크 카드를 다시 패킷을 수신할 수 있는 상태로 조정함</p>
</li>
<li><p>이후 <strong>실제 패킷의 동작 처리</strong>는 <strong>Bottom Half</strong>에서 처리될 수 있도록 <strong>softiqr_pending[] 을 세팅</strong>함</p>
<p>  (Top Half가 수행할 일은 여기서 끝나게 됨)</p>
</li>
</ul>
</li>
<li><p>이후 시스템 제어권을 인터럽트 발생으로 실행이 중단된 코드로 다시 돌려주며, 나머지 패킷 처리는 나중에 후반부 처리에서 진행함.</p>
<ul>
<li>CPU가 처리할 작업의 우선순위를 보고 처리하는데, <strong>softiqr_pending[]</strong> 이 설정된 애들은 <code>do_softiqr()</code>을 실행시켜 나머지 Bottom Half가 처리될 수 있도록 함</li>
</ul>
</li>
</ul>
<p>위 예시를 도식화하면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/5c89c131-b42e-47ae-9b5d-fb64a042e0b6/image.png" alt=""></p>
<h2 id="후반부-처리bottom-half-살펴보기">후반부 처리(Bottom half) 살펴보기</h2>
<p>전반부 처리와 후반부 처리를 다시 상기해보자. 인터럽트 요청이 오면 Top Half 루틴이 수행되고, 큰 작업이 필요한 경우에는 <strong>softiqr_pending[]</strong> 을 설정한다.</p>
<h3 id="softiqr_pending-bit">softiqr_pending: bit</h3>
<p><strong>softiqr_pending: bit</strong>는 다음과 같은 구조로 되어있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/3e0f9acf-e7ce-4881-84a3-116a2446c43c/image.png" alt=""></p>
<ul>
<li>왼족 아래 <strong>softiqr_pending[]</strong> 을 보면, 1로 세팅되어 있는 부분이 현재 Bottom Half 으로 처리해야 할 부분을 의미한다.<ul>
<li>이는 인덱스마다 정해져 있음</li>
<li>ex) 0번 인덱스는 디스크 관련, 1번 인덱스는 TCP/IP 관련</li>
</ul>
</li>
<li>만약 인덱스 1의 bit가 설정되어 있다면, 같은 인덱스의 <strong>soft_vec[]</strong>을 참조한다.<ul>
<li>위 그림에서 <strong>action data</strong> 부분은 기능을 수행하는 <strong>action과 실제 데이터를 가르키는 필드</strong>로 이루어진 구조체임.</li>
</ul>
</li>
</ul>
<h3 id="do_softiqr"><code>do_softiqr()</code></h3>
<p>이제<code>do_softiqr()</code>함수를 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/4b4a0c12-2d03-42cb-8ba6-5b5d428a5481/image.png" alt=""></p>
<ul>
<li>Top Half는 <code>do_irq()</code> 함수를 호출하고, Bottom Half는 <code>do_softirq()</code> 함수를 호출한다.</li>
<li>위 그림과 같이 <code>do_softirq()</code> 함수의 로직은 <code>do_irq()</code>와 비슷하다.</li>
<li>로직<ul>
<li>softiqr_pending[]을 확인하여 1(→ softirq_vec)이면, 이에 해당하는 <code>h → action</code>을 호출한다.</li>
<li>이후 pending을 쉬프트 연산을 통해 하나씩 반복해서 확인한다.</li>
<li>이떄, <code>h → action</code>이 Bottom Half의 softirq handler역할을 수행한다.</li>
</ul>
</li>
</ul>
<h3 id="softirq-tasklet-work-queue">Softirq, Tasklet, Work Queue</h3>
<p><strong>Softirq</strong></p>
<p>앞서 배웠던 <strong>Softiqr</strong>은 여러 개의 CPU가 동시에 ISR 핸들러를 실행시킬 수 있다. <strong>동시성이 높아지기 때문에 처리량이 많아진다</strong>면 이러한 장점은 네트워크 패킷 핸들링과 같은 곳에 매우 유용하다.</p>
<ul>
<li>하지만 코딩하기에는 많은 <strong>복잡성이</strong> 따른다.</li>
</ul>
<p><strong>Tasklet</strong></p>
<p><strong>Tasklet</strong>은 굳이 동시성이라는 것이 필요없는 함수를 수행하기 위해 등장하였다. </p>
<ul>
<li><p>Tasklet은 softirq이긴 하지만, CPU가 한 번에 하나의 ISR 핸들러만 수행시킬 수 있다.</p>
</li>
<li><p>따라서 <strong>구현은 간단하지만, softirq보다는 처리량이 떨어진다</strong></p>
<ul>
<li>따라서 간단한 일을 처리할 때 사용될 수 있다.</li>
</ul>
</li>
<li><p>Tasklet의 구조체를 보면, state라는 필드가 있다.</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/3ffae9b2-3bbd-4160-879e-b2ceb8cd1a09/image.png" alt=""></p>
<ul>
<li>이 state 필드가 1이면 접근을 못하고, 0이면 접근하여 사용이 가능하다.</li>
</ul>
</li>
</ul>
<p><strong>workqueue</strong></p>
<p>Softirq와 Tasklet은 interrupt를 받은 상태로 동작하기 때문에 더 우선순위가 높은 interrupt가(ex. HW interrupt) 오지 않는 이상 작업을 모두 수행하기 전까지 다른 프로세스가 끼어들 수 없다. 만약 Softirq와 tasklet에서 처리하는 작업이 길고 남발하면 다른 작업들은 그만큼 뒤로 밀리게 돼 시스템 전반의 성능 저하가 올 수도 있다.</p>
<p>이러한 경우 <strong>workqueue</strong>를 사용할 수 있다.</p>
<ul>
<li>workqueue는 일반 프로세스가 동작하는 것처럼 동작한다.</li>
<li>workqueue를 관리하는 handler는 일반 프로세스처럼 CPU의 스케줄링을 받기 때문에 Softirq와 Tasklet과는 달리 작업이 끝나지 않았더라도 sleep에 들 수 있다.</li>
<li>즉, 시스템에 무리를 줄 수 있는 요소가 없기 때문에 시간이 충분한 경우 유용하게 사용할 수 있다.</li>
</ul>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0">https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0</a></li>
<li><a href="https://jeongzero.oopy.io/category/kernel">https://jeongzero.oopy.io/category/kernel</a></li>
<li><a href="https://mns010.tistory.com/19">https://mns010.tistory.com/19</a></li>
<li><a href="https://selfish-developer.com/entry/tasklet%EA%B3%BC-workqueue-12-%EC%B0%A8%EC%9D%B4%EC%A0%90">https://selfish-developer.com/entry/tasklet과-workqueue-12-차이점</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux Kernel] Process Management]]></title>
            <link>https://velog.io/@whattsup_kim/Linux-Kernel-Process-Management</link>
            <guid>https://velog.io/@whattsup_kim/Linux-Kernel-Process-Management</guid>
            <pubDate>Sun, 16 Oct 2022 09:31:06 GMT</pubDate>
            <description><![CDATA[<h1 id="fork의-오버헤드-문제">fork()의 오버헤드 문제</h1>
<p>이전 글에서 자식 프로세스를 Create하는 것에 대한 과정을 살펴봤음. 여기서 중요한 것은 프로세스를 생성할 때(fork 할 때) 2가지의 오버헤드가 생길 수 있다는 것임.</p>
<ol>
<li>부모 프로세스의 이미지를 복사하는 데 드는 오버헤드</li>
<li>부모 프로세스의 PCB를 복사하는 데 드는 오버헤드</li>
</ol>
<p>이러한 오버헤드를 줄이기 위해 다음과 같은 방법을 이용할 수 있다.</p>
<h2 id="pcb-복사-시-오버헤드-줄이기---thread와-clone">PCB 복사 시 오버헤드 줄이기 - Thread와 <code>clone()</code></h2>
<p>PCB에는 프로세스 관리를 위한 다양한 리소스가 들어있다. 이를 크게 6가지 구조로 구분지으면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/9d5b6cf8-0a9c-47d1-be5d-9cc83e4ba5be/image.png" alt=""></p>
<ul>
<li>task basic info</li>
<li>files : 프로세스가 오픈한 파일에 대한 정보</li>
<li>fs : 프로세스가 접근 중인 file system에 대한 정보</li>
<li>tty : 프로세스가 사용 중인 터미널 정보</li>
<li>mm : 사용 중인 메인 메모리에 대한 정보</li>
<li>signals : 여러 신호 정보</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/cabe3834-bad7-413c-bf5c-8aa7bb410c20/image.png" alt=""></p>
<ul>
<li>위와 같이 task_struct라는 구조(리눅스가 가지고 있는 PCB) 안에 이러한 PCB 데이터를 포함한 여러 데이터들이 들어있다.</li>
</ul>
<p>그렇다면, 왜 리눅스는 PCB 정보를 왜 하나가 아닌 6개의 구조로 관리하는 것일까?</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/c912a60f-fe81-4da4-b20f-c5bcd513f9ab/image.png" alt=""></p>
<ul>
<li>그림의 위쪽 <strong>heavy-weight creation</strong>과 같이 모든 구조들을 복사한다면, read &amp; write가 되어 상당한 부하가 걸리게 된다.<ul>
<li>초기 리눅스는 이런 방식있다고 함</li>
</ul>
</li>
<li>막상 시스템을 만들다 보니 부모 프로세스가 가지고 있는 tty(터미널)이나 fs(파일 시스템)는 자식이 가지고 있는 것과 동일한 경우가 많다는 것을 알게 되었고,</li>
<li>따라서 아래쪽 <strong>light-weight creation</strong> 방식이 제안됨<ul>
<li>부모 프로세스가 가지고 있는 tty(터미널)이나 fs(파일 시스템)등의 주소를 자식과 공유하는 방식으로 생성하는 것</li>
<li>즉, 부모와 자식 프로세스가 다르게 사용할 것들만 <strong>선택적으로 copy</strong>하자는 것</li>
</ul>
</li>
</ul>
<p>→ 리눅스는 PCB를 6개의 구조로 나눔으로써 부모 PCB 중 일부를 공유할 수 있게 하였고, 이를 통해 오베헤드를 줄이게 됨</p>
<p>예를 들면 다음과 같이 현재 메인 메모리, 4개의 CPU, 그리고 이들의 통로인 버스가 있다고 가정하자.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/e0b46dfb-5fae-4c1e-9997-08bc693512c6/image.png" alt=""></p>
<ul>
<li>이 상황에서 Game XYZ라는 프로그램이 하나 돌아가고 있는 상태이다. 이 게임은 현재 <code>CPU #0</code>에서 실행되고 있으며, Game XYZ 프로세스를 <code>CPU #0</code>의 PC가 가르키고 있다.</li>
<li>이러한 상황에서 전통적인 방법으로 Game XYZ의 자식 프로세스를 생성하려면, 위 그림과 같이 PCB를 통쨰로 복사해야 했고, 이는 오버헤드로 직결된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/fba4a741-b175-4972-9d35-454188fd8457/image.png" alt=""></p>
<ul>
<li>따라서 위 그림과 같이 PCB의 모든 정보가 아닌 <code>Task basic info</code>만 복사하고, 나머지 구조체(files, fs, tty, mm, signals)들은 부모의 것과 공유하게 된다.</li>
<li>즉, <strong>새로운 프로세스로 생성하는 것이 아니라 Thread를 만드는 것이다.</strong><ul>
<li>** 프로세스는 운영체제로부터 <strong>자원을 할당</strong>받는 작업의 단위이며, 스레드는 <strong>프로세스가 할당받은 자원을 이용하여 실행하는 단위</strong>이다.</li>
<li>스레드는 Task bask info 파트만 복사를 하고, 나머지는 부모 프로세스와 공유한다. 이떄, Task bask info 안에는 <strong>state vector save area</strong>가 있기 때문에 각 스레드마다 별도의 PC와 Stack Pointer를 갖고 있을 수 있는 것이다. 또한 각 스레드가 각자의 Stack을 갖고 있기 떄문에 개별적으로 다른 함수들을 호출하면서 실행될 수 있다.</li>
</ul>
</li>
</ul>
<p>이러한 방식은 단순히 복사를 하는 <code>fork()</code>가 아니라 <code>clone()</code>이라는 시스템 콜을 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/ee36de52-5941-4889-acf4-89012df95e00/image.png" alt=""></p>
<ul>
<li><code>sys_clone()</code>이 호출되면, 내부적으로 binary bit 5개를 함께 보내게 된다.<ul>
<li>binary bit 5개는 <code>ask basic info</code>외 나머지 구조체(files, fs, tty, mm, signals)를 의미함</li>
<li>binary bit은 1이면 복사, 0이면 공유한다는 의미를 내포하고 있음</li>
</ul>
</li>
</ul>
<h2 id="이미지를-복사-시-오버헤드-줄이기---cow">이미지를 복사 시 오버헤드 줄이기 - CoW</h2>
<ul>
<li>이때, 이미지는 PCB를 실행하기 위해 필요한 Code, Stack, Data 등을 포함하는 것이다.</li>
</ul>
<p>리눅스 엔지니어들은 child 생성 시 부모의 이미지를 그대로 복사해오는 것이 아니라 <strong>page mapping table</strong>만 복사해오도록 구현하여 오버헤드를 줄였다.</p>
<ul>
<li>자식 프로세스는 이미지를 부모 프로세스로부터 복사하여 가져오는 것이 아니라 부모 프로세스의 이미지를 가르키는 <strong>page mapping table</strong>만 복사해서 가져오게 된다.</li>
<li>자식 프로세스는 이러한 <strong>page mapping table</strong>을 가지고 실행을 하며, 명령어들을 수행시킬 동안 <strong>page mapping table</strong>을 부모와 같이 사용할 수 있게 된다.</li>
</ul>
<blockquote>
<p><strong>페이지 테이블</strong> : 페이징 기법에 사용되는 자료도구로, 프로세스의 페이지 정보를 저장하고 있는 테이블임.</p>
<ul>
<li>테이블의 내용은 해당 페이지에 할당된 물리 메모리의 시작 주소를 담고있음</li>
</ul>
</blockquote>
<p>이러한 방식은 <code>read()</code>만 사용할 떈 문제가 없지만, <code>write()</code>를 해서 무언가를 쓰게 된다면 문제가 발생할 수 있다.</p>
<ul>
<li>이를 해결하기 위해 만약 <code>write()</code>를 하게 되는 경우, 해당하는 페이지만 부모와 자식에게 하나씩 복사본을 따로 만들어주는 방식으로 해결하였다.</li>
<li>이러한 방식을 <strong>Copy on Write</strong>라고 부른다</li>
</ul>
<p>CoW를 fork() 과정에서 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/15ce4153-5dcb-4c2b-8a98-629ea9b66ab5/image.png" alt=""></p>
<ul>
<li>먼저 부모 프로세스에서 <strong>fork()</strong>를 하면 정보를 전부 복사하는 것이 아니라 CoW 방식으로 <strong>page mapping table</strong>만 가져오게 된다.</li>
<li>이후 <code>wait()</code>과정이 일어나며, CPU는 자식 프로세스가 넘겨받게 된다.</li>
<li>이후 자식 프로세스는 <code>fork()</code>가 일어난 곳으로 가서 자신의 일을 하게 된다.</li>
</ul>
<p>그러나 만약, 부모가 <code>fork()</code>를 하고 돌아와서 바로 <code>wait()</code>를 하지 않고, <code>write()</code>를 하게 된다면 <strong>문제가 발생</strong>한다.</p>
<ul>
<li>이렇게 된다면 자식 프로세스가 CPU를 점유하기 전에 페이지 테이블에 변화가 생기는 것이다.</li>
<li>이는 계속해서 CoW를 만들어 낼 것이지만, 어차피 자식 프로세스가 exec()를 하게 되면 덮어씌어지기 때문에 <strong>결국 의미 없는 복사를 한 것이 된다.</strong></li>
<li>즉, 비효율 문제(오버헤드)가 또 발생한다는 것이다.</li>
</ul>
<p>이를 해결하기 위해 다음과 같은 새로운 방법을 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/d34f83cb-baca-491a-95e7-1802beb9c71c/image.png" alt=""></p>
<ol>
<li>부모 프로세스가 <code>fork()</code>를 호출해서 자식 생성을 끝내고, <code>fork()</code>를 했던 곳으로 돌아가려하고 있다.</li>
<li>이때 <code>fork()</code>안에서 <strong>자식 프로세스의 CPU 우선순위를 확 높여버린다.</strong><ul>
<li>이렇게 우선순위를 높이는 이유는, Kernel-mode에서 User-mode로 돌아갈 때에는 우선순위가 제일 높은 프로세스에게 CPU를 넘겨주기 떄문임</li>
</ul>
</li>
<li>이렇게 되면 <strong>CPU가 부모한테 돌아가는 것이 아니라 바로 자식 프로세스에게 가게 된다.</strong><ul>
<li>자식 프로세스는 CPU를 받아 바로 <code>exec()</code>를 하게 된다.</li>
</ul>
</li>
<li>이후에 부모 프로세스가 CPU를 받게 되며, <code>fork()</code>에서 돌아오고 본인이 할 일을 하게 된다.</li>
</ol>
<h1 id="kernel-thread">Kernel Thread</h1>
<p>위에서 말했던 <strong>light-weight creation</strong> 내용을 복기하면 다음과 같다.</p>
<ul>
<li><p>부모 프로세스가 가지고 있는 tty(터미널)이나 fs(파일 시스템)등의 주소를 자식과 공유하는 방식으로 생성하는 것</p>
</li>
<li><p>즉, 부모와 자식 프로세스가 다르게 사용할 것들만 <strong>선택적으로 copy</strong>하자는 것</p>
<p>  → 즉, 자식 프로세스를 생성하는 것이 아니라 자식 스레드를 생성하는 것</p>
</li>
</ul>
<p>그렇다면 커널은 무엇인가?</p>
<ul>
<li><p>커널은 <strong>Memory residen</strong>t한 독립된 C 프로그램</p>
<p>  ** Memory resident: 부팅되고나서부터 꺼질 때까지 항상 메모리에 상주하고 있는 것</p>
</li>
</ul>
<p>스레드와 커널에 대해 알아보았으니 이제 <strong>Kernel Thread</strong>에 대해 알아보자.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/56b4cfc1-63a9-4aad-a1d2-d995cfe71c6d/image.png" alt=""></p>
<ul>
<li><p>컴퓨터가 부팅되고, 커널이 <code>main()</code>이 실행 된 이후, 커널 프로세스가 동작하다가 시스템 콜 <code>clone()</code>을 호출하게 되면 자식 프로세스가 생기는데, 이렇게 만들어진 자식 프로세스를 <strong>커널 스레드라고 한다.</strong></p>
<ul>
<li>커널 프로세스는 커널 공간에서만 실행되는 프로세스이다. 대부분 커널 스레드 형태로 동작한다.</li>
<li>커널 스레드는 리눅스 시스템 프로그래밍에서 데몬과 거의 비슷한 일을 하는데, 데몬과 커널 스레드는 백그라운드 작업으로 실행되면서 시스템 메모리나 전원을 제어하는 동작을 수행한다.</li>
<li>커널 스레드는 유저 영역과 시스템 콜을 받지 않고 동작한다. 이 점이 데몬과 커널 스레드의 차이점이다.</li>
</ul>
</li>
</ul>
<h1 id="process-state">Process State</h1>
<p>프로세스 상태는 기본적으로 running, waiting, ready로 이루어져 있다. </p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/13e2eeee-c223-413e-811f-28f72f12e2cb/image.png" alt=""></p>
<ul>
<li><strong>running</strong> : CPU에 의해 <strong>프로세스가 실행되고 있는 상태</strong><ul>
<li>running 상태에서 <strong>나가지는 경우</strong>는 다음과 같다.<ul>
<li><strong>Preemption</strong> : Ready State로 돌아가는 경우임. 프로세서 스케줄링 등으로, 프로세서를 잃고 <strong>다시 프로세서 할당을 대기</strong>하는 상태.</li>
<li><strong>wait</strong> / sleep : 입/출력 등의 특정한 Event 를 기다리게 된다. 할당받았던 프로세서를 반납하고, <strong>asleep(blocked, waiting) 상태가 되어I/O 자원 등 필요한 자원을 할당 받기를 기다린다.</strong></li>
</ul>
</li>
<li>running 상태가 되었다고 해도 무한정으로 CPU를 점유하는 것은 아님. 프로세스는 특정 시간동안만 CPU를 할당받게 되고, 시간이 끝나면 <strong>ready queue</strong>의 맨 뒤로 돌아가게 됨.</li>
<li>running 중 <code>exit()</code>시스템 콜이 호출되면 좀비 상태가 됨<ul>
<li>좀비 상태가 되면 PCB를 제외한 모든게 없어지게 됨</li>
<li>child로 생성된 프로세스가 <code>exit()</code>되면, 해당 프로세스의 부모 프로세스가 자식의 PCB 정보를 보고, 자기 자식 말소를 시작함. 말소 작업이 다 끝내기 전까지는 좀비 상태를 유지함</li>
</ul>
</li>
</ul>
</li>
<li><strong>waiting</strong> : <strong>프로세서 외 다른 자원을 기다리는 상태</strong> (e.g. I/O 자원).<ul>
<li>다른 자원을 받는 것이 <strong>끝나면 ready 상태로 들어간다</strong>.</li>
</ul>
</li>
<li><strong>ready</strong> : (현재는 CPU가 할당되어 있지 않지만) <strong>CPU 할당을 받으면 바로 Running State로 들어가 실행을 할 수 있는 상태</strong></li>
</ul>
<h1 id="kernel-scheduling">Kernel Scheduling</h1>
<p>리눅스에서는 CPU가 어떤 방식으로 할당되는지(Scheduling)를 알아보자.</p>
<h2 id="scheduling-algorithm">Scheduling Algorithm</h2>
<p>먼저 스케줄링 알고리즘들을 살펴보자</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/78dd8a99-1a23-4a29-b39f-8c19ec1eeaf5/image.png" alt=""></p>
<ul>
<li>위와 같은 형태의 Ready Queue가 있다고 하자. Ready Queue에는 실행할 수 있는 작업들이 위 그림처럼 순차적으로 연결되어 있게 된다.</li>
<li>하지만, 이런 형태는 멀티 프로세서 환경에서 CPU의 개수가 증가하게 되면 레디큐에 연결되어 있는 PCB도 엄청나게 많아 질 것이고, <code>context_switch()</code>를 실행할 때마다 모든 내용을 뒤져서 우선순위가 높은 프로세스를 골라내야하는 작업은 굉장히 비효율성을 야기할 것이다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/4a0a6da8-720a-483f-8d54-6695bee25bc3/image.png" alt=""></p>
<ul>
<li>위와 같은 비효율성을 없애기 위해 단순한 레디큐가 아니라 높은-우선순위 큐 / 낮은-우선순위 큐로 나누게 되었다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/580467f8-2e9f-4fcf-9812-95fd260aef69/image.png" alt=""></p>
<ul>
<li>더 나아가 여러 개의 우선순위 큐를 만들어 더욱 더 효율적이게 동작하게도 만들게 되었다.</li>
<li>하지만 위 주황색 큐를 들여다보면 중간에 비어있는 큐가 보이게 된다. 이런 큐까지 탐색한다면 이 역시도 비효율성을 야기할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/ecad6c03-741c-499d-aca3-09270ac657f4/image.png" alt=""></p>
<ul>
<li>따라서 위와 같이 <strong>바이너리 배열</strong>을 하나 만들게 된다.<ul>
<li>바이너리 배열에서 0은 해당 인덱스의 큐가 비어있다는 뜻이고, 1은 해당 인덱스에 내용물이 있다는 뜻이다.</li>
<li>유닉스에서는 이 바이너리 배열을 <strong>비트맵</strong>이라고 부르며, 이를 통해 탐색 속도를 향상시킨다.</li>
</ul>
</li>
<li>ex)<ul>
<li>예를 들어 위 그림과 같이 binary[2]가 1이라면,</li>
<li><strong>2의 우선순위를 갖는 task가 존재</strong>한다는 것을 바로 알 수 있게 됨</li>
</ul>
</li>
<li>이렇게 비트맵과 큐 배열을 동시에 포함하고 있는 구조체가 바로 <strong>priority array(우선순위 배열)</strong>이다.<ul>
<li>이러한 우선순위 배열은 각 cpu마다 두 개씩 존재한다.</li>
</ul>
</li>
</ul>
<h2 id="우선순위-배열과-스케줄링"><strong>우선순위 배열과</strong> 스케줄링</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/da552899-3b7b-4bd4-87c1-db40d3950fb4/image.png" alt=""></p>
<ol>
<li>bitmap을 <strong>차례로 스캔</strong>한다. </li>
<li>만약, bitmap이 1로 세팅(내용물이 있음)된 <strong>인덱스가 있다면</strong>, 해당 인덱스에 해당하는 <strong>queue의 task list를 순회하며 cpu를 할당</strong>해준다. </li>
</ol>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/20a62e94-12cc-4b72-82ab-0200db1648e2/image.png" alt=""></p>
<ol start="3">
<li><p>빨간색 task는 cpu가 할당되어서 실행중이다. 이는 현재 빨간색 task가 <strong>Active array</strong>에 있으며 해당 task는 <strong>timeslice</strong>가 남아있는 상태임을 의미한다.</p>
<p> a. 만약, timeslice가 0이 된다면, Expired array로 빠지게 되고
 b. Expired array에서 자기 우선순위에 맞는 리스트에 다시 삽입되며
 c. 다시 자신의 차례를 기다리게 된다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/998101b3-3a5e-4eec-99f4-360ca273b447/image.png" alt=""></p>
<ol start="4">
<li>모든 task에 (3)을 반복하다보면, 수행을 끝내지 못한 task들이 전부 Expired array로 모이게 된다. 이때 Expired array에 모인 모든 프로세스들은 한 번에 time slice를 재배정 받게 되며, Expired array영역은 Active 영역이 되고, Active 영역은 Expired 영역으로 바뀌게 된다.</li>
</ol>
<p>→ 각 CPU는 두 개의 우선순위 배열을 가지고 있으며, 해당 스케줄링은 $O(1)$의 시간복잡도를 가진다.</p>
<h1 id="kernel-preemption">Kernel Preemption</h1>
<p>컴퓨터의 일반적인 구조를 살펴보자.</p>
<ul>
<li>CPU에는 저장 공간이 많이 없고, 비싸기 때문에 CPU에 많음 데이터를 저장할 수는 없음</li>
<li>이 떄문에 일반적인 컴퓨터 시스템에서 연산은 CPU에서 이루어지지만, 데이터는 저장소에 저장되게 됨</li>
</ul>
<p>이러한 컴퓨터의 구조는 다음 예시와 같은 문제를 야기할 수 있다.</p>
<ul>
<li>두 개의 프로세스(A, B)가 존재하고, 이 두 프로세스는 한 개의 변수(x)를 공유하고 있다.<ul>
<li>A프로세스는 변수에 1을 더하는 작업을 한다.</li>
<li>B프로세스는 변수에 1을 더하는 작업을 한다.</li>
<li>변수 x의 초기 값은 1이다.</li>
</ul>
</li>
<li>이때, A프로세스와 B프로세스는 동시에 CPU를 할당받아, 공유되고 있는 변수 x에 각자의 작업을 하고 둘 다 저장한다고 해보자</li>
<li>A프로세스에서 +1을 하였고, B프로세스에서 +1을 하였지만, 결국 저장되는 값은 2가 될 것이다.<ul>
<li>A, B 프로세스 모두 1이라는 x값을 불러와서, 1을 더한 후, 이를 기록했기 때문</li>
</ul>
</li>
<li>즉, 두 번의 덧셈연산이 <strong>제대로 동작하지 않은 것</strong>이다.</li>
</ul>
<p>이처럼 프로세스 간 공유되는 메모리에 접근하는 영역을 <strong>critical section</strong>이라고 부르며, <strong>critical section</strong>을 동시에 접근하는 일은 수행되어선 안 된다.</p>
<ul>
<li>즉 한 순간에, 하나의 프로세스만이 <strong>critical section</strong>에 접근해야 한다는 의미이다.</li>
<li>이러한 것을 Mutual Exlusiom(상호 배제) 원칙이라고 부른다.</li>
</ul>
<h2 id="mutual-exlusion상호-배제">Mutual Exlusion(상호 배제)</h2>
<p>유닉스는 이러한 상호 배제의 원칙을 위해 지난 40여년 간 아주 단순하게 다음과 같은 방법을 사용해왔다.</p>
<ul>
<li>Kernel-mode인 경우에는 CPU를 빼았지 않고, User-mode인 경우에만 CPU를 빼았는다,</li>
<li>즉, 커널에 있을 떈 CPU Preemption을 고려하지 않아도 된다.</li>
</ul>
<p>하지만 위와 같은 방법을 사용하게 된다면, <strong>정말 중요한 작업이 도중에 발생</strong>하더라도 모드 비트가 Kernel-mode에 있다면 <strong>CPU를 뻇을 수 없기</strong> 떄문에 중요한 작업을 빠르게 처리할 수 없게 된다는 문제가 발생한다.</p>
<ul>
<li>이와 같은 문제는 실시간 컴퓨팅(real-time system)과 같은 빠른 처리와 빠른 전환을 요구하는 시스템에서는 매우 부적합함.<ul>
<li>** 실시간 컴퓨팅 : 사용할 수 있는 자원이 한정되어 있는 상황에서 작업 수행이 요청되었을 때, 이를 제한된 시간안에 처리해 결과를 내주는 것을 말함</li>
</ul>
</li>
</ul>
<p>이에 리눅스 엔지니어들은 다음과 같은 방법으로 이러한 문제를 해결하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/06586ca2-0d3f-40e6-90a3-0cb62afccee5/image.png" alt=""></p>
<ul>
<li>리눅스에서는 <strong>공유된 변수에 접근할 때 Lock을 거는 시스템</strong>이 있음<ul>
<li><strong>lock이 걸려있다면, Kernel-mode건 아니건 CPU를 빼았는 일은 발생하지 않음</strong></li>
<li><strong>그러나 unlock이라면, Kernel-mode임에도 CPU를 다른 프로세스에 할당하는 것이 가능</strong></li>
</ul>
</li>
</ul>
<p>리눅스의 lock 시스템은 다음과 같은 로직으로 구현된다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/16c4c7b0-29f2-442e-a76e-b29dcf3b61a2/image.png" alt=""></p>
<ul>
<li><strong>preempt_count</strong> - 모든 thread 마다 존재<ul>
<li>global 변수에 접근할 때마다 <strong>preempt_count 개수를 증가</strong>시킨다.</li>
<li>즉, <strong>해당 스레드가 접근 중인 global 변수의 개수(lock의 개수)</strong>라고 볼 수 있다.</li>
<li>접근이 끝나면 count를 감소시킨다.</li>
</ul>
</li>
<li><strong>need_resched</strong><ul>
<li>만약 CPU를 빼았으러 왔는데 <strong>preempt_count</strong>가 0이 아니라 못 뻈었다면, “지금 나 급한 일이 있어서 왔는데, 못 뻈고 기다리고 있어”라는 <strong>표시</strong>를 해주는 용도로 사용된다.</li>
</ul>
</li>
</ul>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0">https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0</a></li>
<li><a href="https://jeongzero.oopy.io/category/kernel">https://jeongzero.oopy.io/category/kernel</a></li>
<li><a href="https://medium.com/pocs/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%95%EC%9D%98%EB%85%B8%ED%8A%B8-0-a6f5702674d">https://medium.com/pocs/리눅스-커널-운영체제-강의노트-0-a6f5702674d</a></li>
<li><a href="http://egloos.zum.com/rousalome/v/10011973">http://egloos.zum.com/rousalome/v/10011973</a></li>
<li><a href="https://nostressdev.tistory.com/16">https://nostressdev.tistory.com/16</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux Kernel] System call]]></title>
            <link>https://velog.io/@whattsup_kim/Linux-Kernel-System-call</link>
            <guid>https://velog.io/@whattsup_kim/Linux-Kernel-System-call</guid>
            <pubDate>Sun, 16 Oct 2022 09:15:03 GMT</pubDate>
            <description><![CDATA[<h1 id="시스템-콜system-call">시스템 콜(System call)</h1>
<p>시스템 콜을 다시 정리해보면, 멀티 유저 시스템에서 한 프로세스가 다른 프로세스에 I/O로 함부로 접근하여 데이터를 망치는 일을 사전방지(prevent)하고자 시스템에 허락을 받는 매커니즘임. </p>
<p><strong>그렇다면, 시스템 콜은 정확히 언제 일어나는 것인가?</strong></p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/b6f20aca-94d7-4adb-893a-d6f382819b74/image.png" alt=""></p>
<blockquote>
<p><strong>참고:</strong></p>
<ul>
<li>리눅스 명령어는 옆에 붙은 숫자에 따라 (1) Commands, (2) System call, (3) library functions 을 구분할 수 있음</li>
<li>이때, 모든 system call function은 <code>sys_</code>로 시작함</li>
</ul>
</blockquote>
<p>위 그림에서 User의 소스를 보면, 함수 안에 <code>add()</code>, <code>sub()</code> 뒤에 <code>printf()</code> 함수가 어떤 순서로 처리되는지를 확인해보자.</p>
<ul>
<li>해당 함수<code>printf()</code>는 라이브러리(3) 내부에 구현되어 있는 곳으로 가게 됨.</li>
<li><code>printf()</code>는 I/O를 위한 함수이므로, <code>write()</code>라는 Wrapper Routine 시스템 콜을 호출하여 작업을 요청하게 됨.<ul>
<li>Wrapper Routine에는 prepare parameter(왜 커널로 가게 되는지 알려주는 정보를 담고 있음)와 chmodk가 들어있음</li>
<li>즉, Wrapper Routine은 트랩으로 넘어갈 내용들을 준비하고, 실질적으로 트랩을 일으키는 공간임</li>
</ul>
</li>
<li>이후 프로그램은 trap에 걸려 커널 영역으로 가게 되며, 커널 내부에 존재하는 <code>sys_write()</code> 함수가 호출되게 됨.</li>
</ul>
<h2 id="wrapper-routine">Wrapper Routine</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/badf0cb7-77ae-4b93-92ae-c55616050457/image.png" alt=""></p>
<p>Wrapper Routine에선 트랩을 일으키기 전에 Prepare parameter들을 준비하게 되는데, 그 중 가장 중요한 것이 <strong>system call number</strong>임</p>
<ul>
<li>** system call number: 커널이 가지고 있는 system call function의 시작 주소를 담고있는 Array의 Index번호<ul>
<li>system call number는 컴파일러와 서로 합의된 규칙하에 적용이 됨</li>
</ul>
</li>
</ul>
<p>이후 <code>int $0x80</code>과 같이 의미없는 문자들을 이용해 Machine Instruction을 주어 트랩을 유발시킴</p>
<ul>
<li>그림에서 나오는 <code>int $0x80</code>는 x86 기준임</li>
</ul>
<p>트랩이 걸린 후에는 커널이 system call number을 가지고 system call function table에 접근해 function의 시작 주소에 접근하게 됨</p>
<h2 id="kernel-system-call-function">Kernel System Call Function</h2>
<p>커널에서는 유저가 원하는 요청에 대한 반환 값을을 시스템 콜을 호출한 유저 영역으로 넘겨줘야 한다. 따라서 떄로는 커널이 유저 영역으로부터 데이터를 가져와야 하는 경우도 있음.</p>
<ul>
<li><p>이러한 기능들은 오직 커널만이 가지고 있음(오직 커널만이 모든 메모리 영역에 접근이 가능함)</p>
<ul>
<li><p><code>chmodk</code>가 호출되면 비트 모드가 Kernel 모드로 바뀌며, 독립된 커널  프로그램이 수행됨.</p>
</li>
<li><p>다음 그림처럼 이러한 것에 필요한 함수들도 다 구현이 되어 있음</p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/63161f8b-8f18-498f-85e7-7e430e6cac13/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<h2 id="나만의-새로운-system-call-작성에-대하여">나만의 새로운 System call 작성에 대하여</h2>
<p>내가 직접 시스템 콜을 직접 만들어 사용한다면, 심플하게 구현할 수 있으며 기존 시스템콜보다 좋은 성능을 보일 수는 있음</p>
<ul>
<li>그러나, 해당하는 시스템 콜만의 새로운 system call number를 정의해야 되며, 이 떄문에 해당 프로그램은 플랫폼에 의존적이게 됨</li>
<li>또한 한 번 만든 시스템 콜은 변경이 불가능하기 때문에 수정을 할 수 없는 문제도 있음</li>
</ul>
<p>대신 기존에 있던 시스템 콜인 <code>read</code>나 <code>write</code>, <code>ioctl</code> 등에 있는 fd(file descriptor)를 이용하는 방법이 있음</p>
<ul>
<li><p>fd란, 운영체제가 만든 파일이나 소켓을 편하게 부르기 위해서 부여하는 0과 음수가 아닌 정수값임.</p>
<p>  (프로세스가 파일들에 접근할 땐 이 fd를 사용하게 됨)</p>
</li>
<li><p>fd는 보통 적은 숫자만이 활용되고 있음. 따라서 잘 쓰지 않는 999와 같은 번호에 본인의 fd를 지정하고 사용하면, 훨씬 안전하게 로직을 수행할 수 있게 됨</p>
</li>
</ul>
<h1 id="프로세스-매니지먼트process-management">프로세스 매니지먼트(Process Management)</h1>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/8b4aad24-9db9-444a-b107-2d3821eb070d/image.png" alt=""></p>
<ul>
<li>위 그림에서 분홍색 부분이 커널임.<ul>
<li>커널이 1차적으로 해야하는 일은 하드웨어를 관리하는 일임(CPU, Memory, disk, tty 등의 하드웨어 자원들을 세팅함)</li>
<li>1차적인 업무가 끝나면, 그 이후에 유저 프로그램들을 support하게 됨</li>
</ul>
</li>
</ul>
<p>이렇듯 커널은 효율적인 하드웨어 관리와 유저 프로그램을 지원하기 위해 다음과 같이 자체적인 Internal Data Structure을 가지고 있음.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/5ceb3e93-0768-4e0b-81e7-b97b01f9d9a9/image.png" alt=""></p>
<ul>
<li>Data Structure에는 (우선) 각 하드웨어에 대한 정보가 담겨있음<ul>
<li>ex) 위 그림에서 <code>mem</code>에는 메모리의 총 크기가 어느정도이며, 어디서부터 어디까지 사용되고 있는지 등에 대한 정보가 담겨있음</li>
</ul>
</li>
<li>또한 Data Structure에는 <strong>프로세스들을 관리</strong>하기 위한 <strong>PCB(Process Control Block)</strong>도 가지고 있음</li>
<li>위 두 Data Structure들을 합하여 메타데이터라고 부름</li>
</ul>
<h2 id="pcbprocess-control-block">PCB(Process Control Block)</h2>
<p>프로세스를 관리하기 위한 PCB에는 다음과 같은 정보들이 들어가있음.</p>
<ul>
<li><p>해당 프로세스의 PID(프로세스 식별자)</p>
</li>
<li><p>프로세스의 우선순위</p>
</li>
<li><p>대기 현상(입출력 작업 시 waiting이 일어날 수 있음)</p>
</li>
<li><p>프로세스 상태(run, sleep)</p>
</li>
<li><p>프로세스가 어디에 올라와 있는지(메모리, disk)</p>
</li>
<li><p>열린 파일들</p>
<ul>
<li>유닉스에서는 모든게 파일임.</li>
<li>이때, 파일은 Sequenct of bytes임</li>
<li>I/O조차 파일로 간주됨</li>
</ul>
</li>
<li><p>현재 프로세스가 실행되고 있는 환경에 대한 정보</p>
</li>
<li><p>터미널</p>
</li>
<li><p><strong>상태 벡터 저장 공간(state vector save area)</strong></p>
<p>  <img src="https://velog.velcdn.com/images/whattsup_kim/post/6e63a042-05b4-4d48-bb66-7e290ff1db3d/image.png" alt=""></p>
<ul>
<li>만약 프로세스 A가 CPU를 사용하다가 디스크로 갔는데, 디스크가 먼저 들어온 일을 처리하고 있으면 waiting을 신청하고 대기열(waiting queue)에 들어가서 기다리게 됨.<ul>
<li>waiting queue 중 cpu에 링크를 걸어두고 기다리는 것을 ready queue라고 부르며, 디스크에 링크를 걸어두고 기다리는 것을 disk wait queue(혹은 Disk I/O queue)라고 부름</li>
</ul>
</li>
<li>이때 A는 점유하던 CPU를 다른 프로세스에게 주게 되고, A가 하던 작업의 내용을 A의 PCB에 저장하게 됨.</li>
<li>이 저장 공간을 <strong>상태 벡터 저장 공간(state vector save area)</strong>라고 부름(즉, 프로세스의 상태들을 저장한다고 보면 됨)</li>
</ul>
</li>
</ul>
<h1 id="child-process-생성">Child process 생성</h1>
<p>컴퓨터를 키면 제일 먼저 Kernel process가 로드됨. 그리고 이 커널은 터미널이 켜질 때 마다 그에 해당하는 <strong>Shell</strong> 즉, <strong>Child Process</strong>를 만듦.</p>
<ul>
<li>** shell: 많은 프로그램들(Utility)들이 disk로부터 언제 올라오고, 언제 내려가는지 등을 컨트롤하는 프로그램(Job(command) Control)</li>
</ul>
<p><strong>Child Process</strong>를 생성하기 위한 과정들은 다음과 같음.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/c2ffb371-2090-4bb5-a696-14512ed38db5/image.png" alt=""></p>
<ol>
<li><p>PCB 공간을 만들어 주고, 초기 값으로 Parent Process의 PCB 값을 복사해옴</p>
<ul>
<li>Parent가 사용하던 Resource들을 자식도 공유함(Parent Process의 실행 환경이 Child Process의 실행 환경이 됨)</li>
</ul>
</li>
<li><p>Child Process가 올라올 메모리 공간을 확보함</p>
<ul>
<li>이를 위해 커널은 Memory의 Data Structure에 가서 빈 공간을 찾아 지정해줌</li>
<li>이때, 빈 공간에 Child Process를 올리기 전에 먼저 Parent Process의 image를 똑같이 복사해옴.<ul>
<li>즉, 부모와 자식은 동일한 코드를 가지게 됨</li>
</ul>
</li>
</ul>
</li>
<li><p>디스크로부터 Child Process에 새로운 image를 로드함</p>
<ul>
<li>즉, 실제 디스크에서 원하는 프로그램을 가져옴</li>
</ul>
</li>
<li><p>새로 생긴 Child Process의 PCB를 CPU의 ready queue에 등록하여 CPU를 사용할 수 있게 준비함</p>
<ul>
<li>이는 아직 CPU를 부모 프로세스가 사용하고 있기 때문</li>
</ul>
<p>→ <strong>1번과 2번 과정을 통틀어서 <code>fork()</code>라고 부름 (부모와 동일하게 만듦)</strong>
→ <strong>3번과 4번 과정을 통틀어서 <code>exec()</code>라고 부름 (새로운 이미지를 가져옴)</strong></p>
</li>
</ol>
<h2 id="fork">fork()</h2>
<p>fork란 1, 2단계. 즉, 부모 프로세스의 PCB와 이미지 정보를 그대로 자식 프로세스에 복사하는 것임. 이때 기억해야 할 것은 <strong>fork는 한 번 호출하면 두 번 리턴한다는 것</strong>임</p>
<pre><code class="language-c">main()
{   int pid;
        pid = fork();
        if (pid == 0) /* this is child */
                    printf(“I am child! \n”);            
        else /* this is parent */
                    printf(“I am parent!\n”);
}</code></pre>
<ul>
<li><p><strong>첫 번째 리턴</strong> : 부모가 자신의 프로세스를 그대로 자식에게 복사하고, CPU의 ready queue에 자식을 등록시키고 다시 부모 프로세스로 리턴하는 과정
  → 부모 프로세스에게 자식 프로세스의 PID 값을 리턴(자식 프로세스의 pid값을 리턴 받음으로써 부모 프로세스는 자식 프로세스를 알고 통제할 수 있음)
  → (위 알고리즘에서) 부모는 자식의 PID 값을 가지고 있으므로, “i am parent!” 를 출력</p>
</li>
<li><p><strong>두 번쨰 리턴</strong> : 자식이 fork로 생성되면 queue에서 기다리다가 CPU를 점유하며 실행이 되게 됨. 이때 자식은 부모 프로세스를 그대로 복사했기 때문에 부모 프로세스와 똑같은 프로그램을 실행하게 됨</p>
<ul>
<li>자식은 부모의 PCB도 복사해왔기 때문에 ‘상태 벡터 저장 공간(state vector save area)’도 전부 동일하게 가지고 있음(어디서부터 실행해야할지 알려주는 PC(Program Counter)와 SP(Stack Pointer) 등 또한 복사됨).</li>
<li>따라서 자식 프로세스의 코드가 실행될 때는 맨 처음부터 실행되는 것이 아니라 <code>fork()</code>중간에서부터 다시 진행하게 됨(fork가 진행중이었던 부분부터 다시 진행이 됨).</li>
</ul>
</li>
</ul>
<pre><code>→ 자식 프로세스에게 0값을 리턴
→ (위 알고리즘에서) 자식 프로세스의 pid는 보통 0을 가지므로, if문을 실행하게 됨.</code></pre><h2 id="exec">exec()</h2>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/8bbf7dc6-f2ad-4bcc-9a17-4c2739af7638/image.png" alt=""></p>
<blockquote>
<p><strong><code>exec()</code> 시스템 콜 관련 참고해야 할 내용들</strong></p>
<ol>
<li><p>리눅스에는 <code>exec~</code>로 시작하는 함수(시스템 콜)가 존재함. 이 함수들은 모두 공통적으로 프로그램을 실행한다는 특징을 갖고 있음.</p>
<ul>
<li><p><code>exec</code>를 사용하게 되면 기존의 <code>exec</code>를 실행시킨 프로세스는 <code>exec</code>가 실행한 프로그램으로 대체됨.<br>  덧붙여 설명하면, <code>exec</code> 계열 함수가 호출되면 그 즉시 현재의 프로세스의 기본적인 정보(file, mask, pid 등)만 유지한 채 <code>exec</code> 함수의 인자로 받은 실행파일(바이너리 이미지 파일 → 디스크로부터 가져옴)이라는 새로운 실행 프로세스(이미지)로 교체됨.</p>
</li>
<li><p>대체 된 이후에는 <code>exec</code>로 새로 실행한 프로그램의 <code>main()</code>으로 넘어가게 됨</p>
</li>
<li><p>이때, 새로운 프로세스가 생기는 것은 아니기 때문에 <strong>exec를 실행시킨 프로세스 ID와 exec로 실행된 프로세스 ID는 같음</strong></p>
<ul>
<li>다만, 프로세스를 구성하는 코드와 데이터, 힙, 그리고 스택 영역의 값들이 exec으로 발생하는 새로운 프로그램의 것으로 바뀌게 됨</li>
</ul>
</li>
</ul>
</li>
<li><p>시스템 콜 <code>exec()</code> 안에 <code>/bin</code> 폴더는 바이너리(binary) 파일만 모아둔 폴더를 의미함</p>
<ul>
<li>그 안에는 바이너리 프로그램들이 여러 개 존재함(ls, cat, …)</li>
</ul>
</li>
</ol>
</blockquote>
<ul>
<li>위 코드를 보면, <strong>자식 프로세스</strong>는 <code>fork()</code>에서 0 값을 리턴받았으므로, if문 안으로 들어가게 됨<ul>
<li>if 문에서 <code>printf()</code>를 실행하고, <code>execlp()</code>로 <strong>자신의 프로세스를 <code>exec</code>가 실행할 프로세스로 대체함</strong></li>
<li>이후 <code>exec</code>의 인자로 왔던 date 프로그램의 <code>main()</code>으로 넘어가게 됨.</li>
</ul>
</li>
</ul>
<p>  → 한 마디로, 기존 작업하던 것을 자신의 프로그램으로 덮어 씌우고, 자신의 프로그램을 가동시킴</p>
<h2 id="wait">wait()</h2>
<p>부모 프로세스가 자식 프로세스를 생성하는 작업 등을 하면, <code>wait()</code> 시스템 콜이 호출됨. 이때, <code>wait()</code>시스템 콜(<code>sys_wait()</code>)을 호출한 <strong>프로세스는 CPU 사용권한을 박탈당하게 됨.</strong></p>
<ul>
<li><p>만약 A라는 (부모) 프로세스가 wait 시스템 콜을 호출하면, trap에 걸려 커널 영역으로 가고, 커널 내부에 존재하는 <code>sys_wait()</code>함수가 호출됨</p>
<ul>
<li>이때 시스템은 <code>wait()</code>시스템 콜을 호출한 프로세스로부터 CPU를 빼앗게 됨</li>
<li>다시 말해, 자신의 일을 다 하고 나면 호출한 프로세스의 유저 모드로 돌아가야 하는데, cpu를 뺴앗기기 때문에 돌아가지 못하게 되고, 기다리게 된다(sleep)는 것임.</li>
</ul>
</li>
<li><p>이후 커널은 <strong>ready queue</strong>(cpu에 링크를 걸어두고 기다리는 곳)로 가서 준비된 프로세스 중 우선순위가 가장 높은 프로그램의 <strong>PCB</strong>를 찾아서 <strong>PC(Program Counter)</strong>를 알아낸 후, <strong>PC</strong>가 가르키는 쪽으로 CPU를 넘겨주게 됨</p>
<ul>
<li><p>** PC(Program Counter) : <strong>다음에 실행될 명령어의 주소</strong>를 가지고 있어, 다음에 실행할 기계어 코드의 위치를 가르키는 역할을 함</p>
<p>  → 이러한 과정을 <strong>preempt</strong>라고 부름</p>
</li>
</ul>
</li>
<li><p>이후 자식 프로세스의 수행이 끝나면서 특정 시그널을 보내면, 그때서야 부모 프로세스의 sleep이 풀리면서 <strong>ready queue</strong>로 들어가게 됨</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/04f1d0c5-202d-40f3-b968-c50fc1fa6b62/image.png" alt=""></p>
<p>위 코드를 해석하면, 다음과 같은 순서로 흘러감</p>
<ol>
<li>부모 프로세스<ul>
<li><code>fork()</code>를 통해 자식 프로세스를 생성</li>
<li><code>else</code>로 빠지게 되며 <code>wait()</code>을 호출하고, sleep상태에 빠지게 됨.</li>
<li>CPU는 부모에게서 자식으로 감</li>
</ul>
</li>
<li>자식 프로세스<ul>
<li><code>if</code>로 빠지게 되며 <code>exec</code>를 호출하여 일을 다 수행하고, 자식 프로세스는 종료될 것임</li>
<li>자식 프로세스는 종료될 때 특정 시그널을 보내게 됨</li>
</ul>
</li>
<li>다시 부모 프로세스<ul>
<li>자식 프로세스가 종료되면(수행이 끝나며 특정 시그널을 보내면) CPU는 <strong>자식 프로세스로부터 부모 프로세스를 찾게 됨</strong></li>
<li>바로 이때 <code>wait()</code>시스템 콜이 풀리게 되며 부모 프로세스를 <strong>ready queue</strong>에 등록시킴</li>
<li>부모 프로세스는 이후 CPU를 받게 되고, 자신의 남은 일을 진행하게 됨</li>
</ul>
</li>
</ol>
<h2 id="exit">exit()</h2>
<p>메인함수 <code>main()</code>이 끝날 떈 반드시 <code>exit()</code>시스템 콜이 존재함.</p>
<ul>
<li>만약 소스에 해당 시스템 콜이 없다고 하더라고, 컴파일러가 알아서 이를 추가함</li>
</ul>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/56a16a69-f616-411e-a3b3-556e5a3fc381/image.png" alt=""></p>
<ul>
<li>위 코드를 보면</li>
<li>자식 프로세스는 if로 빠지게 되고, <code>exec()</code>가 실행되면서 인자로 주어진 프로세스(<code>/bin/date</code>)로 현재 프로세스를 덮어 씌우게 됨</li>
<li>이후 해당 프로세스의 <code>main()</code>을 실행시키게 되며 끝날 떈 <code>exit()</code>를 반드시 실행시킴<ul>
<li><code>exit()</code>는 다음과 같이 동작함<ul>
<li><code>exit()</code>이후로 들어오는 신호들을 전부 무시</li>
<li>파일들이 열려있다면, 닫음</li>
<li>메모리 영역에서 해당 프로세스가 차지하고 있는 부분(image)을 해제</li>
<li>부모 프로세스에 시그널을 보냄(통보)</li>
<li><code>exit()</code>를 호출한 프로세스의 상태를 좀비(ZOMBIE) 상태로 설정</li>
</ul>
</li>
<li>또한 <code>exit()</code>가 호출되면 커널은 다음과 같이 동작함<ul>
<li>CPU를 빼앗고, ready queue에 있는 다른 프로세스에 넘겨줌<ul>
<li>이를 스케쥴링(scheduling)이라고 함</li>
<li><code>exit()</code> 함수는 커널 함수 <code>schedule()</code> 를 호출하여 위와 같은 작업을 함</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="정리---fork-exec-wait-exit">정리 - fork() exec() wait() exit()</h2>
<ul>
<li>fork() : 부모의 리소스를 복제하여 자식을 만듦</li>
<li>exec() : 복제한 자식 프로세스 위에 실행하려는 새로운 프로세스 이미지를 덮어씌우고, 해당 프로세스의 <code>main()</code>으로 감</li>
<li>wait() : wait 시스템 콜을 호출한 프로세스를 (자식 프로세스가 끝날 때까지) sleep 시킴</li>
<li>exit() : (자식 프로세스의) 리소스들을 모두 하제하고, 부모에게 알림</li>
</ul>
<h2 id="context-switch-by-wait--exit">Context Switch by wait() &amp; exit()</h2>
<p>이번에는 <code>wait()</code>와 <code>exit()</code>의 상호작용을 중심으로 Child process 생성 과정을 다시 살펴봐보자.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/dd896e5f-b2e0-4460-8554-6788b6da0017/image.png" alt=""></p>
<ol>
<li>유저가 쉘에 명령어 <code>ls</code>를 입력함<ul>
<li>쉘은 이를 실행시키기 위해 <code>fork()</code>를 실행함 (쉘이 <strong>부모 프로세스</strong>).</li>
<li><code>fork()</code>가 동작하며 <strong>쉘의 PCB와 이미지 정보를 복사</strong></li>
<li>그러나 아직 <strong>CPU는 쉘에 할당되어 있음</strong> → 따라서 ls가 실행되지는 않음</li>
</ul>
</li>
<li>부모 프로세스인 쉘은 <code>wait()</code>를 호출하게 되고, 쉘은 잠들게 됨.<ul>
<li>잠들면서 <strong>자식 프로세스는 CPU의 ready queue</strong>에 들어가게 됨</li>
</ul>
</li>
<li>자식 프로세스가 CPU를 받고나면<ul>
<li>자식은 부모의 리소스들으 똑같이 물려받았으므로, <code>fork()</code>중간에서 동작을 시작하게 됨</li>
<li>이때, <code>fort()</code>로부터 리턴된 값은 자식 프로세스를 뜻하는 PID값 0이므로, 자식 프로세스는 <code>exec~</code>를 실행하게 될 것임</li>
</ul>
</li>
<li>자식 프로세스에서 exec가 실행되면 디스크로부터 <code>ls</code>를 로드하고, 부모 프로세스(쉘)로부터 복사해왔던 이미지 위에 그대로 덮어씌우게 된다. 이후 <code>ls</code>의 <code>main()</code>으로 가게 됨</li>
<li><code>ls</code>가 끝나면 <code>exit()</code>시스템 콜을 하게 됨<ul>
<li>자식 프로세스의 리소스들을 모두 하제하고, 부모에게 알림</li>
<li>이제 CPU는 ready queue에 있던 다른 프로세스에 할당 될 것임 → 이때 부모 프로세스의 <code>wait()</code>콜이 끝난 것으로 인지하게 됨(부모가 sleep에서 깨어남)</li>
</ul>
</li>
<li>(그림에서 7번) 이후 부모 프로세스(쉘)는 ready queue에 등록되어 차례를 기다리다가, CPU를 다시 받으면 다시 돌아와서 일을 시작하게 됨</li>
</ol>
<p>위 과정을 도식화하면, 다음과 같이 User-mode와 Kernel-mode를 왔다갔다 하며 context switching을 하는 것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/b9ee21f1-dc24-4a26-89ca-79853d016fe1/image.png" alt=""></p>
<p>또 다시 이를 커널과 CPU 관점에서 보았을 땐 다음 그림과 같이 표현할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/79116894-d4c3-4bc5-97f4-2430ba0d2674/image.png" alt=""></p>
<ul>
<li>그림에서 메모리에는 P1, P2, Kernel이 올라와 있음<ul>
<li>Kernel 안에는 하드웨어 장치의 정보를 담고 있는 Data Structure(struct CPU)와 프로세스의 정보를 담고 있는 PCB가 들어있음</li>
</ul>
</li>
<li>이때, P1이 자기 자신을 block시킬 때가 되다면, <code>wait()</code> 시스템 콜을 호출하게 된다.<ul>
<li>Kernel-mode로 넘어가며 <code>sys_wait()</code>를 호출하게 됨</li>
<li>또한 현재 P1의 상태(state vector)를 PCB에 저장하게 됨</li>
</ul>
</li>
<li>P1이 wait하게 됐으므로, 커널은 커널 안에 있는 하드웨어 Data Structure의 <strong>ready queue</strong>에서 현재 가장 우선순위가 높은 작업을 찾아 CPU를 넘겨주게 됨</li>
<li>만약 P2가 다음 우선순위 높은 작업이라면, P2의 PCB로부터 state vector들을 cpu에 로드시키고, PC에 저장된 주소로 이동하면서 P2프로세스가 실행됨</li>
</ul>
<p>→ 이 과정이 Context Switching이며, 이를 해주는 함수가 <code>schedule()</code>이라는 함수임</p>
<p>→ 다시 설명하면, Context Switch란, CPU가 한 개의 task(process / thread)를 실행하고 있는 상태에서 interrupt 요청에 의해 다른 task로 실행이 전환되는 과정에서 기존의 task 상태 및 Register 값들에 대한 정보(context)를 저장하고, 새로운 task의 정보(context)로 교체하는 작업을 말함.</p>
<h2 id="context-switch---schedule">Context Switch - <code>schedule()</code></h2>
<p><code>schedule()</code>은 참고로 유저 단에서 시스템 콜로 커널에게 요청함으로써 호출할 수 있는 함수가 아니라, <strong>커널 내부에서만 호출이 가능함 함수</strong>임 (즉, 시스템 콜이 아님)</p>
<ul>
<li><code>schedule()</code>은 <code>read()</code>, <code>wait()</code>, <code>exit()</code>과 같은 함수에 의해서 호출됨</li>
<li><code>schedule()</code>은 CPU를 사용하는 사람이 바뀌어야 할 때 기존 작업의 상태(state vector)를 PCB에 저장해주고, 새로운 작업에 CPU를 할당해주기 위한 내부 작업을 진행해줌</li>
</ul>
<p>→ <strong>즉, <code>schedule()</code>함수는 CPU의 상태(대여자)가 바뀔 때마다 호출되게 됨</strong></p>
<h1 id="🔥-시스템-콜-총-정리">🔥 시스템 콜 총 정리</h1>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/666c678b-f9f5-43fb-a06d-ab93213e581d/image.png" alt=""></p>
<ol>
<li><p>터미널을 키면 쉘이 나오게 됨. 쉘은 사용자의 입력을 기다리다가 <code>ls</code>와 같은 명령을 입력받게 되면, 커널에 있는 <code>fork()</code>를 호출하게 됨(시스템 콜)</p>
<p> ** shell: 많은 프로그램들(Utility)들이 disk로부터 언제 올라오고, 언제 내려가는지 등을 컨트롤하는 프로그램(Job(command) Control)</p>
</li>
<li><p><code>fork()</code>를 요청하면, Kernel-mode로 넘어가게 되고 <code>sys_fork()</code>가 호출됨. </p>
<ul>
<li><code>sys_fork()</code>는 현재 쉘 프로세스(부모) 이미지(코드)를 그대로 복사하여 자식을 만듦</li>
</ul>
</li>
<li><p><code>fork()</code>호출이 끝나면 부모 프로세스인 쉘은 else로 들어가서 <code>wait()</code>시스템 콜을 호출하게 됨</p>
</li>
<li><p><code>wait()</code> 를 요청하면, 또 다시 Kernel-mode로 넘어가게 됨</p>
</li>
<li><p><code>wait()</code>는 CPU를 잠시 포기하겠다는 것이므로, <code>context_switch()</code>함수를 실행함</p>
<ul>
<li>CPU에 있던 (부모 프로세스의) state vector 영역의 정보를 PCB에 저장</li>
<li>CPU의 ready queue에 자식을 등록시킴</li>
<li>부모 프로세스는 sleep 상태가 됨(cpu를 양도함)</li>
</ul>
</li>
<li><p>(자식 프로세스가 CPU를 받고나면) 자식 프로세스가 생겨날 때는 부모  프로세스에서 <code>fork()</code>가 진행되던 시점이었으므로, 자식 프로세스의 PC는 <code>fork()</code> 중간을 가르키고 있음. 따라서 자식 프로세스는 <code>fork()</code>지점에서부터 시작하게 됨</p>
</li>
<li><p>자식 프로세스는 if로 들어가서 <code>exec()</code>시스템 콜을 호출함</p>
</li>
<li><p><code>exec()</code>는 하드디스크에 저장되어 있는 프로그램 코드(<code>exec()</code>의 매개변수로 준 프로그램 = 이미지)를 로드함</p>
</li>
<li><p>(8)에서 로드한 이미지를 현재 진행되고 있던 프로세스 이미지 위에 덮어씌우는 작업을 함</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/whattsup_kim/post/52e50cf8-bf9e-4fa3-9544-17e5b4181328/image.png" alt=""></p>
<ol start="10">
<li>이제 덮어 씌어진 이미지의 <code>main()</code>(이미지에서는 <code>ls</code>)으로 흐름이 넘어가게 됨</li>
<li>해당 이미지의 <code>main()</code>을 전부 실행하고 나면</li>
<li>커널에 <strong>exit()</strong>를 호출하게 됨<ul>
<li>자식 프로그램의 리소스를 모두 해제하고, 부모에게 알림</li>
</ul>
</li>
<li><code>exit()</code>는 <code>context_switch()</code>함수를 실행시킴<ul>
<li>이때 부모 프로세스도 sleep에서 깨어나게 되며, ready queue에 등록되어 차례를 기다리게 됨</li>
</ul>
</li>
<li>ready queue에서 기다리던 부모 프로세스가 다시 선택되면, 다시 유저 모드의 <code>wait()</code> 시스템 콜 요청 때로 돌아가게 됨<ul>
<li>아까 부모프로세스는 <code>wait()</code>중간에서 switch가 됐었기 때문에 그 부분으로 다시 돌아감</li>
</ul>
</li>
<li>이후 쉘은 다시 사용자로부터 또 다른 명령을 기다리는 상태가 됨</li>
</ol>
<hr>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0">https://olc.kr/course/course_online_view.jsp?id=35&amp;s_keyword=Kernel&amp;x=0&amp;y=0</a></li>
<li><a href="https://jeongzero.oopy.io/category/kernel">https://jeongzero.oopy.io/category/kernel</a></li>
<li><a href="https://medium.com/pocs/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%95%EC%9D%98%EB%85%B8%ED%8A%B8-0-a6f5702674d">https://medium.com/pocs/리눅스-커널-운영체제-강의노트-0-a6f5702674d</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>