<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>quro</title>
        <link>https://velog.io/</link>
        <description>개발합니다</description>
        <lastBuildDate>Mon, 15 Jul 2024 17:45:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>quro</title>
            <url>https://velog.velcdn.com/images/quro_97/profile/2939edda-0fc8-450b-9dfa-e92f4a759f69/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. quro. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/quro_97" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[SQL - Join문]]></title>
            <link>https://velog.io/@quro_97/SQL-Join%EB%AC%B8</link>
            <guid>https://velog.io/@quro_97/SQL-Join%EB%AC%B8</guid>
            <pubDate>Mon, 15 Jul 2024 17:45:39 GMT</pubDate>
            <description><![CDATA[<h2 id="join이란">Join이란?</h2>
<p>두 개 이상의 테이블을 서로 연결하여 데이터를 검색할 때 사용하는 방법</p>
<p>두 개의 테이블을 마치 하나의 테이블인 것처럼 보여준다.</p>
<p>Join문은 여러 종류가 있는데, 하나씩 특징을 살펴보자.</p>
<h3 id="implicit-join">Implicit join</h3>
<ul>
<li>from 절에는 table들만 나열하고 where절에 join condition을 명시하는 방식</li>
<li>예전 방식의 join 문법</li>
<li>복잡해질수록 실수할 가능성이 높다</li>
</ul>
<pre><code class="language-sql">SELECT D.name 
FROM employee AS E, department AS D 
WHERE E.id = 1 and E.dept_id = D.id;</code></pre>
<h3 id="explicit-join">Explicit join</h3>
<ul>
<li>from 절에 JOIN 키워드와 함께 joined table들을 명시하는 방식</li>
<li>from 절에서 ON 뒤에 join condition이 명시된다</li>
<li>복잡한 join 쿼리 작성 중에도 실수할 가능성이 적다</li>
</ul>
<pre><code class="language-sql">SELECT D.name
FROM employee AS E JOIN department AS D ON E.dept_id = D.id
WHERE E.id = 1;</code></pre>
<h3 id="inner-joinjoin">Inner join(join)</h3>
<ul>
<li>그냥 JOIN 으로 사용가능(INNER 생략 가능)</li>
<li>두 table에서 join condition을 만족하는 tuple들로 result table(결과 테이블)을 만드는 join</li>
<li>join condition에 사용 가능 연산자 : =, &lt;, &gt;, ≠ 등 여러 비교 연산자 가능</li>
<li>join condition에서 null 값을 가지는 tuple은 result table에 포함되지 않는다</li>
</ul>
<pre><code class="language-sql">SELECT *
FROM employee E INNER JOIN department D on E.dept_id = D.id;</code></pre>
<h3 id="outer-join">Outer join</h3>
<ul>
<li><strong>LEFT</strong>, <strong>RIGHT</strong>, <strong>FULL</strong> OUTER JOIN 이 존재(OUTER 생략 가능)<ul>
<li><strong>LEFT JOIN</strong><ul>
<li>왼쪽 table에서 join condition을 만족하지 않는 tuple들도 result table에 포함</li>
</ul>
</li>
<li><strong>RIGHT JOIN</strong><ul>
<li>오른쪽 table에서 join condition을 만족하지 않는 tuple들도 result table에 포함</li>
</ul>
</li>
<li><strong>FULL JOIN (mysql에서는 지원X)</strong><ul>
<li>두 table에서 join condition을 만족하지 않는 tuple들도 result table에 포함하는 join</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="using">Using</h3>
<ul>
<li><code>USING</code> 키워드를 사용하여 동일한 이름을 갖는 열에 대해 조인을 수행하는 방식</li>
<li><code>ON</code> 절과 같은 결과를 만들지만, 동일한 이름을 갖는 열을 명시적으로 사용할 수 있음</li>
</ul>
<pre><code class="language-sql">SELECT *
FROM employee AS E JOIN department AS D USING (dept_id);</code></pre>
<h3 id="natural-join">Natural join</h3>
<ul>
<li>두 테이블 간 동일한 이름을 갖는 모든 열을 기준으로 조인하는 방식</li>
<li><code>NATURAL JOIN</code>은 동일한 이름을 갖는 열을 자동으로 인식하여 조인</li>
<li>열 이름이 동일하지 않으면 자연 조인을 사용할 수 없음</li>
</ul>
<pre><code class="language-sql">SELECT *
FROM employee AS E NATURAL JOIN department AS D;</code></pre>
<h3 id="cross-join">Cross join</h3>
<ul>
<li>두 테이블 간의 모든 조합을 반환하는 조인 방식</li>
<li>조인 조건이 없기 때문에 카테시안 곱(Cartesian Product)라고도 함</li>
<li>조인 결과가 매우 클 수 있음</li>
</ul>
<pre><code class="language-sql">SELECT *
FROM employee AS E CROSS JOIN department AS D;</code></pre>
<h2 id="join-종류-요약">Join 종류 요약</h2>
<ul>
<li><strong>Implicit join</strong>: WHERE 절을 사용하여 조인 조건을 명시</li>
<li><strong>Explicit join</strong>: JOIN 키워드를 사용하여 조인 조건을 명시</li>
<li><strong>Inner join</strong>: 두 테이블 간 조인 조건을 만족하는 튜플만 반환</li>
<li><strong>Outer join</strong>: 조인 조건을 만족하지 않는 튜플도 포함<ul>
<li><strong>LEFT JOIN</strong>: 왼쪽 테이블의 모든 튜플 포함</li>
<li><strong>RIGHT JOIN</strong>: 오른쪽 테이블의 모든 튜플 포함</li>
<li><strong>FULL JOIN</strong>: 두 테이블의 모든 튜플 포함</li>
</ul>
</li>
<li><strong>Using</strong>: 동일한 이름을 갖는 열을 기준으로 조인</li>
<li><strong>Natural join</strong>: 동일한 이름을 갖는 모든 열을 기준으로 조인</li>
<li><strong>Cross join</strong>: 두 테이블의 모든 조합을 반환</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 정규화]]></title>
            <link>https://velog.io/@quro_97/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%95%EA%B7%9C%ED%99%94</link>
            <guid>https://velog.io/@quro_97/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%95%EA%B7%9C%ED%99%94</guid>
            <pubDate>Sun, 14 Jul 2024 16:37:41 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터베이스-정규화">데이터베이스 정규화?</h1>
<p>데이터베이스 정규화는 DB를 설계하는 공식적인 방법이다. DB를 설계하는 방법의 기본이 되는 함수 종속(functional dependency)과 이를 사용해 정규화하는 방법을 알아보자.</p>
<h2 id="functional-dependencyfd">functional dependency(FD)?</h2>
<p>FD(함수 종속)는 한 테이블에 있는 두 개의 attribute의 집합 사이의 제약이다. </p>
<p>이해를 위해 EMPLOYEE라는 임의의 테이블을 만들어 예로 들어보면</p>
<p><strong>EMPLOYEE</strong></p>
<table>
<thead>
<tr>
<th>empl_id</th>
<th>empl_name</th>
<th>birth_date</th>
<th>position</th>
<th>salary</th>
<th>dept_id</th>
</tr>
</thead>
</table>
<p>이때의 { empl_id }를 <strong>집합 X, {</strong> empl_name, birth_date, position, salary }가 <strong>집합 Y</strong> 라고 가정했을 때,</p>
<p>두 튜플의 X 값이 같다면 Y 값도 같다는 특징이 있다. 왜냐하면 empl_id는 임직원들 개인에게 부여되는 고유값일 텐데, empl_id가 같다면 그 값에 해당하는 속성들도 같을 것이기 때문이다. </p>
<p>이렇듯 X 값에 따라 Y 값이 유일하게 결정될 때, <strong>‘X가 Y를 함수적으로 결정한다’</strong> 라고 말할 수 있고, 이러한 두 집합 사이의 제약 관계를 <strong>functional dependency(FD)</strong> 라고 부른다.</p>
<h3 id="⚠️fd를-파악할-때-주의-사항">⚠️FD를 파악할 때 주의 사항</h3>
<p><strong>테이블의 스키마</strong>를 보고 의미적으로 파악해야 한다.</p>
<p>즉, 테이블의 state를 보고 FD를 파악해서는 안된다.</p>
<p>다시 위의 EMPLOYEE 테이블로 예를들자면</p>
<p><strong>EMPLOYEE</strong></p>
<table>
<thead>
<tr>
<th>empl_id</th>
<th>empl_name</th>
<th>birth_date</th>
<th>position</th>
<th>salary</th>
<th>dept_id</th>
</tr>
</thead>
<tbody><tr>
<td>…</td>
<td>John</td>
<td>1997/03/05</td>
<td>…</td>
<td>…</td>
<td>…</td>
</tr>
<tr>
<td>…</td>
<td>Kim</td>
<td>1998/02/05</td>
<td>…</td>
<td>…</td>
<td>…</td>
</tr>
<tr>
<td>…</td>
<td>Lee</td>
<td>1999/01/05</td>
<td>…</td>
<td>…</td>
<td>…</td>
</tr>
</tbody></table>
<p>FD를 파악할 때는 테이블의 state 즉, 테이블의 데이터를 보고 판단하면 안되는데 위의 데이터만 보면 empl_name과 birth_date 사이에 FD가 존재하는 것처럼 보인다. 하지만 empl_name에는 <strong>동명이인</strong>이 있을 수 있다. 그렇게 되면 <strong>같은 empl_name이지만 다른 birth_date를 가지게 되며</strong> 이는 더 이상 FD가 성립하지 않는 것을 보여준다.</p>
<h2 id="정규화normalization">정규화(Normalization)?</h2>
<p>데이터베이스 정규화는 <strong>데이터베이스 내의 데이터 구조를 조직화하고 최적화하는 과정</strong>이다. 정규화의 목적은 <strong>데이터 중복을 제거</strong>하고, <strong>효율성을 향상</strong>시키며, <strong>데이터 무결성을 보장하</strong>기 위함이다.</p>
<p>이러한 정규화에는 여러 단계가 있는데 보통 3NF 또는 BCNF까지만 정규화를 진행한다.</p>
<p>모든 정규화는 <strong>이전 정규화 단계를 만족</strong>해야 진행 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/quro_97/post/dcc175c3-07a1-43d4-83e8-5fc17478cbeb/image.png" alt=""></p>
<h3 id="제-1-정규화1nf">제 1 정규화(1NF)</h3>
<p> 제 1 정규화는 <strong>테이블의 컬럼이 원자값을 갖도록 테이블을 분해</strong>하는 것이다. 간단히 말해 모든 필드가 단일 값만을 가져야 하며, 중복된 컬럼이나 중첩된 레코드가 없어야 한다.</p>
<h3 id="제-2-정규화2nf">제 2 정규화(2NF)</h3>
<p>제 2 정규화는 <strong>완전 함수 종속을 만족하도록 테이블을 분해하는 것</strong>이다. 예를 들어, 두 개의 컬럼이 결합되어 기본키가 된 composite key라면 비식별자(Non-Key-Attribute)가 두 컬럼 모두에게 종속되어야지 한 컬럼에만 종속되면 안된다.</p>
<h3 id="제-3-정규화3nf">제 3 정규화(3NF)</h3>
<p>제 3 정규화는 <strong>이행 종속성을 제거하여 데이터의 중복을 줄이는 것</strong>이다. 2NF를 만족하고, 비식별자가 다른 비식별자에게 종속되지 않아야 한다.</p>
<h3 id="보이스-코드-정규화bcnf-boyce-codd-normal-form">보이스-코드 정규화(BCNF, Boyce-Codd Normal Form)</h3>
<p>BCNF는 3NF에서 해결되지 않는 <strong>특정 종속성을 제거하여 데이터의 중복을 방지하는 것</strong>이다. 3NF를 만족하고, 모든 결정자가 후보키가 되어야 한다.</p>
<h3 id="제-4-정규화4nf">제 4 정규화(4NF)</h3>
<p>제 4 정규화는 <strong>다치 종속성(Multivalued Dependency)을 제거하는 것</strong>이다. BCNF를 만족하고, 다치 종속성을 갖지 않아야 한다.</p>
<h3 id="제-5-정규화5nf">제 5 정규화(5NF)</h3>
<p>제 5 정규화는 <strong>조인 종속성(Join Dependency)을 제거하여 데이터를 완전히 분리하는 것</strong>이다. 4NF를 만족하고, 조인 종속성을 갖지 않아야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSR & CSR]]></title>
            <link>https://velog.io/@quro_97/SSR-CSR</link>
            <guid>https://velog.io/@quro_97/SSR-CSR</guid>
            <pubDate>Sat, 13 Jul 2024 14:57:41 GMT</pubDate>
            <description><![CDATA[<h2 id="서버-사이드-렌더링server-side-rendering-ssr과-클라이언트-사이드-렌더링client-side-rendering-csr">서버 사이드 렌더링(Server Side Rendering, SSR)과 클라이언트 사이드 렌더링(Client Side Rendering, CSR)</h2>
<p>서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR)은 웹 애플리케이션에서 콘텐츠를 렌더링하는 두 가지 주요 방식이다. 각각의 개념, 특징 및 차이점을 알아보자.</p>
<h3 id="서버-사이드-렌더링-server-side-rendering-ssr">서버 사이드 렌더링 (Server Side Rendering, SSR)</h3>
<h4 id="개념">개념</h4>
<p>SSR은 웹 서버가 HTML 콘텐츠를 생성하여 클라이언트(브라우저)로 전송하는 방식이다. 서버에서 모든 HTML이 준비되어 전송되기 때문에 브라우저는 HTML을 받아 바로 렌더링할 수 있다.</p>
<h4 id="특징">특징</h4>
<ul>
<li><p><strong>빠른 초기 로드 시간</strong> : 서버에서 완성된 HTML을 전송하기 때문에 첫 페이지 로드가 빠르다.</p>
</li>
<li><p><strong>검색 엔진 최적화(SEO)</strong> : 검색 엔진 크롤러가 완전한 HTML을 인덱싱할 수 있어 SEO에 유리하다.</p>
</li>
<li><p><strong>낮은 코드 복잡성</strong> : 클라이언트 측에서 복잡한 자바스크립트 코드가 필요하지 않다.</p>
</li>
</ul>
<h3 id="클라이언트-사이드-렌더링-client-side-rendering-csr">클라이언트 사이드 렌더링 (Client Side Rendering, CSR)</h3>
<h4 id="개념-1">개념</h4>
<p>CSR은 브라우저가 자바스크립트 코드를 다운로드하여 실행하면서 HTML을 동적으로 생성하는 방식이다. 서버는 주로 JSON 데이터를 전송하며, 브라우저는 이를 기반으로 콘텐츠를 렌더링한다.</p>
<h4 id="특징-1">특징</h4>
<ul>
<li><strong>뛰어난 상호작용</strong> : CSR은 사용자와의 상호작용이 많은 애플리케이션에 적합하다. 자바스크립트를 이용해 페이지 일부를 동적으로 갱신할 수 있다.</li>
<li><strong>초기 로드 시간 지연</strong> : 초기에는 자바스크립트 파일을 모두 다운로드하고 실행해야 하기 때문에 첫 페이지 로드가 느릴 수 있다.</li>
<li><strong>불리한 검색 엔진 최적화(SEO)</strong> : 검색 엔진 크롤러는 자바스크립트를 실행하지 못하기 때문에, 초기 로드 시에 HTML이 비어있을 수 있어 SEO에 불리할 수 있다. (이 문제는 최근 서버에서 미리 렌더링하는 방식인 하이브리드 렌더링으로 개선 가능하다.)</li>
</ul>
<h3 id="차이점">차이점</h3>
<h4 id="초기-로드-시간">초기 로드 시간</h4>
<ul>
<li><strong>SSR</strong>: 초기 로드가 빠르며, 서버에서 이미 렌더링된 HTML을 받는다.</li>
<li><strong>CSR</strong>: 자바스크립트를 다운로드하고 실행해야 하므로 초기 로드가 느리다.</li>
</ul>
<h4 id="검색-엔진-최적화seo">검색 엔진 최적화(SEO)</h4>
<ul>
<li><strong>SSR</strong>: 검색 엔진이 완성된 HTML을 인덱싱할 수 있어 SEO에 유리하다.</li>
<li><strong>CSR</strong>: 초기 로드 시 HTML이 비어 있을 수 있어 SEO에 불리하다.</li>
</ul>
<h4 id="사용자-경험">사용자 경험</h4>
<ul>
<li><strong>SSR</strong>: 초기 로드가 빠르지만 페이지 전환 시 서버 요청이 필요해 느릴 수 있다.</li>
<li><strong>CSR</strong>: 초기 로드는 느리지만, 한 번 로드된 이후에는 페이지 전환이 빠르다.</li>
</ul>
<h4 id="복잡성">복잡성</h4>
<ul>
<li><strong>SSR</strong>: 서버 쪽에서 렌더링을 처리하기 때문에 클라이언트 측 코드가 단순하다.</li>
<li><strong>CSR</strong>: 클라이언트 측에서 렌더링을 처리하기 때문에 복잡한 자바스크립트 코드가 필요하다.</li>
</ul>
<h3 id="요약">요약</h3>
<ul>
<li><strong>SSR</strong>: 서버에서 HTML을 렌더링하여 클라이언트에 전송. 초기 로드가 빠르고 SEO에 유리하지만, 상호작용이 적은 웹사이트에 적합하다.</li>
<li><strong>CSR</strong>: 클라이언트에서 자바스크립트를 이용해 HTML을 동적으로 생성. 초기 로드가 느리지만 상호작용이 많고 동적인 웹 애플리케이션에 적합하다.</li>
</ul>
<p>최근에는 두 가지 방식의 장점을 결합한 <strong>하이브리드 렌더링 방식</strong>을 사용하는 경우도 많다. 
예를 들어 Next.js와 같은 프레임워크는 초기에는 SSR을 사용하고, 이후에는 CSR을 사용하는 방식으로 웹 애플리케이션을 최적화한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[메세지 브로커]]></title>
            <link>https://velog.io/@quro_97/%EB%A9%94%EC%84%B8%EC%A7%80-%EB%B8%8C%EB%A1%9C%EC%BB%A4</link>
            <guid>https://velog.io/@quro_97/%EB%A9%94%EC%84%B8%EC%A7%80-%EB%B8%8C%EB%A1%9C%EC%BB%A4</guid>
            <pubDate>Wed, 10 Jul 2024 19:13:47 GMT</pubDate>
            <description><![CDATA[<h2 id="메시지-브로커">메시지 브로커?</h2>
<blockquote>
<p>대용량 데이터 처리를 위한 <strong>미들웨어</strong>(서로 다른 어플리케이션이 서로 통신하는데 사용되는 소프트웨어)이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/quro_97/post/3e8bc52b-dd5b-4eed-a954-baef07ce6d6a/image.png" alt=""></p>
<h3 id="메시지-브로커와-이벤트-브로커의-차이점">메시지 브로커와 이벤트 브로커의 차이점</h3>
<ul>
<li><p><strong>메세지 브로커</strong>: 메시지 브로커는 시스템 간의 메시지 전달을 관리하고 조정하는 소프트웨어다. 메시지를 큐에 저장하고, 메시지를 전송하거나 수신하는 시스템 간의 비동기적 통신을 지원한다. 메시지 브로커는 메시지를 일시적으로 저장하여 수신자가 사용할 준비가 될 때까지 보관한다.</p>
</li>
<li><p><strong>이벤트 브로커</strong>: 이벤트 브로커는 이벤트 기반 아키텍처에서 이벤트를 관리하는 역할을 한다. 이벤트 브로커는 이벤트 소스(프로듀서)로부터 이벤트를 받아 이벤트 핸들러(컨슈머)에게 전달한다. 이벤트 브로커는 주로 이벤트 스트리밍을 지원하며, 실시간으로 데이터를 처리하고 반응하는 데 중점을 둔다.</p>
</li>
</ul>
<h3 id="메시지-브로커-모델의-종류와-특징">메시지 브로커 모델의 종류와 특징</h3>
<ol>
<li><p><strong>포인트 투 포인트(Point-to-Point) 모델</strong></p>
<ul>
<li>메시지 큐를 사용하여 하나의 프로듀서가 하나의 컨슈머에게 메시지를 전송한다.</li>
<li>메시지는 한 번만 전달되며, 한 컨슈머만 해당 메시지를 처리한다.</li>
<li><strong>특징</strong>: 단순하고 확실한 메시지 전달 보장, 큐에 쌓인 메시지는 하나의 컨슈머에 의해 처리됨.</li>
</ul>
</li>
<li><p><strong>발행/구독(Publish/Subscribe) 모델</strong></p>
<ul>
<li>토픽을 사용하여 하나의 프로듀서가 여러 컨슈머에게 메시지를 전송한다.</li>
<li>여러 구독자가 동일한 토픽의 메시지를 수신하고 처리할 수 있다.</li>
<li><strong>특징</strong>: 메시지의 광범위한 전파 가능, 여러 구독자가 동시에 메시지를 처리.</li>
</ul>
</li>
</ol>
<h3 id="메시지-브로커의-동작-개념">메시지 브로커의 동작 개념</h3>
<p>메시지 브로커는 메시지가 적재되는 공간인 메시지 큐(Message Queue)를 통해 작동한다. 메시지의 그룹은 토픽(Topic)으로 묶이며, 각 구독자는 특정 토픽에 맞는 메시지만 전달받는다. 프로듀서와 컨슈머는 메시지를 전송하고 수신하는 역할을 수행하며, 메시지 큐는 이들 간의 버퍼 역할을 한다. 이러한 구조 덕분에 수신자는 자신이 원할 때 메시지를 가져갈 수 있으며, 비동기적으로 메시지를 처리할 수 있다.</p>
<p>기존의 데이터 전달 구조에서는 생산자와 전달자가 직접 연결되어 있어, 구독자가 불안정할 경우 데이터가 손실되는 문제가 있었다. 그러나 메시지 브로커를 사용하면 메시지가 큐에 안전하게 적재되어 구독자가 불안정한 상황에서도 메시지가 보존된다.</p>
<h2 id="메시지-브로커의-대표적인-예--rabbitmq와-kafka">메시지 브로커의 대표적인 예 : RabbitMQ와 Kafka</h2>
<h3 id="rabbitmq">RabbitMQ</h3>
<ul>
<li>AMQP(Advanced Message Queuing Protocol)를 사용하여 메시지를 전송한다.</li>
<li>브로커 기반의 메시징 시스템으로, 메시지를 큐에 저장하고 소비자가 처리할 준비가 되었을 때 전달한다.</li>
<li>복잡한 라우팅 및 메시지 큐 관리 기능을 제공하며, 메시지의 신뢰성과 전달 보장을 강조한다.</li>
<li>메시지 전달 보장이 필수적이다.</li>
<li>메시지 처리 순서가 보장되지 않으며, 메시지가 큐에서 삭제되면 영속성을 보장할 수 없다.</li>
<li>작은 서비스의 메시지 브로커를 구축하는 데 적합하다.</li>
<li>메시지가 성공적으로 전달되었다고 판단하면 큐에서 삭제하기 때문에 이를 다시 재생하기 어렵다.</li>
<li>트래픽이 증가하여 메시지가 증가하면 수평적으로 확장하는 데 어려움이 존재한다.</li>
</ul>
<h3 id="kafka">Kafka</h3>
<ul>
<li>분산 스트리밍 플랫폼으로, 대용량의 실시간 데이터 스트리밍을 처리하는 데 중점을 둔다.</li>
<li>메시지를 토픽에 저장하고, 각 메시지는 오프셋을 통해 순서대로 관리된다.</li>
<li>높은 처리량과 내구성을 제공하며, 이벤트 소싱 및 로그 수집과 같은 시나리오에 적합하다.</li>
<li>메시지 처리 순서가 보장되며, 메시지의 영속성이 보장된다.</li>
<li>스케일 아웃이 중요한 경우에 적합하다.</li>
<li>이벤트 스트리밍 플랫폼으로, 메시지를 토픽에 순서대로 기록하며, 토픽을 유지하기 때문에 오류가 나도 이벤트를 다시 재생할 수 있다.</li>
<li>클러스터로 실행되며, 여러 브로커가 하나의 카프카 서버 내에서 동작한다.</li>
<li>모든 데이터 스트림 처리를 위한 중심 플랫폼 역할을 한다.</li>
</ul>
<h3 id="정리">정리</h3>
<p>RabbitMQ는 큐 기반 메시징 시스템으로 <strong>메시지의 신뢰성과 전달 보장</strong>을 강조하며, <strong>작은 서비스에 적합</strong>하다. Kafka는 <strong>대규모 데이터 스트리밍</strong>과 이벤트 소싱에 최적화되어 있으며, 높은 처리량과 <strong>메시지의 영속성</strong>을 보장하는 데 중점을 둔다. 사용 목적과 요구 사항에 따라 두 시스템 중 하나를 선택할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OSI 7 계층]]></title>
            <link>https://velog.io/@quro_97/OSI-7-%EA%B3%84%EC%B8%B5</link>
            <guid>https://velog.io/@quro_97/OSI-7-%EA%B3%84%EC%B8%B5</guid>
            <pubDate>Tue, 09 Jul 2024 19:08:57 GMT</pubDate>
            <description><![CDATA[<h1 id="osi-7-layer">OSI 7 Layer?</h1>
<blockquote>
<p>OSI 7 계층은 네트워크에서 통신이 일어나는 과정을 7단계로 나눈 것을 말한다. 
계층을 나눈 이유는 통신이 일어나는 과정이 단계별로 파악할 수 있기 때문이다.
흐름을 한눈에 알아보기 쉽고, 사람들이 이해하기 쉽고,
7단계 중 특정한 곳에 이상이 생기면 다른 단계의 장비 및 소프트웨어를 건들이지 않고도 
이상이 생긴 단계만 고칠 수 있기 때문이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/quro_97/post/82992a75-85ff-4b3a-b53f-bbd4ef758bb8/image.PNG" alt=""></p>
<h2 id="1-물리-계층-physical-layer">1. 물리 계층 (Physical Layer)</h2>
<p>물리 계층은 네트워크의 가장 하위 계층으로, 실제 물리적 매체를 통해 데이터를 전송하는 역할을 한다. 이 계층에서는 전기 신호, 라디오 신호, 광 신호 등 다양한 형태의 신호를 사용하여 데이터를 전송한다. 주요 역할은 다음과 같다.</p>
<ul>
<li>데이터 전송 매체의 종류 결정 (유선, 무선 등)</li>
<li>신호 변환 (디지털 -&gt; 아날로그, 아날로그 -&gt; 디지털)</li>
<li>전기적 특성 규정 (전압, 주파수 등)</li>
</ul>
<h2 id="2-데이터-링크-계층-data-link-layer">2. 데이터 링크 계층 (Data Link Layer)</h2>
<p>데이터 링크 계층은 물리 계층을 통해 전달된 데이터를 에러 없이 전달하고, 흐름을 제어하는 역할을 한다. 이 계층은 데이터를 프레임(Frame) 단위로 나누어 전송하며, 주로 다음과 같은 역할을 담당한다.</p>
<ul>
<li>프레임 생성 및 해석</li>
<li>MAC 주소를 사용한 물리적 주소 지정</li>
<li>오류 검출 및 수정 (CRC 등)</li>
</ul>
<h2 id="3-네트워크-계층-network-layer">3. 네트워크 계층 (Network Layer)</h2>
<p>네트워크 계층은 데이터가 다양한 네트워크를 통해 목적지까지 전달될 수 있도록 경로를 설정하는 역할을 한다. 이 계층에서 IP 주소를 사용하여 논리적 주소 지정을 한다. 주요 역할은 다음과 같다.</p>
<ul>
<li>경로 설정 및 최적화 (라우팅)</li>
<li>논리적 주소 지정 (IP 주소)</li>
<li>패킷 전달 및 중계</li>
</ul>
<h2 id="4-전송-계층-transport-layer">4. 전송 계층 (Transport Layer)</h2>
<p>전송 계층은 송신 측과 수신 측 간의 데이터 전송을 관리하고, 신뢰성 있는 데이터 전송을 보장한다. 이 계층에서는 주로 TCP와 UDP 프로토콜을 사용하여 데이터를 세그먼트(Segment) 단위로 전송한다. 주요 역할은 다음과 같다.</p>
<ul>
<li>데이터 전송의 신뢰성 확보 (에러 검출, 재전송 등)</li>
<li>데이터 흐름 제어</li>
<li>포트 번호를 사용한 통신 세션 관리</li>
</ul>
<h2 id="5-세션-계층-session-layer">5. 세션 계층 (Session Layer)</h2>
<p>세션 계층은 통신 세션을 설정하고 관리하며, 종료하는 역할을 한다. 이 계층은 응용 프로그램 간의 대화(Dialogue)를 관리하고, 세션의 유지 및 동기화를 담당한다. 주요 역할은 다음과 같다.</p>
<ul>
<li>세션 설정, 유지 및 종료</li>
<li>대화 제어 (반이중, 전이중 통신)</li>
<li>세션 복구 및 재동기화</li>
</ul>
<h2 id="6-표현-계층-presentation-layer">6. 표현 계층 (Presentation Layer)</h2>
<p>표현 계층은 데이터를 네트워크에 적합한 형태로 변환하고, 수신 측에서 이해할 수 있는 형태로 변환하는 역할을 한다. 이 계층에서는 데이터 암호화, 압축, 변환 등의 작업을 수행한다. 주요 역할은 다음과 같다.</p>
<ul>
<li>데이터 형식 변환 (예: JPEG -&gt; BMP)</li>
<li>데이터 암호화 및 복호화</li>
<li>데이터 압축 및 해제</li>
</ul>
<h2 id="7-응용-계층-application-layer">7. 응용 계층 (Application Layer)</h2>
<p>응용 계층은 사용자와 직접 상호작용하는 응용 프로그램들이 네트워크 서비스를 사용할 수 있도록 한다. 이 계층은 다양한 네트워크 서비스를 제공하며, 주로 다음과 같은 역할을 담당한다.</p>
<ul>
<li>네트워크 프로토콜 정의 (HTTP, FTP, SMTP 등)</li>
<li>사용자 인터페이스 제공</li>
<li>응용 프로그램 간 데이터 교환</li>
</ul>
<h2 id="osi-7계층의-동작-흐름">OSI 7계층의 동작 흐름</h2>
<p>데이터가 송신 측에서 수신 측까지 전달되는 과정에서 OSI 7계층은 중요한 역할을 한다. 각 계층은 서로 다른 기능을 수행하며, 상호작용을 통해 데이터가 안전하고 정확하게 전달될 수 있도록 돕는다. 데이터가 송신 측에서 수신 측으로 이동할 때의 흐름은 다음과 같다.</p>
<ol>
<li><strong>응용 계층 (Application Layer)</strong>: 사용자가 응용 프로그램을 통해 데이터를 입력하면, 이 데이터는 응용 계층에서 시작된다.</li>
<li><strong>표현 계층 (Presentation Layer)</strong>: 응용 계층에서 받은 데이터를 적절한 형식으로 변환한다. 예를 들어, 데이터 암호화나 압축 작업이 이뤄진다.</li>
<li><strong>세션 계층 (Session Layer)</strong>: 변환된 데이터를 세션 계층에서 관리하며, 통신 세션을 설정하고 유지한다.</li>
<li><strong>전송 계층 (Transport Layer)</strong>: 세션 계층에서 받은 데이터를 세그먼트로 나누고, 에러 검출 및 수정, 데이터 흐름 제어 등의 작업을 수행한다.</li>
<li><strong>네트워크 계층 (Network Layer)</strong>: 전송 계층에서 받은 세그먼트를 패킷으로 변환하고, 목적지까지의 최적 경로를 설정한다.</li>
<li><strong>데이터 링크 계층 (Data Link Layer)</strong>: 네트워크 계층에서 받은 패킷을 프레임으로 나누고, 오류 검출 및 수정을 수행하며, 물리적 주소를 지정한다.</li>
<li><strong>물리 계층 (Physical Layer)</strong>: 데이터 링크 계층에서 받은 프레임을 물리적 신호로 변환하여 실제 전송 매체를 통해 데이터를 전송한다.</li>
</ol>
<p>데이터가 수신 측에 도달하면, 물리 계층에서부터 다시 상위 계층으로 전달되며, 각 계층에서 해당 역할을 수행하여 데이터를 처리한다. 최종적으로 응용 계층에서 사용자가 이해할 수 있는 형태로 데이터를 제공한다. 이러한 과정을 통해 OSI 7계층 모델은 데이터 통신의 복잡성을 관리하고, 네트워크의 효율성을 높인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP와 HTTPS 비교]]></title>
            <link>https://velog.io/@quro_97/HTTP%EC%99%80-HTTPS-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@quro_97/HTTP%EC%99%80-HTTPS-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Sun, 07 Jul 2024 19:08:59 GMT</pubDate>
            <description><![CDATA[<h2 id="http와-https의-차이점">HTTP와 HTTPS의 차이점</h2>
<blockquote>
<p><strong>HTTP(Hypertext Transfer Protocol)</strong>는 클라이언트와 서버 간 통신 규칙 즉, 프로토콜이다.
사용자가 웹 사이트를 방문하면 브라우저가 웹 서버에 HTTP 요청을 전송하고 웹 서버는 HTTP 응답으로 요청에 대해 응답한다. 간단히 말해 HTTP 프로토콜은 네트워크 통신을 작동하게 하는 기술이다.
<strong>HTTPS(Hypertext Transfer Protocol Secure)</strong>는 이름에서도 알 수 있듯이 HTTP의 확장 버전 또는 안전한 버전이다. HTTPS에서는 브라우저와 서버가 데이터를 전송하기 전에 안전하고 암호화된 연결을 설정한다.</p>
</blockquote>
<h3 id="1-보안">1. 보안</h3>
<p><strong>HTTP</strong>: 데이터를 일반 텍스트로 전송, 중간에서 데이터를 가로챌 수 있음
<strong>HTTPS</strong>: 데이터를 암호화하여 전송, 중간에서 데이터를 가로채더라도 해독이 어려움</p>
<h3 id="2-포트-번호">2. 포트 번호</h3>
<p><strong>HTTP</strong>: 기본적으로 포트 80 사용
<strong>HTTPS</strong>: 기본적으로 포트 443 사용</p>
<h3 id="3-ssltls-인증서">3. SSL/TLS 인증서</h3>
<p><strong>HTTP</strong>: 인증서 필요 없음
<strong>HTTPS</strong>: 독립된 인증 기관(CA)에서 발급받은 SSL/TLS 인증서 필요</p>
<h3 id="3-브라우저-표시">3. 브라우저 표시</h3>
<p><strong>HTTP</strong>: URL 옆에 특별한 보안 표시 없음
<strong>HTTPS</strong>: URL 옆에 자물쇠 아이콘 표시, 사용자는 보안 연결임을 알 수 있음</p>
<h3 id="4-seo-및-신뢰성">4. SEO 및 신뢰성</h3>
<p><strong>HTTP</strong>: 검색 엔진 순위에서 낮게 평가, 사용자 신뢰도 낮음
<strong>HTTPS</strong>: 검색 엔진 순위에서 높게 평가, 사용자 신뢰도 높음</p>
<h2 id="http의-동작-방식">HTTP의 동작 방식</h2>
<ul>
<li><p><strong>클라이언트 요청</strong>: 사용자가 웹 사이트를 방문하면 브라우저가 웹 서버에 HTTP 요청을 전송</p>
</li>
<li><p><strong>서버 응답</strong>: 웹 서버는 요청에 대한 HTTP 응답을 전송</p>
</li>
<li><p><strong>데이터 교환</strong>: 데이터는 일반 텍스트로 전송됨</p>
</li>
<li><p><strong>요청 유형(Request Methods)</strong></p>
<ul>
<li><strong>GET</strong><ul>
<li>리소스(데이터)를 받기 위함</li>
<li>URL(URI) 형식으로 서버 측에 리소스를 요청</li>
</ul>
</li>
<li><strong>HEAD</strong><ul>
<li>메세지 헤더 정보를 받기 위함</li>
<li>GET과 유사하지만, HEAD는 실제 문서 요청이 아닌 문서에 대한 정보 요청</li>
<li>Response 메세지를 받았을 때, Body는 비어있고, Header 정보만 존재</li>
</ul>
</li>
<li><strong>POST</strong><ul>
<li>내용 및 파일 전송을 하기 위함</li>
<li>클라이언트에서 서버로 어떤 정보를 제출하기 위해 사용</li>
<li>Request 데이터를 HTTP Body에 담아 웹 서버로 전송</li>
</ul>
</li>
<li><strong>PUT</strong><ul>
<li>리소스(데이터)를 갱신하기 위함</li>
<li>POST와 유사하나, 기존 데이터를 갱신할 때 사용</li>
</ul>
</li>
<li><strong>DELETE</strong><ul>
<li>웹 서버측에 요청한 리소스를 삭제할 때 사용</li>
</ul>
</li>
<li><strong>CONNECT</strong><ul>
<li>클라이언트와 서버 사이의 중간 경유를 위함</li>
<li>보통 Proxy를 통해 SSL 통신을 하고자할 때 사용</li>
</ul>
</li>
<li><strong>OPTIONS</strong><ul>
<li>웹 서버 측에서 지원하고 있는 메소드가 무엇인지 알기 위해 사용</li>
</ul>
</li>
<li><strong>PATCH</strong><ul>
<li>PUT과 유사하나, 모든 데이터를 갱신하는 것이 아닌 리소스의 일부분만 수정할 때 사용</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>응답 코드(Status Code)</strong></p>
<ul>
<li><p><strong>10x</strong> : 정보 확인</p>
</li>
<li><p><strong>20x</strong> : 통신 성공</p>
</li>
<li><p><strong>30x</strong> : 리다이렉트</p>
</li>
<li><p><strong>40x</strong> : 클라이언트 오류</p>
</li>
<li><p><strong>50x</strong> : 서버 오류</p>
<h2 id="https의-동작-방식">HTTPS의 동작 방식</h2>
</li>
</ul>
</li>
<li><p><strong>클라이언트 요청</strong>: 사용자가 https:// URL을 입력하여 웹 사이트를 방문</p>
</li>
<li><p><strong>SSL 인증서 요청</strong>: 브라우저가 서버의 SSL 인증서를 요청</p>
</li>
<li><p><strong>서버 응답</strong>: 서버가 퍼블릭 키가 포함된 SSL 인증서를 전송</p>
</li>
<li><p><strong>인증서 검증</strong>: 브라우저가 인증서를 검증하고, 퍼블릭 키를 사용하여 비밀 세션 키가 포함된 메시지를 암호화하여 전송</p>
</li>
<li><p><strong>세션 키 교환</strong>: 서버가 개인 키로 메시지를 해독하고 세션 키를 생성, 세션 키를 암호화하여 브라우저에 전송</p>
</li>
<li><p><strong>보안 통신</strong>: 브라우저와 서버가 동일한 세션 키를 사용하여 암호화된 데이터 교환</p>
<h2 id="http2-http3-https의-차이점">HTTP/2, HTTP/3, HTTPS의 차이점</h2>
</li>
<li><p><strong>HTTP/1.1</strong>: 최초의 HTTP 버전, 텍스트 형식으로 데이터 전송</p>
</li>
<li><p><strong>HTTP/2</strong>: 바이너리 형식으로 데이터 전송, 서버 푸시 기능 추가</p>
</li>
<li><p><strong>HTTP/3</strong>: 실시간 스트리밍 및 최신 데이터 전송 요구 사항을 더 효율적으로 지원</p>
<h2 id="https를-선택하는-이유">HTTPS를 선택하는 이유</h2>
</li>
<li><p><strong>보안 강화</strong>: 모든 데이터가 암호화되어 전송, 민감한 정보 보호</p>
</li>
<li><p><strong>신뢰성 증대</strong>: 검색 엔진 순위에서 우선, 사용자 신뢰도 높음</p>
</li>
<li><p><strong>성능 향상</strong>: HTTPS 웹 애플리케이션이 더 빠르게 로드</p>
</li>
<li><p><strong>정확한 분석</strong>: 추천 트래픽을 정확하게 추적 가능</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis]]></title>
            <link>https://velog.io/@quro_97/Redis</link>
            <guid>https://velog.io/@quro_97/Redis</guid>
            <pubDate>Sat, 06 Jul 2024 18:34:11 GMT</pubDate>
            <description><![CDATA[<h2 id="redis의-기본-개념"><strong>Redis의 기본 개념</strong></h2>
<blockquote>
<p>Redis는 <strong>Remote Dictionary Server</strong>의 약자로, 인메모리 데이터 저장소로서 데이터를 디스크가 아닌 메모리에 저장하여 빠른 읽기 및 쓰기를 제공한다. 다양한 데이터 구조를 지원하며, 주로 캐시, 세션 저장소, 메시지 브로커 등으로 사용된다.</p>
</blockquote>
<h3 id="redis-특징"><strong>Redis 특징</strong></h3>
<ul>
<li><strong>Key-Value 타입의 인메모리 데이터를 저장, 관리하기 위한 오픈 소스 기반의 NoSQL</strong><ul>
<li><strong>인메모리 (In-Memory Database)</strong><ul>
<li>디스크가 아닌 메모리에 모든 데이터를 보유하는 DB.</li>
<li>디스크 검색보다 자료 접근이 훨씬 빠름.</li>
<li>But, 메모리라 휘발성.</li>
</ul>
</li>
<li>In-Memory 기반이라 저장 공간 제약</li>
<li>휘발성이 특징인 메모리와 다르게 <strong>영속성 보장</strong></li>
</ul>
</li>
<li><strong>DB, Cache, Message Queue, Shared Memory 용도로 사용됨</strong></li>
<li><strong>쓰기 성능 증대를 위한 클라이언트 측 샤딩(sharding) 지원</strong><ul>
<li><strong>샤딩 (sharding)</strong><ul>
<li>데이터를 조각내 분산 저장하는 데이터 처리 기법.</li>
<li>일괄적 관리가 힘든 거대 데이터베이스나 네트워크를 작게 나눠서 저장 및 관리.</li>
<li>샤딩을 통해 데이터를 분산 저장하면 노드의 무게를 줄여 데이터 처리 속도 향상.</li>
</ul>
</li>
</ul>
</li>
<li><strong>다양한 데이터형 지원</strong><ul>
<li><strong>문자열, 리스트, 해시, 셋, 정렬된 셋</strong>과 같은 다양한 데이터 구조를 지원</li>
</ul>
</li>
</ul>
<h1 id=""></h1>
<h3 id="어떻게-레디스는-영속성을-보장하나">어떻게 레디스는 영속성을 보장하나?</h3>
<p><strong>AOF, RDB 방식</strong></p>
<p>➜ 인메모리 데이터 저장소가 가지는 휘발성의 특성으로 데이터가 유실될 경우를 방지하여 백업 기능을 제공</p>
<blockquote>
<p>AOF (Append On File) 방식
➜ Redis의 모든 write/update 연산 자체를 모두 log 파일에 기록하는 형태</p>
</blockquote>
<blockquote>
<p>RDB(Snapshotting) 방식
➜ 순간적으로 메모리에 있는 내용 전체를 디스크에 담아 영구 저장하는 방식</p>
</blockquote>
<hr>
<h2 id="spring-boot에서의-redis-사용"><strong>Spring Boot에서의 Redis 사용</strong></h2>
<h3 id="인메모리-저장소로서의-redis"><strong>인메모리 저장소로서의 Redis</strong></h3>
<p>Spring Boot에서 Redis는 캐시 저장소로 자주 사용된다. 캐싱은 데이터베이스의 부하를 줄이고 응답 시간을 줄이는 데 매우 유용하다.</p>
<pre><code class="language-java">@Repository
public class RedisRepository {

    private final RedisTemplate&lt;String, Object&gt; redisTemplate;

    public RedisRepository(RedisTemplate&lt;String, Object&gt; redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void save(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    public Object find(String key) {
        return redisTemplate.opsForValue().get(key);
    }
}</code></pre>
<p>위 코드에서 @Cacheable 애노테이션은 exampleCache라는 이름의 캐시에 데이터를 저장한다. 이후 같은 키로 요청이 들어오면 캐시에서 데이터를 조회하게 된다.</p>
<h3 id="메시지-브로커로서의-redis-사용">메시지 브로커로서의 Redis 사용</h3>
<p>Redis는 메시지 브로커로서도 사용할 수 있다. 이는 RabbitMQ나 Kafka 같은 고도화된 메시징 시스템과는 다르지만, Redis의 인메모리 특성을 활용해 단순하고 빠른 pub/sub 기능을 제공한다</p>
<pre><code class="language-java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.stereotype.Service;

@Service
public class RedisMessagePublisher {
    @Autowired
    private RedisTemplate&lt;String, Object&gt; redisTemplate;

    @Autowired
    private ChannelTopic topic;

    public void publish(String message) {
        redisTemplate.convertAndSend(topic.getTopic(), message);
    }
}</code></pre>
<p>위 코드에서는 RedisTemplate을 이용하여 메시지를 특정 토픽에 발행(publish)하는 방법을 보여준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CORS 정책]]></title>
            <link>https://velog.io/@quro_97/CORS-%EC%A0%95%EC%B1%85</link>
            <guid>https://velog.io/@quro_97/CORS-%EC%A0%95%EC%B1%85</guid>
            <pubDate>Mon, 01 Jul 2024 18:31:29 GMT</pubDate>
            <description><![CDATA[<h2 id="cors-정책">CORS 정책?</h2>
<blockquote>
<p>도메인이 다른 서버끼리 리소스를 주고 받을 때 보안을 위해 설정된 정책인 CORS(Cross-Origin Resource Sharing)</p>
</blockquote>
<p>CORS는 웹 브라우저가 하나의 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 자원에 접근할 수 있는 권한을 부여하도록 하는 메커니즘이다. 웹 사이트가 다른 도메인에서 리소스를 요청할 때 보안을 유지하면서 접근할 수 있도록 도와준다.</p>
<p>예를들어 웹 사이트 A가 API 서버 B에서 데이터를 가져오려 할 때, API 서버 B에서 CORS 허용 설정이 되어 있지 않으면 웹 브라우저에서 API 접근이 거부될 수 있다. 이는 보안을 위해 웹 브라우저에서 자동으로 차단하는 것.</p>
<p><em>ex) React 서버(3000 포트) → Springboot 서버(8080 포트) 리소스를 주고받으려 할 때 CORS 위반 에러가 발생.</em></p>
<h3 id="origin-출처란-무엇인가">Origin (출처)란 무엇인가?</h3>
<p>CORS를 이해하려면 우선 &quot;출처&quot;에 대해 알아야 한다.</p>
<ul>
<li>SOP (Same Origin Policy): 동일한 Origin만 리소스(데이터)를 공유할 수 있는 정책.</li>
<li>Origin: Protocol, Host, Port 번호까지 모두 합친 것.<ul>
<li>same-origin: 위의 3가지가 모두 같으면 동일 출처.</li>
<li>cross-origin: 위의 3가지 중 하나라도 다르면 다른 출처.</li>
</ul>
</li>
</ul>
<h3 id="cors-설정-방법">CORS 설정 방법</h3>
<blockquote>
<p>서버에서 CORS를 허용하려면 몇 가지 설정이 필요하다.
예를 들어, Springboot 서버에서는 다음과 같이 설정할 수 있다.</p>
</blockquote>
<pre><code>import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping(&quot;/**&quot;)
                .allowedOrigins(&quot;http://localhost:3000&quot;)
                        .allowedMethods(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;)
                        .allowedHeaders(&quot;content-type&quot;, &quot;authorization&quot;, &quot;x-requested-with&quot;)
                        .allowCredentials(true);
    }
}</code></pre><h4 id="registryaddmapping">registry.addMapping(&quot;/**&quot;)</h4>
<ul>
<li>모든 경로에 대해 CORS 정책을 적용한다는 의미이다. &quot;/**&quot;는 애플리케이션의 모든 URL 경로를 의미한다.</li>
</ul>
<h4 id="allowedoriginshttplocalhost3000">allowedOrigins(&quot;<a href="http://localhost:3000&quot;">http://localhost:3000&quot;</a>)</h4>
<ul>
<li><a href="http://localhost:3000">http://localhost:3000</a> 출처에서 오는 요청을 허용한다.</li>
</ul>
<h4 id="allowedmethodsget-post-put-delete-options">allowedMethods(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;)</h4>
<ul>
<li>GET, POST, PUT, DELETE, OPTIONS 메서드를 사용할 수 있도록 허용한다.</li>
</ul>
<h4 id="allowedheaderscontent-type-authorization-x-requested-with">allowedHeaders(&quot;content-type&quot;, &quot;authorization&quot;, &quot;x-requested-with&quot;)</h4>
<ul>
<li>content-type, authorization, x-requested-with 헤더를 사용할 수 있도록 허용한다.</li>
</ul>
<h4 id="allowcredentialstrue">allowCredentials(true)</h4>
<ul>
<li>자격 증명(쿠키, 인증 정보 등)을 요청에 포함할 수 있도록 허용한다. 이는 보안 관련 설정으로, 클라이언트가 자격 증명을 포함한 요청을 서버에 보낼 수 있도록 한다</li>
<li>allowCredentials(true)로 설정하면 어떠한 허용에도 (&quot;*&quot;)는 사용이 불가능하다.<ul>
<li><del>여기서 좀 고생했다.</del></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenVidu]]></title>
            <link>https://velog.io/@quro_97/OpenVidu</link>
            <guid>https://velog.io/@quro_97/OpenVidu</guid>
            <pubDate>Sun, 30 Jun 2024 16:46:41 GMT</pubDate>
            <description><![CDATA[<h2 id="openvidu-튜토리얼">OpenVidu 튜토리얼</h2>
<h3 id="백엔드-환경-구성">백엔드 환경 구성</h3>
<ul>
<li><p>Docker 설치 후 실행</p>
</li>
<li><p>Ubuntu 설치</p>
</li>
<li><p>Docker Desktop 설정 확인</p>
<ol>
<li>Docker Desktop을 열고 오른쪽 상단의 톱니바퀴(설정) 아이콘을 클릭</li>
<li><strong>Settings</strong> -&gt; <strong>Resources</strong> -&gt; <strong>WSL Integration</strong>으로 이동</li>
<li>사용 중인 Ubuntu 배포판이 목록에 있고, 해당 항목이 체크되어 있는지 확인</li>
</ol>
</li>
</ul>
<h3 id="ubuntu-접속-후-다음-명령어-입력">Ubuntu 접속 후 다음 명령어 입력:</h3>
<pre><code class="language-bash">sudo su (비밀번호 입력)
cd /opt
curl https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/install_openvidu_latest.sh | bash</code></pre>
<p><img src="https://velog.velcdn.com/images/quro_97/post/9ac5e712-e5bb-4e51-baa1-4af8b557bc7b/image.png" alt=""></p>
<h3 id="다음-명령어로-openvidu-설치">다음 명령어로 OpenVidu 설치:</h3>
<ol>
<li><p>OpenVidu 폴더로 이동</p>
<pre><code class="language-bash"> cd openvidu</code></pre>
</li>
<li><p>.env 파일 설정</p>
<pre><code class="language-bash"> nano .env</code></pre>
<ul>
<li><p>이 명령어를 사용하여 ‘.env’ 파일을 연다</p>
</li>
<li><p>여기서 ‘DOMAIN_OR_PUBLIC_IP’ 와 ‘OPENVIDU_SECRET’ 값을 설정해준다 (안해도 실행은 가능)</p>
<pre><code class="language-bash">DOMAIN_OR_PUBLIC_IP=your.domain.com
OPENVIDU_SECRET=your_secret_password</code></pre>
</li>
</ul>
</li>
<li><p>OpenVidu 시작</p>
<pre><code class="language-bash"> ./openvidu start</code></pre>
</li>
</ol>
<p>추가. Docker 컨테이너 확인</p>
<ul>
<li>OpenVidu가 실행 중인 Docker 컨테이너들을 확인하려면 다음 명령어를 사용</li>
</ul>
<pre><code class="language-bash">docker ps</code></pre>
<p><img src="https://velog.velcdn.com/images/quro_97/post/272ae706-01ed-4d30-8004-f8f78a91b26a/image.png" alt=""></p>
<p>도커에서 컨테이너가 만들어져 돌아가고 있는 모습을 볼 수 있다</p>
<h2 id="프론트-환경-구성">프론트 환경 구성</h2>
<ul>
<li><p>원하는 폴더에 clone 받는다</p>
<pre><code class="language-bash">  git clone https://github.com/OpenVidu/openvidu-tutorials.git -b v2.19.0</code></pre>
</li>
<li><p>VS Code로 openvidu-tutorials\openvidu-insecure-vue 폴더 열기</p>
</li>
<li><p>터미널 창에</p>
<ul>
<li><p>npm install</p>
</li>
<li><p>npm run serve</p>
<ul>
<li><p><strong>여기서 서버 안켜지고 오류 발생시 !!</strong></p>
<ul>
<li><p>package.json 수정</p>
<pre><code class="language-json">&quot;scripts&quot;: {
&quot;serve&quot;: &quot;export NODE_OPTIONS=--openssl-legacy-provider &amp;&amp; vue-cli-service serve&quot;
}</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/quro_97/post/e571f534-9458-4a5d-8c60-de6821b967e1/image.png" alt="">
<em>터미널에 이런 내용이 나오면 성공!</em></p>
<p><a href="http://localhost:8080">localhost:8080</a> 으로 접속하면, 잘 작동한다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WebRTC]]></title>
            <link>https://velog.io/@quro_97/WebRTC</link>
            <guid>https://velog.io/@quro_97/WebRTC</guid>
            <pubDate>Sat, 29 Jun 2024 16:29:21 GMT</pubDate>
            <description><![CDATA[<h1 id="webrtc-">WebRTC ?</h1>
<blockquote>
<p>💡 <strong>Web Real-Time Communication</strong>은 웹 애플리케이션과 사이트가 중간자 없이 브라우저 간에 오디오나 영상 미디어를 포착하고 마음대로 스트림할 뿐 아니라, 임의의 데이터도 교환할 수 있도록 하는 기술입니다.</p>
</blockquote>
<h2 id="서버의-종류">서버의 종류</h2>
<p><img src="https://velog.velcdn.com/images/quro_97/post/1678a110-2436-4a97-b66e-78d294e96d1b/image.png" alt=""></p>
<h3 id="1-signaling-서버-위-그림의-mesh---p2p">1. Signaling 서버 (위 그림의 Mesh - P2P)</h3>
<ul>
<li><p><strong>역할</strong></p>
<ul>
<li>두 클라이언트 간에 <strong>P2P 연결을 설정</strong>하기 위한 초기 통신을 담당합니다.</li>
<li>Signaling 서버 자체는 <strong>미디어 데이터(비디오, 오디오)를 전달하지 않습니다</strong>.</li>
<li>연결 설정 후, 실제 미디어 데이터는 클라이언트 간의 <strong>P2P 연결을 통해</strong> 직접 전달됩니다.</li>
</ul>
</li>
<li><p><strong>특징</strong></p>
<ul>
<li><strong>Peer</strong> 간의 <strong>Offer, Answer의 session 정보 신호</strong>만을 중계합니다.</li>
<li>초기 연결 설정 시에만 서버에 부하가 발생하고, <strong>Peer</strong> 간 연결이 완료된 후에는 서버에 별도의 부하가 없습니다.</li>
<li><strong>1:1</strong> 연결에 적합합니다.</li>
</ul>
</li>
<li><p><strong>장점</strong></p>
<ul>
<li><strong>서버의 부하</strong>가 적습니다.</li>
<li>Peer 간의 직접 연결로 데이터를 송수신하기 때문에 <strong>실시간성을 보장</strong>합니다.</li>
</ul>
</li>
<li><p><strong>단점</strong></p>
<ul>
<li><strong>N:N</strong> 혹은 <strong>N:M</strong> 연결에서 <strong>클라이언트의 과부하</strong>가 급격하게 증가합니다.</li>
</ul>
</li>
</ul>
<h3 id="2-sfu-selective-forwarding-unit-서버">2. SFU (Selective Forwarding Unit) 서버</h3>
<ul>
<li><p><strong>역할</strong></p>
<ul>
<li>P2P와 달리 <strong>서버</strong>를 이용하여 <strong>클라이언트의 미디어 스트림을 중계</strong>합니다.</li>
</ul>
</li>
<li><p><strong>특징</strong></p>
<ul>
<li>종단 간 미디어 트래픽을 중계하는 <strong>중앙 서버 방식</strong>입니다.</li>
<li>클라이언트 Peer 간 연결이 아닌, <strong>서버와 클라이언트 간의 Peer</strong>를 연결합니다.</li>
<li>클라이언트는 연결된 모든 사용자에게 데이터를 보낼 필요 없이 <strong>서버에게만 자신의 영상 데이터를 보냅니다</strong>.</li>
<li><strong>1:N</strong> 혹은 소규모 <strong>N:M</strong> 형식의 <strong>실시간 스트리밍에 적합</strong>합니다.</li>
</ul>
</li>
<li><p><strong>장점</strong></p>
<ul>
<li>P2P 방식만큼은 아니지만 <strong>준수한 실시간성</strong>을 유지할 수 있습니다.</li>
<li>Signaling 서버를 사용하는 것보다 <strong>클라이언트가 받는 부하가 줄어듭니다</strong> (클라이언트가 많을 경우).</li>
</ul>
</li>
<li><p><strong>단점</strong></p>
<ul>
<li>Signaling 서버보다 <strong>서버 비용이 증가</strong>합니다.</li>
<li><strong>대규모 N:M</strong> 구조에서는 여전히 클라이언트가 많은 부하를 감당해야 합니다.</li>
</ul>
</li>
</ul>
<h3 id="3-mcu-multi-point-control-unit-서버">3. MCU (Multi-point Control Unit) 서버</h3>
<ul>
<li><p><strong>특징</strong></p>
<ul>
<li><strong>SFU와 마찬가지로</strong> 서버가 중간에서 미디어 트래픽을 중계합니다.</li>
<li>다수의 송출 미디어를 중앙 서버에서 <strong>혼합 또는 가공하여 수신측으로 전달</strong>합니다.</li>
<li>Uplink와 Downlink <strong>둘 다 1개</strong>만 사용합니다.</li>
<li><strong>중앙 서버의 높은 컴퓨팅 파워</strong>가 요구됩니다.</li>
</ul>
</li>
<li><p><strong>장점</strong></p>
<ul>
<li><strong>클라이언트의 부하</strong>가 현저히 줄어듭니다.</li>
</ul>
</li>
<li><p><strong>단점</strong></p>
<ul>
<li>WebRTC의 최대 장점인 <strong>실시간성이 저해</strong>됩니다.</li>
<li>실시간성이 중요한 게임 등에서는 <strong>MCU는 부적합</strong>합니다.</li>
</ul>
</li>
</ul>
<h2 id="세-가지-주요-api">세 가지 주요 API</h2>
<h3 id="1-mediastream-getusermedia-api">1. MediaStream (GetUserMedia API)</h3>
<ul>
<li><p>사용자의 카메라와 마이크에 접근하여 오디오와 비디오 스트림을 캡쳐합니다.</p>
</li>
<li><p><code>navigator.mediaDevices.getUserMedia()</code>를 사용하여 접근 가능합니다.</p>
</li>
<li><p><strong>예시</strong></p>
<pre><code class="language-jsx">  navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then(stream =&gt; {
      // 스트림을 사용합니다
      videoElement.srcObject = stream;
    })
    .catch(error =&gt; {
      console.error(&quot;Error accessing media devices.&quot;, error);
    });</code></pre>
</li>
</ul>
<h3 id="2-rtcpeerconnection">2. RTCPeerConnection</h3>
<ul>
<li><p>두 피어 간의 오디오, 비디오, 데이터 스트림을 전송하는 데 사용됩니다.</p>
</li>
<li><p>ICE (Interactive Connectivity Establishment) 프로토콜을 통해 피어 간의 연결을 설정하고 유지합니다.</p>
</li>
<li><p>보안, 대역폭 관리, 네트워크 최적화 등을 처리합니다.</p>
</li>
<li><p><strong>예시</strong></p>
<pre><code class="language-jsx">  const peerConnection = new RTCPeerConnection(configuration);

  // 로컬 미디어 스트림을 연결에 추가
  stream.getTracks().forEach(track =&gt; peerConnection.addTrack(track, stream));

  // ICE 후보 수신
  peerConnection.onicecandidate = event =&gt; {
    if (event.candidate) {
      sendCandidateToRemotePeer(event.candidate);
    }
  };

  // 원격 스트림 수신
  peerConnection.ontrack = event =&gt; {
    remoteVideoElement.srcObject = event.streams[0];
  };</code></pre>
</li>
</ul>
<h3 id="3-rtcdatachannel">3. RTCDataChannel</h3>
<ul>
<li><p>피어 간에 임의의 데이터를 전송하는 데 사용됩니다.</p>
</li>
<li><p>게임, 파일 공유, 채팅 어플리케이션 등에서 활용됩니다.</p>
</li>
<li><p><strong>예시</strong></p>
<pre><code class="language-jsx">  const dataChannel = peerConnection.createDataChannel(&quot;myDataChannel&quot;);

  dataChannel.onopen = () =&gt; {
    console.log(&quot;Data channel is open&quot;);
  };

  dataChannel.onmessage = event =&gt; {
    console.log(&quot;Received message:&quot;, event.data);
  };

  dataChannel.send(&quot;Hello, World!&quot;);</code></pre>
</li>
</ul>
<h2 id="webrtc-작동원리">WebRTC 작동원리</h2>
<h3 id="1-신호-교환">1. 신호 교환</h3>
<ul>
<li>WebRTC 자체에는 신호 교환 방법이 정의되어 있지 않습니다.</li>
<li>WebRTC를 사용하려면 시그널링 서버가 필요합니다.</li>
</ul>
<h3 id="2-p2p-연결-설정">2. P2P 연결 설정</h3>
<ul>
<li>두 클라이언트는 시그널링 서버를 통해 초기 통신을 시작합니다.</li>
<li>Offer와 Answer를 교환하여 세션 정보를 설정합니다.</li>
<li>시그널링 서버는 미디어 데이터(비디오, 오디오)를 전달하지 않고, 단지 신호만 중계합니다.</li>
</ul>
<h3 id="3-미디어-스트림-전송">3. 미디어 스트림 전송</h3>
<ul>
<li>P2P 연결이 설정된 후, 실제 미디어 데이터는 클라이언트 간의 직접 연결을 통해 전송됩니다.</li>
<li>MediaStream API를 사용하여 오디오와 비디오 스트림을 캡쳐하고, RTCPeerConnection을 통해 전송합니다.</li>
</ul>
<h3 id="4-데이터-채널-설정">4. 데이터 채널 설정</h3>
<ul>
<li>RTCDataChannel을 사용하여 피어 간에 임의의 데이터를 전송할 수 있습니다.</li>
<li>데이터 채널은 게임, 파일 공유, 채팅 어플리케이션 등에서 활용됩니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PintOS] Project 3 - Virtual Memory 1 ]]></title>
            <link>https://velog.io/@quro_97/PintOS-Project-3-Virtual-Memory-1</link>
            <guid>https://velog.io/@quro_97/PintOS-Project-3-Virtual-Memory-1</guid>
            <pubDate>Mon, 27 May 2024 16:17:48 GMT</pubDate>
            <description><![CDATA[<h2 id="memory-management">Memory Management</h2>
<p>현재 핀토스에서는 레벨 4 페이지 테이블(pml4)을 사용하여 가상 메모리 주소와 물리 메모리 주소 간의 매핑 정보를 저장한다. 하지만 이것만으로는 충분하지 않다. Supplemental Page Table을 구현해 페이지에 대한 추가적인 정보들을 저장하게 되면 Page Fault와 Resource Management를 수월하게 진행할 수 있다.</p>
<h3 id="supplemental-page-table">Supplemental Page Table</h3>
<blockquote>
<p>supplemental page table은 기존의 page table을 보완하기 위해 만든 것이다.</p>
</blockquote>
<p><strong>역할</strong>
크게 두 가지 역할을 수행한다.</p>
<ul>
<li>페이지 폴트 시 커널이 추가 페이지 테이블에서 오류가 발생한 가상 페이지를 조회하여 어떤 데이터가 있어야 하는지 확인</li>
<li>프로세스가 종료될 때 커널이 추가 페이지 테이블을 참조하여 어떤 리소스를 free시킬 것인지 결정</li>
</ul>
<p><strong>struct supplemental_page_table 구현</strong>
먼저 추가 페이지 테이블의 구조체 struct supplemental_page_table을 구현한다. 그 후에 추가 테이블 구조체에 관계된 함수를 구현한다.</p>
<p>Supplemental page table을 구현하는 방식은 여러 가지가 있을 수 있다. 배열이나, 비트맵, 해시 테이블도 가능하다. hash table로 구현하자.</p>
<h3 id="💡-frame-table">💡 frame table</h3>
<p>frame table은 물리 메모리 내의 각 프레임의 정보를 갖고 있다.</p>
<p><strong>역할</strong>
Frame table은 frame entry의 리스트로 구성되는데,
각 엔트리는 물리 메모리 프레임 테이블로 인해 eviction 정책, 즉 SWAP OUT/IN을 위한 프레임 교체 정책을 수행할 수 있게 된다.</p>
<p><strong>struct frame_table 구현</strong></p>
<ul>
<li>사용 가능한 빈 프레임들의 집합</li>
<li>frame_table은 리스트로 구현한다.</li>
<li>frame_table은 전역적으로 구현해야한다.</li>
</ul>
<hr>
<h2 id="구현">구현</h2>
<h3 id="supplemental-page-table-1">Supplemental Page Table</h3>
<ol>
<li>hash, hash_elem 구조체 추가</li>
</ol>
<ul>
<li>include/vm/vm.h</li>
</ul>
<p><strong>1-1. hash.h include</strong></p>
<pre><code class="language-c">#define VM_VM_H
#include &lt;stdbool.h&gt;
#include &quot;threads/palloc.h&quot;
#include &lt;hash.h&gt; /** Project 3-Memory Management */
...</code></pre>
<p><strong>1-2. spt_hash 추가</strong>
supplemental_page_table에 hash 구조체를 추가한다.</p>
<pre><code class="language-c">struct supplemental_page_table {
     struct hash spt_hash; /** Project 3-Memory Management */
};</code></pre>
<p><strong>1-3. hash_elem 추가</strong>
page 구조체에 hash_elem을 추가한다.</p>
<pre><code class="language-c">struct page {
    const struct page_operations *operations;
    void *va;              /* Address in terms of user space */
    struct frame *frame;   /* Back reference for frame */

    /* Your implementation */

    /* Per-type data are binded into the union.
     * Each function automatically detects the current union */
    union {
        struct uninit_page uninit;
        struct anon_page anon;
        struct file_page file;
#ifdef EFILESYS
        struct page_cache page_cache;
#endif
    };
    /** Project 3-Memory Management */
    struct hash_elem hash_elem;
};</code></pre>
<p><strong>2. supplemental_page_table_init() 구현</strong></p>
<ul>
<li>vm/vm.c</li>
<li>supplementary page table을 초기화한다.</li>
<li>hash table을 사용하기로 했으므로 hash_init을 사용하여 초기화한다.</li>
<li>hash_init에서 필요한 page_hash와 page_less는 3번과 4번에 설명되어 있다.</li>
</ul>
<pre><code class="language-c">void
supplemental_page_table_init (struct supplemental_page_table *spt UNUSED) {
    /** Project 3-Memory Management */
    hash_init(spt, page_hash, page_less, NULL);
}</code></pre>
<p><strong>3. page_hash() 구현</strong></p>
<ul>
<li>spt에 넣을 인덱스를 해시 함수를 돌려서 도출한다.</li>
<li>hash table이 hash elem을 원소로 가지고 있으므로 페이지 자체에 대한 정보를 가져온다.</li>
<li>인덱스를 리턴해야하므로 hash_bytes로 리턴한다.</li>
</ul>
<p><strong>3-1. 선언</strong></p>
<ul>
<li>include/vm/vm.h</li>
</ul>
<pre><code class="language-c">/** Project 3-Memory Management */
uint64_t page_hash(const struct hash_elem *e, void *aux);</code></pre>
<p><strong>3-2. 구현</strong></p>
<ul>
<li>vm/vm.c</li>
</ul>
<pre><code class="language-c">/** Project 3-Memory Management */
uint64_t 
page_hash(const struct hash_elem *e, void *aux)
{
    struct page *page = hash_entry(e, struct page, hash_elem);
    return hash_bytes(page-&gt;va, sizeof *page-&gt;va);
}</code></pre>
<p><strong>4. page_less() 구현</strong></p>
<ul>
<li>체이닝 방식의 spt를 구현하기 위한 함수이다.</li>
<li>해시 테이블 버킷 내의 두 페이지의 주소값을 비교한다.</li>
</ul>
<p><strong>4-1. 선언</strong></p>
<ul>
<li>include/vm/vm.h</li>
</ul>
<pre><code class="language-c">/** Project 3-Memory Management */
uint64_t page_hash(const struct hash_elem *e, void *aux);
bool page_less(const struct hash_elem *a, const struct hash_elem *b, void *aux);</code></pre>
<p><strong>4-2. 구현</strong></p>
<ul>
<li>vm/vm.c</li>
</ul>
<pre><code class="language-c">/** Project 3-Memory Management */
bool 
page_less(const struct hash_elem *a, const struct hash_elem *b, void *aux)
{
    struct page *page_a = hash_entry(a, struct page, hash_elem);
    struct page *page_b = hash_entry(b, struct page, hash_elem);

    return page_a-&gt;va &lt; page_b-&gt;va;
}</code></pre>
<p><strong>5. spt_find_page() 구현</strong></p>
<ul>
<li>vm/vm.c</li>
</ul>
<p>supplementary page table에서 va에 해당하는 구조체 페이지를 찾아 반환한다.</p>
<pre><code class="language-c">struct page *
spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {

    /** Project 3-Memory Management */
    struct page *page = (struct page *)malloc(sizeof(struct page));     
    page-&gt;va = pg_round_down(va);                                       
    struct hash_elem *e = hash_find(&amp;spt-&gt;spt_hash, &amp;page-&gt;hash_elem);  
    free(page);                                                         

    return e != NULL ? hash_entry(e, struct page, hash_elem) : NULL;
}</code></pre>
<p><strong>6. spt_insert_page() 구현</strong></p>
<ul>
<li>vm/vm.c</li>
<li>supplementary page table에 struct page를 삽입한다.</li>
<li>가상 주소가 이미 supplementary page table에 존재하면 삽입하지 않고, 존재하지 않으면 삽입한다.</li>
</ul>
<pre><code class="language-c">bool
spt_insert_page (struct supplemental_page_table *spt UNUSED,
        struct page *page UNUSED) {
    /** Project 3-Memory Management */
    return hash_insert(&amp;spt-&gt;spt_hash, &amp;page-&gt;hash_elem) ? false : true;
}</code></pre>
<h3 id="frame-management">Frame Management</h3>
<h4 id="7-frame_table-구조체-추가"><strong>7. frame_table 구조체 추가</strong></h4>
<p><strong>7-1. frame_table 추가 및 초기화</strong></p>
<ul>
<li>vm/vm.c<pre><code class="language-c">/* vm.c: Generic interface for virtual memory objects. */
</code></pre>
</li>
</ul>
<p>#include &quot;threads/malloc.h&quot;
#include &quot;vm/vm.h&quot;
#include &quot;vm/inspect.h&quot;</p>
<p>/** Project 3-Memory Management */
static struct list frame_table; </p>
<p>/* Initializes the virtual memory subsystem by invoking each subsystem&#39;s</p>
<ul>
<li><p>intialize codes. <em>/
void
vm_init (void) {
 vm_anon_init ();
 vm_file_init ();
#ifdef EFILESYS  /</em> For project 4 <em>/
 pagecache_init ();
#endif
 register_inspect_intr ();
 /</em> DO NOT MODIFY UPPER LINES. */</p>
<p> /** Project 3-Memory Management */
 list_init(&amp;frame_table);
}</p>
<pre><code>**7-2. frame_elem 추가**
* include/vm/vm.h
</code></pre></li>
</ul>
<pre><code class="language-c">struct frame {
    void *kva;
    struct page *page;
    /** Project 3-Memory Management */
    struct list_elem frame_elem; 
};</code></pre>
<p><strong>8. vm_get_frame() 구현</strong></p>
<ul>
<li>vm/vm.c</li>
<li>palloc_get_page 함수를 호출하여 사용자 풀에서 새로운 physical page(frame)를 가져온다.</li>
<li>사용 가능한 page가 없다면 swap out을 수행한다.</li>
</ul>
<pre><code class="language-c">static struct frame *
vm_get_frame (void) {
    struct frame *frame = NULL;
    /* TODO: Fill this function. */

    /** Project 3-Memory Management */
    struct frame *frame = (struct frame *)malloc(sizeof(struct frame));
    ASSERT (frame != NULL);

    frame-&gt;kva = palloc_get_page(PAL_USER | PAL_ZERO);  

    if (frame-&gt;kva == NULL)
        frame = vm_evict_frame();  
    else
        list_push_back(&amp;frame_table, &amp;frame-&gt;frame_elem);

    frame-&gt;page = NULL;

    ASSERT (frame-&gt;page == NULL);
    return frame;
}</code></pre>
<p><strong>9. vm_claim_page() 구현</strong></p>
<ul>
<li><p>vm/vm.c</p>
</li>
<li><p>인자로 주어진 va에 페이지를 하나 할당한다.</p>
</li>
<li><p>해당 페이지로 vm_do_claim_page를 호출한다.</p>
<pre><code class="language-c">bool
vm_claim_page (void *va UNUSED) {
  struct page *page = NULL;
  /** Project 3-Memory Management */
  struct page *page = spt_find_page(&amp;thread_current()-&gt;spt, va);

  if (page == NULL)
      return false;

  return vm_do_claim_page (page);
}</code></pre>
</li>
<li><p><em>10. vm_do_claim_page() 구현*</em></p>
</li>
<li><p>vm_get_frame() 함수를 통해 프레임 하나를 얻는다.</p>
</li>
<li><p>프레임의 페이지로 얻은 페이지를 연결한다.</p>
</li>
<li><p>프레임의 물리적 주소로 얻은 프레임을 연결한다.</p>
</li>
<li><p>현재 페이지 테이블에 가상 주소에 따른 frame을 매핑한다.</p>
</li>
</ul>
<p><strong>10-1. writable 구조체 추가</strong></p>
<pre><code class="language-c">struct page {
    const struct page_operations *operations;
    void *va;              /* Address in terms of user space */
    struct frame *frame;   /* Back reference for frame */

    /* Your implementation */

    /* Per-type data are binded into the union.
     * Each function automatically detects the current union */
    union {
        struct uninit_page uninit;
        struct anon_page anon;
        struct file_page file;
#ifdef EFILESYS
        struct page_cache page_cache;
#endif
    };
    /** Project 3-Memory Management */
    struct hash_elem hash_elem;
    bool writable;
};</code></pre>
<p><strong>10-2. mmu.h include</strong></p>
<ul>
<li>vm/vm.c</li>
</ul>
<pre><code class="language-c">/** Project 3-Memory Management */
#include &quot;threads/mmu.h&quot;
static struct list frame_table; 
10-3. vm_do_claim_page() 구현
vm/vm.c
static bool
vm_do_claim_page (struct page *page) {
    struct frame *frame = vm_get_frame ();

    /* Set links */
    frame-&gt;page = page;
    page-&gt;frame = frame;

    if (!pml4_set_page(thread_current()-&gt;pml4, page-&gt;va, frame-&gt;kva, page-&gt;writable))
        return false;

    return swap_in (page, frame-&gt;kva);
}</code></pre>
<p><strong>10-3. vm_do_claim_page() 구현</strong></p>
<ul>
<li><p>vm/vm.c</p>
<pre><code class="language-c">static bool
vm_do_claim_page (struct page *page) {
  struct frame *frame = vm_get_frame ();

  /* Set links */
  frame-&gt;page = page;
  page-&gt;frame = frame;

  if (!pml4_set_page(thread_current()-&gt;pml4, page-&gt;va, frame-&gt;kva, page-&gt;writable))
      return false;

  return swap_in (page, frame-&gt;kva);
}</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PintOS] Project 3 - 관련 개념]]></title>
            <link>https://velog.io/@quro_97/PintOS-Project-3-%EA%B4%80%EB%A0%A8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@quro_97/PintOS-Project-3-%EA%B4%80%EB%A0%A8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Sun, 26 May 2024 10:14:49 GMT</pubDate>
            <description><![CDATA[<h2 id="swap-disk-란">Swap Disk 란?</h2>
<p>👉 OS 관점에서 스왑 디스크는 <strong>주 메모리(RAM)가 가득 찼을 때</strong> 사용되는 보조 저장 공간</p>
<ul>
<li>주로 하드 드라이브나 SSD 같은 보조 저장 장치에 할당되며, 프로세스의 메모리 페이지를 입시로 저장하는 데 사용한다</li>
<li>저장된 공간은 메모리의 일부 페이지를 임시로 저장하는 데 사용되며, 이를 ‘스와핑’이라 한다</li>
</ul>
<h3 id="스왑-디스크의-목적">스왑 디스크의 목적</h3>
<ul>
<li>스왑 디스크는 메모리 관리와 시스템 성능 최적화에서 중요한 역할을 한다</li>
<li>시스템의 물리적 메모리가 부족할 때 추가적인 가상 메모리 공간을 제공한다</li>
<li>이를 통해 시스템은 더 많은 프로세스와 데이터를 동시에 처리할 수 있다</li>
</ul>
<h3 id="스왑-디스크-작동-방식">스왑 디스크 작동 방식</h3>
<ol>
<li>스와핑(Swapping)<ul>
<li>메모리가 가득 차면, 운영 체제는 가장 적게 사용되는 메모리 페이지들을 스왑 디스크로 이동시킨다</li>
<li>이 과정을 스와핑이라고 하며, 이를 통해 메모리에서 더 중요한 데이터를 처리할 수 있게 된다</li>
</ul>
</li>
<li>페이지 교체<ul>
<li>스왑 디스크에 저장된 페이지가 다시 필요할 경우, 해당 페이지를 메모리에 다시 로드 → 이 과정에서 다른 페이지가 swap disk로 밀려날 수 있다</li>
</ul>
</li>
</ol>
<h3 id="스왑-디스크의-장점과-단점">스왑 디스크의 장점과 단점</h3>
<ul>
<li><strong>장점</strong><ul>
<li>메모리 부족 문제를 완화</li>
<li>동시에 실행되는 프로세스의 수 증가 (더 많은 메모리를 사용하기 때문에)</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>디스크 기반이라 메모리보다 속도가 느리다</li>
<li>과도한 스와핑은 시스템 성능 저하를 일으킬 수 있다</li>
</ul>
</li>
</ul>
<hr>
<h2 id="tlbtranslation-lookaside-buffer-란">TLB(Translation Lookaside Buffer) 란?</h2>
<aside>
👉 가상 메모리 주소를 물리적인 주소로 변환하는 속도를 높이기 위해 사용되는 캐시

</aside>

<ul>
<li>최근에 일어난 가상 메모리 주소와 물리 주소의 변환 테이블 저장</li>
<li>일종의 주소 변환 캐시</li>
<li>TLB 구성
  <img src="https://velog.velcdn.com/images/quro_97/post/3dc52625-c54b-4f10-9830-1c90cde40256/image.png" alt=""></li>
</ul>
<pre><code>- 가상 주소가 주어짐 → TLB에서 해당 가상 주소 검색
    - TLB에 존재할 경우 (TLB 히트) → 프레임 번호 추출 → 물리 주소 구성
    - TLB에 존재하지 않을 경우 (TLB 미스) → 페이지 번호를 이용해 페이지 테이블에서 해당 가상 주소 검색
- 페이지 테이블에서 유효 비트 1일 경우 (해당 페이지 메인 메모리에 존재) → 페이지 테이블의 프레임 번호 이용 → 물리 주소 구성 및 TLB 갱신
- 페이지 테이블에서 유효 비트 0일 경우 (해당 페이지 메인 메모리에 존재X) → page fault 발생</code></pre><ul>
<li><p>페이징과 TLB 동작</p>
<p>  <img src="https://velog.velcdn.com/images/quro_97/post/9874242b-9e3e-48cb-a3e8-79298db5e87a/image.png" alt=""></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PintOS] Project 2 - User Programs 4]]></title>
            <link>https://velog.io/@quro_97/PintOS-Project-2-User-Programs-4</link>
            <guid>https://velog.io/@quro_97/PintOS-Project-2-User-Programs-4</guid>
            <pubDate>Sat, 25 May 2024 10:18:16 GMT</pubDate>
            <description><![CDATA[<h1 id="system-calls-함수-설명">System Calls 함수 설명</h1>
<h2 id="프로세스-관련-시스템-콜">프로세스 관련 시스템 콜</h2>
<h3 id="halt">halt()</h3>
<aside>
👉 pintOS 자체를 종료

</aside>

<pre><code class="language-c">void halt(){
    power_off();
}</code></pre>
<h3 id="exit">exit()</h3>
<aside>
👉 현재 프로세스 종료하는 시스템 콜

</aside>

<pre><code class="language-c">void exit(int status){
    struct thread *curr = thread_current();
    curr-&gt;exit_status = status;
    printf(&quot;%s: exit(%d)\n&quot;, curr-&gt;name, status);
    thread_exit();
}</code></pre>
<ul>
<li>종료 시 &quot;프로세스 이름: exit(status)&quot; 출력</li>
<li>thread 구조체에 exit_status 필드 추가<ul>
<li>exit_status : 프로세스 종료 유무 확인</li>
</ul>
</li>
<li>정상적으로 종료 시 status : 04</li>
</ul>
<h3 id="fork">fork()</h3>
<aside>
👉 부모 프로세스로 부터 자식 프로세스를 복제

</aside>

<pre><code class="language-c">pid_t fork(const char *thread_name){
    check_address(thread_name);
    return process_fork(thread_name, f);
}</code></pre>
<ul>
<li><p>thread_name : 새로 생성될 자식 프로세스의 이름</p>
<ul>
<li>자식 프로세스에 부모 프로세스의 레지스터 값(<code>%RBX ~ %R15</code>)을 복사</li>
</ul>
</li>
<li><p>f : 부모의 인터럽트 프레임</p>
</li>
<li><p>반환값 : 생성된 자식 프로세스의 pid</p>
</li>
<li><p>자식 프로세스는 부모 프로세스의 파일 디스크립터와 가상 메모리 공간을 복제해야한다</p>
</li>
<li><p>process_fork 함수 source code 및 해석</p>
<pre><code class="language-c">  tid_t
  process_fork (const char *name, struct intr_frame *if_ UNUSED) {
      /* Clone current thread to new thread.*/

      struct thread *curr = thread_current();

      memcpy(&amp;curr-&gt;parent_if, if_, sizeof(struct intr_frame)); // 전달받은 intr_frame을 parent_if필드에 복사한다.
      // ↳ &#39;__do_fork&#39; 에서 자식 프로세스에 부모의 컨텍스트 정보를 복사하기 위함(부모의 인터럽트 프레임을 찾는 용도로 사용)

      tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, curr); // __do_fork를 실행하는 스레드 생성, 현재 스레드를 인자로 넘겨준다.
      if (tid == TID_ERROR)
          return TID_ERROR;

      struct thread *child = get_child_process(tid);

      sema_down(&amp;child-&gt;fork_sema); // 자식 프로세스가 로드될 때까지 부모 프로세스는 대기한다.
      if (child-&gt;exit_status == TID_ERROR)
          return TID_ERROR;

      return tid; // 부모 프로세스의 리턴값 : 생성한 자식 프로세스의 tid
  }</code></pre>
</li>
<li><p>__do_fork 함수 source code 및 해석</p>
<pre><code class="language-c">  static void
  __do_fork (void *aux) {
      struct intr_frame if_;
      struct thread *parent = (struct thread *) aux; // 부모 프로세스
      struct thread *current = thread_current (); // 새로 생성된 자식 프로세스
      /* TODO: somehow pass the parent_if. (i.e. process_fork()&#39;s if_) */
      struct intr_frame *parent_if;
      bool succ = true;

      parent_if = &amp;parent-&gt;parent_if; // process_fork에서 복사 해두었던 intr_frame
      /* 1. Read the cpu context to local stack. */
      memcpy (&amp;if_, parent_if, sizeof (struct intr_frame));

      if_.R.rax = 0; // fork 시스템 콜의 결과로 자식 프로세스는 0을 리턴해야하므로 0을 넣어준다.

      /* 2. Duplicate PT */
      current-&gt;pml4 = pml4_create(); // 부모의 pte를 복사하기 위해 페이지 테이블을 생성한다.
      if (current-&gt;pml4 == NULL)
          goto error;

      process_activate (current);
  #ifdef VM
      supplemental_page_table_init (&amp;current-&gt;spt);
      if (!supplemental_page_table_copy (&amp;current-&gt;spt, &amp;parent-&gt;spt))
          goto error;
  #else
      // &quot;pml4_for_each&quot; : Apply FUNC to each available pte entries including kernel&#39;s.
      if (!pml4_for_each (parent-&gt;pml4, duplicate_pte, parent)) // &quot;duplicate_pte&quot; : 페이지 테이블을 복제하는 함수(부모 -&gt; 자식) 
          goto error;
  #endif

      /* TODO: Your code goes here.
       * TODO: Hint) To duplicate the file object, use `file_duplicate`
       * TODO:       in include/filesys/file.h. Note that parent should not return
       * TODO:       from the fork() until this function successfully duplicates
       * TODO:       the resources of parent.*/
      /*
       * 파일 객체를 복제하려면 &#39;file_duplicate&#39;를 사용하라.
       * 이 함수가 부모의 리소스를 성공적으로 복제할 때까지 부모 프로세스는 fork로 부터 리턴할 수 없다.
      */
      if (parent-&gt;next_fd == FDCOUNT_LIMIT)
          goto error;

      // 부모의 fdt를 자식의 fdt로 복사한다.
      for (int fd = 2; fd &lt; FDCOUNT_LIMIT; fd++) {
          struct file *file = parent-&gt;fdt[fd];
          if (file == NULL) // fd엔트리가 없는 상태에는 그냥 건너뛴다.
              continue;
          current-&gt;fdt[fd] = file_duplicate (file);
      }

      current-&gt;next_fd = parent-&gt;next_fd; // 부모의 next_fd를 자식의 next_fd로 옮겨준다.
      sema_up(&amp;current-&gt;fork_sema); // fork가 정상적으로 완료되었으므로 현재 wait중인 parent를 다시 실행 가능 상태로 만든다. 

      /* Finally, switch to the newly created process. */
      if (succ)
          do_iret (&amp;if_);
  error: // 제대로 복제가 안된 상태 - TID_ERROR 리턴 
      sema_up(&amp;parent-&gt;fork_sema);
      exit(TID_ERROR);
      // thread_exit (); // origin_code
  }</code></pre>
<ul>
<li><code>__do_fork</code> 의 인자로 <code>thread_create</code> 를 하면서 4번째 인자로 넣은 <code>curr</code> (aux) 가 들어가게 된다.</li>
</ul>
<ol>
<li>자식 프로세스에 부모의 인터럽트 프레임(실행 컨텍스트)를 복사해서 넣어준다.</li>
<li>부모 프로세스의 페이지 테이블을 자식 프로세스의 페이지 테이블로 복제한다.(<code>duplicate_pte</code> _”filesys/filesys.h”) 사용</li>
<li>부모의 fdt와 next_fd를 복사하여 자식 프로세스에게 설정해준다.</li>
<li>이후, 부모 프로세스에서 자식 프로세스로의 모든 복사 과정이 완료되었으므로 <code>sema_up</code> 을 수행한다.<ul>
<li>반드시 <code>process_fork</code> 에서의 세마포어와 동일한 세마포어에 대해 Up 연산을 수행해주어야한다!</li>
</ul>
</li>
<li><code>do_iret (&amp;if_);</code> : <code>sema_up</code> 을 진행해서 부모 프로세스가 다시 Ready 상태가 되었으므로 컨텍스트 스위치를 수행한다.</li>
</ol>
</li>
</ul>
<h3 id="exec">exec()</h3>
<aside>
👉 cmd_line으로 들어온 실행 파일을 실행하는 시스템 콜

</aside>

<pre><code class="language-c">int exec(const char *cmd_line){
    check_address(cmd_line);

    // 파일 사이즈(NULL 포함 +1)
    int size = strlen(cmd_line) + 1;
    char *fn_copy = palloc_get_page(PAL_ZERO);

    // 메모리 할당 불가 시
    if(fn_copy == NULL)
        exit(-1);
    strlcpy(fn_copy, cmd_line, size);

    if(process_exec(fn_copy) == -1)
        return -1;

    return 0;
}</code></pre>
<ul>
<li><p>현재 실행중인 프로세스를 cmd_line에 지정된 실행 파일로 변경하고 인수들을 전달</p>
</li>
<li><p>process_exec 함수 source code 및 해석</p>
<pre><code class="language-c">  int
  process_exec (void *f_name) {
      char *file_name = f_name; // 커맨드 라인 전체가 인자로 들어온다.
      bool success;

      struct intr_frame _if;
      _if.ds = _if.es = _if.ss = SEL_UDSEG;
      _if.cs = SEL_UCSEG;
      _if.eflags = FLAG_IF | FLAG_MBS;

      int argc = 0;
      char *argv[128]; // 64bit computer(uint64_t : 8byte)

      /* We first kill the current context */
      process_cleanup ();

          /* ----- 추가 부분 ------- */
      /* 커맨드 라인을 파싱한다. */
      argument_parse(file_name, &amp;argc, argv);

      /* And then load the binary */
      success = load (file_name, &amp;_if);

      /* If load failed, quit. */
      if (!success){
          palloc_free_page (file_name);
          return -1;
      }

      argument_stack(argc, argv, &amp;_if); // argc, argv로 커맨드 라인 파싱
      // hex_dump(_if.rsp, _if.rsp, USER_STACK - _if.rsp, true); // 메모리에 적재된 상태 출력

      /* Start switched process. */
      do_iret (&amp;_if);
      NOT_REACHED ();
  }</code></pre>
</li>
</ul>
<h3 id="wait">wait()</h3>
<aside>
👉

</aside>

<pre><code class="language-c">int wait(__pid_t pid){
    process_wait(pid);
}</code></pre>
<ul>
<li><p><code>pid</code> : 대기하려는 자식 프로세스의 <code>pid</code></p>
</li>
<li><p>process_wait 함수 source code 및 해석</p>
<pre><code class="language-c">  process_wait (tid_t child_tid UNUSED) {
      struct thread *child = get_child_process(child_tid);

      if(child == NULL) // 해당 자식이 존재하지 않는다면 -1 리턴
          return -1;

      sema_down(&amp;child-&gt;wait_sema); // 자식 프로세스가 종료할 때까지 대기한다.
      // 컨텍스트 스위칭 발생

      int exit_status = child-&gt;exit_status; // 자식으로 부터 종료인자를 전달 받고 리스트에서 삭제한다.
      list_remove(&amp;child-&gt;child_elem);

      sema_up(&amp;child-&gt;free_sema); // 자식 프로세스 종료 상태를 받은 후 자식 프로세스를 종료하게 한다.

      return exit_status;
  }</code></pre>
<ul>
<li>해당 자식 프로세스가 성공적으로 종료될 때까지 부모 프로세스는 대기한다.</li>
<li><code>threads/thread.c -&gt; init_thread</code> 에서 <code>wait_sema</code> 의 값을 0으로 설정해줬으므로 부모 프로세스는 wait_list로 들어가게된다.</li>
<li>⭐ 이후 자식 프로세스가 종료(<code>process_exit</code>)된 후, sema_up을 한 뒤에서야 해당 부모 프로세스가 Ready 상태로 전환된다!</li>
<li>자식 프로세스가 성공적으로 종료됐다면 부모 프로세스의 자식 프로세스 리스트에서 해당 자식 프로세스를 제거한다.</li>
<li><code>free_sema</code> 는 자식 프로세스의 종료 상태를 받고 자식 프로세스를 종료해야하므로 세마포어를 이중적으로 걸어줬다고 보면 된다.</li>
</ul>
</li>
</ul>
<h2 id="파일-관련-시스템-콜">파일 관련 시스템 콜</h2>
<h3 id="create">create()</h3>
<aside>
👉 `file`을 이름으로 하고 크기가 `initial_size`인 새로운 파일 생성

</aside>

<pre><code class="language-c">bool create(const char *file, unsigned initial_size){
    check_address(file);
    return filesys_create(file, initial_size);
}</code></pre>
<ul>
<li><code>file</code> : 생성할 파일의 이름 및 경로 정보</li>
<li><code>initial_size</code> : 생성할 파일의 크기</li>
<li>파일 생성은 파일 열기X</li>
<li>파일 열기는 <code>open</code> 시스템 콜이 수행</li>
</ul>
<h3 id="remove">remove()</h3>
<aside>
👉 `file` 이름을 가진 파일 삭제

</aside>

<pre><code class="language-c">bool remove(const char *file){
    check_address(file);
    return filesys_remove(file);
}</code></pre>
<ul>
<li><code>file</code> : 제거할 파일의 이름 및 경로 정보</li>
<li>파일은 열려있는지 닫혀있는지와 관계없이 삭제될 수 있다<ul>
<li>삭제 전 파일을 꼭 닫아줄 것!</li>
</ul>
</li>
</ul>
<h3 id="open">open()</h3>
<aside>
👉 파일을 열 때 사용하는 시스템 콜

</aside>

<pre><code class="language-c">int open(const char *file){
    check_address(file);
    struct file *target_file = filesys_open(file);

    if(target_file == NULL)
        return -1;
    int fd = fdt_add_fd(target_file);

    // fdt가 가득 찼다면
    if(fd == -1){
        file_close(target_file);
    }
    return fd;
}</code></pre>
<ul>
<li><code>file</code> : 파일의 이름 및 경로 정보</li>
<li>콘솔용으로 예약 되어있는 파일 디스크립터<ul>
<li>0 : 표준 입력(STDIN_FILENO)</li>
<li>1 : 표준 출력(STDOUT_FILENO)</li>
</ul>
</li>
<li>하나의 파일이 한번 이상 열릴 때, 각 open 프로세스는 새로운 파일 디스크립터를 반환</li>
</ul>
<h3 id="filesize">filesize()</h3>
<aside>
👉 파일의 크기를 알려주는 시스템 콜

</aside>

<pre><code class="language-c">int filesize(int fd){
    struct file *target_file = fdt_get_file(fd);
    if(target_file == NULL)
        return -1;
    return file_length(target_file);
}</code></pre>
<h3 id="read">read()</h3>
<aside>
👉 열린 파일의 데이터를 읽는 시스템 콜

</aside>

<pre><code class="language-c">int read(int fd, void *buffer, unsigned size){
    int read_bytes = -1;

    if(fd == STDIN_FILENO){
        int i;
        unsigned char *buf = buffer;

        for(i = 0; i &lt; size; i++){
            char c = input_getc();
            *buf++ = c;
            if(c == &#39;\0&#39;)
                break;
        }
        return i;
    }
    else{
        struct file *file = fdt_get_file(fd);
        if(file != NULL &amp;&amp; fd != STDOUT_FILENO){
            // 파일을 읽는 동안 접근 못하게 락 걸기
            lock_acquire(&amp;filesys_lock);
            read_bytes = file_read(file, buffer, size);
            // 락 해제
            lock_release(&amp;filesys_lock);
        }
    }
    return read_bytes;
}</code></pre>
<ul>
<li><p><code>fd</code> 가 0이면 STDIN이기 때문에 키보드로 들어온 값을 읽고</p>
</li>
<li><p><code>fd</code> 가 1이면 STDOUT이다</p>
</li>
<li><p>그게 아닐 경우 <code>fd</code>로 열린 파일에서 <code>size</code>바이트를 <code>buffer</code>로 읽는다</p>
</li>
<li><p>실제로 읽은 바이트 수를 반환하거나 만약 파일을 읽을 수 없다면 (EOF로 인한) -1을 반환</p>
</li>
<li><p>input_getc 함수 source code 및 해석</p>
<pre><code class="language-c">  uint8_t
  input_getc (void) {
      enum intr_level old_level;
      uint8_t key;

      old_level = intr_disable ();
      key = intq_getc (&amp;buffer);
      serial_notify ();
      intr_set_level (old_level);

      return key;
  }</code></pre>
<ul>
<li>입력 버퍼로부터 누른 키 값을 가져온다</li>
<li>만약 입력 버퍼가 비어있으면 키를 누를 때까지 기다린다</li>
</ul>
</li>
</ul>
<h3 id="write">write()</h3>
<aside>
👉 열린 파일에 데이터를 쓰는 시스템 콜

</aside>

<pre><code class="language-c">int write(int fd, const void *buffer, unsigned size){
    check_address(buffer);
    int write_bytes = -1;

    if(fd == STDOUT_FILENO){
        putbuf(buffer, size);
        return size;
    }
    else{
        struct file *file = fdt_get_file(fd);
        if(file != NULL &amp;&amp; fd != STDIN_FILENO){
            // 파일을 쓰는 동안 접근 못하게 락 걸기
            lock_acquire(&amp;filesys_lock);
            write_bytes = file_write(file, buffer, size);
            lock_release(&amp;filesys_lock);    // 락 해제
        }
    }
    return write_bytes;
}</code></pre>
<ul>
<li><code>buffer</code> 에서 열린 파일 <code>fd</code> 로 <code>size</code> 만큼의 바이트를 쓴다</li>
<li>실제로 쓴 바이트 수를 반환</li>
<li>일부의 바이트를 쓸 수 없는 경우에는 반환값이 <code>size</code> 보다 작을 수 있다</li>
<li>파일 끝까지 가능한 한 많은 바이트를 쓰고, 실제 바이트 수를 반환하거나 쓸 수 없는 경우 0을 반환</li>
<li>fd 1은 <code>putbuf()</code> 를 이용해서 콘솔에 쓴다</li>
</ul>
<h3 id="seek">seek()</h3>
<aside>
👉 열린 파일의 위치를 알려주는 시스템 콜

</aside>

<pre><code class="language-c">void seek(int fd, unsigned position){
    struct file *target_file = fdt_get_file(fd);

    if(fd &lt;= STDOUT_FILENO || target_file == NULL)
        return;

    file_seek(target_file, position);
}</code></pre>
<ul>
<li><p><code>position</code> : 현재 위치(offset)를 기준으로 이동할 거리</p>
</li>
<li><p>열린 파일 <code>fd</code> 에서 읽거나 쓸 다음 바이트를 <code>position</code>으로 바꾼다</p>
<ul>
<li>즉 <code>position 0</code> 은 파일의 시작 위치</li>
</ul>
</li>
<li><p>현재 파일의 끝을 지나서 읽는 것은 오류 X</p>
<ul>
<li>파일의 끝을 지나서 <code>read</code>를 실행하면 0을 반환</li>
</ul>
</li>
<li><p>but 파일의 끝을 지나서 쓰는 건(write) 불가능</p>
</li>
<li><p>file_seek 함수 source code 및 해석</p>
<pre><code class="language-c">  file_seek (struct file *file, off_t new_pos) {
    // 파일 유효성 검사
      ASSERT (file != NULL);
      // 새로운 위치 확인 (파일의 위치는 0 이상이어야 한다)
      ASSERT (new_pos &gt;= 0);
      // 파일 위치 업데이트 (인자값으로 받은 new_pos로 설정)
      file-&gt;pos = new_pos;
  }</code></pre>
</li>
</ul>
<h3 id="tell">tell()</h3>
<aside>
👉 열린 파일의 위치를 알려주는 시스템 콜

</aside>

<pre><code class="language-c">unsigned tell(int fd){
    struct file *target_file = fdt_get_file(fd);

    if(fd &lt;= STDOUT_FILENO || target_file == NULL)
        return;

    file_tell(target_file);
}</code></pre>
<ul>
<li><p>열린 파일 <code>fd</code> 에서 읽거나 쓸 다음 바이트를 반환</p>
</li>
<li><p>file_tell 함수 source code 및 해석</p>
<pre><code class="language-c">  off_t
  file_tell (struct file *file) {
    // 파일 유효성 검사
      ASSERT (file != NULL);
      // 현재 위치 반환
      return file-&gt;pos;
  }</code></pre>
<ul>
<li>현재 파일 내에서의 위치를 나타내는 파일 offset 반환</li>
</ul>
</li>
</ul>
<h3 id="close">close()</h3>
<aside>
👉 파일을 닫는 시스템 콜

</aside>

<pre><code class="language-c">void close(int fd){
    struct file *target_file = fdt_get_file(fd);

    if(fd &lt;= STDOUT_FILENO || target_file == NULL || target_file &lt;= 2)
        return;

    // fdt 에서 해당 fd값을 제거
    fdt_remove_fd(fd);
    // 파일을 닫음
    file_close(target_file);
}</code></pre>
<ul>
<li>파일을 닫고 fd를 제거</li>
</ul>
<h2 id="추가-함수">추가 함수</h2>
<h3 id="fdt_add_fd">fdt_add_fd()</h3>
<aside>
👉 file descriptor table에 file descriptor 추가하는 함수

</aside>

<pre><code class="language-c">static int fdt_add_fd(struct file *f){
    struct thread *curr = thread_current();
    struct file **fdt = curr-&gt;fdt;

    while(curr-&gt;next_fd &lt; FDCOUNT_LIMIT &amp;&amp; fdt[curr-&gt;next_fd]){
        curr-&gt;next_fd++;
    }

    if(curr-&gt;next_fd &gt;= FDCOUNT_LIMIT)
        return -1;

    fdt[curr-&gt;next_fd] = f;
    return curr-&gt;next_fd;
}</code></pre>
<ul>
<li>file descriptor가 제한 범위를 넘지 않으면서 fdt의 인덱스 위치와 일치할 때 
까지 다음 인덱스로 이동하는 것을 반복</li>
<li>fdt가 가득 차게되면 <code>return -1</code></li>
<li>fdt에 인자값으로 받은 fd 삽입하고 다음 fd 인덱스를 반환</li>
</ul>
<h3 id="fdt_get_file">fdt_get_file()</h3>
<aside>
👉 file descriptor table에서 인자로 들어온 file descriptor를 검색 후 파일 객체를 반환

</aside>

<pre><code class="language-c">static struct file *fdt_get_file(int fd){
    struct thread *curr = thread_current();
    if(fd &lt; STDIN_FILENO || fd &gt;= FDCOUNT_LIMIT)    // 실패
        return NULL;                

    return curr-&gt;fdt[fd];                            // 성공
}</code></pre>
<ul>
<li>fd가 0이하 또는 512이상이면 파일 찾기 실패</li>
</ul>
<h3 id="fdt_remove_fd">fdt_remove_fd()</h3>
<aside>
👉 file descriptor table에서 인자로 들어온 file descriptor를 삭제

</aside>

<pre><code class="language-c">static void fdt_remove_fd(int fd){
    struct thread * curr = thread_current();

    if(fd &lt; STDIN_FILENO || fd &gt;= FDCOUNT_LIMIT)    // 실패
        return;

    curr-&gt;fdt[fd] = NULL;                            // 성공
}</code></pre>
<ul>
<li>조건은 위와 동일</li>
</ul>
<h3 id="get_child_process">get_child_process()</h3>
<aside>
👉 자식 리스트를 검색해서 해당 프로세스 디스크립터를 반환

</aside>

<pre><code class="language-c">struct thread *get_child_process(int pid){
    struct thread *curr = thread_current();
    struct list *child_list = &amp;curr-&gt;child_list;

    // 자식 리스트를 순회하면서 프로세스 디스크립터 검색
    for(struct list_elem *e = list_begin(child_list); e != list_end(child_list); e = list_next(e)){
        struct thread *t = list_entry(e, struct thread, child_elem);
        // 해당 pid가 존재하면 프로세스 디스크립터 반환
        if(t-&gt;tid == pid)
            return t;
    }
    // 리스트에 존재하지 않으면 NULL 반환
    return NULL;
}</code></pre>
<ul>
<li><code>process_fork</code> 를 통해 부모 프로세스에서 자식 프로세스를 복제하며 부모 프로세스의 <code>child_list</code> 에 자식 프로세스의 element 값을 넣어준다</li>
<li>이후, <code>process_wait</code> 과 <code>process_fork</code> 의 <code>load</code> 과정 이전에 <code>tid</code> 로 받은 프로세스가 실제 부모의 자식 프로세스가 맞는지 검증하는 과정을 거친다</li>
</ul>
<h3 id="함수-수정-및-추가">함수 수정 및 추가</h3>
<aside>
👉 `thread_create()` 함수에 fdt 공간 할당 부분 추가

</aside>

<pre><code class="language-c">    // fdt 공간 할당
    t-&gt;fdt = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
    if(t-&gt;fdt == NULL)
        return TID_ERROR;
    t-&gt;next_fd = 2;     // 0: stdin, 1: stdout
    t-&gt;fdt[0] = 1;      // STDIN_FILENO -&gt; dummy value
    t-&gt;fdt[1] = 2;      // STDOUT_FILENO -&gt; dummy value</code></pre>
<ul>
<li><p><code>palloc_get_multiple(enum palloc_flags flags, size_t page_cnt)</code></p>
<ul>
<li><p>source code (주석 포함)</p>
<pre><code class="language-c">  void *
  palloc_get_multiple (enum palloc_flags flags, size_t page_cnt) {
    // 사용자 영역인지 커널 영역인지 확인
      struct pool *pool = flags &amp; PAL_USER ? &amp;user_pool : &amp;kernel_pool;

    /* 다중 스레드 환경에서 동시 접근을 방지하기 위해 해당 
       풀에 대한 락(lock) 획득 */
      lock_acquire (&amp;pool-&gt;lock);
      /* bitmap_scan_and_flip 함수를 이용해 풀의 비트맵에서 
         연속된 &#39;page_cnt&#39;개의 빈 페이지를 찾는다 */
      size_t page_idx = bitmap_scan_and_flip (pool-&gt;used_map, 0, page_cnt, false);
      /* 페이지를 찾은 후에는 락을 해제 */
      lock_release (&amp;pool-&gt;lock);
      void *pages;

    /* 페이지를 찾으면, 페이지의 시작 주소를 계산하여
       &#39;pages&#39; 변수에 저장 */
      if (page_idx != BITMAP_ERROR)
          pages = pool-&gt;base + PGSIZE * page_idx;
      else
        // 찾지 못하면 NULL로 설정
          pages = NULL;

      if (pages) {
        // &#39;PAL_ZERO&#39; 비트가 설정되어 있으면 페이지를 0으로 초기화
          if (flags &amp; PAL_ZERO)
              memset (pages, 0, PGSIZE * page_cnt);
      } else {
        // &#39;PAL_ASSERT&#39; 비트가 설정되어 있으면 커널을 패닉시킨다
          if (flags &amp; PAL_ASSERT)
              PANIC (&quot;palloc_get: out of pages&quot;);
      }
    // 찾은 페이지의 시작 주소를 반환
      return pages;
  }</code></pre>
</li>
<li><p>연속된 여러 개의 빈 페이지를 할당하고 반환하는 함수</p>
</li>
<li><p>인자값인 flags에 따라 사용자 영역이나 커널 영역에서 페이지를 할당하고, 필요에 따라 페이지를 0으로 초기화</p>
</li>
<li><p><code>STDIN(0)</code>, <code>STDOUT(1)</code> 은 미리 콘솔을 위해 예약된 fd이므로 <code>next_fd</code> 는 2부터 시작하도록 초기화한다.</p>
</li>
<li><p><code>fdt[0]</code> 과 <code>fdt[1]</code> 에는 0(NULL)이 아닌 값으로 채워준다.</p>
</li>
</ul>
</li>
<li><p><code>enum palloc_flags</code></p>
<ul>
<li><p>source code (주석 포함)</p>
<pre><code class="language-c">  enum palloc_flags {
      PAL_ASSERT = 001,           /* Panic on failure. */
      PAL_ZERO = 002,             /* Zero page contents. */
      PAL_USER = 004              /* User page. */
  };</code></pre>
<ol>
<li><strong>PAL_ASSERT (001)</strong>:<ul>
<li>할당에 실패하면 패닉(Panic)을 발생</li>
<li>할당에 실패할 경우, 프로그램이 중단되고 오류 메시지가 표시</li>
</ul>
</li>
<li><strong>PAL_ZERO (002)</strong>:<ul>
<li>할당된 페이지의 내용을 0으로 초기화</li>
<li>즉, 페이지에 저장되는 데이터를 모두 0으로 설정</li>
</ul>
</li>
<li><strong>PAL_USER (004)</strong>:<ul>
<li>할당된 페이지가 사용자 페이지인 경우</li>
<li>사용자 페이지는 일반적으로 사용자 프로세스에 의해 사용 커널 페이지와는 구분</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<aside>
👉 `process_exit()` 함수 수정

</aside>

<pre><code class="language-c">  ...

  palloc_free_multiple(curr-&gt;fdt, FDT_PAGES);        // fdt 메모리 해제
    file_close(curr-&gt;running);                        // 현재 프로세스가 실행중인 파일 종료

    process_cleanup();

    // 부모 프로세스가 자식 프로세스의 종료상태를 확인하게 한다
    sema_up(&amp;curr-&gt;wait_sema);
    // 부모 프로세스가 자식 프로세스의 종료 상태를 받을때 까지 대기한다
    sema_down(&amp;curr-&gt;free_sema);
}</code></pre>
<ul>
<li>자식 프로세스의 종료 시, 부모 프로세스에게 자식 프로세스의 종료를 알릴 수 있게끔 세마포어 연산 수행</li>
</ul>
<aside>
👉 `syscall_init()` 함수에 락 초기화 부분 추가

</aside>

<pre><code class="language-c">lock_init(&amp;filesys_lock);</code></pre>
<aside>
👉 `thread` 구조체 수정

</aside>

<pre><code class="language-c">#ifdef USERPROG

    ...
    int exit_status;        // 프로세스 종료 유무 확인
    struct file **fdt;      // file descriptor table의 시작주소(프로세스당 개별적으로 존재)
    int next_fd;            // 다음 file descriptor의 인덱스
    struct file *running;   // 현재 실행 중인 파일

    struct intr_frame parent_if;    // 부모 프로세스의 인터럽트 프레임
    struct list child_list;         // 자식 프로세스 리스트
    struct list_elem child_elem;    // 자식 프로세스 리스트의 element

    struct semaphore fork_sema;     // fork가 완료될 때 sema_up 수행
    struct semaphore free_sema;     // 자식 프로세스가 종료될 때까지 부모 프로세스는 대기
    struct semaphore wait_sema;     // 자식 프로세스가 종료될 때까지 대기, 종료 상태 저장
        ...

#endif</code></pre>
<aside>
👉 `init_thread` 함수 수정

</aside>

<pre><code class="language-c">...
    t-&gt;exit_status = 0;
    t-&gt;running = NULL;
    // 자식 리스트 및 세마포어 초기화
    list_init(&amp;t-&gt;child_list);
    sema_init(&amp;t-&gt;wait_sema, 0);
    sema_init(&amp;t-&gt;fork_sema, 0);
    sema_init(&amp;t-&gt;free_sema, 0);
 }</code></pre>
<ul>
<li>수정한 <code>thread</code> 구조체에 대한 초기화 작업 수행</li>
<li>이때 세마포어 값은 <code>0</code><ul>
<li>부모 프로세스에서 먼저 호출하기 때문에 부모 프로세스를 대기(wait 상태) 시키기 위해서 <code>0</code>으로 만들어야함</li>
</ul>
</li>
</ul>
<aside>
👉 `load` 함수 수정

</aside>

<pre><code class="language-c">static bool
load (const char *file_name, struct intr_frame *if_) {
    struct thread *t = thread_current ();
    struct ELF ehdr;
    struct file *file = NULL;
    off_t file_ofs;
    bool success = false;
    int i;

    ...

    t-&gt;running = file;

    file_deny_write(file); // 현재 오픈한 파일에 접근 못하게 함

    ...

    return success;
}</code></pre>
<ul>
<li><code>exec</code>를 통해 파일을 실행하게 되면 해당 프로세스에 해당하는 파일은 열린 상태로 있으므로 <code>struct thread</code> 의 running 필드에 해당 파일을 추가한다.<ul>
<li><code>process_exit</code> 에서 해당 파일에 대해 닫아주는 작업을 수행한다.<ul>
<li>또, 현재 실행하기위해 오픈한 파일에 대해 접근하지 못하게 하기 위해 <code>file_deny_write</code>를 이용해 접근 제한을 걸어준다.</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PintOS] Project 2 - User Programs 3]]></title>
            <link>https://velog.io/@quro_97/PintOS-Project-2-User-Programs-3</link>
            <guid>https://velog.io/@quro_97/PintOS-Project-2-User-Programs-3</guid>
            <pubDate>Sat, 25 May 2024 10:04:29 GMT</pubDate>
            <description><![CDATA[<h2 id="user-memory-access">User Memory Access</h2>
<blockquote>
<p>시스템 콜을 구현하기 위해 유저의 가상 주소 공간에 접근할 수 있는 방법을 제시해줘야 한다</p>
</blockquote>
<aside>
👉 시스템 콜의 인자로 들어오는 모든 포인터 값을 검사

</aside>

<pre><code class="language-c">void check_address(void* uaddr) {
    struct thread *cur = thread_current();
    if (uaddr == NULL || is_kernel_vaddr(uaddr) || pml4_get_page(cur-&gt;pml4, uaddr) == NULL) {
        exit(-1);
    }
}</code></pre>
<ul>
<li>NULL pointer(유효하지 않은 포인터)이거나</li>
<li>커널 메모리에 대한 포인터를 가리키거나</li>
<li>맵핑되지 않은 vm에 대한 포인터라면</li>
<li>프로세스 종료</li>
</ul>
<h2 id="system-call">System Call</h2>
<aside>
👉 pintOS는 시스템 콜 핸들러가 구현되어 있지 않아 시스템 콜이 처리되지 않는다. pintOS의 시스템 콜 메커니즘을 이해하고 시스템 콜 핸들러를 구현해야 한다.

</aside>

<ul>
<li>시스템 콜(halt, exit, create, remove 등)을 구현하고 시스템 콜 핸들러를 통해 호출</li>
<li><code>%rax</code> 는 system call number</li>
<li>네 번째 argument는 <code>%rcx</code>가 아니라 <code>%r10</code> 이다</li>
<li>따라서 <code>syscall_handler()</code> 가 제어권을 잡으면, system call number는 <code>%rax</code> 안에 있게 되고, arguments들은 <code>%rdi</code>, <code>%rsi</code>, <code>%rdx</code>, <code>%r10</code>, <code>%r8</code>, <code>%r9</code> 순서로 전달</li>
</ul>
<h3 id="pintos의-시스템-콜-호출-과정">pintOS의 시스템 콜 호출 과정</h3>
<p><img src="https://velog.velcdn.com/images/quro_97/post/fdce67b4-a7ff-49b7-9b8b-312ff3560f6f/image.png" alt=""></p>
<ul>
<li>유저 프로그램에서 <code>write()</code> 시스템 콜 호출</li>
<li>pintos/lib/user/syscall.c 에서 <code>write()</code> 함수 호출</li>
</ul>
<pre><code class="language-c">int
write (int fd, const void *buffer, unsigned size) {
    return syscall3 (SYS_WRITE, fd, buffer, size);
}</code></pre>
<ul>
<li>SYS_WRITE는 include/lib/syscall-nr.h 에 enum으로 정의(SYS_WRITE = 10)</li>
<li>이 시스템 콜 번호와 받아온 인자값들을 가지고 <code>syscall3</code>을 호출<ul>
<li>이때의 syscall 뒤에 붙는 숫자는 인자(argument)의 개수</li>
</ul>
</li>
<li><code>syscall3</code> 은 syscall을 호출</li>
<li>레지스터에 인자값을 넣고, 어셈블리어로 syscall을 호출 → syscall-entry.s 로 이동</li>
</ul>
<p><img src="https://velog.velcdn.com/images/quro_97/post/6c0c4318-4d0b-4f94-bb3a-3279af814e6e/image.png" alt=""></p>
<ul>
<li>rsp(stack pointer) 값을 바꿔줌으로써 <strong>커널 스택</strong>으로 진입</li>
</ul>
<p><img src="https://velog.velcdn.com/images/quro_97/post/36984988-a1bb-4ca4-857a-62acb7f65a5d/image.png" alt=""></p>
<ul>
<li><code>syscall_handler</code>로 이동 후 시스템 콜 함수 호출</li>
</ul>
<pre><code class="language-c">int write(int fd, const void *buffer, unsigned size) {
    check_address(buffer);
    int bytes_write = 0;
    if (fd == STDOUT_FILENO) {
        putbuf(buffer, size);
        bytes_write = size;
    } else {
        if (fd &lt; 2)
            return -1;
        struct file *file = process_get_file(fd);
        if (file == NULL)
            return -1;
        lock_acquire(&amp;filesys_lock);
        bytes_write = file_write(file, buffer, size);
        lock_release(&amp;filesys_lock);
    }
    return bytes_write;
}</code></pre>
<p><strong>시스템 콜 <code>write</code>함수 호출 완료 !!</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PintOS] Project 2 - User Programs 2]]></title>
            <link>https://velog.io/@quro_97/PintOS-Project-2-User-Programs-2</link>
            <guid>https://velog.io/@quro_97/PintOS-Project-2-User-Programs-2</guid>
            <pubDate>Fri, 24 May 2024 05:41:16 GMT</pubDate>
            <description><![CDATA[<h3 id="첫-번째-과제-command-line-parsing-과-argument-passing">첫 번째 과제 Command Line Parsing 과 Argument Passing</h3>
<ul>
<li><p>pintOS는 프로그램과 인자를 구분하지 못하는 구조이다.
ex) ls -a (pintOS 는 &#39;ls-a&#39;를 하나의 프로그램명으로 인식)</p>
</li>
<li><p>프로그램 이름과 인자를 구분하여 스택에 저장, 인자를 응용 프로그램에 전달하는 기능을 구현하자.</p>
</li>
</ul>
<hr>
<h3 id="과제를-해결하기-위해서는">과제를 해결하기 위해서는?</h3>
<ul>
<li>응용 프로그램 실행 흐름을 추적하여 프로그램 파싱 시점을 파악</li>
<li>문자열 파싱을 담당하는 함수 이용</li>
<li>함수 호출 규약에 따른 인자 전달 메커니즘 이해 및 이를 저장하는 인터페이스 구현</li>
</ul>
<h4 id="함수-호출-규약이란">함수 호출 규약이란?</h4>
<blockquote>
<p>유닉스 64비트 x86-64 구현에 있는 몇 가지 중요한 호출 규약들은 다음과 같다.</p>
</blockquote>
<p>유저-레벨 어플리케이션은 %rdi, %rsi, %rdx, %rcx, %r8, %r9 시퀀스들을 전달하기 위해 정수 레지스터를 사용합니다.
호출자는 다음 인스트럭션의 주소(리턴 어드레스)를 스택에 푸시하고, 피호출자의 첫번째 인스트럭션으로 점프합니다. CALL 이라는 x86-64 인스트럭션 하나가 이 두 가지를 모두 수행합니다.
피호출자가 실행됩니다.
만약 피호출자가 리턴 값을 가지고 있다면, 리턴 값은 레지스터 RAX에 저장됩니다.
피호출자는 x86-64 인스트럭션인 RET 를 사용해서, 스택에 받았던 리턴 어드레스를 pop하고 그 주소가 가리키는 곳으로 점프함으로써 리턴됩니다.</p>
<blockquote>
<p>우리는 <em><strong>userprog</strong></em> 디렉토리의 <em><strong>process.c</strong></em> 파일을 수정할 것이다.</p>
</blockquote>
<p>커널은 유저 프로그램이 실행되기 전에, 레지스터에 올라가 있는 초기 함수를 위한 인자를 반드시 넣어줘야 한다. (이 인자들은 일반적인 호출 규약과 동일한 방식으로 전달)</p>
<p>👉 <code>/bin/ls -l foo bar</code> 와 같은 명령이 주어졌을 때, 인자들을 어떻게 다뤄야 하는지 생각해보자.</p>
<ol>
<li>명령어를 띄어쓰기 단위로 쪼갠다 -&gt; <code>/bin/ls</code>, <code>l</code>, <code>foo</code>, <code>bar</code></li>
<li>이 단어들을 스택에 넣는다. 순서는 상관 X (포인터에 의해 참조될 예정이기 때문)</li>
<li>각 문자열의 주소 + 경계조건을 위한 널포인터를 스택에 오른쪽→왼쪽 순서로 푸시</li>
</ol>
<p>**이들은 <code>argv</code>의 원소가 된다.
널포인터 경계는 <code>argv[argc]</code> 가 널포인터라는 사실을 보장해준다.
그리고 이 순서는 <code>argv[0]</code>이 가장 낮은 가상 주소를 가진다는 사실을 보장해준다.
또한 word 크기에 정렬된 접근이 정렬되지 않은 접근보다 빠르므로, 최고의 성능을 위해서는 스택에 첫 푸시가 발생하기 전에 스택포인터를 8의 배수로 반올림 해준다.</p>
<p><span style="color:gray">// userprog/process.c/argument_stack()</span></p>
<pre><code>...

int padding = (int)*rsp % 8;
    for (int i = 0; i &lt; padding; i++)
    {
        (*rsp)--;
        **(uint8_t **)rsp = 0;        // rsp 직전까지 값 채움
    }

 ...</code></pre><ol start="4">
<li><code>%rsi</code> 가 <code>argv</code> 주소(<code>argv[0]</code>의 주소)를 가리키게 하고, <code>%rdi</code> 를 <code>argc</code> 로 설정한다.</li>
<li>마지막으로 가짜 “리턴 주소”를 푸시 : entry 함수는 절대 리턴되지 않겠지만, 해당 스택 프레임은 다른 스택 프레임들과 같은 구조를 가져야 하기 때문에.</aside>

</li>
</ol>
<aside>
👉 아래의 표는 스택과 관련 레지스터들이 유저 프로그램이 시작되기 직전에 어떤 상태인지를 보여준다 (스택은 아래 방향으로 커진다)
</aside>

<p><img src="https://velog.velcdn.com/images/quro_97/post/b75bdabd-43bb-4f8c-bc2e-9be3c0f08914/image.png" alt=""></p>
<h3 id="argument-passing인자-전달-구현">Argument Passing(인자 전달) 구현</h3>
<blockquote>
<p>구현 할 때 크게 두 부분으로 나눠서 구현한다</p>
</blockquote>
<ul>
<li>받은 문자열을 parsing후 argv, argc에 저장</li>
<li>stack에 인자 넣기</li>
</ul>
<blockquote>
<p>유저 프로그램을 실행시키는 전체적인 흐름은 다음과 같다</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/quro_97/post/1b37dfcd-1b39-4921-beed-d07fbcc39f5d/image.png" alt=""></p>
<h3 id="todo">Todo</h3>
<ul>
<li>command line을 parsing해서 스레드의 이름을 식별한다.</li>
<li>해당 파일 이름을 갖는 프로그램을 찾는다.</li>
<li>user stack에 인자를 push한다.</li>
</ul>
<h3 id="유저-스택에-파싱된-토큰을-저장하는-함수-구현">유저 스택에 파싱된 토큰을 저장하는 함수 구현</h3>
<pre><code class="language-c">void argument_stack(char **parse ,int count ,void **esp)</code></pre>
<ul>
<li>parse : 프로그램 이름과 인자가 저장되어 있는 메모리 공간</li>
<li>count : 인자의 개수</li>
<li>esp : 스택 포인터를 가리키는 주소</li>
</ul>
<h3 id="코드의-흐름">코드의 흐름</h3>
<p>init.c → int main(void) → run_actions(argv) → run_task(char **argv) → process_create_initd(task) → thread_create (file_name, PRI_DEFAULT, initd, fn_copy) → initd→process_exec → load, do_iret</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PintOS] Project 2 - User Programs 1]]></title>
            <link>https://velog.io/@quro_97/PintOS-Project-2-User-Programs-1</link>
            <guid>https://velog.io/@quro_97/PintOS-Project-2-User-Programs-1</guid>
            <pubDate>Fri, 10 May 2024 12:32:07 GMT</pubDate>
            <description><![CDATA[<h1 id="과제-목표">과제 목표</h1>
<ol>
<li>Argument Passing (인자 전달)</li>
<li>User Memory Access (유저 메모리 접근)</li>
<li>System Calls (시스템 콜)</li>
<li>Process Termination Message (프로세스 종료 메세지)</li>
<li>Deny Write on Executables (실행 파일에 쓰기 거부)</li>
<li>Extend File Descriptor (파일 식별자 확장하기 | Extra)</li>
</ol>
<hr>
<h1 id="intro">Intro</h1>
<p>지난번까지의 과제에서 실행한 모든 코드는 커널의 일부로서 시스템에 중요한 부분에 접근 할 수 있는 특권을 가지고 실행되었다. 하지만 유저 프로그램을 실행하기 시작하면 더 이상 그런 특권은 갖지 못한다. 지금부터의 과제는 이런 상황을 다뤄야 한다.</p>
<p>pintOS에서의 프로세스는 하나의 스레드만을 가질 수 있다.(멀티스레드 프로세스 지원 X)
이러한 상황에서 우리는 한번에 하나 이상의 프로그램이 실행될 수 있도록 할 것이다. 이는 우리가 한번에 여러 프로세스들을 로드하고 실행할 때 메모리, 스케쥴링, 그 외 다른 상태들을 모두 관리해야 한다는 것을 의미한다.</p>
<h3 id="source-files">Source Files</h3>
<p><strong>우리는 <em>userprog</em> 디렉토리에서 작업할 것이다.</strong></p>
<p><strong><em>process.c, process.h</em></strong> </p>
<ul>
<li><strong>ELF binaries</strong>를 불러와 프로세스를 시작할 것이다.<blockquote>
<p><strong>ELF binaries</strong> : 리눅스에서 실행 가능(Executable)하고 링크 가능(Linkable)한 file의 Format을 ELF(Executable and Linkable Format)라고 한다. 즉, 실행 가능한 바이너리 또는 오브젝트 파일 등의 형식을 규정한 것 !</p>
</blockquote>
</li>
</ul>
<p><em><strong>syscall.c, syscall.h</strong></em> </p>
<ul>
<li>여러 <strong>시스템 콜</strong>을 수행하는 코드로 이루어진 파일.<blockquote>
<p>유저 프로세스가 <strong>일부 커널 기능</strong>에 접근하려 할때마다 <strong>시스템 콜</strong>이 호출된다.
현재 상태에서는 단지 메세지를 출력하고 유저 프로세스를 종료시키게 되어있다.</p>
</blockquote>
</li>
</ul>
<p><em><strong>syscall-entry.S</strong></em> </p>
<ul>
<li>시스템 콜 핸들러를 부트스트랩하는 어셈블리 코드로 이루어져 있다.<blockquote>
<p>부트스트랩 프로그램 : 전원을 켜거나 재부팅을 할 때 적재되는 프로그램</p>
</blockquote>
</li>
</ul>
<p><em><strong>exception.c, exception.h</strong></em> </p>
<ul>
<li>유저 프로그램이 특별한 접근 권한을 필요로 하거나 금지된 연산을 수행할 때, <strong>exception 혹은 fault</strong>로 커널에서 오류를 띄우는데, 이런 오류를 다루는 코드의 파일이다.<blockquote>
<p>응용프로그램이 시스템 콜을 호출하면 하드웨어는 &#39;트랩 핸들러&#39;를 실행하여 하드웨어 특권 수준을 커널모드로 격상시킨다.
커널모드에서 운영체제는 시스템 하드웨어를 자유롭게 접근할 수 있다.</p>
</blockquote>
</li>
</ul>
<p><em><strong>gdt.c, gdt.h</strong></em></p>
<ul>
<li>x86-64는 segmented architecture이다. Global Descriptor Table(GDT)는 사용중인 세그먼트들을 알려주는 표이고, 이 파일은 GDT를 셋업한다.<blockquote>
<p>segmentation 기법 : 프로그램을 논리적 의미 단위인 세그먼트의 집합으로 구성하는 방식</p>
</blockquote>
</li>
</ul>
<p><em><strong>tss.c, tss.h</strong></em></p>
<ul>
<li>Task-State Segment(TSS)는 x86 아키텍쳐의 context switching에 사용된다.
하지만 x86-64에서 context switching은 지원이 중단된 기능이다.
그래도 TSS는 여전히 ring switching 동안 스택 포인터를 찾아내기 위해 사용되고 있다.<blockquote>
<p>유저 프로세스가 인터럽트 핸들러에 진입할 때, 하드웨어는 TSS에게 커널의 스택 포인터를 찾아 달라고 요청한다.</p>
</blockquote>
</li>
</ul>
<h3 id="virtual-memory-layout">Virtual Memory Layout</h3>
<blockquote>
<p>pintOS에서 가상 메모리는 두 영역으로 나눌 수 있다</p>
</blockquote>
<ul>
<li><strong>User Virtual Memory</strong><ul>
<li>유저 가상 메모리는 가상 주소 0부터 KERN_BASE까지의 범위를 갖는다. (KERN_BASE : 0x8004000000)</li>
</ul>
</li>
<li>Kernel Virtual Memory<ul>
<li>유저 가상 메모리를 제외한 나머지 주소 공간을 차지한다.</li>
</ul>
</li>
</ul>
<p>하나의 프로세스는 하나의 유저 가상 메모리는 갖는다. 프로세스 context switching이 일어날 때, 커널은 유저 가상 주소 공간 또한 바꿔준다. 스레드 구조체는 하나의 프로세스의 페이지 테이블을 가리키는 포인터를 가지고 있다.</p>
<p>pintOS에서 커널 가상 메모리는 물리 메모리와 일대일 대응으로 매핑되는데, 이는 KERN_BASE에서 시작한다. 즉, 가상 주소 KERN_BASE가 물리 메모리 0에 접근하고, 가상주소 KERN_BASE + 0x1234 가 물리주소 0x1234에 접근한다.</p>
<p>유저 프로그램은 자신의 유저 가상 메모리에만 접근할 수 있다. 만약 커널 가상 메모리에 접근하려 시도하면 page fault를 일으키고 프로세스는 종료된다.</p>
<p>반면, 커널 스레드는 커널 가상 메모리와 동작 중인 프로세스의 사용자 가상 메모리에도 접근 가능하다. 하지만 커널 안에서도 매핑되지 않은 주소에 접근하면 page fault를 일으킨다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WebSocket]]></title>
            <link>https://velog.io/@quro_97/week-06-Socket</link>
            <guid>https://velog.io/@quro_97/week-06-Socket</guid>
            <pubDate>Mon, 22 Apr 2024 02:36:37 GMT</pubDate>
            <description><![CDATA[<h2 id="websocket이란">WebSocket이란?</h2>
<blockquote>
<p>WebSocket은 서버와 클라이언트 간의 메시지 교환을 위한 <strong>통신 규약(프로토콜)</strong>이다. HTTP 프로토콜이 요청-응답 방식인 것과 달리, WebSocket은 <strong>한 번 연결이 성립</strong>되면 클라이언트와 서버가 자유롭게 데이터를 주고받을 수 있는 것이 특징이다.</p>
</blockquote>
<h2 id="websocket의-특징">WebSocket의 특징</h2>
<ul>
<li><strong>양방향 통신</strong><ul>
<li>클라이언트와 서버가 양방향으로 동시에 데이터를 주고받을 수 있다</li>
</ul>
</li>
<li><strong>실시간 데이터 전송</strong><ul>
<li>연결이 성립된 후에는 실시간으로 데이터를 주고받을 수 있어, 지연 시간이 적다</li>
</ul>
</li>
<li><strong>낮은 오버헤드</strong><ul>
<li>HTTP 프로토콜보다 헤더 정보가 적어, 데이터 전송 시 오버헤드가 적다</li>
</ul>
</li>
<li><strong>연결 유지</strong><ul>
<li>한 번 연결이 성립되면 연결을 유지하면서 지속적으로 데이터를 주고받을 수 있다. 재연결이 필요 없다</li>
</ul>
</li>
</ul>
<h2 id="웹-소켓이-나오기-전까지의-통신-방식">웹 소켓이 나오기 전까지의 통신 방식</h2>
<blockquote>
<p>웹 소켓이 등장하기 전에는 주로 다음과 같은 방식으로 클라이언트와 서버 간의 통신이 이루어졌다</p>
</blockquote>
<h3 id="http-폴링-http-polling">HTTP 폴링 (HTTP Polling)</h3>
<p>HTTP 폴링은 클라이언트가 주기적으로 서버에 HTTP 요청을 보내고, 서버가 새로운 데이터를 가지고 있으면 응답을 통해 전달하는 방식이다.</p>
<h4 id="특징">특징</h4>
<ul>
<li>클라이언트가 일정 간격으로 서버에 요청을 보낸다.</li>
<li>실시간성이 낮고, 많은 불필요한 요청이 발생한다.</li>
<li>서버 부하가 높아질 수 있다.</li>
</ul>
<h3 id="롱-폴링-long-polling">롱 폴링 (Long Polling)</h3>
<p>롱 폴링은 클라이언트가 서버에 요청을 보내고, 서버는 새로운 데이터가 생길 때까지 응답을 지연시킨 후 전달하는 방식이다. 데이터가 준비되면 즉시 응답을 보내고, 클라이언트는 다시 요청을 보낸다.</p>
<h4 id="특징-1">특징</h4>
<ul>
<li>실시간성은 HTTP 폴링보다 높다.</li>
<li>클라이언트와 서버 간 연결이 오래 유지되므로 HTTP 연결의 오버헤드가 줄어든다.</li>
<li>서버 부하가 다소 줄어들지만, 여전히 연결 관리가 필요하다.</li>
</ul>
<h3 id="http-스트리밍-http-streaming">HTTP 스트리밍 (HTTP Streaming)</h3>
<p>HTTP 스트리밍은 서버가 클라이언트와의 HTTP 연결을 끊지 않고 지속적으로 데이터를 푸시하는 방식이다. 클라이언트는 하나의 긴 HTTP 요청을 보내고, 서버는 응답을 끊지 않고 여러 데이터를 지속적으로 전송한다.</p>
<h4 id="특징-2">특징</h4>
<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>HTTP 기반</strong><ul>
<li>HTTP 프로토콜을 사용하므로, 방화벽 및 프록시 서버를 통과하는 데 유리하다.</li>
</ul>
</li>
</ul>
<h2 id="websocket-동작-과정">WebSocket 동작 과정</h2>
<p><img src="https://velog.velcdn.com/images/quro_97/post/b62cbc00-28bc-4842-bc99-e8262daca90a/image.png" alt=""></p>
<h3 id="1-handshake">1. Handshake</h3>
<blockquote>
<p>WebSocket 연결은 HTTP 요청을 통해 시작된다. 클라이언트가 서버에 WebSocket 연결을 요청하는 HTTP 업그레이드(Upgrade) 요청을 보낸다. 이 요청은 일반적인 HTTP 요청과 비슷하지만, 몇 가지 특별한 헤더가 포함된다.</p>
</blockquote>
<p><strong>클라이언트의 요청 예시:</strong></p>
<pre><code>GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
</code></pre><ul>
<li><strong>Upgrade</strong>: websocket: HTTP 연결을 WebSocket으로 업그레이드하도록 요청.</li>
<li><strong>Connection</strong>: Upgrade: 연결을 업그레이드할 것임을 명시.</li>
<li><strong>Sec-WebSocket-Key</strong>: 클라이언트가 생성한 임의의 키. 서버는 이 키를 이용해 응답을 생성.</li>
<li><strong>Sec-WebSocket-Version</strong>: WebSocket 프로토콜의 버전.</li>
</ul>
<p><strong>서버 응답 예시:</strong></p>
<pre><code>HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
</code></pre><ul>
<li><strong>101 Switching Protocols</strong>: 성공적으로 프로토콜이 업그레이드되었음을 나타냄.</li>
<li><strong>Sec-WebSocket-Accept</strong>: 클라이언트의 Sec-WebSocket-Key를 기반으로 생성된 응답 키.</li>
</ul>
<h3 id="2-데이터-전송-data-transfer">2. 데이터 전송 (Data Transfer)</h3>
<blockquote>
<p>WebSocket은 데이터를 프레임 단위로 전송한다. 각 프레임은 데이터의 조각을 담고 있으며, 여러 종류의 프레임이 존재한다.</p>
</blockquote>
<h4 id="프레임-종류">프레임 종류</h4>
<ul>
<li><strong>텍스트 프레임</strong>: 텍스트 데이터를 전송.</li>
<li><strong>바이너리 프레임</strong>: 바이너리 데이터를 전송.</li>
<li><strong>닫기(Close) 프레임</strong>: 연결을 종료.</li>
</ul>
<h4 id="데이터-프레임-구조">데이터 프레임 구조</h4>
<p>WebSocket 프레임은 헤더와 페이로드(Payload)로 구성된다. 헤더는 프레임의 메타데이터를 포함하고, 페이로드는 실제 전송되는 데이터를 포함한다.</p>
<h3 id="3-연결-종료-connection-closure">3. 연결 종료 (Connection Closure)</h3>
<blockquote>
<p>연결을 종료하려면 클라이언트나 서버 중 하나가 닫기(Close) 프레임을 전송해야 한다. 닫기 프레임은 종료 코드와 선택적인 종료 이유를 포함할 수 있다.</p>
</blockquote>
<ul>
<li><strong>연결 종료 절차</strong><ul>
<li>클라이언트나 서버가 닫기 프레임을 전송.</li>
<li>상대방이 닫기 프레임을 수신하고 응답으로 닫기 프레임을 전송.</li>
<li>양측의 연결이 종료되고, 더 이상 데이터가 전송되지 않음.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>