<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on Lunatine&#39;s Box</title>
        <link>/posts/</link>
        <description>Recent content in Posts on Lunatine&#39;s Box</description>
        <generator>Hugo -- gohugo.io</generator>
        <copyright>&lt;a href=&#34;https://creativecommons.org/licenses/by-nc/4.0/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CC BY-NC 4.0&lt;/a&gt;</copyright>
        <lastBuildDate>Fri, 12 Jul 2019 11:00:00 +0900</lastBuildDate>
        <atom:link href="/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>Ubuntu 16.04 설치 실패 (base-files)</title>
            <link>/2019/07/12/ubuntu-xenial-install-error-base-files/</link>
            <pubDate>Fri, 12 Jul 2019 11:00:00 +0900</pubDate>
            
            <guid>/2019/07/12/ubuntu-xenial-install-error-base-files/</guid>
            <description>2019/07/12 13:40 (+9:00) 기준으로 아래 이슈는 수정 되었습니다. launchpad 본 문서는 2019/07/12 11:00 (+9:00) 기준으로 유효한 내용을 담고 있습니다.  증상 Ubuntu 16.04 설치과정에서 패키지를 설치하는 도중에 오류 메시지와 함께 설치가 중단됩니다. 쉘 프롬프트로 진입해서 /var/log/syslog 파일을 확인하면 아래와 같은 메시지가 나타납니다.
Setting up base-files (9.4ubuntu4.9) ... /var/lib/dpkg/info/base-files.postinst: 131: /var/lib/dpkg/info/base-files.postinst: Automatically: not found dpkg: error processing package base-files (--configure): subprocess installed post-installation script returned error exit status 127 Errors were encountered while processing: base-files E: Sub-process /usr/bin/dpkg returned an error code (1)  원인  base-files 9.</description>
            <content type="html"><![CDATA[

<ul>
<li><strong>2019/07/12 13:40 (+9:00)</strong> 기준으로 아래 이슈는 수정 되었습니다. <a href="https://launchpad.net/ubuntu/+source/base-files/9.4ubuntu4.10" target="_blank">launchpad</a></li>
<li>본 문서는 2019/07/12 11:00 (+9:00) 기준으로 유효한 내용을 담고 있습니다.</li>
</ul>

<h2 id="증상">증상</h2>

<p>Ubuntu 16.04 설치과정에서 패키지를 설치하는 도중에 오류 메시지와 함께 설치가 중단됩니다. 쉘 프롬프트로 진입해서 <code>/var/log/syslog</code> 파일을 확인하면 아래와 같은 메시지가 나타납니다.</p>

<pre><code>Setting up base-files (9.4ubuntu4.9) ...
/var/lib/dpkg/info/base-files.postinst: 131: /var/lib/dpkg/info/base-files.postinst: Automatically: not found
dpkg: error processing package base-files (--configure):
subprocess installed post-installation script returned error exit status 127
Errors were encountered while processing:
base-files
E: Sub-process /usr/bin/dpkg returned an error code (1)
</code></pre>

<h2 id="원인">원인</h2>

<ul>
<li>base-files 9.4ubuntu4.9 버전의 패키지에서 post install 처리하는 스크립트에 문제가 있어서 설치가 비정상 적으로 종료 됩니다.</li>
<li>오류 메시지에 나온 <code>Automatically: not found</code>에서 확인 할 수 있는 것 처럼 스크립트에서 Automatically 커맨드를 찾지 못하는 증상입니다.</li>

<li><p>Automatically는 실제로 존재하는 커맨드가 아니라 post install 스크립트의 주석처리 실수로 인한 오류로 <code>/var/lib/dpkg/info/base-files.postinst</code> 파일의 131번 라인에서 아래와 같이 주석처리되어야 할 부분이 누락 된 문제입니다.</p>

<pre><code># Manually inject expected maintainer script contents based on dh_systemd_*
# Automatically added by dh_systemd_start
</code></pre></li>
</ul>

<h2 id="해결-방법">해결 방법</h2>

<p>임시 방편으로 설치 실패 상태에서 <code>Alt+F2</code> 키로 shell로 진입한 후에 base-files.postinst 파일의 131번 라인을 주석처리 해 주고 dpkg로 재설정하여 정상 상태로 변경합니다.</p>

<pre><code>$ chroot /target
$ sed -i 's/^Automatically/# Automatically/g' /var/lib/dpkg/info/base-files.postinst
$ dpkg --configure base-files
$ exit
</code></pre>

<p>위와 같이 설정 한 후에 <code>Alt+F1</code> 키를 눌러 인스톨러로 돌아 온 후에 중단 된 위치부터 시작하면 설치가 진행 됩니다.</p>

<ul>
<li>현재 관련 된 <a href="https://bugs.launchpad.net/ubuntu/+source/base-files/+bug/1836236" target="_blank">launchpad bug report</a>가 있으며 수정 된 버전이 배포 되면 해소 될 것으로 예상합니다.</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>RHEL 8 소개</title>
            <link>/2019/06/21/rhel-8-release/</link>
            <pubDate>Fri, 21 Jun 2019 14:00:00 +0900</pubDate>
            
            <guid>/2019/06/21/rhel-8-release/</guid>
            <description>본 문서는 RHEL8에서 새롭게 변경 된 정보를 위주로 간략히 정리한 문서입니다. 변경 된 모든 내용을 다루지는 않으며 기존에 사용하던 환경과 관련성이 높은 부분만을 요약하였습니다. 전체 내용 및 자세한 사항은 릴리스노트 문서를 참고하세요.
참고로, 릴리스노트 한국어판도 있지만 영문 버전보다 볼륨이 적습니다.
1. 저장소 구조 RHEL 8에서는 BaseOS, AppStream 두 가지 형태의 저장소를 제공합니다. BaseOS는 기존 RPM 포맷의 패키지를 제공하며 AppSteam은 RPM과 RPM module을 제공합니다. AppStream은 사용자가 다양한 버전의 어플리케이션을 선택적으로 설치하고 사용 할 수 있도록 하기 위해서 구성된 저장소 입니다.</description>
            <content type="html"><![CDATA[

<p>본 문서는 RHEL8에서 새롭게 변경 된 정보를 위주로 간략히 정리한 문서입니다. 변경 된 모든 내용을 다루지는 않으며 기존에 사용하던 환경과 관련성이 높은 부분만을 요약하였습니다. 전체 내용 및 자세한 사항은 <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/pdf/8.0_release_notes/Red_Hat_Enterprise_Linux-8-8.0_release_notes-en-US.pdf" target="_blank">릴리스노트</a> 문서를 참고하세요.</p>

<p>참고로, <a href="https://access.redhat.com/documentation/ko-kr/red_hat_enterprise_linux/8/html/8.0_release_notes/index" target="_blank">릴리스노트 한국어판</a>도 있지만 영문 버전보다 볼륨이 적습니다.</p>

<h2 id="1-저장소-구조">1. 저장소 구조</h2>

<p>RHEL 8에서는 <code>BaseOS</code>, <code>AppStream</code> 두 가지 형태의 저장소를 제공합니다. BaseOS는 기존 RPM 포맷의 패키지를 제공하며 AppSteam은 RPM과 RPM module을 제공합니다. AppStream은 사용자가 다양한 버전의 어플리케이션을 선택적으로 설치하고 사용 할 수 있도록 하기 위해서 구성된 저장소 입니다.</p>

<p>예를들면, PostgreSQL의 경우 v10과 v9.6을 제공하고 사용자는 원하는 버전을 기준으로 설치하고 사용 할 수 있습니다. 즉, 과거에 단일 패키지로 구성되어 최신버전을 제공하던 형태에서 메이저 버전을 선택적으로 지정해서 사용 할 수 있습니다.</p>

<p>이와 별개로 직접적인 패키지 지원이 없는 소프트웨어에 대해서 <code>CodeReady Linux Builder</code> 저장소를 제공합니다.</p>

<h2 id="2-인스톨러">2. 인스톨러</h2>

<h3 id="2-1-인스톨러-부트-옵션">2-1. 인스톨러 부트 옵션</h3>

<ul>
<li>net.ifnames.prefix

<ul>
<li>네트워크 인터페이스에 대한 prefix 스키마를 지정해서 사용 할 수 있습니다.</li>
</ul></li>
<li>inst.stage2

<ul>
<li>stage2 이미지를 여러 위치(네트워크 위치)에 배치하여 사용 할 수 있습니다.</li>
</ul></li>
<li>inst.addrepo

<ul>
<li>base 저장소 외에 추가 저장소를 지정해서 설치 할 수 있습니다.</li>
</ul></li>
</ul>

<h3 id="2-2-kickstart-변경-점">2-2. Kickstart 변경 점</h3>

<ul>
<li>auth, authconfig 대신에 <strong>authselect</strong>를 사용합니다.</li>
<li>install 커맨드 대신에 직접적인 서브 명령어를 사용합니다.</li>
<li>그 외 더 이상 사용되지 않는 명령/옵션

<ul>
<li>부트 옵션에서 <code>inst.ksstrict</code>를 설정하면 아래 명령/옵션은 경고 대신에 에러로 처리됩니다.</li>
<li>device</li>
<li>deviceprobe</li>
<li>dmraid</li>
<li>lilo</li>
<li>lilocheck</li>
<li>mouse</li>
<li>multipath</li>
<li>bootloader &ndash;upgrade</li>
<li>ignoredisk &ndash;insteractive</li>
<li>partition &ndash;active</li>
<li>reboot &ndash;kexec</li>
</ul></li>
<li>삭제 된 명령/옵션

<ul>
<li>upgrade</li>
<li>btrfs

<ul>
<li>part/partition btrfs</li>
<li>part &ndash;fstype btrfs / partition &ndash;fstype btrfs</li>
<li>logvol &ndash;fstype btrfs</li>
<li>raid &ndash;fstype btrfs</li>
</ul></li>
<li>unsupported_hardware</li>
</ul></li>
<li>새로운 명령/옵션

<ul>
<li>authselect

<ul>
<li>인증관련 정보를 설정하며 auth, authconfig를 대체합니다.</li>
</ul></li>
<li>module

<ul>
<li>AppStream에서 설치/활성화 할 모듈 패키지를 지정합니다.</li>
</ul></li>
</ul></li>
</ul>

<h2 id="3-소프트웨어-관리">3. 소프트웨어 관리</h2>

<h3 id="3-1-yum4-제공">3-1. YUM4 제공</h3>

<p>새로운 YUM은 DNF에 기반하여 동작하며 기존 YUM3와 호환성은 제공되고 있습니다. DNF 기반이기 때문에 module 설치를 제공하며 이는 기존보다 빠른 설치작업을 수행 할 수 있게 합니다.</p>

<h3 id="3-2-yum-conf-에서-삭제된-옵션">3-2. yum.conf 에서 삭제된 옵션</h3>

<p>변경 된 커맨드가 많지만 자주 사용되는 옵션 중에서 삭제 된 옵션은 아래와 같습니다.
- yum.conf
    - group_command
    - logfile
- repos.d/*.repo
    - async
    - repositoryid</p>

<h3 id="3-3-그외">3-3. 그외</h3>

<p>기존에 동일한 이름과 버전의 패키지가 여러 저장소에서 제공 되면 한 개의 저장소 패키지만 표시했지만 신규 YUM에서는 각 저장소별로 패키지명을 출력합니다. (yum list)</p>

<h2 id="4-시스템-서비스">4. 시스템 서비스</h2>

<h3 id="4-1-시간-동기화">4-1.  시간 동기화</h3>

<p>기존에는 ntp 데몬을 활용하여 시간 동기화 작업을 수행했지만 RHEL 8에서는 RHEL/CentOS 7 처럼 권고사항을 넘어 기본으로 <strong>chrony</strong>가 적용되었습니다. 따라서, 기존 <code>/etc/ntpd.conf</code> 설정 파일은 <code>/etc/chrony.conf</code> 설정 파일로 대체 되어야 하며 chrony에 맞게 설정을 변경해야 합니다.</p>

<p>chrony는 기본적으로 <code>leapsectz</code> (윤초)를 지원합니다.</p>

<h3 id="4-2-bind">4-2. BIND</h3>

<p>RHEL8는 bind 9.11을 제공합니다.</p>

<ul>
<li>Domain Name System Cookie를 named와 dig에서 전송합니다.</li>
<li>DNSSEC 유효성을 체크하기 위한 <strong>delv</strong> (doamin entity lookup and validation)툴이 제공됩니다.</li>
<li>여러 DNS Query를 전송 할 수 있는 <strong>mdig</strong>이 제공됩니다.</li>
<li>Recursive resolver 성능 개선을 위한 <code>prefetch</code> 옵션이 제공됩니다.</li>
<li>동일한 Zone 설정 파일에 대하여 권한에 따라 다양한 뷰를 제공할 수 있는 <code>in-view</code> 옵션이 추가되었습니다.</li>
<li>Zone에 대한 최대 TTL를 지정할 수 있는 <code>max-zone-ttl</code> 옵션이 추가되었습니다.</li>
<li>쿼리에 제약을 둘 수 있는 quota 기능이 추가되었습니다.</li>
<li><strong>nslookup</strong> 명령이 IPv4(A)와 IPv6(AAAA)를 기본적으로 모두 조회합니다</li>
<li><strong>named</strong> 데몬이 기본적으로 IPv4, IPv6 인터페이스를 모두 Listen 합니다.</li>
<li><strong>named</strong> 데몬의 GeoIP 지원이 제거되었습니다.</li>
</ul>

<h3 id="4-3-tuned">4-3. Tuned</h3>

<p>Tuned 프로파일 적용 방식이 변경되었습니다. <code>tuned-adm recommend</code> 명령은 아래 규칙에 따라서 프로파일이 적용됩니다.</p>

<ul>
<li>syspurpose role에 <code>atomic</code>이 포함되어있을 경우에 아래 프로파일이 적용됩니다.

<ul>
<li>물리서버(bare metal)는 <code>atomic-host</code></li>
<li>가상서버는 <code>atomic-guest</code></li>
</ul></li>
<li>가상 서버는 기본 적으로 <code>virtual-guest</code> 프로파일이 적용됩니다.</li>
<li>syspurpose role에 <code>desktop</code>, <code>workstation</code>이 있고 섀시 타입이 노트북, 랩탑, 포터블인 경우에는 <code>balanced</code> 프로파일이 적용됩니다.

<ul>
<li>섀시 타입은 dmideocde에서 제공되는 정보에 기반합니다</li>
</ul></li>
<li>위 사항 중에서 해당 되는 사항이 없다면 <code>througput-performance</code> 프로파일이 적용됩니다.</li>
</ul>

<h3 id="4-4-rsyslog">4-4. RSYSLOG</h3>

<ul>
<li>rsyslog 버전이 업그레이드 됨에 따라 설정파일은 <strong>기존 버전의 설정 파일과 호환되지 않습니다</strong>.</li>
<li>journald와 중복된 레코드를 최소화 하기 위한 <code>imjournal</code> 옵션이 추가 되었습니다. (참고: <a href="https://access.redhat.com/articles/4058681" target="_blank">KB</a>)

<ul>
<li>위에 링크된 문서는 journald가 디스크를 사용하지 않도록 설정하는 내용을 담고 있습니다. 따라서, journald의 모든 내용은 휘발성인 메모리에만 저장됩니다.</li>
</ul></li>
</ul>

<h3 id="4-5-그-외">4-5. 그 외</h3>

<p>시스템 명령, 데몬 중에서 현재 환경과 관련성이 있는 항목에 대해서 소개합니다.</p>

<ul>
<li>acpid

<ul>
<li>-d (debug) 옵션이 -f 옵션에 적용되지 않습니다.</li>
</ul></li>
<li>GeoIP

<ul>
<li>삭제 되었습니다.</li>
</ul></li>
<li>grep

<ul>
<li>현재 로케일로 인코딩 되지 않은 파일은 바이너리로 취급합니다</li>
<li>유효하지 않은 UTF-8 데이터에 대해서 <strong>-P</strong> 옵션이 에러를 출력하지 않습니다.</li>
<li><code>grep -z</code> 는 <code>\200</code> 바이트를 더이상 바이너리 데이터로 취급하지 않습니다.</li>
</ul></li>
<li>powertop

<ul>
<li>-d 옵션 삭제</li>
<li>-h 대신에  &ndash;html을 사용 (-h는 &ndash;help의 alias로 변경)</li>
<li>-u 옵션 삭제</li>
</ul></li>
<li>wireshark

<ul>
<li>-H 옵션이 SHA256, RIPEMD160, SHA1 해시로 바뀌었습니다. (즉, MD5 삭제되고 SHA256 추가 됨)</li>
</ul></li>
</ul>

<h2 id="5-보안">5. 보안</h2>

<h3 id="5-1-시스템-암호화-정책">5-1. 시스템 암호화 정책</h3>

<ul>
<li><p>삭제 된 암호화 스위트</p>

<ul>
<li>DES</li>
<li>MD5</li>
<li>SSLv2</li>
<li>SSLv3</li>
<li>224bits 미만 모든 ECC curves</li>
<li>모든 binary field ECC curves</li>
</ul></li>

<li><p>비활성화 된 암호화 스위트</p>

<ul>
<li>DH with parameters &lt; 1024 bits</li>
<li>RSA - 1024bits 미만 키 사이즈</li>
<li>Camellia</li>
<li>ARIA</li>
<li>SEED</li>
<li>IDEA</li>
<li>SHA-384 HMAC를 사용하는 TLS CBC</li>
<li>AES-CCM8</li>
<li>TLS 1.3과 호환되지 않는 모든 ECC curves</li>
<li>IKEv1</li>
</ul></li>

<li><p><strong>TLS v1.3</strong>을 지원합니다.</p></li>

<li><p>TLS v1.0과 v1.1은 기본적으로 비활성화 됩니다.</p>

<ul>
<li><code>update-crypto-policies --set LEGACY</code>로 시스템 보안 레벨을 조절하여 사용 가능합니다.</li>
</ul></li>

<li><p>DSA 암호화는 비활성화 됩니다.</p></li>
</ul>

<h3 id="5-3-ssh">5-3. SSH</h3>

<p>OpenSSH가 7.8p1 버전으로 제공됩니다. 아래와 같은 변경 사항이 있습니다.</p>

<ul>
<li>SSH v1 프로토콜을 지원하지 않습니다</li>
<li>hmac-ripemd160 메시지 인증코드를 지원하지 않습니다.</li>
<li>삭제 된 암호화

<ul>
<li>Blowfish</li>
<li>CAST</li>
<li>RC4(arcfour)

<ul>
<li>기존에 rsync를 ssh에 연동하여 사용하셨던 분들 중에서 빠른 파일 전송을 위해서 arcfour를 사용하셨던 분은 다른 가벼운 암호화 방법을 선택해야합니다.</li>
</ul></li>
</ul></li>
<li>UseDNS 옵션의 기본값이 <code>no</code> 입니다.</li>
<li>DSA public key 알고리즘이 기본적으로 비활성화 됩니다.</li>
<li>UsePrivilegeSeparation=sandbox 옵션을 끌 수 없습니다.</li>
<li>1024bits 이상 크기를 가진 RSA 키만 유효합니다.</li>
<li>시스템 암호화 라이브러리와 별개인 libssh가 적용 됩니다.</li>
<li>libssh2 라이브러리는 없습니다.</li>
</ul>

<h3 id="5-4-audit-3-0">5-4. Audit 3.0</h3>

<p><strong>audispd</strong> 패키지는 <strong>auditd</strong>로 통합되었으며 설정파일도 <code>auditd.conf</code> 설정파일로 합쳐졌습니다.</p>

<h3 id="5-5-그-외">5-5. 그 외</h3>

<ul>
<li>useradd, groupadd는 숫자로 된 사용자명과 그룹명을 허용하지 않습니다.

<ul>
<li>shadow-utils 패키지에서 지원하지 않습니다.</li>
</ul></li>
<li><code>/etc/securetty</code>가 기본적으로 비활성화 처리 됩니다.

<ul>
<li>사용하고자 하는 경우에는 <strong>pam_securetty.so</strong>를 활성화하고 개별 설정 해야합니다.</li>
</ul></li>
<li><strong>cryto-utils</strong> 패키지가 삭제 되고 openssl, gnutls-utils, nss-tools로 대체 되었습니다.</li>
</ul>

<h2 id="6-네트워크">6. 네트워크</h2>

<h3 id="6-1-networkmanager">6-1. NetworkManager</h3>

<p>기존 네트워크 설정파일을 관리하던 <code>network-scripts</code>가 기본적으로 삭제되고 NetworkManager로 대체 되었습니다. 또한 ifup, ifdown 명령도 NetworkManager의 nmcli를 호출하는 커맨드로 변경되었습니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ alternatives --display ifup
ifup - status is auto.
 link currently points to /usr/libexec/nm-ifup
/usr/libexec/nm-ifup - priority <span style="color:#ae81ff">50</span>
 slave ifdown: /usr/libexec/nm-ifdown
Current <span style="color:#e6db74">`</span>best<span style="color:#960050;background-color:#1e0010">&#39;</span> version is /usr/libexec/nm-ifup.</code></pre></div>
<p>NetworkManager에서 직접 ifup, ifdown 커맨드를 대체하기 때문에 NetworkManager를 사용하는 시스템임에도 network-scripts 안의 설정 파일을 보면 <code>NM_CONTROLLED</code> 옵션이 없는 걸 알 수 있습니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ cat ifcfg-enp0s3
TYPE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Ethernet&#34;</span>
PROXY_METHOD<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;none&#34;</span>
BROWSER_ONLY<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;no&#34;</span>
BOOTPROTO<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;dhcp&#34;</span>
DEFROUTE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;yes&#34;</span>
IPV4_FAILURE_FATAL<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;no&#34;</span>
IPV6INIT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;yes&#34;</span>
IPV6_AUTOCONF<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;yes&#34;</span>
IPV6_DEFROUTE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;yes&#34;</span>
IPV6_FAILURE_FATAL<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;no&#34;</span>
IPV6_ADDR_GEN_MODE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;stable-privacy&#34;</span>
NAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;enp0s3&#34;</span>
UUID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;68a55877-13f1-46ed-8e39-4ab51f141f35&#34;</span>
DEVICE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;enp0s3&#34;</span>
ONBOOT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;yes&#34;</span></code></pre></div>
<p>만약, 기존 방식의 스크립트 관리를 적용하기 위해서는 별도로 패키지를 설치하여 NetworkManager의 ifup, ifdown을 대체해야 합니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ sudo yum -y install network-scripts</code></pre></div>
<p>NetworkManager가 SR-IOV를 지원하기 시작했으며 와일드카드 매칭 방식의 인터페이스 지정이 가능해 졌습니다. ethtool에서 제공하던 offload 관련 설정 또한 제공 됩니다.</p>

<p>DHCP 환경에 대해서는 기존에 dhclient를 사용하던 것 대신 internal 플러그인으로 DHCP 지원을 합니다. dhclient를 사용하기 위해서는 별도로 설정을 해주어야 합니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#75715e">;/etc/NetworkManager/NetworkManager.conf</span>
<span style="color:#66d9ef">[main]</span>
<span style="color:#a6e22e">dhcp</span><span style="color:#f92672">=</span><span style="color:#e6db74">dhclient</span></code></pre></div>
<h3 id="6-2-패킷-필터링">6-2. 패킷 필터링</h3>

<p>지금까지 사용되던 iptables 대신에 <strong>nftables</strong> 프레임워크가 적용되었습니다. nftables의 설정 형태는 iptables와 다르기 때문에 이를 변환해 주는 유틸리티도 제공합니다.</p>

<p>또한, iptables, ip6tables, ebtables, arptables 툴은 이름이 동일한 nftables 기반의 명령으로 대체 되었습니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ iptables --version
iptables v1.8.0 <span style="color:#f92672">(</span>nf_tables<span style="color:#f92672">)</span></code></pre></div><div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ nft list tables
nft list tables
table ip filter
table ip6 filter
table bridge filter
table ip security
table ip raw
table ip mangle
table ip nat
table ip6 security
table ip6 raw
table ip6 mangle
table ip6 nat
table bridge nat
table inet firewalld
table ip firewalld
table ip6 firewalld</code></pre></div>
<h3 id="6-3-tcp-개선">6-3. TCP 개선</h3>

<p>RHEL8의 기본 커널은 4.18에 기초하고 있기 때문에 새로운 TCP 혼잡제어 알고리즘인 <strong>TCP BBR</strong>과 <strong>TCP NV</strong>(New Vegas)를 제공합니다.</p>

<pre><code>net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_notsent_lowat = 16384
</code></pre>

<ul>
<li>참고

<ul>
<li>BBR - <a href="https://www.vultr.com/docs/how-to-deploy-google-bbr-on-centos-7" target="_blank">How to Deploy Google BBR on CentOS 7 - Vultr.com</a></li>
<li>NV -  <a href="https://access.redhat.com/solutions/3717541" target="_blank">https://access.redhat.com/solutions/3717541</a></li>
</ul></li>
</ul>

<h2 id="7-커널">7. 커널</h2>

<p>RHEL8은 Fedora 28의 4.18 커널을 기반으로 하고 있습니다.</p>

<h3 id="7-1-tech-preview">7-1. Tech Preview</h3>

<p>Redhat에서 Tech Preview 형태로 지원하는 기능에 대해서 나열합니다. 기존에 Tech Preview였던 기능들이 추후 마이너 릴리즈로 업데이트 되면서 정식으로 적용된 적이 있기 때문에 마이너 버전 업데이트에 대해서도 지속적인 확인이 필요합니다.</p>

<ul>
<li>Control group v2</li>
<li>eBPF</li>
<li>BCC</li>
</ul>

<h3 id="7-2-memory">7-2. Memory</h3>

<p>5 레벨 페이지 테이블을 제공합니다. 4 레벨에서 5 레벨로 페이지 테이블의 관리 단계가 늘어났기 때문에 기존에 64TiB까지 지원하던 물리메모리는 4PiB까지 늘어났습니다. 가상 메모리 주소 기준으로는 128PiB까지 메모리 주소를 제공합니다. (57bit 가상주소, 52bit 물리 주소)</p>

<h3 id="7-3-ebpf">7-3. eBPF</h3>

<p><strong>bpftool</strong>을 제공하며 eBPF 관련된 기술이 Tech Preview로 제공됩니다. (BCC - BPF Compiler Connection - 포함)</p>

<h3 id="7-4-부팅-설정">7-4. 부팅 설정</h3>

<p>RHEL8은 BLS(Boot Loader Specification)이 적용되었습니다. 따라서, 기존처럼 /boot/grub2/grub.cfg에 커널 관련 설정이 들어있지 않으며 BLS 파일 포맷에 따라서 <code>/boot/loader/entries</code>에  부팅을 위한 커널설정이 들어있습니다.</p>

<ul>
<li>참고:  <a href="https://systemd.io/BOOT_LOADER_SPECIFICATION" target="_blank">systemd :: The Boot Loader Specification</a></li>
</ul>

<p><strong>grubby</strong>가 BLS를 지원합니다.</p>

<p>참고로, BLS를 위해서  <a href="https://github.com/bmr-cymru/boom" target="_blank">Boom</a> 부트 매니저가 지원됩니다. Boom 부트매니저는 부트로더 설정 자체를 수정하는 것이 아닌 프로파일, 엔트리 정보에 대해서는 수정/추가를 제공합니다. cli 형태로 제공되며 Python API도 제공합니다.</p>

<h3 id="7-5-기타">7-5. 기타</h3>

<ul>
<li>NUMA 노드를 8개 까지 지원합니다. (기존 4개)</li>
<li>Spectre V2 관련

<ul>
<li>IBRS(Indirect Branch Restricted Speculation) 적용에서 retpoline 적용으로 기본 적용 값의 변경</li>
<li>커널부트에서 spectre_v2=ibrs로 IBRS 적용 가능</li>
<li>/sys/devices/system/cpu/vulnerabilities/spectre_v2 키가 추가 됨</li>
<li>개선된 IBRS 제공</li>
<li>IOMMU Passthrough가 기본으로 활성화 (AMD)</li>
</ul></li>
</ul>

<h2 id="8-하드웨어">8. 하드웨어</h2>

<p>하드웨어  지원이 종료 된 장치 중에서 현재 환경과 관련있는 항목만 나열합니다.</p>

<h3 id="8-1-드라이버-제거">8-1. 드라이버 제거</h3>

<ul>
<li>arcmsr</li>
<li>sata_nv</li>
<li>e1000

<ul>
<li>e1000e를 사용해야 합니다</li>
</ul></li>
<li>isci</li>
<li>mpt

<ul>
<li>mptbase</li>
<li>mptctl</li>
<li>mptsas</li>
<li>mptscsih</li>
<li>mptspi</li>
</ul></li>
<li>qla3xxx</li>
</ul>

<h3 id="8-2-드라이버-지원-대상-변경">8-2. 드라이버 지원 대상 변경</h3>

<p>아래 드라이버들이 일부 모델에 대해서 지원을 중단 하였습니다. 구형 장비에 설치 할 때 릴리즈 노트를 참고하여 사용가능 여부를 판단해야 합니다.</p>

<ul>
<li>aacraid</li>
<li>mpt2sas

<ul>
<li>SAS2004, SAS2008, SAS2108_1~3, SAS2116_1~2, SSS6200 제거 됨</li>
</ul></li>
<li>megaraid_sas

<ul>
<li>PERC5 제거</li>
<li>SAS1078R/DE, SAS1064R, SAS1078GEN2, SAS1079GEN2 제거</li>
</ul></li>
<li>qla2xxx

<ul>
<li>QLE220, QLE81xx, QLE10000, QLE84xx, QLE8000, QLE82xx 제거</li>
</ul></li>
<li>qla4xxx

<ul>
<li>QLOGIC_ISP8022/8324/8042 제거</li>
</ul></li>
<li>lpfc</li>
</ul>

<h3 id="8-3-fcoe">8-3. FCoE</h3>

<p><strong>fcoe.ko</strong> 모듈이 삭제 되었기 때문에 Fibre Channel over Ethernet은 사용할 수 없습니다.</p>

<h2 id="9-파일시스템">9. 파일시스템</h2>

<h3 id="9-1-btrfs">9-1. btrfs</h3>

<ul>
<li>RHEL8에서 btrfs 지원을 중단하였습니다. 따라서, btrfs 관련된 커널 모듈 및 패키지가 제공되지 않습니다.</li>
</ul>

<h3 id="9-2-xfs">9-2. XFS</h3>

<p>XFS에서 Shared C.O.W 기능을 지원합니다. 이는 2개 이상의 파일에 대해서 데이터 블록을 공유하여 참조하고 변경 점이 있을 때에만 C.O.W 처럼 처리를 합니다.</p>

<p>이러한 기능이 갖는 장점은 아래와 같습니다.</p>

<pre><code>- 공유 복사본 생성 시 디스크  I/O가 발생하지 않습니다.
- 공간이 절약됩니다. (공유 블럭이 추가 공간을 소비하지 않음)
- 내부적으로 공통 블럭을 공유하지만 일반 파일처럼 동작합니다.
</code></pre>

<p>사용자 영역에서 <code>cp --reflink</code> 명령이나 파일 스냅샷 처리 등으로 활용 할 수 있습니다. 이러한 기능은 OverlayFS, NFS에서 효율을 높여 줄 수 있습니다.</p>

<p>단, DAX(Direct Access) 장치는 현재 지원하지 않기 때문에 해당 기능을 비활성화 하고 포맷해야 합니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ mkfs.xfs -m reflink<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> &lt;block-device&gt;</code></pre></div>
<p>XFS의 최대 크기는 1024TiB로 확장되었습니다. (기존 500TiB)</p>

<h3 id="9-3-ext4">9-3. ext4</h3>

<p>ext4가 메타데이터에 대해서 체크섬(checksum)을 지원합니다.</p>

<h3 id="9-4-nfs">9-4. NFS</h3>

<p>기존에 NFS 환경 설정을 위해서 사용하던 <code>/etc/sysconfig/nfs</code>가 제거 되고 해당 내용은 <code>/etc/nfs.conf</code> 설정 파일로 변경 되었습니다. nfs.conf 설정파일은 고유의 설정 포맷이 있기 때문에 기존 설정을 옮길 때 유의해야 합니다.</p>

<p>NFS 관련 패키지의 서비스 구성이 변경 되었습니다.</p>

<ul>
<li>nfs.service -&gt; nfs-server.service</li>
<li>nfs-secure.service -&gt; rpc-gssd.service</li>
<li>rpcgssd.service -&gt; rpc-gssd.service</li>
<li>nfs-idmap.service -&gt; nfs-idmapd.service</li>
<li>rpcidmapd.service -&gt; nfs-idmapd.service</li>
<li>nfs-lock.service -&gt; rpc-statd.service</li>
<li>nfslock.service -&gt; rpc-statd.service</li>
</ul>

<h2 id="10-스토리지">10. 스토리지</h2>

<h3 id="10-1-신규-기능">10-1. 신규 기능</h3>

<ul>
<li><p><a href="https://stratis-storage.github.io/" target="_blank">Stratis Storage</a>를 제공합니다.</p>

<ul>
<li>Thin provisioning과 스냅샷을 관리할 수 있습니다.</li>
<li>자동 증가되는 파일시스템을 구성할 수 있습니다.</li>
<li>Tech Preview 형태로 제공 됩니다.</li>
</ul></li>

<li><p>파일시스템 암호화</p>

<ul>
<li>LUKS2를 볼륨 암호화의 기본 포맷으로 사용합니다.</li>
</ul></li>
</ul>

<h3 id="10-2-multiqueue-스케줄러">10-2. Multiqueue 스케줄러</h3>

<p>최근 메모리 기반 스토리지가 많이 사용 됨에 따라 RHEL/CentOS 7에서 부터 조금씩 지원되던 멀티큐 I/O 스케줄러에 대해서 기본 설정이 추가되었습니다.</p>

<p>SCSI Multiqueue (scsi-mq) 드라이버가 기본적으로 활성화 되어서 적용 됩니다. (즉, RHEL/CentOS7 에서 scsi_mod.use_blk_mq=y 옵션을 적용하던 부분이 기본 값이 되었습니다)</p>

<h3 id="10-3-기타">10-3. 기타</h3>

<ul>
<li>dmraid 패키지가 삭제되었습니다.

<ul>
<li>mdadm 패키지를 사용하세요</li>
</ul></li>
<li>LVM

<ul>
<li>공유 볼륨을 위한 clvmd가 삭제 되었고 lvmlockd를 대신 사용합니다.</li>
<li>메타 데이터 캐싱을 위한 lvmetad 데몬이 제거 되었습니다.

<ul>
<li>메타 데이터 캐싱은 LVM에서 무조건 활성화 된 상태로 적용 됩니다.</li>
</ul></li>
<li>GFS Pool 볼륨 또는 LVM1 메타데이터에 대해서 지원이 중단 됩니다.</li>
<li>LVM의 Python 바인딩인 lvm2-python-libs가 삭제 되었습니다. 아래 방법이 권장됩니다.

<ul>
<li>lvm2-dbusd 서비스 API를 사용</li>
<li>LVM cli가 JSON 포매팅을 지원하기 때문에 cli를 사용</li>
<li>C/C++로 마이그레이션 해서 libblockdev 라이브러리를 사용</li>
</ul></li>
</ul></li>
</ul>

<h2 id="11-그-외-os-구성-요소-및-서비스">11. 그 외 OS 구성 요소 및 서비스</h2>

<h3 id="11-1-glibc">11-1. glibc</h3>

<p>기존 glibc-common 패키지의 로케일/번역 관련된 부분이 <code>glibc-langpack-&lt;CODE&gt;</code> 형태로 분리되어서 제공 됩니다.</p>

<h3 id="11-2-nobody">11-2. nobody</h3>

<p>기존에 ID 99번을 사용하던 nobody 유저/그룹이 nfsnobody가 사용하던 65534로 변경되면서 두 ID가 합쳐졌습니다. 따라서, RHEL8에는 nfsnobody는 존재하지 않고 nobody 유저/그룹은 65534 ID를 갖게 됩니다.</p>

<h3 id="11-3-버전-컨트롤">11-3. 버전 컨트롤</h3>

<p><code>cvs</code>, <code>rcs</code>는 제거되며 <code>git</code>, <code>mecurial</code>, <code>subversion</code> 만 제공 됩니다.</p>

<h3 id="11-4-python-3">11-4. Python 3</h3>

<p>RHEL8을 구성하는 Python 구현이 모두 Python 3 기준으로 변경 되었습니다. 따라서, Python2와 Python3를 구분짓기 위해 일반적으로 Python 생태계에서 권고되는 것 처럼 버전을 붙인 명령을 사용해야 합니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e"># for Python 2.7</span>
$ python2

<span style="color:#75715e"># for Python 3.6</span>
$ python3</code></pre></div>
<p>이로인해 RHEL8은 기본적으로 <strong>python</strong> 명령이 없습니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ python
-bash: python: command not found</code></pre></div>
<p>버전이 명시되지 않은 python 명령을 사용하고자 한다면 <strong>alternatives</strong>로 별도 설정해 주어야 합니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ alternatives --set python /usr/bin/python3</code></pre></div>
<p>또한, python을 사용하는 어플리케이션에 대한 RPM 패키징 작업을 할 때에는 python 버전을 명시적으로 지정해서 패키지 해야 합니다.</p>

<p>아래와 같은 형태의 Hashbang은 에러를 일으킵니다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/usr/bin/python
</span><span style="color:#75715e"></span><span style="color:#75715e">#!/usr/bin/env python</span></code></pre></div>
<h3 id="11-5-php-ruby-perl-node-js">11-5. PHP, Ruby, Perl, Node.js</h3>

<p>생략합니다. <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/pdf/8.0_release_notes/Red_Hat_Enterprise_Linux-8-8.0_release_notes-en-US.pdf" target="_blank">릴리스노트</a> 문서를 참고하세요.</p>

<ul>
<li>Node.js 10이 RHEL8에서 새롭게 제공됩니다.</li>
<li>Ruby 2.5가 제공됩니다.</li>
<li>PHP 7.2가 제공됩니다.</li>
<li>Perl 5.26이 제공됩니다.</li>
</ul>

<h3 id="11-6-apache-http-server">11-6. Apache HTTP Server</h3>

<p>Apache HTTP Server 2.4.37 이 제공됩니다.</p>

<ul>
<li>HTTP/2 사용을 위해서는 mod_http2 패키지 모듈을 설치해서 사용해야 합니다</li>
<li><code>mod_md</code> 모듈이 추가 되었습니다. (ACME 프로토콜 SSL/TLS 인증서 서비스용)</li>
<li>ListenFree 옵션이 제공 됩니다. (네트워크 인터페이스 상태와 무관한 바인딩을 위해)</li>
<li>아래 모듈이 삭제되었습니다.

<ul>
<li><code>mod_file_cache</code></li>
<li><code>mod_nss</code></li>
<li><code>mod_perl</code></li>
</ul></li>
<li><code>mod_wsgi</code> 가 Python3만 지원합니다.</li>
<li>기존 prefork, worker 대신에 <strong>event</strong> MPM이 기본으로 적용됩니다.</li>
<li><code>/etc/sysconfig/httpd</code> 설정 파일이 더 이상 유효하지 않습니다.</li>
<li>httpd 서비스 종료는  <code>graceful stop</code>을 기본으로 사용합니다.</li>
<li><code>mod_auth_kerb</code>는 <code>mod_auth_gssapi</code>로 대체되었습니다.</li>
</ul>

<h3 id="11-7-apache-tomcat">11-7. Apache Tomcat</h3>

<p>제거 되었습니다. JBoss Web Server를 권장 합니다.</p>

<h3 id="11-8-caching-server">11-8. Caching Server</h3>

<ul>
<li><a href="https://varnish-cache.org/" target="_blank">Varnish Cache</a> 6.0 이 제공됩니다.</li>
<li>Squid 4.4 가 제공 됩니다.</li>
</ul>

<h3 id="11-9-db">11-9. DB</h3>

<p>아래와 같은 DB를 제공합니다.</p>

<ul>
<li>MySQL 8.0

<ul>
<li>권한모음과 같은 role을 지원합니다.</li>
<li>기본 캐릭터셋이 latin1에서 utf8mb4로 변경 되었습니다.</li>
<li>InnoDB가 NOWAIT과 SKIP LOCKED 옵션을 지원합니다.</li>
<li>JSON 함수기능의 개선</li>
<li>mariadb-connector-c 패키지가 공통 클라이언트 라이브러리를 제공하여 이 라이브러리를 사용하면 MySQL, MariaDB에 모두 사용가능한 어플리케이션을 만들 수 있습니다.</li>
<li>RHEL8의 MySQL은 <code>caching_sha2_paswd</code> 대신에 <code>mysql_native_passwd</code>를 기본 인증 플러그인으로 사용합니다.</li>
</ul></li>
<li>MariaDB 10.3

<ul>
<li>FOR 루프</li>
<li>투명화 컬럼</li>
<li>InnoDB를 위한 인스턴트 ADD COLUMN</li>
<li>병렬 리플리케이션</li>
<li>멀티소스 리플리케이션</li>
</ul></li>
<li>PostgreSQL 9.6

<ul>
<li>scan, join, aggregate에 대한 병렬 실행</li>
<li>동기화 복제의 개선</li>
<li>join, sort, UPDATE, DELETE에 대한 postres_fdw 드라이버 지원</li>
</ul></li>
<li>PostgreSQL 10

<ul>
<li>SCRAM-SHA-256 메커니즘 기반의 암호 인증 제공</li>
<li>선언적인 테이블 분할</li>
<li>쿼리 병렬처리 향상</li>
<li>상세: <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html-single/deploying_different_types_of_servers/index#using-postgresql" target="_blank">RHEL8 Deploy Guides</a></li>
</ul></li>
<li>Redis 5</li>
<li>MongoDB는 지원하지 않습니다. (라이센스 이슈)</li>
</ul>

<h3 id="11-10-컴파일러">11-10. 컴파일러</h3>

<ul>
<li>gcc 8.2 를 제공합니다.</li>
<li>AppStream에서 아래 개발 툴셋을 제공합니다.

<ul>
<li>Clang / LLVM Toolset 7.0.1</li>
<li>Rust Toolset 1.31</li>
<li>Go Toolset 1.11.5</li>
<li>java-11-openjdk</li>
<li>java-1.8.0-openjdk</li>
<li>icedtea-web</li>
<li>ant 1.10</li>
<li>maven</li>
<li>scala</li>
</ul></li>
</ul>

<h3 id="11-11-웹-콘솔">11-11. 웹 콘솔</h3>

<p><a href="https://cockpit-project.org/" target="_blank">Cockpit</a>이 RHEL8 기본 웹콘솔로 제공 됩니다.</p>

<h3 id="11-12-컨테이너">11-12. 컨테이너</h3>

<ul>
<li>RHEL 8에는 docker가 포함되어있지 않습니다.

<ul>
<li>podman, buildah, skopeo, runc 를 사용하세요</li>
</ul></li>
</ul>

<h3 id="11-13-패키지-변경">11-13. 패키지 변경</h3>

<p>현재 운영 환경과 관련있는 패키지에 대한 변경 목록입니다. 전체 목록은 공식 문서를 참고하세요.</p>

<ul>
<li>abrt-python -&gt; python3-abrt</li>
<li><strong>dhclient -&gt; dhcp-client</strong></li>
<li>dracut -&gt; dracut, dracut-live</li>
<li><strong>git -&gt; git, git-core, git-core-doc, git-subtree</strong></li>
<li>glibc -&gt; glibc, glibc-all-langpacks, glibc-locale-source, glibc-minimal-langpack, libnsl, libxcrypt, nss_db</li>
<li>glibc-common -&gt; glibc-common, rpcgen</li>
<li>grub2-common -&gt; efi-filesystem, grub2-common</li>
<li>grub2-tools -&gt; grub2-tools, grub2-tools-efi</li>
<li>initscripts -&gt; initscripts, netconsole-service, network-scripts, readonly-root</li>
<li>iproute -&gt; iproute, iproute-tc</li>
<li>iptables -&gt; iptables, iptables-libs</li>
<li><strong>kernel -&gt; kernel, kernel-core, kernel-modules, kernel-modules-extra</strong>

<ul>
<li>Ubuntu 처럼 분리되었으며 kernel 패키지는 메타패키지로 파일이 없는 패키지 입니다.</li>
</ul></li>
<li>kernel-tools, qemu-kvm-tools -&gt; kernel-tools</li>
<li>mod_nss -&gt; mod_ssl</li>
<li>mod_wsgi -&gt; python3-mod_wsgi</li>
<li>ncurses-libs -&gt; ncurses-c++-libs, ncurses-compat-libs, ncurses-libs</li>
<li><strong>ntp -&gt; chrony</strong></li>
<li><strong>ntpdate -&gt; chrony</strong></li>
<li>numpy -&gt; python2-numpy, python3-numpy</li>
<li><strong>OpenIPMI -&gt; OpenIPMI, OpenIPMI-lanserv</strong></li>
<li>OpenIPMI-python -&gt; python3-openipmi</li>
<li>oprofile -&gt; perf</li>
<li>pkgconfig -&gt; pkgconf-pkg-config</li>
<li>postfix -&gt; postfix, postfix-mysql, postfix-pgsql</li>
<li>strace, strace32 -&gt; strace</li>
<li>syslinux -&gt; syslinux, syslinux-nonlinux</li>
<li><strong>systemd -&gt; systemd, systemd-container, systemd-udev</strong></li>
<li><strong>systemd-journal-gateway -&gt; systemd-journal-remote</strong></li>
<li><strong>systemd-libs -&gt; systmd-libs, systemd-pam</strong></li>
<li><strong>systemd-networkd, systemd-resolved -&gt; systemd</strong>

<ul>
<li>Ubuntu 18.04 와 다르게 systemd-resolved 대신에 NetworkManager에서 resolv.conf를 관리합니다.</li>
</ul></li>
<li><strong>wireshark -&gt; wireshark-cli</strong></li>
<li><strong>wireshark-gnome -&gt; wireshark</strong></li>
<li>yum-cron -&gt; dnf-automatic</li>
<li><strong>yum-plugin-aliases, yum-plugin-fastestmirror, yum-plugin-priorities, yum-plugin-remove-with-leaves, yum-plugin-tmprepo, yum-plugin-tsflags -&gt; dnf</strong></li>
<li><strong>yum-plugin-auto-update-debug-info, yum-plugin-changelog, yum-plugin-copr -&gt; dnf-plugins-core</strong> (기존 이름 사용 가능)</li>
<li><strong>yum-plugin-versionlock -&gt; python3-dnf-plugin-versionlock</strong> (기존 이름 사용가능)</li>
<li><strong>yum-utils -&gt; dnf-utils</strong> (기존 이름 사용 가능)</li>
</ul>

<p>아래 패키지들은 삭제 되었습니다. 전체 목록은 공식 문서를 참고하세요.</p>

<ul>
<li>acpid-sysvinit</li>
<li>btrfs-progs</li>
<li>btrfs-progs-devel</li>
<li>cvs</li>
<li>dmraid -&gt; mdadm으로 대체 되었습니다.</li>
<li>finger -&gt; who, pinky, last 명령으로 대체 가능</li>
<li>gcc-go</li>
<li>gcc-objc</li>
<li>GeoIP

<ul>
<li>RHEL8는 libmaxminddb, geoipupdate를 제공합니다.</li>
</ul></li>
<li>grub2</li>
<li>ImageMagick</li>
<li>rcs</li>
<li>rsh</li>
<li>system-config-date</li>
<li>system-config-language</li>
<li>systemd-sysv</li>
<li>tcp_wrappers</li>
<li>tomcat</li>
<li>whois</li>
<li>yum-langpacks</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>NVMe에서 iostat의 높은 %util 값 (RHEL/CentOS 7.6)</title>
            <link>/2019/03/22/high-iostat-util-on-nvme/</link>
            <pubDate>Fri, 22 Mar 2019 06:16:42 +0000</pubDate>
            
            <guid>/2019/03/22/high-iostat-util-on-nvme/</guid>
            <description>안내: 이 글을 읽기 전에 [Linux iostat] 문서를 먼저 읽어주시기 바랍니다. 버전: 2019/03/22 (수정), 2019/01/28 (초안)  최근 NVMe와 같이 디스크 장치에서 병렬처리가 가능한 제품을 사용 할 때에 iostat의 지표 값이 이상하다는 이야기가 많습니다. 잘 알려진 내용으로는 Linux iostat에서 소개하고 있는 내용처럼 svctm 지표 자체가 제대로 계산되지 않아서 제 성능을 내기도 전에 100%의 Utilization 지표를 나타내는 이슈가 있습니다.
다만, 최근 RHEL/CentOS 7.6에서 동일하거나 유사한 수준의 I/O 처리에 대해서 기존 7.</description>
            <content type="html"><![CDATA[

<ul>
<li>안내: 이 글을 읽기 전에 [<a href="https://brunch.co.kr/@lars/7" target="_blank">Linux iostat</a>] 문서를 먼저 읽어주시기 바랍니다.</li>
<li>버전: 2019/03/22 (수정), 2019/01/28 (초안)</li>
</ul>

<p>최근 NVMe와 같이 디스크 장치에서 병렬처리가 가능한 제품을 사용 할 때에 iostat의 지표 값이 이상하다는 이야기가 많습니다. 잘 알려진 내용으로는 <a href="https://brunch.co.kr/@lars/7" target="_blank">Linux iostat</a>에서 소개하고 있는 내용처럼 svctm 지표 자체가 제대로 계산되지 않아서 제 성능을 내기도 전에 100%의 Utilization 지표를 나타내는 이슈가 있습니다.</p>

<p>다만, 최근 RHEL/CentOS 7.6에서 동일하거나 유사한 수준의 I/O 처리에 대해서 기존 7.4와 달리 iostat 값의 <code>%util</code> 지표가 100%에 가깝게 나타난다는 제보를 받고 관련 된 내용을 확인해 보았습니다.</p>

<h2 id="svctm">svctm</h2>

<p>먼저, iostat에서 svctm은 서비스 처리시간을 의미하는 지표입니다. 앞서 소개했던 글에서 자세히 설명해 준 것 처럼 해당 지표는 과거 단일 I/O 스케줄러에 의해서 관리 될 때에만 의미가 있지 지금과 같은 Multi-Queue 환경이나 병렬처리 환경에는 의미가 없습니다.</p>

<p>sysstat 프로젝트에서 이미 버전 10에서 매뉴얼 페이지에서 svctm 지표에 대해서 아래와 같이 신뢰하지 말라고 소개하고 있으며 향후 버전에 삭제 된다는 말처럼 최신 버전 12에서는 해당 지표는 존재하지 않습니다.</p>

<pre><code>svctm
	The average service time (in milliseconds) for I/O requests that were issued to the device. Warning! Do not trust this field any more.  This field will be removed in a future  sysstat  version.

</code></pre>

<h2 id="util">%util</h2>

<p>최근 iostat의 매뉴얼 페이지에서 <code>%util</code> 지표를 아래와 같이 소개하고 있습니다.</p>

<pre><code>%util
	Percentage  of elapsed time during which I/O requests were issued to the device (bandwidth utilization for the device). Device saturation occurs when this value is close to 100% for devices serving requests serially.  But for devices serving requests in parallel, such as RAID arrays and modern SSDs, this number does not reflect their performance limits.

</code></pre>

<p>I/O 처리요청의 발생에 따른 소요 시간의 비율로 해당 값이 높으면 높을 수록 I/O 처리에 많은 시간을 소요한 것처럼 보일 수 있습니다. 하지만,  매뉴얼에서 설명하는 것 처럼 병렬처리가 가능한 경우에는 제대로 된 연산이 안되기 때문에 100% 이상이 나올 수 있도록 표기한다면 NVMe와 같은 장치에서는 수백%의 지표로 표기도 가능할 것입니다.</p>

<p>즉, sysstat 프로젝트에서도 인지하고 있는 것 처럼 익히 알고 있던 iostat의 %util 지표는 병렬처리와 멀티큐가 가능한 환경에서 더 이상은 부하의 지표로 삼기 어렵다는 이야기가 됩니다.</p>

<h2 id="배포판-버전에-따른-지표-차이">배포판 버전에 따른 지표 차이</h2>

<p>초반에 이야기 했던 CentOS 7.6에서 7.4와 다른 지표를 보이는 문제에 대해서 이야기해보면 iostat이 연산을 위해서 참고하는 정보를 확인해 볼 필요가 있습니다.</p>

<p>iostat은 %util 계산을 위해서 <strong>/proc/diskstats</strong> 파일을 참조합니다. 이 파일의 항목 중에서 %util과 관련이 있는 부분은 I/O 처리에 소요된 시간(ms) 값 입니다. (참조: <a href="https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats" target="_blank">/proc/diskstats</a>)</p>

<p>이 값의 변화를 간단한 스크립트로 작성해서 1초마다 기록해보면 7.4와 7.6에서 아래오 같은 차이를 보이고 있었습니다.</p>

<pre><code>[CentOS 7.6]
diff: 956
diff: 976
diff: 982
......

</code></pre>

<pre><code>[CentOS 7.4]
diff: 9
diff:12
diff: 13
......

</code></pre>

<p>동일한 수준의 I/O에 대해서 해당 값의 변화폭이 너무커서 결과적으로 7.6 버전에서는 iostat의 %util 값이 100%에 가깝게 표기되는 것을 알 수 있었습니다.</p>

<h2 id="blk-mq">blk-mq</h2>

<p>병렬처리가 가능한 하드웨어가 등장함에 따라 Linux 커널도 Block I/O Layer의 구조가 바뀌었습니다. 기존에는 I/O Scheduler에 따라서 처리되던 Block Layer에 Multi-Queue가 적용 되었습니다. 이를 blk-mq라고 합니다. blk-mq에 대한 자세한 내용은 아래 내용을 참고하세요.</p>

<ul>
<li><a href="https://lwn.net/Articles/735275/" target="_blank">Improvements in the block layer LWN.net</a></li>
<li>[Linux Multi-Queue Block IO Queueing Mechanism (blk-mq) - Thomas-Krenn-Wiki]</li>
<li><a href="https://lwn.net/Articles/709354/" target="_blank">blk-mq scheduling framework  LWN.net</a> - Patchiset</li>
<li><a href="https://lwn.net/Articles/769836/" target="_blank">Add support for multiple queue maps LWN.net</a> - Patchset</li>
</ul>


    <figure class="left" >
        <img src="/images/2019/03/image.png"   />

        
    </figure>



<ul>
<li>출처: <a href="https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram" target="_blank">thomas-krenn</a></li>
</ul>

<p>blk-mq 프레임워크가 커널에 적용되었고 이를 활용하는 드라이버 들이 생겨났습니다. (ex. nvme) 따라서 모든 CPU 코어에 개별적인 I/O Queue를 할당하고 I/O 성능을 개선 할 수 있게 되었습니다.</p>

<p>하지만, 드라이버와 달리 I/O 스케줄러는 상대적으로 늦게 준비되었습니다. 그렇기 때문에NVMe의 경우에는 일반적으로 I/O 스케줄러가 적용되지 않습니다. 보통은 sysfs에서 확인 할 수 있는 I/O 스케줄러 (elevator) 정보가 <strong>none</strong>으로 표기 됩니다. (none은 noop과 다르며 I/O 스케줄러를 거치지 않는 다는 이야기 입니다)</p>

<pre><code>$ cat /sys/block/nvme0n1/queue/scheduler
none

</code></pre>

<h2 id="multi-queue-i-o-scheduler">Multi-Queue I/O Scheduler</h2>

<p>RHEL Release note를 보면 7.2에서 blk-mq에 대해서 소개하고 있습니다. 다만, PCI 장치들에 대한 blk-mq 커널 config 옵션은 7.5 릴리즈 커널부터 포함되었습니다. 그리고 이 시점에 Multi-Queue를 지원하는 I/O Scheduler를 포함시켰습니다.</p>

<pre><code>CONFIG_POSIX_MQUEUE=y
CONFIG_POSIX_MQUEUE_SYSCTL=y
CONFIG_BLK_MQ_PCI=y
CONFIG_MQ_IOSCHED_DEADLINE=y
CONFIG_MQ_IOSCHED_KYBER=y

</code></pre>

<p>따라서, 7.5 릴리즈 커널(3.10.0-862)에서는 sysfs 정보를 보면 <code>mq-deadline</code>과 <code>kyber</code> 스케줄러가 추가되어있습니다.</p>

<pre><code>$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber

</code></pre>

<h2 id="none-none">none. none?</h2>

<p>다시 이슈가 된 이유로 돌아가 보면 7.5 릴리즈 부터 추가 된 blk-mq를 지원하는 스케줄러와 별개로 과거부터 계속해서 <code>none</code>으로 설정하여 I/O Scheduler를 별도로 적용하지 않았음에도 7.4와 7.6에서 <strong>/proc/diskstats</strong>의 지표는 차이가 있었습니다. (이 값은 sysfs의 각 블럭 디바이스 stat 정보입니다. ex. /sys/block/nvme0n1/stat) 이에 7.4 릴리즈부터 7.6 릴리즈까지의 커널의 변경 점에 대해서 찾아본 결과 <code>blk-mq</code>와 관련 된 220여개 이상의 패치 사항이 있었으며 그 중 눈여겨 볼 만한 아래와 같은 내용을 확인 할 수 있었습니다.</p>

<pre><code>- [block] blk-mq: issue directly if hw queue isn't busy in case of 'none' (Ming Lei) [1599682]
- [block] blk-mq: fix sysfs inflight counter (Ming Lei) [1548261]
- [block] blk-mq: count allocated but not started requests in iostats inflight (Ming Lei) [1548261]
- [block] blk-mq: enable checking two part inflight counts at the same time (Ming Lei) [1548261]
- [block] blk-mq: only attempt to merge bio if there is rq in sw queue (Ming Lei) [1597068]
- [block] blk-mq: update nr_requests when switching to 'none' scheduler (Ming Lei) [1585526]
- [block] blk-mq: set mq-deadline as default scheduler for single queue device (Ming Lei) [1154525]
- [block] blk-mq: introduce request_aux (Ming Lei) [1458104]

</code></pre>

<p>blk-mq 관련 버그적인 수정사항도 있지만 로직과 카운팅에 대한 수정 사항도 있었습니다. 따라서, <code>none</code>으로 설정하여 사용하던 NVMe 장치에 대해서는 커널에서 기본 스케줄러로 blk-mq를 적용하고 있으며 blk-mq의 카운팅 방식에 따라서 해당 수치의 변화가 발생했다고 보여집니다. (가장 영향이 크다고 보여지는건 inflight에 대한 카운트 및 측정이지만 커널 패치 설명이 명확하지 않아 조금은 모호한 부분도 있습니다) 패치내용에 Legacy가 의도한 바대로 맞췄다는 얘기도 있지만 과연 이게 바람직한 것인지 까지는 판단하기 어렵습니다.</p>

<h3 id="2019-03-22-추가">2019/03/22 추가</h3>

<ul>
<li>이 부분이 바람직한지에 대한 의문이 있었는데 <a href="https://brunch.co.kr/@lars/" target="_blank">박지훈</a>님의 제보로 RedHat에서 이를 문제로 인지하고 조만간 수정 할 계획이라는 문서가 확인되었습니다. (<a href="https://access.redhat.com/solutions/3901291" target="_blank">iostat -x and sar -d output shows incorrect high %util values for nvme SSD disks  - Red Hat Customer Portal</a>)</li>
<li>위 수정사항이 반영되면 none 상태에서도 지표가 기존처럼 기대한 수치로 나올 것으로 보입니다.</li>
</ul>

<p>즉, 결과적으로 blk-mq의 변화에 따라서 /sys/block/장치/stat, /proc/diskstats 값의 차이가 발생했다고 볼 수 있으며 이를 확인 할 수 있는 방법으로 커널 버전을 바꾸지 않더라도 NVMe 디스크를 LVM으로 사용하고 dm에 Multi-Queue(blk-mq)를 사용하지 않는 경우(<code>dm_mod.use_blk_mq=n</code> 인 경우이며 기본 값 입니다) 기대하는 stat(/proc/diskstats)값을 볼 수 있습니다. 마찬가지로 iostat에서 개별 파티션의 상태를 확인해도 기대하는 %util 값을 확인 할 수 있습니다. 아래는 LVM 구성을 한 서버에서 개별 파티션 정보까지 iostat으로 확인 한 내용입니다.</p>


    <figure class="left" >
        <img src="/images/2019/03/image-1.png"   />

        
            <figcaption class="center" >iostat -x -p ALL 1 결과</figcaption>
        
    </figure>



<p>위 결과에서 보이는 것 처럼 <code>svctm</code>, <code>%util</code> 항목에서 dm-0와 NVMe 개별파티션의 합이 NVMe 장치에 대한 지표와 일치 하지 않는 것을 볼 수 있습니다.</p>

<h2 id="mq-deadline-kyber">mq-deadline, kyber</h2>

<p>앞서 언급한 Multi-Queue I/O 스케줄러(elevator)가 새롭게 추가 되었기 때문에 blk-mq에 맞춰서 제공되는 해당 스케줄러를 적용해보면 sysfs의 stat과 proc의 diskstats 지표가 기대하는 형태로 바뀌게 되는 걸 확인 할 수 있습니다.</p>

<pre><code>$ echo 'mq-deadline' &gt; /sys/block/nvme0n1/queue/scheduler
$ iostat -x -p ALL 1
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           8.25    0.00    0.78    0.03    0.00   90.93

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
nvme0n1           0.00     0.00   21.00  169.00   184.00  1488.00    17.60     0.00    0.01    0.05    0.01   0.01   0.20
nvme0n1p1         0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
nvme0n1p2         0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
nvme0n1p3         0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
nvme0n1p4         0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
nvme0n1p5         0.00     0.00   21.00  169.00   184.00  1488.00    17.60     0.00    0.01    0.05    0.01   0.01   0.20
dm-0              0.00     0.00   21.00  169.00   184.00  1488.00    17.60     0.00    0.02    0.05    0.01   0.01   0.20
$ echo 'kyber' &gt; /sys/block/nvme0n1/queue/scheduler
$ iostat -x -P ALL 1
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           9.16    0.00    0.82    0.00    0.00   90.02

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
nvme0n1           0.00     0.00   24.00  160.00   208.00  1568.00    19.30     0.00    0.03    0.00    0.03   0.02   0.40
nvme0n1p1         0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
nvme0n1p2         0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
nvme0n1p3         0.00     0.00    0.00    1.00     0.00     4.00     8.00     0.00    0.00    0.00    0.00   0.00   0.00
nvme0n1p4         0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
nvme0n1p5         0.00     0.00   24.00  159.00   208.00  1564.00    19.37     0.00    0.03    0.00    0.03   0.02   0.40
dm-0              0.00     0.00   24.00  159.00   208.00  1564.00    19.37     0.00    0.02    0.00    0.03   0.02   0.40

</code></pre>

<h2 id="blk-mq에-맞는-i-o-scheduler-사용이-필수">blk-mq에 맞는 I/O Scheduler 사용이 필수?</h2>

<p>blk-mq를 지원하는 커널에서 <code>none</code>을 사용하게 될 때와 <code>mq-deadline</code>과 같은 blk-mq 지원 스케줄러를 사용 할 때 지표의 차이가 발생하는 부분은 blk-mq에 적합한 형태의 스케줄링 알고리즘이 적용되어서라고 볼 수 있습니다. 하지만, 한편으로는 직접적인 I/O Scheduler가 적용되지 않더라도(none) blk-mq에서 적절히 카운팅해서 처리한다면 이러한 혼동을 예방할 수 있을텐데 왜 이러한 형태로 지표를 제공하는지 의문스럽긴 합니다.</p>

<p>다만, sysstat 프로젝트에서도 <code>svctm</code>과 <code>%util</code> 지표에 대해서 더 이상 사용하지 않거나 신뢰하지 말라고 얘기하기 때문에 (svctm을 삭제한 것 처럼 %util도 삭제하는게 혼선을 줄이는 방법일 수 있겠지만 여전히 일반 HDD 타입은 존재) 모니터링 하는 입장에서 NVMe 디스크 장치의 부하(saturation)를 확인 할 적절한 지표를 마련하는 것은 필요하다고 봅니다.</p>

<p>모니터링 지표의 방법으로는 아래와 같은 방안을 검토 해 볼 수 있습니다.</p>

<ul>
<li><strong>3월 22일 추가</strong> - blk-mq / none 상태에 대한 수정이 적용되면 기존 방식대로 모니터링 결과를 얻을 수 있을 것으로 보입니다. 다만, sysstat 프로젝트에서 svctm과 %util에 대한 신뢰도 부분을 얘기하고 있기 때문에 향후에도 계속 이를 절대적 지표로 보긴 어렵기 때문에 대안 마련도 필요해 보입니다.</li>
<li>mq를 지원하는 I/O Scheduler의 사용  디스크 타입에 맞추어 스케줄러 존재 여부를 체크하고 적용하는 시스템적 처리가 필요합니다 mq를 지원하는 I/O Scheduler의 벤치마크를 통해서 지표 개선 뿐만 아니라 성능 개선에 대해서도 검토해 볼 수 있습니다. (참고 링크의 벤치마크 정보 참조)</li>
<li>실제 사용하는 디스크 볼륨에 대한 iostat 지표 사용  파티셔닝해서 사용하는 경우에는 개별 파티션에 대한 지표를 사용 LVM/RAID를 사용하는 장치에 대해서는 DM 장치에 대한 지표를 사용</li>
<li>Read tick과 Write tick 값을 별도로 취해서 지표로 사용 (sysfs/block/<dev>/stat, proc/diskstats 공통)</li>
</ul>

<h2 id="참고-할-만한-링크">참고 할 만한 링크</h2>

<ul>
<li><a href="https://www.kernel.org/doc/Documentation/block/stat.txt" target="_blank">sysfs/stat</a></li>
<li><a href="https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats" target="_blank">proc/diskstats</a></li>
<li><a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/7.2_release_notes/storage" target="_blank">Red Hat Enterprise Linux 7 Chapter 14. Storage - Red Hat Customer Portal</a></li>
<li><a href="https://patchwork.kernel.org/patch/10364821/" target="_blank"><sup>2</sup>&frasl;<sub>2</sub> blk-mq: fix sysfs inflight counter - Patchwork</a></li>
<li><a href="https://github.com/torvalds/linux/blob/master/block/blk-mq-pci.c" target="_blank">github: blk-mq-pci</a></li>
<li><a href="http://brooker.co.za/blog/2014/07/04/iostat-pct.html" target="_blank">Two traps in iostat: %util and svctm - Marc&rsquo;s Blog</a></li>
<li><a href="https://coderwall.com/p/utc42q/understanding-iostat" target="_blank">Understanding iostat</a></li>
<li><a href="https://www.phoronix.com/scan.php?page=article&amp;item=linux417-nvme-io&amp;num=4" target="_blank">Linux 4.17 I/O Scheduler Tests On An NVMe SSD Yield Surprising Results - Phoronix</a></li>
<li><a href="https://lwn.net/Articles/720675/" target="_blank">Two new block I/O schedulers for 4.12 LWN.net</a></li>
<li><a href="https://lwn.net/Articles/738449/" target="_blank">Block layer introduction part 2: the request layer LWN.net</a></li>
<li><a href="https://lwn.net/Articles/700932/" target="_blank">blk-mq: Introduce combined hardware queues  LWN.net</a></li>
<li><a href="https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers" target="_blank">Ubuntu I/O Schedulers</a></li>
<li><a href="https://www.cs.utah.edu/~manua/pubs/systor15.pdf" target="_blank">Performance Analysis of NVMe SSDs and their Implication on Real World Databases - PDF</a></li>
<li><a href="https://github.com/munin-monitoring/munin/issues/1119" target="_blank">diskstats plugin reports near 100% usage for NVME drives</a></li>
<li><a href="https://wiki.archlinux.org/index.php/Improving_performance#Kernels_I/O_schedulers" target="_blank">Archlinux - Improving performance</a></li>
<li><a href="https://mahmoudhatem.wordpress.com/2016/02/08/oracle-uek-4-where-is-my-io-scheduler-none-multi-queue-model-blk-mq/" target="_blank">ORACLE UEK 4 : Where is my I/O scheduler ? None ?  Multi-queue model blk-mq | Hatem Mahmoud Oracle&rsquo;s blog</a></li>
<li><a href="https://www.percona.com/blog/2014/06/25/why-util-number-from-iostat-is-meaningless-for-mysql-capacity-planning/" target="_blank">Why %util number from iostat is meaningless for MySQL capacity planning</a></li>
<li><a href="https://www.thomas-krenn.com/en/wiki/Linux_Multi-Queue_Block_IO_Queueing_Mechanism_(blk-mq)" target="_blank">Linux Multi-Queue Block IO Queueing Mechanism</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Ubuntu에서 Swap 파티션 비활성화</title>
            <link>/2019/01/29/disable-swap-partition-on-ubuntu/</link>
            <pubDate>Tue, 29 Jan 2019 08:31:25 +0000</pubDate>
            
            <guid>/2019/01/29/disable-swap-partition-on-ubuntu/</guid>
            <description>일반적으로 Linux에서 swap은 전용 파티션을 구성하거나 swap 파일을 생성해서 사용합니다. 그리고 파티션의 경우에는 /etc/fstab에 설정되어 부팅 할 때 활성화 되도록 되어있습니다.
사용자가 swap을 영구적으로 비활성화 하고 싶을 경우에는 간단히 /etc/fstab을 주석처리하는 형태로 비활성화 하는데 Ubuntu의 경우에는 /etc/fstab 설정파일에서 삭제하더라도 부팅 했을 때 활성화 되는 경우가 있습니다.
이는 swap 관련된 설정이 파일시스템 설정에만 존재하는 것이 아니라 부팅할 때 사용하는 initramfs 이미지에도 설정되어있기 때문입니다.
initramfs 수정 및 업데이트 보통 아래 위치에 설정파일이 존재하며 swap 파티션의 UUID 값이 설정되어있습니다.</description>
            <content type="html"><![CDATA[

<p>일반적으로 Linux에서 swap은 전용 파티션을 구성하거나 swap 파일을 생성해서 사용합니다. 그리고 파티션의 경우에는 /etc/fstab에 설정되어 부팅 할 때 활성화 되도록 되어있습니다.</p>

<p>사용자가 swap을 영구적으로 비활성화 하고 싶을 경우에는 간단히 /etc/fstab을 주석처리하는 형태로 비활성화 하는데 Ubuntu의 경우에는 /etc/fstab 설정파일에서 삭제하더라도 부팅 했을 때 활성화 되는 경우가 있습니다.</p>

<p>이는 swap 관련된 설정이 파일시스템 설정에만 존재하는 것이 아니라 부팅할 때 사용하는 initramfs 이미지에도 설정되어있기 때문입니다.</p>

<h2 id="initramfs-수정-및-업데이트">initramfs 수정 및 업데이트</h2>

<p>보통 아래 위치에 설정파일이 존재하며 swap 파티션의 UUID 값이 설정되어있습니다.</p>

<pre><code>[/etc/initramfs-tools/conf.d/resume]
RESUME=UUID=f67867df-3f62-4045-9d07-7aa2adab3523

</code></pre>

<p>따라서, swap을 영구적으로 비활성화 하고자 한다면 위 설정파일 내용을 주석처리하거나 삭제하고 아래 명령으로 initramfs를 새로 생성해 주어야 합니다.</p>

<pre><code>$ sudo update-grub
$ sudo update-initramfs -u

</code></pre>

<h2 id="systemd-swap-unit-비활성화">systemd swap unit 비활성화</h2>

<p>또한, <strong>systemd</strong> 환경에서 동작하는 경우에는 Swap 파티션이 swap unit으로 등록되어 관리되기 때문에 위의 설정을 하고나서 재부팅을 하더라도 Swap이 활성화 되는 경우가 있습니다.</p>

<p>이러한 경우에는 아래와 같이 등록 된 swap unit(보통 dev-파티션이름.swap)을 찾아서 <strong>mask</strong>해서 비활성화 해 줍니다.</p>

<pre><code>$ sudo systemctl list-unit-files --type swap
UNIT FILE     STATE
dev-sda3.swap loaded

1 unit files listed.

$ sudo systemctl mask dev-sda3.swap
Created symlink from /var/run/systemd/generator.late/swap.target.wants/dev-sda3.swap to /dev/null.

</code></pre>

<h2 id="기타">기타</h2>

<p>initramfs 설정 및 파일을 업데이트하고 systemd 설정을 해주는 과정이 불편하다면 간단하게 해당 파티션을 swap으로 인식되지 않게 일반 파일시스템으로 포맷하거나 삭제하면 됩니다.</p>
]]></content>
        </item>
        
        <item>
            <title>비 정상적인 Swap 수치</title>
            <link>/2019/01/29/abnormal-swap-values/</link>
            <pubDate>Tue, 29 Jan 2019 08:29:40 +0000</pubDate>
            
            <guid>/2019/01/29/abnormal-swap-values/</guid>
            <description>증상서버 시스템에서 Swap 크기를 확인 할 때 아래와 같이 보유한 스왑 크기보다 훨씬 큰 비 정상적인 수치가 표시 됩니다.
$ free total used free shared buffers cached Mem: 65944564 42388800 23555764 48 3281272 30509248 -/+ buffers/cache: 8598280 57346284  cat /proc/meminfo | grep ^Swap SwapCached: 1380 kB SwapTotal: 2096124 kB SwapFree: 77770336 kB  원인 커널의 get_swap_page()와 관련된 수정 사항에서 스핀락(swap_lock)이 제거 되었는데 이로 인해서 nr_swap_pages 오류와 /proc/meminfo의 오류를 야기하게 되었습니다.</description>
            <content type="html"><![CDATA[

<p>증상서버 시스템에서 Swap 크기를 확인 할 때 아래와 같이 보유한 스왑 크기보다 훨씬 큰 비 정상적인 수치가 표시 됩니다.</p>

<pre><code>$ free
             total       used       free     shared    buffers     cached
Mem:      65944564   42388800   23555764         48    3281272   30509248
-/+ buffers/cache:    8598280   57346284

</code></pre>

<pre><code>cat /proc/meminfo | grep ^Swap
SwapCached:         1380 kB
SwapTotal:       2096124 kB
SwapFree:       77770336 kB

</code></pre>

<h2 id="원인">원인</h2>

<p>커널의 <code>get_swap_page()</code>와 관련된 수정 사항에서 스핀락(<code>swap_lock</code>)이 제거 되었는데 이로 인해서 <code>nr_swap_pages</code> 오류와 /proc/meminfo의 오류를 야기하게 되었습니다.</p>

<h2 id="대상">대상</h2>

<ul>
<li>RHEL/CentOS 6.7</li>
<li>kernel-2.6.32-573 버전 계열</li>
</ul>

<h2 id="해결-방법">해결 방법</h2>

<p>커널 코드에 의한 버그이기 때문에 (BZ#1252362) 커널 버전을 RHEL/CentOS 6.8의 베이스 커널인 <strong>2.6.32-642</strong> 이상으로 업데이트 해주면 됩니다.</p>

<p>상기 버그에 대한 수정 내용은 <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/6.8_technical_notes/bug_fixes_kernel" target="_blank">6.8 릴리즈노트</a>에 소개되어있습니다.</p>
]]></content>
        </item>
        
        <item>
            <title>ext4 포맷 속도 개선</title>
            <link>/2019/01/29/improve-ext4-format-speed/</link>
            <pubDate>Tue, 29 Jan 2019 08:26:19 +0000</pubDate>
            
            <guid>/2019/01/29/improve-ext4-format-speed/</guid>
            <description>Linux에서 ext4 파일시스템을 사용하면서 종종 겪는 이슈 중의 하나가 바로 포맷 속도 입니다. 과거에는 이러한 포맷 속도가 그렇게까지 문제가 되지 않았지만 6TB, 8TB 디스크가 보편화 되면서 이러한 큰 디스크를 포맷할 때 시간이 너무 많이 소요되는 문제가 있습니다.
사실, 이게 딱히 문제라고 보긴 어렵지만 특정 경우에는 (포맷 작업을 순차적으로 처리하는 업무라던지 ) 많은 시간을 소모하는 포맷 작업이 부담스러울 수 있습니다. 그러다보니 ext4를 주력으로 하는 Ubuntu 같은 배포판에서도 포맷 속도가 빠른 XFS를 선호하시는 분도 보았었습니다.</description>
            <content type="html"><![CDATA[<p>Linux에서 ext4 파일시스템을 사용하면서 종종 겪는 이슈 중의 하나가 바로 포맷 속도 입니다. 과거에는 이러한 포맷 속도가 그렇게까지 문제가 되지 않았지만 6TB, 8TB 디스크가 보편화 되면서 이러한 큰 디스크를 포맷할 때 시간이 너무 많이 소요되는 문제가 있습니다.</p>

<p>사실, 이게 딱히 문제라고 보긴 어렵지만 특정 경우에는 (포맷 작업을 순차적으로 처리하는 업무라던지 ) 많은 시간을 소모하는 포맷 작업이 부담스러울 수 있습니다. 그러다보니 ext4를 주력으로 하는 Ubuntu 같은 배포판에서도 포맷 속도가 빠른 XFS를 선호하시는 분도 보았었습니다. (저는 개인적인 선호도로 XFS를 씁니다)</p>

<p>그래서, 특별한 기술은 아니지만 이러한 ext4의 포맷 속도를 개선 할 방법을 소개합니다. ext4가 포맷 작업을 할 때 가장 많은 시간을 할애하는 부분은 <strong>inode table</strong>을 초기화 하는 작업입니다. 특히, 디스크 크기가 크면 클 수록 inode의 개수도 많기 때문에 더 많은 시간이 소요됩니다. 이를 백그라운드에서 초기화 작업을 하도록 옵션을 지정 할 수 있습니다.</p>

<pre><code>$ sudo mkfs.ext4 -E lazy_itable_init=1 /dev/sdc1

</code></pre>

<p>위와 같이 <code>lazy_itable_init</code> 옵션을 지정해서 포맷하면 inode table 초기화를 포맷 시점에 하지 않고 마운트 된 후에 초기화 작업을 수행하게 됩니다.</p>

<p>단, <strong>주의할 점</strong>은 이렇게 포맷 된 볼륨은 처음 마운트 되면 초기화 작업이 안정적으로 마무리 되어야 하기 때문에 초기화가 완전히 끝나기 전에 갑작스런 시스템 크래시로 인해 마운트가 해제 되었다가 마운트를 다시하게 되면 파일시스템에 손상이 생길 수 있습니다. (그래서 최대한 손상을 줄이기 위해서 저널 초기화도 지연시키는 <code>lazy_journal_init</code> 옵션은 쓰지 않고 inode table 초기화만 지연시키는 옵션을 사용합니다.)</p>

<p>또한, 백그라운드에서 초기화 작업을 수행하기 때문에 마운트 후에 초기화가 완료될 때 까지 I/O 성능이 조금 떨어질 수 있습니다.</p>

<p>위 예시 처럼 포맷한 후에 볼륨을 마운트 해서 iostat으로 모니터링하면 지속적으로 초기화 작업(inode table zeroing)을 수행하는 걸 확인 할 수 있습니다.</p>

<pre><code>$ iostat -x 1 /dev/sdc
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.82    0.00    0.34    1.35    0.00   97.50

Device:         rrqm/s   wrqm/s     r/s     w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sdc               0.35   166.55    3.76   18.66    32.81  6804.69   304.93     6.34  282.67   2.88   6.45

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.13    0.00    0.46    4.42    0.00   95.00

Device:         rrqm/s   wrqm/s     r/s     w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sdc               0.00     0.00    0.00   12.00     0.00 12288.00  1024.00     0.06    5.00   2.00   2.40

</code></pre>
]]></content>
        </item>
        
        <item>
            <title>[macOS] iTerm에서 ssh 접속 시 LC_CTYPE 오류</title>
            <link>/2018/07/17/macos-itermeseo-ssh-jeobsog-si-lc_ctype-oryu/</link>
            <pubDate>Tue, 17 Jul 2018 04:19:05 +0000</pubDate>
            
            <guid>/2018/07/17/macos-itermeseo-ssh-jeobsog-si-lc_ctype-oryu/</guid>
            <description>macOS에서 iTerm으로 ssh 접속을 할 때 아래와 같은 오류 메시지가 발생하는 경우가 있다.
-bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory  이는 LANG 환경변수의 전달에 의한 것으로 보통 /etc/ssh/ssh_config의 설정 값 때문에 발생한다. 따라서 아래와 같이 ssh_config 파일에서 SendEnv LANG 항목을 주석처리 해주면 된다.
$ sudo vi /etc/ssh/ssh_config  ... 상략 ... Host * # SendEnv LANG LC_* &amp;lt;- 이 부분을 #으로 주석처리  </description>
            <content type="html"><![CDATA[<p>macOS에서 iTerm으로 ssh 접속을 할 때 아래와 같은 오류 메시지가 발생하는 경우가 있다.</p>

<pre><code>-bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory
</code></pre>

<p>이는 LANG 환경변수의 전달에 의한 것으로 보통 <code>/etc/ssh/ssh_config</code>의 설정 값 때문에 발생한다. 따라서 아래와 같이 ssh_config 파일에서 <code>SendEnv LANG</code> 항목을 주석처리 해주면 된다.</p>

<pre><code>$ sudo vi /etc/ssh/ssh_config
</code></pre>

<pre><code>... 상략 ...
Host *
#   SendEnv LANG LC_*        &lt;- 이 부분을 #으로 주석처리
</code></pre>
]]></content>
        </item>
        
        <item>
            <title>nf_conntrack과 docker</title>
            <link>/2018/04/12/nf_conntrackgwa-docker/</link>
            <pubDate>Thu, 12 Apr 2018 08:35:03 +0000</pubDate>
            
            <guid>/2018/04/12/nf_conntrackgwa-docker/</guid>
            <description>본 문서는 docker를 사용 중인 시스템에서 아래와 같은 커널 메시지를 발생시키며 패킷이 드랍되는 증상에 대한 설명과 해결 방법에 대해서 소개하고 있습니다.
nf_conntrack: table full, dropping packet.  1. nf_conntrack nf_conntrack은 ip_conntrack의 후속 커널 모듈로 netfilter가 네트워크에서 발생하는 커넥션에 대해 해당 내용을 기록하고 추적하기 위한 모듈 입니다. 일반적으로 활성화 되지는 않지만 iptables를 이용한 NAT 환경 같은 경우에 사용되기도 합니다. 특히, 아래와 같은 경우에 사용자 모르게 활성화 되어서 사용되는 경우가 있습니다.</description>
            <content type="html"><![CDATA[

<p>본 문서는 docker를 사용 중인 시스템에서 아래와 같은 커널 메시지를 발생시키며 패킷이 드랍되는 증상에 대한 설명과 해결 방법에 대해서 소개하고 있습니다.</p>

<pre><code>nf_conntrack: table full, dropping packet.
</code></pre>

<h2 id="1-nf-conntrack">1. nf_conntrack</h2>

<p>nf_conntrack은 ip_conntrack의 후속 커널 모듈로 netfilter가 네트워크에서 발생하는 커넥션에 대해 해당 내용을 기록하고 추적하기 위한 모듈 입니다. 일반적으로 활성화 되지는 않지만 iptables를 이용한 NAT 환경 같은 경우에 사용되기도 합니다. <strong>특히, 아래와 같은 경우에 사용자 모르게 활성화</strong> 되어서 사용되는 경우가 있습니다.</p>

<ul>
<li>iptables -t nat -L 같은 NAT 테이블 확인 명령을 한번이라도 수행한 경우</li>
<li>docker와 같이 iptables의 NAT 기능이 필요한 어플리케이션을 사용 할 경우</li>
</ul>

<p>단순히, 해당 모듈이 활성화 된다고해서 문제가 되지는 않지만 접속량이 많은 네트워크 서비스를 제공하는 경우에는 nf_conntrack을 기본 값으로 사용할 경우 연결을 기록하는 테이블의 크기를 기본 값인 65536를 사용하기 때문에 문제가 발생 할 수 있습니다.</p>

<p>따라서, 해당 값을 서버의 환경에 맞추어 충분히 늘려주는게 좋으며 적절한 값에 대해서 아래에 소개하도록 하겠습니다.</p>

<h2 id="2-설정-값-살펴보기">2. 설정 값 살펴보기</h2>

<p>커널 버전 2.6 이전에는 ip_conntrack을 사용하였기 때문에 커널 파라미터 값이 다릅니다. 본 문서에서는 nf_conntrack에 대해서만 다룹니다.</p>

<pre><code>예시)

[커널 2.6 이전]
net.ipv4.netfilter.ip_conntrack_count
net.ipv4.netfilter.ip_conntrack_max

[커널 2.6 이후]
net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_max
</code></pre>

<h3 id="2-1-nf-conntrack-max">2-1. nf_conntrack_max</h3>

<p>nf_conntrack_max는 nf_conntrack 모듈이 기록 할 최대 연결 개수를 지정하는 파라미터 입니다. 기본 값은 65536이며 적당히 크게 잡아줘도 무방하지만 단순히 값을 크게 잡는 것이 능사가 아니기 때문에 이에 대해서 살펴보도록 하겠습니다.</p>

<p>먼저 Conntrack Hash Table이 어떻게 구현되어있는지 살펴보면 아래 그림과 같습니다.</p>

<p><img src="/images/2018/04/conntrack_hash_table.png" alt="conntrack_hash_table" /></p>

<p>해시 테이블의 각 구성요소는 bucket이라는 녀석으로 구성되어 있으며 bucket은 내부적으로 연결 리스트로 구현 되어 있습니다. 아시다시피 해시 테이블은 O(1)의 효율을 보여주지만 연결 리스트의 경우 O(n)의 효율을 보여주기 때문에 연결 리스트를 최소화 하고 해시테이블의 크기를 키우는게 가장 좋아 보입니다.</p>

<p>하지만, 해시테이블을 크게 갖는 다는 것은 그만큼 정적으로 많은 메모리를 할당해서 사용해야하는 부담이 존재합니다. 예를 들어 2097152개의 연결을 처리하기 위해 nf_conntrack_max 값을 2097152으로 지정하고 해시 테이블도 동일한 크기로 잡게 된다면 단순히 해시 테이블을 구성하는데 아래와 같은 메모리 자원을 사용하게 됩니다.</p>

<ul>
<li>연결을 기록하는 각 엔트리의 크기는 308byte 라고 합니다.</li>
<li>2097152(해시크기) * 308(byte) / 1048576(1MiB) = 616 MB</li>
<li>즉, 600MB가 넘는 크기를 해시테이블 구성에만 낭비하게 됩니다.</li>
</ul>

<p>따라서, 단순히 해시테이블 크기 자체를 크게하기 보다는 연결 리스트를 적절히 활용하여 성능과 공간의 균형을 맞추는게 좋습니다. 커널 문서에서는 아래와 같이 nf_conntrack_max 값을 정의하고 있습니다. (ip_conntrack 시절에는 8배 였습니다)</p>

<pre><code>nf_conntrack_max - INTEGER
    Size of connection tracking table.  Default value is
    nf_conntrack_buckets value * 4.
</code></pre>

<p>여기에서 nf_conntrack_buckets는 bucket들의 개수이며 다시 말해 해시테이블의 크기를 의미합니다. 커널 문서에서는 해시테이블 크기의 4배로 max 값을 지정하는 걸 기본으로 하고 있기 때문에 각 해시테이블의 bucket은 4개의 노드를 갖는 연결 리스트로 구성됩니다. 따라서, 앞서 살펴본 2097152개의 연결을 처리하는 경우로 살펴 본다면 616MB의 크기는 154MB로 줄어들게 됩니다.</p>

<h3 id="2-2-과거에는">2-2. 과거에는&hellip;.</h3>

<p>참고로, 과거 ip_conntrack 시절에는 CONNTRACK_MAX 값에 대한 기본 값을 결정하는 요소에는 시스템 아키텍처도 변수로 작용했었습니다. CONNTRACK_MAX를 수식으로 표현하면 아래와 같은데 이에 대한 자세한 내용은 <a href="https://wiki.khnet.info/index.php/Conntrack_tuning" target="_blank">이곳</a>을 참고하시면 됩니다.</p>

<pre><code>nf_conntrack_max
    = 메모리크기(바이트) / 16384 / (아키텍처 비트 / 32)
</code></pre>

<p>또한, 과거에는 CONNTRACK_MAX를 해시테이블 크기의 8배로 사용했기 때문에 보통 CONNTRACK_MAX 값을 결정하고 이를 8로 나누어서 해시테이블 크기를 지정했으나 최근에는 4배로 사용하기 때문에 4로 나누어서 계산하면 됩니다.</p>

<h3 id="2-3-nf-conntrack-buckets">2-3. nf_conntrack_buckets</h3>

<p>아시다시피 네트워크 관련 커널 파라미터 설정에는 정답이란 것은 없습니다. 다만, 앞서 살펴본대로 무작정 nf_conntrack_max를 키우기만 하면 연결 리스트에 대한 부하가 높아지기 때문에 nf_conntrack_buckets 값도 같이 조절을 해 주어야 합니다.</p>

<p>커널 문서에서는 아래와 같이 소개하고 있습니다.</p>

<pre><code>nf_conntrack_buckets - INTEGER
    Size of hash table. If not specified as parameter during module
    loading, the default size is calculated by dividing total memory
    by 16384 to determine the number of buckets but the hash table will
    never have fewer than 32 and limited to 16384 buckets. For systems
    with more than 4GB of memory it will be 65536 buckets.
    This sysctl is only writeable in the initial net namespace.
</code></pre>

<p>4GB 메모리에는 65536이 적합하지만 별도로 해시 크기를 지정해서 모듈을 올리지 않는다면 16384 값으로 정해지게 됩니다. 따라서, 이 상황에서 nf_conntrack_max를 계속 키우게 되면 연결 리스트 길이만 계속 길어지기 때문에 바람직하지 않습니다.</p>

<p>최근 시스템들은 많은 메모리를 가지고 있기 때문에 65536개 이상의 연결을 충분히 처리할 수 있으며 해시 크기에 대한 모듈 파라미터를 지정해서 수동으로 로딩하지 않고 docker나 iptables에 의해서 자동으로 로딩 되므로 이 값을 변경해 주어야 하는데 한 번 모듈이 올라가게 되면 sysctl을 이용해서 커널 파라미터 값을 변경 할 수 없으며 sysfs를 통해서만 변경이 가능합니다.</p>

<pre><code>[예시]
$ echo 65536 &gt; /sys/module/nf_conntrack/parameters/hashsize
</code></pre>

<h3 id="2-4-적절한-값을-찾아서">2-4. 적절한 값을 찾아서&hellip;</h3>

<p>요즘 시스템은 메모리가 넉넉하기 때문에 65536의 배수로 적당히 지정하고 거기에 맞추어 해시 크기를 지정하는 것도 방법입니다.</p>

<p>또는, 과거 ip_conntrack 시절에 계산하던 방법을 차용하면 아래와 같이 계산해 볼 수도 있습니다.</p>

<pre><code>[예시]
- 64bit 기반 8GB 메모리를 가진 서버

nf_conntrack_max
    = 메모리크기 / 16384 / (아키텍처비트 / 32)
    = (8 * 1073741824) / 16384 / (64/32)
    = 262144
nf_conntrack_buckets
    = nf_conntrack_max / 4
    = 65536
</code></pre>

<h3 id="2-5-추가-설정">2-5. 추가 설정</h3>

<p>nf_conntrack_max, nf_conntrack_buckets 외에도 네트워크 서비스를 위해서 몇 가지 중요한 설정 값이 있습니다.</p>

<p>먼저 nf_conntrack_generic_timeout이 있는데 Layer 4 기반 타임아웃 설정 값으로 기본 값은 600초로 되어있습니다. 이 값이 너무 길기 때문에 이를 적절히 (예를 들면 120) 낮춰주는게 좋습니다.</p>

<p>그리고, 활성화 된 연결에 대한 타임아웃 파라미터로 nf_conntrack_tcp_timeout_established이 있으며 기본 값은 무려 432000초(5일) 입니다. 이 값 또한 적당히 낮춰 주는게 좋습니다.</p>

<h2 id="3-추천하는-설정">3. 추천하는 설정</h2>

<p>예를들어 32GB의 메모리를 갖고 있는 64bit 시스템에 대해서는 아래와 같이 설정 값을 추천 할 수 있습니다. 다만, 앞서 설명 드렸던 것 처럼 nf_conntrack_max는 <strong>필요에 따라 임의로 적정 값을 직접 지정</strong>해도 무방합니다. 특히, 연결 양이 많지 않은 네트워크 서비스 시스템에서는 기본 값으로도 충분 할 경우가 많습니다.</p>

<table>
<thead>
<tr>
<th>키</th>
<th>계산</th>
<th>값</th>
</tr>
</thead>

<tbody>
<tr>
<td>nf_conntrack_max</td>
<td>32 * 1073741824 / 16384 / 2</td>
<td>1048576</td>
</tr>

<tr>
<td>nf_conntrack_buckets</td>
<td>nf_conntrack_max / 4</td>
<td>262144</td>
</tr>

<tr>
<td>nf_conntrack_generic_timeout</td>
<td></td>
<td>120</td>
</tr>

<tr>
<td>nf_conntrack_tcp_timeout_established</td>
<td></td>
<td>54000</td>
</tr>
</tbody>
</table>

<h3 id="3-1-설정-스크립트">3-1. 설정 스크립트</h3>

<p>위에서 언급한 4가지 항목 설정을 위한 쉘 스크립트를 공유 드립니다. (nf_conntrack/nf_conntrack_ipv4 모듈이 활성화 되어있지 않다면 동작하지 않습니다)</p>

<pre><code>#!/bin/bash
#
# nf-setup.sh: nf_conntrack configurator
#
# by lunatine
#
_arch=1
_max=0
_bucket=0
_gto=120
_tcp=54000

help() {
    cat &lt;&lt; EOF
  $ nf-setup.sh [OPTIONS ...]

  Options:
    -m|--max   : nf_conntrack_max (default: memsize / 16384 / arch bit)
    -b|--bucket: nf_conntrack_buckets (default: nf_conntrack_max / 4)
    -g|--gto   : nf_conntrack_generic_timeout (default: 120)
    -t|--tcp   : nf_conntrack_tcp_timeout_established (default: 54000)
    -h|--help  : help message
EOF
}

# get arguments
while [[ -n $1 ]]
do
    case &quot;$1&quot; in
        # nf_conntrack_max
        -m|--max) _max=$1; shift 2;;
        -b|--bucket) _bucket=$1; shift 2;;
        -g|--gto) _gto=$1; shift 2;;
        -t|--tcp) _tcp=$1; shift 2;;
        -h|--help) help; exit 0;;
    esac
done

if [ &quot;$(id -u)&quot; -ne 0 ]; then
    echo &quot;[Error] root privilege required ...&quot;
    exit 1
else
    for modname in nf_conntrack nf_conntrack_ipv4
    do
        if [ &quot;$(lsmod | grep -c $modname)&quot; -eq 0 ]; then
            echo &quot;[Error] No $modname module found&quot;
            exit 1
        fi
    done
fi

# when nf_conntrack_max omitted
if [ &quot;$_max&quot; -eq 0 ]; then
    memtotal=$(grep MemTotal /proc/meminfo | awk '{print $2}')
    [ &quot;$(uname -m)&quot; == &quot;x86_64&quot; ] &amp;&amp; _arch=2
    _max=$(( memtotal * 1024 / 16384 / _arch ))
fi

# when nf_conntrack_buckets omitted
if [ &quot;$_bucket&quot; -eq 0 ]; then
    _bucket=$(( _max / 4 ))
fi

# asking for apply
CONF_VALUES=&quot;
[Applying]
---------------------------------------------------
  nf_connectrack_max                  : $_max
  nf_conntrack_generic_timeout        : $_gto
  nf_conntrack_tcp_timeout_established: $_tcp
  nf_conntrack hash size              : $_bucket
---------------------------------------------------
  are you sure? (Cancel: Ctrl+C) &quot;
read -p &quot;$CONF_VALUES&quot; ans

# apply configurations
sed -i &quot;/^net.netfilter.nf_conntrack_max/d&quot; /etc/sysctl.conf
sed -i &quot;/^net.netfilter.nf_conntrack_buckets/d&quot; /etc/sysctl.conf
sed -i &quot;/^net.netfilter.nf_conntrack_generic_timeout/d&quot; /etc/sysctl.conf
sed -i &quot;/^net.netfilter.nf_conntrack_tcp_timeout_established/d&quot; /etc/sysctl.conf
{
    echo &quot;net.netfilter.nf_conntrack_max = $_max&quot;
    echo &quot;net.netfilter.nf_conntrack_generic_timeout = $_gto&quot;
    echo &quot;net.netfilter.nf_conntrack_tcp_timeout_established = $_tcp&quot;
} &gt;&gt; /etc/sysctl.conf
echo $_bucket &gt; /sys/module/nf_conntrack/parameters/hashsize

# Result
cat &lt;&lt; EOF
[Result]
---------------------------------------------------
  max                    : $(sysctl net.netfilter.nf_conntrack_max)
  hash size (buckets)    : $(sysctl net.netfilter.nf_conntrack_buckets)
  generic_timeout        : $(sysctl net.netfilter.nf_conntrack_generic_timeout)
  tcp_timeout_established: $(sysctl net.netfilter.nf_conntrack_tcp_timeout_established)
---------------------------------------------------
EOF

exit 0
</code></pre>

<h2 id="참고문서">참고문서</h2>

<ul>
<li><a href="https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt" target="_blank">nf_conntrack_sysctl 커널 문서</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Virtualbox 환경설정 실행 시 종료 될 때</title>
            <link>/2016/08/29/virtualbox-crash-on-mac-os-x/</link>
            <pubDate>Mon, 29 Aug 2016 04:56:04 +0000</pubDate>
            
            <guid>/2016/08/29/virtualbox-crash-on-mac-os-x/</guid>
            <description> Mac OS X에서 Virtualbox의 환경설정을 실행하면 어플리케이션이 종료되는 이슈에 대해서 소개합니다.
2016&amp;frasl;10 - 추가 정보  MacOS Sierra에서는 https://github.com/tekezo/Karabiner-Elements를 설치해서 사용하면 됩니다.  환경 및 증상  Mac OS X Virtualbox 5.1 이상 환경설정 메뉴에 진입 (또는 단축키 Command+,)   원인  Qt와 관련하여 Karabiner 앱과 충돌을 일으키는 것 (관련링크) 베타버전에서는 이슈가 해결 된 것으로 보임  해결방법  Karabiner에서 해결 된 버전을 정식으로 릴리즈 할 때 까지 기다리는 방법 Karabiner를 베타버전으로 업데이트하는 방법  &amp;ldquo;환경설정&amp;gt;보안 및 개인정보보호&amp;rdquo;에 들어가서 &amp;ldquo;개인 정보 보호&amp;rdquo; 탭의 &amp;ldquo;손쉬운 사용&amp;rdquo; 항목에서 Karabiner_AXNotifier 사용을 해제  </description>
            <content type="html"><![CDATA[

<p>Mac OS X에서 Virtualbox의 환경설정을 실행하면 어플리케이션이 종료되는 이슈에 대해서 소개합니다.</p>

<h2 id="2016-10-추가-정보"><sup>2016</sup>&frasl;<sub>10</sub> - 추가 정보</h2>

<ul>
<li>MacOS Sierra에서는 <a href="https://github.com/tekezo/Karabiner-Elements를" target="_blank">https://github.com/tekezo/Karabiner-Elements를</a> 설치해서 사용하면 됩니다.</li>
</ul>

<h2 id="환경-및-증상">환경 및 증상</h2>

<ul>
<li>Mac OS X</li>
<li>Virtualbox 5.1 이상</li>
<li>환경설정 메뉴에 진입 (또는 단축키 Command+,)
<img src="/images/2016/08/virtualbox-preferences.png" alt="" /></li>
</ul>

<h2 id="원인">원인</h2>

<ul>
<li>Qt와 관련하여 Karabiner 앱과 충돌을 일으키는 것 (<a href="https://www.virtualbox.org/ticket/15617" target="_blank">관련링크</a>)</li>
<li>베타버전에서는 이슈가 해결 된 것으로 보임</li>
</ul>

<h2 id="해결방법">해결방법</h2>

<ol>
<li>Karabiner에서 해결 된 버전을 정식으로 릴리즈 할 때 까지 기다리는 방법</li>
<li>Karabiner를 베타버전으로 업데이트하는 방법
<img src="/images/2016/09/karabiner-update.png" alt="" /></li>
<li>&ldquo;환경설정&gt;보안 및 개인정보보호&rdquo;에 들어가서 &ldquo;개인 정보 보호&rdquo; 탭의 &ldquo;손쉬운 사용&rdquo; 항목에서 <strong>Karabiner_AXNotifier</strong> 사용을 해제</li>
</ol>

<p><img src="/images/2016/08/karabiner-disable.png" alt="" /></p>
]]></content>
        </item>
        
        <item>
            <title>NUMA with Linux</title>
            <link>/2016/07/14/numa-with-linux/</link>
            <pubDate>Thu, 14 Jul 2016 06:18:34 +0000</pubDate>
            
            <guid>/2016/07/14/numa-with-linux/</guid>
            <description>개인 위키에서 정리하던 내용을 블로그로 옮긴 것 입니다. 이 문서는 NUMA 아키텍처에 대한 간략한 소개와 Linux에서의 활용 방법에 대해서 소개하고 있습니다.
 1. System Topology 1-1. CMP 최근 CPU는 하나의 소켓에 여러개의 코어를 가지고 있다. 이를 보통 멀티코어라고 지칭하며 하나의 칩에 여러개의 프로세서가 올라가기 때문에 CMP(Chip-level Multi Processor)라고도 부른다. 이러한 멀티코어 CPU에 대한 메모리 관계는 아래와 같이 표현 할 수 있다.
1-2. SMP 하지만, 멀티코어 CPU를 2개 이상 장착한 시스템의 경우에는 메모리를 2개 이상의 CPU가 접근하기 때문에 CPU와 메모리 사이를 네트워크로 묶어서 접근이 필요하며 아래와 같이 표현이 가능하다.</description>
            <content type="html"><![CDATA[

<blockquote>
<p>개인 위키에서 정리하던 내용을 블로그로 옮긴 것 입니다. 이 문서는 NUMA 아키텍처에 대한 간략한 소개와 Linux에서의 활용 방법에 대해서 소개하고 있습니다.</p>
</blockquote>

<h2 id="1-system-topology">1. System Topology</h2>

<h3 id="1-1-cmp">1-1. CMP</h3>

<p>최근 CPU는 하나의 소켓에 여러개의 코어를 가지고 있다. 이를 보통 멀티코어라고 지칭하며 하나의 칩에 여러개의 프로세서가 올라가기 때문에 CMP(Chip-level Multi Processor)라고도 부른다. 이러한 멀티코어 CPU에 대한 메모리 관계는 아래와 같이 표현 할 수 있다.</p>

<p><img src="/images/2016/07/cmp.png" alt="" /></p>

<h3 id="1-2-smp">1-2. SMP</h3>

<p>하지만, 멀티코어 CPU를 2개 이상 장착한 시스템의 경우에는 메모리를 2개 이상의 CPU가 접근하기 때문에 CPU와 메모리 사이를 네트워크로 묶어서 접근이 필요하며 아래와 같이 표현이 가능하다. 이를 SMP(Symmetric Multi Processor)라고 한다.</p>

<p><img src="/images/2016/07/smp.png" alt="" /></p>

<p>이러한 메모리접근을 위한 네트워크를 과거 펜티엄(Pentium)기반 프로세서는 FSB(Front-Side Bus)라는 방식으로 제공되었고 이를 개선하기 위해서 AMD에서는 HTT(Hyper Transport Technology)를 Intel에서는 QPI(Quick Path Interconnect)라는 기술을 통해서 보다 빠른 성능을 제공하였다. 다만, HTT나 QPI 모두 연결방식에 대한 기술이지 시스템 위상(Topology)에 대한 기술이 아니다. 예를 들어 QPI의 경우에는 Point-to-Point 양방향 연결에 대해서 빠른 속도를 제공하는 기술이기 때문에 각 장치마다의 연결점을 갖게 되고 결과적으로 많은 프로세서를 갖게 되면 이를 연결하기 위한 복잡성은 커질 것이다. (아래 그림은 실제 SMP에 QPI를 적용한 그림은 아니며 QPI 연결에 대한 예시를 위해 인용한 그림이다)</p>

<p><img src="/images/2016/07/intel-qpi2.png" alt="" /></p>

<ul>
<li>출처: <a href="http://www.qdpma.com/systemarchitecture/NUMA.html" target="_blank">http://www.qdpma.com/systemarchitecture/NUMA.html</a></li>
</ul>

<p>또한, SMP 구조에서는 메모리를 공유하기 때문에 한 번에 한개의 프로세서만이 동일한 메모리에 접근이 가능하기 때문에 다른 프로세서들을 대기하게 만드는 문제점이 있다. 이는 프로세서가 많아 질 수록 더 성능적인 문제점을 야기 할 수 있다. (메모리 접근은 CPU에 비해서 월등히 느리다. 즉, 메모리 접근을 대기하는 것은 큰 손실이라고 볼 수 있다.)</p>

<h3 id="1-3-numa">1-3. NUMA</h3>

<p>그래서, 이를 개선하기 위해 나온 형태가 NUMA(불균일 기억 장치 접근, Non-Uniform Memory Access) 이며 각 프로세서가 독립적인 로컬 메모리를 보유하고 프로세서의 상대적 위치에 따라서 메모리 접근 속도가 달라지는 방식이다. 따라서, 각 프로세서는 로컬 메모리에 접근 할 경우에 다른 프로세서에 의한 대기를 하지 않아도 되고 빠른 속도로 접근이 가능하다.</p>

<p><img src="/images/2016/07/numa-arch.png" alt="" /></p>

<ul>
<li>출처: <a href="https///www.sqlskills.com/blogs/jonathan/understanding-non-uniform-memory-accessarchitectures-numa/" target="_blank">Understanding Non-Uniform Memory Access/Architectures</a></li>
</ul>

<p>하지만, 여러 프로세서가 동시에 동일한 데이터가 필요할 경우 메모리 뱅크들 사이로 데이터를 이동하기 때문에 성능이 떨어 질 수 있다. 그렇기 때문에 무조건적으로 효율이 좋은 것은 아니므로 프로세스가 로컬메모리를 사용하도록 하는게 최적화의 키 포인트이다. 그리고 CPU에 달려있는 작은 크기의 비공유 메모리(CPU캐시라 불리우는 것)가 있는데 기본적인 NUMA 형태는 프로그래밍 상으로 공유 메모리에 대해서 <a href="https///en.wikipedia.org/wiki/Cache_coherence" target="_blank">캐시 일관성(Cache Coherence)</a>을 유지하기 어렵기 때문에 대부분의 NUMA 시스템은 <a href="https///en.wikipedia.org/wiki/Non-uniform_memory_access#Cache_coherent_NUMA_.28ccNUMA.29" target="_blank">ccNUMA(cache-coherence NUMA)</a>형태로 하드웨어적으로 구현하여 제공하고 있다.</p>

<h3 id="1-4-node-interleaving">1-4. Node Interleaving</h3>

<p>NUMA에서는 프로세서에 메모리 컨트롤러가 장착되어서 로컬 메모리를 가지고 있지만 BIOS에서 이를 기존의 SMP처럼 사용하도록 설정하는 기능을 제공하고 있다. 흔히 BIOS 상에서 <strong>Node Interleaving</strong>이라는 설정 값으로 존재한다.</p>

<p><img src="/images/2016/07/numa-node-interleaving.png" alt="" /></p>

<p>이 기능을 활성화 한다면 시스템은 노드의 모든 메모리를 하나의 연속된 메모리처럼 매핑되고 각 메모리 페이지는 RR(Round-robin) 형태로 노드에 흩뿌려지게 된다.</p>

<p><img src="/images/2016/07/suma.png" alt="" /></p>

<ul>
<li>출처: <a href="http://frankdenneman.nl/2016/07/07/numa-deep-dive-part-1-uma-numa/" target="_blank">NUMA Deep Dive Part I</a></li>
</ul>

<p>일반적인 SMP 시스템처럼 전체 메모리에 일관된 주소로 접근 할 수 있지만 이미 NUMA 구조인 시스템을 SMP처럼 흉내내기 때문에 SUMA(Sufficiently Uniform Memory Architecture)라고 불리우기도 한다. 실제로 Intel의 <a href="https///software.intel.com/en-us/articles/intelr-memory-latency-checker" target="_blank">Memory Latency Checker</a>로 메모리 응답 속도를 확인해 보면 아래와 같이 차이가 발생한다. 즉, SUMA의 경우 메모리 응답속도가 고른편이지만 최적의 응답속도를 제공하지는 않는다.</p>

<ul>
<li>Haswell 시스템에서 테스팅 한 값이며 단위는 ns</li>
</ul>

<p><img src="/images/2016/07/numa-suma-latency.png" alt="" /></p>

<p>시스템에서 동작하는 어플리케이션이 NUMA 아키텍처와 잘 맞지 않거나 최적화를 통한 빠른 성능보다는 균일한 응답속도와 대량의 메모리를 SMP처럼 사용하는 환경에 더 적합하다면 Node Interleaving을 활성화 하는 것도 좋은 방법이 될 수 있다.</p>

<h2 id="2-numa-with-linux">2. NUMA with Linux</h2>

<p>이제 Linux에서 CPU Affinity 및 NUMA 정책을 설정하는 방법에 대해서 알아보자.</p>

<h3 id="2-1-taskset">2-1. taskset</h3>

<p>taskset 커맨드는 현재 동작중인 프로세스에 대해서 CPU Affinity를 확인하거나 지정하는 도구이다. 또한, 지정된 Affinity 값으로 프로그램을 실행 할 때도 사용 할 수 있다.</p>

<p>먼저 <strong>-p</strong> 옵션을 통해서 현재 프로세스의 affinity mask 값을 확인하면 아래와 같다.</p>

<pre><code>$ sudo taskset -p 874
pid 874's current affinity mask: ffffff
</code></pre>

<p>특정 프로세스의 affinity mask를 변경하려면 아래와 같이 실행하면 된다.</p>

<pre><code>taskset -p &lt;마스크&gt; &lt;PID&gt;

$ sudo taskset -p 01 16764
pid 16764's current affinity mask: 3
pid 16764's new affinity mask: 1
</code></pre>

<p>위 예시에서는 2개의 CPU를 가진 시스템에서 1개의 CPU만 사용하도록 mask를 적용 한 것이다. 프로세스를 새로 띄우려면 아래와 같이 하면 된다.</p>

<pre><code>$ sudo taskset &lt;마스크&gt; -- &lt;프로그램&gt;
</code></pre>

<p>affinity mask는 2진수 마스크로 가장 높은 자리가 마지막 논리 CPU를 의미하며 가장 낮은 자리가 첫 번째 논리 CPU로 지정하여 이를 16진수로 표기 한 것이다.</p>

<pre><code>8개의 논리 CPU에 대한 마스크는 8자리
00000000

첫 번째와 두 번째 CPU
00000011 =&gt; 0x03

첫 번째와 마지막 CPU
10000001 =&gt; 0x81

모든 홀수 번째 CPU
01010101 =&gt; 0x55

모든 CPU
11111111 =&gt; 0xff
</code></pre>

<p>affinity mask를 16진수로 지정하는 부분은 사용자 친화적이지 않기 때문에 <strong>-c</strong> 옵션을 통해서 특정 CPU 번호를 지정 할 수도 있다.</p>

<pre><code>$ sudo taskset -c 0,3,7-9 -- testprogram
</code></pre>

<h3 id="2-2-numactl">2-2. numactl</h3>

<p>taskset이 CPU affinity 설정을 위한 툴이라면 numactl 패키지에 포함되어있는 numactl 커맨드는 NUMA 정책을 설정하는 유틸리티이다.</p>

<h4 id="2-2-1-numa-상태-확인">2-2-1. NUMA 상태 확인</h4>

<p>먼저 numactl의 <strong>&ndash;show</strong>(-s) 옵션을 통해서 현재 프로세스의 NUMA 정책을 볼 수 있다.</p>

<pre><code>$ sudo numactl --show
policy: default
preferred node: current
physcpubind: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
cpubind: 0 1
nodebind: 0 1
</code></pre>

<p><strong>&ndash;hardware</strong>(-H) 옵션은 현재 사용가능한 NUMA 노드 정보를 보여준다.</p>

<pre><code>$ sudo numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 12 13 14 15 16 17
node 0 size: 65410 MB
node 0 free: 10950 MB
node 1 cpus: 6 7 8 9 10 11 18 19 20 21 22 23
node 1 size: 65536 MB
node 1 free: 10660 MB
node distances:
node   0   1
  0:  10  21
  1:  21  10
</code></pre>

<p>현재 NUMA의 운영 상태를 보기 위해서는 numactl 패키지에 포함된 <strong>numastat</strong> 커맨드를 통해서 확인 할 수 있다.</p>

<pre><code>[서버 A]
$ sudo numastat
                           node0           node1
numa_hit              4537819602      4427659820
numa_miss                      0               0
numa_foreign                   0               0
interleave_hit             37568           37596
local_node            4537819489      4427618495
other_node                   113           41325

[서버 B]
$ sudo numastat
                           node0           node1
numa_hit             18900017512     14587522385
numa_miss             6031138144      2662445985
numa_foreign          2662445985      6031138144
interleave_hit             18816           18826
local_node           18900011126     14587500303
other_node            6031144530      2662468067
</code></pre>

<p>각 항목의 의미를 살펴보면 다음과 같다.</p>

<p>![]()</p>

<p>numa_miss, numa_foreign이 높다는 것은 의도한대로 메모리가 할당되지 못하고 있다는 것을 의미하기 때문에 이를 줄이는 것이 보다 효율성을 높이는 방법이라고 할 수 있다. 앞서 본 예시에서 [서버 B]의 상태를 좀 더 다른 형태로 확인 해 보자. <strong>-m</strong> 옵션으로 메모리 정보(meminfo) 형태로 살펴보면 아래와 같다.</p>

<pre><code>$ sudo numastat -m
Per-node system memory usage (in MBs):
                          Node 0          Node 1           Total
                 --------------- --------------- ---------------
MemTotal                16259.82        16384.00        32643.82
MemFree                   880.89           55.18          936.07
MemUsed                 15378.93        16328.82        31707.76
Active                   8223.18        11994.76        20217.94
Inactive                 6126.18         3857.87         9984.05
Active(anon)             5262.63        11008.37        16271.00
Inactive(anon)           1150.93         1104.14         2255.07
Active(file)             2960.55          986.39         3946.95
Inactive(file)           4975.25         2753.73         7728.98
Unevictable                 0.00            0.00            0.00
Mlocked                     0.00            0.00            0.00
Dirty                       1.03            0.04            1.07
Writeback                   0.00            0.00            0.00
FilePages                7936.11         3740.25        11676.36
Mapped                     13.36            2.07           15.42
AnonPages                6411.74        12110.38        18522.12
Shmem                       0.30            0.13            0.43
KernelStack                10.64            1.30           11.94
PageTables                 16.93           28.00           44.93
NFS_Unstable                0.00            0.00            0.00
Bounce                      0.00            0.00            0.00
WritebackTmp                0.00            0.00            0.00
Slab                      435.75          108.10          543.85
SReclaimable              366.86           91.49          458.35
SUnreclaim                 68.89           16.61           85.50
HugePages_Total             0.00            0.00            0.00
HugePages_Free              0.00            0.00            0.00
HugePages_Surp              0.00            0.00            0.00
</code></pre>

<p>실제 MemFree와 Active가 어느 노드에 더 집중되어있는지 비교해 볼 수 있다. 또한 특정 프로세스의 상태를 보고 싶다면 <strong>-p</strong> 옵션으로 해당 PID를 입력하거나 패턴문자를 통해서 확인이 가능하다.</p>

<pre><code>$ sudo numastat -p mysql
Per-node process memory usage (in MBs)
PID                          Node 0          Node 1           Total
------------------  --------------- --------------- ---------------
1838 (mysqld_safe)             1.22            0.15            1.37
3628 (mysqld)               6390.69        12068.24        18458.93
------------------  --------------- --------------- ---------------
Total                       6391.91        12068.39        18460.30
</code></pre>

<p>mysqld 프로세스가 Node 1에 메모리 리소스를 더 많이 사용하는 것을 확인 할 수 있다. <strong>-v</strong> 옵션을 주면 좀 더 상세한 정보를 확인 할 수 있다. (할당 영역)</p>

<pre><code>$ sudo numastat -p mysql -v

Per-node process memory usage (in MBs) for PID 1838 (mysqld_safe)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.11            0.06            0.17
Stack                        0.00            0.01            0.02
Private                      1.11            0.08            1.18
----------------  --------------- --------------- ---------------
Total                        1.22            0.15            1.37

Per-node process memory usage (in MBs) for PID 3628 (mysqld)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                        32.08          445.15          477.23
Stack                        0.00            0.07            0.07
Private                   6359.44        11623.02        17982.46
----------------  --------------- --------------- ---------------
Total                     6391.52        12068.24        18459.76
</code></pre>

<h4 id="2-2-2-numa-정책-설정-numactl">2-2-2. NUMA 정책 설정 (numactl)</h4>

<p>numactl로 정책(policy)을 설정하고 프로그램을 실행하면 해당 프로그램의 프로세스는 numactl이 지정한 정책에 따라서 움직이게 된다. numactl의 옵션은 아래와 같다.</p>

<p><img src="/images/2016/07/numactl-options.png" alt="" /></p>

<p>간단한 프로그램을 통해서 각 옵션별로 메모리 할당 상태를 확인해 보자. 프로그램 내용은 아래와 같다.</p>

<pre><code>#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

int main(){
  void *ptr;
  long size = 40*1024*1024*1024ULL;

  ptr = malloc(size);
  memset(ptr, 0, size);

  for(;;) { ; }
  return 0;
}
</code></pre>

<ul>
<li><p>테스트 시스템: 64GB 메모리를 가진 NUMA 시스템. 2개의 노드를 가지고 있다. (Haswell)</p></li>

<li><p>테스트 프로그램: 매우 단순하게 40GB 메모리를 할당 받아 무한 루프를 돈다.</p></li>
</ul>

<p>먼저 해당 프로그램을 컴파일 하고 실행하기 전의 시스템 상태이다.</p>

<pre><code>$ sudo numastat -c
                Node 0 Node 1 Total
                ------ ------ -----
Numa_Hit          3396   3475  6871
Numa_Miss            0      0     0
Numa_Foreign         0      0     0
Interleave_Hit     100    100   200
Local_Node        3320   3410  6730
Other_Node          76     65   141
</code></pre>

<p>이제 해당 프로그램을 &ndash;membind=0 옵션을 주고 실행해 보자. 컴파일한 바이너리 이름은 malloc 이다.</p>

<pre><code>$ sudo numactl --membind=0 -- ./malloc &amp;
[1] 9249
$ sudo numastat -p malloc

Per-node process memory usage (in MBs) for PID 7870 (malloc)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.00            0.00            0.00
Stack                        0.00            0.00            0.01
Private                  30866.64            0.29        30866.93
----------------  --------------- --------------- ---------------
Total                    30866.64            0.30        30866.94
[1]+  Killed                  numactl --membind=0 -- ./malloc
</code></pre>

<p>메모리를 할당하다가 한쪽 노드의 메모리양(32GB)를 초과하여 프로세스가 죽어버렸다. (만약 시스템에 넉넉한 swap 공간이 있어서 swap을 포함한 메모리 공간이 충분하다면 프로세스가 죽지 않을 수도 있다) numa_miss로 찍힌 내용은 해당 프로그램이 아닌 다른 프로세스에 의해서 발생한 것이다. (해당 노드 메모리가 부족하여 다른 노드로 요청) 그리고, dmesg에는 아래와 같은 메시지가 남는다.</p>

<pre><code>Out of memory: Kill process 9249 (malloc) score 870 or sacrifice child
</code></pre>

<p>이제 이 프로그램을 &ndash;preferred=0 옵션을 주고 실행 해 보자. 선호하는 노드의 메모리가 부족하면 다른 노드로 부터 메모리를 가져올 것이므로 프로세스가 죽지 않을 것으로 쉽게 예상 할 수 있다.</p>

<pre><code>$ sudo numactl --preferred=0 -- ./malloc &amp;
[1] 9823
$ sudo numastat -p malloc

Per-node process memory usage (in MBs) for PID 9823 (malloc)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.00            0.00            0.00
Stack                        0.01            0.00            0.01
Private                  30355.08        10605.28        40960.36
----------------  --------------- --------------- ---------------
Total                    30355.09        10605.28        40960.37
</code></pre>

<p>0번 노드로부터 최대한 메모리를 할당 받고 부족한 부분을 1번 노드로부터 받아왔음을 알 수 있다. 그리고, &ndash;cpunobind 옵션과 함께 지정한다면 가급적 해당 프로세스가 동작하는 노드의 CPU로부터 메모리 접근이 이루어지기 때문에 로컬메모리에 접근 할 확률이 높아지게 된다. 특정 프로세스가 모든 코어를 활용 할 것이 아니라면 이러한 방법(선호하는 노드 지정)도 좋은 전략이 될 수 있다.</p>

<p>이제 &ndash;interleaved=all 옵션을 주고 실행 해 보자.</p>

<pre><code>$ sudo numactl --interleave=all -- ./malloc &amp;
[1] 8506
$ sudo numastat -p malloc

Per-node process memory usage (in MBs) for PID 8506 (malloc)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.00            0.00            0.00
Stack                        0.01            0.00            0.01
Private                  20480.04        20480.32        40960.36
----------------  --------------- --------------- ---------------
Total                    20480.04        20480.33        40960.37
</code></pre>

<p>모든 노드에 고르게 메모리가 할당 된 것을 확인 할 수 있다. 따라서, interleave로 나누어진 메모리에 대한 접근은 로컬과 리모트의 평균 값에 해당하는 응답속도와 대역폭을 기대 할 수 있으며 일반적으로 하나의 노드가 보유한 로컬 메모리 크기 이상으로 메모리를 사용하는 어플리케이션에 유리 할 수 있다.</p>

<h3 id="2-3-numad">2-3. numad</h3>

<p>앞서 살펴본 taskset, numactl의 경우 사용자가 직접 개입해서 특정 어플리케이션, 프로세스에 대한 리소스 할당 정책을 지정하는 관리 방법이다. 시스템의 리소스를 많이 사용하는 어플리케이션이 명확하고 소수라면 numactl을 통해서 지정하는 방법이 효과적 일 수 있지만 일반적으로 다수의 프로세스가 작업을 수행하는 환경에서는 이를 일일이 관리하는건 매우 어려운 일이다.</p>

<p>이를 위해 numad라는 사용자레벨의 서비스 데몬을 활용하는 방법을 살펴보자. numad는 현재 실행중인 프로세스에 대해서 리소스 사용 현황을 살피고 CPU, Memory에 대한 affinity를 조절하는 프로그램이다. 즉, numad는  아래 그림과 같이 특정 프로세스가 동일한 노드의 CPU와 메모리를 사용 할 수 있도록 조절한다.</p>

<p><img src="/images/2016/07/numad-conceptual.png" alt="" /></p>

<p>출처: <a href="https///access.redhat.com/articles/1286673" target="_blank">RED HAT ENTERPRISE LINUX 7: OPTIMIZING MEMORY SYSTEM PERFORMANCE</a></p>

<p>앞서 설명한 것처럼 numad는 실행중인 프로세스가 동일한 NUMA 노드(CPU와 메모리가 같은 노드)에서 실행되도록 하기 때문에 일반적으로 하나의 시스템에 수십, 수백개의 어플리케이션이 실행되거나 여러 가상 게스트 시스템이 동작하는 집약적(Consolidation)인 시스템 환경에 적합하도록 되어있다. 따라서, 단일 어플리케이션이 많은 메모리 리소스를 사용하는 환경에서는 numad를 사용하기 보다는 해당 어플리케이션을 직접 numactl로 제어하는 편이 나을 수 있다.</p>

<p>2-2-2. 에서 사용했던 테스트 프로그램을 numad 환경에서도 테스트 해보도록 하자. numad 패키지를 설치했다면 서비스로 실행 할 수도 있지만 테스트 옵션을 쉽게 주기위해서 단독명령으로 실행하였다.</p>

<pre><code>$ sudo lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                24
On-line CPU(s) list:   0-23
Thread(s) per core:    2
Core(s) per socket:    6
Socket(s):             2
NUMA node(s):          2
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 63
Stepping:              2
CPU MHz:               2399.789
BogoMIPS:              4799.31
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              15360K
NUMA node0 CPU(s):     0,2,4,6,8,10,12,14,16,18,20,22
NUMA node1 CPU(s):     1,3,5,7,9,11,13,15,17,19,21,23
</code></pre>

<p>먼저, 노드 0번의 CPU 번호와 1번의 CPU 번호를 기억해두자.</p>

<pre><code>$ sudo numad -i 15
$ ./malloc &amp;
[1] 24191
$ ps -aF | grep mallo[c]
root      24191   7935 99 10486741 41943416 15 18:29 pts/0 00:04:12 ./malloc
</code></pre>

<p>먼저 별다른 옵션 없이 시스템 스캔 주기만 15로 주고 띄운 후에 프로그램을 실행 해 보았다.  <strong>ps -aF</strong> 명령으로 확인 한 값의 7번 째 컬럼은 PSR 값으로 현재 할당 된 CPU ID가 찍힌다. 여기에서는 15로 확인되었고 앞서 살펴본대로 15번은 1번노드이다.</p>

<pre><code>$ sudo numastat -p malloc
Per-node process memory usage (in MBs) for PID 24191 (malloc)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.00            0.00            0.00
Stack                        0.00            0.01            0.01
Private                   9333.02        31627.34        40960.36
----------------  --------------- --------------- ---------------
Total                     9333.02        31627.35        40960.37
</code></pre>

<p>1번 노드에 프로세스가 할당 되었기 때문에 메모리도 1번 노드부터 먼저 사용되고 있는 것을 확인 할 수 있다. 그리고 요구하는 메모리양이 노드를 넘어선 후 부터 옆 노드로 메모리 할당을 요청하게 된다.</p>

<p>numad에 적용가능한 몇 가지 옵션을 살펴보자.</p>

<p><img src="/images/2016/07/numad-options.png" alt="" /></p>

<p>옵션 중에서 -K 옵션의 경우 numad가 기본적으로 프로세스가 실행되는 노드의 CPU와 메모리를 맞추려고 하기 때문에 interleave 된 메모리에 대해서 interleave 상태로 유지하도록 지정하는 옵션이다. 일반적인 상황에서는 별 문제 없지만 앞서 살펴봤던 단일 어플리케이션이 단일 노드 이상의 자원을 사용 할 경우 interleaved memory를 활용하는데 있어서 영향을 줄 수 있기 때문에 참고하자.</p>

<p>numad를 기본으로 실행하고 테스트하던 프로그램을 numactl을 통해서 interleaved memory로 할당해 보자.</p>

<pre><code>$ sudo numad -i 1:5
$ sudo numactl --interleave=all ./mallc &amp;
[1] 109091
$ sudo numastat -p malloc

Per-node process memory usage (in MBs) for PID 109091 (malloc)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.00            0.00            0.00
Stack                        0.00            0.01            0.01
Private                  15176.32        25784.04        40960.36
----------------  --------------- --------------- ---------------
Total                    15176.32        25784.05        40960.37
</code></pre>

<p>numactl로 interleave 설정을 하였음에도 불구하고 메모리를 할당하는 과정에서 중간중간에 1번 노드로 리소스가 더 많이 할당 되는 것을 볼 수 있다. 즉, interleaved memory를 최대한 로컬 노드로 할당하려고 numad는 영향을 주게 된다. 이제 numad를 -K1 옵션으로 설정하고 다시 실행 한 후에 프로그램을 실행 해 보자.</p>

<pre><code>$ sudo numad -K1 -i 1:5
$ sudo numactl --interleave=all ./malloc &amp;
[1] 109861
$ sudo numastat -p malloc

Per-node process memory usage (in MBs) for PID 109861 (malloc)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.00            0.00            0.00
Stack                        0.01            0.00            0.01
Private                  20480.03        20480.33        40960.36
----------------  --------------- --------------- ---------------
Total                    20480.04        20480.33        40960.37
</code></pre>

<p>numactl로 설정한대로 interleaved memory에 영향을 주지 않음을 확인 할 수 있다. 따라서, numad를 사용하는 환경에서 interleaved memory를 유지하려면 -K1 옵션을 켜고 사용해야 한다.</p>

<p>-H 옵션은 Transparent Hugepage에 대해서 스캔하는 주기를 설정하는 옵션으로 /sys/kernel/mm/tranparent_hugepage/khugepaged/scan_sleep_millisecs 에 설정된 10000ms를 1000으로 기본으로 바꾼다. THP를 사용하는 환경이라면 NUMA 노드간에 메모리 내용이 이동 되면 THP를 재구축해야하기 때문에 스캔 주기를 짧게 줄 수록 성능 향상에 도움이 된다.</p>

<p>-u 옵션은 노드의 최대 소비 비율(%)을 설정하는데 실행되는 프로세스가 단일 노드 자원으로도 충분하다면 이 값을 100으로 설정하여 각 노드의 자원 소비율을 최대로 높이는 것이 유리하다. 즉, 최대한 동일한 노드의 CPU와 메모리를 사용하도록 하는 것이다.</p>

<p>이외의 옵션들은 man numad를 참고하도록 하자.</p>

<h3 id="2-4-automatic-numa-balancing">2-4. Automatic NUMA Balancing</h3>

<p>커널 3.8에서 Automatic NUMA Balancing(<a href="https///lwn.net/Articles/524977/" target="_blank">LWN 문서</a>)에 대한 기능이 본격적으로 적용되었고 따라서, 3.10 커널을 기본으로 하는 RHEL/CentOS 7 부터는 해당 기능이 기본적으로 활성화 되어서 제공 되고 있다.</p>

<p>앞서 살펴본 유틸리티들이 사용자레벨에서 NUMA 정책을 설정하는 것이었다면 Automatic NUMA Balancing은 커널레벨에서 제공하는 기능이다. 간단히 설명하자면 주기적으로 프로세스의 메모리 매핑을 해제하고 메모리를 프로그램이 실행되는 노드로 옮기며 반대로 태스크를 메모리에 가깝게 옮기는 등의 기능을 수행한다. 기능적인 측면으로 볼 때 numad와 유사하다고 보면 되며 상세한 내용은 <a href="http://events.linuxfoundation.org/sites/events/files/slides/summit2014_riel_chegu_w_0340_automatic_numa_balancing_0.pdf" target="_blank">2014 Redhat Summit</a>에서 발표 된 자료를 참고하자.</p>

<p>Automatic NUMA Balancing의 활성화 여부는 아래 커널 키 값으로 확인 해 볼 수 있다. NUMA를 지원하는 시스템이라면 기본 값이 1이다. (RHEL/CentOS 7 이상)</p>

<pre><code>$ cat /proc/sys/kernel/numa_balancing
1
$ sysctl kernel.numa_balancing
kernel.numa_balancing = 1
</code></pre>

<p>해당 값을 0으로 변경하면 커널에서 제공하는 NUMA Balancing 기능은 비활성화 되게 된다.</p>

<h3 id="2-5-성능-비교">2-5. 성능 비교</h3>

<p><a href="https///access.redhat.com/articles/1286673" target="_blank">RED HAT ENTERPRISE LINUX 7: OPTIMIZING MEMORY SYSTEM PERFORMANCE</a> 문서에서 제공하는 numactl, numad 그리고 Automatic NUMA Balancing의 비교 자료를 살펴보면</p>

<p><img src="/images/2016/07/numa-performance.png" alt="" /></p>

<p>노란색 라인의 커널이 제공하는 Automatic NUMA Balancing도 나쁘지 않은 결과를 보이고 있다. NUMA에 대한 관리를 전혀 하지 않거나 numad를 기본으로만 띄운 것 보다 좋다. 그리고, 엔지니어/관리자가 수동으로 설정한 하늘색 라인(numactl)이 좋은 성능을 보이는 것으로 보이는데 이에 못지 않게 numad를 -u100, -H100 옵션을 주고 실행 한 경우도 결과가 좋다. 즉, 엔지니어/관리자가 직접 개입해서 설정하는 것도 좋지만 관리의 편의성을 고려한다면 numad의 옵션을 적절히 설정해서 운영하는게 좋다고 판단 된다.</p>

<h3 id="2-6-기타">2-6. 기타</h3>

<p>numactl의 man page를 읽어봤다면 알겠지만 numactl에서(2.0.8 이상) 지정하는 노드 번호에 NUMA I/O Feature가 추가 되었다. 즉, 단순히 CPU에 대한 노드번호가 아니라 특정 네트워크 장치, IO 장치, 파일 등으로 지정이 가능하다. 이는 특정 프로세스가 I/O 처리를 할 때 사용되는 인터럽트, 메모리 데이터 등이 해당 I/O 장치와 연계된 노드로 할당하여 보다 나은 성능을 얻을 수 있도록 하기 위함이다.</p>

<h2 id="3-정리">3. 정리</h2>

<p>NUMA는 멀티프로세서 환경에서의 성능 개선을 위한 방안으로 나온 구조이다. 절대적으로 완벽한 구조는 아니겠지만 잘 활용한다면 충분히 효과를 볼 수 있다. 앞서 살펴 본 내용을 토대로 아래와 같은 운영방법을 고려 해 볼 수 있다.</p>

<h3 id="3-1-node-interleaving-vs-numactl-interleave">3-1. Node Interleaving vs numactl interleave</h3>

<ul>
<li><p>Node Interleaving</p>

<ul>
<li>특정 어플리케이션이 전체 메모리의 과반 이상을 사용하는 경우 고려 해 볼 수 있지만 추천하지 않는다.</li>
<li>해당 어플리케이션 입장에서는 <code>numactl --interleave=all</code> 과 거의 같은 결과일 수 있으나</li>
<li>BIOS에서 설정한다면 전체적으로 떨어지는 응답속도를 해당 어플리케이션 뿐만 아니라 다른 프로세스들과 커널도 영향을 받는다.</li>
</ul></li>

<li><p>interleaved memory가 필요한 어플리케이션을 numactl로 interleaved memory를 사용하도록 실행하는게 바람직하다.</p></li>
</ul>

<h3 id="3-2-numad">3-2. numad</h3>

<ul>
<li><p>numad는 프로세스를 동일 노드에서 실행되도록 최선을 다하는 데몬</p></li>

<li><p>대량의 멀티프로세스 또는 다수의 싱글 프로세스 들이 많은 시스템에 적합</p></li>

<li><p>가상머신을 사용하는 환경에서도 가상 머신이 동일 노드 안에서 수행되도록 정렬하므로 유리함</p></li>

<li><p>단일 프로세스가 단일 노드 이상의 리소스 자원을 요구하고 사용할 때</p>

<ul>
<li>어차피 단일 노드 이상의 리소스이므로 옆 노드로 메모리 할당이 발생 함</li>
<li>최대한 로컬 메모리 접근이 많이 발생 할 수 있도록 하기 때문에 나쁘지 않다</li>
<li>멀티스레드의 경우는 interleave가 유리 하다

<ul>
<li>프로세스가 메모리 리소스를 할당하고 스레드는 그 메모리를 공유하며 작업을 수행하는 경우 (ex. mongod)</li>
<li>즉, 다양한 스레드가 여러 노드에서 분산되어 실행 된다면 메모리가 모든 노드에 뿌려져 있는게 더 효율적임</li>
<li>3-3. interleaved memory 항목의 그림 참조</li>
</ul></li>
</ul></li>

<li><p>만약, interleave가 필요한 어플리케이션이 존재한다면 -K1 옵션을 주고 실행하자</p></li>

<li><p>커널 3.8 이상의 시스템에서는 Auto NUMA balancing이 있기 때문에 numad는 필요 없나?</p>

<ul>
<li>좀 더 효율을 높이고자 한다면 <code>numad -u100</code> 설정으로 운영하는 것을 권장</li>
</ul></li>
</ul>

<h3 id="3-3-interleaved-memory">3-3. interleaved memory</h3>

<ul>
<li><p>interleaved memory가 만능 해결 책은 아니다</p>

<ul>
<li>옆 노드에의 접근속도가 느리기 때문에 RR(round-robin)로 할당하는 메모리는 평균적으로 로컬보다 느리다</li>
</ul></li>

<li><p>아래의 조건을 충족하는 경우에 효율적이다</p>

<ul>
<li>단일 어플리케이션이 노드를 넘어서는 많은 메모리를 사용하는 경우</li>
<li>해당 어플리케이션이 싱글 프로세스가 아니라 멀티쓰레드 형태로 되어있는 경우

<ul>
<li>즉, 수 많은 쓰레드가 전체 노드의 CPU를 골고루 사용한다면 메모리가 흩어져있는 편이 낫다</li>
</ul></li>
</ul></li>
</ul>

<p><img src="/images/2016/07/numa-interleave.png" alt="" /></p>

<p>위의 그림처럼 프로세스가 NODE0에 있는 메모리(Memory 0)을 할당받아 사용하고 스레드는 여러 노드의 CPU에서 동작 될 때 CPU1을 사용하는 스레드는 공유하는 메모리가 다른 노드에 있기 때문에 응답속도가 CPU0를 사용하는 스레드보다 느리게 된다.</p>

<ul>
<li>아래의 경우에는 interleaved memory 보다는 numad를 권장

<ul>
<li>싱글 프로세스 형태의 프로그램이 노드를 넘어서는 많은 메모리를 사용하는 경우</li>
<li>여러 프로세스의 노드 별 정렬이 중요 할 때 (ex. Hypervisor)</li>
</ul></li>
</ul>

<h2 id="a-추천-링크">A. 추천 링크</h2>

<p>아래는 NUMA와 관련된 추천 글에 대한 링크이다.</p>

<ul>
<li><p><a href="http://frankdenneman.nl/2016/07/06/introduction-2016-numa-deep-dive-series/" target="_blank">NUMA Deep Dive Series</a> : 2016년 7월 10일 기준 연재 중 이며 NUMA 환경과 가상화(VMware) 환경에 대한 부분을 중점적으로 다루고 있다. 그림 정리도 좋기 때문에 적극 추천.</p></li>

<li><p><a href="http://rhelblog.redhat.com/2015/01/12/mysteries-of-numa-memory-management-revealed/" target="_blank">Mysteries of NUMA Memory Management Revealed</a>: 본문에서 언급한 OPTIMIZING MEMORY SYSTEM PERFORMANCE 백서에 대한 글</p></li>

<li><p><a href="http://www.slideshare.net/tommylee98229/shak-larryjederperfandtuningsummit14part1final" target="_blank">Performance Analysis and Tuning 2014</a>: Red Hat Summit 2014에서 발표된 자료. NUMA뿐만 아니라 전반적인 성능 튜닝에 대한 발표 자료.</p></li>

<li><p><a href="https///youtu.be/ckarvGJE8Qc" target="_blank">Performance Analysis and Tuning 2015</a>: Red Hat Summit 2015의 발표 영상.</p></li>

<li><p><a href="https///events.linuxfoundation.org/sites/events/files/eeus13_shelton.pdf" target="_blank">High Performance I/O with NUMA Systems in Linux</a>: Fusion IO에서 발표한 NUMA와 I/O에 대한 자료</p></li>

<li><p><a href="http://en.community.dell.com/cfs-file/__key/telligent-evolution-components-attachments/13-4491-00-00-20-26-69-46/NUMA-for-Dell-PowerEdge-12G-Servers.pdf" target="_blank">NUMA Best Practices for Dell PowerEdge 12th Generation Servers</a>: PDF 파일로 Dell 서버에서의 NUMA 및 NUMA I/O에 대해서 다루고 있다.</p></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Bash FAQ (Part 1)</title>
            <link>/2016/03/21/bash-faq-part-1/</link>
            <pubDate>Mon, 21 Mar 2016 07:39:59 +0000</pubDate>
            
            <guid>/2016/03/21/bash-faq-part-1/</guid>
            <description>본 문서는 Bash 쉘 스크립트에 대한 FAQ 내용을 정리한 문서입니다. 따라서, 기본적인 Bash 쉘 스크립트의 문법과 사용 방법에 대해서는 알고 있다는 가정하에 작성되었습니다. Part 1이라고 제목을 붙인 이유는 자주묻는 질문이 추가로 생기거나 생각나는대로 FAQ를 정리해서 올리려고 합니다. 어찌보면 위키로 정리하는게 적합한 문서이기도 합니다.
[[ ]] vs [ ] if 구문과 단짝처럼 사용되는 []에 대해서 [[]]과 []의 차이점을 묻는 경우가 많다. 먼저, 각각의 의미를 살펴보도록 하자.
if/then 구문의 경우 해당 구문의 결과(exit 값)에 대한 조건을 판단하고 구문의 내용을 실행하도록 되어있다.</description>
            <content type="html"><![CDATA[

<p>본 문서는 Bash 쉘 스크립트에 대한 FAQ 내용을 정리한 문서입니다. 따라서, 기본적인 Bash 쉘 스크립트의 문법과 사용 방법에 대해서는 알고 있다는 가정하에 작성되었습니다. Part 1이라고 제목을 붙인 이유는 자주묻는 질문이 추가로 생기거나 생각나는대로 FAQ를 정리해서 올리려고 합니다. 어찌보면 위키로 정리하는게 적합한 문서이기도 합니다.</p>

<h2 id="vs">[[ ]] vs [ ]</h2>

<p>if 구문과 단짝처럼 사용되는 []에 대해서 [[]]과 []의 차이점을 묻는 경우가 많다. 먼저, 각각의 의미를 살펴보도록 하자.</p>

<p>if/then 구문의 경우 해당 구문의 결과(exit 값)에 대한 조건을 판단하고 구문의 내용을 실행하도록 되어있다. 그렇기에 원래 if/then 구문은 아래와 같이 사용되었다.</p>

<pre><code>if test 3 \&gt; 2; then
	echo &quot;3이 2보다 큽니다&quot;
fi
</code></pre>

<p>test 명령은 주어진 내용에 대한 참, 거짓을 판별하여 리턴 값을 결정하는 명령이며 보통 <code>/bin/test</code>에 위치해 있다. 그리고, 이 test 명령을 좀 더 보기 편하게 쓰기 위해서 존재하는 명령이 있는데 그게 <code>[</code> 이다. 흔히들 <code>[</code>는 Bash의 문법으로 알고 있지만 실제로 존재하는 실행 파일이며 보통 <code>/bin/[</code>에 위치해 있다. (어떤 배포판의 경우에는 test에 대한 심볼릭 링크로 존재하기도 한다)</p>

<pre><code>$ ls -l /bin/[
-rwxr-xr-x. 1 root root 41448 Jun 10  2014 /bin/[
</code></pre>

<p>그래서 우리가 알고 있는 if 구문에서 <code>-f</code>와 같은 연산자가 실질적으로 test 명령의 옵션이다. 옵션처럼(-로 시작하는)생긴 테스트 연산자를 사용하는 이유가 <code>[</code>는 test는 실질적으로 같은형태의 명령이다. 그리고, 이 둘은 Bash Builtin(내장형 명령)으로도 구현이 되어있다. 성능상 이유로 Bash 내장형 명령으로 구현이 되어있으며 단순히 명령 형태로 실행을 하면 <code>/bin</code>아래의 실행 파일로 처리를 하고 Bash 인터프리터로 처리하면 내장형 명령이 동일한 기능을 수행하여 처리한다.</p>

<pre><code>$ test 3 \&gt; 2
$ [ 3\&gt; 2 ]

위와 같이 사용하면 /bin 아래에 있는 명령을 실행하게 된다
</code></pre>

<p>그리고, Bash 2.02 버전에서는 확장된 테스트 명령으로 <code>[[</code>라는 키워드를 선보였다. (즉, <code>[</code>처럼 외부 프로그램으로 존재하지는 않는다) 이 확장된 테스트 명령은 기존 <code>[</code>과 달리 일반적인 프로그래밍 언어에서 사용되는 비교 명령처럼 동작을 하는 차이점을 갖는다. (ksh으로 부터 차용된 기능이다)</p>

<pre><code>f=&quot;/bin/unknown&quot;

if [[ -e $f ]]; then
  echo &quot;unknown exists&quot;
fi

if [ -e $f ]; then
  echo &quot;unknown exists&quot;
fi
</code></pre>

<p>위 두가지 형태의 사용은 동일한 결과를 갖는다. 일반적인 파일에 대한 테스트에서는 별다른 차이점을 보기 힘들지만 아래와 같은 수식 연산에 대한 부분에서는 차이점을 느낄 수 있다.</p>

<pre><code>x=10
if [[ $x -gt $z ]]; then
  echo &quot;X가 Z보다 크다&quot;
fi
if [ $x -gt $z ]; then
  echo &quot;X가 Z보다 크다&quot;
fi
</code></pre>

<p>위에서 <code>[</code>으로만 테스트한 구문은 <code>-bash: [: 10: unary operator expected</code> 같은 오류 메시지를 발생시킨다. 왜냐하면 $z는 존재하지 않는 변수이기 때문에 null로 대체 되고 <code>[ $x &gt; ]</code>는 미완성 구문이 되어서 오류가 일어나는 것이다. 하지만, <code>[[</code> 의 경우는 1차적으로 내부 구문에 대해서 번역을 시도하고 존재하지 않는 변수 $z를 0으로 처리한다. 그래서 0보다 큰 10에 대한 숫자 비교 처리가 가능하다. 또한, <code>[[</code> 키워드는 아래와 같은 차이점을 보인다.</p>

<p><img src="/images/2016/03/bash-faq-test.png" alt="" /></p>

<ul>
<li>출처: <a href="http://mywiki.wooledge.org/BashFAQ/031" target="_blank">http://mywiki.wooledge.org/BashFAQ/031</a></li>
</ul>

<p>위 표를 살펴보면 AND, OR를 나타내는 -a, -o 대신에 보다 프로그래밍 언어같은 &amp;&amp;, ||를 제공하며 정규표현식 매칭을 제공한 다는 것을 확인 할 수 있다. 즉, <code>[[</code> 구문이 <code>[</code>보다 유용하고 장점이 많아 보인다.</p>

<p><code>[[</code>는 <code>[</code>보다 안전한 테스트 구문을 만들 수 있다는 점이 좋지만 POSIX 표준은 아니기 때문에 범용성 면에서는 떨어진다. 다만, Bash 2.02 버전 이상을 사용한다는 전제 조건만 있다면 <code>[[</code> 구문이 더 많은 장점을 갖게 된다. 그리고 <code>[</code>을 사용할 경우에는 아래와 같이 변수를 <code>&quot;&quot;</code>로 감싸는 습관을 갖는 것이 좋다. 위에서 오류가 발생한 구문을 <code>&quot;&quot;</code>로 감싸서 실행해 보면</p>

<pre><code>x=10
if [ &quot;$x&quot; -gt &quot;$z&quot; ]; then
  echo &quot;X가 Z보다 크다&quot;
fi

-bash: [: : integer expression expected
</code></pre>

<p>좀 더 명확한 오류를 찾을 수 있는 메시지(숫자연산에서 문자열이 사용되었음)가 나타나게 된다. 그리고 문자열의 경우에 있어서도 <code>&quot;&quot;</code>로 감싸게 되면 null 문자열로 비교 테스트가 가능하다.</p>

<pre><code>#!/bin/bash
# 매개변수를 주지않으면 아래 테스트 구문은 연산 오류를 일으킨다
if [ $1 = &quot;hi&quot; ]; then
    echo &quot;there&quot;
fi

# 아래와 같이 &quot;&quot;로 감싸게 되면 null 문자열(&quot;&quot;)로 처리되어 오류가 발생하지는 않는다
if [ &quot;$1&quot; = &quot;hi&quot; ]; then
    echo &quot;there&quot;
fi

# 종종 아래와 같이 양쪽 변수에 공통의 문자를 붙여서 매개변수가 누락되어도 오류가 발생하지 않는 트릭을 사용하기도 한다
if [ x$1 = xhi ]; then
    echo &quot;there&quot;
fi
</code></pre>

<h2 id="시그널-처리">시그널 처리</h2>

<p>쉘 스크립트도 시그널에 대한 처리를 수행 할 수 있다. trap이란 커맨드를 통해서 사용 가능하며 사용방법은 아래와 같다.</p>

<pre><code>trap 처리명령 시그널
</code></pre>

<p>처리명령은 Bash 함수를 지정해도 되기 때문에 아래와 같은 형태로 구현이 가능하다.</p>

<pre><code>sig_int() {
  echo &quot;Interrupted&quot;
  exit 1
}
sig_exit() {
  echo &quot;Exit&quot;
  exit 0
}

trap sig_int TERM
trap sig_int INT
trap sig_exit EXIT
</code></pre>

<p>위 예시의 경우 Interrupt(INT), Terminate(TERM), Exit(EXIT) 시그널을 해당 스크립트가 받게 되면 각각 지정된 함수가 실행되고 종료값을 달리하여 끝마칠 수가 있다. 만약 처리명령에 <code>''</code>을 지정하게 되면 해당 시그널은 무시하게 되고 <code>-</code>를 지정하면 Bash 기본 시그널처리로 동작하게 된다.</p>

<h2 id="버전-비교">버전 비교</h2>

<p>Bash 스크립트를 작성해서 사용하다보면 버전 비교가 필요한 경우가 있다. 버전 번호를 확인하고 최신버전이면 패키지를 업데이트 한다거나 특정 버전을 충족 하는지 비교해보고자 할 때 등이 그러한 경우인데 버전 번호를 비교하는 함수를 만들어두고 사용하면 좋다. 버전 번호를 비교하는 함수에 대한 구현은 다양한 방법이 있지만 그 중에서 한가지 방법을 소개한다.</p>

<pre><code>version_comp() {
    if [[ $1 == $2 ]]; then # string match
        return 0
    fi
    local i
    ver1=(${1//./ })
    ver2=(${2//./ })
    for ((i=${#ver1[@]}; i&lt;${#ver2[@]}; i++))
    do
        ver1[i]=0
    done
    for ((i=0; i&lt;${#ver1[@]}; i++))
    do
        if [[ -z ${ver2[i]} ]]; then
            ver2[i]=0
        fi
        if [[ ${ver1[i]} -gt ${ver2[i]} ]]; then
            return 1
        fi
        if [[ ${ver1[i]} -lt ${ver2[i]} ]]; then
            return 2
        fi
    done
    return 0
}
</code></pre>

<p>위 함수는 2개의 인자 값을 받고 그 값을 비교하는 함수이다. 첫 번재 인자 값이 현재 버전 두 번째 인자 값이 확인하려는 버전 값이라고 가정을 하여 동작하도록 되어있는데 순서대로 살펴보도록하자.</p>

<pre><code>    if [[ $1 == $2 ]]; then # string match
        return 0
    fi
</code></pre>

<p>먼저, 2개의 문자열이 동일한지 확인한다. 완전히 일치하는 2개의 값이라면 굳이 비교 연산을 따로 할 필요가 없기 때문에 바로 0을 리턴하도록 한다.</p>

<pre><code>    ver1=(${1//./ })
    ver2=(${2//./ })
</code></pre>

<p>그리고, 2개의 변수를 <code>.</code>을 기준으로 분리한다. Bash에서 제공하는 문자열 처리 기능을 이용한 것인데 이를 잠깐 살펴보면 아래와 같다.</p>

<pre><code>${#string} - 문자열 길이를 리턴한다
${string:position} - 지정 된 위치(position)로부터 문자를 추출한다
${string:position:length} - 지정 된 위치(position)로 부터 지정 된 길이(length)만큼 문자를 추출한다
${string#substring} - string에서 substring과 앞에서부터 가장 짧게 매칭되는 부분을 삭제
${string##substring} - string에서 substring과 앞에서부터 가장 길게 매칭되는 부분을 삭제
${string%substring} - 위에서 사용된 #의 반대 형태로 뒤에서부터 가장 짧게 매칭되는 부분을 삭제
${string%%substring} - 위에서 사용된 ##의 반대 형태로 뒤에서부터 가장 짧게 매칭되는 부분을 삭제
${string/substring/replacement}	- 첫 번째로 매칭되는 substring을 replacement로 대체
${string//substring/replacement} - 모든 substring을 replacement로 대체
${string/#substring/replacement} - 앞에서부터 가장 먼저 매칭되는 substring을 replacement로 대체
${string/%substring/replacement} - 뒤에서부터 가장 먼저 매칭되는 substring을 replacement로 대체
</code></pre>

<p>이러한 문자열 처리에 대한 예시는 <a href="http://tldp.org/LDP/abs/html/string-manipulation.html" target="_blank">이 문서</a>를 참고하도록 하자.</p>

<pre><code>    for ((i=${#ver1[@]}; i&lt;${#ver2[@]}; i++))
    do
        ver1[i]=0
    done
</code></pre>

<p>앞서 2개의 변수를 <code>.</code> 기준으로 분리하여 배열로 저장을 했는데 이 변수는 다시 첫 번째 변수의 배열 길이를 기준으로 두 번째 변수의 배열 길이까지 루프를 돌게 된다. 이러한 부분이 추가된 이유는 현재 버전 값이 <code>3.4</code>인데 확인하려는 버전 값이 <code>3.4.1</code>로 버전 번호 구분 자릿 수가 다른 경우에 대해서 0으로 채워서 비교가 가능하도록 한다.</p>

<pre><code>    for ((i=0; i&lt;${#ver1[@]}; i++))
    do
        if [[ -z ${ver2[i]} ]]; then
            ver2[i]=0
        fi
        if [[ ${ver1[i]} -gt ${ver2[i]} ]]; then
            return 1
        fi
        if [[ ${ver1[i]} -lt ${ver2[i]} ]]; then
            return 2
        fi
    done
</code></pre>

<p>이제 현재 버전 값($ver1)을 기준으로 루프를 돌면서 각 자릿수를 비교하는데 만약 확인하려는 버전 값($ver2)이 자릿수가 다를경우 0으로 채워주고 비교를 한다. 비교 결과 현재 버전이 더 높으면 1을 더 낮으면 2를 리턴하도록 하여 이 함수를 사용하여 리턴 값을 보고 버전 판단을 하면 된다.</p>

<h2 id="if-vs-case">if vs case</h2>

<p>비교 구문에 있어서 가장 일반적인 것은 if문이지만 case가 좀 더 유용한 경우가 있다. 특정 값에 대한 판별을 할 때 한가지 조건이 아니라 여러 값인 경우가 대표적인데 예를 들어 특정 스크립트에 옵션으로 주어진 값에 대한 처리를 할 때 유용하다.</p>

<p>예를 들면, 첫 번째 인자 값이 -d, &ndash;delete, -r, &ndash;remove 인 경우에 대해서 동일 한 액션을 취한다고 했을 때 if 문을 이용하면 각 경우에 대해서 모두 테스트 처리를 해야하지만 case를 이용하면 아래와 같이 사용이 가능하다</p>

<pre><code>case $1 in
    -r|--remove|-d|--delete) echo &quot;Delete option&quot;;;
esac
</code></pre>

<p>따라서, 단일 값에 대한 여러 문자 판단 및 간단한 패턴 판탄의 경우에는 <code>csae</code>를 사용하는 것이 가독성도 좋고 사용하기도 편리하다.</p>

<h2 id="sed-i-옵션의-위험성">sed -i 옵션의 위험성</h2>

<p>Bash 쉘 자체에 대한 이야기는 아니지만 쉘 스크립트를 작성 할 때 자주 보이는 실수이기 때문에 <code>sed -i</code>옵션을 사용하는 경우에 대해서 간단히 소개하고자 한다. <code>sed -i</code>의 경우 sed로 수정 한 내용을 바로 파일에 적용하는 옵션인데 주의 할 사항은 sed가 처리한 문자열에 대해서 파일에 적용 할 때 오픈 된 파일에 대해서 내용을 수정해서 저장하는 것이 아니라 임시파일을 생성해서 덮어씌우는 형태로 동작한다는 것이다. sed를 -i 옵션을 주고 실행할 때 동작을 strace로 살펴보면 아래와 같다.</p>

<pre><code>open(&quot;./sed1nw6ql&quot;, O_RDWR|O_CREAT|O_EXCL, 0600) = 4
umask(02)                               = 0700
fcntl(4, F_GETFL)                       = 0x8002 (flags O_RDWR|O_LARGEFILE)
fstat(3, {st_mode=S_IFREG|0775, st_size=18, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f64fc144000
read(3, &quot;this is test text\n&quot;, 4096)    = 18
fstat(4, {st_mode=S_IFREG, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f64fc143000
write(4, &quot;this is test file\n&quot;, 18)     = 18
... 생략 ...
rename(&quot;./sed1nw6ql&quot;, &quot;string.txt&quot;)     = 0
close(1)                                = 0
close(2)                                = 0
</code></pre>

<p>위 내용을 보면 임시파일을 생성해서 처리한 문자열을 저장하고 임시파일을 rename해서 원본 파일을 생성하는 것을 볼 수 있다. 일반적인 경우에는 별 문제가 되지 않지만 대상 파일이 심볼릭 링크의 경우에는 이야기가 달라진다. 보통 RHEL/CentOS 계열 배포판의 경우 <code>rc.local</code> 파일이 /etc/rc.d/rc.local에 존재하고 /etc/rc.local에 심볼릭 링크를 걸어서 사용한다. 그런데 <code>sed -i</code>의 대상을 /etc/rc.local 심볼릭 파일로 지정하게 되면 처리한 내용을 임시파일에 저장하고 이를 /etc/rc.local로 rename해서 생성해버리기 때문에 /etc/rc.local은 일반 파일로 변해버리고 실제 시스템이 사용하는 /etc/rc.d/rc.local과 별개의 파일로 다뤄지게 된다. 아래에 예시를 보자.</p>

<pre><code>$ ls -l /etc/rc.local
lrwxrwxrwx. 1 root root 13 Oct 20 16:28 /etc/rc.local -&gt; rc.d/rc.local
$ sed -i &quot;s/performance/powersave/&quot; /etc/rc.local
$ ls -l /etc/rc.local
-rwxr-xr-x 1 root root 1361 Mar 18 14:33 /etc/rc.local
</code></pre>

<p>위와 같이 기존 심볼릭링크 파일은 일반 파일로 변해버리기 때문에 의도한대로 시스템에 반영이 되지 않는 경우를 겪을 수 있다. rc.local과 유사하게 /etc/grub.conf (RHEL/CentOS 6까지)의 경우도 /boot/grub 아래에 있는 원본파일을 수정하려는 목적과는 달리 별개의 파일이 /etc/grub.conf로 생성되는 일을 겪을 수 있다.</p>

<p>따라서, 수정하는 대상 파일을 즉시 반영하는 <code>-i</code> 옵션에 대해서는 심볼릭 링크인지 여부를 판단하고 심볼릭 링크이면 대상 파일을 직접 수정하는 형태로 예외처리를 해주는 것이 좋다. 아래는 $_target 변수에 대해서 대상 파일이 심볼릭 링크이면 링크의 대상으로 바꿔주는 처리의 예시이다.</p>

<pre><code>_target=&quot;&quot;
if [ -L &quot;$_target&quot; ]; then
  _target=$( readlink $_target )
fi
</code></pre>

<h2 id="bash-변수-값-반올림">Bash 변수 값 반올림</h2>

<p>가끔 수치 데이터에 대해서 스크립트 처리를 할 때 반올림이 필요한 경우가 있다. 안타깝게도 Bash에는 반올림을 직접 처리해주는 builtin 명령은 없다. 그래서 <strong>awk</strong>,<strong>python</strong>,<strong>perl</strong>과 같은 별도 스크립트 언어를 이용해서 처리하기도 하는데 가장 Bash 스크립트 고유의 형태에 가깝게 반올림 처리하는 방법에 대해서 알아보자. (사실 이것도 bc라는 외부 프로그램을 사용한다)</p>

<pre><code>roundup() {
    echo $( printf %.0f $( echo &quot;scale=1;((10*$1)+0.5)/10&quot; | bc ))
}

$ roundup 1.1
1
$ roundup 1.4
1
$ roundup 1.5
2
</code></pre>

<p>위 명령은 소수값으로 전달 된 인자에 대해서 반올림을 하여 정수로 만들어준다. 내부 내용을 살펴보면 주어진 값을 10으로 곱하고 0.5를 더해서 다시 10으로 나눠서 값을 정리하는데 scale 옵션을 1로 주어 소수 첫 째자리까지 만을 bc에서 다루도록 한다. 이렇게하면 쉽게 반올림 기능을 구현할 수 있다. 하지만, 사실 저 방법은 소수 첫째 자리기준으로만 정확하게 동작하기 때문에 완벽한 방법은 아니다. 예를 들어 1.48을 매개변수로 주게되면 <code>(14.8+0.5)/10</code>이 되므로 1.53으로 여겨져서 결과 값이 <strong>2</strong>가 되어버린다. 따라서, 자리수에 상관없이 소수점 첫째 자리에서 반올림 하도록 정밀도를 높이기 위해서는 아래와 같이 하면 된다.</p>

<pre><code>roundup2() {
    minor=${1#*.} # 소수자리만 남기고 지운다
    prc=$(( 10 ** ${#minor})) # 자리수 개수만큼 10의 제곱으로 구한다
    echo $( printf %.0f $( echo &quot;scale=${#minor};(($prc*$1)+0.5)/$prc&quot; | bc ))
}

$ roundup2 1.4999999
1
</code></pre>

<p>또는 직접 두 번째 인자 값을 받아서 반올림 할 위치를 명시적으로 두고 계산하는 방법도 있다. 위에서 언급한 방법은 수 많은 방법 중의 일부분일 뿐이다.</p>

<h2 id="의존성이-있는-커널-모듈-한-번에-제거하기-재귀함수">의존성이 있는 커널 모듈 한 번에 제거하기 (재귀함수)</h2>

<p>특정 커널 모듈을 제거하는데 있어서 해당 모듈을 사용 중에 있는 다른 모듈이 존재 할 경우에는 제거가 되지 않는데 이를 자동으로 따라가며 삭제하는 방법을 생각해 보자. 보통 <code>lsmod</code>의 실행 결과는 아래와 같다.</p>

<pre><code>$ lsmod
Module                  Size  Used by
drm                   311588  4 ttm,drm_kms_helper,vmwgfx
scsi_transport_sas     41034  1 mptsas
mptscsih               40150  1 mptsas
i2c_core               40325  3 drm,i2c_piix4,drm_kms_helper
libata                218854  3 pata_acpi,ata_generic,ata_piix
mptbase               105960  2 mptsas,mptscsih
... 생략 ...
</code></pre>

<p>각 모듈의 이름과 이 모듈을 사용중인 다른 모듈에 대한 목록을 얻을 수 있는데 이것을 이용해서 의존성에 따른 모듈을 먼저 찾아내서 제거하고 마지막으로 원하는 모듈을 삭제하는 방법을 들 수 있다.</p>

<pre><code>r_rmmod() {
    _list=$( lsmod | grep &quot;^$1 &quot; | awk '{print $4}' | sed &quot;s/,/ /g&quot; )
    for x in $_list
    do
        r_rmmod $x
    done
    rmmod $1
}
</code></pre>

<p>위의 방법은 재귀함수를 이용한 방법이다. 간단히 제거하고자 하는 모듈을 인자 값으로 받고 lsmod에서 보여주는 의존성이 있는 모듈의 목록을 얻은 후 이 모듈들에 대해서 다시 의존성 모듈 목록을 얻고 차례대로 rmmod로 제거하는 형태이다. 재귀를 이용하기 때문에 스크립트는 그리 길지 않다. 다만, 실제 사용중인 (마운트 등) 모듈의 경우에는 위와 같은 방법으로도 제거되지 않을 수 있으며 모듈 제거는 <strong>위험한</strong>작업 중 하나이기 때문에 주의해야 한다. 여기에서는 재귀함수 사용의 예시로 소개했다.</p>

<h2 id="여러대의-원격서버에-명령-수행">여러대의 원격서버에 명령 수행</h2>

<p>요즘에는 ansible을 비롯한 좋은 배포 툴들이 있기 때문에 그 활용도가 많이 줄어들었지만 간단히 여러대의 서버에 원하는 명령을 실행하고 싶을 때에는 <code>xargs</code>를 이용하면 편리하다. 먼저 접속하여 실행 할 대상 서버목록을 target.txt로 저장해두고 아래와 같이 실행하면 된다.</p>

<pre><code>$ cat target.txt | xargs -ILUNA ssh -l username LUNA 'swapon -a'
</code></pre>

<p>위 명령은 target.txt 파일에 저장된 대상 서버 호스트명(주소)을 xargs에 넘겨서 각각 서버에 username으로 접속하여 스왑을 활성화 하는 명령을 수행한다. <code>-I</code> 옵션은 대치 문자열을 지정하는 것이다. 일반적으로 xargs는 표준입력으로 넘겨 받은 값에 대해서 맨 마지막에 인자로 더해서 실행을 하지만 <code>-I</code>옵션으로 대치 문자열을 지정하면 원하는 위치에 표준입력으로 넘겨 받은 값을 지정 할 수가 있다. 따라서, 위의 예시에서는 ssh로 접속 할 서버의 호스트명(주소) 위치에 <code>LUNA</code>라는 대치문자열로 지정하여 사용하는 것이다.</p>

<pre><code>$ cat target.txt | xargs -P10 -ILUNA ssh -l username LUNA 'swapon -a'
</code></pre>

<p>위의 예시는 <code>-P</code> 옵션이 추가되었는데 이는 동시에 실행 할 최대 프로세스의 개수를 지정하는 것인데 <code>-P10</code>의 경우 넘겨받은 대상 서버를 10대씩 접속하여 처리 할 수가 있다. 많은 수의 서버에 대해서 수행 할 때에는 <code>-P</code>옵션을 주면 유용하다.</p>

<p>xargs와 유사한 툴로는 <a href="http://www.gnu.org/software/parallel/" target="_blank">GNU Parallel</a>이 있다. 이 툴은 병렬처리에 최적화 된 툴로 xargs도 병렬 처리를 제공하지만 GNU Parallel의 경우에는 CPU Core 당 작업 개수 지정 및 출력의 그룹화와 같은 병렬처리에 최적화 된 기능을 많이 제공하고 있다. (상세한 차이점은 <a href="http://www.gnu.org/software/parallel/" target="_blank">공식문서</a> 참조) 아무래도 프로그램의 태생의 차이가 다르기 때문에 병렬처리에 있어서는 GNU Parallel이 xargs과 같은 툴보다는 우세하다. 다만, 간단한 작업을 돌리기에는 xargs로도 충분하고 간편하다.</p>

<h2 id="실행-결과에-타임스탬프-남기기">실행 결과에 타임스탬프 남기기</h2>

<ul>
<li>참고: <a href="http://mywiki.wooledge.org/BashFAQ/107" target="_blank">http://mywiki.wooledge.org/BashFAQ/107</a></li>
</ul>

<p>보통 아래와 같이 함수를 생성해 두고 특정 작업에 대한 출력 결과를 파이프로 전달해서 실행 된 시간마다 타임스탬프를 기록하며 사용한다.</p>

<pre><code>ts_gen() {
    while IFS= read -r line; do
        echo &quot;$(date +%Y%m%d-%H:%M:%S) $line&quot;
    done
}

$ find / -name &quot;*.so&quot; | ts_gen
20160318-15:47:48 /opt/pypy/lib/libtk.so
20160318-15:47:48 /opt/pypy/lib/libssl.so
20160318-15:47:48 /opt/pypy/lib/libcrypto.so
20160318-15:47:48 /opt/pypy/lib/libgdbm.so
20160318-15:47:48 /opt/pypy/lib/libexpat.so
20160318-15:47:48 /opt/pypy/lib/libsqlite3.so
20160318-15:47:48 /opt/pypy/lib/libffi.so
20160318-15:47:48 /opt/pypy/lib/libtcl.so
... 생략 ...
</code></pre>

<p>예시를 든 find 명령의 경우는 별 의미는 없는 예시이지만 위 실행 결과가 어떤식으로 보여지는지를 나타내려고 넣었다. 특정 어플리케이션을 <code>--debug</code> 모드로 실행 할 때 끊임 없이 화면을 쳐다보기 보다는 이렇게 타임스탬프를 찍게해서 확인할 때 편리하다. 다만, 중간에 buffer가 끼어들게 되면 정확도는 조금 달라질 수 있지만. 간단히 체크하기에는 무난하다.</p>

<h2 id="vs-1">$( &hellip; ) vs `&hellip;`</h2>

<p>아마 쉘 스크립트 관련 된 문서를 보면 명령치환의 방법으로 `&hellip;`을 사용하는 경우를 많이 봤을 것이다. 그리고, 요즘에는 $(&hellip;) 형태의 명령치환도 자주 보인다. 어떤 것이 더 좋은 방법이냐는 질문을 종종 받았는데 결론부터 이야기하면 $(&hellip;)을 추천한다. (그러면서도 습관적으로 `&hellip;`를 쓰는 내 자신을 보기도 한다)</p>

<p>$(&hellip;)과 `&hellip;`은 일반적으로는 동일한 명령치환으로 알려져 있지만 사용을 해보면 차이점을 느낄 수 있다.</p>

<p>먼저, `&hellip;`의 경우에는 중첩 된 명령치환을 하는데 번거롭고 가독성이 떨어진다.</p>

<pre><code>_cnt=$( grep -ic &quot;$(basename &quot;$1&quot;)&quot; /tmp/tfile)
_cnt=`grep -ic &quot;\`basename \&quot;$1\&quot;\`&quot; /tmp/tfile`
</code></pre>

<p>위 명령은 특정 파일에서 주어진 경로 값의 파일 명을 포함하고 있는 라인 개수를 세는 동작이다. 중첩해서 사용하는데 있어서 <code>\</code>가 많이 사용되고 그로인해 가독성도 떨어진다. <code>\</code>에 의해서 가독성만 떨어지는 것이 아니라 <code>\</code> 문자 자체를 사용하고자 할 때도 복잡하다. `&hellip;`구문 안에서 <code>\</code>문자를 출력한다고 가정해보자.</p>

<pre><code>$ echo &quot;`echo &quot;this is back-slash: \&quot;`&quot;
this is back-slash:
$ echo &quot;`echo &quot;this is back-slash: \\&quot;`&quot;
echo &quot;`echo &quot;this is back-slash: \\&quot;`&quot;
-bash: command substitution: line 1: unexpected EOF while looking for matching `&quot;'
-bash: command substitution: line 2: syntax error: unexpected end of file
$ echo &quot;`echo &quot;this is back-slash: \\\\&quot;`&quot;
this is back-slash: \

$ echo &quot;$(echo &quot;this is back-slash: \\&quot;)&quot;
this is back-slash: \
</code></pre>

<p>위의 예시처럼 <code>\</code>를 출력하기 위해서 <code>\</code>를 4번이나 사용해야 한다. 흔히 <code>\</code>를 앞에 사용해서 이스케이프 시켰다고 생각해서 <code>\\</code>를 사용하지만 결과는 <code>\\</code>를 이스케이프 처리를 위한 <code>\</code>처럼 다루어서 오류를 일으킨다. 그래서 보통 변수가 많지 않은 경우에는 <code>&quot;</code>(쌍따옴표) 대신에 <code>'</code> (홑따옴표)를 이용해서 사용하는데 여기에서도 `&hellip;`은 아래와 같은 모습을 보인다.</p>

<pre><code>$ echo &quot;`echo 'this is back-slash: \'`&quot;
this is back-slash: \
$ echo &quot;`echo 'this is back-slash: \\'`&quot;
this is back-slash: \

$ echo &quot;$(echo 'this is back-slash: \')&quot;
this is back-slash: \
$ echo &quot;$(echo 'this is back-slash: \\')&quot;
this is back-slash: \\
</code></pre>

<p><code>\</code>를 1개를 사용한 결과와 2개를 사용한 결과가 같다. $(&hellip;)이 보여주는 <code>'</code>안에서의 일관된 결과와는 사못 다르다.</p>

<p>따라서, POSIX에 맞지도 않고 오래된 방식인 `&hellip;`보다는 <strong>$(&hellip;)</strong> 사용을 개인적으로 권장한다.</p>
]]></content>
        </item>
        
        <item>
            <title>Load Average에 대하여</title>
            <link>/2016/02/19/about-load-average/</link>
            <pubDate>Fri, 19 Feb 2016 07:13:33 +0000</pubDate>
            
            <guid>/2016/02/19/about-load-average/</guid>
            <description>Load Average 본 문서는 Linux Load Average 값에 대해 소개하고 있습니다.
Load Load Average는 말 그대로 Load 값의 평균을 나타낸 수치를 의미한다. 이를 이해하기 위해서는 먼저 Load가 무엇인지에 대해서 알아보는 것이 필요하다. 흔히, Load는 우리말로 &amp;lsquo;부하&amp;rsquo;로 표현된다. 부하라는 것의 사전적 정의를 보면 아래와 같다.
부하(負荷) 1.짐을 짐 2.[물리] 전기를 띠게 하거나 기계의 힘을 내게 하는 장치의 출력 에너지를 소비하는 일 3.일이나 책임을 맡김 - 출처: Daum 어학사전  Load에 대해서 거의 직역에 가깝게 표현했기 때문에 조금은 추상적이면서 모호한 표현이라고 볼 수 있다.</description>
            <content type="html"><![CDATA[

<h1 id="load-average">Load Average</h1>

<p>본 문서는 Linux Load Average 값에 대해 소개하고 있습니다.</p>

<h2 id="load">Load</h2>

<p>Load Average는 말 그대로 Load 값의 평균을 나타낸 수치를 의미한다. 이를 이해하기 위해서는 먼저 Load가 무엇인지에 대해서 알아보는 것이 필요하다. 흔히, Load는 우리말로 &lsquo;부하&rsquo;로 표현된다. 부하라는 것의 사전적 정의를 보면 아래와 같다.</p>

<pre><code>부하(負荷)
1.짐을 짐
2.[물리] 전기를 띠게 하거나 기계의 힘을 내게 하는 장치의 출력 에너지를 소비하는 일
3.일이나 책임을 맡김

- 출처: Daum 어학사전
</code></pre>

<p>Load에 대해서 거의 직역에 가깝게 표현했기 때문에 조금은 추상적이면서 모호한 표현이라고 볼 수 있다. 따라서, 사전적 의미로 접근하면 쉽게 이해되지 않을 수 있다. 앞으로 자세히 살펴보겠지만 먼저 개략적으로 정의를 하자면 Load는 Linux에서 특정 작업(Task)가 처리되는 과정에서 처리되기를 기다리는 정도를 표현 한 값으로 표현 할 수 있다.</p>

<p>Load에 대해서 좀 더 개념적인 이해를 위해서 이를 Linux에서 계산하는 과정에 대해서 자세히 살펴보도록 하자.</p>

<h2 id="process">Process</h2>

<p>프로세스는 흔히 작업(Task)으로도 불리운다. 어떠한 명령을 수행하기 위한 코드와 거기에 필요한 데이터 값의 덩어리를 객체로 표현한 것이 프로세스이다. 프로세스가 시스템에서 처리되기 위해서는 CPU(Processor) 연산을 필요로 한다. 하지만, CPU는 물리적으로 한정적인 자원이기 때문에 이를 특정 프로세스가 독점하지 못하도록 OS(Linux)는 프로세스 스케줄러(Scheduler)를 통해서 조절하게 된다. 멀티(Multi)프로세서는 CPU 자원이 1개 이상일 뿐이지 근본적으로 한정적 자원이라는 점에서는 변함이 없다.</p>

<p><img src="/images/2016/02/process-scheduling.png" alt="프로세스 스케줄러" />
- 출처: <a href="https://www.cs.rutgers.edu/~pxk/416/notes/07-scheduling.html" target="_blank">cs.rutgers.edu</a></p>

<p>프로세스 스케줄러는 프로세스에 대한 디스크립터(기술자 - Descriptor)를 두고 관리를 하는데 프로세스 스케줄러에 의해서 관리되는 프로세스는 디스크립터에 아래와 같은 상태 값을 갖게 된다.</p>

<p><img src="/images/2016/02/process-status-table.png" alt="" /></p>

<p>보다 더 많은 상태 값이 존재하지만 여기서는 생략하도록 한다. 커널 소스의 <code>include/linux/sched.h</code> 파일을 참조하자. 프로세스의 상태 값은 보통 아래와 같은 과정에 의해서 상태가 변경된다.</p>

<p><img src="/images/2016/02/process-status.png" alt="프로세스 상태변화" /></p>

<h2 id="process-scheduler">Process Scheduler</h2>

<p>좀 더 구체적인 프로세스의 상태 변화를 살펴보도록 하자. 먼저 4개의 프로세스 A,B,C가 있고 각각의 프로세스는 아래와 같다.</p>

<ul>
<li>A: 복잡한 수식을 계산하는 프로세스 (우선순위가 보통)</li>
<li>B: 사용자가 SSH를 통해서 접속한 Bash 프로세스 (우선순위 보통)</li>
<li>C: 네트워크로부터 받은 데이터를 로그파일에 기록하는 프로세스 (우선순위가 가장 높음)</li>
</ul>

<p>이제 각 프로세스가 순서대로 실행 되었을 때의 상태를 살펴보자.</p>

<p><img src="/images/2016/02/process-schedule-1.png" alt="" /></p>

<p>스케줄러에 의해서 A,B,C 프로세스는 순서대로 큐(queue)에 준비 된다. 각 프로세스는 막 실행되었기 때문에 TASK_RUNNING 상태를 갖고 있다. (1)</p>

<p>먼저, 스케줄러가 A 프로세스를 CPU에 할당하여 처리하도록 한다. 이 상황에서 여전히 프로세스 A는 B,C와 똑같이 RUNNING 상태이다. 차이점은 스케줄러에게 선택받아서 CPU 자원을 사용 중에 있다는 점이다. (2)</p>

<p>일정시간이 지나면 스케줄러는 타이머(timer)를 통해 A 프로세스를 중단시키고 다시 큐에 넣게 된다. 이제 B가 CPU를 사용할 차례가 온 것이다. (3)</p>

<p>하지만, B는 사용자가 터미널에 아무런 입력을 하고 있지 않기에 이내 곧 TASK_INTERRUPTIBLE 상태로 바뀌고 대기하게 되며 바로 C에게 CPU 사용 차례를 넘기게 된다. (3)</p>

<p><img src="/images/2016/02/process-schedule-2.png" alt="" /></p>

<p>프로세스 C는 받은 데이터를 로그파일에 기록하기 위해서 파일 디스크립터에 데이터를 쓰게 된다. 이 과정에서 디스크로 부터 응답을 기다리게 되며 (실제로는 캐시 등에 의해서 바로 Write 작업에 대해 응답을 받기 때문에 대기하지는 않지만 본 예시는 이해를 돕기 위함이므로) TASK_UNINTERRUPTIBLE 상태로 바뀌게 된다. (4)</p>

<p>프로세스 스케줄러는 디스크 응답을 기다리고 있는 프로세스 C에서 프로세스 A에게 다시금 CPU 사용 권한을 주게 된다. (5)</p>

<p>프로세스 A가 CPU를 통해서 연산작업을 수행하고 있는 중에 프로세스 C가 디스크로부터 응답을 받고 TASK_RUNNING 상태로 바뀌며 스케줄러에 의해서 프로세스 C가 CPU 사용 권한을 획득하게 된다. (6)</p>

<p>실제 프로세스 스케줄러는 이보다 훨씬 더 복잡한 방식을 통해서 수행되지만 이해를 위한 간단한 예시라고 보면 된다. 이제 위의 과정에서 CPU 자원을 사용하기 위해서 각 프로세스가 기다렸던 시점을 생각해보자. 프로세스A가 CPU를 처음 사용 중일 때 B,C는 TASK_RUNNING 상태에서 자기 차례를 기다리고 있었으며 프로세스 B는 스케줄러에 의해 자기 차례가 왔지만 이내 사용자 입력을 기다리는 상태로 바로 차례를 넘기게 되었다. 이 때에도 프로세스 A,C는 차례를 기다리고 있었다. 프로세스 C 차례에서는 디스크에 기록한 데이터처리가 끝나기를 기다리게 되었고 이내 A에게 차례를 양보하게 되었다.</p>

<p>위의 예시를 보면 TASK_RUNNING 상태(CPU 사용중인 프로세스 제외)와 TASK_UNINTERRUPTIBLE 상태가 실질적으로 자기 차례가 되기를 기다리는 상태라고 볼 수 있는데 Load 값은 이 TASK_RUNNING과 TASK_UNINTERRUPTIBLE 상태에 있는 프로세스를 수치로 표현 한 값이다.</p>

<p>정리하면 <strong>처리를 위해서 기다리는 상태의 프로세스</strong>를 Load로 표현 한 것이다.</p>

<h2 id="load-average-in-kernel">Load Average in Kernel</h2>

<p>이제 커널에서 실제로 Load 값을 계산하는 부분을 찾아보도록 하자. 커널에서 Load Average를 계산하는 부분은 <code>calc_load()</code>라 불리우는 부분인데 이 함수 또한 사연(?)이 많다. 2.6 커널 이전에는 <code>kernel/sched.c</code> 파일에 있다가 2.6.18 커널에서는 <code>kernel/timer.c</code> 파일에 그리고 2.6.32 커널에서는 다시 <code>kernel/sched.c</code>로 돌아왔으며 <code>calc_global_load()</code> 함수가 호출하는 형태로 바뀌었다. 기존에 <code>CALC_LOAD</code>라는 매크로의 역할을 <code>calc_load()</code> 함수로 대체하고 <code>calc_load()</code>는 <code>calc_global_load()</code>로 바뀌었다고 보면 된다.</p>

<p>3.10 커널에서는 <code>kernel/sched/core.c</code>에 그리고 비교적 최근인 4.4 커널에서는 <code>kernel/sched/loadavg.c</code> 파일에 존재한다. 각 버전별로 해당 부분의 소스는 아래와 같다.</p>

<ul>
<li><p>kernel 2.0.40 (kernel/sched.c)</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#66d9ef">static</span> <span style="color:#66d9ef">inline</span> <span style="color:#66d9ef">void</span> <span style="color:#a6e22e">calc_load</span>(<span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> ticks)
{
    <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> active_tasks; <span style="color:#75715e">/* fixed-point */</span>
    <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">int</span> count <span style="color:#f92672">=</span> LOAD_FREQ;

    count <span style="color:#f92672">-=</span> ticks;
    <span style="color:#66d9ef">if</span> (count <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">0</span>) {
            count <span style="color:#f92672">+=</span> LOAD_FREQ;
            active_tasks <span style="color:#f92672">=</span> count_active_tasks();
            CALC_LOAD(avenrun[<span style="color:#ae81ff">0</span>], EXP_1, active_tasks);
            CALC_LOAD(avenrun[<span style="color:#ae81ff">1</span>], EXP_5, active_tasks);
            CALC_LOAD(avenrun[<span style="color:#ae81ff">2</span>], EXP_15, active_tasks);
    }
}</code></pre></div></li>

<li><p>kernel 2.6.18 (kernel/timer.c - RHEL5)</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#66d9ef">static</span> <span style="color:#66d9ef">inline</span> <span style="color:#66d9ef">void</span> <span style="color:#a6e22e">calc_load</span>(<span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> ticks)
{
    <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> active_tasks; <span style="color:#75715e">/* fixed-point */</span>
    <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">int</span> count <span style="color:#f92672">=</span> LOAD_FREQ;

    count <span style="color:#f92672">-=</span> ticks;
    <span style="color:#66d9ef">if</span> (count <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">0</span>) {
            count <span style="color:#f92672">+=</span> LOAD_FREQ;
            active_tasks <span style="color:#f92672">=</span> count_active_tasks();
            CALC_LOAD(avenrun[<span style="color:#ae81ff">0</span>], EXP_1, active_tasks);
            CALC_LOAD(avenrun[<span style="color:#ae81ff">1</span>], EXP_5, active_tasks);
            CALC_LOAD(avenrun[<span style="color:#ae81ff">2</span>], EXP_15, active_tasks);
    }
}</code></pre></div></li>

<li><p>kernel 2.6.32 (kernel/sched.c - RHEL6)</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">calc_global_load</span>(<span style="color:#66d9ef">void</span>)
{
    <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> upd <span style="color:#f92672">=</span> calc_load_update <span style="color:#f92672">+</span> <span style="color:#ae81ff">10</span>;
    <span style="color:#66d9ef">long</span> active;

    <span style="color:#66d9ef">if</span> (time_before(jiffies, upd))
            <span style="color:#66d9ef">return</span>;

    active <span style="color:#f92672">=</span> atomic_long_read(<span style="color:#f92672">&amp;</span>calc_load_tasks);
    active <span style="color:#f92672">=</span> active <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">?</span> active <span style="color:#f92672">*</span> FIXED_1 : <span style="color:#ae81ff">0</span>;

    avenrun[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">0</span>], EXP_1, active);
    avenrun[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">1</span>], EXP_5, active);
    avenrun[<span style="color:#ae81ff">2</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">2</span>], EXP_15, active);

    calc_load_update <span style="color:#f92672">+=</span> LOAD_FREQ;
}</code></pre></div></li>

<li><p>kernel 3.10 (kernel/sched/core.c - RHEL7)</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">calc_global_load</span>(<span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> ticks)
{
    <span style="color:#66d9ef">long</span> active, delta;

    <span style="color:#66d9ef">if</span> (time_before(jiffies, calc_load_update <span style="color:#f92672">+</span> <span style="color:#ae81ff">10</span>))
            <span style="color:#66d9ef">return</span>;

    <span style="color:#75715e">/*
</span><span style="color:#75715e">     * Fold the &#39;old&#39; idle-delta to include all NO_HZ cpus.
</span><span style="color:#75715e">     */</span>
    delta <span style="color:#f92672">=</span> calc_load_fold_idle();
    <span style="color:#66d9ef">if</span> (delta)
            atomic_long_add(delta, <span style="color:#f92672">&amp;</span>calc_load_tasks);

    active <span style="color:#f92672">=</span> atomic_long_read(<span style="color:#f92672">&amp;</span>calc_load_tasks);
    active <span style="color:#f92672">=</span> active <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">?</span> active <span style="color:#f92672">*</span> FIXED_1 : <span style="color:#ae81ff">0</span>;

    avenrun[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">0</span>], EXP_1, active);
    avenrun[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">1</span>], EXP_5, active);
    avenrun[<span style="color:#ae81ff">2</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">2</span>], EXP_15, active);

    calc_load_update <span style="color:#f92672">+=</span> LOAD_FREQ;

    <span style="color:#75715e">/*
</span><span style="color:#75715e">     * In case we idled for multiple LOAD_FREQ intervals, catch up in bulk.
</span><span style="color:#75715e">     */</span>
    calc_global_nohz();
}</code></pre></div></li>

<li><p>kernel 4.4 (kernel/sched/loadavg.c)</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">calc_global_load</span>(<span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> ticks)
{
    <span style="color:#66d9ef">long</span> active, delta;

    <span style="color:#66d9ef">if</span> (time_before(jiffies, calc_load_update <span style="color:#f92672">+</span> <span style="color:#ae81ff">10</span>))
            <span style="color:#66d9ef">return</span>;

    <span style="color:#75715e">/*
</span><span style="color:#75715e">     * Fold the &#39;old&#39; idle-delta to include all NO_HZ cpus.
</span><span style="color:#75715e">     */</span>
    delta <span style="color:#f92672">=</span> calc_load_fold_idle();
    <span style="color:#66d9ef">if</span> (delta)
            atomic_long_add(delta, <span style="color:#f92672">&amp;</span>calc_load_tasks);

    active <span style="color:#f92672">=</span> atomic_long_read(<span style="color:#f92672">&amp;</span>calc_load_tasks);
    active <span style="color:#f92672">=</span> active <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">?</span> active <span style="color:#f92672">*</span> FIXED_1 : <span style="color:#ae81ff">0</span>;

    avenrun[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">0</span>], EXP_1, active);
    avenrun[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">1</span>], EXP_5, active);
    avenrun[<span style="color:#ae81ff">2</span>] <span style="color:#f92672">=</span> calc_load(avenrun[<span style="color:#ae81ff">2</span>], EXP_15, active);

    calc_load_update <span style="color:#f92672">+=</span> LOAD_FREQ;

    <span style="color:#75715e">/*
</span><span style="color:#75715e">     * In case we idled for multiple LOAD_FREQ intervals, catch up in bulk.
</span><span style="color:#75715e">     */</span>
    calc_global_nohz();
}</code></pre></div></li>
</ul>

<p>커널이 업그레이드 되면서 구현 방법이 조금씩은 바뀌었지만 근본적인 형태는 변하지 않았기 때문에 <code>calc_global_load()</code>가 있기 전 후에 해당하는 2.6.18 커널과 2.6.32 커널을 살펴보도록 하겠다. 이 두 커널은 위에도 표기한 것 처럼 RHEL5(CentOS5)과 RHEL6(CentOS6)의 근간이 되는 커널 버전이다.</p>

<p>먼저 2.6.18 커널의 <code>count_active_tasks()</code> 함수와 2.6.32 커널의 <code>atomic_long_read</code> 매크로가 불러들이는 <code>calc_load_tasks</code>값을 주목하자. 구현 방식은 다르지만 궁극적으로 해당 부분이 하는 역할은 현재의 Activ Task 개수를 계산하는데는 변함이 없다.</p>

<p>먼저 <code>count_active_tasks()</code>는 아래와 같다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#66d9ef">static</span> <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> <span style="color:#a6e22e">count_active_tasks</span>(<span style="color:#66d9ef">void</span>)
{
        <span style="color:#66d9ef">return</span> nr_active() <span style="color:#f92672">*</span> FIXED_1;
}

<span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> <span style="color:#a6e22e">nr_active</span>(<span style="color:#66d9ef">void</span>)
{
        <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> i, running <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>, uninterruptible <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;

        for_each_online_cpu(i) {
                running <span style="color:#f92672">+=</span> cpu_rq(i)<span style="color:#f92672">-&gt;</span>nr_running;
                uninterruptible <span style="color:#f92672">+=</span> cpu_rq(i)<span style="color:#f92672">-&gt;</span>nr_uninterruptible;
        }

        <span style="color:#66d9ef">if</span> (unlikely((<span style="color:#66d9ef">long</span>)uninterruptible <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">0</span>))
                uninterruptible <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;

        <span style="color:#66d9ef">return</span> running <span style="color:#f92672">+</span> uninterruptible;
}</code></pre></div>
<p><code>nr_active()</code> 함수의 결과를 가져와서 계산하는데 <code>nr_active()</code> 함수는 각각 nr_running(TASK_RUNNING)과  nr_uninterruptible(TASK_UNINTERRUPTIBLE) 상태의 프로세스 개수를 더해서 리턴하게 된다.</p>

<p>이제 <code>calc_load_tasks</code> 값을 저장하는 함수인 <code>alc_load_account_active()</code> 함수를 살펴보자.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#66d9ef">static</span> <span style="color:#66d9ef">void</span> <span style="color:#a6e22e">calc_load_account_active</span>(<span style="color:#66d9ef">struct</span> rq <span style="color:#f92672">*</span>this_rq)
{
        <span style="color:#66d9ef">long</span> nr_active, delta;

        nr_active <span style="color:#f92672">=</span> this_rq<span style="color:#f92672">-&gt;</span>nr_running;
        nr_active <span style="color:#f92672">+=</span> (<span style="color:#66d9ef">long</span>) this_rq<span style="color:#f92672">-&gt;</span>nr_uninterruptible;

        <span style="color:#66d9ef">if</span> (nr_active <span style="color:#f92672">!=</span> this_rq<span style="color:#f92672">-&gt;</span>calc_load_active) {
                delta <span style="color:#f92672">=</span> nr_active <span style="color:#f92672">-</span> this_rq<span style="color:#f92672">-&gt;</span>calc_load_active;
                this_rq<span style="color:#f92672">-&gt;</span>calc_load_active <span style="color:#f92672">=</span> nr_active;
                atomic_long_add(delta, <span style="color:#f92672">&amp;</span>calc_load_tasks);
        }
}</code></pre></div>
<p>현재 프로세스 Run Queue(this_rq)에서 nr_running과 nr_uninterruptible의 값을 가져와서 더한다. 마찬가지로 TASK_RUNNING과 TASK_UNINTERRUPTIBLE 상태의 프로세스 개수를 구해오는 것이다. 즉, Active Task는 이 둘의 합을 의미하며 Linux 커널은 이 값을 주기적으로 계산해서 평균을 내고 그것을 1분,5분,15분 기준으로 표현한 값을 <strong>Load Average</strong>로 나타내는 것이다.</p>

<h2 id="proc-loadavg">/proc/loadavg</h2>

<p>앞서 살펴 본 계산을 통해서 구해진 Load Average 값은 <code>/proc/loadavg</code> 파일로 보여지게 된다. 이 파일의 내용을 보면 보통 아래와 같다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ cat /proc/loadavg
<span style="color:#ae81ff">1</span>.81 <span style="color:#ae81ff">1</span>.70 <span style="color:#ae81ff">1</span>.65 <span style="color:#ae81ff">3</span>/1179 <span style="color:#ae81ff">39063</span></code></pre></div>
<p>총 5개의 데이터가 있으며 앞에서 부터 1분 평균, 5분 평균, 15분 평균, 큐 상태, PID를 나타낸다. 각각의 평균 값 외에 큐 상태와 PID 값이 있는데 이 값은 어떤 의미를 갖는지 알아보자. <code>/proc/loadavg</code> 값을 보여주는 부분은 <code>fs/proc/loadavg.c</code>에 있으며 아래와 같다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#66d9ef">static</span> <span style="color:#66d9ef">int</span> <span style="color:#a6e22e">loadavg_proc_show</span>(<span style="color:#66d9ef">struct</span> seq_file <span style="color:#f92672">*</span>m, <span style="color:#66d9ef">void</span> <span style="color:#f92672">*</span>v)
{
        <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> avnrun[<span style="color:#ae81ff">3</span>];

        get_avenrun(avnrun, FIXED_1<span style="color:#f92672">/</span><span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">0</span>);

        seq_printf(m, <span style="color:#e6db74">&#34;%lu.%02lu %lu.%02lu %lu.%02lu %ld/%d %d</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>,
                LOAD_INT(avnrun[<span style="color:#ae81ff">0</span>]), LOAD_FRAC(avnrun[<span style="color:#ae81ff">0</span>]),
                LOAD_INT(avnrun[<span style="color:#ae81ff">1</span>]), LOAD_FRAC(avnrun[<span style="color:#ae81ff">1</span>]),
                LOAD_INT(avnrun[<span style="color:#ae81ff">2</span>]), LOAD_FRAC(avnrun[<span style="color:#ae81ff">2</span>]),
                nr_running(), nr_threads,
                task_active_pid_ns(current)<span style="color:#f92672">-&gt;</span>last_pid);
        <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>;
}</code></pre></div>
<p>여기에서 4번째 큐 상태의 의미를 확인 할 수 있다. 프로세스 스케줄러가 스케줄링한 <strong>전체 Task의 개수(nr_threads) 중</strong>에서  현재 <strong>실행 중인 Task의 개수</strong>(nr_running())를 보여주는 것이다. 그리고 마지막 PID 항목은 최근에 실행된 Task의 PID 값을 의미한다.</p>

<h2 id="평균-계산-방법">평균 계산 방법</h2>

<h4 id="경고">경고</h4>

<p>이 부분은 수학적인 계산식이 많이 나온다. 평균 값을 구하는 방법으로 통계학에서 자주 사용되는 (실제로는 주식시장 얘기에 더 많이 등장한다) <a href="https://en.wikipedia.org/wiki/Moving_average" target="_blank">이동평균(Moving Average)</a>과 <a href="https://en.wikipedia.org/wiki/Exponential_smoothing" target="_blank">지수평활법(Exponetial Smoothing)</a>이 사용되는데 - 정확히는 지수감소이동평균(exponential-damped moving average) - 나처럼 수학적으로 이해하는데 어려움이 있다면 다음 문장만 생각하고 이 단락을 건너 뛰어도 좋다.</p>

<ul>
<li>모든 Load 값을 저장하고 평균을 낼 수 없기 때문에 과거 시점의 평균 값을 토대로 현재의 평균 값을 계산하는 방법을 사용한다. 이러한 방법은 평균 그래프 등을 그릴 때도 자주 사용된다.</li>
</ul>

<h4 id="계산-방법">계산 방법</h4>

<p>앞서 살펴 본 대로 Load는 처리를 대기하는 Task(프로세스)의 개수를 의미하는데 이 값을 평균내는 방법에 대해서 간단히 살펴보도록 하자.</p>

<p>특정한 값에 대해서 평균을 내기 위해서는 일반적으로 전체를 더한 뒤에 개수로 나눈 값으로 표현한다. 하지만, 이러한 방법은 모든 값을 전부 저장하고 있어야 하기 때문에 한정적인 자원에서는 사용하기가 어렵다. 그렇기 때문에 특정 시점의 평균 값을 이전 시점의 평균 값을 참고하여 계산하는 방법을 사용하게 되는데 Linux에서는 지수이동평균(EMA: Exponetial Moving Average) 방식을 바탕으로 Load Average를 구하고 있다. 지수감소이동평균을 통해서 계산하는 수식은 아래와 같다.</p>

<p><img src="/images/2016/02/calc-load-macro.png" alt="calc-load-macro.png" /></p>

<ul>
<li>m: 리포팅을 위한 시간 (1분, 5분, 15분 등)</li>
<li>load(t): 현재의 Load 값</li>
<li>load(t-1): 지난 Load 값</li>
<li>n(t): 현재의 Active Task 개수</li>
</ul>

<p>이 수식이 의미하는 바를 이해하기 위해 커널 코드를 참조해 보자. 먼저 커널 코드에서 <code>sched.h</code> 파일의 <code>#define</code> 부분을 살펴보면 아래와 같다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C" data-lang="C"><span style="color:#75715e">/*
</span><span style="color:#75715e"> * These are the constant used to fake the fixed-point load-average
</span><span style="color:#75715e"> * counting. Some notes:
</span><span style="color:#75715e"> *  - 11 bit fractions expand to 22 bits by the multiplies: this gives
</span><span style="color:#75715e"> *    a load-average precision of 10 bits integer + 11 bits fractional
</span><span style="color:#75715e"> *  - if you want to count load-averages more often, you need more
</span><span style="color:#75715e"> *    precision, or rounding will get you. With 2-second counting freq,
</span><span style="color:#75715e"> *    the EXP_n values would be 1981, 2034 and 2043 if still using only
</span><span style="color:#75715e"> *    11 bit fractions.
</span><span style="color:#75715e"> */</span>
<span style="color:#66d9ef">extern</span> <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span> avenrun[];         <span style="color:#75715e">/* Load averages */</span>

<span style="color:#75715e">#define FSHIFT          11              </span><span style="color:#75715e">/* nr of bits of precision */</span><span style="color:#75715e">
</span><span style="color:#75715e">#define FIXED_1         (1&lt;&lt;FSHIFT)     </span><span style="color:#75715e">/* 1.0 as fixed-point */</span><span style="color:#75715e">
</span><span style="color:#75715e">#define LOAD_FREQ       (5*HZ+1)        </span><span style="color:#75715e">/* 5 sec intervals */</span><span style="color:#75715e">
</span><span style="color:#75715e">#define EXP_1           1884            </span><span style="color:#75715e">/* 1/exp(5sec/1min) as fixed-point */</span><span style="color:#75715e">
</span><span style="color:#75715e">#define EXP_5           2014            </span><span style="color:#75715e">/* 1/exp(5sec/5min) */</span><span style="color:#75715e">
</span><span style="color:#75715e">#define EXP_15          2037            </span><span style="color:#75715e">/* 1/exp(5sec/15min) */</span><span style="color:#75715e">
</span><span style="color:#75715e"></span>
<span style="color:#75715e">#define CALC_LOAD(load,exp,n) \
</span><span style="color:#75715e">        load *= exp; \
</span><span style="color:#75715e">        load += n*(FIXED_1-exp); \
</span><span style="color:#75715e">        load &gt;&gt;= FSHIFT;</span></code></pre></div>
<p>주석에서는  Load Average를 구하기 위해서 고정소수점 방식을 사용하고 11비트의 소수자리를 사용하는 것을 알 수 있다. <code>FSHIFT</code>는 쉬프트 연산을 위해 11의 값을 갖게 되고 1.0을 의미하는 값 <code>FIXED_1</code>은 1을 11비트 쉬프트(shift) 하여 표현하게 된다. (즉, 일반정수라면 2048 = 2^11을 의미하겠지만 고정소수점이기에 1.0이 된다)</p>

<p><code>EXP_1</code>의 값이 나오게 된 원인을 찾아보자. 먼저 Load Average를 계산하는데 있어서 지수이동평균의 감소인수(damping factor) 값은 <code>e^-(5/60m)</code>이다. 이를 다시 표현하면 주석에 나와 있는대로 <code>1/e^(5/60m)</code>이다. 여기에서 <code>EXP_1</code>은 1분에 대한 값이기 때문에 m은 1이 되고 아래와 같이 표현할 수 있다.</p>

<pre><code>1) 1 / e^(5/60m)
2) m = 0
3) 1 / e^(5/60)
4) e^(-(5/60))
</code></pre>

<p>이제 e^(-(<sup>5</sup>&frasl;<sub>60</sub>)) 값을 구해보도록하자. 간편하게 python을 실행해서 math 모듈을 이용해서 계산해 보았다.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#f92672">import</span> math
<span style="color:#f92672">&gt;&gt;&gt;</span> e <span style="color:#f92672">=</span> math<span style="color:#f92672">.</span>exp(<span style="color:#f92672">-</span>(<span style="color:#ae81ff">5.0</span><span style="color:#f92672">/</span><span style="color:#ae81ff">60.0</span>))
<span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#66d9ef">print</span> e
<span style="color:#ae81ff">0.920044414629</span></code></pre></div>
<p>이 값을 고정소수점으로 표현하기 위해서 앞서 계산한 변수 e에 다가 2^11(2048)을 곱해보자. (고정소수점 표시를 위해 11자리를 SHIFT 했던 것을 기억하자)</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> e <span style="color:#f92672">*</span> <span style="color:#ae81ff">2048</span>
<span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#ae81ff">1884.250961160854</span></code></pre></div>
<p><code>1884.250961160854</code>가 나왔는데 소수점을 버리면 <code>EXP_1</code>인 <strong>1884</strong>가 된다. 즉, 고정소수점 연산을 위해서 미리 계산된 감소인수 값이다. 마찬가지로 1분 대신 5분, 15분에 대해서 값을 대입해보면 2014, 2037의 값을 얻을 수 있다.</p>

<p>이제, <code>CALC_LOAD</code> 매크로를 다시 풀어서 살펴보면 맨 처음에 봤던 수식과 동일 한 것을 알 수 있다.</p>

<pre><code>load*exp + n*(FIXED_1 - exp)
= load(t-1)*e + n(t)*(1 - e)

e는 감소인수(= e^(-5/60))를 간략히 쓴 것이다
</code></pre>

<h3 id="기타">기타</h3>

<p>앞에서 살펴 본 주석에서도 설명하고 있듯이 5초(LOAD_FREQ)를 기준으로하여 EXP_1, EXP_5, EXP_15 값이 고정적이므로 주기를 더 짧게 하기 위해서는 EXP_n 값도 같이 맞춰서 변경 해 주어야 한다. 또한, 최근 커널의 경우 앞에서 살펴 본 계산식에 대한 구현을 조금 달리 하고 있으며 이는 보다 정확한 값을 얻기 위함이라고 생각하면 된다.</p>

<h2 id="load-average-판단">Load Average 판단</h2>

<p>Load Average는 Active Task의 평균 값을 의미한다고 확인했다. 그렇다면 이 Load Average는 어떤 값일 때 정상이라고 봐야 할까? <a href="http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages" target="_blank">이 문서</a>에서는 Load Average를 교통량에 비유하여 쉽게 설명하고 있다.</p>

<p><img src="/images/2016/02/load-average-lane.png" alt="" />
- 출처: <a href="http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages" target="_blank">scoutapp</a></p>

<p>1개의 차선(1 CPU 또는 Core)에 차가 문제 없이 지속적으로 지나갈 때를 1로 보고 있기 때문에 전체 시스템의 CPU(또는 Core) 개수 만큼의 Load 값에 대해서는 문제가 없다고 판단한다. 다시 말해 CPU 마다 처리를 기다리는 프로세스가 1개씩 있을 경우에는 큰 문제가 없다고 판단하는 것이다.</p>

<p>하지만, Active Task에는 TASK_UNINTERRUPTIBLE이 있기 때문에 주의 할 필요가 있다. 특정 프로세스가 장시간 I/O를 점유하고 있으면 같은 자원을 사용하려는 다른 프로세스의 I/O 처리를 위한 대기시간에 크게 영향을 미치기 때문에 겉으로 보기에는 Load Average가 높지 않지만 실제 시스템에서 I/O 및 Buffer를 거치는 작업 등에서 지연을 경험 할 수도 있다. 반대로 Load Average가 높지만 다른 프로세스와 동일한 자원을 사용하지 않는 I/O 처리 대기나 프로세스 간에 Context switching이 원활 할 경우에는 성능저하가 별로 느껴지지 않을 수도 있다.</p>

<p>게다가 Load Average는 단순히 Active Task의 평균 값이기 때문에 시스템이 갖고 있는 CPU 개수에 따라서 그 의미가 달라지게 된다. 즉, 절대적인 기준 값을 잡기에는 어려움이 존재한다.</p>

<p>과거 Unix의 경우에는 TASK_RUNNING의 개수만을 Load Average 계산 대상으로 보았고 Linux에서는 좀 더 현실성있는 수치로 만들기 위해 TASK_UNINTERRUPTIBLE을 추가하였지만 여전히 Load Average는 절대적 기준을 두기에는 무리가 있는 수치라고 보여진다.</p>

<p>정답은 없겠지만 개인적인 판단으로는 CPU(Core)의 개수를 기준으로 두고 비율을 계산했을 때 100%가 되지 않도록 유지하는 것이 좋다고 본다. 예를 들어 Quad Core(4 Core) CPU 1개가 있는 시스템에서 Load Average가 3이라면 75%의 사용률이라고 볼 수 있기 때문에 3이하로 유지할 수 있도록 하는 것이 좋다고 생각한다. 앞서 이야기 한 것처럼 TASK_UNINTERRUPTIBLE 상태의 프로세스도 있을 수 있으므로 단순히 Load Average로만 판단하기 보다는 procps의 다양한 툴들(top, vmstat, slabtop 등)과 다른 모니터링 툴의 수치도 참고하여 판단하는 것이 좋다.</p>

<h2 id="요약">요약</h2>

<ul>
<li>Load Average는 Active Task(TASK_RUNNING, TASK_UNINTERRUPTIBLE) 개수의 평균 값이다</li>
<li>일반적으로 CPU(Core)개수보다 Load Avearge 값이 적으면 문제가 없지만 상황에 따라 다를 수도 있다</li>
<li>여타 모니터링 데이터가 그러하듯이 Load Average 만으로 시스템 부하를 판단하지 않는 것이 좋다</li>
</ul>

<h4 id="참고문헌">참고문헌</h4>

<ul>
<li>본문의 링크</li>
<li>Linux Load Reveald, CMG Conference 2004</li>
<li>Understanding load averages and stretch factors STRETCH (<a href="http://linux-magazine.com" target="_blank">http://linux-magazine.com</a>)</li>
<li><a href="https://en.wikipedia.org/wiki/Load_(computing)" target="_blank">Load - Wikipedia</a></li>
<li><a href="http://jvns.ca/blog/2016/02/07/cpu-load-averages/" target="_blank">CPU Load Averages - Julia Evans</a></li>
<li>Analyzing Computer System Performance with Perl - Neil J. Gunther</li>
</ul>

<p>p.s: Load Average에 대해서 쉽게 써보려고 했으나 결과적으로 망했다고 생각한다.</p>
]]></content>
        </item>
        
        <item>
            <title>modalias와 장치 드라이버</title>
            <link>/2015/12/23/modalias-device-driver/</link>
            <pubDate>Wed, 23 Dec 2015 06:13:19 +0000</pubDate>
            
            <guid>/2015/12/23/modalias-device-driver/</guid>
            <description>본 문서는 devtmpfs와 udev에 등장했던 sysfs의 정보를 바탕으로 실제 장치 드라이버가(커널 모듈) 인식되고 로딩 되는 방법에 대해서 간단히 설명하고 있습니다.
Device Driver 일반 적으로 장치 드라이버(Device driver)는 해당 모듈이 빌드(build/compile)될 때 modalias 정보를 담고 생성 됩니다. 이 modalias 정보는 벤더와 장치에 대한 ID와 벤더와 장치의 서브시스템 버전 그리고 클래스(base/sub class) 정보를 담고 있습니다. 장치 드라이버에 들어있는 modalias 정보는 PCI ids에서 자세히 확인 할 수 있습니다.
modalias 해석 예시를 통해서 살펴보는게 이해하기 좋기 때문에 실제 서버에 설치 된 RAID 카드의 모듈을 어떻게 인식 되었는지에 대해서 살펴보겠습니다.</description>
            <content type="html"><![CDATA[

<p>본 문서는 <a href="http://lunatine.net/devtmpfs-udev" target="_blank">devtmpfs와 udev</a>에 등장했던 sysfs의 정보를 바탕으로 실제 장치 드라이버가(커널 모듈) 인식되고 로딩 되는 방법에 대해서 간단히 설명하고 있습니다.</p>

<h2 id="device-driver">Device Driver</h2>

<p>일반 적으로 장치 드라이버(Device driver)는 해당 모듈이 빌드(build/compile)될 때 modalias 정보를 담고 생성 됩니다. 이 modalias 정보는 벤더와 장치에 대한 ID와 벤더와 장치의 서브시스템 버전 그리고 클래스(base/sub class) 정보를 담고 있습니다. 장치 드라이버에 들어있는 modalias 정보는 <a href="http://pci-ids.ucw.cz/" target="_blank">PCI ids</a>에서 자세히 확인 할 수 있습니다.</p>

<h2 id="modalias-해석">modalias 해석</h2>

<p>예시를 통해서 살펴보는게 이해하기 좋기 때문에 실제 서버에 설치 된 RAID 카드의 모듈을 어떻게 인식 되었는지에 대해서 살펴보겠습니다.</p>

<p>먼저 lspci에서 보여주는 RAID 카드의 정보는 아래와 같습니다.</p>

<pre><code>$ lspci | grep -i &quot;raid bus&quot;
03:00.0 RAID bus controller: Hewlett-Packard Company Smart Array Gen9 Controllers (rev 01)
</code></pre>

<p>해당 PCI 장치는 03:00.0에 존재하기 때문에 sysfs(/sys)에 해당 위치로 이동해 봅니다.</p>

<pre><code>$ cd /sys/bus/pci/devices/0000\:03\:00.0/
$ cat modalias
pci:v0000103Cd00003239sv0000103Csd000021C0bc01sc04i00
</code></pre>

<p>해당 장치의 modalias정보를 열어보면 <code>pci:v0000103Cd00003239sv0000103Csd000021C0bc01sc04i00</code> 값을 확인 할 수 있는데 이 값을 좀 더 보기 편하게 나눠보면 아래와 같습니다.</p>

<pre><code>pci:v0000103Cd00003239sv0000103Csd000021C0bc01sc04i00

v 0000103C (벤더ID)
d 00003239 (장치ID)
sv 0000103C (서브시스템 벤더ID)
sd 000021C0 (서브시스템 장치ID)
bc 01 (Base Class)
sc 04 (Sub Class)
i 00 (프로그래밍 인터페이스)
</code></pre>

<p><a href="http://pci-ids.ucw.cz/" target="_blank">PCI ids</a>에서 벤더ID 103C를 검색해보면 <a href="https://pci-ids.ucw.cz/read/PC/103C" target="_blank">HP를 확인</a> 할 수 있으며 장치ID를 검색해보면 3239는 <a href="https://pci-ids.ucw.cz/read/PC/103c/3239" target="_blank">Smart Array Gen9 Controllers</a> 임을 알 수 있습니다. 여기에 서브시스템의 벤더와 장치 ID인 103C:21C0을 찾아보면 <a href="https://pci-ids.ucw.cz/read/PC/103c/3239/103c21c0" target="_blank">Smart Array P440ar</a> 까지 확인이 가능합니다.</p>

<p>그리고 Base/Sub Class 값인 01 04에 대해서 확인해 보면 <a href="https://pci-ids.ucw.cz/read/PD/01/04" target="_blank">Mass Storage Controller-RAID bus controller</a>로 확인이 됩니다</p>

<pre><code>103C
↑ Hewlett-Packard
103C:3239
     ↑ Smart Array Gen9 Controllers
103C:3239:103C 21C0
          ↑ Smart Array P440ar
01 Mass storage controller
04 RAID bus controller
</code></pre>

<p>이러한 정보를 가지고 있는 하드웨어 장치에 대해서 커널 모듈을 로딩하기 위해서 udev에는 아래와 같은 룰이 있습니다.</p>

<pre><code>DRIVER!=&quot;?*&quot;, ENV{MODALIAS}==&quot;?*&quot;, RUN{builtin}=&quot;kmod load $env{MODALIAS}&quot;
</code></pre>

<p>modalias에 매칭되는 모듈을 읽어들이도록 하는 매우 심플한 룰셋 입니다. 현재 앞에서 살펴본 RAID 카드를 위한 모듈의 modalias 정보를 보면 아래와 같습니다.</p>

<p><img src="/images/2015/12/modinfo.png" alt="modinfo.png" /></p>

<p>위에 우리가 찾아봤던 modalias값과 유사한 부분이 있는데 여기에서 <code>*</code>은 와일드카드로 모든 값에 매칭 된다는 표시입니다. 따라서, hpsa 모듈이 가진 modalias 값에 RAID 카드의 modalias가 매칭 되기 때문에 hpsa 드라이버가 로딩 되는 것 입니다. 추가로 다른 서브시스템 장치ID가 hpsa에 존재하는 것을 통해서 이 모델 뿐만 아니라 여러 RAID 카드를 지원하는 드라이버라는 것을 확인 할 수 있습니다.</p>
]]></content>
        </item>
        
        <item>
            <title>devtmpfs와 udev</title>
            <link>/2015/12/23/devtmpfs-udev/</link>
            <pubDate>Wed, 23 Dec 2015 06:11:39 +0000</pubDate>
            
            <guid>/2015/12/23/devtmpfs-udev/</guid>
            <description>본 문서는 Linux booting 문서에서 생략 된 부분이면서 현재 Linux에서 장치관리를 수행하는 udev와 devtmpfs의 상관관계에 대해서 설명하고 있습니다. 또한, devtmpfs의 의미와 역할에 대한 질문의 답변을 정리한 문서이기도 합니다.
 마지막수정: 12/22/2015 기존문서와 기술하는 말투를 달리하였으니 양해부탁드립니다.  devfs 먼저 udev가 도입되기 전에 존재 했던 devfs라는 녀석에 대해서 살펴보겠습니다. devfs는 2000년에 도입된 파일시스템으로 장치에 대한 정보를 커널에서 관리 할 수 있도록 만들어진 파일시스템 입니다. 실제 여전히 많은 UNIX 계열 시스템은 devfs를 사용하고 있습니다.</description>
            <content type="html"><![CDATA[

<p>본 문서는 <a href="http://lunatine.net/linux-booting/" target="_blank">Linux booting</a> 문서에서 생략 된 부분이면서 현재 Linux에서 장치관리를 수행하는 udev와 devtmpfs의 상관관계에 대해서 설명하고 있습니다. 또한, devtmpfs의 의미와 역할에 대한 질문의 답변을 정리한 문서이기도 합니다.</p>

<ul>
<li>마지막수정: 12/22/2015</li>
<li>기존문서와 기술하는 말투를 달리하였으니 양해부탁드립니다.</li>
</ul>

<h2 id="devfs">devfs</h2>

<p>먼저 udev가 도입되기 전에 존재 했던 devfs라는 녀석에 대해서 살펴보겠습니다. devfs는 2000년에 도입된 파일시스템으로 장치에 대한 정보를 커널에서 관리 할 수 있도록 만들어진 파일시스템 입니다. 실제 여전히 많은 UNIX 계열 시스템은 devfs를 사용하고 있습니다. 예를 들어 Mac OS X의 경우 BSD Unix 커널 계열이기 때문에 마운트 정보를 보면 devfs를 발견 할 수 있습니다.</p>

<pre><code>$ mount
/dev/disk2 on / (hfs, local, journaled)
devfs on /dev (devfs, local, nobrowse)
map -hosts on /net (autofs, nosuid, automounted, nobrowse)
map auto_home on /home (autofs, automounted, nobrowse)
</code></pre>

<p>Linux의 devfs는 UNIX의 devfs와 완전히 같지는 않지만 사용되는 목적은 동일하다고 보시면 됩니다. 이러한 devfs의 역할은 기존에 major, minor 숫자를 통해서 장치 노드(device node)파일을 수작업으로 생성하는 방식 대신에 커널이 장치 이름을 결정 짓고 이를 자동으로 등록해주는 방식입니다. 기존의 정적인 장치 노드 파일을 대체하고 좀 더 능동적으로 관리 할 수 있다는 장점이 있습니다.</p>

<p>다만, 장점 못지 않게 단점과 한계점이 드러났는데 가장 대표적인 부분이 장치를 인식하고 장치 노드를 생성 할 때 사용하는 이름이 문제가 되었습니다. <a href="http://www.lanana.org/" target="_blank">LANANA</a>에서 정책적으로 정리된 이름과 맞지 않았기 때문에 devfs를 실제 물리장치에 있는 /dev로 스위칭 할 때 이름을 맞추기 위해서 좀 더 많은 부가적인 설정이 필요하게 되었고 관리가 복잡하게 되었습니다.</p>

<p>그리고, 이러한 장치 이름 결정 방식이 별로 환영 받지 못하였는지 꽤 오랜기간 개선되거나 수정되지 못하고 방치되고 있다가 2.4 커널에서 널리 사용되던 devfs는 2.6 커널에서 udev로 대체 되기 시작하였고 2006년 커널 2.6.13 에서 사라집니다.</p>

<h2 id="udev">udev</h2>

<p>Linux 커널 2.6의 개발버전인 2.5에서 등장한 <a href="https://en.wikipedia.org/wiki/Udev" target="_blank">udev</a>는 아래와 같은 목적으로 만들어 졌습니다.</p>

<ul>
<li>사용자 영역에서 실행</li>
<li>동적인 장치 노드(/dev) 파일 생성</li>
<li>필요 할 때 지속적으로 동일한 장치명을 제공 할 수 있어야 함</li>
<li>현재 시스템 장치의 정보를 얻을 수 있는 API의 제공</li>
</ul>

<p>udev는 기본적으로 사용자 모드(user mode/user space)에서 동작하기 때문에 <a href="http://lunatine.net/linux-booting/" target="_blank">Linux 부팅</a> 과정에서  최대한 빨리 실행되어야 장치 노드 정보를 설정하고 이를 통해 서비스가 올바르게 구동 됩니다. 보통 udev 데몬이 실행 되는 건 rootfs 스위칭이 일어나고 init 프로세스가 구동 되면서 시작됩니다. 하지만, 이러한 방식은 장치 노드 설정이 매우 늦어지게 만드는 문제를 일으키기 때문에 일반적으로 initrd/initramfs 이미지(임시 rootfs 이미지)에 udev와 udev의 기본설정을 포함시켜서 부팅합니다.</p>

<p>이렇게 하면 Linux가 부팅 할 때 실제 디스크장치의 rootfs(/)로 스위칭 작업을 수행하기 전에 메모리에 올라가 rootfs로 사용 중인 initrd/initramfs를 통해서 udev가 실행됩니다. 이렇게 하면 init 전에 장치 노드를 설정 할 수가 있습니다. 아래는 CentOS 6 에서 캡처한 udev 실행 전후의 부트 메시지 입니다.</p>

<p><img src="/images/2015/12/udev-dmesg.png" alt="udev-dmesg.png" /></p>

<p>사용자 모드에서 동작하는 udev는 어떻게 하드웨어 정보를 확인하고 장치 노드를 등록 하는지에 대한 의문이 들 수 있는데 이는 sysfs 라는 파일시스템을 통해서 해결 하고 있습니다.</p>

<h2 id="sysfs">sysfs</h2>

<p>Linux 2.5 커널 트리에서 등장해서 2.6 안정버전에 등장한 가상 파일시스템인 sysfs는 사용자 모드에서 현재 커널이 가지고 있는 서브시스템(subsystem)과 하드웨어 정보를 보여주는 파일시스템 입니다. 이 파일시스템은 kobject(kernel object)의 정보를 바탕으로 하여 데이터 구조와 속성 정보를 제공합니다. 주로 커널이 인지한 하드웨어 정보를 구조적으로 투영해주고 있으며 이 정보를 바탕으로 udev는 장치 노드를 설정 할 때 어떻게 설정해야하는지를 알게됩니다.</p>

<p>사실 sysfs가 보여주는 하드웨어 정보는 <a href="/proc">procfs</a>에서도 일부 볼 수 있는데 procfs의 원래 목적이 프로세스 정보를 투영하는 것이었기 때문에 procfs가 보여주는 하드웨어 정보는 procfs의 관리에 혼란을 가중시키게 되었습니다. 따라서, procfs와 별개로 시스템 하드웨어 정보를 체계적으로 보여주는 파일시스템의 필요성이 대두 되었고 그게 sysfs로 나타나게 되었습니다.</p>

<h2 id="devtmpfs">devtmpfs</h2>

<p>devfs 2.0이라고 불리우기도 하는 devtmpfs는 기본적으로 <a href="https://en.wikipedia.org/wiki/Tmpfs" target="_blank">tmpfs</a>의 일종 입니다. tmpfs는 흔히 ramdisk와 비교되기도 하는데 ramdisk와의 큰 차이점은 아래와 같습니다.</p>

<ul>
<li>tmpfs는 모든 내용을 커널 내부 캐시메모리에 저장합니다</li>
<li>page cache에 올라갈 수 있는 구조이기 때문에 swap 장치로 swap out도 가능합니다

<ul>
<li>즉, 메모리에 항상 존재하는게 아니라 상황에 따라 swap 장치(디스크 등)로 저장이 됨</li>
</ul></li>
</ul>

<p>tmpfs의 일종인 devtmpfs가 등장하게 된 이유는 간단합니다. Linux 부팅 시간의 단축을 위해서 입니다. udev가 전체적인 장치 노드를 생성 및 관리를 하더라도 필요 할 때 연결하는 USB 같은 장치가 아닌이상 대부분의 장치는 이미 시스템에 고정적으로 존재하기 때문에 커널이 부팅 할 때 인식하고 필요한 모듈의 로딩이 가능합니다. 그래서 initrd/initramfs에서 udev가 장치 노드를 등록하기도 전에 커널이 시작하는 시점에 장치 노드를 등록 할 수 있는 파일시스템을 생성하고 /dev에 마운트를 하게 되면 실제 rootfs(/) 파티션으로 스위칭하고 init 과정을 마친 후에도 계속해서 유지할 수 있는 파일시스템을 갖게 됩니다.</p>

<p>다시말해, 대부분의 장치에 대해서 장치 노드를 커널 부팅 초반에 생성해서 유지/관리 할 수 있기 때문에 사용자 모드에서의 장치 노드 생성/관리보다 매우 빨리 처리 할 수 있고 tmpfs 기반의 파일시스템이기 때문에 사용자 모드에서도 활용이 가능한 장점을 갖게 됩니다.</p>

<h2 id="요약">요약</h2>

<p>devfs는 지원가능한 장치노드를 major, minor 번호로 관리하며 모두 생성하는 정적인 방식보다는 나았지만 사용자 모드에서의 접근성과 장치 노드 관리가 좋지 않았기 때문에 이를 대체 할 수 있는 udev를 통해서 장치 노드를 관리하는 방안이 도입되었으며 여기에 부팅 시간 개선을 위해 devtmpfs가 도입이 되었습니다.</p>
]]></content>
        </item>
        
        <item>
            <title>Linux 부팅과정</title>
            <link>/2015/12/18/linux-booting/</link>
            <pubDate>Fri, 18 Dec 2015 07:17:02 +0000</pubDate>
            
            <guid>/2015/12/18/linux-booting/</guid>
            <description>Linux Boot 가끔씩 Linux의 부팅과정에 대해서 질문이 들어오면 dmesg를 보여주면서 설명하거나 칠판에 낙서하듯이 설명해 주곤 했는데 이번에 지금까지 받았던 질문과 설명을 토대로해서 개략적으로나마 Linux의 부팅과정을 문서로 정리해 보았다.
 본 문서에서는 OS는 CentOS 6를 기준으로 하며 부트로더는 MBR위의 GRUB v1을 기준으로 설명합니다. 오타는 발견 때 마다 수정 중에 있습니다 마지막수정: 12/18/2015  GRUB Boot Loader GRUB 부트로더는 첫 번째 섹터(Sector 0)인 MBR(Master Boot Record)에 위치하고 있으며 MBR에 대한 문서에서 소개했던 것 처럼 첫 번째의 446바이트 공간안에 존재한다.</description>
            <content type="html"><![CDATA[

<h1 id="linux-boot">Linux Boot</h1>

<p>가끔씩 Linux의 부팅과정에 대해서 질문이 들어오면 dmesg를 보여주면서 설명하거나 칠판에 낙서하듯이 설명해 주곤 했는데 이번에 지금까지 받았던 질문과 설명을 토대로해서 개략적으로나마 Linux의 부팅과정을 문서로 정리해 보았다.</p>

<ul>
<li>본 문서에서는 OS는 CentOS 6를 기준으로 하며 부트로더는 MBR위의 GRUB v1을 기준으로 설명합니다.</li>
<li>오타는 발견 때 마다 수정 중에 있습니다</li>
<li>마지막수정: 12/18/2015</li>
</ul>

<h2 id="grub-boot-loader">GRUB Boot Loader</h2>

<p>GRUB 부트로더는 첫 번째 섹터(Sector 0)인 MBR(Master Boot Record)에 위치하고 있으며 <a href="http://lunatine.net/faq-linuxeseo-mbr-patisyeon-teibeul-salpyeobogi/" target="_blank">MBR</a>에 대한 문서에서 소개했던 것 처럼 첫 번째의 446바이트 공간안에 존재한다. 여기에 존재하는 부트로더 파일은 전체 GRUB이 아니라 부트로더의 모든 기능을 불러 낼 수 있는 최소한의 기능만을 담은 이미지(stage1)를 담고 있다. 부팅 된 리눅스서버에서 보통 아래 경로에서 해당 파일을 확인 할 수 있다.</p>

<pre><code>$ ls -l /boot/grub/stage1
-rw-r--r--. 1 root root 512 May  2  2013 stage1
</code></pre>

<p>512바이트 크기의 이 파일은 정확히 0번 섹터에 들어맞는 크기로 되어있으며 이 파일을 hexdump로 떨구면 아래와 같이 mbr의 시그니처인 <strong>0xaa55</strong>를 확인 할 수 있다. (하지만, 그 앞의 값은 MBR의 파티션 엔트리 값과 동일하지 않다. 부트로더 설치를 위한 파일이지 설치된 부트로더를 의미하는 파일이 아니기 때문이다)</p>

<pre><code>$ hexdump -s 446 stage1
00001be 1224 090f be00 7dbd c031 13cd 8a46 800c
00001ce 00f9 0f75 dabe e87d ffc9 97eb 6c46 706f
00001de 7970 bb00 7000 01b8 b502 b600 cd00 7213
00001ee b6d7 b501 e94f fee0 0000 0000 0000 0000
00001fe aa55
0000200
</code></pre>

<h3 id="stage1">Stage1</h3>

<p>stage1을 조금 더 자세히 살펴보자. stage1이 설치된 MBR을 덤프하고 이를 hexdump로 살펴보도록 하겠다. 이 부분은 실제 저장된 값을 읽어서 분석하기 때문에 어셈블리에 친숙하지 않을 경우 (물론 나도 그렇게 친숙하지는 않다) 어려울 수 있으나 생각보다 어려운 항목은 없기 때문에 천천히 따라해보면 이해하는데 큰 어려움은 없을 것이다.</p>

<pre><code>$ dd if=/dev/sda of=./mbr bs=512 count=1

/dev/sda는 부트 디스크를 의미하며 512크기로 1개를 복제하도록 하였다
</code></pre>

<p>위에서 덤프한 MBR 파일을 hexdump로 좀 더 보기 쉽게 읽어보면 아래와 같다</p>

<p><img src="/images/2015/12/mbr-hexdump.png" alt="" /></p>

<p>처음 부터 차근차근 살펴보도록 하자.</p>

<p><img src="/images/2015/12/mbr-hexdump-1.png" alt="mbr-hexdump-1.png" /></p>

<p>처음 3개의 바이트 값은 <strong>eb 48 90</strong>인데 여기에서 <strong>eb 48</strong>은 <strong>JMP(eb) 0x48</strong>을 의미하며 현재 명령의 위치로부터 0x48만큼 점프하게 된다. 따라서, 인자 값에서 2바이트를 더한 0x48 + 0x02 = <strong>0x4a</strong> 위치로 이동하게 되며 <strong>eb 48</strong> 뒤에 따르는 90은 아무것도 하지 않는다는 의미다.</p>

<pre><code>0xeb: JMP - short relative jump instruction
0x90: NOP - no operation
</code></pre>

<p>이렇게 부트로더는 0x4a로 점프해서 실행하게 되는데, 사이에 있는 데이터는 스토리지 볼륨의 레이아웃에 대한 정보를 알려주는 BPB(BIOS Parameter Block)영역으로 자세한 사항은 <a href="https://en.wikipedia.org/wiki/BIOS_parameter_block" target="_blank">여기</a>를 참고하도록 하자.</p>

<p><img src="/images/2015/12/mbr-hexdump-2.png" alt="mbr-hexdump-2.png" /></p>

<p>그 중에서 위 그림의 녹색영역인 <strong>0x3e</strong> 부터 12바이트 데이터는 GRUB에 대한 정보를 담고 있다. 앞에서부터 하나씩 해석하면 아래와 같다.</p>

<pre><code>0x003e - 03: Major 버전 번호
0x003f - 02: Minor 버전 번호
0x0040 - ff: Boot Drive
0x0041 - 00: Force LBA Mode byte
0x0042 - 00 20: 다음 스테이지 GRUB 메모리 주소 (0x2000)
0x0044 - 01 00 00 00: stage1이 알고 있는 stage2의 섹터 위치 값이다. 보통 GRUB MBR 뒤에 오게 된다면 01 00 00 00이지만 그렇지 않을 경우 실제 섹터 값이 들어있다.
0x0048 - 00 02: 다음 스테이지의 위치 (메모리 주소가 아니다 - 0x0200 = 512)
</code></pre>

<p><img src="/images/2015/12/mbr-hexdump-3.png" alt="mbr-hexdump-3.png" /></p>

<p><strong>0x4a</strong>부터 시작되는 프로그램 코드는 다음 스테이지로 넘어가기위한 작업을 진행하고 문제가 발생 할 경우에는 아래의 <strong>0x0179</strong>에 위치한 문자열로 오류 메시지를 출력하게 된다. 프로그램 코드와 파티션 테이블의 위치 (<strong>0x01be</strong>)사이의 데이터는 보통 사용하지 않는데 <strong>0x01b8</strong>에서 <strong>0x01bb</strong>까지의 1WORD는 Windows에서 NT Drive 시리얼넘버로 사용한다. 그리고 <strong>0x01be</strong>부터 시작되는 파티션 엔트리는 <a href="http://lunatine.net/faq-linuxeseo-mbr-patisyeon-teibeul-salpyeobogi/" target="_blank">MBR</a>문서에서 소개한 것 처럼 파티션 테이블의 정보를 담고 있다.</p>

<h3 id="stage1-5">stage1.5</h3>

<p>앞서 살펴본 것 처럼 stage1 이미지는 512byte의 MBR에 들어갈 수 있는 작은 크기로 다음 단계의 부트로더 이미지를 읽기 위한 작업만을 처리하게 된다. 다음 단계에 해당하는 stage2 이미지는 GRUB의 코어 이미지로 스스로 부팅 할 수 있는 기능을 제외한 모든 기능을 담고 있다. 하지만, 요즘에는 다양한 파일시스템과 볼륨 구조위에 stage2 이미지가 존재하기 때문에 stage1에서 모든 파일시스템을 다룰 수 없어서 중간 단계에 해당하는 stage1.5 이미지를 먼저 불러들인다.</p>

<p>/boot/grub 아래에는 아래와 같이 stage1.5에 해당하는 파일들이 존재한다.</p>

<pre><code>$ ls -l /boot/grub/*1_5
-rw-r--r--. 1 root root 13380 May  2  2013 /boot/grub/e2fs_stage1_5
-rw-r--r--. 1 root root 12620 May  2  2013 /boot/grub/fat_stage1_5
-rw-r--r--. 1 root root 11748 May  2  2013 /boot/grub/ffs_stage1_5
-rw-r--r--. 1 root root 11756 May  2  2013 /boot/grub/iso9660_stage1_5
-rw-r--r--. 1 root root 13268 May  2  2013 /boot/grub/jfs_stage1_5
-rw-r--r--. 1 root root 11956 May  2  2013 /boot/grub/minix_stage1_5
-rw-r--r--. 1 root root 14412 May  2  2013 /boot/grub/reiserfs_stage1_5
-rw-r--r--. 1 root root 12024 May  2  2013 /boot/grub/ufs2_stage1_5
-rw-r--r--. 1 root root 11364 May  2  2013 /boot/grub/vstafs_stage1_5
-rw-r--r--. 1 root root 13964 May  2  2013 /boot/grub/xfs_stage1_5
</code></pre>

<p>stage1.5 파일들의 이름을 유심히 보면 파일시스템 이름으로 시작하는데 이 파일들을 통해서 각각의 파일시스템을 이해하고 stage2 이미지를 불러들일 수 있는 것이다. 만약 ext4 파일시스템 위에 /boot 파티션과 stage2 이미지가 있다면 GRUB 부트로더가 설치 될 때에 stage1.5로 e2fs_stage1_5 파일이 설치된다.</p>

<p>앞서 stage1을 살펴볼 때 GRUB 정보에서 다음 스테이지의 섹터 위치가 MBR 바로 뒤(01 00 00 00)로 표기되었기 때문에 Sector 1번 부터 stage1.5 이미지가 설치되어 있을 것이다. 테스트 시스템의 경우 /boot가 ext4 파일시스템에 있기 때문에 e2fs_stage1_5 파일의 내용이 들어 있는지 확인해 보도록 하자.</p>

<p>먼저 e2fs_stage1_5 파일을 hexdump로 확인해보고 시작 데이터와 마지막 부분을 확인 해 두자.</p>

<p><img src="/images/2015/12/stage15_hexdump-1-1.png" alt="" /></p>

<p><strong>52 56</strong>으로 시작 된 내용은 중간에 stage1.5 에러 텍스트와 stage2 이미지경로를 담고 있으며 0x3443까지 데이터가 있다. (13380바이트 파일이므로 0x3444 크기이다) 이제 실제 디스크에서 MBR 바로 뒤 부터 27개의 섹터를 저장해 보자</p>

<ul>
<li>e2fs_stage1_5 파일은 13380 byte</li>
<li>1섹터는 512바이트</li>
<li><sup>13380</sup>&frasl;<sub>512</sub> = 26.1328125 이므로 최소 27개 섹터가 필요하다</li>
</ul>

<p><img src="/images/2015/12/stage15_hexdump-2-1.png" alt="" /></p>

<p>디스크로 부터 저장한 27섹터를 열어보면 e2fs_stage1_5의 내용이 그대로 들어있는 것을 확인 할 수 있다. 그리고, 해당 파일은 0x3443에서 끝났기 때문에 위의 내용처럼 0x3444부터 00으로 섹터 끝까지 채워져 있는 것을 볼 수 있다.</p>

<p>과거에는 MBR아래에서 관리되던 파티션 들은 대부분이 Sector 63부터 시작하였다. MS-DOS 시절부터 첫 번째 트랙은 시스템정보를 저장하기 위한 용도로 사용되었으며 플로피의 경우 &lsquo;Track 0&rsquo;가 망가지면 전체 디스크를 사용할 수 없었기 때문에 복구를 위해 놔두기도 하였다. DOS가 사용되던 시절에는 <a href="https://en.wikipedia.org/wiki/Cylinder-head-sector" target="_blank">CHS</a> 기준으로 1 트랙은 63개의 섹터를 가지고 있었다. 그렇기 때문에 보통 디스크 볼륨의 부트레코드는 두 번째 트랙에 해당하는 64번 섹터(LBA 63)부터 시작하였고 MBR을 제외한 Sector 1~62는 비어있는 섹터이면서 기본적으로 사용하지 않는 섹터였다. 그리고, GRUB은 여기에 stage1.5를 저장해서 동작 한다.</p>

<p>여담으로, 최근에는 <a href="https://en.wikipedia.org/wiki/Advanced_Format" target="_blank">AF</a> 디스크들을 위해서 파티션의 섹터 정렬이 필요하게 되었는데 정렬을 위해서는 파티션 시작 섹터 값이 8의 배수이면 문제가 없지만 Windows가 LBA 2048 섹터부터 시작하기 때문에 파티셔닝 툴 들이 LBA 2048 섹터부터 시작하는 것을 기준으로 설정하는 것을 권장하고 있다.</p>

<h3 id="stage-2">stage 2</h3>

<p>앞서 살펴 본 stage1, stage1.5의 과정을 도식화 하면 아래와 같다.</p>

<p><img src="/images/2015/12/grub-mbr.png" alt="grub-mbr.png" />
- 출처: <a href="https://en.wikipedia.org/wiki/GNU_GRUB" target="_blank">GNU GRUB</a></p>

<p>위의 그림처럼 MBR에 위치한 stage1은 파일시스템 접근을 위해 stage1.5 파일을 불러들이고 stage1.5를 통해서 root 디스크의 지정된 위치로 부터 stage2 이미지를 불러들여서 GRUB 부트로더가 정상적인 모든 기능을 수행 할 수 있도록 준비를 마친다. stage2는 아래 처럼 용량이 크기 때문에 (어디까지나 MBR과 Track 0에 해당하는 63섹터 이내 기준보다) 파일시스템에 보통 위치하게 된다.</p>

<pre><code>$ ls -l /boot/grub/stage2
-rw-r--r--. 1 root root 125976 May  2  2013 /boot/grub/stage2
</code></pre>

<ul>
<li>125976 byte = 125976 / 512 = 246.046875 = 247섹터 필요</li>
</ul>

<p>사실, 커널의 위치가 고정되어서 관리된다면 굳이 다음 단계의 부트로더를 불러들일 필요 없이 MBR(stage1)에서 바로 해당 커널이 위치한 섹터로 접근해서 부팅을 시도할 수도 있다. 이 경우에는 커널파일이 존재하는 파일시스템의 종류나 구조에 대해서 전혀 알 필요가 없게 된다.</p>

<p>하지만, 이런식으로 부팅을 관리하게 되면 새로 설치되는 커널에 대해서는 바뀐 위치를 매번 갱신해야하거나 기존 섹터가 새로운 섹터를 가리키도록 지정 해야만 한다. 따라서 이러한 방법들은 오히려 관리의 복잡성을 높이고 다양한 기능을 추가하기 어렵기 때문에 GRUB과 같은 형태의 부팅 방식을 취하고 있다.</p>

<p>그렇기 때문에 한번 설치한 GRUB 부트로더 환경에서는 /boot/grub/grub.conf 파일만 수정해서 설정을 쉽게 변경 할 수 있는 것이다. (보통 /etc/grub.conf로 심볼릭 링크가 걸려있다)</p>

<h2 id="linux-kernel">Linux Kernel</h2>

<p>BIOS에서는 디스크 부트섹터를 읽어 들여서 GRUB 부트로더가 구동이 되며 GRUB 부트로더는 설정 된 값에 따라서 커널 이미지를 읽어 실행시키는 작업을 하게 된다. GRUB 부트로더가 커널 이미지를 읽어서 부팅하는 과정에 대해서 살펴보자.</p>

<h3 id="bzimage">bzImage</h3>

<p>현재의 리눅스커널은 bzImage 형태로 되어있는데 이름에서도 유추할 수 있듯이 gzip으로 압축된 바이너리 이미지이다. (zImage와 bzImage 모두 압축된 형태를 의미하며 bzImage가 더 큰 이미지로 <a href="https://en.wikipedia.org/wiki/Real_mode" target="_blank">리얼모드</a> 메모리 영역 밖에 커널 이미지를 둬야하는 차이가 있다) bzImage는 크게 부트섹션(bootsect), 셋업코드(bootsetup), 커널이미지(vmlinux + 전개루틴)로 구성되어 있다. 부트섹션은 512 byte 공간으로 커널 2.4까지는 플로피 디스크를 위한 부트로더가 들어있었으나 2.6부터는 에러메시지와 부트로더가 참조 할 파라미터 정도만 들어있다. (그렇기 때문에 2.4커널의 경우 플로피에 복사하면 바로 부팅이 가능했다). - <a href="https://en.wikipedia.org/wiki/Vmlinux#bzImage" target="_blank">참고문헌</a></p>

<p><img src="/images/2015/12/bzimage.png" alt="" />
- 이미지출처: <a href="https://en.wikipedia.org/wiki/Vmlinux#bzImage" target="_blank">위키피디아</a></p>

<h3 id="memory-boot-loader">Memory - Boot loader</h3>

<p>BIOS는 부트로더를 물리 메모리 0x7C00에 로드 시킨다. - 왜 0x7C00에 로딩하는지에 대해서는 <a href="http://www.glamenv-septzen.net/en/view/6" target="_blank">여기</a> 문서에 잘 설명되어 있다. 쉽게 말해 BIOS 개발진이 최소 필요한 메모리 영역을 비워두고 부트로더를 로딩 할 메모리 주소를 선택한 것이 0x7C00이다.</p>

<p>로딩 된 부트로더는 커널을 메모리에 올리고 부팅하기 위한 사전 작업을 수행하는데 먼저 부트섹션과 부트섹션에 기재 된 셋업코드 크기를 참고하여 (512 + 512*n byte)를 메모리에 세팅하게 된다. 세팅하는 메모리 위치는 <a href="Real Mode" target="_blank">리얼 모드</a>에서 접근 가능한 주소여야 하기 때문에 0x090000 (576KB)에 부트섹션을 세팅하고 0x90200에 셋업코드를 세팅하게 된다. 그리고 나머지 커널 이미지를 0x010000 (64KB)에 세팅하게 된다.</p>

<p><img src="/images/2015/12/kernel-boot-1.png" alt="" /></p>

<p>하지만, 위와 같은 메모리 전개는 과거 커널 이미지(Image or zImage)에서 주로 사용하던 메모리 전개이며 최근 bzImage 커널 이미지의 경우는 [리얼 모드]에서 접근 가능한 위치 중 부트로더가 제공하는 제일 낮은 위치에 부트섹터와 셋업코드를 세팅하고 커널이미지는 0x100000(1MB 이후)에 세팅하여 아래와 같은 모습을 가지고 있다. 이는 큰 커널이미지를 사용 할 때도 작은 커널과 같은 부팅 정책을 사용하지만 ISA Hole 위치까지 커널이미지가 로딩되는 것을 방지하기 위해서 1MB 이후에 로딩하는 것이다.</p>

<ul>
<li>ISA Hole 이란?

<ul>
<li>IBM 호환 PC에서는 물리주소 0x000a0000 ~ 0x000fffff까지는 BIOS의 함수와 ISA 그래픽카드의 내부 메모리를 매핑하는 용도로 예약이 되어 있으며 운영체제가 해당 페이지 프레임을 사용할 수 없다. 이는 640KB~1MB까지의 공간으로 ISA Hole이라고 칭한다.</li>
</ul></li>
</ul>

<p><img src="/images/2015/12/kernel-boot-2.png" alt="" /></p>

<p>부트로더는 커널 이미지 뿐만아니라 설정파일에 <strong>initrd/initramfs</strong>가 설정되어 있다면 해당 이미지 파일 또한 메모리에 올려두며 커널에 전달 할 <strong>커맨드 라인</strong>(커널 파라미터)도 저장한다. 과거(Image/zImage)에는 커맨드 라인이 0x98000에서 0x9A000까지 저장되었기 때문에 255글자 제한이 있었지만 지금은 cmd_line_ptr 필드값을 통해서 원하는 위치에 저장이 가능하다. (셋업코드가 사용하는 힙이 끝나는 영역부터 0x0A0000 사이에 아무곳이나 가능)</p>

<h3 id="kernel-setup">kernel :: setup()</h3>

<p>커널의 셋업코드(부트섹터 바로 다음)는 하드웨어 장치를 리눅스 커널에 맞게 초기화 작업을 수행한다. 그렇기 때문에 BIOS에서 보여지는 하드웨어 정보와 별개로 하드웨어를 인식하고 제어할 수 있는 것이다.</p>

<ul>
<li><a href="http://www.acpi.info/DOWNLOADS/ACPIspec40a.pdf" target="_blank">ACPI</a> 적용 시스템에서 시스템의 물리 메모리 배치를 나타내는 메모리 테이블을 구축하기 위해서 BIOS루틴에서 BIOS-e820을 찾는다</li>
<li>키보드 반복 간격과 속도를 설정한다</li>
<li>VGA를 초기화 한다</li>
<li>디스크 컨트롤러를 다시 초기화 하고, 하드 디스크의 파라미터를 알아낸다</li>
<li>IBM MCA(Micro Channel Bus)를 검사한다</li>
<li>PS/2 장치 검사</li>
<li>APM(Advanced Power Management)의 BIOS지원 여부를 검사</li>
<li>EDD(Enhanced Disk Drive)서비스의 BIOS지원 여부를 검사</li>
<li>만약 커널 이미지가 0x00010000(64KB)에 위치해 있다면 0x00001000(4KB)으로 옮긴다

<ul>
<li>이는 커널 이미지가 압축을 풀기위해 이미지 뒤에 여유공간이 필요하기 때문에 더 앞쪽 주소로 옮긴다</li>
<li>만약 1MB(0x00100000) 뒤에 있다면 이 과정은 생략된다</li>
</ul></li>
<li>8042 키보드 컨트롤러의 A20 핀을 설정한다 - <a href="http://www.win.tue.nl/~aeb/linux/kbd/A20.html" target="_blank">참고문서</a>

<ul>
<li>80286과 8088 마이크로프로세서의 물리 주소 호환을 위한 것으로 설정되지 않으면 모든 21번째 핀이 0으로 취급되어버린다</li>
</ul></li>
<li>부트 작업을 위한 IDT(Interrupt Descriptor Table)과 GDT(Global Descriptor Table)을 세팅한다</li>
<li>FPU(Floating Point Unit)이 존재 한다면 이를 초기화 한다</li>
<li>PIC(Programmable Interrupt Controllers)를 다시 프로그래밍하여 모든 인터럽트를 마스크 한다</li>
<li>CPU의 cr0 상태 레지스터 PE 비트를 설정하여 CPU를 real mode에서 protected mode로 전환한다</li>
<li>startup_32()로 넘어간다.</li>
</ul>

<p>하드웨어 처리와 관련 된 부분이기 때문에 상세한 내용은 실제 <a href="http://www.tldp.org/HOWTO/Linux-i386-Boot-Code-HOWTO/setup.html" target="_blank">어셈블리 코드를 설명한 문서</a>를 참조하도록 하고 이 중에서 눈여겨 볼 만한 것만 자세히 짚어보면 BIOS-e820에 대한 부분이 있다. BIOS-e820은 시스템 주소 맵을 얻기 위한 것으로 INT 15h, AX=E820h를 호출하면 real mode에서 현재 시스템에서 사용가능한 메모리 맵 정보를 얻을 수 있다. 이 정보는 시스템이 부팅되면 &ldquo;/sys/firmware/memmap&rdquo; 에 반영된다. 상세 정보는 <a href="http://www.uruk.org/orig-grub/mem64mb.html" target="_blank">여기</a>와 <a href="http%3A%2F%2Fwiki.osdev.org%2FDetecting_Memory_%28x86%29%23BIOS_Function%3A_INT_0x15.2C_EAX_.3D_0xE820" target="_blank">여기</a>를 참고하자.</p>

<p>startup_32()로 넘어가기 전에 cr0 상테 레지스터에 대한 내용이 나오는데 cr은 Control Register를 의미하며 cr0는 프로세서에 대한 다양한 동작을 지정하는 플래그를 가지고 있다. 그 중에서 0번째 비트가 1로 세팅이되면 시스템은 <a href="https://en.wikipedia.org/wiki/Real_mode" target="_blank">리얼모드</a>에서 <a href="Protected Mode" target="_blank">보호모드</a>로 넘어가게 된다.</p>

<p><a href="https://en.wikipedia.org/wiki/Real_mode" target="_blank">리얼모드</a>와 <a href="https://en.wikipedia.org/wiki/Protected_mode" target="_blank">보호모드</a>에 대한 설명을 전부 하기에는 그 내용이 많다. 따라서, 간단히 설명하면 <a href="https://en.wikipedia.org/wiki/Real_mode" target="_blank">리얼모드</a>는 시스템 물리 메모리 주소를 바탕으로 단일 프로세스가 독점으로 접근하는 상태이며 <a href="https://en.wikipedia.org/wiki/Protected_mode" target="_blank">보호모드</a>는 가상 메모리 주소를 제공하고 이를 통해서 Linux와 같은 시스템 소프트웨어가 가상 메모리, 페이징, 멀티태스킹을 가능하도록 지원하는 상태라고 이해하면 된다. 80286 CPU가 나오기 전에는 <a href="https://en.wikipedia.org/wiki/Real_mode" target="_blank">리얼모드</a>/<a href="https://en.wikipedia.org/wiki/Protected_mode" target="_blank">보호모드</a>로 구분하는 것은 없었다. 80286이 하드웨어 수준의 메모리 보호를 적용하도록 디자인 되면서 기존 <sup>8086</sup>&frasl;<sub>8088</sub> CPU와의 호환성을 맞추기 위해서 <a href="다시 말해 8088/8086 호환모드" target="_blank">리얼모드</a>라는 개념을 도입한 것이다.</p>

<p><img src="/images/2015/12/cr0.png" alt="cr0.png" />
- 출처: <a href="https://en.wikipedia.org/wiki/Control_register" target="_blank">위키피디아</a></p>

<h3 id="kernel-startup-32">kernel :: startup_32()</h3>

<p>arch/i386/boot/compressed/head.S 파일에 있는 startup_32()는 아래와 같은 작업을 수행한다.</p>

<ul>
<li>Segmentation 레지스터와 임시 스택을 초기화한다</li>
<li>eflags 레지스터 안의 모든 비트를 지운다</li>
<li>_edata와 _end 심볼 사이에 있는 커널의 초기화되지 않은 영역을 0으로 채운다</li>
<li>decompress_kernel() 함수를 호출하여 커널 이미지의 압축을 푼다

<ul>
<li>0x00001000(4KB)에 이미지가 있을 경우에는 0x00100000(1MB)로 압축해제 된 커널을 옮긴다</li>
<li>0x00100000(1MB)에 이미지가 있을 경우에는 이미지 뒤의 임시버퍼에 압축을 풀고 0x00100000로 옮긴다</li>
</ul></li>
<li>0x00100000(1MB)위치로 점프(JMP)한다</li>
</ul>

<p>여기에서 초기화하는 Segmentation 레지스터는 아래 그림처럼 메모리 관리 방법인 세그먼트에 대한 정보를 담고 있는 레지스터를 의미한다.</p>

<p><img src="/images/2015/12/memory-segment.gif" alt="" /></p>

<p>compressed/head.S의 startup_32()는 주로 압축해제의 역할만을 수행한다. 여기에서 eflags는 현재 프로세서의 상태를 저장하는 레지스터로 아래와 같은 정보들을 가지고 있다.</p>

<p><img src="/images/2015/12/eflags.png" alt="eflags.png" />
- 출처: <a href="http://www.simonganiere.ch/2012/07/27/introduction-to-x86-assembly-language-part-ii/" target="_blank">Introuction to x86 assembly language</a></p>

<p>그리고, _edata와 _end는 아래 그림처럼 초기 3MB의 공간에서 커널 뒤에오는 초기화 되지 않은 커널 데이터 영역을 의미한다.</p>

<p><img src="/images/2015/12/768page.png" alt="768page.png" />
- 출처: Understanding the Linux Kernel</p>

<p>압축해제 작업을 마치고 커널이미지의 시작지점(0x00100000)으로 이동하고 이는 다시금 arch/i386/kernel/head.S 파일에 있는 startup_32()를 실행하게 된다. 이름은 같지만 두 번째 startup_32() 함수는 <strong>첫 번째 리눅스 프로세스(PID 0)</strong>를 실행하는 환경을 구성하게 된다.</p>

<ul>
<li><a href="https://en.wikipedia.org/wiki/.bss" target="_blank">BSS</a> 영역을 초기화 한다</li>
<li>페이징 모드로 변환한다

<ul>
<li>cr3 레지스터에 페이지 전역 디렉토리 주소를 설정하고 cr0의 PG 비트를 1로 설정</li>
</ul></li>
<li>커맨드라인(커널 파라미터)을 첫 번째 페이지 프레임에 저장한다 (커널의 데이터 영역)</li>
<li>IDT를 NULL(가짜)로 채우고 GDT 설정

<ul>
<li>gdtr과 idtr 레지스터를 각각 GDT와 IDT 테이블 주소로 설정한다</li>
</ul></li>
<li>eflags 레지스터 안의 모든 비트를 지운다</li>
<li>프로세스 0을 위한 커널 모드 스택을 설정한다.</li>
<li>CPU의 타입을 판별한다</li>
<li>init_task_union()을 통해서 idle 프로세스 구조(task_struct)를 갖추게 되고 이는 곧 PID 0이 된다

<ul>
<li>이 과정에서 커널스택을 사용하도록 설정이 이루어진다</li>
</ul></li>
<li>start_kernel() 함수로 점프(JMP)한다</li>
</ul>

<p>이 다음부터는 C언어로 작성된 실제 커널의 시작지점으로 진입하게 되는데 startup_32()를 통해서 task_struct 구조와 페이지에 기반한 환경을 최소한으로 갖추었기 때문에 커널 바이너리의 초기화 작업을 준비된 환경에서 실행 될 수 있도록 해줍니다. 참고로, startup_32()의 페이지환경은 8MB 짜리의 일시적인 형태의 페이지 테이블입니다. 상세한 코드 설명은 <a href="http://www.tldp.org/HOWTO/Linux-i386-Boot-Code-HOWTO/kernel_head.html" target="_blank">이 문서</a>를 참고하세요.</p>

<h3 id="kernel-start-kernel">kernel :: start_kernel()</h3>

<p>본격적으로 커널을 실행하는 부분으로 init/main.c 안에 들어있는 C언어 함수이다. 코드에 대한 설명은 <a href="http://www.tldp.org/HOWTO/Linux-i386-Boot-Code-HOWTO/init_main.html" target="_blank">이 문서</a>를 참조하면 되며 내용 중에서 흐름의 이해를 돕기 위한 함수에 대해서 간단히 코멘트를 달아두었다. 또한, 최신 커널에만 있는 함수를 언급하기 위해서 kernel 4.3에 있는 init/main.c 코드를 인용하였다.</p>

<p><img src="/images/2015/12/start-kernel-1-1.png" alt="" />
- 버디시스템은 메모리 할당을 위한 알고리즘으로 전체 설명은 이 문서의 범위를 벗어나므로 이 <a href="https://youtu.be/qdkxXygc3rE" target="_blank">영상</a>을 보도록 하자.</p>

<p><img src="/images/2015/12/start-kernel-1-2-1.png" alt="" /></p>

<p><img src="/images/2015/12/start-kernel-2.png" alt="" /></p>

<p>마지막의 rest_init()이 수행되면 앞서 초기화 하였던 관리환경을 토대로 프로세스가 생성되게 된다.
- kernel_thread()를 통해서 PID 1인 커널 스레드를 생성한다
  - PID 1 커널 스레드는 필요한 커널 스레드 들을 생성하고나서 init()으로 넘어간다
    - 최신 커널은 kernel_init() 함수이다
  - init()에서 멀티프로세서의 초기화, 드라이버 초기화, initrd/initramfs 처리 및 rootfs 마운트
  - /sbin/init, /etc/init, /bin/init, /bin/sh 등을 찾아서 init 단계로 넘어간다
    - init을 찾지 못하면 커널패닉 발생</p>

<h2 id="부트-메시지">부트 메시지</h2>

<p>앞서 살펴보았던 부트로더, 커널의 부팅 및 초기화 과정의 내용을 바탕으로 하여 Linux 커널이 부팅할 때 기록하는 메시지에 대해서 이해해 보도록 하자. 이 메시지는 부팅이 완료된 이후에 dmesg와 같은 명령을 통해서 확인하거나 /var/log 아래에 기록된 로그파일로 확인 할 수 있다.</p>

<pre><code>[/var/log/dmesg]
Initializing cgroup subsys cpuset
Initializing cgroup subsys cpu
Linux version 2.6.32-573.8.1.el6.centos.plus.x86_64 (mockbuild@c6b9.bsys.dev.centos.org) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC) ) #1 SMP Tue Nov 10 18:20:27 UTC 2015
Command line: ro root=UUID=f7b6075b-d9f0-4ce7-bbf8-eec678d60269 intel_idle.max_cstate=0 crashkernel=auto biosdevname=0 console=tty0 console=ttyS0,115200
KERNEL supported cpus:
  Intel GenuineIntel
  AMD AuthenticAMD
  Centaur CentaurHauls
</code></pre>

<p>처음 Linux control group에 대한 초기화 메시지와 함께 현재 사용되는 커널에 대한 정보를 출력하고 있다. 그리고, 해당 커널을 부팅하기 위해서 전달 된 커널 파라미터(Command line)의 정보를 확인 할 수 있으며 해당 커널이 지원하는 CPU 아키텍처에 대한 정보가 나타난다.</p>

<pre><code>[/var/log/dmesg]
BIOS-provided physical RAM map:
 BIOS-e820: 0000000000000000 - 000000000009c000 (usable)
 BIOS-e820: 0000000000100000 - 00000000bf699000 (usable)
 BIOS-e820: 00000000bf699000 - 00000000bf6af000 (reserved)
 BIOS-e820: 00000000bf6af000 - 00000000bf6ce000 (ACPI data)
 BIOS-e820: 00000000bf6ce000 - 00000000c0000000 (reserved)
 BIOS-e820: 00000000e0000000 - 00000000f0000000 (reserved)
 BIOS-e820: 00000000fe000000 - 0000000100000000 (reserved)
 BIOS-e820: 0000000100000000 - 0000000440000000 (usable)
</code></pre>

<p>이 메시지는 앞서 보았던 BIOS-e820에 따른 현재 메모리 중에서 사용가능한 영역에 대해서 BIOS로부터 받은 내용을 보여준다. 마지막 메모리 주소는 0x440000000(=17GB)로 확인된다.</p>

<pre><code>DMI 2.6 present.
SMBIOS version 2.6 @ 0xFCAB0
DMI: Dell Inc. PowerEdge R210/05KX61, BIOS 1.10.0 09/10/2013
e820 update range: 0000000000000000 - 0000000000001000 (usable) ==&gt; (reserved)
e820 remove range: 00000000000a0000 - 0000000000100000 (usable)
No AGP bridge found
last_pfn = 0x440000 max_arch_pfn = 0x400000000
x86 PAT enabled: cpu 0, old 0x7040600070406, new 0x7010600070106
e820 update range: 00000000c0000000 - 0000000100000000 (usable) ==&gt; (reserved)
last_pfn = 0xbf699 max_arch_pfn = 0x400000000
initial memory mapped : 0 - 20000000
init_memory_mapping: 0000000000000000-00000000bf699000
 0000000000 - 00bf600000 page 2M
 00bf600000 - 00bf699000 page 4k
kernel direct mapping tables up to bf699000 @ 8000-d000
Use unified mapping for non-reserved e820 regions.
init_memory_mapping: 0000000100000000-0000000440000000
 0100000000 - 0440000000 page 2M
kernel direct mapping tables up to 440000000 @ b000-19000
.... 중략 ....
Reserving 130MB of memory at 48MB for crashkernel (System RAM: 17408MB)
 [ffffea0000000000-ffffea000ebfffff] PMD -&gt; [ffff880028600000-ffff8800363fffff] on node 0
 [ffffea000ec00000-ffffea000edfffff] PMD -&gt; [ffff880038000000-ffff8800381fffff] on node 0
Zone PFN ranges:
  DMA      0x00000001 -&gt; 0x00001000
  DMA32    0x00001000 -&gt; 0x00100000
  Normal   0x00100000 -&gt; 0x00440000
Movable zone start PFN for each node
</code></pre>

<p>하드웨어 정보를 인지하고 그에 따라서 BIOS-e820 주소 정보를 계속해서 업데이트하게 된다. 앞서 살펴보았던 start_kernel()에서 진행되는 내용에 따라서 초기화 작업에 따라 지속적으로 갱신된다.</p>

<pre><code>Kernel command line: ro root=UUID=f7b6075b-d9f0-4ce7-bbf8-eec678d60269 intel_idle.max_cstate=0 crashkernel=130M@0M biosdevname=0 console=tty0 console=ttyS0,115200
</code></pre>

<ul>
<li><p>커널에 전달되는 파라미터를 확인한다</p>

<pre><code>Memory: 16290300k/17825792k available (5425k kernel code, 1058608k absent, 476884k reserved, 6986k data, 1296k init)
</code></pre></li>

<li><p>앞서 진행했던 초기화 작업에 의한 최종 메모리 사용량을 확인한다</p>

<pre><code>HPET: 8 timers in total, 5 timers will be used for per-cpu timer
hpet0: at MMIO 0xfed00000, IRQs 2, 8, 24, 25, 26, 27, 28, 0
hpet0: 8 comparators, 64-bit 14.318180 MHz counter
hpet: hpet2 irq 24 for MSI
hpet: hpet3 irq 25 for MSI
hpet: hpet4 irq 26 for MSI
hpet: hpet5 irq 27 for MSI
hpet: hpet6 irq 28 for MSI
Switching to clocksource hpet
</code></pre></li>

<li><p>부팅 시점에서 클럭소스를 HPET로 설정</p>

<pre><code>Trying to unpack rootfs image as initramfs...
Freeing initrd memory: 27291k freed
</code></pre></li>

<li><p>initrd/initramfs를 임시로 root 파일시스템으로 설정하여 자세한 하드웨어 초기화 및 준비를 진행한다</p>

<pre><code>Refined TSC clocksource calibration: 2925.981 MHz.
Switching to clocksource tsc
dracut: Starting plymouth daemon
usb 1-1: new high speed USB device number 2 using ehci_hcd
mpt2sas version 20.101.00.00 loaded
mpt2sas 0000:01:00.0: PCI INT A -&gt; GSI 16 (level, low) -&gt; IRQ 16
mpt2sas 0000:01:00.0: setting latency timer to 64
</code></pre></li>

<li><p>클럭소스가 TSC로 변경 되고 시스템에 있는 여러 PCI 장치들을 설정하기 시작한다</p></li>
</ul>

<p>전체 메시지 중에서 눈여겨 볼만한 부분만 추려서 코멘트를 달았다.</p>

<p>중간에 initramfs를 root 파일시스템으로 설정하고나서 여러 PCI 장치에 대한 초기화 작업을 수행하는 것을 확인 할 수 있을 것이다. 그 전에도 하드웨어 관련된 초기화 작업이 진행되었는데 initramfs가 초기화 되고나면 드라이버(커널 모듈)형태의 장치에 대한 초기화/설정이 본격적으로 이루어 진다.</p>

<pre><code>sd 0:0:0:0: [sda] 585937500 512-byte logical blocks: (300 GB/279 GiB)
sd 0:0:0:0: [sda] Write Protect is off
sd 0:0:0:0: [sda] Mode Sense: bb 00 10 08
sd 0:0:0:0: [sda] Write cache: enabled, read cache: enabled, supports DPO and FUA
 sda: sda1 sda2 sda3
sd 0:0:0:0: [sda] Attached SCSI disk
.... 중략 ....
EXT4-fs (sda3): mounted filesystem with ordered data mode. Opts:
dracut: Remounting /dev/disk/by-uuid/f7b6075b-d9f0-4ce7-bbf8-eec678d60269 with -o noatime,nodiratime,data=writeback,errors=remount-ro,ro
EXT4-fs (sda3): mounted filesystem with writeback data mode. Opts:
dracut: Mounted root filesystem /dev/sda3
SELinux:  Disabled at runtime.
SELinux:  Unregistering netfilter hooks
type=1404 audit(1450120538.478:2): selinux=0 auid=4294967295 ses=4294967295
dracut:
dracut: Switching root
udev: starting version 147
.... 하략 ....
</code></pre>

<p>mpt2sas 호스트 인터페이스 드라이버를 통해서 인식 된 sda 디스크 장치에서 커널파라미터로 전달 받은 UUID로 root 파티션을 찾아내고 이를 정상적으로 마운트하는 작업을 거치게 된다. 메시지를 보면 해당 파티션을 기본적으로 마운트 작업을 한 뒤에 다시 /etc/fstab에 설정된 값으로 마운트하는 것을 볼 수 있다. 그리고 이제 init으로 넘어가면서 사용자 영역에서의 부팅 작업이 본격적으로 시작된다. 이 과정에서 네트워크 장치의 활성화 및 시스템 서비스/데몬 들을 실행하게 된다.</p>

<p>이러한 사용자 영역에서의 부팅 과정은 <a href="https://wiki.archlinux.org/index.php/SysVinit" target="_blank">SysV init</a>이나 <a href="http://www.freedesktop.org/wiki/Software/systemd/" target="_blank">Systemd</a>를 참고하도록 하자. (이 사이트에도 <a href="http://lunatine.net/about-systemd/" target="_blank">관련 글</a>이 있다)</p>

<h3 id="end">END</h3>

<p>어쩌면 사용자 영역에서 부팅을 진행하는 부분이 더 이해하기 쉽고 시스템을 사용하는데 있어서 좀 더 유용할 수도 있지만 사용자 영역의 부팅을 위한 사전 준비과정을 개략적으로 알고 있는 것도 시스템을 이해하는데 도움이 될 것 이다. 추가로, 여기에서 설명한 여러 레지스터나 메모리 구조에 대해서는 <a href="http://www.intel.co.kr/content/www/kr/ko/processors/architectures-software-developer-manuals.html" target="_blank">Intel IA-32</a> 메뉴얼을 참고하면 많은 도움이 된다. (다만, 어렵다..)</p>

<ul>
<li>참고문헌

<ul>
<li>개별 링크에 기재되어있음</li>
<li><a href="http://lxr.free-electrons.com/source/Documentation/x86/boot.txt" target="_blank">Kernel Document - boot</a></li>
<li><a href="http://thestarman.pcministry.com/asm/mbr/index.html" target="_blank">The Starman&rsquo;s Realm - MBR</a></li>
<li>책: <a href="http://shop.oreilly.com/product/9780596005658.do" target="_blank">Understanding the Linux kernel</a></li>
<li>책: <a href="http://www.amazon.co.jp/Linux%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB2-6%E8%A7%A3%E8%AA%AD%E5%AE%A4-%E9%AB%98%E6%A9%8B%E6%B5%A9%E5%92%8C/dp/4797338261/" target="_blank">Linuxカーネル2.6解読室</a></li>
</ul></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>[Tip] Github SSH주소를 HTTP Proxy로 접근하기</title>
            <link>/2015/08/12/tip-ssh-github-url-with-http-proxy/</link>
            <pubDate>Wed, 12 Aug 2015 05:29:18 +0000</pubDate>
            
            <guid>/2015/08/12/tip-ssh-github-url-with-http-proxy/</guid>
            <description>본 문서는 Linux 사설 환경에서 HTTP/HTTPS Proxy를 이용해서 git SSH 접근을 이용하는 방법에 대해서 소개하고 있습니다.  Github 원격 저장소로 유명한 github. 이 사이트를 통해서 작업을 할 때 HTTPS 주소로 접근하면(remote로 설정하면) push 등의 작업을 할 때 마다 인증을 해야하는 번거로움 때문에 꽤나 많은 분들이 SSH Key 방식의 인증을 선호하곤 한다.
물론 credential cache를 이용하면 HTTPS 인증이 좀 더 수월하며 이에 대한 내용은 이 문서를 참고하면 된다.
HTTP/S Proxy 일반적으로 사설IP 환경에서 외부 웹 페이지를 접근하기 위해서 HTTP Proxy를 사용하여 접근한다.</description>
            <content type="html"><![CDATA[

<ul>
<li>본 문서는 Linux 사설 환경에서 HTTP/HTTPS Proxy를 이용해서 git SSH 접근을 이용하는 방법에 대해서 소개하고 있습니다.</li>
</ul>

<h2 id="github">Github</h2>

<p>원격 저장소로 유명한 github. 이 사이트를 통해서 작업을 할 때 HTTPS 주소로 접근하면(remote로 설정하면) push 등의 작업을 할 때 마다 인증을 해야하는 번거로움 때문에 꽤나 많은 분들이 SSH Key 방식의 인증을 선호하곤 한다.</p>

<p>물론 credential cache를 이용하면 HTTPS 인증이 좀 더 수월하며 이에 대한 내용은 <a href="https://help.github.com/articles/caching-your-github-password-in-git/" target="_blank">이 문서</a>를 참고하면 된다.</p>

<h2 id="http-s-proxy">HTTP/S Proxy</h2>

<p>일반적으로 사설IP 환경에서 외부 웹 페이지를 접근하기 위해서 HTTP Proxy를 사용하여 접근한다. 따라서, 사설IP 환경에서 SSH Key 방식의 인증으로 github을 사용하려면 SOCKS Proxy가 필요한데 경우에 따라서는 이런 Proxy가 제공되지 않을 때가 많다. 본 문서에서는 HTTP/HTTPS Proxy만 제공되는 사설환경에서 github을 SSH 방식으로 접근하는 방법을 소개하고자 한다.</p>

<h2 id="해결방안">해결방안</h2>

<p>생각보다 간단하다. 기본적으로 대부분 HTTP/HTTPS Proxy도 SOCKS Proxy와 크게 다르지 않게 데이터를 포워딩하는 형태이기 때문에 git이 SSH 기반 주소를 접근할 때 HTTP/HTTPS Proxy를 이용하도록 넘겨주기만 하면 된다.</p>

<p>하지만, 보통 http/https Proxy를 Linux에서 범용으로 사용할 때 http_proxy, https_proxy 환경변수를 이용하는데 이는 git이 https 주소를 접근할 때나 사용하지 ssh 주소를 접근할 때는 이용하지 않는다. 그렇기 때문에 아래와 같은 설정을 추가해주면 된다.</p>

<p>먼저 홈디렉토리 아래에 ssh client config 파일을 편집기로 연다. ($HOME/.ssh/config)</p>

<pre><code>$ vim ~/.ssh/config
</code></pre>

<p>그리고, 아래 내용을 설정파일에 추가해 준다.</p>

<pre><code>Host github.com
   ProxyCommand          nc -X connect -x 서버주소:포트번호 %h %p
   ServerAliveInterval   10
</code></pre>

<p>서버주소와 포트번호에는 HTTP Proxy의 주소와 포트번호를 넣는다. (프로토콜 지시자는 생략한다. 예를들면 example.com:3128 형식)</p>

<p>ssh client 설정으로 위와 같은 proxy 명령을 추가하면 시스템 엔지니어의 맥가이버 칼과 같은 nc(netcat)가 git이 시도하는 ssh 접속을 HTTP Proxy 서버로 넘겨준다. 이렇게해서 별다른 문제 없이 외부에 있는 github 서버에 SSH Key 방식으로 인증하고 접근 할 수 있다.</p>

<h2 id="결론은">결론은..</h2>

<p>허무할 정도로 간단한 위 내용은 사실 git/github에 대한 Tip이라기 보다 nc의 활용방안 중 하나인 내용이다. 나중에 시간이 된다면 여러가지 작업을 할 수 있는 nc에 대해서 상세히 소개해 보려고 한다. 끝.</p>
]]></content>
        </item>
        
        <item>
            <title>[Mac] Junos Pulse를 El Capitan에서 사용하기</title>
            <link>/2015/07/31/junos-pulse-on-el-capitan/</link>
            <pubDate>Fri, 31 Jul 2015 08:16:11 +0000</pubDate>
            
            <guid>/2015/07/31/junos-pulse-on-el-capitan/</guid>
            <description>Mac OS X El Capitan Mac OS X 10.11 (El Capitan)도 벌써 PB3(Public Beta 3)까지 나왔다. PB3 정도면 큰 문제는 없겠거니 싶어서 사용하는 노트북을 업그레이드 했는데 VPN 접속에 있어서 문제가 발생했다.
Junos Pulse 전 세계적으로 많이 사용되는 Juniper 장비의 VPN 클라이언트 프로그램이다. 내가 일하는 환경에서도 이 프로그램으로 VPN을 사용하는데 이번에 El Capitan으로 업그레이드 이후에 아래와 같은 화면을 마주하게 되었다.
접속 정보에 대한 모든 프로파일이 비활성화가 되었는데 수동으로 설정 값을 추가하려해도 계속 오류만 발생하는 것이다.</description>
            <content type="html"><![CDATA[

<h2 id="mac-os-x-el-capitan">Mac OS X El Capitan</h2>

<p>Mac OS X 10.11 (El Capitan)도 벌써 PB3(Public Beta 3)까지 나왔다. PB3 정도면 큰 문제는 없겠거니 싶어서 사용하는 노트북을 업그레이드 했는데 VPN 접속에 있어서 문제가 발생했다.</p>

<h2 id="junos-pulse">Junos Pulse</h2>

<p>전 세계적으로 많이 사용되는 Juniper 장비의 VPN 클라이언트 프로그램이다. 내가 일하는 환경에서도 이 프로그램으로 VPN을 사용하는데 이번에 El Capitan으로 업그레이드 이후에 아래와 같은 화면을 마주하게 되었다.</p>

<p><img src="/images/2015/07/junos-pulse.png" alt="" /></p>

<p>접속 정보에 대한 모든 프로파일이 비활성화가 되었는데 수동으로 설정 값을 추가하려해도 계속 오류만 발생하는 것이다. 이를 해결해 보고자 오랜만에 Xcode와 Mac OS X Console을 열어서 어플리케이션 오류 로그를 확인해 봤는데 아무래도 인증서 부분이 문제되는 것으로 보였다.</p>

<h3 id="세상에는-나-처럼-삽질하는-사람이-많이-있다">세상에는 나 처럼 삽질하는 사람이 많이 있다</h3>

<p>확인된 로그 정보를 바탕으로 Apple Forum, Juniper Forum 등을 찾아보니 역시나 나와 같은 상황에 빠진 사람들이 있었다. 그리고 그 원인을 찾았는데 역시나 인증서 관련된 문제였다.</p>

<p><code>VeriSign Class 3 Public Primary Certification Authority - G5</code></p>

<p>상기 인증서가 문제였는데 현재 사용중인 Junos Pulse는 Re-branding 처리가 된 앱으로 El Capitan의 인증서와 맞지 않아서 발생하는 문제였다.</p>

<h3 id="해결-방법">해결 방법</h3>

<h4 id="1-junos-pulse-삭제">1. Junos Pulse 삭제</h4>

<p>먼저 현재 설치 된 Junos Pulse를 깔끔하게 삭제를 한다. AppCleaner 같은 어플리케이션을 이용해도 되지만 아래 경로에 들어가면 Uninstall 앱이 존재한다.</p>

<p><code>/Libarary/Application Support/Juniper Networks/Junos Pulse</code></p>

<p><img src="/images/2015/07/junos-pulse-uninstall.png" alt="" /></p>

<p>Unistall 앱을 실행하면 Save 정보를 남길건지 물어보는데 No를 선택해서 모두 삭제해도 상관없다.</p>

<h4 id="2-인증서-다운로드">2. 인증서 다운로드</h4>

<p>Symantec 사이트에서 SSL 인증서를 다운로드 받는다.</p>

<ul>
<li><a href="https://knowledge.symantec.com/support/ssl-certificates-support/index?page=content&amp;actp=CROSSLINK&amp;id=INFO2550" target="_blank">다운로드 링크</a></li>
</ul>

<p>상기 페이지의 하단에 Symc_Cross_Root.txt 파일을 cer 확장자 파일로 다운로드 받아둔다.</p>

<h4 id="3-key-chanin에-인증서-등록-및-설치">3. Key Chanin에 인증서 등록 및 설치</h4>

<p>Key Chain 앱을 실행하고 좌측 키체인 카테고리에서 시스템(System)을 선택한다. 그리고 파일 메뉴에서 항목 가져오기(Import Items&hellip;)를 선택해서 앞서 다운로드 받은 파일을 받고 등록한다. 등록한 인증서를 더블클릭하여 상세 정보 창을 열고 신뢰 항목을 선택해서 해당 인증서에 대해서 &lsquo;항상 신뢰&rsquo;(Always Trust)로 변경한다.</p>

<p><img src="/images/2015/07/verisign-trusted.png" alt="" /></p>

<p>그리고, Junos Pulse를 다시 설치하면 드디어 정상적으로 동작 한다. =)</p>

<h2 id="el-capitan-sandbox">El Capitan Sandbox</h2>

<p>Yosemite 버전 보다 안정성과 최적화에 신경을 썼다고하여 업데이트를 했지만 Junos Pulse 외에도 호환성이 떨어지는 앱들이 많이 있다. 아무래도 새롭게 도입된 Sandbox 환경 때문에 기존 앱의 동작에 필요한 권한과 관련된 부분에 있어서 앱이 지원할 부분이 많아 보인다. 즐겨 사용하는 Monosnap 이미지 캡처 프로그램도 윈도우 캡처에 문제가 있다.</p>

<p>늘 그렇듯이 Beta 버전은 신경써야 할 부분이 많아서 별로 권장하지 않는다.</p>

<h4 id="참조-페이지">참조 페이지</h4>

<ul>
<li><a href="http://forums.juniper.net/t5/SSL-VPN/Junos-Pulse-DOA-with-El-Capitan-Mac-OS-10-11/td-p/276235" target="_blank">Juniper Networks 포럼</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>[TIP] Bash - 반복 출력</title>
            <link>/2015/07/22/tip-bash-brace-expansion/</link>
            <pubDate>Wed, 22 Jul 2015 06:16:00 +0000</pubDate>
            
            <guid>/2015/07/22/tip-bash-brace-expansion/</guid>
            <description>Shell Bash 환경에서 작업을 하다보면 연속 된 숫자 값을 이용하는 경우가 종종 있다. 특정 단어에 연속 된 숫자를 붙이거나 연속 된 숫자 값의 개수만큼 Loop를 실행(반복)하거나 하는 작업들이 대표적이다. 오랜 기간 Unix/Linux를 사용해 왔던 분들이라면 보통 아래와 같은 형태로 사용 하는 것을 종종 볼 수 있다.
$ echo `seq 1 10` 1 2 3 4 5 6 7 8 9 10 $ for x in `seq 1 10` &amp;gt; do &amp;gt; echo -n &amp;quot;$x &amp;quot; &amp;gt; done 1 2 3 4 5 6 7 8 9 10$  사실 seq (sequence)는 별도의 실행 파일이기 때문에 bash와 직접적인 관련은 없다.</description>
            <content type="html"><![CDATA[

<h2 id="shell">Shell</h2>

<p>Bash 환경에서 작업을 하다보면 연속 된 숫자 값을 이용하는 경우가 종종 있다. 특정 단어에 연속 된 숫자를 붙이거나 연속 된 숫자 값의 개수만큼 Loop를 실행(반복)하거나 하는 작업들이 대표적이다. 오랜 기간 Unix/Linux를 사용해 왔던 분들이라면 보통 아래와 같은 형태로 사용 하는 것을 종종 볼 수 있다.</p>

<pre><code>$ echo `seq 1 10`
1 2 3 4 5 6 7 8 9 10
$ for x in `seq 1 10`
&gt; do
&gt; echo -n &quot;$x &quot;
&gt; done
1 2 3 4 5 6 7 8 9 10$
</code></pre>

<p>사실 seq (sequence)는 별도의 실행 파일이기 때문에 bash와 직접적인 관련은 없다. 하지만, 많은 bash 스크립트 예제에서 `seq`는 항상이라고 생각 될 정도로 자주 등장한다. 그러다보니 어느 덧 손에 익은 seq는 나도 모르게 자주 사용되는 명령 중 하나가 되곤 했다.</p>

<p>간단히 <code>seq</code>를 살펴보면 단지 순차적인 숫자를 출력해주는 명령이다. 게다가 증가분까지 지정해서 연속 적으로 숫자를 출력해 준다. (문자 포맷을 비롯한 자세한 옵션은 man 페이지 참조)</p>

<pre><code>$ seq 1 2 10
1
3
5
7
9
</code></pre>

<p>seq는 매우 직관적이고 간편한 명령이기 때문에 연속 된 숫자를 생성하는데 있어서 별다른 문제가 되지 않지만 아래와 같은 경우에는 조금 고민이 필요해 지게 된다.</p>

<ul>
<li>webserver1부터 webserver20까지 순차적인 문자+숫자 목록을 만들어내려면 어떻게 해야할까?</li>
</ul>

<p>문자 뒤에 숫자를 붙이면 되지 않을까?</p>

<pre><code>$ echo &quot;webserver`seq 1 20`&quot;
webserver1
2
3
... 생략 ...
</code></pre>

<p>seq는 new line을 구분자로 하여 생성하기 때문에 위와 같이 되어버린다. <code>-s</code> 옵션을 주고 구분자를 공백으로 하더라도 상황은 별 차이가 없다. 앞의 문자열을 반복해주지 않기 때문이다. 그렇기 때문에 결국 아래와 같이 사용하게 된다. (아래 예시에선 개행문자 대신 공백으로 구분해서 출력시켰다)</p>

<pre><code>$ for x in `seq 1 20`
&gt; do
&gt; echo -n &quot;webserver$x &quot;
&gt; done
webserver1 webserver2 webserver3 webserver4 webserver5 webserver6 webserver7 webserver8 webserver9 webserver10 webserver11 webserver12 webserver13 webserver14 webserver15 webserver16 webserver17 webserver18 webserver19 webserver20 $
</code></pre>

<p>이러한 <strong>문자+숫자</strong> 형태의 순차적인 결과 값을 자주 사용하다보면 for 구문을 타이핑하기 귀찮아서 쉘 스크립트로 짜두고 실행하는 경우도 있다. 첫 번째 인자 값으로 공통된 문자를 받고 두 번째 인자로 반복 할 숫자를 받게 했다.</p>

<pre><code>#!/bin/bash
# $1: 문자, $2: 마지막 숫자
for x in `seq 1 $2`
do
	echo -n &quot;${1}${x} &quot;
done
</code></pre>

<p>하지만, 사실 이 방법은 썩 좋은 방법이 아니다. 해당 쉘 스크립트가 없는 시스템에서 작업 하게 될 경우도 있기 때문이다. 그래서 printf 유틸리티를 이용해서 아래와 같이 처리 할 수 있다.</p>

<pre><code>$ printf &quot;webserver%d&quot; `seq 1 20`
webserver1 webserver2 webserver3 webserver4 webserver5 webserver6 webserver7 webserver8 webserver9 webserver10 webserver11 webserver12 webserver13 webserver14 webserver15 webserver16 webserver17 webserver18 webserver19 webserver20 $
</code></pre>

<p>비록 외부 유틸리티를 이용한 것이지만 (어차피 seq에서 순수 Bash는 잃어버렸다) 간단히 한 줄의 명령을 통해서 원하는 결과를 얻을 수 있다. &gt;_&lt;)b</p>

<h2 id="brace-expansion">brace expansion</h2>

<p>앞서 살펴본 seq를 이용한 반복 출력을 획기적으로 개선 할 수 있는 괄호 확장(brace expansion)구문이 bash 3.0에서 추가 되었다. 요즘 대부분의 *nix 계열 운영체제는 Bash 3.x 이상 버전을 사용하기 때문에 꽤나 유용하다.</p>

<p>새로 추가된 괄호 확장이라는 구문은 아래와 같이 사용이 가능하다.</p>

<pre><code>$ echo {1,2,3}
1 2 3
$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
$ for x in {1..10}
&gt; do
&gt; echo -n &quot;$x &quot;
&gt; done
1 2 3 4 5 6 7 8 9 10 $
</code></pre>

<p>괄호 안에 숫자를 <code>,</code>로 이어주면 개별적으로 반복하며 <code>..</code>으로 이어주면 첫 번째 숫자부터 두 번째 숫자까지를 순차적으로 확장해 준다. 괄호 확장이 좋은 점은 해당 구문과 연결 된 문자열도 같이 반복처리를 해준다는 점이다. 앞서 seq를 이용해서 webserver1~20까지를 출력 하는 경우 아래와 같이 한 줄로 처리가 가능하다.</p>

<pre><code>$ echo webserver{1..20}
webserver1 webserver2 webserver3 webserver4 webserver5 webserver6 webserver7 webserver8 webserver9 webserver10 webserver11 webserver12 webserver13 webserver14 webserver15 webserver16 webserver17 webserver18 webserver19 webserver20
</code></pre>

<p>이렇게 간편한 괄호 확장은 단순히 숫자처리만 하는 것이 아니라 문자도 확장이 가능하다. 뿐만 아니라 역순으로도 확장이 가능하다.</p>

<h3 id="기본-예시">기본 예시</h3>

<pre><code>1. 문자 확장
$ echo /dev/sd{a..z}
/dev/sda /dev/sdb /dev/sdc /dev/sdd /dev/sde /dev/sdf /dev/sdg /dev/sdh /dev/sdi /dev/sdj /dev/sdk /dev/sdl /dev/sdm /dev/sdn /dev/sdo /dev/sdp /dev/sdq /dev/sdr /dev/sds /dev/sdt /dev/sdu /dev/sdv /dev/sdw /dev/sdx /dev/sdy /dev/sdz$ echo 
$ echo /dev/sd{z..r}
/dev/sdz /dev/sdy /dev/sdx /dev/sdw /dev/sdv /dev/sdu /dev/sdt /dev/sds /dev/sdr

2. 디렉토리 생성
$ mkdir -p /mnt/sd{a..c} # /mnt/sda, /mnt/sdb, /mnt/sdc가 생성된다.

3. ASCII 순서
$ echo {A..a}
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [  ] ^ _ ` a
$ echo {[..f}
{[..f}
</code></pre>

<p>1번 예시에서 처럼 ASCII 알파벳 순서에 의거해서 문자가 확장되는 것을 볼 수 있다. 또한 2번 예시처럼 확장해서 반복처리하는 구문이기 때문에 명령의 인자 값으로 전달도 가능하다. 괄호 확장이 ASCII 값이 기초하기 때문에 3번 예시 같은 형태의 확장도 가능하다. (하지만, 가급적 사용하지 말자. 특히 시작 값을 기호로 주면 확장되지 않는다)</p>

<pre><code>4. 괄호확장 연결
$ echo {a..c}{1..3}
a1 a2 a3 b1 b2 b3 c1 c2 c3

5. 중첩 된 괄호 확장
$ echo {{a..c},{1..3},{A..D}}
a b c 1 2 3 A B C D

6. 기본 괄호 확장
$ mkdir -p /disk/{a,b,z} # /disk/a, /disk/b, /disk/z 디렉토리가 생성된다
</code></pre>

<p>4번 예시처럼 두 개의 괄호 확장을 연결하면 두 집합의 곱집합 형태로 결과를 생성해 낸다. 그리고 6번 예시처럼 <code>..</code> 대신에 <code>,</code>를 이용해서 사용하는 확장이 일반 적이며 이를 이용해서 5번 예제처럼 중첩 된 괄호 확장을 사용 할 수도 있다. (실제로 이렇게 쓸 일은 별로 없지만 서로 다른 형태의 목록을 한 줄로 표현 할 때 사용 될 수 있다)</p>

<h3 id="변수-예시">변수 예시</h3>

<p>이렇게 편리한 괄호 확장을 변수 값을 이용해서 사용하고 싶을 때가 있는데</p>

<pre><code>$ foo=1
$ bar=10
$ echo sample{$foo..$bar}
</code></pre>

<p>$foo 변수 값인 1부터 $bar 변수 값인 10까지 확장하고자 의도한 것이지만 실제로 실행해보면 결과는 아래와 같다.</p>

<pre><code>$ echo sample{$foo..$bar}
sample{1..10}
</code></pre>

<p>이는 괄호 확장이 변수 값 치환보다 먼저 처리 되기 때문에(괄호 확장으로 인식하지 못하므로) 발생 한다. 이런 경우에는 아래처럼 변수 값을 치환해주는 eval 명령을 통해서 처리가 가능하다.</p>

<pre><code>$ foo=1
$ bar=5
$ eval echo {$foo..$bar}
1 2 3 4 5
</code></pre>

<h3 id="0으로-채우기">0으로 채우기</h3>

<p>괄호 확장을 이용하다보면 종종 숫자 값을 0을 채워서 출력하고 싶을 때가 있다. 예를 들어 web-server001 부터 web-server020까지의 호스트명을 출력하고 싶다면 아래와 같이 실행하면 된다.</p>

<pre><code>$ echo web-server{001..020}
web-server001 web-server002 web-server003 web-server004 web-server005 web-server006 web-server007 web-server008 web-server009 web-server010 web-server011 web-server012 web-server013 web-server014 web-server015 web-server016 web-server017 web-server018 web-server019 web-server020
</code></pre>

<p>하지만, 대부분 위 예시를 실행해 보면 아래와 같은 결과를 얻게 된다.</p>

<pre><code>$ echo web-server{001..020}
web-server1 web-server2 web-server3 web-server4 web-server5 web-server6 web-server7 web-server8 web-server9 web-server10 web-server11 web-server12 web-server13 web-server14 web-server15 web-server16 web-server17 web-server18 web-server19 web-server20
</code></pre>

<p>왜냐하면 0으로 채워주는 기능은 <code>Bash 4.0</code>부터 지원하기 때문이다. 여전히 Bash 3.x 버전이 많이 사용되므로 위와 같은 작성 방법은 하위 호환성에 좋지 않다.</p>

<p>따라서, 편법 적이지만 0으로 값을 채우고자 할 때는 앞서 언급했던 printf를 활용한 아래의 방법으로 사용하면 된다. (쉘 스크립트는 원하는 결과를 얻기 위해 다양한 방법으로 처리가 가능하기 때문에 꼭 이렇게 해야만 하는 것은 아니다)</p>

<pre><code># 03은 0으로 채워진 3자리 숫자를 의미하며 d는 정수를 의미한다.
$ printf &quot;web-server%03d &quot; {1..20}
web-server001 web-server002 web-server003 web-server004 web-server005 web-server006 web-server007 web-server008 web-server009 web-server010 web-server011 web-server012 web-server013 web-server014 web-server015 web-server016 web-server017 web-server018 web-server019 web-server020 $
</code></pre>

<h3 id="그-외-예시">그 외 예시</h3>

<h4 id="파일을-bak-확장자를-붙인-이름으로-변경하기">파일을 .bak 확장자를 붙인 이름으로 변경하기</h4>

<p><code>{}</code>안에 <code>,</code>로 빈 값(null)을 넣게 되면 연결 된 문자열을 단순히 반복하는 점을 이용해서 조금 더 간편하게 파일 명을 변경 할 수 있다. (당연히 cp 같은 명령과 함께해서 복사 처리도 가능하다.)</p>

<pre><code>$ mv /usr/local/chroot/var/log/activity.log{,.bak}

위 명령은 아래와 같은 효과이다. 경로가 길수록 효율이 좋은 습관.

$ mv /usr/local/chroot/var/log/activity.log  /usr/local/chroot/var/log/activity.log.bak
</code></pre>

<h4 id="괄호-확장의-증가-값-지정">괄호 확장의 증가 값 지정</h4>

<p>Bash 버전이 4.0 이상 이라면 seq의 증가분 지정 처럼 아래와 같이 증가 분을 지정해서 출력이 가능하다.</p>

<pre><code>$ echo {1..10..2}
1 3 5 7 9
$ echo {a..f..2}
a c e
</code></pre>

<h4 id="옵션-반복">옵션 반복</h4>

<p>괄호 확장에는 빈 값(null)을 넣을 수 있기 때문에 아래와 같이 사용도 가능하다.</p>

<pre><code>$ command arg{,,,,}

command라는 프로그램을 실행 한다면 아래와 같은 효과를 얻게 된다

$ command arg arg arg arg arg
</code></pre>

<h3 id="끝으로">끝으로</h3>

<p>Linux 시스템을 다루다 보면 어떻게 하면 타이핑 양을 줄여볼까 하는 귀차니즘에 의해서 이러한 방법들을 활용하게 되었다. 그리고, 괄호 확장의 경우 bash에 내장된 구문이기 때문에 외부 명령보다 성능적인 효율이 좋을 것으로 예상되지만 실제로 측정해보면 꼭 그렇지 않았다. (물론, 상세한 측정을 위해선 아래와 다른 방법의 측정이 필요하지만 귀찮으므로&hellip;)</p>

<pre><code>$ time echo `seq 1 1000000` &gt; /dev/null

real	0m1.432s
user	0m1.418s
sys	0m0.053s

$ time echo {1..1000000} &gt; /dev/null

real	0m1.869s
user	0m1.803s
sys	0m0.061s
</code></pre>

<p>이 문서에서 소개한 내용은 Bash의 기초적인 내용이긴 하지만 지금까지 몰랐다면 꼭 알아두면 좋다. 본인의 귀차니즘을 해소해주고 손가락 타이핑을 줄여 줄 것이다.</p>

<p>- END -</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] ldirectord의 성능이 안나오는 증상</title>
            <link>/2015/07/17/faq-ldirectord-performance-issue/</link>
            <pubDate>Fri, 17 Jul 2015 11:06:29 +0000</pubDate>
            
            <guid>/2015/07/17/faq-ldirectord-performance-issue/</guid>
            <description>요즘에는 많이 사용하는 어플리케이션은 아니지만 종종 L3DSR을 간편하게 구현하기 위해서 LVS ldirectord를 이용한 LB(Load Balancing)구성을 하는 경우가 있다.
주로 오래된 시스템을 LVS 밸런싱 서버로 사용하곤 했는데 최근에 RHEL/CentOS 6 기준으로 설정하다보니 특이한 증상이 발견되어서 이에 대해서 간단히 해결책을 정리해 본다.
환경 OS: RHEL/CentOS 6.x Package: - heartbeat: 3.0.4-2.el6 - ldirectord: 3.9.6-1.fc22.x86_64 참고로, ldirectord의 경우 RHEL/CentOS 6부터 기본 패키지에서 빠졌기 때문에 fedora core 22 버전의 패키지를 사용하면 된다  증상 ldirectord를 이용해서 L3DSR 밸런싱을 구성하여 웹 서비스를 동작시켰는데 해당 서버들의 응답속도가 300ms 이상 느려지는 현상이 발생 하였다.</description>
            <content type="html"><![CDATA[

<p>요즘에는 많이 사용하는 어플리케이션은 아니지만 종종 L3DSR을 간편하게 구현하기 위해서 LVS ldirectord를 이용한 LB(Load Balancing)구성을 하는 경우가 있다.</p>

<p>주로 오래된 시스템을 LVS 밸런싱 서버로 사용하곤 했는데 최근에 RHEL/CentOS 6 기준으로 설정하다보니 특이한 증상이 발견되어서 이에 대해서 간단히 해결책을 정리해 본다.</p>

<h2 id="환경">환경</h2>

<pre><code>OS: RHEL/CentOS 6.x
Package:
  - heartbeat: 3.0.4-2.el6
  - ldirectord: 3.9.6-1.fc22.x86_64

참고로, ldirectord의 경우 RHEL/CentOS 6부터 기본 패키지에서 빠졌기 때문에 fedora core 22 버전의 패키지를 사용하면 된다
</code></pre>

<h2 id="증상">증상</h2>

<p>ldirectord를 이용해서 L3DSR 밸런싱을 구성하여 웹 서비스를 동작시켰는데 해당 서버들의 응답속도가 300ms 이상 느려지는 현상이 발생 하였다. 구형 장비보다 더 높은 사양과 비교적 더 최근 버전을 사용하는데 서비스 할 수 없을 만큼 속도가 나오지 않았다.</p>

<p>심지어 노드간의 균형이 맞지 않는 증상은 Bonus.</p>

<ul>
<li>응답속도의 지연: 100ms 이상. 심하면 500ms도 넘어간다. (미쳤다)</li>
<li>노드 불균형: 스케줄러를 rr(round robin)로 설정했는데 응답속도의 지연이 발생하다보니 불균형 발생.</li>
</ul>

<h2 id="해결">해결</h2>

<p>해당 증상을 일으키는 요인은 Redhat이 권장하지 않는 패키지를 설치한 내가 잘못이다. 좀더 끝까지 파고들지는 못했으나 노드에 오고가는 패킷을 보니 재전송이 빈번하게 발생하는 것을 보았고 이 증상을 바탕으로 문서들을 검색해 본 결과 <a href="http://www.gossamer-threads.com/lists/lvs/users/24305" target="_blank">이 글</a>에서 보여지는 증상과 꽤나 유사해서 GRO(generic-receive-offload)를 꺼봤더니 해결.</p>

<ul>
<li>GRO는 RHEL/CentOS 6에서 소개된 기능으로 LRO (Large Receive Offload)와 유사한 형태로 들어오는(inbound) 트래픽을 보다 CPU 리소스 소모 없이 처리하는 기법이다.</li>
</ul>

<p>따라서, 아래와 같이 설정하면 된다</p>

<pre><code># GRO 끄기
$ sudo ethtool -K eth0 gro off

# 부팅 때 마다 적용
echo &quot;ethtool -K eth0 gro off&quot; &gt;&gt; /etc/rc.local
</code></pre>

<h2 id="결론">결론</h2>

<p>지금까지 heartbeat+ldirectord 환경을 운영하면서 다양한 증상을 겪어보았는데 이제는 ldirectord를 떠나보내 줄 때가 된 것 같다. 말도 많고 탈도 많은 ldirectord를 떠나보내고 <a href="http://www.keepalived.org/" target="_blank">Keepalived</a>나 <a href="www.haproxy.org/" target="_blank">HAProxy</a>, <a href="https://www.google.co.kr/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=1&amp;cad=rja&amp;uact=8&amp;ved=0CCEQFjAAahUKEwjnuMCQguLGAhWMFZQKHQILC8Y&amp;url=http%3A%2F%2Fsiag.nu%2Fpen%2F&amp;ei=7OCoVeeSHIyr0ASClqywDA&amp;usg=AFQjCNG-8d595u8BUHQLAbTdIrwbmBfr3A" target="_blank">Pen</a> 등을 사용하도록 하자. (돈이 많으신 분은 네트워크 밸런서를 구매하시고)</p>

<p>그래도, 습관이 어디 가겠냐만&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>[영화] 나이트 크롤러</title>
            <link>/2015/07/05/movie-nightcrawler-2015/</link>
            <pubDate>Sun, 05 Jul 2015 08:26:42 +0000</pubDate>
            
            <guid>/2015/07/05/movie-nightcrawler-2015/</guid>
            <description>소개 특종을 위한 완벽한 조작! 지금, 당신이 보고 있는 뉴스는 진실인가? 루이스 (제이크 질렌할)는 우연히 목격한 교통사고 현장에서, 특종이 될 만한 사건 현장을 카메라에 담아 TV 매체에 고가에 팔아 넘기는 일명 ‘나이트 크롤러’를 보게 된다. 경찰이 도착하기 전에 빠르게 나타나 현장을 스케치하고 전화를 통해 가격을 흥정하는 그들에게서 묘한 돈 냄새를 맡은 루이스는 즉시 캠코더와 경찰 무전기를 구입하고 사건현장에 뛰어든다. 유혈이 난무하는 끔찍한 사고 현장을 적나라하게 촬영해 첫 거래에 성공한 루이스는 남다른 감각으로 지역채널의 보도국장 니나(르네 루소)의 적극적인 지지를 받게 된다.</description>
            <content type="html"><![CDATA[

<h2 id="소개">소개</h2>

<p><img src="/images/2015/02/nightcrawler.jpeg" alt="" /></p>

<pre><code>특종을 위한 완벽한 조작!
지금, 당신이 보고 있는 뉴스는 진실인가? 

루이스 (제이크 질렌할)는 우연히 목격한 교통사고 현장에서, 특종이 될 만한 사건 현장을 카메라에 담아 TV 매체에 고가에 팔아 넘기는 일명 ‘나이트 크롤러’를 보게 된다. 경찰이 도착하기 전에 빠르게 나타나 현장을 스케치하고 전화를 통해 가격을 흥정하는 그들에게서 묘한 돈 냄새를 맡은 루이스는 즉시 캠코더와 경찰 무전기를 구입하고 사건현장에 뛰어든다. 

유혈이 난무하는 끔찍한 사고 현장을 적나라하게 촬영해 첫 거래에 성공한 루이스는 남다른 감각으로 지역채널의 보도국장 니나(르네 루소)의 적극적인 지지를 받게 된다. 매번 더욱 더 자극적이고 충격적인 뉴스를 원하는 니나와 그 이상을 충족 시켜주는 루이스는 최상의 시청률을 만들어내며 승승장구한다. 자신의 촬영에 도취된 루이스는 결국 완벽한 특종을 위해 사건을 조작하기에 이르는데…

2월, 숨막히는 특종 추적 스릴러가 온다!
</code></pre>

<p>(출처: <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=87254" target="_blank">다음 영화</a>)</p>

<h2 id="미친놈-이야기">미친놈 이야기</h2>

<p>특종을 향해 집요하게 매달리는 한 남자에 대한 이야기. 처음엔 일개 잡범과 같은 찌질한 모습으로 등장하여 점점 본인이 가진 야욕을 드러내며 대담해 지는데 영화 보는 내내 &lsquo;미친 것 같다&rsquo;라는 생각이 강렬히 드는 캐릭터였다. 주연 배우는  내게 <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=56394" target="_blank">소스코드</a>, <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=43175" target="_blank">페르시아의 왕자: 시간의 모래</a>로 기억되는 <a href="http://movie.daum.net/movieperson/Biography.do?personId=1205" target="_blank">제이크 질렌할</a>. 주연 배우의 명 연기가 있었기에 캐릭터의 미친 모습이 더 강렬하게 다가왔다.</p>

<h2 id="자극">자극</h2>

<p>영화는 특종에 집작하는 언론을 보여주고 있는데 이 특종이라는 것이 보다 자극적인 장면을 통한 시청률 올리기라는 것에서 문제점을 제시한다. 사실, 국내 언론도 기사 제목을 자극적으로 뽑아서 올리는 경우가 비일비재 하다. &lsquo;충격&rsquo;,&lsquo;경악&rsquo;이란 단어를 남발하는 기사를 본 적이 어디 하루 이틀인가. 오죽하면 <a href="http://hot.coroke.net/" target="_blank">충격 고로케</a>라는 사이트가 나왔을까 싶다.</p>

<p>비단 뉴스 뿐만 아니라 인터넷에는 자극적인 매체가 많다. 이러한 자극이 계속 반복되다보니 무뎌진 감각을 더욱 자극하기 위해서 더 자극적인 컨텐츠가 쏟아지고 있다. 사실보다는 얼마나 더 <strong>자극</strong>적이고 더 <strong>재미</strong>와 <strong>흥미</strong>를 유발하는지가 중요하게 된 인터넷 커뮤니티를 보면 남의 얘기만은 아니다. 국내에도 <strong>자극</strong>과 <strong>과시</strong>에 목숨걸면서 컨텐츠를 생산하다보니 <strong>개념</strong>은 쌈싸먹고 고소/고발 당하는게 일상사인 곳도 있지 않은가. 과연 이게 정상일까?</p>

<h2 id="현실">현실</h2>

<p>이 영화는 뉴스가 진실보다는 <strong>자극</strong>적인 요소에 집중하다가는 어떻게 되어가는지를 적나라하게 보여준다. 다만, 이들의 파멸을 보여주기 보다는 현재 진행형이라고 암시하는 듯 한 결말이 더 무섭기까지하다.</p>

<ul>
<li>추천

<ul>
<li>스릴러를 좋아하신다면</li>
<li>현실적인 스토리를 좋아하신다면</li>
</ul></li>
<li>비추천

<ul>
<li>미친놈들이 싫다면

<ul>
<li>하지만 이게 현실이라는게 슬프다</li>
</ul></li>
</ul></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>카카오톡 테마 소개 - Plain</title>
            <link>/2015/03/07/kakaotalk-theme-iphone/</link>
            <pubDate>Sat, 07 Mar 2015 19:58:32 +0000</pubDate>
            
            <guid>/2015/03/07/kakaotalk-theme-iphone/</guid>
            <description>우연히 오늘의 유머 사이트에서 카카오톡 테마를 보았는데 심플한 디자인을 좋아하는 내 맘에 들어서 블로그에도 그 내용을 올려본다. 깔끔한 선을 기반으로 한 디자인이기에 질리지 않고 사용 할 수 있고 소개글에 나온 것 처럼 아이폰하고 잘 어울린다.
출처:
색상도 4가지에 깔끔한 테마. 사실 나는 테마 변경을 잘 안하는 사람인데 보자마자 바로 변경했다. 매우 만족.
설정 방법은 사파리에서 Plain 테마 사이트에 접속해서 색상을 선택 후 다운로드 링크를 클릭하면 ktheme 파일 링크가 나오고 우측 상단에 &amp;lsquo;카카오톡으로 열기&amp;rsquo;를 선택하면 간단히 설치 된다.</description>
            <content type="html"><![CDATA[<p>우연히 <a href="http://todayhumor.com/" target="_blank">오늘의 유머</a> 사이트에서 <a href="http://todayhumor.com/?bestofbest_199793" target="_blank">카카오톡 테마</a>를 보았는데 심플한 디자인을 좋아하는 내 맘에 들어서 블로그에도 그 내용을 올려본다. 깔끔한 선을 기반으로 한 디자인이기에 질리지 않고 사용 할 수 있고 소개글에 나온 것 처럼 아이폰하고 잘 어울린다.</p>

<p><img src="/images/2015/03/Thumbnail_Share.png" alt="" />
출처:</p>

<p>색상도 4가지에 깔끔한 테마. 사실 나는 테마 변경을 잘 안하는 사람인데 보자마자 바로 변경했다. 매우 만족.</p>

<p>설정 방법은 사파리에서 <a href="http://stleam.com/plain/" target="_blank">Plain</a> 테마 사이트에 접속해서 색상을 선택 후 다운로드 링크를 클릭하면 ktheme 파일 링크가 나오고 우측 상단에 &lsquo;카카오톡으로 열기&rsquo;를 선택하면 간단히 설치 된다.</p>

<p>좋은 테마를 만들고 공유해주신 <a href="https://twitter.com/Stleamist" target="_blank">Stleam</a>님께 감사드린다.</p>
]]></content>
        </item>
        
        <item>
            <title>[영화] 슬로우 비디오</title>
            <link>/2015/02/22/movie-slow-video-2014/</link>
            <pubDate>Sun, 22 Feb 2015 09:41:56 +0000</pubDate>
            
            <guid>/2015/02/22/movie-slow-video-2014/</guid>
            <description>소개 전국민이 지켜본다! 믿고 보는 배우 차태현의 관심충만 해피무비! 남들이 못 보는 찰나의 순간까지 볼 수 있는 남자 여장부(차태현). 독특한 시력으로 놀림 받던 어린 시절을 뒤로 하고 뛰어난 순간포착 능력을 인정 받아 CCTV 관제센터 에이스로 떠오르게 된다. CCTV 너머 하루 종일 지켜보며 우리의 일상에 느닷없이 찾아오는 이 남자! 올 가을, 특별한 남자의 독.특.한 세상보기가 시작된다!  (출처: 다음 영화)
빰빠밤~ 영화 시작할 때 &amp;lsquo;20세기 폭스&amp;rsquo;의 로고가 등장해서 영화를 잘 못 선택한 줄 알았다.</description>
            <content type="html"><![CDATA[

<h2 id="소개">소개</h2>

<p><img src="/images/2015/02/slow-video.jpeg" alt="" /></p>

<pre><code>전국민이 지켜본다! 믿고 보는 배우 차태현의 관심충만 해피무비!

남들이 못 보는 찰나의 순간까지 볼 수 있는 남자 여장부(차태현).
독특한 시력으로 놀림 받던 어린 시절을 뒤로 하고 
뛰어난 순간포착 능력을 인정 받아 CCTV 관제센터 에이스로 떠오르게 된다.
CCTV 너머 하루 종일 지켜보며 우리의 일상에 느닷없이 찾아오는 이 남자!

올 가을, 특별한 남자의 독.특.한 세상보기가 시작된다!
</code></pre>

<p>(출처: <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=81815" target="_blank">다음 영화</a>)</p>

<h2 id="빰빠밤">빰빠밤~</h2>

<p>영화 시작할 때 &lsquo;20세기 폭스&rsquo;의 로고가 등장해서 영화를 잘 못 선택한 줄 알았다. 알고보니 제작/배급사가 &lsquo;20세기 폭스&rsquo;. 이제 국산 영화의 제작/배급도 조금씩 달라지나 보다.</p>

<h2 id="잔잔함">잔잔함</h2>

<p>이 영화는 <a href="http://movie.daum.net/movieperson/Summary.do?personId=112737" target="_blank">김영탁</a> 감독의 작품이다. 혹시 <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=57345" target="_blank">헬로우 고스트</a>라는 영화를 알고 있다면 대충 어떠한 스타일일지 감이 올 것이다. 차이점이라면 이 영화는 제목대로 &lsquo;슬로우&rsquo;를 강조하고 있기 때문에 더 잔잔한 영화라는 점이다.</p>

<p>사람에 따라서는 이러한 영화가 지루하거나 유치할 수 있다. 개인적으로 <a href="http://movie.daum.net/movieperson/Summary.do?personId=112737" target="_blank">김영탁</a>감독의 작품은 <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=40539&amp;t__nil_main_workList=workname" target="_blank">스윙 걸즈</a>의 <a href="http://movie.daum.net/movieperson/Summary.do?personId=1174" target="_blank">야구치 시노부</a> 감독의 작품들과 유사한 점이 많다고 생각한다. (영화 개봉당시에 쇼프로에 게스트로 나와서 &lsquo;잘 되어서 지루 한 영화를 찍어보고 싶다&rsquo;라는 계획을 말한 걸 보면 조금은 다를 지도 - <a href="http://media.daum.net/entertain/enews/newsview?newsid=20140925082902273" target="_blank">기사</a>)</p>

<p>잔잔하면서 소소한 웃음과 감동도 담고 있는 영화. 마음의 힐링을 해보고 싶다면 이런 영화도 한 편 보는 건 어떨까 싶다.</p>

<h2 id="기억에-남는-대사">기억에 남는 대사</h2>

<pre><code>꽃이 피어서가 아니라 네가 와서 봄이야
</code></pre>

<ul>
<li>추천

<ul>
<li>헬로우 고스트나 스윙 걸즈처럼 잔잔한 재미가 담긴 영화를 좋아하신다면</li>
<li>차태현을 좋아하신다면</li>
</ul></li>
<li>비추천

<ul>
<li>잔잔한 영화를 싫어하시는 분</li>
<li>헬로우 고스트가 지루하셨던 분</li>
</ul></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>[영화] 레고 무비</title>
            <link>/2015/02/22/movie-the-lego-movie-2014/</link>
            <pubDate>Sun, 22 Feb 2015 09:20:07 +0000</pubDate>
            
            <guid>/2015/02/22/movie-the-lego-movie-2014/</guid>
            <description>소개 세상의 모든 영웅들이 &#39;레고&#39;로 조립된다! 배트맨, 슈퍼맨, 원더우먼, 인어공주, 초록닌자, 1980몇년 우주인, 미켈란젤로, 미켈란젤로 닌자거북이, 2002 NBA 올스타 등등등 이들 마스터 빌더 사이에서 희망으로 선택된 평범한 미니피겨! 그의 작고 노란 손에 레고 세계의 운명이 달렸다!  (출처: 다음 영화)
LEGO! 레고를 영화화 했단 소식에 예전부터 보려고 했는데 계속 잊어버리고 이제서야 보게 되었다. 영화는 제목부터 명확하다. &amp;lsquo;레고&amp;rsquo;.
매뉴얼 이 영화에서는 매뉴얼적인 삶과 창의적인 삶에 대한 대립구도를 보여주고 있는데 주인공은 전형적인 매뉴얼 대로 살아가는 캐릭터이다.</description>
            <content type="html"><![CDATA[

<h2 id="소개">소개</h2>

<p><img src="/images/2015/02/----.jpeg" alt="" /></p>

<pre><code>세상의 모든 영웅들이 '레고'로 조립된다!

배트맨, 슈퍼맨, 원더우먼, 인어공주, 초록닌자,
1980몇년 우주인, 미켈란젤로, 미켈란젤로 닌자거북이, 2002 NBA 올스타 등등등
이들 마스터 빌더 사이에서 희망으로 선택된 평범한 미니피겨!
그의 작고 노란 손에 레고 세계의 운명이 달렸다!
</code></pre>

<p>(출처: <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=78296" target="_blank">다음 영화</a>)</p>

<h2 id="lego">LEGO!</h2>

<p>레고를 영화화 했단 소식에 예전부터 보려고 했는데 계속 잊어버리고 이제서야 보게 되었다. 영화는 제목부터 명확하다. <strong>&lsquo;레고&rsquo;</strong>.</p>

<h2 id="매뉴얼">매뉴얼</h2>

<p>이 영화에서는 매뉴얼적인 삶과 창의적인 삶에 대한 대립구도를 보여주고 있는데 주인공은 전형적인 매뉴얼 대로 살아가는 캐릭터이다. 그러한 주인공이 창의적인 캐릭터로 성장해가고  보잘 것 없어보이는 주인공의 생각도 하나의 창조적 가치를 지닐 수 있다는 교훈으로 영화는 끝난다. 영화 막바지 반전(?)요소도 있고&hellip;</p>

<p>어떻게 보면 복합적인 내용을 담은 것 같기도 하다. 내가 느낀 걸 정리하면 레고를 구매했을 때 동봉 된 매뉴얼 대로만 만드는 사람과 매뉴얼을 아예 무시하고 만드는 사람 둘 다 나름의 장점을 갖고 있기 때문에 어떤 것이 정답이라고 단정 할 수 없다는 것. 무한한 가능성과 정해진 규칙에서 오는 아름다움 모두 가치가 있다는 것. 이 정도로 정리 할 수 있겠다.</p>

<ul>
<li>추천

<ul>
<li>LEGO의 팬</li>
<li>숨겨진 다양한 패러디를 좋아하는 분</li>
<li>가족영화를 찾으시는 분</li>
</ul></li>
<li>비추천

<ul>
<li>전형적인 스토리를 싫어하시는 분</li>
</ul></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>[영화] 나를 찾아줘</title>
            <link>/2015/02/22/movie-gone-girl-2014/</link>
            <pubDate>Sun, 22 Feb 2015 09:07:47 +0000</pubDate>
            
            <guid>/2015/02/22/movie-gone-girl-2014/</guid>
            <description>소개 우리부부는 누구나 부러워하는 ‘완벽한 커플’이었다. 그날, 아내가 사라지기 전까지… 모두가 부러워하는 삶을 살아가는 완벽한 커플 닉&amp;amp;에이미. 결혼 5주년 기념일 아침, 에이미가 흔적도 없이 실종된다. 유년시절 어린이 동화시리즈 ‘어메이징 에이미’의 실제 여주인공이었던 유명인사 아내가 사라지자, 세상은 그녀의 실종사건으로 떠들썩해진다. 한편 경찰은, 에이미가 결혼기념일 선물로 숨겨뒀던 편지와 함께 곳곳에서 드러나는 단서들로 남편 닉을 유력한 용의자로 지목한다. 미디어들이 살인 용의자 닉의 일거수일투족을 보도하기 시작하고, 시간이 갈수록 세상의 관심이 그에게 더욱 집중된다. 과연 닉은 아내를 죽였을까?</description>
            <content type="html"><![CDATA[

<h2 id="소개">소개</h2>

<p><img src="/images/2015/02/-----.jpeg" alt="" /></p>

<pre><code>우리부부는 누구나 부러워하는 ‘완벽한 커플’이었다.
그날, 아내가 사라지기 전까지…

모두가 부러워하는 삶을 살아가는 완벽한 커플 닉&amp;에이미.
결혼 5주년 기념일 아침, 에이미가 흔적도 없이 실종된다.
유년시절 어린이 동화시리즈 ‘어메이징 에이미’의 실제 여주인공이었던
유명인사 아내가 사라지자, 세상은 그녀의 실종사건으로 떠들썩해진다.

한편 경찰은, 에이미가 결혼기념일 선물로 숨겨뒀던 편지와 함께
곳곳에서 드러나는 단서들로 남편 닉을 유력한 용의자로 지목한다.
미디어들이 살인 용의자 닉의 일거수일투족을 보도하기 시작하고,
시간이 갈수록 세상의 관심이 그에게 더욱 집중된다.

과연 닉은 아내를 죽였을까? 진실은 무엇일까?
</code></pre>

<p>(출처: <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=82344" target="_blank">다음 영화</a>)</p>

<h2 id="나를-찾아줘">나를 찾아줘</h2>

<p>제목이 &lsquo;나를 찾아줘&rsquo;라서 내 자신을 찾아가는 이야기로 생각하고 접근했다. 영어 원제는 &lsquo;Gone Girl&rsquo;. 사라진 그녀 정도? 제목을 이렇게 바꾼 이유는 모르겠지만 아무래도 반전 요소를 최대한 끌어 올리기 위해서 &lsquo;그녀가 사라졌다&rsquo;라는 부분을 감추고 내 자신을 찾는 부분에 대해서만 알린건 아닐까 싶다.</p>

<p>사실, 반전요소가 아니라고 볼 수도 있는데, 영화가 시작되고 얼마 되지 않아서 충분히 유추할 수 있는 내용이었다. 그래서 &lsquo;와~ 영화가 벌써 끝나네?&lsquo;하는 생각을 갖게 되었는데 영화는 이 시점부터 본격적으로 시작된다.</p>

<p>이미 아내의 죽음과 관련된 사실이 밝혀졌기 때문에 흥미롭지 못 할 수 있었으나 영화는 이야기를 계속 흥미롭게 이어가고 있었다. 끝났다고 생각한 이야기를 계속해서 잘 구성했기 때문에 남은 시간이 지루하지 않았다.</p>

<p>데이빗 핀처 감독의 여타 영화들 처럼 자극적이지 않은(다른 표현으로는 물 흐르듯 쉽게 흘러가는) 이야기도 충분히 흥미롭게 이끈 힘은 대단하다고 생각된다.</p>

<h2 id="다만">다만</h2>

<p>결말이 반전이라면 반전 일 수도 있다. 영화를 보는 내내 기대한 심리를 찝집하게 만들어버렸으니&hellip;</p>

<ul>
<li>추천

<ul>
<li>스릴러 영화를 좋아하시는 분</li>
<li>데이빗 핀처 감독의 스토리 텔링 방법을 좋아하는 분</li>
</ul></li>
<li>비추천

<ul>
<li>깔끔한 결말을 원하시는 분 (권선징악 기준)</li>
</ul></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>[영화] 응징자</title>
            <link>/2015/02/22/movie-punisher-2013/</link>
            <pubDate>Sun, 22 Feb 2015 08:54:09 +0000</pubDate>
            
            <guid>/2015/02/22/movie-punisher-2013/</guid>
            <description>소개 우리가 친구였던 적이 단 한번이라도 있었냐? 고등학교 동창인 준석(주상욱)과 창식(양동근)은 20년 뒤 우연히 재회한다. 단 하루도 잊을 수 없었던 친구를 만난 준석, 그리고 과거의 일은 까맣게 잊은 창식. 준석은 자신의 삶을 망가뜨린 창식이 행복하게 사는 모습을 보니 참아왔던 분노가 치밀고.. 20년 전 하지 못한 그날의 악행에 대한 응징을 시작한다. 우정 대신 증오만이 남은 두 친구의 재회, 그리고 가해자와 피해자를 넘나드는 쫓고 쫓기는 복수! 괴물이 되어버린 두 남자에겐 과연 무슨 일이 있었던 것일까?</description>
            <content type="html"><![CDATA[

<h2 id="소개">소개</h2>

<p><img src="/images/2015/02/----1.jpeg" alt="" /></p>

<pre><code>우리가 친구였던 적이 단 한번이라도 있었냐?

고등학교 동창인 준석(주상욱)과 창식(양동근)은 20년 뒤 우연히 재회한다. 
단 하루도 잊을 수 없었던 친구를 만난 준석, 그리고 과거의 일은 까맣게 잊은 창식. 
준석은 자신의 삶을 망가뜨린 창식이 행복하게 사는 모습을 보니
참아왔던 분노가 치밀고.. 
20년 전 하지 못한 그날의 악행에 대한 응징을 시작한다. 

우정 대신 증오만이 남은 두 친구의 재회, 
그리고 가해자와 피해자를 넘나드는 쫓고 쫓기는 복수! 
괴물이 되어버린 두 남자에겐 과연 무슨 일이 있었던 것일까?
</code></pre>

<p>(출처: <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=75289" target="_blank">다음 영화</a>)</p>

<h2 id="복수">복수?</h2>

<p>영화 제목이 응징자 이다. 당연히 복수에 대한 내용을 기대하게 된다. 그리고 제목과 줄거리에서 어느 정도 짐작이 되는 것 처럼 학창시절의 폭력이 현재의 폭력으로 돌아온다는 내용이 주를 이룬다. 다만, 그 복수의 방법이 조금 답답하기도 해서 이걸 응징이라고 표현해야할지 애매한 부분이 있긴하다.</p>

<p>분명 복수를 하긴 했지만 찝집한 기분이 남는다고 해야할까? 어쩌면 감독이 주고자 했던 메시지 일지도 모른다. 폭력에 대한 응징을 이렇게 하면 안된다라고 말이다. 하지만, 영화는 생각보다 잔인한 장면 묘사도 등장하고 처절하기까지 한데 꼭 저렇게 스토리를 이어 갔어야만 했을까 하는 생각도 든다. 등장하는 캐릭터에 집중하다보면 굳이 등장해서 비극속에 같이 휩쓸려야 했을까 싶다.</p>

<h2 id="그래서">그래서</h2>

<ul>
<li>추천

<ul>
<li>양동근의 연기력을 좋아하는 분</li>
<li>복수의 구도를 기대하신 분</li>
</ul></li>
<li>비추천

<ul>
<li>깔끔한 결말을 좋아하는 분</li>
<li>잔인한 장면을 싫어하시는 분

<ul>
<li>물론, 요즘 영화들의 잔혹한 장면묘사 치고는 무난할 수도 있다.</li>
</ul></li>
</ul></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>[영화] 툼스톤</title>
            <link>/2015/02/22/movie-walking-among-the-tombstones/</link>
            <pubDate>Sun, 22 Feb 2015 08:42:20 +0000</pubDate>
            
            <guid>/2015/02/22/movie-walking-among-the-tombstones/</guid>
            <description>Taken? 영화 포스터에는 &amp;lsquo;추격 액션 스릴러&amp;rsquo;라고 되어있다. 물론 이 말이 틀린 말은 아니지만 &amp;lsquo;테이큰&amp;rsquo;을 기대하고 이 영화를 보게 된다면 실망 할 수 있을 것 같다. 개인 적으로는 스릴러에 더 초점이 맞추어져 있는 것 같으니깐.
소개 오늘, 새 의뢰인이 찾아왔다! 과거의 실수로 인해 가족도 없이 혼자 지내는 전직 형사 맷(리암 니슨)에게 어느 날, 한 남자가 찾아왔다. 살해당한 아내의 복수해달라는 의뢰를 거절하려던 맷은 납치범이 제시한 금액의 40%를 보내자 아내의 신체 중 40%만 돌려보냈다는 잔혹한 범행 행각을 전해 듣고 의뢰를 수락하고 만다.</description>
            <content type="html"><![CDATA[

<h2 id="taken">Taken?</h2>

<p>영화 포스터에는 &lsquo;추격 액션 스릴러&rsquo;라고 되어있다. 물론 이 말이 틀린 말은 아니지만 &lsquo;테이큰&rsquo;을 기대하고 이 영화를 보게 된다면 실망 할 수 있을 것 같다. 개인 적으로는 스릴러에 더 초점이 맞추어져 있는 것 같으니깐.</p>

<h2 id="소개">소개</h2>

<p><img src="/images/2015/02/---.jpeg" alt="" /></p>

<pre><code>오늘, 새 의뢰인이 찾아왔다!

과거의 실수로 인해 가족도 없이 혼자 지내는 전직 형사 맷(리암 니슨)에게
어느 날, 한 남자가 찾아왔다.
살해당한 아내의 복수해달라는 의뢰를 거절하려던 맷은
납치범이 제시한 금액의 40%를 보내자 아내의 신체 중 40%만 돌려보냈다는
잔혹한 범행 행각을 전해 듣고 의뢰를 수락하고 만다.

사건을 조사하던 맷은 3개월 전 발생한 유사 범죄를 알게 되고
살해된 시신이 버려졌던 무덤 근처에서 수상한 남자 루건과 마주친다
루건에게 의도적으로 접근한 맷은
연쇄납치살인사건의 희생자들의 긴밀한 공통점을 발견하고
사건을 둘러싼 충격적인 음모와 진실에 점차 가까워 지는데…

빈틈없는 연쇄살인마를 잡기 위한 치밀한 추격이 시작된다!
</code></pre>

<p>(출처: <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=77739" target="_blank">다음 영화</a>)</p>

<h2 id="인과관계">인과관계</h2>

<p>모든 사건에는 인과관계가 있기 마련이고 이 영화에서도 그 부분을 전체 이야기에 녹여내고 있다. 다만, 그 구성이 조금은 느슨하다고 할지 아니면 단편적이라고 해야 할지 매끄럽지 못 한 부분은 아쉽다. 특히, 조력자인 등장인물에 대한 배경설명이 부족하고 왜 그 타이밍에 등장해야하는지도 잘 모르겠다. 그리고, 생각보다 잔혹한 장면과 묘사가 나오기에 앞서 말한대로 &lsquo;테이큰&rsquo;을 기대하고 봤다면 더 마음에 안 들지도 모르겠다.</p>

<h2 id="그래서">그래서</h2>

<p>전체적으로 나쁘지 않지만 아쉬운 구성으로 인해 범작이 되버린 영화가 아닌가 싶다.</p>

<ul>
<li>추천

<ul>
<li>추적 스릴러를 좋아하는 분</li>
<li>리암닐슨을 좋아하는 분</li>
</ul></li>
<li>비추천

<ul>
<li>테이큰을 기대하신 분</li>
</ul></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>ONBOOT=no 옵션 적용이 안되는 증상</title>
            <link>/2015/02/11/onboot-no-options-does-not-work/</link>
            <pubDate>Wed, 11 Feb 2015 10:14:36 +0000</pubDate>
            
            <guid>/2015/02/11/onboot-no-options-does-not-work/</guid>
            <description>RHEL 기반의 배포판은 /etc/sysconfig/network-scripts/ 아래에 설정파일을 두고 네트워크를 관리한다. (NetworkManager 사용을 하지 않을 경우) 이 때 설정파일 옵션 중에 ONBOOT 옵션이 있는데 해당 옵션이 제대로 적용이 되지 않는 증상을 보이는 경우가 있어서 이에 대해서 간단히 소개한다.
시스템 환경  OS: RHEL 6 64bit NIC: Broadcom Corporation NetXtreme II BCM57810 10G Driver: bnx2x 1.78.80 설정파일
DEVICE=eth1 BOOTPROTO=static ONBOOT=no  기타: NetworkManager를 사용하지 않음
  증상 eth1에 ONBOOT=no로 설정하였음에도 불구하고 재부팅을 하면 eth1 인터페이스가 up 상태로 올라오는 증상이 보임.</description>
            <content type="html"><![CDATA[

<p>RHEL 기반의 배포판은 /etc/sysconfig/network-scripts/ 아래에 설정파일을 두고 네트워크를 관리한다. (NetworkManager 사용을 하지 않을 경우) 이 때 설정파일 옵션 중에 ONBOOT 옵션이 있는데 해당 옵션이 제대로 적용이 되지 않는 증상을 보이는 경우가 있어서 이에 대해서 간단히 소개한다.</p>

<h2 id="시스템-환경">시스템 환경</h2>

<ul>
<li>OS: RHEL 6 64bit</li>
<li>NIC: Broadcom Corporation NetXtreme II BCM57810 10G</li>
<li>Driver: bnx2x 1.78.80</li>

<li><p>설정파일</p>

<pre><code>DEVICE=eth1
BOOTPROTO=static
ONBOOT=no
</code></pre></li>

<li><p>기타: NetworkManager를 사용하지 않음</p></li>
</ul>

<h2 id="증상">증상</h2>

<p>eth1에 ONBOOT=no로 설정하였음에도 불구하고 재부팅을 하면 eth1 인터페이스가 up 상태로 올라오는 증상이 보임.</p>

<h2 id="해결방법">해결방법</h2>

<p>설정파일 옵션 중에 HOTPLUG라는 옵션이 있다. 레드햇 매뉴얼에 따르면</p>

<pre><code>HOTPLUG=answer
  where answer is one of the following:
    yes — This device should be activated when it is hot-plugged (this is the default option).
    no — This device should not be activated when it is hot-plugged.

The HOTPLUG=no option can be used to prevent a channel bonding interface from being activated when a bonding kernel module is loaded.
</code></pre>

<p>hot-plug로 인식될 때 장비를 활성화 할지 여부를 지정하는 옵션이다. 여러 환경에서 재현 테스트를 해보면 좋겠지만 여의치 않아서 몇 가지 테스트만 해 본 결과 PCIe NIC 카드 때문으로 보인다.</p>

<p>즉, 시스템 환경의 BCM57810 NIC카드는 서버의 메인보드에 달린 NIC이 아니라 별도의 10G NIC을 장착하였기 때문에 부팅 때 bnx2x 드라이버 모듈이 로딩 되면서 해당 NIC을 hot-plug 장치로 처리하는 것으로 보인다.</p>

<h2 id="결론">결론</h2>

<p>아무튼, 설정파일에 해당 네트워크 인터페이스가 부팅 때 활성화 되지 않기를 원한다면 ONBOOT와 HOTPLUG 옵션 모두 설정해 주는 것이 좋다.</p>

<pre><code>DEVICE=eth1
BOOTPROTO=static
HOTPLUG=no
ONBOOT=no
</code></pre>

<h4 id="끝">끝</h4>
]]></content>
        </item>
        
        <item>
            <title>glibc 취약점 GHOST (CVE-2015-0235)</title>
            <link>/2015/01/29/glibc-ghost-vulnerability/</link>
            <pubDate>Thu, 29 Jan 2015 19:54:32 +0000</pubDate>
            
            <guid>/2015/01/29/glibc-ghost-vulnerability/</guid>
            <description>경고(?)  이 글은 GHOST에 대한 분석의 글이 아닙니다. 보안에 기웃거렸을 뿐 보안이 뭔지 모르는 저의 개인적인 소감입니다.   GHOST? heartbleed, shellshock, POODLE &amp;hellip; 작년에 이름을 떨쳤던 보안 취약점이다. 그리고 이번엔 GHOST란다. 며칠 전에 접했던 취약점인며 국내 뉴스기사도 있다. GHOST는 glibc의 취약점인데 glibc가 갖는 범용성 때문에 꽤나 심각하게 보이는 취약점이다.
glibc를 잘 모르는 분들을 위해 추가 설명을 하자면 glibc는 GNU C라이브러리 (GNU libc)를 의미하며 C라이브러리라는 것은 C언어의 기본 중의 기본이 되는 라이브러리를 의미한다.</description>
            <content type="html"><![CDATA[

<ul>
<li><strong>경고(?)</strong>

<ul>
<li>이 글은 GHOST에 대한 분석의 글이 아닙니다.</li>
<li>보안에 기웃거렸을 뿐 보안이 뭔지 모르는 저의 개인적인 소감입니다.</li>
</ul></li>
</ul>

<h1 id="ghost">GHOST?</h1>

<p>heartbleed, shellshock, POODLE &hellip; 작년에 이름을 떨쳤던 보안 취약점이다. 그리고 이번엔 GHOST란다. 며칠 전에 접했던 취약점인며 <a href="http://www.ddaily.co.kr/news/article.html?no=126823" target="_blank">국내 뉴스기사</a>도 있다. GHOST는 glibc의 취약점인데 glibc가 갖는 범용성 때문에 꽤나 심각하게 보이는 취약점이다.</p>

<p>glibc를 잘 모르는 분들을 위해 추가 설명을 하자면 glibc는 GNU C라이브러리 (GNU libc)를 의미하며 C라이브러리라는 것은 C언어의 기본 중의 기본이 되는 라이브러리를 의미한다. 다시말해 C언어로 작성 된 프로그램 중에 glibc에 의존하지 않는 프로그램이 없다는 뜻이기도 하다. (Linux는 C언어 기반의 운영체제이다)</p>

<h2 id="취약점-설명">취약점 설명</h2>

<p>아래의 링크는 취약점에 대한 소개와 이에 대한 문서 들이다.</p>

<ul>
<li><a href="http://www.openwall.com/lists/oss-security/2015/01/27/9" target="_blank">Qualys Security Advisory</a> (영문)</li>
<li><a href="https://access.redhat.com/articles/1332213" target="_blank">Redhat Article</a> (영문)</li>
<li><a href="http://www.dailysecu.com/news_view.php?article_id=8689" target="_blank">뉴스기사</a> (국문, 분석 보고서를 받을 수 있는 링크도 있다)</li>
</ul>

<p>상세한 내용은 위 링크들에 잘 나와있고 요약하면 아래와 같습니다.</p>

<ul>
<li>2000년도에 포함 된 __nss_hostname_digits_dots() 코드에 취약점이 존재</li>
<li>이를 호출하는 시스템 콜 중 gethostbyname()이 대표적</li>
<li>호스트 주소와 이름을 저장하기 위한 버퍼 계산에서 포인터 크기만큼을 빼먹는 실수가 있음</li>
<li>따라서, 32bit 머신은 4바이트 64bit 머신은 8바이트 만큼 크기 오차 발생</li>
<li>계산 된 값으로 strcpy()로 복사를 하는데 복사 할 때는 제대로 된 크기만큼 복사해서 포인터 크기만큼의 취약점이 생겨남</li>
<li>Qualys가 소개한 내용처럼 몇몇 어플리케이션에서 해당 취약점을 악용 할 수 있음</li>
<li>크기 계산 오류이기 때문에 2013년에 패치가 있었으며 이 때는 보안버그로 취급하지 않고 일반적인 버그로 취급하여 패치했다</li>
<li>배포 주기가 긴 리눅스 배포판 들은 여전히 취약한 버전을 사용한다</li>
</ul>

<h2 id="제-3의-heartbleed">제 3의 Heartbleed?</h2>

<p>뉴스기사에서는 제 3의 Heartbleed 관측도 있다는 제목을 뽑았다.</p>

<p>먼저, 소스코드에서 취약한 부분을 동작시키기 위해서는 입력 값에 아래와 같은 전제 조건이 수반된다.</p>

<ol>
<li>첫 번째 바이트는 숫자</li>
<li>마지막 바이트는 점(.)이면 안된다</li>
<li>숫자와 점 그리고 NULL로만 이루어져야 한다</li>
<li>버퍼의 크기가 커야 한다 (gethostbyname() 처럼)</li>
<li>inet_aton()에서 정상적으로 파싱이 되어야 함</li>
</ol>

<p>그런데, 개인적으로 이게 정말 Heartbleed 급이라고 볼 수 있을까라는 의문이 든다. 내가 드는 생각을 요약하면 아래와 같다.</p>

<ul>
<li>예외처리 없이 위 전제조건을 정상적인 입력 값으로 받아서 gethostbyname()을 호출하는 프로그램이 많을까?</li>
<li>2000년에 추가 된 코드가 아직까지 문제를 일으킨 사례가 있었나?

<ul>
<li>물론, 이를 잘 써먹고 있던 공격자가 있을 수도 있다.</li>
</ul></li>
<li>공격자에게 4(또는 8)바이트의 크기가 협소한 건 아닐까?</li>
<li>IPv6도 지원하지 않는 gethostbyname()보단 더 간편하고 좋은 getaddrinfo()를 요즘엔 많이 쓰지 않나?</li>
</ul>

<h2 id="제-점수는요">제 점수는요</h2>

<p>물론, 이 보안 취약점을 시스템 관리자가 무시 할 내용은 아니다. 대상이 광범위하고 세상에 많은 능력자가 있기 때문에 저걸 쉽게 사용하는 Exploit을 만들어 낼 지도 모른다.</p>

<p>하지만, 지금 상황에서는 Heartbleed 급이라는 얘기는 내게는 설레발처럼 느껴진다. 내가 공격자라도 이걸 이용하기 보다는 웹에서 상대적으로 쉽게(?) 만날 수 있는 여러 Injection 종류의 공격을 하겠다. (서버 어플리케이션은 패치 되어도 서비스 프로그램 버그는 널려있으니깐)</p>

<h4 id="끝">끝</h4>
]]></content>
        </item>
        
        <item>
            <title>[영화] 로봇G</title>
            <link>/2015/01/21/movie-robo-g/</link>
            <pubDate>Wed, 21 Jan 2015 14:12:33 +0000</pubDate>
            
            <guid>/2015/01/21/movie-robo-g/</guid>
            <description>야구치 시노부 &amp;lsquo;워터보이즈&amp;rsquo;, &amp;lsquo;스윙걸즈&amp;rsquo;로 유명한 감독 야구치 시노부의 2012년 영화이다. 앞의 영화들을 워낙 재밌게 봤기 때문에 로봇G를 케이블TV에서 방영해 준다고 했을 때 바로 챙겨서 보게 되었다.
로봇G (Robo-G) 이미지출처 - Daum Movie
로봇G의 이야기는 로봇박람회에 로봇을 출품하게 된 가전제품회사 직원 세 명이서 로봇을 만들 능력이 없기에 할아버지를 로봇 속에 들어가도록 하면서(이 또한 면접으로 뽑은 우수(?)인재) 벌어지는 이야기를 다루고 있다. 야구치 시노부의 여타 다른 영화들 처럼 기복없이 흐르는 이야기 속의 자잘한 재미와 감정이 묻어나는 영화이다.</description>
            <content type="html"><![CDATA[

<h2 id="야구치-시노부">야구치 시노부</h2>

<p>&lsquo;워터보이즈&rsquo;, &lsquo;스윙걸즈&rsquo;로 유명한 감독 야구치 시노부의 2012년 영화이다. 앞의 영화들을 워낙 재밌게 봤기 때문에 로봇G를 케이블TV에서 방영해 준다고 했을 때 바로 챙겨서 보게 되었다.</p>

<h2 id="로봇g-robo-g">로봇G (Robo-G)</h2>

<p><img src="/images/2015/01/t1-daumcdn-3.jpg" alt="" /></p>

<p><a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=69129" target="_blank">이미지출처 - Daum Movie</a></p>

<p>로봇G의 이야기는 로봇박람회에 로봇을 출품하게 된 가전제품회사 직원 세 명이서 로봇을 만들 능력이 없기에 할아버지를 로봇 속에 들어가도록 하면서(이 또한 면접으로 뽑은 우수(?)인재) 벌어지는 이야기를 다루고 있다. 야구치 시노부의 여타 다른 영화들 처럼 기복없이 흐르는 이야기 속의 자잘한 재미와 감정이 묻어나는 영화이다.</p>

<p>영화 리뷰사이트에서는 &lsquo;인간과 로봇과의 관계&rsquo;라는 주제를 걸고 리뷰를 하고 있던데 내가 느낀 감정은 조금은 다른 것 같다. 나이를 먹고 이제는 사람과의 관계가 <strong>희미해진</strong> 할아버지가 다시금 사람과의 감정을 주고 받는 이야기. <strong>꿈</strong>을 향해 달려가는 소녀의 순수함과 그 순수함이 만들어낸 <strong>이해</strong>의 감정. 어쩔 수 없이 벌인 일이지만 그냥 무능하게 주저앉기는 싫은 이들의 <strong>노력</strong>. 사소한 이야기들이 서로 얽히면서 영화를 만들어간 느낌이었다.</p>

<h2 id="꿈-많은-소녀">꿈 많은 소녀</h2>

<p>이 영화에서 &lsquo;로보트 오타쿠&rsquo; 소녀가 등장한다. 대학교 4학년생, 하지만 그녀가 보이는 모습은 &lsquo;오타쿠&rsquo;, &lsquo;공순이&rsquo;라는 형태 뿐만 아니라 &lsquo;꿈&rsquo;을 갖고 있는 사람. 그리고, 이 소녀를 연기한 <strong><a href="http://movie.daum.net/movieperson/Summary.do?personId=100610" target="_blank">요시타카 유리코</a></strong>라는 배우는 매우 매력이 넘쳤다. (처음에는 저런 역할 하기에는 너무 예쁘장한 것 아닌가 했으나 4차원적인 매력발산 연기는 충분히 캐릭터에 어울렸다. 시쳇말로 매력 터진다.)</p>

<p><img src="/images/2015/01/t1-daumcdn-4.jpg" alt="" /></p>

<p><a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=69129" target="_blank">이미지출처 - Daum Movie</a></p>

<p>로봇을 사랑하기에 이 영화의 주인공 로봇 &lsquo;뉴 시오카제&rsquo;에 푹 빠진 이 소녀는 단순한 사랑이 아닌 자신의 열정을 녹여낸 사랑을 선 보인다. 즉, 야구치 시노부 감독 영화의 주인공 다운 매력을 갖춘 캐릭터.</p>

<p>소소하면서 억지스럽지 않은 (물론 우연이 좀 많아서 개연성은 떨어지는 편) 재미와 감정을 느껴보고 싶다면 추천한다. 이 영화를 보고나니 <a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=85989" target="_blank">우드잡</a>도 감상 예정 목록에 포함시키게 되었다.</p>

<p><strong>[공식 트레일러]</strong>
<iframe width="560" height="315" src="//www.youtube.com/embed/HrP7foRMVL4" frameborder="0" allowfullscreen></iframe></p>
]]></content>
        </item>
        
        <item>
            <title>[영화] 아메리칸 갱스터</title>
            <link>/2015/01/21/movie-american-gangster/</link>
            <pubDate>Wed, 21 Jan 2015 13:39:42 +0000</pubDate>
            
            <guid>/2015/01/21/movie-american-gangster/</guid>
            <description>리들리 스콧, 덴젤 워싱턴, 러셀크로우 &amp;lsquo;에일리언&amp;rsquo;으로 유명한 감독 리들리 스콧의 실화를 바탕으로 한 영화. 역시나 감독과 배우를 보고 선택한 영화이다. 실화를 바탕으로 한 영화이다 보니 1960년대 말에서 1970년대의 미국의 모습을 간접적으로 볼 수 있다.
아메리칸 갱스터 이미지 출처 - Daum Movie
영화에서도 언급되지만 당시에는 &amp;lsquo;흑인&amp;rsquo;이 갱스터의 거물이 되는 경우는 흔한 일은 아닌가 보다. 하긴, 대부라는 영화를 보아 짐작하면 이탈리안 마피아가 그 시절 암흑가를 지배했었을테니..
겉으로 보이는 모습은 한심하지만 실제 성격과 사상이 올곧은 형사 리치와 겉으로 보이는 모습은 소위 &amp;lsquo;젠틀&amp;rsquo;이라는 단어가 어울리는 사람이지만 실제로는 암흑가의 실세이자 마약왕인 프랭크 사이에 벌어지는 이야기가 주를 이룬다.</description>
            <content type="html"><![CDATA[

<h3 id="리들리-스콧-덴젤-워싱턴-러셀크로우">리들리 스콧, 덴젤 워싱턴, 러셀크로우</h3>

<p>&lsquo;에일리언&rsquo;으로 유명한 감독 리들리 스콧의 실화를 바탕으로 한 영화. 역시나 감독과 배우를 보고 선택한 영화이다. 실화를 바탕으로 한 영화이다 보니 1960년대 말에서 1970년대의 미국의 모습을 간접적으로 볼 수 있다.</p>

<h2 id="아메리칸-갱스터">아메리칸 갱스터</h2>

<p><img src="/images/2015/01/t1-daumcdn-2.jpg" alt="" /></p>

<p><a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=44004" target="_blank">이미지 출처 - Daum Movie</a></p>

<p>영화에서도 언급되지만 당시에는 &lsquo;흑인&rsquo;이 갱스터의 거물이 되는 경우는 흔한 일은 아닌가 보다. 하긴, 대부라는 영화를 보아 짐작하면 이탈리안 마피아가 그 시절 암흑가를 지배했었을테니..</p>

<p>겉으로 보이는 모습은 한심하지만 실제 성격과 사상이 올곧은 형사 <strong>리치</strong>와 겉으로 보이는 모습은 소위 &lsquo;젠틀&rsquo;이라는 단어가 어울리는 사람이지만 실제로는 암흑가의 실세이자 마약왕인 <strong>프랭크</strong> 사이에 벌어지는 이야기가 주를 이룬다.</p>

<p><strong>프랭크</strong>는 자신이 옳다고 생각하고 자신이 계획하는 것을 진리라 생각하며 (물론, 존경하는 범피의 영향도 있겠지만) 묵묵히 나쁜일을 수행한다. 워낙 진지하게 그 일을 수행하기 때문에 흔한 사악함을 넘어서는 악이 보이기도하다. 그리고 겉모습과 달리 착한 형사 <strong>리치</strong> 또한 옳고 그름에 대한 가치관 대로 움직인다.</p>

<p>그러다보니 영화를 보는 내내 악당인 프랭크와 형사인 리치보다 <strong>부패한 경찰</strong>들이 가장 악하게 보이는 것이 특이한 점.</p>

<p>결국 망해버린 프랭크는 <strong>부패한 경찰</strong>에게 합법적(?)으로 복수를 하면서 영화는 끝을 향하고 마지막 추가 코멘터리는 범죄자와 형사의 인연에 대한 부연설명으로 막을 내린다.</p>

<p>전체적인 스토리는 튀는 부분 없이 묵묵히 흘러가지만 두 주연 배우가 캐릭터를 훌륭히 반영해서 지루하지도 않았다. 그러나, 뭔가 자극적인 사건/사고를 기대한다면 조금 실망 할 수 있는 영화.</p>
]]></content>
        </item>
        
        <item>
            <title>RHEL6에서 Python 2.7/3.4 사용하기</title>
            <link>/2015/01/21/python27-34-on-rhel-centos/</link>
            <pubDate>Wed, 21 Jan 2015 06:27:21 +0000</pubDate>
            
            <guid>/2015/01/21/python27-34-on-rhel-centos/</guid>
            <description>본 문서는 RHEL6에서 Python 2.7과 3.4를 사용하기 위해 추천하는 방법을 안내하고 있다. 비단, RHEL6 뿐만 아니라 CentOS6와 RHEL5, CentOS5에도 적용이 가능하다.
Python 2.6 RHEL6에는 기본적으로 python 2.6 버전이 탑재되어 있다. 그리고 OS를 구성하는 많은 패키지들이 이 버전에 기초하여 의존성을 갖고 있다. 보통 이런 환경에서 python 2.7을 사용하기 위해서 별도로 python 2.7을 다운로드 받아 설치한다. 다만, 그냥 설치하다보니 OS 기본 python의 실행경로나 라이브러리 참조 경로를 변경해버려서 OS가 기본적으로 (python 2.6에 의존하여) 제공하는 많은 서비스 패키지가 제대로 동작하지 않는 문제가 발생한다.</description>
            <content type="html"><![CDATA[

<p>본 문서는 RHEL6에서 Python 2.7과 3.4를 사용하기 위해 추천하는 방법을 안내하고 있다. 비단, RHEL6 뿐만 아니라 CentOS6와 RHEL5, CentOS5에도 적용이 가능하다.</p>

<h2 id="python-2-6">Python 2.6</h2>

<p>RHEL6에는 기본적으로 python 2.6 버전이 탑재되어 있다. 그리고 OS를 구성하는 많은 패키지들이 이 버전에 기초하여 의존성을 갖고 있다. 보통 이런 환경에서 python 2.7을 사용하기 위해서 별도로 python 2.7을 다운로드 받아 설치한다. 다만, 그냥 설치하다보니 OS 기본 python의 실행경로나 라이브러리 참조 경로를 변경해버려서 OS가 기본적으로 (python 2.6에 의존하여) 제공하는 많은 서비스 패키지가 제대로 동작하지 않는 문제가 발생한다.</p>

<h2 id="제안하는-방법">제안하는 방법</h2>

<ul>
<li>OS가 기본적으로 가지고 있는 python은 그대로 둔다.

<ul>
<li>절대 덮어씌우거나 심볼릭 링크를 고치거나 하지 않는다.</li>
<li>python은 별도로 설치하는 옵션을 이미 가지고 있다.</li>
</ul></li>
<li>python 2.<sup>7</sup>&frasl;<sub>3</sub>.4 환경으로 프로그램을 작성하거나 구동 할 때 virtualenv를 활용한다.</li>
</ul>

<h2 id="python-설치">Python 설치</h2>

<p>자, 이제 python 2.7.<sup>9</sup>&frasl;<sub>3</sub>.4.2를 설치해 보자. (이 문서 작성기준 최신 버전)</p>

<h3 id="사전준비">사전준비</h3>

<ul>
<li>빌드를 위해서 개발 관련 패키지가 모두 설치되어 있어야 한다</li>

<li><p>추가로 자주 사용되는 라이브러리의 개발 패키지도 필요하다</p>

<pre><code>$ yum -y groupinstall 'Development tools'
$ yum -y install openssl-devel bzip2-devel sqlite-devel zlib-devel
</code></pre></li>
</ul>

<h3 id="패키지-다운로드">패키지 다운로드</h3>

<ul>
<li><p>python 패키지를 다운로드 받는다</p>

<ul>
<li>xz 파일을 다루기 위해서는 &lsquo;xz-libs&rsquo;를 설치한다</li>

<li><p>tgz(tar+gzip)은 보통 기본 설치에 포함된다</p>

<pre><code>[xz 파일의 경우]
# 2.7.9
$ wget https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tar.xz
$ tar Jxvf Python-2.7.9.tar.xz
# 3.4.2
$ https://www.python.org/ftp/python/3.4.2/Python-3.4.2.tar.xz
$ tar Jxvf Python-3.4.2.tar.xz

[tgz 파일의 경우]
# 2.7.9
$ wget https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz
$ tar zxvf Python-2.7.9.tgz
# 3.4.2
$ wget https://www.python.org/ftp/python/3.4.2/Python-3.4.2.tgz
$ tar zxvf Python-3.4.2.tgz
</code></pre></li>
</ul></li>

<li><p>압축을 해제하면 디렉토리가 생생되고 해당 디렉토리에 들어가서 설정을 한다</p>

<ul>
<li>반드시 /usr/local 일 필요는 없으니 원하는 경로로 사용한다</li>

<li><p>UTF-32 지원 등 부가적인 옵션은 기호에 따라 추가한다</p>

<pre><code># 2.7.9
$ cd Python-2.7.9/
$ ./configure --prefix=/usr/local --enable-shared
# 3.4.2
$ cd Python-3.4.2/
$ ./configure --prefix=/usr/local --enable-shared
</code></pre></li>
</ul></li>
</ul>

<h3 id="빌드-그리고-설치">빌드 그리고 설치</h3>

<ul>
<li><p>설정 된 패키지를 빌드하고 설치한다</p>

<ul>
<li><p>반드시 <strong>altinstall</strong>로 설치한다</p>

<pre><code>$ make &amp;&amp; make altinstall

멀티코어 프로세스를 사용한다면 -j 옵션으로 동시작업 개수를 지정해서 컴파일 시간을 단축 할 수 있다. (아래는 4개로 지정)
$ make -j 4 &amp;&amp; make install
</code></pre></li>
</ul></li>
</ul>

<h3 id="설치-확인">설치 확인</h3>

<p>설치가 완료되면 아래 경로에 python2.<sup>7</sup>&frasl;<sub>3</sub>.4 바이너리가 존재 한다</p>

<pre><code>$ ls -l /usr/local/bin/python*
lrwxrwxrwx 1 root root     7 Jan 21 13:28 /usr/local/bin/python -&gt; python2
lrwxrwxrwx 1 root root     9 Jan 21 13:28 /usr/local/bin/python2 -&gt; python2.7
-rwxr-xr-x 1 root root  9792 Jan 21 14:33 /usr/local/bin/python2.7
-rwxr-xr-x 1 root root  1687 Jan 21 14:34 /usr/local/bin/python2.7-config
lrwxrwxrwx 1 root root    16 Jan 21 13:28 /usr/local/bin/python2-config -&gt; python2.7-config
-rwxr-xr-x 2 root root 12650 Jan 21 14:40 /usr/local/bin/python3.4
-rwxr-xr-x 2 root root 12650 Jan 21 14:40 /usr/local/bin/python3.4m
-rwxr-xr-x 1 root root  3011 Jan 21 14:40 /usr/local/bin/python3.4m-config
lrwxrwxrwx 1 root root    14 Jan 21 13:28 /usr/local/bin/python-config -&gt; python2-config

</code></pre>

<p>Shared Library로 설치했기 때문에 /etc/ld.so.conf.d/에 아래와 같은 설정을 추가해 주고 적용한다</p>

<pre><code>$ cat /etc/ld.so.conf.d/python.conf
/usr/local/lib
$ ldconfig -v | grep libpython
	libpython2.7.so.1.0 -&gt; libpython2.7.so.1.0
	libpython3.so -&gt; libpython3.so
	libpython3.4m.so.1.0 -&gt; libpython3.4m.so.1.0
	libpython2.6.so.1.0 -&gt; libpython2.6.so.1.0
</code></pre>

<hr />

<h3 id="만약에">만약에&hellip;</h3>

<p><strong>Q.</strong> python 명령으로 python2.7이 실행되도록 하고 싶습니다.</p>

<p><strong>A.</strong> python2.7을 python으로 실행하기 위해서는 /usr/local/bin(또는 설치한 경로의 bin 디렉토리)을 PATH의 <strong>맨 앞에</strong> 추가해 주면 된다. 하지만 임의로 PATH를 고치는 것 보단 가급적이면 python2.7로 사용하는 습관이 좋다고 생각한다. 이유는 이 다음 절에서 설명하겠다. (특히 /usr/bin/python에 수정을 가하거나 링크 거는 것은 권장하지 않는다)</p>

<pre><code>$ export PATH=&quot;/usr/local/bin:$PATH&quot;
쉘에서 직접 실행하거나 .bashrc에 포함 시킨다
</code></pre>

<p>위에서 설정한 PATH 우선 순위에 따라서 python만 실행해도 python 2.7.9 버전이 실행된다.</p>

<pre><code>$ python
Python 2.7.9 (default, Jan 21 2015, 13:27:03)
[GCC 4.4.6 20120305 (Red Hat 4.4.6-4)] on linux2
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt;
</code></pre>

<hr />

<h3 id="유의사항-이렇게-하지-말자">유의사항 (이렇게 하지 말자)</h3>

<p>설치하고나서 PATH를 고치기 보다는 /usr/bin/python을 새로 설치한 python 바이너리로 심볼릭링크를 걸어서 (또는 덮어씌워서) 사용하는 경우가 많은데 이렇게 하면 RHEL/CentOS에 있는 yum을 비롯한 많은 OS 기본 python으로 실행되는 프로그램이 python 2.7.9에서 package를 찾기 때문에 오류가 발생한다. 아래는 대표적인 경우 중 하나인 yum의 오류 상황이다.</p>

<p>만약 /usr/bin/python을 /usr/local/bin/python (2.7.9)으로 대체하거나 링크를 걸었다면</p>

<pre><code>$ ls -l /usr/bin/python
lrwxrwxrwx 1 root root 7 Jan 21 13:28 /usr/bin/python -&gt; /usr/local/bin/python

$ head -n 3 /usr/bin/yum
#!/usr/bin/python
import sys
try:
</code></pre>

<p>yum 프로그램은 /usr/bin/python을 찾기 때문에 아래와 같은 에러가 발생한다.</p>

<pre><code>$ yum
There was a problem importing one of the Python modules
required to run yum. The error leading to this problem was:

   No module named yum

Please install a package which provides this module, or
verify that the module is installed correctly.

It's possible that the above module doesn't match the
current version of Python, which is:
2.7.9 (default, Jan 21 2015, 13:27:03)
[GCC 4.4.6 20120305 (Red Hat 4.4.6-4)]

If you cannot solve this problem yourself, please go to
the yum faq at:
  http://yum.baseurl.org/wiki/Faq

</code></pre>

<hr />

<h2 id="사용하기">사용하기</h2>

<p>앞서 설치한 python 2.7.<sup>9</sup>&frasl;<sub>3</sub>.4.2 버전을 실행하는데 문제는 없지만 python 환경에서 개발 할 때 라이브러리 관리의 편의성을 위해서 추가적인 작업을 해주도록 한다.</p>

<h3 id="setuptools-pip">setuptools &amp; pip</h3>

<p>라이브러리를 설치하거나 패키징할 때 유용한 setuptools와 pip를 설치하도록 한다. 2.7.9 기준이며 3.4.2의 경우에는 기본 패키지에 번들로 들어 있다. 별도로 설치하고자 할 경우에는 실행 파일을 python3.4로 실행해주면 된다.</p>

<pre><code># https 접속이기 때문에 certificate 체크를 건너뛰는 옵션을 줬다
$ wget --no-check-certificate https://pypi.python.org/packages/source/s/setuptools/setuptools-12.0.4.tar.gz

# 압축을 풀고 python2.7로 설치를 한다
$ tar zxvf setuptools-12.0.4.tar.gz
$ cd setuptools-12.0.4
$ python2.7 setup.py install

# pip 다운로드
$ wget --no-check-certificate https://pypi.python.org/packages/source/p/pip/pip-6.0.6.tar.gz

# setuptools와 마찬가지로 설치
$ tar zxvf pip-6.0.6.tar.gz
$ cd pip-6.0.6
$ python2.7 setup.py install

# /usr/local/bin에 pip가 추가되어있다. 기존에 OS python에서 사용하던 pip가 있다면 pip2.7로 실행하는 편이 명확하고 좋다.

</code></pre>

<h3 id="virtualenv">virtualenv</h3>

<p>독립적인 python 개발 및 구동 환경을 갖기 위해서는 virtualenv를 설치해서 사용하는 것을 추천하기 때문에 virtualenv도 설치한다. 앞서 pip를 설치 했기 때문에 pip로 간단하게 설치하도록 한다</p>

<pre><code>$ pip2.7 install virtualenv
</code></pre>

<p>python3.4에는 virtualenv가 pyvenv로 내장되어 있다.</p>

<h3 id="virtualenv-환경구축">virtualenv 환경구축</h3>

<p>virtualenv까지 설치했기 때문에 내가 원하는 python버전에 따라서 환경을 구축하고 필요한 패키지들을 설치하는 형태로 사용하면 된다.</p>

<p>virtualenv를 사용하면 좋은 점이 개발/테스트/작업 등을 수행 할 때 설치하는 패키지가 시스템에 설치 된 패키지에 영향을 주지 않으며 일반 사용자가 얼마든지 원하는 패키지를 추가 할 수 있고 복잡한(?) PYTHONPATH를 고려하지 않아도 된다.</p>

<p>참고로, virtualenv로 생성한 환경의 bin 디렉토리를 보면 pip, easy_install이 기본으로 포함된다.</p>

<pre><code># p27env는 예를 든 값이다. 원하는 이름을 사용하면 된다.
$ virtualenv-2.7 p27env
New python executable in p27env/bin/python2.7
Also creating executable in p27env/bin/python
Installing setuptools, pip...done.
# p34env는 예를 든 값이다. 원하는 이름을 사용하면 된다.
$ pyvenv-3.4 p34env

# 기본 python 버전 확인
$ python --version
Python 2.6.6

# p27env 환경 적용
$ . p27env/bin/activate
(p27env)$ python --version
Python 2.7.9

# p34env 환경 적용 (기존 환경을 먼저 비활성화 한다)
$ deactivate
$ . p34env/bin/activate
(p34env)$ python --version
Python 3.4.2
</code></pre>
]]></content>
        </item>
        
        <item>
            <title>[영화] 조난자들</title>
            <link>/2015/01/20/movie-intruders/</link>
            <pubDate>Tue, 20 Jan 2015 15:03:30 +0000</pubDate>
            
            <guid>/2015/01/20/movie-intruders/</guid>
            <description>독립영화 예전부터 보고 싶었던 영화인데 마침 케이블TV에서 방영해주어서 챙겨보게 된 영화. 상대적으로 저 예산이 들었던 독립영화로 꽤나 독특한 구성의 영화이다. &amp;lsquo;노영석&amp;rsquo; 감독의 &amp;lsquo;낯술&amp;rsquo;이란 영화도 유명하다고 하는데 시간이 나면 챙겨봐야겠다.
제목이 스포일러 국내 제목은 조난자들이다. 영어로 된 제목이 Intruders. 국내 제목을 그대로 직역한다면 &amp;lsquo;&amp;hellip; 왜?&amp;lsquo;라는 생각이 드는 영문 제목인데, 영화를 보고나니 제목이 스포일러였다.
조난자들 영화정보 - Daum Movie
영화는 덤덤하게 시작한다. 어찌보면 세상에서 제일 운 없는 주인공의 고생 담이라고 봐도 될 것 같다.</description>
            <content type="html"><![CDATA[

<h2 id="독립영화">독립영화</h2>

<p>예전부터 보고 싶었던 영화인데 마침 케이블TV에서 방영해주어서 챙겨보게 된 영화. 상대적으로 저 예산이 들었던 독립영화로 꽤나 독특한 구성의 영화이다. &lsquo;노영석&rsquo; 감독의 &lsquo;낯술&rsquo;이란 영화도 유명하다고 하는데 시간이 나면 챙겨봐야겠다.</p>

<h2 id="제목이-스포일러">제목이 스포일러</h2>

<p>국내 제목은 <strong>조난자들</strong>이다. 영어로 된 제목이 Intruders. 국내 제목을 그대로 직역한다면 &lsquo;&hellip; 왜?&lsquo;라는 생각이 드는 영문 제목인데, 영화를 보고나니 제목이 스포일러였다.</p>

<h2 id="조난자들">조난자들</h2>

<p><img src="/images/2015/01/t1-daumcdn-1.jpg" alt="" /></p>

<p><a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=81077&amp;t__nil_upper_mini=title" target="_blank">영화정보 - Daum Movie</a></p>

<p>영화는 덤덤하게 시작한다. 어찌보면 세상에서 제일 운 없는 주인공의 고생 담이라고 봐도 될 것 같다. 포스터에 나온 &lsquo;고립된 펜션, 의문의 살인사건&hellip; 그리고&rsquo; 라는 문구를 보면 스릴러를 연상시키지만 내겐 스릴러라기 보다는 주인공의 고생담. 그리고 서로를 믿지 못하고 오해하고 선입견을 갖는 이들의 비극이라고 보였다.</p>

<p>그래서인지 학수라는 캐릭터를 제외하고는 대부분의 출연자들의 모습이 명확한 성격을 가지고 있었다. 학수라는 캐릭터는 영화를 보면서 쉽게 생각하게 되는 &lsquo;선 인지 악 인지&rsquo;를 애매하게 만드는 캐릭터였고 주인공은 그냥 순둥이. 주변 사람들은 싸가지 없거나 별다른 색깔이 없는게 대부분이었다.</p>

<p>이러한 캐릭터들이 덤덤하게 이야기를 진행 시키는데 생각보다 지루하지 않았다는게 좀 신기하다. 99분의 시간이 결코 짧은 시간이 아닌데 말이다.</p>

<p>결말이야 열린 결말이지만 그 결말은 생각하기에 따라서 최악의 비극이 될 수도 있고 모든게 오해에서 비롯 되었다는 형태가 될 수 있지 않을까 한다. 마지막 장면에서 주인공을 엄습하는 소리가 그가 부른 &lsquo;여자&rsquo; 였다면?</p>

<p>뭐, 깔끔한 결말이 아닌 영화들이 주는 여운은 있지만 난 이런 영화를 보면 좀 찜찜하다. 그래도 재밌게 봤고 추천 할 만한 영화.</p>
]]></content>
        </item>
        
        <item>
            <title>[영화] 화이</title>
            <link>/2015/01/20/movie-hwa-ee/</link>
            <pubDate>Tue, 20 Jan 2015 14:53:36 +0000</pubDate>
            
            <guid>/2015/01/20/movie-hwa-ee/</guid>
            <description>이사하고 처음? 예전에는 블로그에 영화나 음악같은 내가 좋아하는 이야기들을 종종 올렸는데 요즘엔 일과 관련된 내용이 더 많은 것 같아서 생각나는 김에 비교적 최근에 봤던(하지만 개봉한지 오래 된) 영화들에 대해서 기록을 남겨보려고 한다. 스포일러가 있을 수도 있고 그렇다고 모든 줄거리에 하나하나 감상평을 기록하진 않았다. 그냥 내 감상만 기록해 둘 뿐.
김윤석이란 배우 영화를 선택하는데 있어서 나는 줄거리를 가급적 안보는 편이다. 미리 예상하거나 심지어 예고편이 전부인 영화도 많으니깐. 그러다보니 자연스럽게 출연 배우나 감독을 보고 선택하는 경우가 많은데 화이라는 영화도 &amp;lsquo;김윤석&amp;rsquo;이라는 배우가 보게 된 동기가 되었다.</description>
            <content type="html"><![CDATA[

<h2 id="이사하고-처음">이사하고 처음?</h2>

<p>예전에는 블로그에 영화나 음악같은 내가 좋아하는 이야기들을 종종 올렸는데 요즘엔 일과 관련된 내용이 더 많은 것 같아서 생각나는 김에 비교적 최근에 봤던(하지만 개봉한지 오래 된) 영화들에 대해서 기록을 남겨보려고 한다. 스포일러가 있을 수도 있고 그렇다고 모든 줄거리에 하나하나 감상평을 기록하진 않았다. 그냥 내 감상만 기록해 둘 뿐.</p>

<h2 id="김윤석이란-배우">김윤석이란 배우</h2>

<p>영화를 선택하는데 있어서 나는 줄거리를 가급적 안보는 편이다. 미리 예상하거나 심지어 예고편이 전부인 영화도 많으니깐. 그러다보니 자연스럽게 출연 배우나 감독을 보고 선택하는 경우가 많은데 <strong>화이</strong>라는 영화도 &lsquo;김윤석&rsquo;이라는 배우가 보게 된 동기가 되었다.</p>

<h2 id="화이">화이</h2>

<p><img src="/images/2015/01/t1-daumcdn.jpg" alt="" /></p>

<p><a href="http://movie.daum.net/moviedetail/moviedetailMain.do?movieId=72864" target="_blank">영화정보 - Daum Movie</a></p>

<p><strong>지구를 지켜라</strong>라는 독특한 영화로 인상 깊은 장준환 감독. 그의 작품이기에 평범한 영화는 아닐거라 예상했지만 생각보다 대중적인 요소가 많은 영화였다. 반전이라기 보다는 주인공이 서서히 진실을 알아가는 과정의 전개가 자연스럽게 예상가능한 영화.</p>

<p>물론, 개연성이 좀 떨어지는 부분도 있었지만 배우들의 연기가 워낙 출중해서 몰입하기에 부족하지 않았다. 과연 누가 &lsquo;악&rsquo;이라고 단정 짓기 어려운 영화. 감독을 보면 꽤나 잔인 할 것 같았지만 (잔인한 영화를 그리 좋아하진 않는다) 그런대로 봐줄 만 했다.</p>

<p>화이를 자신과 동일 시 했던 아버지 석태. 사랑이라고만 표현하기에는 복잡한 다양한 감정들 하지만, 끝내 화이를 해치지 못 한 그의 모습을 볼 때&hellip; 차마 자기 자신을 해치지 못 한 감정도 있지 않았을까.</p>
]]></content>
        </item>
        
        <item>
            <title>Mac OS X에서 연말정산 간소화  사이트 접속</title>
            <link>/2015/01/15/connect-to-tax-refund-site-on-mac/</link>
            <pubDate>Thu, 15 Jan 2015 07:34:29 +0000</pubDate>
            
            <guid>/2015/01/15/connect-to-tax-refund-site-on-mac/</guid>
            <description>2016년도 업데이트 정보 연말정산 간소화 페이지를 기존 방법대로 접속해 보려고 했으나 Veraport 기반으로 접속하게 끔 변경해 버려서 접속이 불가 한 상태이다. 구 버전 Safari를 통하면 접속이 될지 모르겠으나 사이트 하나 접속하자고 이따위 짓을 해야한다면 안하는게 낫다고 본다.
Veraport 맥 클라이언트 링크는 존재하지만 설치하고나서 페이지를 다시 접속해도 &amp;ldquo;axinfo request fail!&amp;rdquo; 메시지만 볼 수 있을 것인데 확인해보니 Access-Control-Allow-Origin 제한에 걸려서 그렇다. 쉽게 말해서 요즘 브라우저에서는 보안을 위해서 교차도메인(즉, 접속하려는 사이트와 전혀 다른 도메인) 출처를 향한 요청에 제한이 설정되어서 해당 컨텐츠를 차단해버린다.</description>
            <content type="html"><![CDATA[

<h2 id="2016년도-업데이트-정보">2016년도 업데이트 정보</h2>

<p>연말정산 간소화 페이지를 기존 방법대로 접속해 보려고 했으나 Veraport 기반으로 접속하게 끔 변경해 버려서 <strong>접속이 불가</strong> 한 상태이다. 구 버전 Safari를 통하면 접속이 될지 모르겠으나 사이트 하나 접속하자고 이따위 짓을 해야한다면 안하는게 낫다고 본다.</p>

<p>Veraport 맥 클라이언트 링크는 존재하지만 설치하고나서 페이지를 다시 접속해도 &ldquo;axinfo request fail!&rdquo; 메시지만 볼 수 있을 것인데 확인해보니 Access-Control-Allow-Origin 제한에 걸려서 그렇다. 쉽게 말해서 요즘 브라우저에서는 보안을 위해서 교차도메인(즉, 접속하려는 사이트와 전혀 다른 도메인) 출처를 향한 요청에 제한이 설정되어서 해당 컨텐츠를 차단해버린다. 그래서, 교차 출처에 대한 요청을 허용하도록 강제로 고쳐서 시도해 보았으나 결국 내부적으로 JRE 체크하는 코드에서 오류를 뱉어버린다.</p>

<p><img src="/images/2016/01/--------------.jpg" alt="" /></p>

<p>심지어 사이트는 SHA-1 인증이다. 최신 브라우저가 제공하는 높은 수준의 보안 기술은 어디다 두고 이따위 플러그인을 설치하도록 하는지 이해 할 수가 없다.</p>

<p><img src="/images/2016/01/verport.png" alt="" />
- 사파리도 veraport를 비웃는다</p>

<p>연말정산 홈페이지가 진정한 오픈웹으로 거듭나는 날이 언제 올지 궁금 할 따름이다.</p>

<ul>
<li>한줄요약: <strong>NProtect 같은 Veraport 때문에 안됨</strong></li>
</ul>

<hr />

<p>2015년 1월 연말정산 시즌이다. 그러다보니 국세청에서 제공하는 연말정산 간소화 서비스에서 증명문서를 조회/다운로드 할 필요가 있는데 Mac에서 이용하는 방법이나 공인인증서 위치를 모르시는 분들을 위해서 정리해 본다.</p>

<h2 id="java">JAVA</h2>

<p><a href="http://lunatine.net/mevericks-java6-enable/" target="_blank">JAVA6 사용하기</a> 문서에 나온 내용을 먼저 적용해야 한다.</p>

<h2 id="인증서-준비">인증서 준비</h2>

<p>Windows 계열 PC의 경우 Program Files/NPKI 폴더나 사용자 문서 폴더에 인증서가 들어있는데 Mac의 경우에는 아래 위치를 기본 공인인증서 위치로 찾게 된다.</p>

<pre><code>/Users/사용자명/Library/Prefrences/NPKI
</code></pre>

<p>따라서, NPKI 디렉토리 아래의 인증서 파일을 위의 경로로 복사해 준다. 기본 설정으로 사용하는 경우 파인더에서 Library 위치가 나타나지 않는다. 파인더를 실행하고 Command(애플키)+Shift+G를 누르면 폴더 이동 창이 나타나는데 여기에 아래와 같이 입력한다</p>

<p><img src="/images/2015/01/path.png" alt="" /></p>

<pre><code>~/Library/Prefrences
</code></pre>

<p>그리고 NPKI 폴더를 생성하고 여기에 인증서 파일을 복사한다. 복사가 끝나면  대충 아래와 같은 모습일 것이다.</p>

<p><img src="/images/2015/01/npki.png" alt="" /></p>

<h2 id="사이트-접속">사이트 접속</h2>

<p>먼저 Safari 브라우저를 실행해서 <a href="http://yesone.go.kr" target="_blank">http://yesone.go.kr</a> 로 접속한다.
사용자가 많을 경우에는 선택지 화면이 나타나는데 &lsquo;소득공제자료 조회/출력&rsquo;을 선택한다.</p>

<p><img src="/images/2015/01/home.png" alt="" /></p>

<p>키보드 보안 프로그램을 물어보는데 개인 Mac의 경우에는 취소하고 넘어가도 무방하다. (보안이 염려되는 사람은 승인을 누르고 설치해도 된다)</p>

<p><img src="/images/2015/01/keyboard.png" alt="" /></p>

<p>브라우저 상단에 JAVA 플러그인을 사용하도록 허락 할지 여부를 선택하는 창이 나타나면 신뢰를 눌러서 진행한다.</p>

<p><img src="/images/2015/01/plugin.png" alt="" /></p>

<p>그리고 JAVA 애플릿이 동작하면서 프로그램 제작사의 인증서를 신뢰할지 물어본다. 이 때 <strong>허용</strong>하는 버튼 자체가 활성화 되어있지 않는데 &lsquo;<strong>세부사항 보기</strong>&lsquo;를 들어가서 해당 업체의 인증서를 &lsquo;항상 신뢰&rsquo;로 수정하고 계속 버튼을 누른다.</p>

<p><img src="/images/2015/01/applet.png" alt="" /></p>

<p><img src="/images/2015/01/dreamsecurity.png" alt="" /></p>

<p>위 과정을 거치면 다시 애플릿에 대한 신뢰 여부를 묻는 창이 나오고 &lsquo;<strong>허용</strong>&rsquo; 버튼이 활성화 되어있다. 허용을 선택한다.</p>

<p><img src="/images/2015/01/applet2.png" alt="" /></p>

<p>위 과정을 모두 거치면 드디어 로그인 창이 등장한다.</p>

<p><img src="/images/2015/01/login.png" alt="" /></p>

<p>이제 주민등록번호를 입력하고 공인인증서 로그인을 선택하면 인증서 선택창이 뜨게 되며 정상적으로 로그인이 가능해 진다</p>

<p><img src="/images/2015/01/select.png" alt="" /></p>

<p>만약 USB에 인증서를 저장하고 있는 사람의 경우 선택창에서 &lsquo;<strong>이동식디스크</strong>&lsquo;를 선택하여 로그인 하면 된다.</p>

<h2 id="end">END</h2>

<p>여전히 국내에서 비Windows 환경에서 공공기관 사이트 접속하는 방법은 불가능하거나 어렵지만 그래도 국세청 연말정산 간소화 페이지는 간편한(?!)편에 속한다.</p>
]]></content>
        </item>
        
        <item>
            <title>RHEL6 - intel_idle과 C States</title>
            <link>/2015/01/06/rhel6-intel_idle-and-c-states/</link>
            <pubDate>Tue, 06 Jan 2015 17:02:03 +0000</pubDate>
            
            <guid>/2015/01/06/rhel6-intel_idle-and-c-states/</guid>
            <description>C/G/S/P states intel_idle과 관련된 내용을 다루기에 앞서 P-States와 C-States에 대해서 간단히 정리하고자 한다. Intel 아키텍처 환경에서 리눅스 커널과 CPU를 알아가다보면 P/S/G/C States에 대한 내용을 접할 수 있다. 이러한 상태 값에 대해서 간단히 설명하도록 하겠다.
P-States P-States는 작업 부하에 따라서 CPU의 전압과 클럭주파수를 조절하는 정도를 정의 한 값으로 명령어 처리(Operation)상태를 기준으로 절전 및 성능 향상을 꾀하기 위한 기법이다. 과거에는 SpeedStep이라는 기술로 소개 되었기(정확히 같은 것은 아니다) 때문에 단순히 클럭주파수를 조절해서 에너지 절약을 위한 방안으로만 치부되었는데 CPU가 연산처리를 할 때의 상태를 반영하고 있다.</description>
            <content type="html"><![CDATA[

<h1 id="c-g-s-p-states">C/G/S/P states</h1>

<p>intel_idle과 관련된 내용을 다루기에 앞서 P-States와 C-States에 대해서 간단히 정리하고자 한다. Intel 아키텍처 환경에서 리눅스 커널과 CPU를 알아가다보면 P/S/G/C States에 대한 내용을 접할 수 있다. 이러한 상태 값에 대해서 간단히 설명하도록 하겠다.</p>

<h2 id="p-states">P-States</h2>

<p>P-States는 작업 부하에 따라서 CPU의 전압과 클럭주파수를 조절하는 정도를 정의 한 값으로 명령어 처리(Operation)상태를 기준으로 절전 및 성능 향상을 꾀하기 위한 기법이다. 과거에는 SpeedStep이라는 기술로 소개 되었기(정확히 같은 것은 아니다) 때문에 단순히 클럭주파수를 조절해서 에너지 절약을 위한 방안으로만 치부되었는데 CPU가 연산처리를 할 때의 상태를 반영하고 있다.</p>

<p>예를 들어 최근 사용되는 Intel CPU의 Turbo Boost 상태는 P-State 0(P0)를 의미한다.</p>

<h2 id="c-states">C-States</h2>

<p>C-States는 CPU 내부의 특정 부분이 활성화 되거나 낮은 성능 상태로 실행될지를 반영하는 값으로 CPU에서 사용중이 아닌 부분들을 비활성화하여 전원의 효율화를 높이기 위한 상태 값이다. P-State와 다르게 C-State는 유휴(Idle)상태를 기준으로하여 평가한다. 따라서, C0는 활성화 된 일반적인 상태를 의미하며 C0 상태에서 P0~Pn 상태로 나누어서 볼 수 있다. 네할렘부터는 C6 상태가 추가되었는데 C6는 각종 작업들을 저장하고 이미 작동을 멈춘 CPU코어에 공급되는 전원을 차단하는 상태이다. 그리고, 샌디브리지에서 C7이 추가되었고 이는 C6에서 추가로 L3캐시까지 비워버린(Flush) 상태를 의미한다. (p.s 절전도 좋지만 머리 아프다 인텔 놈들아..)</p>

<h2 id="g-states">G-States</h2>

<p>Global States를 의미하며 간단히 말해 사용자가 인지할 수 있는 상태를 반영한다. G0는 동작상태 (전원 On) G1은 잠자기모드 상태 G3는 전원 Off 상태이다.</p>

<h2 id="s-states">S-States</h2>

<p>Sleep States를 의미하며 G1에서 세부적인 잠자기모드 상태를 나타낸다.</p>

<h2 id="processor-power-states">Processor Power States</h2>

<p>앞에서 언급한 상태들을 알아보기 쉽게 도식화 하면 아래와 같다.</p>

<p><img src="/images/2015/01/powerstate.jpg" alt="powerstate.jpg" />
이미지 출처: <a href="http://wccftech.com/review/intel-core-i7-4790k-haswell-refresh-devils-canyon-processor-review/" target="_blank">Intel Core i7-4790K Haswell Refresh “Devil’s Canyon” Processor Review</a></p>

<h1 id="intel-idle">intel_idle</h1>

<p>모듈 이름처럼 Idle 상태를 관리하기 위한 것으로 C-States와 관련이 있는 모듈이다. intel_idle이 사용되기 이전에는 C-States를 OS에서 관리하기 위해서 acpi_idle이란 모듈이 사용되었다. 그리고, 그 당시에 intel_idle은 EXPERIMENTAL로 커널에 포함되어 있었는데 2.6.35 버전부터로 알고 있었기 때문에 2.6.32 기반의 RHEL6에서 별로 신경쓰고 있지 않았다. (RHEL7만 신경쓰였을 뿐이지&hellip;) 그런데, <strong>스마트한 직장동료</strong>가 RHEL6에서도 intel_idle 때문에 C-States 영향을 받고 있다고 제보하였고 이를 해결하기 위한 설정방법 등을 공유하였다. 그래서 찾아보니 RHEL6.2 기술문서부터 intel_idle.max_cstate 파라미터에 대한 내용이 언급 되어있었다.</p>

<p><img src="/images/2015/01/oops.png" alt="oops.png" /></p>

<h2 id="왜-문제가-되는-것인가">왜 문제가 되는 것인가?</h2>

<p>기존 acpi_idle 모듈의 경우 C-State latency와 관련하여 정확도도 높지 않았고 기본적으로 BIOS 설정에 따라서 주어진 환경 내에서만 상태를 변경하는 정도가 고작이었으나 intel_idle의 경우 BIOS 설정에 직접적으로 개입하여 C-State를 조절하는 모듈이기 때문에 문제가 되는 것이다.</p>

<p>일반적으로 서버시스템의 경우 빠른 응답속도를 목표로하기 때문에 소위 Performance 모드로 통칭되는 BIOS 설정상태를 유지하여 CPU가 잠들지 않도록 하는 편인데 C-State를 커널이 개입하여 제어해 버리면 쉬고 있던 상태에서 C0 상태로 만들기 위한 시간(Wake-up time)이 소모되어 성능 저하로 이어지게 된다. 즉, 절전보다는 빠른 반응이 필요한데 이를 intel_idle 모듈이 작업 상태에 따라서 조절해 버리는 것이다. 그것도 BIOS 설정과 무관하게&hellip;</p>

<h2 id="해결방법">해결방법</h2>

<p>친절하게도 Red Hat Enterprise Linux Technical Note에 아래와 같이 소개하고 있다.</p>

<pre><code>intel_idle.max_cstate

A new kernel parameter, intel_idle.max_cstate, has been added to specify the maximum depth of a C-state, or to disable intel_idle and fall back to acpi_idle. For more information, refer to the /usr/share/doc/kernel-doc-&lt;version&gt;/Documentation/kernel-parameters.txt file.
</code></pre>

<p>즉, 커널 부팅 파라미터에 <code>intel_idle.max_cstate=0</code> 값을 설정하면 acpi_idle을 이용하도록 부팅하게 된다는 것이다.</p>

<h2 id="온라인-해결방법">온라인 해결방법</h2>

<p>직장동료 Alden이 커널파라미터의 경우 리부팅의 부담이 있기 때문에 기존에 운영하는 장비도 적용 할 수 있도록 tuned의 프로파일을 이용한 설정 방법을 제안하였다.</p>

<pre><code>$ tuned-adm profile latency-performance
</code></pre>

<p>tuned의 프로파일 중에서 latency-performance라는 프로파일이 있으며 이를 적용하면 아래 그림과 같이 기존에 C7 상태까지 떨어지던 idle 상태가 C1이하로 내려가지 않는 모습을 볼 수 있다.</p>

<ul>
<li><p>latency-performance 적용 전
<img src="/images/2015/01/cstate-c7-1.png" alt="cstate-c7.png" /></p></li>

<li><p>latency-performance 적용 후
<img src="/images/2015/01/cstate-c1.png" alt="cstate-c1.png" /></p></li>
</ul>

<h3 id="예외사항-1">예외사항 (1)</h3>

<p>실제 장비들을 샘플링하여 테스트 해 본 결과 tuned-adm을 설정하더라도 E3/E5 계열(샌디브리지)의 CPU는 바로 적용이 되었으나 C6까지있는 네할렘 CPU들은 여전히 말을 듣지 않았다.</p>

<p><img src="/images/2015/01/cstate-c6.png" alt="cstate-c6.png" /></p>

<p>그래서, /dev/cpu_dma_latency 장치(4바이트 값을 갖는 장치이다)에 직접 latency 값을 100으로 설정해 보았다.</p>

<pre><code>$ exec 3&gt; /dev/cpu_dma_latency
$ echo -ne '\0144\000\000\000' &gt;&amp;3
</code></pre>

<p><img src="/images/2015/01/cstate-c3.png" alt="cstate-c3.png" /></p>

<p>그랬더니 C3 상태이하로 내려가지 않았다. 조금더 욕심을 내서 8로 설정했더니 C1상태 이하로 내려가지 않는 것을 확인 할 수 있었다.</p>

<p><img src="/images/2015/01/cstate-c1-nehal.png" alt="cstate-c1-nehal.png" /></p>

<p>그래서 설정이 가능함에도 불구하고 왜 tuned에서 처리가 안되는지 확인해보니 cpu_dma_latency 값을 조절하는 <code>/usr/libexec/tuned/pmqos-static.py</code> 파일이 설치되지 않는 것을 확인 할 수 있었다.</p>

<p>결론은 tuned-0.2.19-13.el6 이상의 패키지를 사용해야만 cpu_dma_latency 장치를 조절하는 PMQOS스크립트 패치가 반영되어 Python 스크립트가 포함된다. 이 패키지는 RHEL6.5 이상 저장소에서 제공하고 있다.</p>

<p><strong>한줄요약: RHEL6.5에 있는 tuned-0.2.19-13.el6 버전 이상을 사용해라. RHEL6.3에도 아무 문제 없이 잘 설치된다.</strong></p>

<h3 id="예외사항-2">예외사항 (2)</h3>

<p>tuned가 제공하는 latency-performance 프로파일에는 IO스케줄러를 <strong>deadline</strong>으로 설정하도록 되어 있다. 만약, CPU C-State 값 때문에 적용하는 것이라면 그리고 IO스케줄러를 바꿀 생각이 없다면 <code>/etc/tune-profiles/latency-performance/ktune.sysconfig</code> 파일을 열어 ELEVATOR 항목을 원하는 스케줄러로 수정하고 적용하도록 하자.</p>

<pre><code># ktune service configuration

# This is the ktune sysctl file.  You can comment this out to prevent ktune
# from applying its sysctl settings.
#SYSCTL=&quot;/etc/sysctl.ktune&quot;

# Use *.conf files in the ktune configuration directory /etc/ktune.d.
#   Value: yes|no,  default: yes
# It is useful if you want to load settings from additional files. Set this to
# no if you to prevent ktune from using these additional files.
USE_KTUNE_D=&quot;yes&quot;

# This is the custom sysctl configuration file.  Any settings in this file will
# be applied after the ktune settings, overriding them.  Comment this out to
# use only the ktune settings.
SYSCTL_POST=&quot;/etc/sysctl.conf&quot;

# This is the I/O scheduler ktune will use.  This will *not* override anything
# explicitly set on the kernel command line, nor will it change the scheduler
# for any block device that is using a non-default scheduler when ktune starts.
# You should probably leave this on &quot;deadline&quot;, but &quot;as&quot;, &quot;cfq&quot;, and &quot;noop&quot; are
# also legal values.  Comment this out to prevent ktune from changing I/O
# scheduler settings.
ELEVATOR=&quot;deadline&quot;

# These are the devices, that should be tuned with the ELEVATOR
ELEVATOR_TUNE_DEVS=&quot;/sys/block/{sd,cciss,dm-,vd}*/queue/scheduler&quot;
</code></pre>

<h3 id="끝">끝</h3>

<p><strong>Thanks to</strong> Alden</p>
]]></content>
        </item>
        
        <item>
            <title>Transparent Huge Pages 와 page allocation error</title>
            <link>/2014/10/29/thp-and-page-allocation-error/</link>
            <pubDate>Wed, 29 Oct 2014 09:36:11 +0000</pubDate>
            
            <guid>/2014/10/29/thp-and-page-allocation-error/</guid>
            <description>1. Page allocation Error RHEL6/Centos6를 사용하다보면 아래와 같은 메시지를 마주 할 때가 있다.
page allocation failure. order:4, mode:0x8020 kernel: Pid: 7036, comm: sas2ircu Not tainted 2.6.32-358.6.2.el6.x86_64 #1 kernel: Call Trace: kernel: [&amp;lt;ffffffff8112c207&amp;gt;] ? __alloc_pages_nodemask+0x757/0x8d0 kernel: [&amp;lt;ffffffff81010ff6&amp;gt;] ? dma_generic_alloc_coherent+0xa6/0x160 kernel: [&amp;lt;ffffffff8103c8f1&amp;gt;] ? x86_swiotlb_alloc_coherent+0x31/0x70 kernel: [&amp;lt;ffffffffa002e74a&amp;gt;] ? pci_alloc_consistent+0x5a/0xc0 [mpt2sas] kernel: [&amp;lt;ffffffffa0030155&amp;gt;] ? _ctl_do_mpt_command+0x7c5/0xcb0 [mpt2sas] kernel: [&amp;lt;ffffffff8119abe7&amp;gt;] ? __d_lookup+0xa7/0x150 kernel: [&amp;lt;ffffffffa0030762&amp;gt;] ? _ctl_compat_mpt_command+0x122/0x160 [mpt2sas] kernel: [&amp;lt;ffffffff8150f026&amp;gt;] ? mutex_lock_interruptible+0x16/0x50 kernel: [&amp;lt;ffffffffa003167b&amp;gt;] ? _ctl_ioctl_main+0x5cb/0x1070 [mpt2sas] kernel: [&amp;lt;ffffffff81193010&amp;gt;] ?</description>
            <content type="html"><![CDATA[

<h1 id="1-page-allocation-error">1. Page allocation Error</h1>

<p>RHEL6/Centos6를 사용하다보면 아래와 같은 메시지를 마주 할 때가 있다.</p>

<pre><code>page allocation failure. order:4, mode:0x8020
kernel: Pid: 7036, comm: sas2ircu Not tainted 2.6.32-358.6.2.el6.x86_64 #1
kernel: Call Trace:
kernel: [&lt;ffffffff8112c207&gt;] ? __alloc_pages_nodemask+0x757/0x8d0
kernel: [&lt;ffffffff81010ff6&gt;] ? dma_generic_alloc_coherent+0xa6/0x160
kernel: [&lt;ffffffff8103c8f1&gt;] ? x86_swiotlb_alloc_coherent+0x31/0x70
kernel: [&lt;ffffffffa002e74a&gt;] ? pci_alloc_consistent+0x5a/0xc0 [mpt2sas]
kernel: [&lt;ffffffffa0030155&gt;] ? _ctl_do_mpt_command+0x7c5/0xcb0 [mpt2sas]
kernel: [&lt;ffffffff8119abe7&gt;] ? __d_lookup+0xa7/0x150
kernel: [&lt;ffffffffa0030762&gt;] ? _ctl_compat_mpt_command+0x122/0x160 [mpt2sas]
kernel: [&lt;ffffffff8150f026&gt;] ? mutex_lock_interruptible+0x16/0x50
kernel: [&lt;ffffffffa003167b&gt;] ? _ctl_ioctl_main+0x5cb/0x1070 [mpt2sas]
kernel: [&lt;ffffffff81193010&gt;] ? do_filp_open+0x7d0/0xdd0
kernel: [&lt;ffffffff8104757c&gt;] ? __do_page_fault+0x1ec/0x480
kernel: [&lt;ffffffffa0032133&gt;] ? _ctl_ioctl_compat+0x13/0x20 [mpt2sas]
kernel: [&lt;ffffffff811d6bbd&gt;] ? compat_sys_ioctl+0xed/0x510
kernel: [&lt;ffffffff8104dc73&gt;] ? ia32_sysret+0x0/0x5
</code></pre>

<p>특히 위의 메시지 형태 중에서 swapper에서 발생시키는 메시지의 경우 <a href="https://bugzilla.redhat.com/show_bug.cgi?id=761442" target="_blank">Bug 761442</a>에서 언급 된 것 처럼 zone_reclaim_mode를 설정해서 해결하는 방안이 있었다.</p>

<p>하지만, swapper가 아닌 일반 프로세스에서도 (특히, IO작업이 많거나 네트워크 처리량이 많은 서버) page allocation error가 발생하는 경우가 RHEL6.4/CentOS6.4 이하에서 많이 발견 되었다.</p>

<p>이를 해결하기 위해 커널 패치 이력을 살펴보았다. 이력 중에 관련성이 있어보이는 몇개의 Bug Fix 내용은 접근 권한이 없어서 상세히 못 봤지만 유추되는 내용을 통해서 해결 할 수가 있었다.</p>

<p>본 문서에서는 THP(Transparent Hugepages)와 Page alloation failure 메시지 해결 방안 2가지를 안내한다.</p>

<ul>
<li>대상: RHEL6.4/CentOS6.4 이하의 환경</li>
</ul>

<h1 id="2-transparent-huage-pages">2. Transparent Huage Pages</h1>

<p>Linux는 메모리에 대한 관리를 Pages 블록을 통해서 하는 사실은 익히 알려진 내용이다. 그리고 기본 페이지는 4096바이트(4K)로 고정되어있다. 1GB의 메모리는 256,000개의 page로 구성되어있는 것이다. 이러한 페이지는 전체 메모리 크기가 늘어남에 따라 관리테이블(TLB)도 커지게 되는데 이를 해결하기 위해서는 <strong>페이지의 크기를 확대</strong>하는 방법이 있다. 이를, <strong>Huge Pages</strong>라 지칭하며 보통 Oracle과 같은 DB서버에서 활용하곤 했다.</p>

<p>Huge Pages는 기본 크기가 2MB를 많이 사용하는데 2MB 단위로 페이지를 사용하게 되면 1GB 메모리는 512개의 페이지로 관리 할 수 있다. 요즘처럼 서버에 수십에서 수백GB의 메모리를 탑제하는 환경에서는 Huge Pages가 관리/성능적인 측면에서 유리한 것이 맞다.</p>

<p>다만, 이러한 Huge Pages의 경우 부팅시에 커널에 파라미터를 지정해서 관리해야하는 번거로움이 있기 때문에 이를 자동으로 관리하는 방안으로 Transparent Huge Pages(THP)가 등장하게 되었다. THP를 사용하게 되면 페이지의 생성, 관리, 사용에 대한 모든 부분을 자동으로 관리하게 되며 어플리케이션에서 대용량의 메모리를 요구하게 되면 알아서 2MB (현재 2MB 단위로 제공된다) 크기의 페이지로 할당해준다.</p>

<p>THP는 커널 2.6.38에 등장하였다. (<a href="http://lwn.net/Articles/423584/" target="_blank">LWN문서</a>) 그리고 RHEL 계열의 경우 2.6.32커널을 Base로 하면서도 THP 기능을 추가하여 릴리즈했고 무엇보다 THP를 <strong>기본으로 활성</strong>화 되도록 하였다. 문제는 성능 향상을 목적으로 한 THP가 오히려 성능을 저하하는 경우가 자주 발견되고 있다. 대표적으로 Oracle, JVM, Hadoop 등이 있으며 Google에 THP에 대해 검색해보면 많은 불만이 보일 것이다.</p>

<p>실제로 본 문서에서 언급한 page allocation failure의 메시지도 THP에 의해서 유동적으로 관리되던 Huge Page의 버그성 동작으로 인해 발생하는 것으로 보이며 THP 관련 된 작업을 통해서 해당 오류 메시지를 제거하였다.</p>

<h1 id="3-해결방안">3. 해결방안</h1>

<p>page allocation failure 메시지를 제거하기 위한 방법은 크게 2가지이다.</p>

<h2 id="1-thp-비활성화">1) THP 비활성화</h2>

<p>첫 번째 방법은 THP를 Disable 시키는 것이다. 별도의 바이너리 패치작업 없이 Disable 설정 후에 리부팅만 진행하면 된다. 리부팅을 안해도 되는 경우도 있지만 이미 AnonHugePages가 많이 사용되고 있는 시스템 (cat /proc/meminfo로 확인해보자)에서는 리부팅을 해야 효과가 있다.</p>

<h3 id="sys-파일시스템-설정으로-비활성화">/sys 파일시스템 설정으로 비활성화</h3>

<p>아래 파일의 설정 값을 변경하여 비활성화 할 수 있다.</p>

<pre><code>echo never &gt; /sys/kernel/mm/transparent_hugepage/enabled
echo never &gt; /sys/kernel/mm/transparent_hugepage/defrag
</code></pre>

<p>위 방법은 리부팅 후에도 적용해줘야 하기 때문에 보통 /etc/rc.d/rc.local 파일에 아래와 같이 추가해 줘서 적용한다.</p>

<pre><code># Disable THP
if [[ -f &quot;/sys/kernel/mm/transparent_hugepage/enabled&quot; ]]; then
    echo never &gt; /sys/kernel/mm/transparent_hugepage/enabled
fi
if [[ -f &quot;/sys/kernel/mm/transparent_hugepage/defrag&quot; ]]; then
    echo never &gt; /sys/kernel/mm/transparent_hugepage/defrag
fi
</code></pre>

<h3 id="커널-파라미터로-비활성화">커널 파라미터로 비활성화</h3>

<p>부트로더에 직접 커널 파라미터를 지정하여 비활성화 할 수 있다. grub.conf 파일의 부트 옵션에 아래의 내용을 추가해주면 된다.</p>

<pre><code>transparent_hugepage=never
</code></pre>

<h2 id="2-rhel6-5-centos6-5-커널로-패치">2) RHEL6.5/CentOS6.5 커널로 패치</h2>

<p>두 번째 방법은 커널을 패치하는 방법이다. 6.4에서 6.5릴리즈 커널로 변경 될 때 메모리 관리 쪽에 많은 패치가 이루어졌는데 그로 인해 THP에 의한 page allocation failure가 사라진 것을 커널 패치와 한 달 이상의 모니터링을 통해서 확인하였다.</p>

<p>커널 패치는 RHEL6/CentOS6 6.5릴리즈 기준 커널인 <strong>2.6.32-431.el6</strong> 이상으로 설치하면 된다. 만약 6.5릴리즈에 대한 저장소가 설정 되어있다면 간단히 yum으로 패치가 가능하다.</p>

<pre><code>$ yum install kernel
</code></pre>

<h1 id="4-어떤-방법이-좋은가">4. 어떤 방법이 좋은가?</h1>

<p>page allocation failure 메시지를 없애기 위한 방법으로 크게 2가지가 있지만 (sysfs vs kernel) 어떤 방법을 적용해야 좋을지에 대해선 정답은 없다. 현재 운영하고 있는 시스템의 관리 방법에 가장 적합한 방법을 선택하기를  바란다.</p>

<p><strong>끝</strong></p>
]]></content>
        </item>
        
        <item>
            <title>systemd 살펴보기</title>
            <link>/2014/10/21/about-systemd/</link>
            <pubDate>Tue, 21 Oct 2014 14:15:46 +0000</pubDate>
            
            <guid>/2014/10/21/about-systemd/</guid>
            <description>systemd 살펴보기 우선, systemd가 이렇게 급 부상하게 될 줄은 솔직히 몰랐다. 워낙 많은 오픈소스 프로젝트들이 나오고 있기 때문에 그 중 하나 정도로만 생각했는데 어느 순간 Fedora에 적용이 되더니 당연한 수순대로 RHEL7에 도입이 되었다. 아직 Debian/Ubuntu 계열은 기본으로 채택되지 않았지만 조만간 릴리즈되는 버전에서 새로운 PID 1으로 자리잡게 될 것 같다.
이러한 분위기에 맞추어 systemd가 무엇이고 이에 대해서 간단히 짚어 볼 필요가 있을 듯 하여 간단히 소개해 본다. (늘 그렇듯이 상세한 내용은 공식홈페이지의 문서를 참고하는 것이 좋다)</description>
            <content type="html"><![CDATA[

<h1 id="systemd-살펴보기">systemd 살펴보기</h1>

<p>우선, systemd가 이렇게 급 부상하게 될 줄은 솔직히 몰랐다. 워낙 많은 오픈소스 프로젝트들이 나오고 있기 때문에 그 중 하나 정도로만 생각했는데 어느 순간 Fedora에 적용이 되더니 당연한 수순대로 RHEL7에 도입이 되었다. 아직 Debian/Ubuntu 계열은 기본으로 채택되지 않았지만 조만간 릴리즈되는 버전에서 새로운 <strong>PID 1</strong>으로 자리잡게 될 것 같다.</p>

<p>이러한 분위기에 맞추어 systemd가 무엇이고 이에 대해서 간단히 짚어 볼 필요가 있을 듯 하여 간단히 소개해 본다. (늘 그렇듯이 상세한 내용은 <a href="http://www.freedesktop.org/wiki/Software/systemd/" target="_blank">공식홈페이지</a>의 문서를 참고하는 것이 좋다)</p>

<p>사실 RHEL6가 등장하고 Upstart가 도입되었을 때는 그저 초기화 스크립트 관리 방안이 바뀌었을 뿐이기에 크게 신경쓰지 않았고 주변에 관련내용 공유도 별로하지 않았다. (설정에 꼭 필요한 방법만 공유했었다) 하지만, systemd의 경우는 Upstart와 비교되지 않는 물건이기 때문에 이렇게 문서까지 작성해서 공유하게 되었다.</p>

<h1 id="systemd는">systemd는?</h1>

<p>systemd가 처음 소개되고 프로젝트가 진행 될 때 커뮤니티 반응은 시끌벅적했다. 우선 systemd는 전통적으로 Unix계열 운영체제의 PID 1이었던 init(System V Init)을 교체하는 역할 뿐만 아니라 초기화 스크립트 관리자이고 로그시스템 관리자이기도 하다. 또한, 하드웨어에 대한 부분과 cgroup 관리 등 시스템 전반적인 부분에 관여하고 있다. 심지어 기존 SysV에서 공통적으로 사용되었던 프로세스 데몬을 만들기 위한 setsid() 콜도 필요없고 PID파일을 따로 관리할 필요도 없다. 이러한 systemd는 유닉스의 철학인 <strong>&lsquo;한 가지만 잘하자&rsquo;</strong>와 상반되기 때문에 논란이 되곤 했었다.  PulseAudio 개발자라는 이유(커뮤니케이션이 잘 안되기로 유명했다 한다)로 싸우는건 또 다른 논란이었고&hellip;</p>

<p>여러 논란가운데 결국 대표적인 배포판에 입성하게 되었는데 논란의 결과가 어찌되었건 납득할 만한 디자인과 성능을 가지고 있기에 채택되지 않았을까 한다.</p>

<p>이 프로젝트의 철학(?)을 알고 싶다면 개발자인 <a href="http://0pointer.de/blog/projects/systemd.html" target="_blank">Nennart의 블로그</a>를 읽어보는 것도 좋다.</p>

<h2 id="준비물">준비물</h2>

<p>실제 systemd가 어떠한 물건인지 알아보기 위한 간단한 실습을 위해서 당연히 systemd를 사용중인 배포판을 설치해서 사용해 보는 것이 좋다.</p>

<ul>
<li>RHEL7</li>
<li>CentOS 7</li>
<li>Fedora</li>
<li>OpenSUSE</li>
</ul>

<p>Debian이나 Ubuntu에서 사용하는 방법은 나중에 시간 될 때 다른 문서를 통해서 공유하겠다. (아마 그 전에 정식채택 될 것 같다. - <a href="http://www.markshuttleworth.com/archives/1316" target="_blank">관련내용</a>)</p>

<h2 id="실행하며-살펴보기">실행하며 살펴보기</h2>

<p>아래 내용은 RHEL7에서 실행한 내용이며 어느 배포판을 사용하더라도 크게 차이는 없다.</p>

<h3 id="1-pid-1">1. PID 1</h3>

<p>먼저 ps 명령을 통해서 PID 1 프로세스를 확인해 보자</p>

<pre><code>$ ps -p 1 ef
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /usr/lib/systemd/systemd --switched-root --system --deserialize 24
</code></pre>

<p>늘 익숙했던 init 대신에 systemd가 PID 1로 자리잡고 있다.</p>

<h3 id="2-설정파일">2. 설정파일</h3>

<p>systemd는 /etc/systemd 아래에 설정파일을 두고 있다.</p>

<pre><code>$ ls /etc/systemd
bootchart.conf  journald.conf  logind.conf  system  system.conf  user  user.conf
</code></pre>

<p>이러한 설정파일과 시스템에 미리 정의 된 Service, Target 파일을 통해서 시스템을 컨트롤하게 되는데 Service, Target 파일은 아래에 정의 되어있으며 보통 /etc/systemd에서 설정하면서 심볼릭 링크를 통해서 사용한다.</p>

<pre><code>바이너리 실행파일은 아래 경로에서 확인 가능하며
$ ls /lib/systemd/

기본적인 시스템의 Service, Target은 아래에 위치하고 있다
$ ls /lib/systemd/system
</code></pre>

<h3 id="3-부팅시간">3. 부팅시간</h3>

<p>systemd는 최소한의 서비스만을 실행시키고 병렬화해서 실행시키는데 주안점을 두고 있기 때문에 기존에 순차적 방식으로 처리하는 SysV에 비해서 부팅속도가 빠른 편이다. RHEL7이 RHEL6보다 부팅속도가 매우 빠른건 systemd 때문이다.</p>

<p>그러면, 부팅에 걸린 시간을 알아보자</p>

<pre><code>$ systemd-analyze
Startup finished in 421ms (kernel) + 1.206s (initrd) + 25.873s (userspace) = 27.501s
</code></pre>

<p>총, 27초 정도가 소요 된 것을 확인 할 수 있는데 커널 초기화 작업에는 1초미만 램디스크 초기화에 1.2초 그리고 실제 systemd 프로세스에 의해서 초기화 작업이 진행 된 시간은 26초 정도 이다. 이를 토대로 부팅 시간을 단축 시킬(즉, 불필요한 프로세스가 있는지 여부부터 오동작으로 인해 시간을 많이 잡아먹는지) 방안에 대해서 생각해 볼 수 있다.</p>

<pre><code>$ systemd-analyze blame
         20.732s kdump.service
          1.395s firewalld.service
          1.040s postfix.service
          1.031s lvm2-monitor.service
           997ms tuned.service
           974ms boot.mount
           782ms network.service
           588ms lvm2-pvscan@8:2.service
           571ms iprupdate.service
           571ms iprinit.service
           423ms sshd.service
           348ms systemd-logind.service
           324ms avahi-daemon.service
           312ms iprdump.service
           296ms NetworkManager.service
           269ms rsyslog.service
           192ms systemd-fsck-root.service
           191ms kmod-static-nodes.service
           ... 하략 ...
</code></pre>

<p>상기 결과는 가상머신에 올린 게스트 머신이기 때문인지 kdump 서비스가 차지하는 시간이 많은 것으로 나타났다. 이 blame 결과를 통해서 불필요한 서비스를 제거하거나 이상이 있는 서비스를 확인해 볼 수 있다. (<a href="http://0pointer.de/blog/projects/blame-game.html" target="_blank">Blame Game</a> 참고)</p>

<p>또한, 시간이 많이 소요된 서비스에 대해 실행과 대기에 대해서 체인형태로 확인하는 방법도 있다.</p>

<pre><code>$ systemd-analyze critical-chain
The time after the unit is active or started is printed after the &quot;@&quot; character.
The time the unit takes to start is printed after the &quot;+&quot; character.

multi-user.target @25.865s
└─kdump.service @5.131s +20.732s
  └─network.target @5.130s
    └─network.service @4.346s +782ms
      └─NetworkManager.service @4.049s +296ms
        └─firewalld.service @2.649s +1.395s
          └─basic.target @2.646s
            └─paths.target @2.646s
              └─brandbot.path @2.645s
                └─sysinit.target @2.638s
                  └─systemd-update-utmp.service @2.630s +7ms
                    └─auditd.service @2.517s +111ms
                      └─local-fs.target @2.513s
                        └─boot.mount @1.538s +974ms
                          └─systemd-fsck@dev-disk-by\x2duuid-b4f107ad\x2df256\x2d48b4\x2d9558\x2d24
                            └─dev-disk-by\x2duuid-b4f107ad\x2df256\x2d48b4\x2d9558\x2d2483cbca6a7d.
</code></pre>

<p>그 외에 systemd-analyze 툴을 통해서 부팅 과정을 그래프화 해서 볼 수 있으며</p>

<pre><code>$ systemd-analyze dot | dot -Tsvg &gt; systemd.svg
$ systemd-analyze plot &gt; systemd.svg
</code></pre>

<p>이러한 부팅 분석 툴만으로도 기존 init에 비해서 프로파일링이 편리해졌음을 확인 할 수 있다.</p>

<h3 id="4-run-level-변경">4. Run Level 변경</h3>

<p>systemd는 기존 init 커맨드와 달리 숫자 기반의 런레벨이 아니라 각 런레벨에 대한 설정 세트를 통해서 런레벨을 변경합니다.</p>

<h4 id="싱글모드-기존-런레벨1">싱글모드(기존 런레벨1)</h4>

<pre><code>$ systemctl rescue
</code></pre>

<h4 id="멀티유저모드-기존-런레벨3">멀티유저모드(기존 런레벨3)</h4>

<pre><code>$ systemctl isolate multi-user.target
$ systemctl isolate runlevel3.target
</code></pre>

<p>과거 init 시스템에 익숙한 사용자를 위해서 runlevel3라는 이름으로 multi-user.target 파일을 심볼릭 링크를 걸어두었기 때문에 위 두가지 명령이 모두 사용가능 하다.</p>

<pre><code>$ ls -l /lib/systemd/system/runlevel3.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 /lib/systemd/system/runlevel3.target -&gt; multi-user.target
</code></pre>

<h4 id="그래픽모드-기존-런레벨5">그래픽모드(기존 런레벨5)</h4>

<pre><code>$ systemctl isolate graphical.target
$ systemctl isolate runlevel5.target
</code></pre>

<p>멀티유저모드와 마찬가지로 2가지 명령으로 전환 가능하며 실제 기존 형태의 런레벨+숫자 형태의 Target 파일은 아래와 같이 심볼릭 링크로 연결되어있다.</p>

<pre><code>lrwxrwxrwx. 1 root root 15 Oct 21 00:28 runlevel0.target -&gt; poweroff.target
lrwxrwxrwx. 1 root root 13 Oct 21 00:28 runlevel1.target -&gt; rescue.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel2.target -&gt; multi-user.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel3.target -&gt; multi-user.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel4.target -&gt; multi-user.target
lrwxrwxrwx. 1 root root 16 Oct 21 00:28 runlevel5.target -&gt; graphical.target
lrwxrwxrwx. 1 root root 13 Oct 21 00:28 runlevel6.target -&gt; reboot.target
</code></pre>

<p>즉, 시스템 종료/재부팅을 위한 런레벨도 여전히 사용가능하다.</p>

<h4 id="런레벨-기본-값-설정">런레벨 기본 값 설정</h4>

<p>상기에서 전환하는 런레벨 Target을 아래와 같은 명령을 통해서 기본 값으로 설정 할 수 있다. 또한, 현재 설정된 기본 값을 확인 할 수도 있다</p>

<pre><code>$ systemctl set-default multi-user.target
$ systemctl get-default
multi-user.target
</code></pre>

<h4 id="시스템-명령">시스템 명령</h4>

<p>앞서 각각의 런레벨 파일이 poweroff.target 등 으로 연결되어있는 것을 확인하였는데 isolate 명령이 아닌 시스템 명령을 통해서 해당 Target을 바로 적용하는게 가능하다. 아래는 몇 가지 예시이다.</p>

<pre><code>$ systemctl poweroff (Shutdown처리 후 Power-Off처리)
$ systemctl emergency (Rescue와 유사하지만 root 파일시스템만 읽기전용으로 마운트한다)
$ systemctl halt (Shutdown처리 후 Halt처리)
$ systemctl reboot (Shutdown처리 후 리부팅처리)
$ systemctl kexec (kexec를 통해서 리부팅한다)
$ systemctl suspend (시스템 정지)
$ systemctl hibernate (시스템 Hibernate)
$ systemctl hybrid-sleep (시스템을 Hibernate하고 정지시킨다)
</code></pre>

<h3 id="5-서비스-목록">5. 서비스 목록</h3>

<p>systemctl을 통해서 서비스를 관리 할 수 있는데 먼저 서비스 목록 확인 방법을 알아보자.</p>

<p>서비스 목록은 간단하게 <code>systemctl</code> 명령만 실행해도 확인 할 수 있으며 이를 상태기준으로 보기편하게 아래와 같이 확인 할 수도 있다.</p>

<pre><code>$ systemctl list-unit-files
UNIT FILE                                   STATE
sys-fs-fuse-connections.mount               static
sys-kernel-config.mount                     static
sys-kernel-debug.mount                      static
tmp.mount                                   disabled
brandbot.path                               disabled
systemd-ask-password-console.path           static
systemd-ask-password-plymouth.path          static
systemd-ask-password-wall.path              static
session-1.scope                             static
session-2.scope                             static
auditd.service                              enabled
autovt@.service                             disabled
avahi-daemon.service                        enabled
... 하략 ...
</code></pre>

<p>그 외에 Listening하는 소켓관련 목록을 확인 할 수도 있고 각 서비스를 의존성에 따라 확인 할 수도 있다.</p>

<pre><code>$ systemctl list-sockets
LISTEN                          UNIT                         ACTIVATES
/dev/initctl                    systemd-initctl.socket       systemd-initctl.service
/dev/log                        systemd-journald.socket      systemd-journald.service
/run/dmeventd-client            dm-event.socket              dm-event.service
/run/dmeventd-server            dm-event.socket              dm-event.service
/run/lvm/lvmetad.socket         lvm2-lvmetad.socket          lvm2-lvmetad.service
/run/systemd/journal/socket     systemd-journald.socket      systemd-journald.service
/run/systemd/journal/stdout     systemd-journald.socket      systemd-journald.service
/run/systemd/shutdownd          systemd-shutdownd.socket     systemd-shutdownd.service
/run/udev/control               systemd-udevd-control.socket systemd-udevd.service
/var/run/avahi-daemon/socket    avahi-daemon.socket          avahi-daemon.service
/var/run/dbus/system_bus_socket dbus.socket                  dbus.service
kobject-uevent 1                systemd-udevd-kernel.socket  systemd-udevd.service

12 sockets listed.
</code></pre>

<p>list-dependencies의 경우 뒤에 Service/Target 이름을 별도로 지정 가능하다.</p>

<pre><code>$ systemctl list-dependencies swap.target
swap.target
├─dev-disk-by\x2did-dm\x2dname\x2drhel\x2dswap.swap
├─dev-disk-by\x2did-dm\x2duuid\x2dLVM\x2dWu6fS25DomohkfmsDzYY8SzAfEPmpojKfhfiV6D6AYa86f2Bb7nOkSq...
├─dev-disk-by\x2duuid-c3591b93\x2d0cc0\x2d457c\x2db1f5\x2d79ea0658d54d.swap
├─dev-dm\x2d1.swap
├─dev-mapper-rhel\x2dswap.swap
├─dev-mapper-rhel\x2dswap.swap
└─dev-rhel-swap.swap
</code></pre>

<h4 id="실패-서비스-확인">실패 서비스 확인</h4>

<p>부팅하는 과정에서 실패한 서비스에 대해서 아래와 같이 확인이 가능하다. 또한, 위에서 언급되었던 list-sockets 같은 명령에도 옵션으로 지정하여 실패한 항목을 확인 할 수 있다.</p>

<pre><code>$ systemctl --failed
systemctl --failed
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
rhnsd.service loaded failed failed LSB: Starts the Spacewalk Daemon

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

1 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.
</code></pre>

<h3 id="6-서비스-관리">6. 서비스 관리</h3>

<p>서비스를 설정하고 관리하는 방법은 기본적으로 아래와 같다.</p>

<pre><code>- 서비스 활성화
$ systemctl enable [서비스명]

- 서비스 비활성화
$ systemctl disable [서비스명]

- 서비스 시작
$ systemctl start [서비스명]

- 서비스 종료
$ systemctl stop [서비스명]

- 서비스 재시작
$ systemctl restart [서비스명]

- 서비스 갱신
$ systemctl reload [서비스명]
</code></pre>

<p>그리고, 각각의 서비스에 대해서 부팅 때 실행되도록 설정 되었는지 여부 및 현재 실행 여부 등을 확인 할 수 있으며 간단한 응답으로 종료하기 때문에 스크립트 작성할 때 좋다.</p>

<pre><code>$ systemctl is-enabled [서비스명]
$ systemctl is-active [서비스명]

예시)
$ systemctl is-enabled crond
enabled
$ systemctl is-active auditd
active
</code></pre>

<p>이렇게 변경한 서비스 설정 정보를 데몬에 반영하기 위해서는 아래와 같이 실행하면 된다</p>

<pre><code>$ systemctl daemon-reload
</code></pre>

<p>서비스 상태도 확인이 가능한데 이 상태 확인은 기존 init 스크립트가 제공하던 실행 여부 이상으로 각 서비스의 CGroup 관련 정보 및 로그정보까지 확인이 가능하다. (-ㅣ 옵션은 한 줄을 넘어서는 라인을 축약하지 말고 전부 보여주라는 옵션이다)</p>

<pre><code>$ systemctl status crond -l
crond.service - Command Scheduler
   Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled)
   Active: active (running) since Tue 2014-10-21 00:31:58 EDT; 2h 10min ago
 Main PID: 583 (crond)
   CGroup: /system.slice/crond.service
           └─583 /usr/sbin/crond -n

Oct 21 00:31:58 localhost.localdomain systemd[1]: Started Command Scheduler.
Oct 21 00:31:58 localhost.localdomain crond[583]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 2% if used.)
Oct 21 00:31:59 localhost.localdomain crond[583]: (CRON) INFO (running with inotify support)
</code></pre>

<p>상태확인 뿐만 아니라 <code>kill</code> 명령을 통해서 서비스 관련 모든 프로세스에 kill 시그널을 보낼 수도 있다.</p>

<pre><code>$ systemctl kill httpd

웹서버(http) 관련 프로세스가 모두 죽어있음을 확인할 수 있다.
</code></pre>

<h3 id="7-로그-관리">7. 로그 관리</h3>

<p>systemd는 단순한 init 대체제가 아니라 시스템 전반적인 부분을 관리하는 프로그램이기 때문에 로그에 대한 관리 부분도 있다. 로그는 <code>systemd-journald</code>를 통해서 관리되며 이를 제어하는 툴은 <code>journalctl</code>이다.</p>

<p>단순히 전체 이벤트 로그를 확인하기 위해서는 journalctl 만 실행하면 되며 몇 가지 유용한 커맨드를 소개한다.</p>

<h4 id="바이너리에-대한-이벤트">바이너리에 대한 이벤트</h4>

<p>프로세스로 실행이 가능한 특정 바이너리에 대한 이벤트는 아래와 같이 확인 할 수 있다.</p>

<pre><code>$ journalctl /sbin/crond
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:58 localhost.localdomain crond[583]: (CRON) INFO (RANDOM_DELAY will be scaled with fac
Oct 21 00:31:59 localhost.localdomain crond[583]: (CRON) INFO (running with inotify support)
lines 1-3/3 (END)
</code></pre>

<h4 id="기간-조회">기간 조회</h4>

<p>특정 일자부터의 이벤트 로그를 확인하는 방법은 아래와 같은데</p>

<pre><code>$ journalctl --since=today
</code></pre>

<p>today 대신에 yesterday, tomorrow 같은 단어도 가능하다. 또한, &ldquo;YYYY-MM-DD HH:MM:SS&rdquo; 형태의 시간 값을 이용해서 구간 별 조회가 가능한데 시간을 생략하면 0시0분0초를 기준으로 하게 된다.</p>

<pre><code>$ journalctl --since=2014-10-21 --until=2014-10-22
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:54 localhost.localdomain systemd-journal[81]: Runtime journal is using 8.0M (max 92.0M
Oct 21 00:31:54 localhost.localdomain systemd-journal[81]: Runtime journal is using 8.0M (max 92.0M
Oct 21 00:31:54 localhost.localdomain kernel: Initializing cgroup subsys cpuset
</code></pre>

<p>마지막 부팅 이후의 로그는 <strong>-b</strong> 옵션으로 확인이 가능하며</p>

<pre><code>$ journalctl -b
</code></pre>

<h4 id="속성에-따른-조회">속성에 따른 조회</h4>

<p>특정, 우선순위에 따른 (syslog에서 지정하는 debug, info, err 등) 조회도 가능하다.</p>

<pre><code>$ journalctl -p err
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:54 localhost.localdomain kernel: Detected CPU family 6 model 69
Oct 21 00:31:54 localhost.localdomain kernel: Warning: Intel CPU model - this hardware has not unde
Oct 21 00:31:54 localhost.localdomain kernel: tsc: Fast TSC calibration failed
Oct 21 00:31:54 localhost.localdomain systemd-fsck[260]: fsck failed with error code 8.
Oct 21 00:31:56 localhost.localdomain kernel: piix4_smbus 0000:00:07.0: SMBus base address uninitia
Oct 21 00:31:58 localhost.localdomain audispd[544]: No plugins found, exiting
Oct 21 00:32:00 localhost.localdomain systemd[1]: Failed to start LSB: Starts the Spacewalk Daemon.
lines 1-8/8 (END)
</code></pre>

<h4 id="기타-옵션들">기타 옵션들</h4>

<p>이 외에 <strong>tail -f</strong>로 로그파일을 걸어둔 것 같은 효과를 갖는 <strong>-f</strong> 옵션과 json을 비롯한 다양한 포맷으로 로그를 재포매팅 하는 옵션인 <strong>-o</strong> 옵션 등이 있다.</p>

<pre><code>$ journalctl -f
$ journalctl -p err -o json-pretty
{
        &quot;__CURSOR&quot; : &quot;s=ee396e27b84346d4a5163e52bb6a839c;i=5b;b=03fa23106cc04ce99a97bf6a5e45e6aa;m=
        &quot;__REALTIME_TIMESTAMP&quot; : &quot;1413865914539904&quot;,
        &quot;__MONOTONIC_TIMESTAMP&quot; : &quot;459391&quot;,
        &quot;_BOOT_ID&quot; : &quot;03fa23106cc04ce99a97bf6a5e45e6aa&quot;,
        &quot;_MACHINE_ID&quot; : &quot;a73fc4e71dd64fe98f580bffe567ea29&quot;,
        &quot;_HOSTNAME&quot; : &quot;localhost.localdomain&quot;,
        &quot;_SOURCE_MONOTONIC_TIMESTAMP&quot; : &quot;0&quot;,
        &quot;_TRANSPORT&quot; : &quot;kernel&quot;,
        &quot;SYSLOG_IDENTIFIER&quot; : &quot;kernel&quot;,
        &quot;PRIORITY&quot; : &quot;2&quot;,
        &quot;MESSAGE&quot; : &quot;Detected CPU family 6 model 69&quot;
}
</code></pre>

<h3 id="8-cgroup-관리">8. CGroup 관리</h3>

<h4 id="리소스-정보-조회">리소스 정보 조회</h4>

<p>systemd에는 CGroup(control group)을 관리하는 기능도 포함되어있다. (홈페이지에서 문서를 읽다보면 없는게 있을까 싶을 정도로 너무 많은 기능을 가지고 있다. 문서 서두에 이야기 한 것 처럼 이로 인해 Unix 철학과 대치 된다고 논란이 있던 프로그램이다)</p>

<p>먼저 <code>systemd-cgls</code> 명령은 현재 cgroup에 대한 정보를 타입별로 출력해준다.</p>

<pre><code>$ systemd-cgls
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 24
├─user.slice
│ ├─user-1000.slice
│ │ └─session-2.scope
│ │   ├─10191 sshd: lunatine [priv
│ │   ├─10195 sshd: lunatine@pts/0
│ │   └─17794 systemd-cgls
│ └─user-0.slice
│   └─session-1.scope
│     ├─ 595 login -- root
│     └─7805 -bash
└─system.slice
  ├─httpd.service
  │ ├─17779 /usr/sbin/httpd -DFOREGROUND
  │ ├─17780 /usr/sbin/httpd -DFOREGROUND
  │ ├─17781 /usr/sbin/httpd -DFOREGROUND
  │ ├─17782 /usr/sbin/httpd -DFOREGROUND
  │ ├─17783 /usr/sbin/httpd -DFOREGROUND
  │ └─17784 /usr/sbin/httpd -DFOREGROUND
... 하략 ...
</code></pre>

<p><code>systemd-cgtop</code>의 경우는 흔히 알고 있는 <strong>top</strong> 명령과 같이 cgroup에 대하여 CPU, Memory, I/O에 대한 정렬 결과를 출력해 준다. 본인이 설정한 cgroup에 대해 적합하게 리소스가 분배되고 있는지 확인하는데 유용하다.</p>

<h4 id="리소스-관리">리소스 관리</h4>

<p>앞서 살펴보았던 systemctl의 경우 set-property 명령을 통해서 리소스 값을 제어할 수 있는데 실제로는 systemctl로 설정하면 <strong>systemd.resource-control</strong>이라는 프로그램이 해당 리소스 할당작업을 수행한다.</p>

<pre><code>$ systemctl set-property httpd.service CPUShares=200
$ systemctl show -p CPUShares httpd.service
CPUShares=200
$ cat /sys/fs/cgroup/cpu/system.slice/httpd.service/cpu.shares
200
</code></pre>

<p>systemctl을 통해서 설정을 하지만 실제로 systemd.resource-control에 의해서 설정되기 때문에 상세한 설정 옵션에 대해서는 systemd.resource-control의 man 페이지를 확인해야 한다.</p>

<p>이러한 리소스관리 툴도 포함되어있기 때문에 cgroup 설정을 위한 스크립트 작성이 한결 간편해지고 체계적이 될 수 있다.</p>

<h3 id="9-호스트명-설정">9. 호스트명 설정</h3>

<p>systemd에는 hostnamectl이란 툴도 있는데 이 툴로 호스트명 설정도 가능하다.</p>

<pre><code>$ hostnamectl
   Static hostname: rhel7
         Icon name: computer
           Chassis: n/a
        Machine ID: a73fc4e71dd64fe98f580bffe567ea29
           Boot ID: 711ec89043a543fa8751aa686257dd81
    Virtualization: oracle
  Operating System: Red Hat Enterprise Linux Server 7.0 (Maipo)
       CPE OS Name: cpe:/o:redhat:enterprise_linux:7.0:GA:server
            Kernel: Linux 3.10.0-123.el7.x86_64
      Architecture: x86_64

$ hostnamectl set-hostname new-rhel7
</code></pre>

<p>위 명령은 /etc/hostname 설정파일을 변경하게 된다. 옵션 중에 <code>--transient</code>는 DHCP, mDNS에 의해서 변경가능한 커널에 의해 관리되는 호스트명을 수정하고 <code>--pretty</code>는 UTF-8 인코딩으로 호스트명을 지정한다.</p>

<h3 id="10-로케일-설정">10. 로케일 설정</h3>

<p>systemd에 포함된 localectl은 시스템의 로케일을 설정한다. 현재 설정된 정보는 localectl 실행 결과로 확인 할 수 있다.</p>

<pre><code>$ localectl
   System Locale: LANG=en_US.UTF-8
       VC Keymap: us
      X11 Layout: us
</code></pre>

<p>로케일 변경은 <code>set-locale</code>로 키맵은 <code>set-keymap</code>, X서버를위한 키맵은 <code>set-x11-keymap</code>으로 변경 가능하다. 이 툴은 /usr/lib/locale/locale-archive 정보를 바탕으로 /var/run/dbus/system_bus_socket을 통해서 변경을 수행한다.</p>

<pre><code>$ localectl set-locale LANG=ko_KR.UTF-8
$ localectl set-keymap fr
$ localectl set-x11-keymap fr
$ localectl
   System Locale: LANG=ko_KR.UTF-8
       VC Keymap: fr
      X11 Layout: fr
</code></pre>

<p>변경가능한 로케일, 키맵 등은 <code>list-locales</code>, <code>list-keymaps</code>, <code>list-x11-keymap-models</code>, <code>list-x11-keymap-layouts</code>, <code>list-x11-keymap-variants</code>, <code>list-x11-keymap-options</code>으로 확인 가능하다.</p>

<h3 id="11-사용자-관리">11. 사용자 관리</h3>

<p>loginctl 툴을 이용해서 현재 사용자 세션에 대해서 관리가 가능하다.</p>

<pre><code>$ loginctl list-users
       UID USER
      1000 lunatine

1 users listed.
</code></pre>

<p><code>list-session</code>으로 현재 세션들을 확인 할 수 있으며 <code>lock-session</code> 등의 명령으로 세션을 잠글 수 있다. 또한, <code>show-user</code>를 통해 사용자 정보 조회도 가능하고 <code>kill-user</code>를 통해서 사용자 프로세스에 시그널을 보낼 수도 있다.</p>

<p>자세한 내용은 <code>loginctl --help</code>로 확인해 보도록 하자.</p>

<h3 id="12-시간-설정">12. 시간 설정</h3>

<p>timedatectl은 시간대를 조회하고 설정하는 기능을 제공한다. <code>set-time</code>은 시간을 <code>set-timezone</code>은 시간대를 설정한다. 또한, <code>set-ntp</code>를 통해 NTP 활성화 여부도 설정이 가능하다.</p>

<pre><code>$ timedatectl
      Local time: Tue 2014-10-21 09:45:29 EDT
  Universal time: Tue 2014-10-21 13:45:29 UTC
        RTC time: Tue 2014-10-21 13:45:29
        Timezone: America/New_York (EDT, -0400)
     NTP enabled: n/a
NTP synchronized: no
 RTC in local TZ: no
      DST active: yes
 Last DST change: DST began at
                  Sun 2014-03-09 01:59:59 EST
                  Sun 2014-03-09 03:00:00 EDT
 Next DST change: DST ends (the clock jumps one hour backwards) at
                  Sun 2014-11-02 01:59:59 EDT
                  Sun 2014-11-02 01:00:00 EST

$ timedatectl set-timezone Asia/Seoul
$ timedatectl
      Local time: Tue 2014-10-21 22:46:04 KST
  Universal time: Tue 2014-10-21 13:46:04 UTC
        RTC time: Tue 2014-10-21 13:46:04
        Timezone: Asia/Seoul (KST, +0900)
     NTP enabled: n/a
NTP synchronized: no
 RTC in local TZ: no
      DST active: n/a
</code></pre>

<h3 id="13-원격-관리">13. 원격 관리</h3>

<p>systemd의 모든 명령어들은 <strong>-H</strong>옵션을 제공하는데 이 옵션을 통해서 원격지 서버에 ssh 접속을 통해 설정 및 정보조회가 가능하다. 아래 예제는 원격지 서버의 호스트명을 수정하는 내용이다.</p>

<pre><code>$ hostnamectl -H root@rhel7.test.com set-hostname rhel7-new
root@rhel7.test.com's password:

$ hostnamectl -H root@rhel7.test.com
root@rhel7's password:
   Static hostname: rhel7-new
         Icon name: computer
           Chassis: n/a
        Machine ID: a73fc4e71dd64fe98f580bffe567ea29
           Boot ID: 711ec89043a543fa8751aa686257dd81
    Virtualization: oracle
  Operating System: Red Hat Enterprise Linux Server 7.0 (Maipo)
       CPE OS Name: cpe:/o:redhat:enterprise_linux:7.0:GA:server
            Kernel: Linux 3.10.0-123.el7.x86_64
      Architecture: x86_64
</code></pre>

<h2 id="마치며">마치며</h2>

<p>본 문서에서 systemd의 기능들을 간단히 알아보았다. systemd는 마치 맥가이버칼 처럼 다양한 시스템 설정 기능을 포함하고 있으며 계속해서 개선되고 추가되고 있다. Unix의 &lsquo;한 가지만 잘하자&rsquo; 철학에 위배되는 프로그램일지 모르지만 실제는 내부적으로 한가지 일을 잘 하는 툴 들로 구성되어있기 때문에 위배라고 보기도 어렵다.</p>

<p>그리고 systemd는 기존 System V Init에 익숙한 사용자를 위해 디렉토리 구조 및 호환성을 일부 제공하고 있다. 심볼릭 링크이지만 init도 존재하고 0123456qQuUsS 옵션도 제공한다. 이 때문에 runlevel$.target 심볼릭링크 파일이 존재한다.</p>

<p>대표적인 리눅스 배포판에서 선택되었으며 이제는 엔터프라이즈 리눅스 배포판에도 선택이 되었다. 머지 않아 Debian 계열 배포판에서도 기본으로 채택이 될 것이라고 하니 이제 슬슬 System V Init을 떠나 보낼 때가 왔나보다.</p>

<p>기존 시스템 관리에 사용하던 자동화 스크립트를 systemd에 맞춰서 수정 할 일만 남았다.</p>

<p><strong>끝&hellip;</strong></p>

<p>p.s: 나중에 기회가 되면 systemd의 활용 및 트러블슈팅에 대해서도 정리해 볼 생각이다.</p>
]]></content>
        </item>
        
        <item>
            <title>Linux : 기호 연결의 단계가 너무 많음</title>
            <link>/2014/08/19/linux-too-many-levels-of-symbolic-links/</link>
            <pubDate>Tue, 19 Aug 2014 05:55:07 +0000</pubDate>
            
            <guid>/2014/08/19/linux-too-many-levels-of-symbolic-links/</guid>
            <description>무슨 의미지? Linux 시스템을 사용하다가 &amp;ldquo;기호 연결의 단계가 너무 많음&amp;rdquo;이라는 메시지를 만나게 되는 경우가 있다. 사람에 따라 다르겠지만 주변 반응으로 보아서 쉽게 의미가 파악되지 않는 것으로 보인다. 사실 ko_KR.UTF-8 로케일처럼 한글기반 언어설정이 된 시스템에서만 볼 수 있는 메시지로 영문 메시지는 아래와 같다.
Too many levels of symbolic links
즉, 심볼릭링크의 단계가 너무 많아서 발생하는 오류다. 대부분 이 경우는 무한반복(Self Looping)형태로 링크가 걸린 심볼릭링크 파일 때문에 발생한다. 예를 들면, 아래와 같이</description>
            <content type="html"><![CDATA[

<h2 id="무슨-의미지">무슨 의미지?</h2>

<p>Linux 시스템을 사용하다가 &ldquo;기호 연결의 단계가 너무 많음&rdquo;이라는 메시지를 만나게 되는 경우가 있다. 사람에 따라 다르겠지만 주변 반응으로 보아서 쉽게 의미가 파악되지 않는 것으로 보인다. 사실 ko_KR.UTF-8 로케일처럼 한글기반 언어설정이 된 시스템에서만 볼 수 있는 메시지로 영문 메시지는 아래와 같다.</p>

<p><code>Too many levels of symbolic links</code></p>

<p>즉, 심볼릭링크의 단계가 너무 많아서 발생하는 오류다. 대부분 이 경우는 무한반복(Self Looping)형태로 링크가 걸린 심볼릭링크 파일 때문에 발생한다. 예를 들면, 아래와 같이</p>

<pre><code>[tuser@localhost /test/files]$ ls -l
-rw-r--r-- 1 root     root        126 Aug 14 10:57 Terminal
-rw-r--r-- 1 root     root      92261 Aug 14 10:57 Key
-rw-r--r-- 1 root     root      15289 Aug 14 10:57 Text_Message
lrwxrwxrwx 1 root     root         30 Aug 14 10:57 apps -&gt; /test/files/apps
</code></pre>

<p>/test/files 아래에 있는 apps 파일이 /test/files/apps를 가리키게 되면 자기 자신을 가리키므로 이 파일을 가지고 어떤 액션을 취하거나 현재 디렉토리에서 액션을 취할 때 오류가 발생하게 된다. (만약, 보통의 Linux 배포판의 터미널이라면 해당 파일의 링크가 붉은색으로 문제가 있는 것 으로 보일 것이다) 해당 파일에 대해서는 가볍게 unlink 시켜주면 된다.</p>

<h2 id="결과적으로">결과적으로</h2>

<p>&ldquo;기호 연결의 단계가 너무 많음&rdquo; 이라는 번역이 나쁘다고 할 수 없지만 조금 아쉬운 건 사실. 아무래도, 기호 연결이라는 단어보다 심볼릭링크라는 단어가 더 익숙해서 그럴지도 모르겠다. 문득, kernel을 알맹이로 queue를 대롱으로 번역했던 책이 생각난다.</p>
]]></content>
        </item>
        
        <item>
            <title>[TIP] vi 편집기에서 root권한 파일저장</title>
            <link>/2014/08/12/tip-vi-write-with-root-privilege/</link>
            <pubDate>Tue, 12 Aug 2014 05:04:35 +0000</pubDate>
            
            <guid>/2014/08/12/tip-vi-write-with-root-privilege/</guid>
            <description>깜박잊고 일반유저 권한으로 파일을 열었다 vi편집기(Linux에서는 보통 vim)를 이용해서 편집하는 작업을 하다보면 가끔 겪는 일이있는데 root 권한으로 설정 된 파일을 실수로 일반유저 상태에서 열심히 수정하는 경우이다. 이럴 때면 :q! 명령으로 종료하고 다시 root권한으로 파일을 열어 수정작업을 하는 번거로움이 발생하는데 이를 만회하는 방법을 소개하고자 합니다.
vi 편집기의 명령모드 이용 일반유저 권한에서 vi편집기로 수정하고 아래 명령을 실행하면 sudo를 이용해서 root권한을 획득하여 변경사항을 반영시킬 수가 있다.
:w !sudo tee %  여기에서 %는 현재 파일명을 의미하며 tee 명령을 통해서 현재 수정된 내용을 stdin으로 전달하여 파일로 기록 할 수 있는 것이다.</description>
            <content type="html"><![CDATA[

<h2 id="깜박잊고-일반유저-권한으로-파일을-열었다">깜박잊고 일반유저 권한으로 파일을 열었다</h2>

<p>vi편집기(Linux에서는 보통 vim)를 이용해서 편집하는 작업을 하다보면 가끔 겪는 일이있는데 root 권한으로 설정 된 파일을 실수로 일반유저 상태에서 열심히 수정하는 경우이다. 이럴 때면 <strong>:q!</strong> 명령으로 종료하고 다시 root권한으로 파일을 열어 수정작업을 하는 번거로움이 발생하는데 이를 만회하는 방법을 소개하고자 합니다.</p>

<h2 id="vi-편집기의-명령모드-이용">vi 편집기의 명령모드 이용</h2>

<p>일반유저 권한에서 vi편집기로 수정하고 아래 명령을 실행하면 sudo를 이용해서 root권한을 획득하여 변경사항을 반영시킬 수가 있다.</p>

<pre><code>:w !sudo tee %
</code></pre>

<p>여기에서 %는 현재 파일명을 의미하며 tee 명령을 통해서 현재 수정된 내용을 stdin으로 전달하여 파일로 기록 할 수 있는 것이다. 위 명령을 수행하면 먼저 sudo를 통해 root유저 권한을 얻기위해 패스워드를 물어볼 것이고 패스워드를 입력하면 파일기록은 완료된다. 그 뒤에 파일이 변경되었다고 하며 변경사항을 반영해서 다시 열지 물어보는 프롬프트를 넘기고 <strong>:q!</strong> 로 종료하면 된다.</p>

<h2 id="vi-명령으로-등록">vi 명령으로 등록</h2>

<p>매번 위와 같이 입력하기 귀찮을 때는 .vimrc 파일에 아래와 같이 등록한다</p>

<pre><code>ca w!! w !sudo tee &quot;%&quot;
</code></pre>

<p>이제 이런경우에 <strong>:w!!</strong> 만 입력하면 sudo를 이용해서 저장하게 된다.</p>

<h2 id="sudo-등록유저가-아닐경우">sudo 등록유저가 아닐경우</h2>

<p>sudo 등록된 유저가 아닐경우에는 su 명령이 표준입력 처리를 제대로 해주지 않기 때문에 별도의 사본을 저장하는 방법을 사용하면 된다. (중요한건 열심히 수정했던 내용이기에)</p>

<pre><code>:w !cat &gt; /tmp/tfile
</code></pre>

<p>위와 같이 실행하면 /tmp/tfile에 현재 수정 된 내용을 저장하게 된다. 그리고 편집기를 빠져나와서 root 권한으로 원본을 덮어 씌우면 된다.</p>
]]></content>
        </item>
        
        <item>
            <title>brew error on Mac OS X 10.10 (yosemite)</title>
            <link>/2014/07/28/brew-error-on-mac-os-x-10-10-yosemite/</link>
            <pubDate>Mon, 28 Jul 2014 08:20:31 +0000</pubDate>
            
            <guid>/2014/07/28/brew-error-on-mac-os-x-10-10-yosemite/</guid>
            <description>brew error LunaMac:~ lunatine$ brew /usr/local/bin/brew: /usr/local/Library/brew.rb: /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby: bad interpreter: No such file or directory /usr/local/bin/brew: line 23: /usr/local/Library/brew.rb: Undefined error: 0  Mac OS X 10.10 Yoseimite가 Public Beta로 공개되어서 업데이트를 진행하였는데 brew가 제대로 동작하지 않았다. 오류메시지가 No such file or directory 인 것으로 보아 인터프리터 경로가 바뀐걸로 보인다.
그래서 Mac OS X의 Ruby 프레임워크 경로를 찾아보니 역시나 바뀌었다.
원인  기존 10.9 (Mavericks)에서는 Ruby 1.8 기준으로 인터프리터 경로가 설정되었으나 10.</description>
            <content type="html"><![CDATA[

<h1 id="brew-error">brew error</h1>

<pre><code>LunaMac:~ lunatine$ brew
/usr/local/bin/brew: /usr/local/Library/brew.rb: /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby: bad interpreter: No such file or directory
/usr/local/bin/brew: line 23: /usr/local/Library/brew.rb: Undefined error: 0
</code></pre>

<p>Mac OS X 10.10 Yoseimite가 Public Beta로 공개되어서 업데이트를 진행하였는데 brew가 제대로 동작하지 않았다. 오류메시지가 <strong>No such file or directory</strong> 인 것으로 보아 인터프리터 경로가 바뀐걸로 보인다.</p>

<p>그래서 Mac OS X의 Ruby 프레임워크 경로를 찾아보니 역시나 바뀌었다.</p>

<h2 id="원인">원인</h2>

<ul>
<li>기존 10.9 (Mavericks)에서는 Ruby 1.8 기준으로 인터프리터 경로가 설정되었으나 10.10으로 넘어오면서 Ruby는 2.0으로 업그레이드 되었고 해당 경로에 대해서 Current로 심볼릭 링크가 되어있기 때문에 기존 brew.rb가 제대로 실행되지 않은 것이다.</li>
</ul>

<h2 id="해결방법">해결방법</h2>

<ul>
<li><p>먼저 인터프리터의 경로를 수정한다</p>

<pre><code>$ vi /usr/local/Library/brew.rb


[File: brew.rb]
#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -W0

첫 번째 줄의 1.8 버전 경로를 아래와 같이 수정해 주고 저장한다.

#!/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby -W0
</code></pre></li>

<li><p>기존 brew 환경에 인터프리터를 수정한 내용을 반영하기 위해 아래의 명령을 수행한 후에 brew update를 하면 된다.</p>

<pre><code>$ cd $(brew --repository)
$ git commit -a -m 'yosemite update'
$ brew update
</code></pre></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>[Mac OS X] Wireshark 화면 문제</title>
            <link>/2014/07/09/wireshark-on-mac-os-x-display-error/</link>
            <pubDate>Wed, 09 Jul 2014 09:00:52 +0000</pubDate>
            
            <guid>/2014/07/09/wireshark-on-mac-os-x-display-error/</guid>
            <description>Wireshark on Mac os X  본 문서는 Wireshark를 Mac에서 사용할 때 거의 겪을일이 없는 증상에 대해서 소개하고 있습니다
 예전부터 Mac OS X에 Wireshark를 설치하려면 X11 서버가 필요했고 폰트 모양이 이쁘지 않아 설정파일을 변경하는 등의 작업을 해야만 했다. 최근에 Wireshark를 쓸 일이 생겨서 최신버전을 다운받아 설치하니 아래 gtk 설정파일에 폰트가 &amp;ldquo;Lucida Grande&amp;rdquo;로 이쁘게 잘 들어가 있었다. (개발사에서도 이런 불만사항을 반영했나보다)
$ vi /Applications/Wireshark.app/Contents/Resources/themes/Clearlooks-Quicksilver-OSX/gtk-2.0/pre_gtkrc [폰트 설정] gtk-font-name=&amp;quot;Lucida Grande 12&amp;quot;  그런데, 아래 화면처럼 안보이는 증상이 발생했다.</description>
            <content type="html"><![CDATA[

<h1 id="wireshark-on-mac-os-x">Wireshark on Mac os X</h1>

<blockquote>
<p>본 문서는 Wireshark를 Mac에서 사용할 때 거의 겪을일이 없는 증상에 대해서 소개하고 있습니다</p>
</blockquote>

<p>예전부터 Mac OS X에 Wireshark를 설치하려면 X11 서버가 필요했고 폰트 모양이 이쁘지 않아 설정파일을 변경하는 등의 작업을 해야만 했다. 최근에 Wireshark를 쓸 일이 생겨서 최신버전을 다운받아 설치하니  아래 gtk 설정파일에 폰트가 &ldquo;Lucida Grande&rdquo;로 이쁘게 잘 들어가 있었다. (개발사에서도 이런 불만사항을 반영했나보다)</p>

<pre><code>$ vi /Applications/Wireshark.app/Contents/Resources/themes/Clearlooks-Quicksilver-OSX/gtk-2.0/pre_gtkrc

[폰트 설정]
gtk-font-name=&quot;Lucida Grande 12&quot;
</code></pre>

<p>그런데, 아래 화면처럼 안보이는 증상이 발생했다.</p>

<p><img src="/images/2014/Jul/20140709-03.png" alt="" /></p>

<p>처음에는 폰트 설정 문제인 줄 알았으나 원인은 아래와 같았다.</p>

<p><img src="/images/2014/Jul/20140709-04.png" alt="" /></p>

<p>X11 기반의 어플리케이션을 쓸 일이 xterm 외에는 거의 없어서 &ldquo;256색&rdquo;으로 설정해 둔 것이 문제였다. 이를 &ldquo;모니터에서&rdquo;로 고쳐주고 재시작하면 아래와 같이 정상적으로 잘 나온다.</p>

<p><img src="/images/2014/Jul/20140709-02.png" alt="" /></p>

<p>XQuartz를 별도로 설정하지 않는 이상 겪을일이 없는 문제이지만 혹시나 몰라서 문서로 남겨둔다.</p>
]]></content>
        </item>
        
        <item>
            <title>Docky에서 Chrome 아이콘 문제</title>
            <link>/2014/06/23/chrome-icon-error-on-docky/</link>
            <pubDate>Mon, 23 Jun 2014 11:12:26 +0000</pubDate>
            
            <guid>/2014/06/23/chrome-icon-error-on-docky/</guid>
            <description>Google Chrome 아이콘이 Docky에서 분리되는 문제  본 문서는 Google Chrome을 실행하였을 때 Docky에서 저해상도 아이콘으로 표시 될 뿐만 아니라 프로그램 메뉴에서 등록한 것과 분리되는 문제를 해결하기 위한 내용을 담고 있습니다
 증상  프로그램 메뉴에서 Google Chrome을 끌어다가 Docky에 등록하여 실행하였으나 별도 아이콘(저해상도)으로 분리되어 실행되는 경우 Google Chrome을 실행 한 상태에서 해당 아이콘을 우클릭해도 고정시키는 메뉴가 나타나지 않는 경우
  원인  기본적인 원인은 Google Chrome 사이트 (http://chrome.</description>
            <content type="html"><![CDATA[

<h1 id="google-chrome-아이콘이-docky에서-분리되는-문제">Google Chrome 아이콘이 Docky에서 분리되는 문제</h1>

<blockquote>
<p>본 문서는 Google Chrome을 실행하였을 때 Docky에서 저해상도 아이콘으로 표시 될 뿐만 아니라 프로그램 메뉴에서 등록한 것과 분리되는 문제를 해결하기 위한 내용을 담고 있습니다</p>
</blockquote>

<h2 id="증상">증상</h2>

<ul>
<li>프로그램 메뉴에서 Google Chrome을 끌어다가 Docky에 등록하여 실행하였으나 별도 아이콘(저해상도)으로 분리되어 실행되는 경우</li>

<li><p>Google Chrome을 실행 한 상태에서 해당 아이콘을 우클릭해도 고정시키는 메뉴가 나타나지 않는 경우</p>

<p><img src="/images/2014/Jun/20140623-01.png" alt="" /></p></li>
</ul>

<h2 id="원인">원인</h2>

<ul>
<li>기본적인 원인은 Google Chrome 사이트 (<a href="http://chrome.google.com" target="_blank">http://chrome.google.com</a>) 에서 다운로드 받은 Chrome 패키지를 설치 할 때 실행파일인 google-chrome-stable이란 명칭과 데스크탑 어플리케이션의 설정 이름이 다르기 때문에 발생한다.</li>
</ul>

<h2 id="해결방법">해결방법</h2>

<ul>
<li><p>먼저 Chrome을 실행하고 실제 인식되는 이름을 확인하기 위해 아래 명령을 터미널에서 실행한다
<code>$ xprop | grep WM_CLASS | awk '{print $4}'</code></p></li>

<li><p>위 명령을 실행하면 &lsquo;+&rsquo; 커서가 나타나는데 이를 Chrome 브라우저로 가져다 대고 클릭하면 아래 예시와 같은 이름이 나타난다.
<code>&quot;Google-chrome-stable&quot;</code></p></li>

<li><p>어플리케이션 설정 파일을 편집한다.</p>

<pre><code>$ sudo vi /usr/share/applications/google-chrome.desktop

편집화면에서 아래 내용을 추가해 준다 (추가해 주는 이름은 2번에서 확인 된 이름을 &quot; 없이 입력한다)

StartupWMClass=Google-chrome-stable
</code></pre></li>

<li><p>Docky를 종료하고 캐시 파일을 삭제한다</p>

<pre><code>$ rm ~/.cache/docky/docky.desktop.*.desktop

* 부분은 보통 로케일 설정에 따라 다르다 (ko_KR.UTF-8 또는 en_US.UTF-8)
</code></pre></li>

<li><p>Docky를 실행하고 Chrome을 실행하여 Dock에 있는 아이콘과 실행된 아이콘이 분리되지 않고 표시 되는지 확인한다</p></li>

<li><p>정상적으로 수정 된 화면</p></li>
</ul>

<p><img src="/images/2014/Jun/20140623-02.png" alt="" /></p>
]]></content>
        </item>
        
        <item>
            <title>Smart Array P420 캐시 비활성화 증상</title>
            <link>/2014/06/17/smart-array-p420-kaesi-bihwalseonghwa-jeungsang/</link>
            <pubDate>Tue, 17 Jun 2014 10:20:13 +0000</pubDate>
            
            <guid>/2014/06/17/smart-array-p420-kaesi-bihwalseonghwa-jeungsang/</guid>
            <description>Smart Array Cache 비활성화 증상  본 문서는 Smart Array 컨트롤러(P420, P420i, P421 등)에서 발생하는 캐시 비활성화 증상과 해결방법에 대해서 소개하고 있습니다.
 증상  시스템의 I/O 성능이 낮음
 Smart Array 컨트롤러의 캐시가 비 활성화 되어있음 컨트롤러 상태 정보 확인 결과 Capacitor 충전상태에 문제가 있으며 임시로 비활성화 되어있음을 확인할 수 있습니다.   컨트롤러 상태 메시지
 Cache Status: Temporarily Disabled Cache Status Details: Cache disabled; capacitor charge is low  상기 예시의 컨트롤러는 별도의 배터리유닛을 사용하지 않는 FBWC 임에도 불구하고 충전 오류 메시지가 발생하고 있음.</description>
            <content type="html"><![CDATA[

<h1 id="smart-array-cache-비활성화-증상">Smart Array Cache 비활성화 증상</h1>

<blockquote>
<p>본 문서는 Smart Array 컨트롤러(P420, P420i, P421 등)에서 발생하는 캐시 비활성화 증상과 해결방법에 대해서 소개하고 있습니다.</p>
</blockquote>

<h2 id="증상">증상</h2>

<ul>
<li><p>시스템의 I/O 성능이 낮음</p>

<ul>
<li>Smart Array 컨트롤러의 캐시가 비 활성화 되어있음</li>
<li>컨트롤러 상태 정보 확인 결과 Capacitor 충전상태에 문제가 있으며 임시로 비활성화 되어있음을 확인할 수 있습니다.
<img src="/images/2014/Jun/20140617-02-1.png" alt="" /></li>
</ul></li>

<li><p>컨트롤러 상태 메시지</p>

<ul>
<li>Cache Status: Temporarily Disabled</li>
<li>Cache Status Details: Cache disabled; capacitor charge is low</li>
</ul></li>

<li><p>상기 예시의 컨트롤러는 별도의 배터리유닛을 사용하지 않는 FBWC 임에도 불구하고 충전 오류 메시지가 발생하고 있음.</p></li>
</ul>

<h2 id="원인">원인</h2>

<ul>
<li>해당 컨트롤러의 펌웨어 버그로 <a href="http://h20566.www2.hp.com/portal/site/hpsc/template.PAGE/public/psi/swdDetails/?sp4ts.oid=5193409&amp;spf_p.tpst=swdMain&amp;spf_p.prp_swdMain=wsrp-navigationalState%3Didx%253D3%257CswItem%253DMTX_7903b59b20034c6d920a68f808%257CswEnvOID%253D4004%257CitemLocale%253D%257CswLang%253D%257Cmode%253D4%257Caction%253DdriverDocument&amp;javax.portlet.begCacheTok=com.vignette.cachetoken&amp;javax.portlet.endCacheTok=com.vignette.cachetoken" target="_blank">버전 5.42</a>에서 수정 되었습니다.
<img src="/images/2014/Jun/20140617-01.png" alt="20140617-01.png" /></li>
</ul>

<h2 id="해결-방법">해결 방법</h2>

<ul>
<li>Smart Array P420/P420i/P421/P721m/P822/220i/222의 펌웨어를 5.42 버전으로 업데이트 한 후에 리부팅 해주면 됩니다.</li>
<li>Smart Array 5.42 : <a href="http://h20566.www2.hp.com/portal/site/hpsc/template.PAGE/public/psi/swdDetails/?sp4ts.oid=5193409&amp;spf_p.tpst=swdMain&amp;spf_p.prp_swdMain=wsrp-navigationalState%3Didx%253D3%257CswItem%253DMTX_7903b59b20034c6d920a68f808%257CswEnvOID%253D4004%257CitemLocale%253D%257CswLang%253D%257Cmode%253D4%257Caction%253DdriverDocument&amp;javax.portlet.begCacheTok=com.vignette.cachetoken&amp;javax.portlet.endCacheTok=com.vignette.cachetoken" target="_blank">다운로드</a></li>
</ul>

<h2 id="관련문서">관련문서</h2>

<ul>
<li><a href="http://h20565.www2.hp.com/portal/site/hpsc/template.PAGE/public/psi/topIssuesDisplay/?sp4ts.oid=5177953&amp;spf_p.tpst=psiContentDisplay&amp;spf_p.prp_psiContentDisplay=wsrp-navigationalState%3DdocId%253Demr_na-c04258375-2%257CdocLocale%253Den_US&amp;javax.portlet.begCacheTok=com.vignette.cachetoken&amp;javax.portlet.endCacheTok=com.vignette.cachetoken" target="_blank">HP Customer advisory</a> - DocID:c04258375 version 2</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>RHEL/CentOS 6에서 heartbeat 오류</title>
            <link>/2014/06/16/epel-6-heartbeat-error/</link>
            <pubDate>Mon, 16 Jun 2014 06:46:44 +0000</pubDate>
            
            <guid>/2014/06/16/epel-6-heartbeat-error/</guid>
            <description>EPEL6 - heartbeat error  본 문서는 EPEL의 heartbeat 3.0.4-2 설치 및 사용 시 발생하는 오류에 대해서 설명하고 있습니다.
 환경 및 증상  운영체제 : RHEL 6.4 이하 EPEL 패키지
 heartbeat-3.0.4-2.el6.[i386/x86_64].rpm heartbeat-libs-3.0.4-2.el6.[i386/x86_64].rpm  증상
 heartbeat 설치 후 실행 할 때 아래와 같은 오류 메시지 발생
Jun 16 14:03:16 server1 heartbeat: [720]: ERROR: glib: ucast: error setting option SO_REUSEPORT(w): Protocol not available Jun 16 14:03:16 server1 heartbeat: [720]: ERROR: make_io_childpair: cannot open ucast eth0 Jun 16 14:03:17 server1 heartbeat: [726]: CRIT: Emergency Shutdown: Master Control process died.</description>
            <content type="html"><![CDATA[

<h1 id="epel6-heartbeat-error">EPEL6 - heartbeat error</h1>

<blockquote>
<p>본 문서는 EPEL의 heartbeat 3.0.4-2 설치 및 사용 시 발생하는 오류에 대해서 설명하고 있습니다.</p>
</blockquote>

<h2 id="환경-및-증상">환경 및 증상</h2>

<ul>
<li>운영체제 : RHEL 6.4 이하</li>

<li><p>EPEL 패키지</p>

<ul>
<li>heartbeat-3.0.4-2.el6.[i386/x86_64].rpm</li>
<li>heartbeat-libs-3.0.4-2.el6.[i386/x86_64].rpm</li>
</ul></li>

<li><p>증상</p>

<ul>
<li><p>heartbeat 설치 후 실행 할 때 아래와 같은 오류 메시지 발생</p>

<pre><code>Jun 16 14:03:16 server1 heartbeat: [720]: ERROR: glib: ucast: error setting option SO_REUSEPORT(w): Protocol not available
Jun 16 14:03:16 server1 heartbeat: [720]: ERROR: make_io_childpair: cannot open ucast eth0
Jun 16 14:03:17 server1 heartbeat: [726]: CRIT: Emergency Shutdown: Master Control process died.
Jun 16 14:03:17 server1 heartbeat: [726]: CRIT: Killing pid 720 with SIGTERM
Jun 16 14:03:17 server1 heartbeat: [726]: CRIT: Emergency Shutdown(MCP dead): Killing ourselves.
</code></pre></li>
</ul></li>
</ul>

<h2 id="원인-및-해결방법">원인 및 해결방법</h2>

<p>duplicate ucast 패치와 관련이 있지만 실질적인 원인은 EPEL 저장소의 패키징 관리 방침과 관련이 있다. EPEL의 경우 별도의 RHEL/CentOS 업데이트 릴리즈 별로 관리되고 있지 않으며 최신 버전에 맞추어져 패키징이 되어있기 때문에 최신 릴리즈 버전 (20140616 기준 6.5)을 사용하지 않을 경우에는 EPEL 패키지와 맞지 않을 수 있다.</p>

<p>EPEL에서 제공하는 heartbeat-3.0.4-2 패키지를 사용하기 위해서는 RHEL/CentOS 6.5 패키지의 커널인 2.6.32-431.el6 커널로 업그레이드를 해주어야만 정상적으로 사용이 가능하다. 사실 별도의 소스를 가져다 빌드하는 방법도 있지만 cluster-glue 및 의존성 라이브러리 맞추는 작업이 더 복잡하기 때문에 별다른 이슈가 없는 한 커널 업데이트 방법을 권장한다.</p>

<ul>
<li>요약

<ul>
<li>EPEL 6에서 제공하는 heartbeat-3.0.4-2는 커널 2.6.32-431을 기준으로 패키징 되어있다</li>
<li>커널 버전을 2.6.32-431 이상으로 업데이트 한다</li>
</ul></li>

<li><p>해결방법</p>

<ul>
<li><p>REHEL/CentOS 6.5 저장소가 yum에 설정되어있다고 가정하고 아래 명령을 통해서 업데이트 진행</p>

<pre><code>$ yum -y upgrade kernel kernel-devel kernel-headers
</code></pre></li>

<li><p>커널 업그레이드가 완료되면 grub.conf에 기본 커널 파라미터가 추가 되므로 필요에 따라서 삭제하면 된다.</p></li>
</ul></li>
</ul>

<h2 id="관련문서">관련문서</h2>

<ul>
<li><a href="https://bugzilla.redhat.com/show_bug.cgi?id=1028127" target="_blank">Redhat Bugzilla</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>yum 저장소에서 checksum error 발생</title>
            <link>/2014/06/12/yum-repository-checksum-error/</link>
            <pubDate>Thu, 12 Jun 2014 06:06:12 +0000</pubDate>
            
            <guid>/2014/06/12/yum-repository-checksum-error/</guid>
            <description>yum checksum error 증상  yum 저장소를 새로 생성하였으나 해당 목록을 받아올 때 아래 메시지 발생
Loaded plugins: security RHEL5.10/primary | 1.2 MB 00:00 http://exampledomain.com/rhel/5Server/x86_64/5.10/repodata/primary.xml.gz: [Errno -3] Error performing checksum Trying other mirror.  발생 조건
 RHEL5 저장소를 RHEL6에서 생성하였을 경우   원인 및 해결방안 createrepo로 저장소를 생성 할 때 저장소 정보를 RHEL5의 Checksum은 SHA-1 기반으로 되어있는 반면에 RHEL6의 경우는 SHA-256으로 생성하기 때문에 RHEL5에서 해당 저장소를 사용하려고 하면 오류가 발생하는 것이다.</description>
            <content type="html"><![CDATA[

<h1 id="yum-checksum-error">yum checksum error</h1>

<h2 id="증상">증상</h2>

<ul>
<li><p>yum 저장소를 새로 생성하였으나 해당 목록을 받아올 때 아래 메시지 발생</p>

<pre><code>Loaded plugins: security
RHEL5.10/primary                                         | 1.2 MB     00:00
http://exampledomain.com/rhel/5Server/x86_64/5.10/repodata/primary.xml.gz: [Errno -3] Error performing checksum
Trying other mirror.
</code></pre></li>

<li><p>발생 조건</p>

<ul>
<li>RHEL5 저장소를 RHEL6에서 생성하였을 경우</li>
</ul></li>
</ul>

<h2 id="원인-및-해결방안">원인 및 해결방안</h2>

<p>createrepo로 저장소를 생성 할 때 저장소 정보를 RHEL5의 Checksum은 SHA-1 기반으로 되어있는 반면에 RHEL6의 경우는 SHA-256으로 생성하기 때문에 RHEL5에서 해당 저장소를 사용하려고 하면 오류가 발생하는 것이다. 따라서 저장소를 생성 할 때 아래와 같이 생성해 주면 된다</p>

<pre><code>$ createrepo -s sha 경로

또는

$ createrepo -s sha1 경로
</code></pre>
]]></content>
        </item>
        
        <item>
            <title>limits.conf와 오픈파일개수(nofile) 영향</title>
            <link>/2014/05/28/limits-conf-nofile-big-value-effect/</link>
            <pubDate>Wed, 28 May 2014 06:50:58 +0000</pubDate>
            
            <guid>/2014/05/28/limits-conf-nofile-big-value-effect/</guid>
            <description>nofile Linux에는 로그인 했을 때 얻는 쉘에 대해서 리소스를 제한하는 설정이 존재한다. 대표적으로 인증과정에서 pam_limits 모듈에 의해 적용되는 limits.conf 설정파일이 있다.
해당 파일은 /etc/security/limits.conf에 있으며 최근 배포판에 사용되는 버전의 경우 /etc/security/limits.d/ 아래에 별도 설정파일을 가지고 있는 형태이다. 여기에 설정 된 값은 로그인 쉘에서 ulimit 명령을 통해 어떠한 값이 반영 되었는지 확인 할 수 있는데 보통 nofile, nproc 등의 설정을 많이 수정하는 편이다.
nofile : 해당 도메인(사용자, 그룹)이 오픈할 수 있는 최대 파일 개수 nproc : 해당 도메인(사용자, 그룹)의 최대 프로세스 개수  최근 빅데이터가 화두가 되면서 Hadoop을 비롯한 많은 어플리케이션에서 대규모 데이터와 파일을 처리하는 과정에서 nofile의 수치를 높게 설정 할 필요성이 생겨났다.</description>
            <content type="html"><![CDATA[

<h1 id="nofile">nofile</h1>

<p>Linux에는 로그인 했을 때 얻는 쉘에 대해서 리소스를 제한하는 설정이 존재한다. 대표적으로 인증과정에서 pam_limits 모듈에 의해 적용되는 limits.conf 설정파일이 있다.</p>

<p>해당 파일은 /etc/security/limits.conf에 있으며 최근 배포판에 사용되는 버전의 경우 /etc/security/limits.d/ 아래에 별도 설정파일을 가지고 있는 형태이다. 여기에 설정 된 값은 로그인 쉘에서 ulimit 명령을 통해 어떠한 값이 반영 되었는지 확인 할 수 있는데 보통 nofile, nproc 등의 설정을 많이 수정하는 편이다.</p>

<pre><code>nofile : 해당 도메인(사용자, 그룹)이 오픈할 수 있는 최대 파일 개수
nproc : 해당 도메인(사용자, 그룹)의 최대 프로세스 개수
</code></pre>

<p>최근 빅데이터가 화두가 되면서 Hadoop을 비롯한 많은 어플리케이션에서 대규모 데이터와 파일을 처리하는 과정에서 nofile의 수치를 높게 설정 할 필요성이 생겨났다. 본 문서에서는 이러한 값을 크게 잡았을 경우에 발생 할 수 있는 케이스에 대해서 설명한다.</p>

<h1 id="case-서버가-느려졌다">Case : 서버가 느려졌다</h1>

<p>HBase를 사용하는 서버시스템에서 많은 파일(데이터)을 처리하기 위해서 nofile을 매우 높은 값으로 수정한 서버가 있었다. 해당 서버의 경우는 10000000 정도의 값을 설정하였는데 이 서버에서 특이한 증상이 발견 되었다.</p>

<p>우선 limits.conf 설정파일을 보면 아래와 같다.</p>

<pre><code>*			soft	nofile	10000000
*			hard	nofile	10000000
</code></pre>

<p>문제는 위 설정파일을 적용한 서버에서 pexpect를 이용해 작성한 자동화 스크립트를 사용하면 1~2초의 실행 지연이 발생하는 증상이 생겨났다. 원인을 찾기위해 해당 스크립트를 단순히 ls 명령만 실행하는 것으로 변경해도 지연증상은 발생하고 있었다.</p>

<p>원인을 찾기 위해서 해당 스크립트를 실행할 때 생성되는 모든 프로세스에 대해 strace로 추적해보았더니 아래와 같은 인상적인 부분을 발견할 수 있었다.</p>

<pre><code>close(3)                                = -1 EBADF (Bad file descriptor)
close(4)                                = -1 EBADF (Bad file descriptor)
close(5)                                = -1 EBADF (Bad file descriptor)
... 생략 ...
close(75821)                            = -1 EBADF (Bad file descriptor)
close(75822)                            = -1 EBADF (Bad file descriptor)
... 생략 ...
</code></pre>

<p>해당 메시지를 보자마자 &ldquo;아차&rdquo;하는 생각이 들어서 pexpect가 명령을 실행 할 때 사용하는 spawn 메소드의 동작형태를 확인해 봤다. 역시나, 명령을 실행할 때 생성하는 프로세스를 daemon화해서 실행하고 있었다. 이것이 문제였다.</p>

<h2 id="daemon">Daemon</h2>

<p>C언어로 Daemon 프로그램을 작성할 때 보통 아래 단계를 거쳐서 작성하도록 배우곤한다. (물론 구현하는 사람에 따라서 Double fork를 하기도 하고 조금은 다르지만 아래 부분은 대체로 적용한다)</p>

<ul>
<li>부모로부터 프로세스 분리 (fork)</li>
<li>새로운 SID 설정 (setsid)</li>
<li>파일 마스크값 설정 (umask)</li>
<li>작업 디렉토리 설정 (chdir)</li>
<li>파일디스크립터 정리 (close - getrlimit(RLIMIT_NOFILE))</li>
<li>시그널처리 (signal)</li>
</ul>

<p>참고로, 전통적인 유닉스의 경우 _NOFILE을 이용해서 파일 디스크립터를 닫는 걸로 보통 설명하곤한다.</p>

<p>실제 내부구현에서 차이가 조금씩은 있겠지만 대체로 Daemon처럼 Background에서 조용히 실행되도록 하는 경우에는 위의 내용을 적용하게 되는데 문제는 파일디스크립터 정리 부분에 있다.</p>

<p>즉, limits.conf에 설정한 nofile이 10000000이기 때문에 getrlimit(RLIMIT_NOFILE)을 통해서 얻는 rlim_cur, rlim_max 값이 10000000이 되는 것이며 이를 루프를 통해서 명시적으로 close()하게 된다.</p>

<p>이를 단순히 비교해 보고자 보통 많이 설정하는 nofile 값 8192일 때와 10000000일 때 디스크립터를 close()하는 부분을 C로 작성하여 소요되는 시간을 비교하면 아래와 같다.</p>

<p><img src="/images/2014/May/20140528_01.png" alt="8192_10000000" /></p>

<p>즉, nofile 값이 너무 크기 때문에 daemon 형태로 동작하는 프로세스는 명시적으로 파일 디스크립터를 닫는 작업에 드는 시간이 길어서 느린 것 처럼 느껴지는 것이다.</p>

<h2 id="추천방안">추천방안</h2>

<p>사실 해당서버에 저렇게까지 큰 값을 설정 할 필요는 없었다. 제한에 걸려서 문제가 생기지 않길 바라기 때문에 해당 값을 크게 잡아둔 것일 뿐이었다. 따라서, 적당한 값으로 변경하는 것이 가장 추천하는 방안이다.</p>

<p>보통 많은 데이터와 소켓 통신을 하는 서버의 경우도 65535 또는 131072 정도면 큰 문제 없이 사용 할 수 있다.</p>

<p>또한, 서버에서 모든 프로세스가 위와 같은 큰 리소스제한을 갖고 사용될 필요가 없기 때문에 더 높은 리소스 제한이 필요한 프로세스가 속한 도메인(사용자, 그룹)을 명시적으로 지정해서 설정하는 것을 권장한다.</p>

<pre><code>예) appuser 계정으로 서비스를 운영할 경우
appuser			soft	nofile	131072
appuser			hard	nofile	131072
</code></pre>

<h2 id="여담">여담</h2>

<p>사실 파일 디스크립터가 닫히는 과정을 알고는 있지만 limits.conf 설정 값 때문에 단순한 스크립트 실행이 체감할 정도로 지연이 발생할거라고는 생각지 못했다. 역시 경험은 중요하다.</p>
]]></content>
        </item>
        
        <item>
            <title>Mavericks에서 JAVA6 사용하기</title>
            <link>/2014/04/21/mevericks-java6-enable/</link>
            <pubDate>Mon, 21 Apr 2014 08:57:57 +0000</pubDate>
            
            <guid>/2014/04/21/mevericks-java6-enable/</guid>
            <description>Java 6 for Mac os X Mavericks 업무를 하다보면 웹 기반 어플리케이션을 사용 할 때가 간혹 있는데 해당 어플리케이션이 더이상 기술지원이 안되거나 여러 이유로 인해 JAVA 7과 호환되지 않는 경우가 꽤 많다. 이런 경우에 현재 사용중인 Mavericks에서 JAVA 6를 활성화 하는 방법을 간단히 정리 해보면 아래와 같다.
Java 6 설치 먼저, 애플에서 Lion을 위해서 제공했던 Java 6를 설치한다.
 다운로드  몇 가지 테스트결과 이 버전 이전 버전을 사용하면 호환성 문제로 설치가 되지 않았다.</description>
            <content type="html"><![CDATA[

<h1 id="java-6-for-mac-os-x-mavericks">Java 6 for Mac os X Mavericks</h1>

<p>업무를 하다보면 웹 기반 어플리케이션을 사용 할 때가 간혹 있는데 해당 어플리케이션이 더이상 기술지원이 안되거나 여러 이유로 인해 JAVA 7과 호환되지 않는 경우가 꽤 많다. 이런 경우에 현재 사용중인 Mavericks에서 JAVA 6를 활성화 하는 방법을 간단히 정리 해보면 아래와 같다.</p>

<h2 id="java-6-설치">Java 6 설치</h2>

<p>먼저, 애플에서 Lion을 위해서 제공했던 Java 6를 설치한다.</p>

<ul>
<li><a href="http://support.apple.com/kb/dl1572" target="_blank">다운로드</a>

<ul>
<li>몇 가지 테스트결과 이 버전 이전 버전을 사용하면 호환성 문제로 설치가 되지 않았다.
<br /></li>
</ul></li>
</ul>

<h2 id="java-6-활성화">Java 6 활성화</h2>

<p>Java 6를 설치해도 Java 7이 활성화 되어있기 때문에 Java 7을 비활성화 하고 Java 6를 활성화 해야 한다.</p>

<h3 id="플러그인-백업">플러그인 백업</h3>

<pre><code>$ sudo mkdir -p /Library/Internet\ Plug-Ins/disabled 
$ sudo mv /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin /Library/Internet\ Plug-Ins/disabled
</code></pre>

<h3 id="자바-애플릿-웹-스타트-활성화">자바 애플릿/웹 스타트 활성화</h3>

<pre><code>$ sudo ln -sf /System/Library/Java/Support/Deploy.bundle/Contents/Resources/JavaPlugin2_NPAPI.plugin /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin 
$ sudo ln -sf /System/Library/Frameworks/JavaVM.framework/Commands/javaws /usr/bin/javaws
</code></pre>

<p>위 작업을 마치고 Safari를 재시작하면 자바 애플릿과 웹 스타트가 Java 6 기반으로 동작하게 된다.</p>

<h2 id="되돌리기">되돌리기</h2>

<p>만약, Java 6 환경에서 Java 7 또는 Java 8으로 되돌아 가고자 한다면 아래 명령으로 웹 스타트 연결을 바꿔주고</p>

<pre><code>$ sudo ln -sf /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/javaws /usr/bin/javaws
</code></pre>

<p><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html" target="_blank">이 곳</a>에서 원하는 버전을 다운로드하여 설치하면 된다.</p>

<ul>
<li>참고문헌

<ul>
<li><a href="http://support.apple.com/kb/dl1572" target="_blank">Java 6 for Mac os X</a></li>
<li><a href="http://support.apple.com/kb/ht5559" target="_blank">Learn how to re-enable the Apple-provided Java SE 6 Java SE 6 web plug-in and Web Start functionality in Lion and Mountain Lion.</a></li>
</ul></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>FAQ : Telnet 한글입력 문제</title>
            <link>/2014/03/25/faq-telnet-hangeulibryeog-munje/</link>
            <pubDate>Tue, 25 Mar 2014 15:55:30 +0000</pubDate>
            
            <guid>/2014/03/25/faq-telnet-hangeulibryeog-munje/</guid>
            <description>Telnet 한글입력 문제 Telnet을 이용해 서버에 접속하여 한글을 입력 할 경우 한 글이 제대로 입력되지 않고 깨지거나 이스케이프 문자로 인식되어 처리 되는 (사용자 입장에서는 갑자기 이상한 메시지와 함께 telnet&amp;gt; 프롬프트가 떠버리는) 증상이 보이는 경우가 있다.
이 경우에는 서버의 언어 설정이 ko_KR.UTF-8과 같이 되어있더라도 입력이 제대로 안 되는데 접속 할 때 아래와 같이 접속을 해보도록 하자.
$ telnet -8 서버옵션  -8 옵션은 8bit 모드(바이너리 모드라고도 함)로 데이터를 전송하도록 지시하는 것으로 telnet 자체가 기본적으로 7bit로 표현가능한 ASCII 코드에 기반하여 문자를 전송하도록 고안 되었기 때문에 별도의 옵션으로 존재한다.</description>
            <content type="html"><![CDATA[

<h2 id="telnet-한글입력-문제">Telnet 한글입력 문제</h2>

<p>Telnet을 이용해 서버에 접속하여 한글을 입력 할 경우 한 글이 제대로 입력되지 않고 깨지거나 이스케이프 문자로 인식되어 처리 되는 (사용자 입장에서는 갑자기 이상한 메시지와 함께 telnet&gt; 프롬프트가 떠버리는) 증상이 보이는 경우가 있다.</p>

<p>이 경우에는 서버의 언어 설정이 ko_KR.UTF-8과 같이 되어있더라도 입력이 제대로 안 되는데 접속 할 때 아래와 같이 접속을 해보도록 하자.</p>

<pre><code>$ telnet -8 서버옵션
</code></pre>

<p>-8 옵션은 8bit 모드(바이너리 모드라고도 함)로 데이터를 전송하도록 지시하는 것으로 telnet 자체가 기본적으로 7bit로 표현가능한 ASCII 코드에 기반하여 문자를 전송하도록 고안 되었기 때문에 별도의 옵션으로 존재한다.</p>

<p>따라서, 7bit로 입력을 전달하게 되면 한글과 같은 입력은 제대로 전송되지 않고 한글 코드 중간의 값들이 이스케이프 문자로 인식되는 문제가 발생한다.</p>

<p>단순한 문제이고 man page에 해당 옵션에 대한 설명이 있지만 의외로 많은 분들이 이 옵션을 몰라서 물어보기에 문서로 남긴다.</p>

<p>그리고, 이제는 telnet은 버리고 ssh를 사용하도록 하자. :)</p>
]]></content>
        </item>
        
        <item>
            <title>FAQ : Telnet 접속 안 되는 증상</title>
            <link>/2014/03/25/faq-telnet-connection-failed/</link>
            <pubDate>Tue, 25 Mar 2014 15:40:43 +0000</pubDate>
            
            <guid>/2014/03/25/faq-telnet-connection-failed/</guid>
            <description>Telnet 접속 실패 관련 본 문서에서는 Telnet을 이용한 접속을 시도 할 때 안되는 상황 별로 해결 방법을 소개합니다. 자주 받는 질문 위주로 기록 되었습니다.
Case 1: Telnet 접속 시 바로 거절 당함 (tcp wrapper) 별것 아닌 내용이지만 tcp wrapper에 적절 한 설정을 하지 않아서 접속을 거부 당하는 경우가 있다. /etc/hosts.allow 파일에 아래와 같은 설정이 되어있는지 확인해 보자.
in.telnetd : 접속허용IP주소 또는 대역 예) in.telnetd : 192.168.2.7, 192.168.3.  Case 2: telnet.</description>
            <content type="html"><![CDATA[

<h2 id="telnet-접속-실패-관련">Telnet 접속 실패 관련</h2>

<p>본 문서에서는 Telnet을 이용한 접속을 시도 할 때 안되는 상황 별로 해결 방법을 소개합니다. 자주 받는 질문 위주로 기록 되었습니다.</p>

<h3 id="case-1-telnet-접속-시-바로-거절-당함-tcp-wrapper">Case 1: Telnet 접속 시 바로 거절 당함 (tcp wrapper)</h3>

<p>별것 아닌 내용이지만 tcp wrapper에 적절 한 설정을 하지 않아서 접속을 거부 당하는 경우가 있다. /etc/hosts.allow 파일에 아래와 같은 설정이 되어있는지 확인해 보자.</p>

<pre><code>in.telnetd : 접속허용IP주소 또는 대역

예)
in.telnetd : 192.168.2.7, 192.168.3.
</code></pre>

<h3 id="case-2-telnet-deny에-등록-된-경우">Case 2: telnet.deny에 등록 된 경우</h3>

<p>/etc/telnet.deny라는 설정파일이 있다. 이 설정파일에 등록 된 계정이름은 텔넷 접속 할 때 거절 당하기 때문에 이 설정파일에 계정이 등록 된 것은 아닌지 확인해 보자.</p>

<h3 id="case-3-접속-횟수-제한으로-잠긴-경우">Case 3: 접속 횟수 제한으로 잠긴 경우</h3>

<p>PAM모듈 중에 pam_tally.so 라는 모듈이 있다. 로그인을 카운팅 하는 모듈로 이 모듈을 통해 로그인을 인증 할 경우 지정된 횟수를 초과하는 인증 실패가 발생하면 설정 된 내용에 따라 계정이 잠기게 된다.</p>

<p>먼저, 인증에 pam_tally.so 파일이 사용되는지 확인하려면 아래 파일을 열어보면 된다.</p>

<p><img src="/images/2014/Mar/system_auth.png" alt="system-auth" /></p>

<p>설정 내용 중간에 pam_tally.so (또는 pam_tally2.so)가 보인다면 계정이 인증 실패 횟수 제한에 걸려 잠겨있을 수 확률이 높다. (위 예제에는 unlock_time 설정이 있기 때문에 지정 된 시간이 지나면 자동으로 잠금이 해제 되지만 이 설정이 없는 경우 완전히 잠기게 된다. 오래된 OS에서는 no_magic_root 옵션과 같이 볼 수 있다)</p>

<p>이 경우에는 아래 명령을 통해서 계정의 상태를 확인 해 볼 수 있는데 (pam_tally와 pam_tally2의 출력 결과는 살짝 다르다)</p>

<pre><code>$ pam_tally --user tester
User tester	(501)	has 0

$ pam_tally2 --user tester
Login           Failures Latest failure     From
tester            0    
</code></pre>

<p>상태 확인 결과 실패 횟수로 인해 계정이 잠긴경우 아래의 명령으로 잠금을 해제 할 수 있다.</p>

<pre><code>$ pam_tally --user tester --reset
$ pam_tally2 --user tester --reset
</code></pre>
]]></content>
        </item>
        
        <item>
            <title>VirtualBox RHEL5 부팅 시 행업증상 #2</title>
            <link>/2014/03/03/virtualbox-rhel5-64bit-boot-error-lapic/</link>
            <pubDate>Mon, 03 Mar 2014 07:07:03 +0000</pubDate>
            
            <guid>/2014/03/03/virtualbox-rhel5-64bit-boot-error-lapic/</guid>
            <description>본 문서는 RHEL5 64bit를 부팅 할 때 행업을 발생 시키는 요인 중 하나에 대해서 안내하고 있습니다.
nmi_watchdog에 의한 부팅 불가 증상은 이 문서를 참고하시기 바랍니다.
환경  VirtualBox 4.2.16 RHEL5 64bit  증상  부팅 과정에서 아래 메시지를 끝으로 시스템이 멈추는 현상
NET: registered protocol family 2   해결방법 방법1  가상머신의 설정 값을 변경하는 방법  시스템 &amp;gt; 마더보드 &amp;gt; IO APIC 사용하기를 체크하는 방법   방법2 (추천)  부팅시 커널 파라미터에 nolapic를 추가한다 CD-ROM/USB 부팅 시에 부트 프롬프트에서 아래와 같이 입력</description>
            <content type="html"><![CDATA[

<p>본 문서는 RHEL5 64bit를 부팅 할 때 행업을 발생 시키는 요인 중 하나에 대해서 안내하고 있습니다.</p>

<p>nmi_watchdog에 의한 부팅 불가 증상은 <a href="http://lunatine.net/faq-virtualboxeseo-rhel5-buting-oryumeomcum-hyeonsang/" target="_blank">이 문서</a>를 참고하시기 바랍니다.</p>

<h2 id="환경">환경</h2>

<ul>
<li>VirtualBox 4.2.16</li>
<li>RHEL5 64bit</li>
</ul>

<h2 id="증상">증상</h2>

<ul>
<li><p>부팅 과정에서 아래 메시지를 끝으로 시스템이 멈추는 현상</p>

<pre><code>NET: registered protocol family 2
</code></pre></li>
</ul>

<h2 id="해결방법">해결방법</h2>

<h3 id="방법1">방법1</h3>

<ul>
<li>가상머신의 설정 값을 변경하는 방법

<ul>
<li>시스템 &gt; 마더보드 &gt; IO APIC 사용하기를 체크하는 방법</li>
</ul></li>
</ul>

<p><img src="/images/2014/Mar/TestBox_64bit__6_176____l__l___574.png" alt="" /></p>

<h3 id="방법2-추천">방법2 (추천)</h3>

<ul>
<li>부팅시 커널 파라미터에 nolapic를 추가한다</li>

<li><p>CD-ROM/USB 부팅 시에 부트 프롬프트에서 아래와 같이 입력</p>

<pre><code>Boot: linux nolapic
</code></pre></li>

<li><p>nolapic는 LAPIC를 비활성 하는 옵션이며 이에 대한 내용은 <a href="http://osdev.berlios.de/pic.html#local_apic" target="_blank">이 문서</a>를 참고하면 된다.</p></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>AKG K619 헤드폰</title>
            <link>/2014/02/18/akg-k619-headphone/</link>
            <pubDate>Tue, 18 Feb 2014 13:58:00 +0000</pubDate>
            
            <guid>/2014/02/18/akg-k619-headphone/</guid>
            <description>기존에 사용하던 AKG의 Q460 헤드폰이 고장나는 바람에 (2년 보증인데 26개월만에 고장)이를 대체 할 헤드폰을 찾게 되었다. (관련글 : Q460 사용후기)
역시나 이번에도 AKG의 제품을 찾기 시작했는데 퀸시(Quicy)라인업에 새로운 모델은 없었다. 그러다가, DJ 라인업에서 K619를 발견하였고 Q460과 비슷한 가격대(좀 더 싸다)와 컬러풀 한 디자인을 가진 이녀석을 구매하게 되었다.
그리고, 도착한 K619
여러 색상 중에서 사람들이 많이 선호하지 않는 오렌지 색상으로 선택하였는데 내가 좋아하는 색상이어서 그런지 몰라도 디자인과 잘 어울렸다. 무엇보다 &amp;ldquo;AKG 컬러팝 거치대&amp;rdquo;(일명 페인트통 거치대)가 독특하면서 예뻤다.</description>
            <content type="html"><![CDATA[

<p>기존에 사용하던 <a href="http://akgkorea.com/shop/main/index.php" target="_blank">AKG</a>의 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a> 헤드폰이 고장나는 바람에 (2년 보증인데 26개월만에 고장)이를 대체 할 헤드폰을 찾게 되었다. (관련글 : <a href="http://tumblr.lunatine.net/post/16515758995/akg-q460-sony" target="_blank">Q460 사용후기</a>)</p>

<p>역시나 이번에도 AKG의 제품을 찾기 시작했는데 퀸시(Quicy)라인업에 새로운 모델은 없었다. 그러다가, DJ 라인업에서 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=133&amp;category=005" target="_blank">K619</a>를 발견하였고 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>과 비슷한 가격대(좀 더 싸다)와 컬러풀 한 디자인을 가진 이녀석을 구매하게 되었다.</p>

<p>그리고, 도착한 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=133&amp;category=005" target="_blank">K619</a></p>

<p><img src="/images/2014/Feb/IMG_3281.png" alt="K619" /></p>

<p>여러 색상 중에서 사람들이 많이 선호하지 않는 오렌지 색상으로 선택하였는데 내가 좋아하는 색상이어서 그런지 몰라도 디자인과 잘 어울렸다. 무엇보다 &ldquo;AKG 컬러팝 거치대&rdquo;(일명 페인트통 거치대)가 독특하면서 예뻤다. (헤드폰을 사려한게 맞는 건가&hellip;)</p>

<p>주변 사람들 반응도 헤드폰 보다 이 거치대를 더 눈여겨 보는데 인터넷 쇼핑몰에서 5만원대의 금액으로 팔고 있었다. 하지만, 컬러팝 거치대를 사은품으로 주는 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=133&amp;category=005" target="_blank">K619</a>의 가격을 보면 말도 안되는 금액이다. 차라리 헤드폰을 사는게 낫지. 뭐야 이게&hellip;</p>

<p>암튼 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>을 대신 할 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=133&amp;category=005" target="_blank">K619</a>를 사용해본 소감을 정리하면</p>

<h3 id="디자인">디자인</h3>

<p>드라마를 잘 안봐서 모르겠지만 &ldquo;너의 목소리가 들려&rdquo;라는 드라마에서 주인공이 사용하는 PPL 광고가 있었다고 한다. (AKG가 PPL 광고도 하다니&hellip; AKG가 아니라 수입판매처에서 한거겠지?) 그만큼, 디자인은 미려하면서 산뜻하다. 전에 사용하던 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>에 비해 전혀 떨어지는 디자인은 아닌 것 같다.</p>

<h3 id="음질">음질</h3>

<p><a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>과는 Frequency와 Sensitivity 스펙이 좀 차이가 나는데, 실제로 청음을 해보면 드라이버 크기의 차이에서 오는 느낌의 차이가 꽤 크다. 결론부터 이야기 하자면 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>이 내게는 더 나은 음질을 들려 주는 것 같다.</p>

<p>감도의 차이인지 모르겠지만 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=133&amp;category=005" target="_blank">K619</a>로 듣다보니 어느새 볼륨을 평상 시 보다 1~2 정도 올려서 듣고 있는 날 발견 할 수 있었다. 그만큼 드라이버 크기에 비해서 소리의 감도는 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>보다 떨어진다고 봐야겠다.</p>

<p>그리고, <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>에 비해서 저음이 약하고 고음이 더 진하게 들린다.</p>

<p>결과적으로 라인업이 다른 제품군에서 동일한 음질을 기대한 것 자체가 무리였던 건 아닌가 싶다. (<a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>을 대체하려면 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=49&amp;category=007" target="_blank">K480NC</a>를 살걸 그랬나..)</p>

<h3 id="차음성">차음성</h3>

<p>Over ear 제품이기 때문에 차음성은 좋은 편이다. 하지만, 버스안에서 들었을 때 안내방송에 집중을 해 보니 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>보다 차음성에서 더 뛰어나다는 느낌도 없었다. <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>은 Over ear 제품도 아닌데 말이다. 그래도, 개인적으로 완전한 차음보다는 조금 주변 환경 소리가 들리는 걸 선호하니 (외부에서 차음이 심한 제품을 사용하다가는 사고날 위험이 높으니깐..) 나쁘진 않다.</p>

<h3 id="크기-및-휴대성">크기 및 휴대성</h3>

<p>크기는 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>보다 확실히 크다. 드라이버 크기 부터가 이미 큰 차이를 가지고 있다. 그렇기 때문에 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>처럼 완전히 접히는 건 아니지만 적당히 접어서 파우치에 담을 정도는 된다. (물론, 나는 그냥 목에 걸고 다니지만&hellip;)</p>

<p>그리고, AKG 제품 아니랄까봐 헤어밴드가 길지 않다. 서양 사람들 머리가 작은 건 익히 알지만 그래도 좀 여유를 두어서 만들어주면 안되나 싶다. (꼭, 확인해보고 사는 걸 권장한다)</p>

<h3 id="기타">기타</h3>

<p>큰 드라이버의 크기 때문에 나 처럼 안경을 쓴 사람은 잘못 쓰면 큰 드라이버와 탄탄한 밴드의 압력에 눌려 귀가 아프다. 헤어밴드 길이도 넉넉한 것이 아니므로 꼭 청음 할 수 있는 매장에 가서 사용해 보고 구매하기를 권장한다.</p>

<p>그리고, iOS 제품과 호환이 된다.</p>

<h4 id="장점">장점</h4>

<ul>
<li><a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a> 보다 연결부 잭이 튼튼하다. (<a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>의 경우 연결부 고장으로 케이블을 교체 한 적이 있다)</li>
<li>크기에 비해 가볍고 튼튼하다.</li>
<li>연장 케이블 길이가 넉넉하다.</li>
<li>매력적인 헤드폰 거치대를 제공한다.</li>
<li><a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=133&amp;category=005" target="_blank">K619</a>의 리모콘이 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a> 리모콘보다 좋다. (견고함, 감촉 등)</li>
</ul>

<h4 id="단점">단점</h4>

<ul>
<li>케이블이 분리형이 아니다. (단선 되면 지옥이다)</li>
<li><a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>처럼 완전히 접히지 않는다.</li>
<li>음질이 나쁜 건 아니지만 아주 좋다고 보기 어려운 애매한 점. (이 항목은 매우 주관적이다)</li>
</ul>

<h3 id="결론">결론</h3>

<p>AKG <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>이 고장나서 유사한 외출용 헤드폰을 구하려고 구매 한 제품인데 음질이 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>보다 아쉽게 느껴진다. 라인업도 다르고 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=23&amp;category=008" target="_blank">Q460</a>이 <a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=133&amp;category=005" target="_blank">K619</a>보다는 고가의 모델이기 때문에 어찌보면 당연한 결과인지도 모르겠다.</p>

<p>컬러팝 거치대는 별도로 구매하고 싶은 사람이 있을 정도로 예쁘고 화사한 색감을 자랑하기 때문에 거치대 때문에 이 제품을 구매 할 사람이 있을지도 모르겠다. 하지만, 꼭 한번 들어보고 본인이 원하는 음질을 제공하는지 확인해 본 후에 구매하기를 권장한다.</p>

<h4 id="추천">추천</h4>

<ul>
<li>가볍고 튼튼한 휴대용 오버 이어 헤드폰을 구매하시려는 분</li>
<li>AKG 특유의 음색을 좋아하시는 분</li>
<li>AKG의 매정한(?) 헤어밴드 길이도 잘 맞으시는 분</li>
</ul>

<h4 id="비추천">비추천</h4>

<ul>
<li>컬러팝 거치대를 갖고 싶은 분 (그냥 따로 구매하세요)</li>
<li><a href="http://akgkorea.com/shop/goods/goods_view.php?goodsno=49&amp;category=007" target="_blank">K480NC</a> 이상의 음질을 기대하시는 분</li>
<li>드라마에 나왔으니 나도 써봐야지 하는 분</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Mac OS X - DLNA 미디어 서버 (Serviio)</title>
            <link>/2014/02/14/mac-os-x-dlna-media-server-serviio/</link>
            <pubDate>Fri, 14 Feb 2014 17:48:24 +0000</pubDate>
            
            <guid>/2014/02/14/mac-os-x-dlna-media-server-serviio/</guid>
            <description>Mac OS X 그리고 DLNA 미디어 서버 맥에 저장된 동영상 파일을 TV로 감상하기 위해 여러 종류의 미디어 서버를 테스트 해 보았다. 테스트 하면서 겪었던 경험을 토대로 현 시점(2014년 2월 15일)에서 가장 괜찮은 DLNA 미디어 서버를 소개하고 설정하는 방법을 공유하고자 한다. (설정법만 보기 위해서는 다음 단락으로 넘어가도 좋다)
먼저, 미디어 서버를 테스트하면서 확인한 사항은 아래와 같았다.
 TV에서 미디어 서버 인식  당연한 내용이지만 TV에서 검색 안되는 미디어 서버도 있었다  자막지원(SMI, SRT) 및 자막 인코딩 문제 다양한 동영상 코덱을 재생하기 위한 Transcoding 지원 여부  위 사항을 바탕으로 PS3 Media Server(PMS), Universal Media Server(UMS), Plex Media Server, Serviio, XBMC 등을 테스트 해보며 최종적으로 UMS(PMS와 같은 계열이다)와 Serviio가 후보에 올랐다.</description>
            <content type="html"><![CDATA[

<h1 id="mac-os-x-그리고-dlna-미디어-서버">Mac OS X 그리고 DLNA 미디어 서버</h1>

<p>맥에 저장된 동영상 파일을 TV로 감상하기 위해 여러 종류의 미디어 서버를 테스트 해 보았다. 테스트 하면서 겪었던 경험을 토대로 현 시점(2014년 2월 15일)에서 가장 괜찮은 DLNA 미디어 서버를 소개하고 설정하는 방법을 공유하고자 한다. (설정법만 보기 위해서는 다음 단락으로 넘어가도 좋다)</p>

<p>먼저, 미디어 서버를 테스트하면서 확인한 사항은 아래와 같았다.</p>

<ul>
<li>TV에서 미디어 서버 인식

<ul>
<li>당연한 내용이지만 TV에서 검색 안되는 미디어 서버도 있었다</li>
</ul></li>
<li>자막지원(SMI, SRT) 및 자막 인코딩 문제</li>
<li>다양한 동영상 코덱을 재생하기 위한 Transcoding 지원 여부</li>
</ul>

<p>위 사항을 바탕으로 PS3 Media Server(PMS), Universal Media Server(UMS), Plex Media Server, Serviio, XBMC 등을 테스트 해보며 최종적으로 UMS(PMS와 같은 계열이다)와 Serviio가 후보에 올랐다.</p>

<p>그리고, Universal Media Server를 제치고 Serviio를 선택하게 되었는데 그 이유는 아래와 같다.</p>

<ul>
<li>UMS는 동영상 트랙에 있는 자막이 아닌 외부자막(한글자막)을 제대로 보기 위해 Transcoding 폴더를 통해서 인코더를 매번 선택해야하는 번거로움이 있었다.</li>
<li>SMI, SRT 모두 지원하지만 기본 인코더인 MEncoder의 경우 영상과 음성의 동기화가 잘 맞지 않았고 이를 맞추면 화질이 많이 떨어졌다.</li>
<li>FFMpeg을 선택하여 Transcoding을 하였을 경우 화질/동기화 문제는 없지만 원하는 글꼴로 자막표기가 잘 안되었다.</li>
<li>Transcoding을 하게 되면 구간 건너뛰기 기능 등을 사용할 수 없었다. (Serviio는 LG TV 기준 2배속 재생까지 되었다)</li>
<li>요약하면, 원하는 바가 아예 안되는 것은 아니지만 번거로움이 Serviio에 비해서 컸다.</li>
</ul>

<h1 id="serviio">Serviio</h1>

<h2 id="serviio-다운로드">Serviio 다운로드</h2>

<p><img src="/images/2014/Feb/serviio_download.png" alt="Download" /></p>

<p><a href="http://www.serviio.org/" target="_blank">Serviio</a>는 <a href="http://serviio.org" target="_blank">http://serviio.org</a> 사이트에서 Download 메뉴를 통해서 받을 수 있다. 설치 후 15일 동안 Pro 버전으로 동작하고 구매를 하지 않을 경우에는 자동으로 Free 버전으로 변경 된다.</p>

<p><img src="/images/2014/Feb/serviio_freepro.png" alt="Free vs Pro" /></p>

<p>Free 버전과 Pro 버전은 위와 같은 차이점이 존재하는데 Free 버전으로도 동영상 감상에 전혀 지장이 없기 때문에 신경 쓸 필요는 없다.</p>

<h2 id="serviio-실행">Serviio 실행</h2>

<p><img src="/images/2014/Feb/serviio_app.png" alt="app" /></p>

<p>Serviio를 설치하면 Serviio와 Serviio-Console이라는 2개의 어플리케이션이 생기며 미디어 서버 역할을 하는 Serviio를 먼저 실행하고 환경 설정을 위한 Serviio-Console을 실행하면 된다.</p>

<p>Serviio-Console은 설정을 목적으로 실행하기 때문에 설정이 완료 된 이후에는 설정을 바꾸고자 하지 않는 이상 Serviio만 실행하면 된다.</p>

<h2 id="serviio-제약사항">Serviio 제약사항</h2>

<p>Serviio를 설정하기 전에 Serviio의 제약사항에 대해서 이야기하면</p>

<ul>
<li>SAMI(smi)자막은 한글 지원이 제대로 되지 않았다.</li>
</ul>

<p>국내에서 많이 사용되는 smi가 안되기 때문에 SubRip(srt)자막을 사용하게 되었는데 srt의 경우 한글에 대해서는 UTF-8로 지정해도 글자가 깨졌고 cp949(euc-kr, Korean Windows)로 저장했을 때 제대로 표기 되었다.</p>

<p>개인적으로 UTF-8로 자막파일을 관리하기 때문에 불편했던 부분인데 몇 가지 테스트 결과 Serviio가 UTF-8을 지원 함에도 깨졌던 이유가 BOM이 없어서 였다. 즉, <a href="BOM" target="_blank">Byte Order Mark</a>이 없으면 UTF-8로 한글자막을 저장해도 제대로 표현을 못하는 문제가 있었던 것이다.</p>

<p>따라서, 한글자막을 위해서는 srt 파일을</p>

<ul>
<li>cp949 (euc-kr, Korean Windows)</li>
<li>UTF-8 with BOM</li>
</ul>

<p>위 두 종류 중에서 한가지로 저장해야만 제대로 표기가 된다.</p>

<h2 id="serviio-설정">Serviio 설정</h2>

<p><img src="/images/2014/Feb/serviio_console.png" alt="korean" /></p>

<p>한글 메뉴를 보기 위해서는 Serviio-Console을 실행 후 &ldquo;Console Settings&rdquo; 메뉴에 들어가서 &ldquo;한국어&rdquo;를 선택하고 Serviio-Console을 재시작 해준다.</p>

<p><img src="/images/2014/Feb/serviio_lib.png" alt="library" /></p>

<p>위 그림과 같이 &ldquo;라이브러리 &gt; 공유폴더&rdquo;로 들어가서 동영상 파일이 저장된 경로를 추가해 주고 비디오, 자동갱신 체크 박스를 선택해 준다. (여기에 추가 된 폴더는 TV에서 DNLA 서버를 선택 했을 때 &ldquo;비디오 &gt; 폴더&rdquo;에서 확인 할 수 있다)</p>

<p>여기까지만 설정해도 동영상을 TV에서 재생할 수 있다. 이제 자막이 필요한 영상을 위한 설정을 살펴보면</p>

<p><img src="/images/2014/Feb/serviio_trans.png" alt="transcoding" /></p>

<p>&ldquo;Delivery&rdquo;메뉴를 선택한 후 트랜스코딩 사용함에 체크 하도록 한다. 그리고, Subtitles 탭에서 3개의 체크 박스를 모두 선택하고 인코딩은 &ldquo;UTF-8&rdquo;로 지정한다. UTF-8로 지정하더라도 cp949 코드로 저장된 srt 한글자막도 잘 나온다. (즉, UTF-8 with BOM, cp949 모두 사용이 가능하다)</p>

<p><img src="/images/2014/Feb/serviio_sub.png" alt="encoding" /></p>

<p>&ldquo;Preferred Language&rdquo;는 선호하는 언어를 지정하는 부분으로 보이지만 어차피 SAMI(smi)에서 한글이 제대로 안되므로 별 의미는 없다.</p>

<p>여기까지 설정하고 저장을 누르면 이제 한글 자막이 제대로 지원되는 DLNA 서버 준비가 끝나며 감상하는 일만 남았다.</p>

<h2 id="추가로">추가로&hellip;</h2>

<p>맥에서 SAMI(smi) 자막을 변환하기 위해서 내가 사용하는 방법은 <a href="http://blog.myhyuny.com/m/post/view/id/375" target="_blank">MinySubtitleConverter</a>라는 툴을 이용한다. 다만, cp949(euc-kr)로 저장된 파일을 변환 시키면 간혹 한글이 일부 깨져서 저장되는 증상이 발견되서 변환 전에 SAMI(smi) 자막을 UTF-8로 먼저 저장해서 변환하는 편이다.</p>

<p>문자인코딩 변환은 <a href="https://itunes.apple.com/kr/app/textwrangler/id404010395?mt=12)" target="_blank">TextWrangler</a>나 iconv 등 본인이 편한 툴을 이용해서 변환하면 된다.</p>

<p>어찌보면 별거 아닌 내용이다. 계속 붙잡고 있던 것은 아니지만 불편한대로 감상하다가 <a href="http://www.serviio.org/" target="_blank">Serviio</a>에서 UTF-8로 저장된 한글 자막이 제대로 안보이는 증상을 해결하려고 틈틈히 삽질을 했었고 <a href="http://www.serviio.org/" target="_blank">Serviio</a>는 까칠하게도 <a href="http://en.wikipedia.org/wiki/Byte_order_mark" target="_blank">Byte Order Mark</a>를 꼭 포함 해 줘야 했다.</p>
]]></content>
        </item>
        
        <item>
            <title>megasas_register_aen[0]: already registered</title>
            <link>/2014/02/10/megasas_register_aen0-already-registered/</link>
            <pubDate>Mon, 10 Feb 2014 02:24:30 +0000</pubDate>
            
            <guid>/2014/02/10/megasas_register_aen0-already-registered/</guid>
            <description>MegaRAID 기반 시스템의 메시지 증상 및 환경 RHEL5 2.6.18-194.26.1el5 커널 부터 dmesg에 아래와 같은 메시지가 나타납니다.
megasas_register_aen[0]: already registered  원인 RedHat 문서에서도 밝히고 있는 것 처럼 해당 메시지는 오류 메시지가 아니라 이벤트 등록과 관련 된 단순한 정보전달 메시지이므로 무시해도 됩니다.
커널 패치 내용은 아래와 같습니다.
--- kernel-2.6.18-194.17.4/linux-2.6.18.x86_64/drivers/scsi/megaraid/megaraid_sas.c 2010-12-09 20:53:39.000000000 +0530 +++ kernel-2.6.18-194.26.1/linux-2.6.18.x86_64/drivers/scsi/megaraid/megaraid_sas.c 2010-12-09 21:23:56.000000000 +0530 @@ -2992,6 +3681,8 @@ megasas_register_aen(struct megasas_inst * Previously issued event registration includes * current request.</description>
            <content type="html"><![CDATA[

<h1 id="megaraid-기반-시스템의-메시지">MegaRAID 기반 시스템의 메시지</h1>

<h2 id="증상-및-환경">증상 및 환경</h2>

<p><a href="http://www.redhat.com" target="_blank">RHEL5</a> 2.6.18-194.26.1el5 커널 부터 dmesg에 아래와 같은 메시지가 나타납니다.</p>

<pre><code>megasas_register_aen[0]: already registered
</code></pre>

<h2 id="원인">원인</h2>

<p><a href="https://access.redhat.com/site/solutions/43634" target="_blank">RedHat 문서</a>에서도 밝히고 있는 것 처럼 해당 메시지는 오류 메시지가 아니라 이벤트 등록과 관련 된 단순한 정보전달 메시지이므로 무시해도 됩니다.</p>

<p>커널 패치 내용은 아래와 같습니다.</p>

<pre><code>--- kernel-2.6.18-194.17.4/linux-2.6.18.x86_64/drivers/scsi/megaraid/megaraid_sas.c     2010-12-09 20:53:39.000000000 +0530
+++ kernel-2.6.18-194.26.1/linux-2.6.18.x86_64/drivers/scsi/megaraid/megaraid_sas.c     2010-12-09 21:23:56.000000000 +0530

@@ -2992,6 +3681,8 @@ megasas_register_aen(struct megasas_inst
         * Previously issued event registration includes
         * current request. Nothing to do.
         */
+       printk(KERN_INFO &quot;%s[%d]: already registered\n&quot;,
+               __FUNCTION__, instance-&gt;host-&gt;host_no);
        return 0;
    } else {
        curr_aen.members.locale |= prev_aen.members.locale;

</code></pre>

<h2 id="관련-문서">관련 문서</h2>

<ul>
<li><a href="https://access.redhat.com/site/solutions/43634" target="_blank">Redhat Knowledgebase 43634</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>mount : already mounted or busy 메시지</title>
            <link>/2014/02/10/mount-already-mounted-or-busy-mesiji/</link>
            <pubDate>Mon, 10 Feb 2014 02:10:41 +0000</pubDate>
            
            <guid>/2014/02/10/mount-already-mounted-or-busy-mesiji/</guid>
            <description>환경 및 증상 디스크 교체 및 비RAID 환경에서 아래와 같은 메시지와 함께 마운트가 되지 않는 증상 발생
$ mount -t ext4 /dev/sdb1 /disk/2 mount : /dev/sdb1 already mounted or /disk/2 busy 원인 교체 된 디스크에 다른 곳에서 설정 된 RAID flag 정보가 남아있거나 dmraid가 RAID 멤버로 잘못 인지한 경우에 발생.
해결 Case 1. dmraid 아래 명령을 통해서 dmraid가 해당 디스크를 RAID 구성원으로 판단하고 있는지 확인이 가능.
$ dmraid -s  dmraid 패키지를 통해 RAID를 사용하지 않는다면 해당 패키지를 삭제하고 재부팅 하는 방법이 있지만 mkinitrd와 의존성을 같는 경우가 있기 때문에 현 시스템에 RAID를 사용하지 않는 다면(혹은 H/W RAID 컨트롤러로만 볼륨을 관리한다면) 아래 명령을 통해서 설정을 삭제할 수 있음.</description>
            <content type="html"><![CDATA[

<h1 id="환경-및-증상">환경 및 증상</h1>

<p>디스크 교체 및 비RAID 환경에서 아래와 같은 메시지와 함께 마운트가 되지 않는 증상 발생</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ mount -t ext4 /dev/sdb1 /disk/2
mount : /dev/sdb1 already mounted or /disk/2 busy</code></pre></div>
<h1 id="원인">원인</h1>

<p>교체 된 디스크에 다른 곳에서 설정 된 RAID flag 정보가 남아있거나 dmraid가 RAID 멤버로 잘못 인지한 경우에 발생.</p>

<h1 id="해결">해결</h1>

<h2 id="case-1-dmraid">Case 1. dmraid</h2>

<p>아래 명령을 통해서 dmraid가 해당 디스크를 RAID 구성원으로 판단하고 있는지 확인이 가능.</p>

<pre><code>$ dmraid -s
</code></pre>

<p>dmraid 패키지를 통해 RAID를 사용하지 않는다면 해당 패키지를 삭제하고 재부팅 하는 방법이 있지만 mkinitrd와 의존성을 같는 경우가 있기 때문에 현 시스템에 RAID를 사용하지 않는 다면(혹은 H/W RAID 컨트롤러로만 볼륨을 관리한다면) 아래 명령을 통해서 설정을 삭제할 수 있음.</p>

<pre><code>$ dmraid -r -E
</code></pre>

<p>위 명령을 실행하여 설정을 삭제하고 아래와 같이 RAID 구성목록 확인 후 시스템 재부팅.</p>

<pre><code>$ dmraid -s
no raid disks
</code></pre>

<h2 id="case-2-dmsetup">Case 2. dmsetup</h2>

<p>dmraid로 제대로 삭제가 되지 않았거나 LVM 형태의 정보가 남아서 마운트 되지 않는 경우에는 아래 명령으로 현재 장치의 매핑 상태를 확인 할 수 있다.</p>

<pre><code>$ dmsetup status
VG_XenStorage--0f3d6feb--3e29--bc3b--85f0--17e66fa43a0d-MGT: 0 8192 linear
</code></pre>

<p>위 명령으로 결과가 나온다면 아래와 같이 매핑 된 장치를 삭제해 준다</p>

<pre><code>$ dmsetup remove 장치명

ex)
$ dmsetup remove VG_XenStorage--0f3d6feb--3e29--bc3b--85f0--17e66fa43a0d-MGT
</code></pre>

<p>삭제가 제대로 되었는지 다시 한 번 확인 해준 뒤에</p>

<pre><code>$ dmsetup status
No devices found
</code></pre>

<p>서버를 재부팅 해주면 끝.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] Linux 메모리 효율을 위한 vfs_cache_pressure</title>
            <link>/2014/02/06/faq-linux-memori-hyoyuleul-wihan-vfs_cache_pressure/</link>
            <pubDate>Thu, 06 Feb 2014 17:41:01 +0000</pubDate>
            
            <guid>/2014/02/06/faq-linux-memori-hyoyuleul-wihan-vfs_cache_pressure/</guid>
            <description>유휴 메모리가 전부 어디로 간거지? (Page Cache) Linux는 I/O 성능을 높이기 위해서 Page Cache를 사용한다. 이 글에서는 Page Cache에 대해서는 다루지 않지만 간단히 설명하면 다음과 같다. Linux는 물리적인 저장/통신 장치와 데이터를 주고 받을 때 메모리에 먼저 적재한 후에 데이터를 주고 받는데 이는 동일한 데이터에 대한 접근을 할 경우 메모리에서 바로 가져오도록 하여 I/O 성능을 높이기 위함이다. 이를 Page라는 단위로 관리를 하며 흔히 Page Cache라고 이야기 한다.
따라서, 한번이라도 데이터를 읽거나 쓴 적이 있다면 메모리는 Page Cache에 적재되고 아래의 파일에서 Cached 영역으로 표기 된다.</description>
            <content type="html"><![CDATA[

<h1 id="유휴-메모리가-전부-어디로-간거지-page-cache">유휴 메모리가 전부 어디로 간거지? (Page Cache)</h1>

<p>Linux는 I/O 성능을 높이기 위해서 Page Cache를 사용한다. 이 글에서는 Page Cache에 대해서는 다루지 않지만 간단히 설명하면 다음과 같다. Linux는 물리적인 저장/통신 장치와 데이터를 주고 받을 때 메모리에 먼저 적재한 후에 데이터를 주고 받는데 이는 동일한 데이터에 대한 접근을 할 경우 메모리에서 바로 가져오도록 하여 I/O 성능을 높이기 위함이다. 이를 Page라는 단위로 관리를 하며 흔히 Page Cache라고 이야기 한다.</p>

<p>따라서, 한번이라도 데이터를 읽거나 쓴 적이 있다면 메모리는 Page Cache에 적재되고 아래의 파일에서 Cached 영역으로 표기 된다.</p>

<pre><code>/proc/meminfo
</code></pre>

<p>Linux 커뮤니티에서 흔히 받는 질문 중 하나인 &lsquo;왜? 제 Linux의 Free 메모리가 이것밖에 안되나요?&lsquo;의 원인이 Page Cache 매커니즘이다.</p>

<h1 id="메모리를-전부-사용하고-있는-시스템">메모리를 전부 사용하고 있는 시스템</h1>

<p>아래 그림은 특정 시스템의 메모리 사용현황을 RRDTool을 이용해서 그래프로 도식화 한 것이다. 24GB 메모리의 대부분을 사용중에 있는 것으로 나타나는데 실제 해당 서버의 프로세스가 사용하고 있는 메모리 크기를 확인해 보면 15GB 정도를 사용하고 있는 시스템이었다.</p>

<p><img src="https://lh4.googleusercontent.com/-hJeiJRbIWeM/UBo20Cr9TgI/AAAAAAAAACU/BI00tARL5-Y/s800/vfs_cache_pressure1.jpg" alt="시스템1" /></p>

<p>그렇다면, 남은 메모리는 어디로 간 것일까? 그래프에서 Page Cache로 잡힌 부분은 Cached (파란색)으로 표기가 된다. 따라서, 남은 메모리가 Page Cache에 의해서 사용 된 것이 아니라는 걸 알 수 있다.</p>

<p>그 많은 메모리는 대체 어디로 사라진 걸까?</p>

<h1 id="slab">Slab</h1>

<p>Slab Allocator라는 것이 있다. 이는 일종의 자원 할당자 중 하나로 4KB의 크기를 가진 Page로 데이터를 저장하고 관리할 경우 발생하는 단편화를 최소화 하기 위해 고안 된 물건이다. Linux의 커널은 자료구조로 Slab을 사용하고 있으며 /proc/meminfo에서 아래 항목은 Linux 커널이 사용하는 캐시 크기를 의미한다.</p>

<pre><code>Slab:           349364 kB
</code></pre>

<p>Linux 커널에서 커널과 디바이스 드라이버, 파일시스템 등은 영구적이지 않은 데이터들을 저장하기 위한 공간이 필요한데(inode, task 구조체, 장치 구조체 등) 이것이 Slab 구조하에 관리 되고 있다. 따라서, 앞서 언급한 meminfo 파일의 Slab 항목은 이러한 데이터들의 메모리상 크기를 의미한다. 그래서 커널 캐시라고도 표현한다.</p>

<p>이러한 캐시 데이터 중에서 본 글의 목적과 관련성이 높은 것은 inode와 dentry에 대한 캐시이다. inode와 dentry는 파일 자료구조를 의미한다. VFS(Virtual File System)와 관련된 부분을 공부하다보면 자주 만나게 되는 dentry는 경로명 탐색을 위한 캐시 역할도 수행한다고 알려져 있다.</p>

<p>간단히 얘기해서 어떠한 파일을 생성할 때 파일의 정보를 담고 있는 inode와 dentry는 보다 빠른 데이터 접근을 위해서 커널의 Slab 자료구조에 추가된다고 이해하면 된다.</p>

<h1 id="사라진-메모리를-찾아보자">사라진 메모리를 찾아보자</h1>

<p>앞서 살펴본 시스템에서 어플리케이션(프로세스)이 사용하는 메모리를 뺀 나머지 메모리는 /proc/meminfo 파일의 Slab 항목에서 찾을 수 있었다. Slab의 크기가 9GB 정도 되는 것으로 확인 되었다. 즉, 커널이 Slab 자료구조에 계속해서 캐시데이터로 담고 있었던 것이다.</p>

<p>왜 이런 일이 벌어졌을까?</p>

<p>이는 동작하고 있는 프로세스의 성격과 관련이 높다. 해당 프로세스가 주로 하는 작업의 패턴을 확인 해 본 결과 특정 파일들을 다량으로 생성하고 이 데이터를 가공처리하는 작업을 반복하고 있었다. 다만, 이러한 작업 과정에서 생성되고 삭제되는 파일이 매우 많은 것으로 확인 되었다.</p>

<p>앞서 이야기 한대로 Linux 커널은 파일시스템의 성능을 나아가 시스템의 성능을 개선하기 위해 inode와 dentry를 메모리에 캐시한다. 하지만, 파일을 빈번하게 생성/삭제 하거나 다량의 파일을 다루는 시스템의 경우 해당 파일을 자주 재활용하지 않는다면 (즉, 생성/기록 후에 데이터를 지속해서 접근하여 읽지 않는 경우) 캐시에 메모리를 사용하기 보다는 I/O를 위한 버퍼 또는 프로세스에 할당되어 활용 되는 편이 좋다.</p>

<p>Linux Slab 자료구조의 상태에 대해서 자세히 살펴 볼 수 있는 slabtop이란 명령이 존재한다. 이 명령을 실행 해 보면 inode 캐시(ext3혹은 ext4_inode_cache라는 이름)와 dentry_cache의 현재 크기를 알 수 있다.</p>

<p>Slab에 대해서 조금 더 알고 싶으면 <a href="http://www.secretmango.com/jimb/Whitepapers/slabs/slab.html" target="_blank">이 글</a>을 추천한다.</p>

<h1 id="vfs-cache-pressure">vfs_cache_pressure</h1>

<p>Linux 커널의 vm 구조와 관련된 파라미터로 vfs_cache_pressure라는 것이 존재한다. 이 파라미터는 디렉토리와 inode 오브젝트에 대한 캐시로 사용된 메모리를 반환(reclaim)하는 경향의 정도를 지정하는 항목이다. 기본 값은 100.</p>

<p>이 값을 0으로 설정하게 되면 Linux 커널은 오브젝트에 대한 캐시를 반환하려고 하지 않을 것이며 얼마 지나지 않아 시스템은 Out of Memory 상태를 호소할 것이다. (커널이 메모리를 다 먹어버렸다고!!)</p>

<p>그리고, 100 이상의 값을 주면 Linux 커널은 오브젝트에 대한 캐시를 가급적 반환하려고 하며 (다른 말로 가급적 캐시해서 보관하려고 하지 않으려 든다) 이를 이용하면 inode와 dentry 캐시를 줄일 수가 있다.</p>

<h1 id="메모리를-되찾자">메모리를 되찾자</h1>

<p>vfs_cache_pressure를 이용해서 Linux 커널에게 캐시 데이터를 반환하도록 요구해 보자. 100이상의 값을 설정하면 되는데 경험상 10000정도로 설정하면 큰 문제 없이 (즉, 커널이 반환한 캐시 데이터로 인해 성능이 저하되는 문제 등) 운영할 수 있다. 이 값에 대해서는 각자 사용하는 시스템에 값을 바꾸어보면서 확인하는게 가장 좋다.</p>

<p>해당 값의 변경은 아래와 같이 할 수 있다. (이 글에 관심이 있는 분이라면 당연히 알만한 내용이지만)</p>

<pre><code>echo 10000 &gt; /proc/sys/vm/vfs_cache_pressure
또는
$ sysctl vm.vfs_cache_pressure=10000

영구적으로 설정하기 위해서는 /etc/sysctl.conf 파일에 아래와 같이 추가
vm.vfs_cache_pressure = 10000
</code></pre>

<p>앞서 살펴본 시스템에서 값을 변경하고 나니 아래와 같이 바뀌었다. 9GB 정도의 Real 영역이 해제되고 이 영역이 Buffer로 활용되기 시작하였다.</p>

<p><img src="https://lh4.googleusercontent.com/-58K71GSvprE/UBo20woClNI/AAAAAAAAACc/RWaSTR0wWJg/s800/vfs_cache_pressure2.jpg" alt="시스템2" /></p>

<p>즉, 커널이 잡고 있던 캐시를 해제하여 Buffer로 활용되고 나니 시스템의 부하 값 (Load 값)이 낮아지기 시작했다. 힘겨워 하던 시스템을 안정시킬 수 있게 되었다.</p>

<p><img src="https://lh6.googleusercontent.com/-2iHpoEvlZ6A/UBqrDpNhXFI/AAAAAAAAAC0/at-KyH-n3V4/s800/vfs_cache_pressure3.jpeg" alt="시스템3" /></p>

<blockquote>
<p>Load 값 : 현재 시스템에서 실행 중인 프로세스와  non-interruptible 상태에 있는 프로세스의 숫자에 대한 평균 값으로 시스템의 부하 정도를 가늠하는 지표로 사용된다. 좀 더 재밌는 설명을 원한다면 <a href="http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages" target="_blank">이 글</a>을 추천한다.</p>
</blockquote>

<h1 id="그래서">그래서</h1>

<p>재미없는 이야기로 기술되었지만 요약하자면 기본적으로 Linux 커널은 유휴 메모리가 있다면 캐시하려고 들기 때문에 어떤 경우에는 이렇게 사용되는 메모리의 양을 조절하면 더 좋은 효과를 얻을 수 있다는 이야기 이다. (이 말이 더 어렵다)</p>

<h1 id="추가로-알아두면-좋은-팁">추가로 알아두면 좋은 팁</h1>

<p>앞서 vfs_cache_pressure를 통해서 Linux 커널의 VFS 관련 오브젝트 캐시경향을 조절하는 방법외에도 이러한 정책을 세워서 효과를 보기보다 지금 당장 캐시를 비우기만 하고 싶을 때는 아래와 같은 방법이 있다.</p>

<pre><code>echo 1 &gt; /proc/sys/vm/drop_caches
echo 2 &gt; /proc/sys/vm/drop_caches
echo 3 &gt; /proc/sys/vm/drop_caches
</code></pre>

<p>각각의 숫자 값은 아래와 같은 의미를 가지고 있으며 해당 값이 설정되면 영구적으로 지속되는 것이 아니라 값이 설정되는 순간만 그 값에 따라서 반영 될 뿐이다.</p>

<blockquote>
<p><strong>주의</strong> : 아래 명령을 수행하기 전에 반드시 sync 등을 통해서 캐시에 휘발성으로 담긴 데이터를 실제 저장 장치에 반영시키도록 해야 한다.</p>
</blockquote>

<ul>
<li>drop_caches = 1

<ul>
<li>Page cache를 해제 한다.</li>
</ul></li>
<li>drop_caches = 2

<ul>
<li>inode, dentry cache를 해제 한다.</li>
</ul></li>
<li>drop_caches = 3

<ul>
<li>Page cache, inode cache, dentry cache를 모두 해제 한다.</li>
</ul></li>
</ul>

<p>만약, 3번으로 설정하면 시스템이 잠시 멈추는 증상을 경험 할 수도 있다. (모든 캐시를 비우기 위해서 혼신의 힘을 다할테니깐)</p>

<h2 id="주저리">주저리</h2>

<p>다른 쓸 주제들이 아직도 많은데&hellip;. 이놈의 귀차니즘&hellip;.</p>
]]></content>
        </item>
        
        <item>
            <title>Linux에서 MBR 파티션 테이블 살펴보기</title>
            <link>/2014/02/05/faq-linuxeseo-mbr-patisyeon-teibeul-salpyeobogi/</link>
            <pubDate>Wed, 05 Feb 2014 17:41:55 +0000</pubDate>
            
            <guid>/2014/02/05/faq-linuxeseo-mbr-patisyeon-teibeul-salpyeobogi/</guid>
            <description>기존 Tumblr의 글을 옮겨온 것이기 때문에 Markdown 확장문법 유무로 인한 차이가 있습니다.  MBR 파티션 테이블 본 문서는 리눅스 시스템에서 MBR 파티션 테이블의 구조를 확인하는 방법에 대해서 설명하고 있습니다.1
MBR이란? MBR은 Master Boot Record의 약자로 부팅을 하기 위한 정보를 담고 있으며 일반적으로 0번 섹터에 저장 되어 있습니다. MBR이 여전히 많이 쓰이고 있지만 오래 된 기술이다보니 그 크기는 1개 섹터 크기 (512Byte)로 되어있어 제약사항이 많습니다. 상세한 정보는 위키피디아 문서를 참고하시면 됩니다.</description>
            <content type="html"><![CDATA[

<ul>
<li>기존 Tumblr의 글을 옮겨온 것이기 때문에 Markdown 확장문법 유무로 인한 차이가 있습니다.</li>
</ul>

<h1 id="mbr-파티션-테이블">MBR 파티션 테이블</h1>

<p>본 문서는 리눅스 시스템에서 MBR 파티션 테이블의 구조를 확인하는 방법에 대해서 설명하고 있습니다.<sup class="footnote-ref" id="fnref:1"><a href="#fn:1">1</a></sup></p>

<h2 id="mbr이란">MBR이란?</h2>

<p>MBR은 Master Boot Record의 약자로 부팅을 하기 위한 정보를 담고 있으며 일반적으로 0번 섹터에 저장 되어 있습니다. MBR이 여전히 많이 쓰이고 있지만 오래 된 기술이다보니 그 크기는 1개 섹터 크기 (512Byte)로 되어있어 제약사항이 많습니다. 상세한 정보는 위키피디아 문서를 참고하시면 됩니다.</p>

<ul>
<li>위키피디아 <a href="http://en.wikipedia.org/wiki/Master_boot_record" target="_blank">MBR</a> 문서</li>
</ul>

<h2 id="mbr-구조">MBR 구조</h2>

<p><img src="http://i.technet.microsoft.com/dynimg/IC367604.jpg" alt="MBR Structure" /></p>

<ul>
<li>표1. (출처 : Microsoft Technet)</li>
</ul>

<p>MBR은 위와 같은 구조로 되어있습니다. 부팅을 위한 코드 공간이 446Byte. 프라이머리 파티션 테이블을 위한 공간이 64Byte 그리고 MBR 시그니쳐 값으로 2Byte (0xAA55)를 사용합니다. 본 문서에서는 프라이머리 파티션 테이블 부분을 살펴보도록 하겠습니다.</p>

<h2 id="mbr-primary-partition-table">MBR Primary Partition Table</h2>

<p><img src="http://i.imgur.com/9ZG9HQo.png" alt="Imgur" /></p>

<ul>
<li>표2. (출처: <a href="http://ko.wikipedia.org/wiki/%EB%A7%88%EC%8A%A4%ED%84%B0_%EB%B6%80%ED%8A%B8_%EB%A0%88%EC%BD%94%EB%93%9C" target="_blank">위키피디아</a>)</li>
</ul>

<p>64Byte의 MBR 파티션 테이블 구조는 위와 같은 16Byte 레코드 4개로 이루어져 있습니다. 각 레코드에는 파티션의 상태 정보와 종류, C/H/S 기반의 시작/끝 주소, LBA 기반의 섹터 시작 주소와 파티션 크기 정보가 담겨져 있습니다. 요즘에는 CHS 기반으로 접근하지 않기 때문에 실질적으로 <strong>첫 번째 LBA주소와 파티션 크기</strong>를 통해서 파티션 테이블 레이아웃을 표현 합니다.</p>

<p>먼저 디스크의 파티션 테이블을 살펴보면 아래와 같습니다. 섹터단위로 보기 위해서 <strong>-lu</strong> 옵션을 주었습니다. 아래 파티션 테이블은 첫 번째 파티션이 <a href="abbr:Advanced Format" target="_blank">AF</a> 디스크를 위해서 2048 섹터부터 시작하도록 파티셔닝 되어있습니다.</p>

<pre><code>$ fdisk -lu /dev/sda

Disk /dev/sda: 256.1 GB, 256060514304 bytes
255 heads, 63 sectors/track, 31130 cylinders, total 500118192 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000f1ef8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048   199999487    99998720   83  Linux
/dev/sda2       199999488   500118191   150059352   83  Linux
</code></pre>

<p>앞서 살펴본 MBR 파티션 테이블에서는 Start에 해당하는 LBA주소 2048과 그 크기인 199997440이 저장되어있고 이를 통해서 End 위치<sup class="footnote-ref" id="fnref:2"><a href="#fn:2">2</a></sup> 를 표기하게 됩니다.</p>

<p>자, 이제 MBR 파티션 테이블을 열어서 실제로 저장이 되어있는지 확인해 보도록 하겠습니다.</p>

<h3 id="mbr-파티션-테이블-열어보기">MBR 파티션 테이블 열어보기</h3>

<p>먼저 MBR은 0번 섹터에 저장 되기 때문에 아래 커맨드를 통해서 MBR을 통째로 덤프 받도록 합니다. (본 예제에서는 /dev/sda가 OS 디스크 입니다) 1섹터는 512byte 이기 때문에 512byte 1개를 파일로 내려 받습니다.</p>

<pre><code>$ dd if=/dev/sda of=mbr.dump bs=512 count=1
</code></pre>

<p>이제 이 파일에서 파티션 테이블에 해당하는 뒤쪽 66byte만 확인하면 아래와 같습니다.</p>

<ul>
<li><p>사실 MBR을 따로 받았기 때문에 -n 66 옵션은 생략해도 무방합니다. 그리고 MBR을 저장하지 않고 직접 디스크에서 덤프하는 방법도 있습니다. 본 예제에서는 덤프받은 파일을 가지고 설명합니다.</p>

<pre><code>$ hexdump -s 446 -n 66 mbr.dump
00001be 2080 0021 fe83 ffff 0800 0000 b800 0beb
00001ce 6400 a104 e983 9a7f c000 0beb 72b0 11e3
00001de 0000 0000 0000 0000 0000 0000 0000 0000
*
00001fe aa55
0000200
</code></pre></li>
</ul>

<p>마지막에 앞서 살펴본 MBR의 시그니쳐인 0xAA55의 값인 <strong>aa55</strong>가 보입니다.</p>

<p>이렇게 보면 읽기 어렵기 때문에 MBR 파티션테이블의 레코드가 16Byte 이므로 16Byte씩 끊어서 보기좋게 표시해 보도록 하겠습니다.</p>

<pre><code>$ hexdump -s 446 -e '8/1 &quot;0x%02x &quot; &quot;\t&quot; 2/4 &quot;%0d &quot;\n&quot;' mbr.dump
0x80 0x20 0x21 0x00 0x83 0xfe 0xff 0xff 2048 199997440
0x00 0x64 0x04 0xa1 0x83 0xe9 0x7f 0x9a 199999488 300118704
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0 0
*
0x55 0xaa 0x   0x   0x   0x   0x   0x
</code></pre>

<p>먼저, 표2.의 파티션테이블의 레코드 명세에 따라서 8Byte는 1Byte 씩 Hex로 표기하도록 하였고 나머지 8Byte는 4Byte씩 숫자로 표기하도록 하였습니다. 그 결과 첫 번째 줄의 마지막 두 컬럼에 fdisk로 확인했던 첫 번째 파티션의 시작 주소인 2048과 그 크기를 확인 할 수 있습니다. 마찬가지로 총 2개의 파티션 테이블을 가지고 있기 때문에 두 번째 줄에서도 두 번째 파티션의 시작 주소와 크기를 확인할 수 있습니다.</p>

<p>이제 Hex로 표기한 8Byte를 살펴보도록 하겠습니다. 첫 번째 줄을 살펴보면 표2.의 명세대로 첫 번째 Byte는 파티션의 상태를 의미합니다. 첫 번째 줄의 0x80을 통해서 부트 파티션임을 확인 할 수 있으며 이는 fdisk에서 &ldquo;*&rdquo; 로 표기 된 부트 플래그를 의미하는 걸 알 수 있습니다.</p>

<p>그 뒤의 3Byte(0x20 0x21 0x00)는 C/H/S 값을 의미하며 다음 1Byte 0x83은 파티션의 종류를 의미합니다. 낯익은 숫자인데 이 숫자는 fdisk에서 파티션 타입을 지정할 때 사용하는 값과 정확히 일치합니다.</p>

<p><img src="http://i.imgur.com/YI5E4bY.png" alt="Imgur" /></p>

<p>그리고 나머지 3Byte (0xfe 0xff 0xff)는 종료 지점을 나타내는 CHS 값 입니다.</p>

<p>다시, 시작지점 C/H/S를 나타내는 앞의 3Byte 값을 CHS to LBA 변환 식에 대입해 보면</p>

<pre><code>LBA = ( ( CYLINDER * heads per cylinder + HEAD ) * sectors per track ) + SECTOR – 1
LBA = ( ( 0x00 * 255 + 0x20 ) * 63) + 0x21 -1
 = 2048
</code></pre>

<p>위와 같이 시작 지점을 나타내는 LBA 주소 값을 얻을 수 있습니다. 실제로 요즘에는 사용되지 않는(다만 OS에서 지원해주고 있는) C/H/S 값은 LBA를 통해 역으로 계산 된 것이기 때문에 참고로만 보시면 됩니다.</p>

<p>이렇게 실제 디스크의 첫 번째 섹터에 있는 MBR을 열어보고 시스템 툴에서 보여주는 값이 어떻게 저장되어 있는지 확인해 보았습니다.</p>

<h2 id="보너스">보너스</h2>

<ul>
<li>MBR은 0번 섹터에 기록되기 때문에 [GPT] 는 1번 섹터부터 기록 됩니다.</li>
<li>MBR이 담긴 섹터의 앞쪽 446Byte는 부트코드가 담겨있는데 이곳에 GRUB 부트로더가 들어있습니다. ($ hexdump -C -n446 mbr.dump로 확인해 보세요)</li>
<li>MBR의 파티션 테이블 레코드에서 파티션 크기를 나타내는 값이 4Byte이기 때문에 2^32인 2TB까지만 사용이 가능합니다. 즉, MBR로 단일 파티션이 2TB이상 사용하지 못하는 이유입니다</li>
</ul>
<div class="footnotes">

<hr />

<ol>
<li id="fn:1">이 문서는 MBR에 대한 질문을 받아서 설명해 줬던 내용을 정리한 것입니다.
 <a class="footnote-return" href="#fnref:1"><sup>[return]</sup></a></li>
<li id="fn:2">2048 + 199997440 - 1 (2048부터 시작하기 때문)
 <a class="footnote-return" href="#fnref:2"><sup>[return]</sup></a></li>
</ol>
</div>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] libudev.so.0 라이브러리 오류</title>
            <link>/2014/02/05/faq-libudev-so-0-raibeureori-oryu/</link>
            <pubDate>Wed, 05 Feb 2014 17:41:43 +0000</pubDate>
            
            <guid>/2014/02/05/faq-libudev-so-0-raibeureori-oryu/</guid>
            <description>Error while loading shared libraries (libudev.so.0)  오류메시지  error while loading shared libraries: libudev.so.0: cannot open shared object file
 환경 - Ubuntu 13.04 또는 Linux Mint 15 (64bit)  Ubuntu 저장소를 이용한 패키지 설치가 아닌 외부 패키지를 설치했을 때 이런 메시지를 종종 볼 수 있는데 오류 메시지 그대로 라이브러리를 찾지 못해서 발생하는 메시지이다. 아래 명령을 통해서 쉽게 수정 할 수 있다.
$ cd /lib/x86_64-linux-gnu $ sudo ln -s libudev.</description>
            <content type="html"><![CDATA[

<h3 id="error-while-loading-shared-libraries-libudev-so-0">Error while loading shared libraries (libudev.so.0)</h3>

<ul>
<li>오류메시지</li>
</ul>

<p><code>error while loading shared libraries: libudev.so.0: cannot open shared object file</code></p>

<ul>
<li>환경 - Ubuntu 13.04 또는 Linux Mint 15 (64bit)</li>
</ul>

<p>Ubuntu 저장소를 이용한 패키지 설치가 아닌 외부 패키지를 설치했을 때 이런 메시지를 종종 볼 수 있는데 오류 메시지 그대로 라이브러리를 찾지 못해서 발생하는 메시지이다. 아래 명령을 통해서 쉽게 수정 할 수 있다.</p>

<pre><code>$ cd /lib/x86_64-linux-gnu
$ sudo ln -s libudev.so.1 libudev.so.0
$ sudo ldconfig
</code></pre>

<p>별 것 아닌 메시지 이지만 libudev 파일 위치를 몰라서 헤매는 경우를 봤기에 간단히 포스팅 해봅니다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] Upstart 사용에 대해서</title>
            <link>/2014/02/05/faq-upstart-sayonge-daehaeseo/</link>
            <pubDate>Wed, 05 Feb 2014 17:41:32 +0000</pubDate>
            
            <guid>/2014/02/05/faq-upstart-sayonge-daehaeseo/</guid>
            <description>Upstart 기존 Unix &amp;ldquo;System V&amp;rdquo;에 있던 init 시스템을 대체하기 위한 프로그램 입니다. init을 대체할 뿐만 아니라 다양한 기능을 제공 합니다 또한, upstart는 이벤트 기반으로 동작하기 때문에 설정 내용에 따라서 init 뿐만 아니라 cron, atd, anacron을 대체 할 수도 있으며 inetd와 유사하게 설정도 가능합니다.
예제로 알아보는 job 설정  기본적으로 upstart의 job 설정파일은 /etc/init 아래에 존재 합니다 상세한 내용보다는 즉시 써먹을 만한 간단한 설정에 대해서 소개 합니다  예제 : tty1 설정 파일  # tty1 - getty # # This service maintains a getty on tty1 from the point the system is # started until it is shut down again.</description>
            <content type="html"><![CDATA[

<h1 id="upstart">Upstart</h1>

<p>기존 Unix &ldquo;System V&rdquo;에 있던 init 시스템을 대체하기 위한 프로그램 입니다. init을 대체할 뿐만 아니라 다양한 기능을 제공 합니다
또한, upstart는 이벤트 기반으로 동작하기 때문에 설정 내용에 따라서 init 뿐만 아니라 cron, atd, anacron을 대체 할 수도 있으며 inetd와 유사하게 설정도 가능합니다.</p>

<h1 id="예제로-알아보는-job-설정">예제로 알아보는 job 설정</h1>

<ul>
<li>기본적으로 upstart의 job 설정파일은 /etc/init 아래에 존재 합니다</li>
<li>상세한 내용보다는 즉시 써먹을 만한 간단한 설정에 대해서 소개 합니다</li>
</ul>

<h2 id="예제-tty1-설정-파일">예제 : tty1 설정 파일</h2>

<pre>
# tty1 - getty
#
# This service maintains a getty on tty1 from the point the system is
# started until it is shut down again.

start on stopped rc RUNLEVEL=[2345] and (
            not-container or
            container CONTAINER=lxc or
            container CONTAINER=lxc-libvirt)

stop on runlevel [!2345]

respawn
exec /sbin/getty -8 38400 tty1
</pre>
JOB 파일의 문법은 직관적 형태를 띄고 있습니다.

### start on / stop on
먼저 start on, stop on에 대해서 살펴보면 뒤에 지시되는 이벤트에 대해서 각각 시작, 정지를 하도록 지정하는 내용입니다. upstart는 관리자가 직접 시작/종료 처리를 해 줄 수도 있지만 JOB 파일에 지정한 이벤트에 따라서 자동으로 시작/종료가 가능합니다.
start on/stop on 뒤에는 보통 아래와 같은 이벤트 지시지가 따라 붙습니다
<pre>
start on startup
start on runlevel [23]
start on stopped rcS
start on started tty1
</pre>

<ul>
<li>startup : 시스템이 부팅할 때를 의미합니다 (보통 start on과 같이 쓰임)</li>
<li>runlevel : 뒤에 지정 된 런레벨을 의미합니다. 만약 [!23] 으로 설정되었다면 2,3 런레벨을 제외한 나머지 경우를 의미합니다. 런레벨은 0~6,S가 올 수 있습니다.</li>
<li>stopped : 뒤에 지정 된 JOB이 종료 되었을 때를 의미합니다.</li>
<li>started : 뒤에 지정 된 JOB이 시작 되었을 때를 의미합니다.</li>
<li>shutdown : 시스템 종료할 때를 의미합니다. (보통 stop on과 같이 쓰임)</li>
</ul>

<h3 id="respawn">respawn</h3>

<p>이 지시자가 있으면 해당 프로세스가 죽었을 경우 (또는 crash 되었을 경우) 자동으로 프로세스를 다시 실행시켜 줍니다.</p>

<h3 id="exec">exec</h3>

<p>실제 서비스가 시작될 때 실행하는 내용을 담고 있습니다. 위 예제에서는 /sbin/getty를 실행하도록 되어있습니다.</p>

<p>이 외에도 많은 설정 방법이 존재하는데 <a href="http://upstart.ubuntu.com/cookbook/" target="_blank">Upstart Cookbook</a>을 참고하시면 됩니다.</p>

<h2 id="upstart-명령">Upstart 명령</h2>

<p>Upstart 명령은 아래와 같습니다. init system을 대체하기 때문에 init과 동일한 이름의 명령도 존재 합니다.</p>

<h4 id="서비스-제어-service-명령으로도-바꿔서-사용할-수-있습니다">서비스 제어 (service 명령으로도 바꿔서 사용할 수 있습니다)</h4>

<ul>
<li>initctl - service 명령의 대체 명령으로 사용이 가능합니다. 보통 initctl [JOB이름] [명령] 형태로 사용합니다</li>
<li>start - 서비스 시작</li>
<li>stop - 서비스 종료</li>
<li>reload - SIGHUP 시그널을 전송합니다.</li>
<li>restart - JOB설정파일을 읽지 않고 서비스를 재시작 합니다</li>
<li>status - 서비스 상태 확인</li>
<li>예제
<pre>
$ initctl console stop

<dl>
<dd>initctl을 통해서 console JOB을 종료한다
$ stop console</dd>
<dd>console JOB을 종료한다
$ start serial</dd>
<dd>serial JOB을 실행한다
</pre></dd>
</dl></li>
</ul>

<h4 id="리부팅-시스템-종료">리부팅 / 시스템 종료</h4>

<ul>
<li>halt - 시스템을 종료 후 전원을 OFF합니다</li>
<li>poweroff - halt와 동일합니다.</li>
<li>reboot - 시스템을 재부팅합니다.</li>
<li>shutdown - 시스템을 종료합니다.</li>
</ul>

<h4 id="그-외의-명령-직접-실행-할-일은-없음">그 외의 명령 (직접 실행 할 일은 없음)</h4>

<ul>
<li>init - Upstart 프로세스 관리 데몬</li>
<li>runlevel - 기존 runlevel 호환성을 위한 명령</li>
<li>telinit - 기존 runlevel 호환성을 위한 명령</li>
<li>upstart-udev-bridge - Upstart와 Udev간의 연계를 위한 명령</li>
</ul>

<h2 id="그-외">그 외</h2>

<ul>
<li>initctl list 명령으로 현재 upstart로 관리되는 프로세스의 상태를 모두 확인 할 수 있습니다.</li>
<li>환경변수로 지정 된 값에 대해서 수동으로 시작/종료 할 경우에는 명령 뒤에 옵션으로 지정할 수 있습니다
<pre>
예시) initctl serial stop 을 실행했을 경우
$ initctl serial stop
stop: Unknown parameter: DEV</li>
</ul>

<p>수동으로 실행 할 경우에는 DEV 변수 값이 필요하기 때문에 아래와 같이 실행합니다.
$ initctl serial stop DEV=ttyS0
</pre></p>

<h2 id="references">References</h2>

<ul>
<li>upstart homepage : <a href="http://upstart.ubuntu.com/" target="_blank">http://upstart.ubuntu.com/</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>League of Legends 맥(Mac OS X) 버전으로 한국 서버 접속</title>
            <link>/2014/02/05/league-of-legends-korean-on-mac-os-x/</link>
            <pubDate>Wed, 05 Feb 2014 17:41:22 +0000</pubDate>
            
            <guid>/2014/02/05/league-of-legends-korean-on-mac-os-x/</guid>
            <description>안내 변경사항  4.21 패치 이후 맥 클라이언트 에디터를 이용하여 한국서버 접속은 가능하지만 한국어 표기가 안되는 경우는 아래 파일을 편집기로 열어서 맨 윗 줄의 en_US를 ko_KR로 수정하시면 됩니다.
파일위치 /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_patcher/managedfiles/0.0.0.0/regions.txt 첫 번째 줄의 en_US를 ko_KR로 수정하고 저장 na, na, ko_KR,    맥 클라이언트 에디터 프로그램을 사용하시기를 권장드립니다.   0.0.0.39 버전부터 lol.properties 설정 값이 아래와 같이 변경 되었음 출처 : ThisIsGame   host=prod.kr.lol.riotgames.com xmpp_server_url=chat.kr.lol.riotgames.com lq_uri=https://lq.</description>
            <content type="html"><![CDATA[

<h1 id="안내">안내</h1>

<h4 id="변경사항">변경사항</h4>

<ul>
<li><p>4.21 패치 이후 <a href="http://macnews.tistory.com/2057" target="_blank">맥 클라이언트 에디터</a>를 이용하여 한국서버 접속은 가능하지만 한국어 표기가 안되는 경우는 아래 파일을 편집기로 열어서 맨 윗 줄의 en_US를 ko_KR로 수정하시면 됩니다.</p>

<pre><code>파일위치
/Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_patcher/managedfiles/0.0.0.0/regions.txt

첫 번째 줄의 en_US를 ko_KR로 수정하고 저장
na,            na,            ko_KR,
</code></pre></li>
</ul>

<hr />

<ul>
<li><a href="http://macnews.tistory.com/2057" target="_blank">맥 클라이언트 에디터</a> 프로그램을 사용하시기를 권장드립니다.</li>
</ul>

<hr />

<ul>
<li>0.0.0.39 버전부터 lol.properties 설정 값이 아래와 같이 변경 되었음</li>
<li>출처 : <a href="http://www.thisisgame.com/board/view.php?category=13707&amp;amp;id=1468761" target="_blank">ThisIsGame</a></li>
</ul>

<pre>
host=prod.kr.lol.riotgames.com
xmpp_server_url=chat.kr.lol.riotgames.com
lq_uri=https://lq.kr.lol.riotgames.com
rssStatusURLs=null
regionTag=kr

lobbyLandingURL=http://leagueoflegends.co.kr/Launcher/launcher_main.php
featuredGamesURL=http://spectator.kr.lol.riotgames.com:8088/observer-mode/rest/featured
storyPageURL=http://leagueoflegends.co.kr/Launcher/launcher_journal.php
ladderURL=http://www.leagueoflegends.co.kr

platformId=KR1
</pre>

<h1 id="lol">LOL</h1>

<p>요즘 가장 Hot한 게임으로 유명한 League of Legends. 하지만, 인기가 있는 만큼 그리고 승부욕이 매우 강한 한국 사람들의 특성에 따라서 승리에 적합한 플레이를 하지 못할 경우 서로의 부모님 안부를 물어보고 주변 사람들의 안부도 물어보는 훈훈한 장면이 연출 된다고 한다.</p>

<p>나는 주로 심심풀이로 1~2시간 정도 하는데 AI하고만 놀고 있다. 스트레스 해소를 위해 게임을 하면서 다른 이유로 인해 스트레스 받는 것은 내 플레이 스타일하고 맞지 않는다. 무엇보다 내가 게임을 단지 즐기는 편이지 승부욕은 없기 때문일지도 모르겠다. 여튼, 지인들과 같이 모여서 오프라인에서 만나서 노는게 아닌 이상 AI하고만 대결을 펼치는 편이다.</p>

<p>그래서, 가상 머신에 LOL을 설치해서 가끔 플레이 하는 정도인데 최근에 후배를 만나  LOL 맥 버전 정식 클라이언트가 나왔다는 소식을 접했다. 그래서 찾아보니 아직은 북미 한정으로 베타버전으로 배포되고 있었다. 물론, 나야 AI하고만 놀기 때문에 북미 서버밖에 접속 안되는 것이 큰 문제는 안되지만 상대적으로 빠른 응답속도를 위해서 한국 서버로 접속 하고 싶은 생각이 들었다. 느낌상 과거 디아블로3 맥 클라이언트를 수정한 것처럼 수정하면 가능할 것 같았다.</p>

<h1 id="lol-북미버전-다운로드">LOL 북미버전 다운로드</h1>

<p>아래 주소는 LOL Mac OS X 북미버전을 다운로드 링크이다.
    <a href="http://signup.leagueoflegends.com/en/signup/redownload" target="_blank">http://signup.leagueoflegends.com/en/signup/redownload</a></p>

<p>dmg 파일을 다운로드하고 드래그&amp;드롭으로 설치 후 실행하면 게임에 필요한 파일을 다운로드 하기 시작한다.</p>

<h1 id="로케일-설정">로케일 설정</h1>

<p>런처에서 다운로드 및 설치가 완료 되면 아래 경로의 파일을 찾아서 수정해 준다. 터미널에서 찾거나 아래처럼 파인더에서 패키지 내용보기로 들어가면 된다.</p>

<p><img src="http://i.imgur.com/9kGITZQ.png" alt="imgur" />
<img src="http://i.imgur.com/0rLvc44.png" alt="Imgur" /></p>

<pre><code>파인더 : 패키지 내용보기 &amp;gt; Contents &amp;gt; LOL &amp;gt; RADS &amp;gt; system &amp;gt; locale.cfg
터미널 : /Applications/League of Legends.app/Contents/LOL/RADS/system/locale.cfg

locale = en_US

위 내용을 아래와 같이 바꾼다

locale = ko_KR
</code></pre>

<h1 id="한글-언어팩-다운로드-확인">한글 언어팩 다운로드 확인</h1>

<p>로케일 값을 수정 한 후에 런처를 실행하면 한글과 관련 된 데이터를 추가로 다운로드하게 된다</p>

<p><img src="http://i.imgur.com/HJRHmBx.png" alt="Imgur" /></p>

<p>다운로드가 모두 완료 되면 런처를 종료하고 서버 접속에 필요한 정보를 수정 해 준다</p>

<h1 id="서버-접속-정보-수정">서버 접속 정보 수정</h1>

<p>아래 경로의 파일을 찾아서 먼저 백업을 해 둔다. (북미와 한국의 패치버전이 다를 경우에는 접속이 안되는 문제가 발생 할 수 있기 때문에 백업해 둔다) 그리고 편집기로 아래와 같이 수정해 준다.</p>

<h2 id="기본-접속-정보-파일-위치">기본 접속 정보 파일 위치</h2>

<ul>
<li>/Applications/League of Legends.app/Contents/LOL/RADS/projects/lol_air_client_config_na/releases/0.0.0.38/deploy/lol.properties</li>
<li>0.0.0.38은 버전 번호이다. 이 파일을 편집기로 열면 아래와 같다</li>
</ul>

<p><img src="http://i.imgur.com/19AZeLu.png?1" alt="Imgur" /></p>

<h2 id="한국서버-접속-정보-파일로-수정">한국서버 접속 정보 파일로 수정</h2>

<p>위에서 찾은 파일을 아래와 같이 수정해 준다</p>

<pre><code>host=prod.kr.lol.riotgames.com
xmpp_server_url=chat.kr.lol.riotgames.com
ladderURL=http://www.leagueoflegends.co.kr
storyPageURL=http://www.leagueoflegends.co.kr/Launcher/launcher_journal.php
lq_uri=https://lq.kr.lol.riotgames.com/login-queue/rest/queue
ekg_uri=https://ekg.riotgames.com
regionTag=kr
rssStatusURLs=null
lobbyLandingURL=http://leagueoflegends.co.kr/Launcher/launcher_main.php
loadModuleChampionDetail=true
featuredGamesURL=http://spectator.kr.lol.riotgames.com:8088/observer-mode/rest/featured
</code></pre>

<h1 id="저장-후-플레이">저장 후 플레이</h1>

<p>설정 파일을 모두 저장하고 런처를 실행 시키면 끝. 그런데, 사용자 지원에 충실하기로 소문난 라이엇게임즈이기 때문에 조만간 한국어 클라이언트가 올라올 듯 하다.</p>

<p>이 내용은 어디까지나 임시방편.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] Cylinder 값이 초과 되었는데? (CHS vs LBA)</title>
            <link>/2014/02/05/faq-cylinder-gabsi-cogwa-doeeossneunde-chs-vs-lba/</link>
            <pubDate>Wed, 05 Feb 2014 17:41:12 +0000</pubDate>
            
            <guid>/2014/02/05/faq-cylinder-gabsi-cogwa-doeeossneunde-chs-vs-lba/</guid>
            <description>이미 인터넷에 CHS와 LBA에 대한 내용을 다룬 훌륭한 문서가 많이 있다. 다만, 최근 Cylinder 값의 초과에 대해서 이상하게 생각하는 경우에 대한 답변을 위해 본 문서를 작성하게 되었다.
Advanced Format과 alignment 최근 Advanced Format(AF) 디스크가 등장하면서 디스크를 파티셔닝하는데 정렬(alignment)에 대한 이슈가 생겨났다. 기존 섹터가 512byte 단위였던 것이 4096byte(4K)로 증가하면서 디스크 접근에 있어서 성능저하를 막기 위해 파티션의 시작지점을 4K 단위로 맞춰 줄 필요가 생긴 것이다. 최신 parted에서는 aligment 옵션을 제공하지만 fdisk와 같은 툴 에서는 제공하지 않아 Bash 쉘 스크립트로 섹터 값으로 계산해서 파티셔닝 하도록 툴을 만들어 해결 했고 이를 업무에 활용하였다.</description>
            <content type="html"><![CDATA[

<p>이미 인터넷에 CHS와 LBA에 대한 내용을 다룬 훌륭한 문서가 많이 있다. 다만, 최근 Cylinder 값의 초과에 대해서 이상하게 생각하는 경우에 대한 답변을 위해 본 문서를 작성하게 되었다.</p>

<h1 id="advanced-format과-alignment">Advanced Format과 alignment</h1>

<p>최근 Advanced Format(AF) 디스크가 등장하면서 디스크를 파티셔닝하는데 정렬(alignment)에 대한 이슈가 생겨났다. 기존 섹터가 512byte 단위였던 것이 4096byte(4K)로 증가하면서 디스크 접근에 있어서 성능저하를 막기 위해 파티션의 시작지점을 4K 단위로 맞춰 줄 필요가 생긴 것이다. 최신 parted에서는 aligment 옵션을 제공하지만 fdisk와 같은 툴 에서는 제공하지 않아 Bash 쉘 스크립트로 섹터 값으로 계산해서 파티셔닝 하도록 툴을 만들어 해결 했고 이를 업무에 활용하였다. 그런데, 파티션 결과에 대한 문의 사항이 많아 이 문서를 작성하게 되었다.</p>

<p>본 글에서는 AF에 대해서는 다루지 않을 것이기 때문에 관련 된 내용은 구글(Google)신에게..</p>

<h1 id="cylinder의-초과">Cylinder의 초과</h1>

<p>먼저 500GB SATA 디스크의 파티션 정보(fdisk -l /dev/sda)를 보면</p>

<p><img src="http://i.imgur.com/eE5zJ.png" alt="Imgur" /></p>

<p>분명히 디스크 정보에는 실린더가 <strong>60788</strong>개가 존재 한다고 되어있는데 파티션 된 정보를 보면 <strong>60789</strong>까지 파티셔닝이 되어있다. 디스크에 허용된 범위를 초과해 버린 잘못된 파티셔닝이 아닌가 하는 의구심이 들 수 있다.</p>

<p>또한, 3개 파티션의 시작과 끝 부분이 다음 파티션 실린더 값과 동일하다. 파티션의 시작과 끝이 서로 맞물려 버렸으니 문제가 된다라고 생각할 수 있다.</p>

<p>그러면 해당 디스크 정보를 섹터(sector) 단위로 확인해 보자. (fdisk -lu /dev/sda)</p>

<p><img src="http://i.imgur.com/WYCBZ.png" alt="Imgur" /></p>

<p>위 그림에서 보이는 것 처럼 전체 976562176개의 섹터 중에서 976562175까지만을 사용 한 것을 알 수 있다. 그리고, 각 파티션은 맞물리는 섹터가 없이 깔끔하게 나뉘어 있는 것을 볼 수 있다.</p>

<ul>
<li>참고로 첫 번째 파티션이 2048부터 시작하는 것은 AF 디스크 정렬을 위해서 이다</li>
</ul>

<p>전체 섹터를 다 사용하지도 않았는데 왜 실린더는 초과한 것으로 보일까? 먼저 이렇게 된 배경을 살펴보도록 하겠다.</p>

<h1 id="chs-cylinder-head-sector">CHS (Cylinder-Head-Sector)</h1>

<p>CHS 주소 지정 방식은 물리적인 디스크의 위치를 나타내기 위한 주소 방식이다.</p>

<p><img src="http://i.imgur.com/ylwj4wY.png" alt="Imgur" /></p>

<p>위 그림의 각 항목은</p>

<ul>
<li>Platter : 원형 판. 과거 플로피 디스크의 내부 저장 마그네틱 판 또는 CD-ROM 처럼 원형이다.</li>
<li>Track : 나무의 나이테처럼 원형으로 데이터를 기록하는 줄이 트랙이다.</li>
<li>Sector : 트랙을 일정한 구간으로 나누어 놓은 것이 섹터이며 512byte (AF는 4KB)이다. 1부터 시작.</li>
<li>Head : 디스크를 읽어들이는 부분.</li>
<li>Cylinder : 트랙을 수직으로 잘랐을 때 같은 위치에 있는 트랙의 집합으로 물리적인 값이라기보다는 논리적인 값 이다.</li>
</ul>

<p>위 그림은 총 3개의 플래터와 6개의 헤드를 가지고 있다. 즉, 플래터가 양면에 데이터를 기록 할 수 있다. 운영체제에 의해서 특정 파일을 접근하려고 하면 <a href="http://en.wikipedia.org/wiki/INT_13H" title="BIOS INT_13h" target="_blank">BIOS INT 13h</a> 인터럽트를 통해서 디스크 컨트롤러에 명령을 내리게 되는데 만약 CHS(10/3/6)라는 주소에 접근하도록 명령을 내렸다면 <strong>4번 째 헤드를 11번 째 실린더의 6번 째 섹터</strong>에 위치시키고 데이터를 읽게 된다.</p>

<p>이러한 디스크 접근 방법이 CHS 방식이며 초기에 제안된 ATA 표준에 의해서 28bit 블럭 주소방식을 사용하였다. 28bit는 실린더 16bit, 헤드 4bit, 섹터 8bit로 할당 되었다. 나중에 <a href="http://en.wikipedia.org/wiki/Parallel_ATA#IDE_and_ATA-1" title="ATA-1" target="_blank">ATA-1</a>이 정식으로 소개되면서 <a href="http://en.wikipedia.org/wiki/INT_13H" title="BIOS INT_13h" target="_blank">BIOS INT 13h</a>가 지정할 수 있는 24bit에 맞추어 아래와 같이 바뀌었다.</p>

<ul>
<li>Sylinder : 10bit (2^10 = 1024)</li>
<li>Head : 8bit (2^8 = 256)</li>
<li>Sector : 6bit (2^6 = 64)</li>
</ul>

<p>결과적으로 아래 표와 같이 가용공간을 계산 할 수 있게 된다. (※ 참고로 섹터는 1부터 시작한다)</p>

<p><img src="http://i.imgur.com/rYRLw.png" alt="Imgur" /></p>

<p>표에서 나타난 것 처럼 CHS 방식으로는 504MB까지 밖에 사용할 수 없기 때문에 이를 개선하고자 <a href="http://www.pcguide.com/ref/hdd/bios/modesECHS-c.html" title="ECHS" target="_blank">ECHS</a>(Extended CHS)라는 것이 등장하였다. Large Mode라고도 불리우는 ECHS는 BIOS가 전달하는 값에 특정 값을 곱하거나 나누어서 확장시키는 방식인데 그 결과 아래 표와 같은 가용 공간을 사용 할 수 있게 되었다.</p>

<p><img src="http://i.imgur.com/lSBdE.png" alt="Imgur" /></p>

<p>즉, <a href="http://en.wikipedia.org/wiki/INT_13H" title="BIOS INT_13h" target="_blank">BIOS INT 13h</a>는 실린더를 1024로 제한하고 있지만 실제 디스크는 그 이상의 실린더를 사용하고 전달하는 과정에서 8로 나누어 1024 제한을 충족시키는 방식이다. 하지만, 이 방식도 위 표에서 나타나는 것 처럼 7.88GB 이상을 사용할 수 없기 때문에 그리 오래가지 못하였다.</p>

<p>이 모든걸 해결하고자 등장 한 것이 <a href="http://en.wikipedia.org/wiki/Logical_block_addressing" title="LBA" target="_blank">LBA</a>(Logical Block Addressing) 모드이다.</p>

<h1 id="lba">LBA</h1>

<p>앞서 이야기 한 CHS를 해결하고자 등장 한 것이 <a href="http://en.wikipedia.org/wiki/Logical_block_addressing" title="LBA" target="_blank">LBA</a>라는 것은 정확한 말은 아니다. 실제로 <a href="http://en.wikipedia.org/wiki/Logical_block_addressing" title="LBA" target="_blank">LBA</a> 주소 지정방식은 IDE 표준에서 22bit를 옵션으로 포함하고 있었으며 <a href="http://en.wikipedia.org/wiki/Parallel_ATA#IDE_and_ATA-1" title="ATA-1" target="_blank">ATA-1</a>이 공표될 때 28bit로 확장되었다. (※ ATA-6에서는 48bit)</p>

<p>단지, CHS가 물리적 접근에 있어서는 보다 명료했으며 먼저 사용되기 시작하였고 한계점을 드러내면서 LBA가 주목 받은 것이다. LBA 방식은 CHS 처럼 물리적인 연산이 아닌 사용가능한 모든 섹터를 배열로 나타낸 것이다. 즉, LBA 주소 0은 CHS(0,0,1)이다.</p>

<p>실질적으로 ATA-6가 등장하면서 48bit LBA 주소가 제안되었고  LBA 주소로 접근하면 디스크 컨트롤러가 알아서 물리적인 주소로 변환해서 접근하기 때문에 신경 쓸 필요가 없지만 CHS로 디스크 정보를 보여주는 툴의 이해를 위해서 (혹은, 임베디드 같은 분야에서 필요로해서)  CHS와 LBA와의 상관관계를 살펴보도록 하겠다.</p>

<ul>
<li>참고로 <a href="http://en.wikipedia.org/wiki/INT_13H" title="BIOS INT_13h" target="_blank">BIOS INT 13h</a>의 위키 문서에도 나와있지만 웨스턴디지털과 피닉스 테크놀러지에서 INT 13h Extensions을 소개하였다. 이는 현 시스템들이 사용하는 방식이며 64bit LBA 주소(8ZiB까지 사용가능)까지 지원한다.</li>
</ul>

<p>먼저, CHS를 LBA로 변환하는 수식이다. <a href="http://www.datarecoverytools.co.uk/2009/12/22/chs-lba-addressing-and-their-conversion-algorithms/" target="_blank">참고문서</a></p>

<pre><code>LBA = ((실린더 x 실린더 당 헤드 + 헤드) x 트랙 당 섹터) + 섹터 - 1
</code></pre>

<p>중요한 것은 LBA 주소로 부터 CHS주소인 실린더, 헤드, 섹터 값을 얻어내는 것인데 이는 아래와 같다.</p>

<pre><code>실린더 = LBA / (실린더 당 헤드 * 트랙당 섹터)
헤드 = (LBA % (실린더 당 헤드 * 트랙당 섹터)) / 트랙당 섹터
섹터 = (LBA % (실린더 당 헤드 * 트랙당 섹터)) % 트랙당 섹터 + 1
</code></pre>

<h1 id="fdisk의-결과는">fdisk의 결과는?</h1>

<p><img src="http://i.imgur.com/WYCBZ.png" alt="Imgur" /></p>

<p>이제 다시 처음 살펴봤던 500GB 디스크의 fdisk 결과를 살펴보면 전체 섹터는 976562176개 였다. LBA는 논리적인 섹터 배열 주소 값이기 때문에 마지막 파티션의 끝 섹터 976562175는 LBA로 976562174이며 실린더 값을 구해보면 다음과 같다.</p>

<pre><code>실린더 = 976562174 / (255 * 63) = 60788.183877995645
</code></pre>

<p>실린더는 논리적인 값이기 때문에 정확히 나누어 떨어지지 않는다. 여튼, 소숫점을 떼어내면 해당 파티션이 끝나는 위치의 실린더는 fdisk가 보여주는 전체 실린더 개수 60788과 일치한다.</p>

<p>정리하면 /dev/sda3 파티션의 마지막 섹터가 속한 실린더 위치는 60788.183877995645라는 위치가 된다. 그렇기 때문에 fdisk는 이것을 60789 (60788을 넘어선 위치)로 판단한 것 같다. 상세한 것은 fdisk 소스를 열어보면 되겠지만 시간도 걸리고 크게 중요한 것은 아니라서 생략했다. 대신 구시대 유물인 fdisk 보다 최신의 파티션 툴인 parted의 결과를 보여주면 아래와 같다.</p>

<p><img src="http://i.imgur.com/lsY4w.png" alt="Imgur" /></p>

<p>parted는 해당 디스크가 60788 실린더를 가지고 있으며 마지막 파티션이 60788에서 끝난다고 표기해 주고 있다. 첫 번째 파티션의 시작 지점의 표현이 fdisk와는 달리 0부터 표기되는 것을 볼 수 있다.</p>

<h1 id="결론은">결론은</h1>

<p>실린더는 물리적인 것이 아니라 트랙에 존재하는 섹터의 논리적인 집합이기 때문에 그 표현이 툴에 따라서 달라질 수 있다는 것을 볼 수 있다. 따라서, 섹터 단위로 정확히 파티셔닝을 했다면 실린더 값이 겹치거나 초과되어 보일지라도 전혀 문제가 없다고 볼 수 있다.</p>

<p>즉, 디스크 파티셔닝의 핵심은 섹터이다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] Linux Swappiness 관련</title>
            <link>/2014/02/05/faq-linux-swappiness-gwanryeon/</link>
            <pubDate>Wed, 05 Feb 2014 17:40:50 +0000</pubDate>
            
            <guid>/2014/02/05/faq-linux-swappiness-gwanryeon/</guid>
            <description>Swap Linux 시스템에서는 다양한 용도로 스왑을 사용한다. 일반적으로 부족한 메모리를 보충하기 위한 용도로만 알려져 있지만 아래와 같은 용도로 활용이 되고 있다.
 메모리를 많이 사용하는 프로그램을 위해 (가장 일반적인 용도) Hibernation (메모리의 내용을 디스크에 저장해 두기 위한 용도)  일반 노트북이나 랩탑에서 Hibernation을 하기 위해서는 시스템의 메모리 크기보다 큰 스왑 공간이 반드시 필요하다. 메모리의 정보를 모두 디스크에 담아야 하기 때문이다.
 예측에서 벗어난 메모리 공간을 사용하는 프로그램에 대비하기 위한 경우 메모리의 효율을 높이기 위해서  스왑 영역은 일반적으로 디스크에 존재하기 때문에 메모리에 비교할 수 없을 정도로 성능이 떨어지지만 이러한 스왑 영역이 메모리 사용 효율을 높일 수 있다.</description>
            <content type="html"><![CDATA[

<h1 id="swap">Swap</h1>

<p>Linux 시스템에서는 다양한 용도로 스왑을 사용한다. 일반적으로 부족한 메모리를 보충하기 위한 용도로만 알려져 있지만 아래와 같은 용도로 활용이 되고 있다.</p>

<ul>
<li>메모리를 많이 사용하는 프로그램을 위해 (가장 일반적인 용도)</li>
<li>Hibernation (메모리의 내용을 디스크에 저장해 두기 위한 용도)</li>
</ul>

<p>일반 노트북이나 랩탑에서 Hibernation을 하기 위해서는 시스템의 메모리 크기보다 큰 스왑 공간이 반드시 필요하다. 메모리의 정보를 모두 디스크에 담아야 하기 때문이다.</p>

<ul>
<li>예측에서 벗어난 메모리 공간을 사용하는 프로그램에 대비하기 위한 경우</li>
<li>메모리의 효율을 높이기 위해서</li>
</ul>

<p>스왑 영역은 일반적으로 디스크에 존재하기 때문에 메모리에 비교할 수 없을 정도로 성능이 떨어지지만 이러한 스왑 영역이 메모리 사용 효율을 높일 수 있다. 프로세스가 데이터를 읽어들일 때 메모리에 저장하여 읽게 되고 이러한 작업이 빈번하게 발생할 경우 메모리에 캐시하여 응답속도를 높이는 형태로 동작한다.</p>

<p>즉, 메모리에 캐시할 수 있는 공간이 많으면 많을 수록 효율을 높일 수 있는데 스왑 영역은 프로세스가 예약한 메모리 공간 중에서 사용되지 않는 혹은 당장 필요하지 않는 부분을 저장하여 <strong>메모리가 캐시 역할을 할 수 있는 공간을 더 확보</strong> 할 수 있도록 한다.</p>

<h1 id="메모리가-많은데도-swap을-사용합니다">메모리가 많은데도 Swap을 사용합니다</h1>

<p>자주 문의 받는 내용 중 하나이다. 분명 시스템의 RAM은 여유가 있음에도 스왑으로 할당해 둔 공간에 데이터를 쓰는 경우가 종종 있다. 사실 크게 문제되지는 않지만 스왑 영역과 데이터를 자주 주고 받게 된다면 디스크 I/O의 성능에 전체 시스템의 성능이 영향을 받는 경우가 있다.</p>

<p>Linux가 메모리가 많음에도 불구하고 스왑공간을 사용하는 이유는 스왑 사용여부를 결정하는 값의 계산식에서 비롯되는데 Linux Kernel의 mm/vmscan.c에는 아래와 같은 코드가 있다.</p>

<pre><code>swap_tendency = mapped_ratio / 2 + distress + sc-&gt;swappiness;
</code></pre>

<p>스왑을 사용하려는 경향(tendency)을 계산하는데 있어서 mapped_ratio와 distress 그리고 swappiness라는 변수가 영햐을 미친다.</p>

<p>먼저, mapped_ratio는 아래와 같이 계산되는데</p>

<pre><code> mapped_ratio = ((global_page_state(NR_FILE_MAPPED) +
                  global_page_state(NR_ANON_PAGES)) * 100) / vm_total_pages;
</code></pre>

<p>쉽게 말해서 전체 메모리 중에서 프로세스가 사용하고 있는(Mapped) 메모리의 크기에 대한 비율(% 값)이다.</p>

<p>두 번째로 distress 변수는 아래와 같이 표현되며</p>

<pre><code>distress = 100 &gt;&gt; min(zone-&gt;prev_priority, priority);
</code></pre>

<p>페이지(메모리 저장구조 단위)를 얼마나 많이 스캔하게 될 건지를 뜻한다. 일반적으로 값은 0이며 100에 가까울 수록 스캔해야하는 양이 많아져서 시스템에 문제가 있다는 뜻이다. (커널 코드 주석에서는 Great Trouble이라고 표현되어 있다)</p>

<p>그리고 swappiness 값은 사용자가 직접 수정할 수 있는 변수로 /proc/sys/vm/swappiness에서 확인 할 수 있으며 기본 값은 60이다.</p>

<p>첫 번째 확인했던 수식을 다시금 정리하면 일반적인 경우에 distress는 0이고 swappiness는 60이기 때문에 아래와 같이 표현 된다.</p>

<pre><code>swap_tendency = mapped_ratio / 2 + 0 + 60;
</code></pre>

<p>만약, 4GB 메모리를 가진 시스템에서 3GB를 사용 중이라고 가정하면</p>

<pre><code>swap_tendency = (3GB / 4GB * 100) / 2 + 0 + 60 = 97.5
</code></pre>

<p>97.5이기 때문에 바로 스왑이 일어나지는 않는다. (100이상이면 발생한다)</p>

<h1 id="swappiness-값에-따른-영향">swappiness 값에 따른 영향</h1>

<p>실질적으로 사용자가 변경할 수 있는 값은 swappiness 변수 이기 때문에 이 값에 따른 스왑이 발생하는 시점을 유추해 볼 필요가 있을 것이다.</p>

<p>먼저 기본 값인 60일 경우에는 언제 스왑이 발생할지 계산해 보기위해서는 swap_tendency가 100이 되는 시점을 찾으면 되는데</p>

<pre><code>100 = mapped_ratio / 2 + distress + swappiness
 mapped_ratio = (100 - distress - swappiness) * 2
 mapped_ratio = (100 - 0 - 60) * 2 = 80
</code></pre>

<p>즉, 80% 이상의 메모리를 사용하게 되면 스왑이 발생하게 될 것을 예측 할 수 있다. <strong>다만 distress가 0인 상황에서라는 전제조건이 필요하다.</strong></p>

<p>만약, swappiness 값을 10으로 설정하고 distress가 0이면 mapped_ratio는 200%이기 때문에 스왑이 발생하는 경우의 조건이 성립하지 않지만 distress가 50이면 80%로 계산된다.</p>

<h1 id="그래서">그래서</h1>

<p>앞의 예제와 같이 distress 값은 <strong>유동적</strong>이기 때문에 결과적으로 <strong>swappiness 값의 설정이 절대적인 결과를 얻는 값으로 사용될 수 없지만</strong> 0에 가까울 수록 가급적 스왑을 하지 않으려 할 것이고 100에 가까울 수록 스왑을 하도록 설정할 수 있다는 부분은 어렵지 않게 알 수 있다.</p>

<h1 id="추가적으로">추가적으로</h1>

<p>자주 질문 받던 내용을 정리하기 위해 다시금 리눅스 소스코드를 열어보았다가 발견한 사실로 위에서 언급한 계산 식이 vmscan.c 코드에서 사라졌다.</p>

<p>몇 개의 버전을 더 받아서 확인해 본 결과 CentOS 5.7/RHEL 5.7의 2.6.18-274 커널에는 존재하지만 kernel.org에서 내려받은 2.6.35.13과 3.4 커널 코드에는 존재하지 않았다.</p>

<p>즉, 메모리 관리 기법에 대한 변화로 인해서 해당 코드는 삭제 된 것으로 추측된다. (2.6.20 커널의 패치 파일에서는 수식 변경이 발견되었고 그 뒤 버전에서는 아예 사라졌다) 변경된 내용에 대한 내용까지 조사해서 정리하면 좋겠지만 이 문서를 작성하면서 고려하지 않았던 내용이기 때문에 이 부분은 나중으로 미루려고 한다.</p>

<p>다만, 최신 커널 문서에서도 여전히 <strong>swappiness 값은 0과 100사이에서 동일한 의미로 설명</strong>되고 있기 때문에 설정 값에 대한 내부 처리 부분의 변경은 다시 확인해야겠지만 당장 설정하는데 별다른 문제는 없다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] Linux LD_ASSUME_KERNEL 변수에 대한 이야기</title>
            <link>/2014/02/05/faq-linux-ld_assume_kernel-byeonsue-daehan-iyagi/</link>
            <pubDate>Wed, 05 Feb 2014 17:40:40 +0000</pubDate>
            
            <guid>/2014/02/05/faq-linux-ld_assume_kernel-byeonsue-daehan-iyagi/</guid>
            <description>간혹, 특정 어플리케이션을 설치 후 실행 할 때 라이브러리 경로를 못 찾는 경우가 있다. (특히, 오라클, JAVA 등) 이러한 경우 LD_LIBRARY_PATH가 잘못 설정되어 문제가 되기도 하지만 LD_ASSUME_KERNEL 변수 때문에 발생하기도 한다.
Linux 커널의 Threads 구조 변천사 LD_ASSUME_KERNEL 변수는 Linux 커널의 Threads 라이브러리 구조의 변천사와 관련이 있다. Linux 커널 2.6 버전 이전에는 POSIX Threads에 적합하지 않은 스레드 구조를 가지고 있었으며 Linux Threads를 개선하기 위한 2가지의 프로젝트가 가동 되었다. 그 중 하나는 IBM쪽 개발자들에 의한 NGPT (Next Generation POSIX Threads)이었고 다른 하나는 Redhat 개발자들에 의한 NTPL(Native POSIX Thread Library) 였다.</description>
            <content type="html"><![CDATA[

<p>간혹, 특정 어플리케이션을 설치 후 실행 할 때 라이브러리 경로를 못 찾는 경우가 있다. (특히, 오라클, JAVA 등) 이러한 경우 LD_LIBRARY_PATH가 잘못 설정되어 문제가 되기도 하지만  LD_ASSUME_KERNEL 변수 때문에 발생하기도 한다.</p>

<h1 id="linux-커널의-threads-구조-변천사">Linux 커널의 Threads 구조 변천사</h1>

<p>LD_ASSUME_KERNEL 변수는 Linux 커널의 Threads 라이브러리 구조의 변천사와 관련이 있다. Linux 커널 2.6 버전 이전에는 POSIX Threads에 적합하지 않은 스레드 구조를 가지고 있었으며 Linux Threads를 개선하기 위한 2가지의 프로젝트가 가동 되었다. 그 중 하나는 IBM쪽 개발자들에 의한 NGPT (Next Generation POSIX Threads)이었고 다른 하나는 Redhat 개발자들에 의한 <a href="http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library" target="_blank">NTPL</a>(Native POSIX Thread Library) 였다.결과적으로 NGPT는 사라지고 <a href="http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library" target="_blank">NTPL</a>이 Redhat 9이 릴리즈 되면서 소개가 되었다. 하지만, 문제는 여기에서 발생한다.</p>

<p>과거 자체적인 구조의 Threads 라이브러리를 가지고 있던 Linux를 개선하기위해 <a href="http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library" target="_blank">NTPL</a>이 소개되었지만 기존 소프트웨어와 <a href="http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library" target="_blank">NTPL</a>이 호환되지 않는 문제점이 발생하였다. 따라서, Redhat 9이 릴리즈 될 무렵에 3가지 구조의 Threads 라이브러리를 제공하게 되었는데</p>

<ul>
<li>/lib/tls/libpthread.so (Kernel 2.4.20 NPTL)</li>
<li>/lib/i686/libpthread.so (Kernel 2.4.1의 비교적 최신 LinuxThreads - 32bit 기준)</li>
<li>/lib/libpthread.so (Kernel 2.2.5의 오래된 LinuxThreads)</li>
</ul>

<p>이렇게 3가지의 라이브러리를 제공한 것은 기존의 어플리케이션과 호환성을 위한 것이다. 대부분의 어플리케이션은 <a href="http://en.wikipedia.org/wiki/Dynamic_Shared_Object" target="_blank">DSO</a>(Dynamic Shared Object) 형태로 링커를 통해서 라이브러리를 호출하는 구조이기 때문에 여러 종류의 라이브러리를 제공하는 구조를 취할 수 밖에 없었다.</p>

<h1 id="ld-assume-kernel-변수의-의미">LD_ASSUME_KERNEL 변수의 의미</h1>

<p>어플리케이션이 실행될 때 Dynamic Linker에 의해서 Shared Library를 링크하고 불러들이게 되는데 앞서 이야기한 대로 3가지 구조의 라이브러리가 존재하기 때문에 각 어플리케이션에 적합한 라이브러리를 링크할 필요성이 발생하였다.</p>

<p>※ 일반적으로 별도의 라이브러리를 링크하기 위해서는 LD_LIBRARY_PATH와 같은 변수를 사용한다. 앞서 이야기한 libpthread의 3가지 타입은 특수한 경우이다.</p>

<p>※ Dynamic Library 파일에는 해당 파일이 구동되기 위한 최소한의 OS ABI 버전 정보를 담고 있는데 (.note.ABI-tag로 불리우는 ELF note 섹션) 특정 라이브러리에 대해서 아래와 같은 명령으로 확인해 볼 수 있다. (아래는 RHEL5에서 실행한 결과)</p>

<pre><code>$ eu-readelf -n /lib/libc-2.5.so
Note section [ 1] '.note.ABI-tag' of 32 bytes at offset 0x174:
  Owner          Data size  Type
  GNU                   16  VERSION
    OS: Linux, ABI: 2.6.9
</code></pre>

<p>3가지 구조의 라이브러리를 찾아가기 위해서 LD_ASSUME_KERNEL 이란 변수가 사용되게 되었고 이 변수를 해당 라이브러리의 ABI 버전으로 지정하여 적합한 라이브러리를 찾아가도록 지정하는 것이다.</p>

<h1 id="ld-assume-kernel의-필요성">LD_ASSUME_KERNEL의 필요성?</h1>

<p>결론적으로 이야기하면 현재의 Linux에서는 보통의 경우 해당 변수가 필요하지 않다. (현재는 GNU C 라이브러리에 NTPL이 완전히 통합되어 있다) 이러한 변수가 필요한 어플리케이션(최신버전의 Oracle에 대해서는 잘 모르기 때문에 확답하긴 어렵지만 적어도 9i 시절의 Oracle의 경우는 해당 변수가 필요하였다) 을 사용해야하는 경우가 아닌이상 필요없는 변수이다.</p>

<p>만약, 필요성이 없음에서 설정되어서 문제를 일으킨다면 간단히 주석처리해버리자. 보통 .bash_profile, .cshrc과 같은 환경설정 파일에 등록되어있을 것이다.</p>

<p>요즘 Linux에서 LD_ASSUME_KERNEL을 사용하면 어떻게 되는지 궁금한 분들은 아래와 같은 명령을 실행해보면 된다.</p>

<pre><code>$ LD_ASSUME_KERNEL=2.4.20 ls
</code></pre>

<h1 id="마무리">마무리..</h1>

<p>어찌보면 추억의 변수명이다. (요즘도 화두가 되는지는 모르겠다) 잊고 있던 변수명인데 오라클을 설치 중에 기본 명령도 안먹는 증상이 발생한다는 문의를 받고 확인하던 중에 발견하고 이 기회에 간단히 정리해 보았다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] VirtualBox에서 RHEL5 부팅 오류/멈춤 현상</title>
            <link>/2014/02/05/faq-virtualboxeseo-rhel5-buting-oryumeomcum-hyeonsang/</link>
            <pubDate>Wed, 05 Feb 2014 17:40:28 +0000</pubDate>
            
            <guid>/2014/02/05/faq-virtualboxeseo-rhel5-buting-oryumeomcum-hyeonsang/</guid>
            <description>VirtualBox에 RHEL5 설치하기 별것 아니지만 VirtualBox에 RHEL5를 설치하다가 겪을 수 있는 증상에 대해서 간단히 공유하려한다. VirtualBox 버전에 따라 그리고 GuestOS인 RHEL의 버전에 따라 발생하는 경우가 다를 수 있지만 개인적으로 RHEL5 64bit 배포판에서 대부분 발생한 증상이다.
증상 보통 GuestOS를 설치하기 위해서 ISO 이미지파일을 마운트하여 부팅시키는데 부팅 초반에 멈추는 증상이다. 이는 ISO 이미지로 부팅할 때 뿐만 아니라 설치 후에 부팅할 때도 발생하곤 한다.
원인 발생하는 경우데 따라서 조금씩 다른 것으로 보이지만 결과적으로 NMI Watchdog이 CPU0에서 LOCKUP 되는 증상이 발생하면서 일어난다.</description>
            <content type="html"><![CDATA[

<h1 id="virtualbox에-rhel5-설치하기">VirtualBox에 RHEL5 설치하기</h1>

<p>별것 아니지만 VirtualBox에 RHEL5를 설치하다가 겪을 수 있는 증상에 대해서 간단히 공유하려한다. VirtualBox 버전에 따라 그리고 GuestOS인 RHEL의 버전에 따라 발생하는 경우가 다를 수 있지만 개인적으로 RHEL5 64bit 배포판에서 대부분 발생한 증상이다.</p>

<h2 id="증상">증상</h2>

<p>보통 GuestOS를 설치하기 위해서 ISO 이미지파일을 마운트하여 부팅시키는데 부팅 초반에 멈추는 증상이다. 이는 ISO 이미지로 부팅할 때 뿐만 아니라 설치 후에 부팅할 때도 발생하곤 한다.</p>

<h2 id="원인">원인</h2>

<p>발생하는 경우데 따라서 조금씩 다른 것으로 보이지만 결과적으로 NMI Watchdog이 CPU0에서 LOCKUP 되는 증상이 발생하면서 일어난다. 따라서 NMI Watchdog만 잘 달래면 해결될 수 있는 문제이기도 하다.</p>

<h2 id="해결방법">해결방법</h2>

<p>ISO로 부팅할 경우에는 원하는 커맨드 뒤에 아래의 옵션을 입력한다.</p>

<pre><code>nmi_watchdog=0

예) &gt; linux nmi_watchdog=0
</code></pre>

<p>설치 후에 발생하는 문제의 경우에는 grub.conf 부트로더 설정파일의 커널설정 라인에 위의 옵션을 추가해 준다.</p>

<pre><code>예) kernel /boot/vmlinuz-2.6.18-274.el5 ro  root=/dev/sda1 nmi_watchdog=0
</code></pre>

<p>만약, 설치 후 처음 부팅 때도 발생한다면 GRUB 부트로더 화면에서 &lsquo;e&rsquo;를 눌러 에디터 모드로 들어가서 커널 설정 라인에 위 내용을 추가 해 준다.</p>

<p>별거 아닌 것이긴 하지만.. 디버깅하면서 메시지를 확인하지 않는다면 이미지 불량으로 착각하고 삽질할 가능성이 높은 증상이다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] Synergy 서버 실행 오류 해결 방안</title>
            <link>/2014/02/05/faq-synergy-seobeo-silhaeng-oryu-haegyeol-bangan/</link>
            <pubDate>Wed, 05 Feb 2014 17:40:12 +0000</pubDate>
            
            <guid>/2014/02/05/faq-synergy-seobeo-silhaeng-oryu-haegyeol-bangan/</guid>
            <description>Synergy 최근에 MBP를 마련하면서 Windows와 번갈아가면서 하는 작업이 늘게 되어서 Synergy를 사용하게 되었는데 설정을 바꿔가며 테스트 하다가 이상한 증상이 발견되어 이를 해결하는 방법을 공유 하고자 한다.
환경 및 증상  환경 : Synergy 서버는 Windows7 64bit 버전 증상 : Synergy 서버를 종료 했다가 다시 실행하면 아래와 같은 오류 메시지가 발생하면서 실행되지 않음  ERROR: failed to initialize hook library, is synergy already running? FATAL: failed to start server: unable to open screen   해결방법 먼저, 알아두어야 할 것은 Synergy 서버프로세스가 이미 떠 있다면 종료 시키면 해결된다.</description>
            <content type="html"><![CDATA[

<h1 id="synergy">Synergy</h1>

<p>최근에 MBP를 마련하면서 Windows와 번갈아가면서 하는 작업이 늘게 되어서 <a href="http://code.google.com/p/synergy-plus/" target="_blank">Synergy</a>를 사용하게 되었는데 설정을 바꿔가며 테스트 하다가 이상한 증상이 발견되어 이를 해결하는 방법을 공유 하고자 한다.</p>

<h2 id="환경-및-증상">환경 및 증상</h2>

<ul>
<li>환경 : <a href="http://code.google.com/p/synergy-plus/" target="_blank">Synergy</a> 서버는 Windows7 64bit 버전</li>
<li>증상 : <a href="http://code.google.com/p/synergy-plus/" target="_blank">Synergy</a> 서버를 종료 했다가 다시 실행하면 아래와 같은 오류 메시지가 발생하면서 실행되지 않음

<ul>
<li>ERROR: failed to initialize hook library, is synergy already running?</li>
<li>FATAL: failed to start server: unable to open screen</li>
</ul></li>
</ul>

<h2 id="해결방법">해결방법</h2>

<p>먼저, 알아두어야 할 것은 Synergy 서버프로세스가 이미 떠 있다면 종료 시키면 해결된다. 하지만, 서버프로세스 중복에 의한 경우는 거의 없고 Hooking 하는 부분에서 무언가 트러블을 일으키는 것으로 보인다.</p>

<ul>
<li>Ctrl+Alt+Delete를 눌르고 사용자전환(W)을 선택한다</li>
<li>로그인 창이 나오면 방금전에 사용하던 계정으로 다시 로그인한다.</li>
<li>스크린과 관련된 부분이 초기화 되면서 Synergy 서버가 정상적으로 실행된다</li>
</ul>

<p>일종의 버그라고 보여지며.. 현재까지는 Windows7 64bit에서만 확인되었다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] ht = HyperThread에 대한 오해</title>
            <link>/2014/02/05/faq-ht-hyperthreade-daehan-ohae/</link>
            <pubDate>Wed, 05 Feb 2014 17:39:57 +0000</pubDate>
            
            <guid>/2014/02/05/faq-ht-hyperthreade-daehan-ohae/</guid>
            <description>과거에 대략 정리했던 내용을 다시 포스팅하는 이유는 문서 정리차원입니다.
일반적으로 Linux의 CPU정보 (/proc/cpuinfo)에서 ht 플래그 가 보이면 HyperThread가 지원되는 CPU로 알려져 있습니다. 따라서 해당 플래그의 존재 여부에 따라서 HyperThread가 되고 안되고를 많이 판단하는데 결과부터 이야기 한다면 적어도 지금의 CPU로는 ht 플래그로는 HyperThread 기능 여부를 정확히 판별할 수 없습니다.
개인적으로 HyperThread 기능이 없는 CPU가 ht 플래그를 가지고 있는 것을 발견하였고 이에 대한 의문점을 해결하기 위해 커널 소스를 모두 보았으나 소스의 변화나 특이점은 없었습니다.</description>
            <content type="html"><![CDATA[<p>과거에 대략 정리했던 내용을 다시 포스팅하는 이유는 문서 정리차원입니다.</p>

<p>일반적으로 Linux의 CPU정보 (/proc/cpuinfo)에서 <strong>ht 플래그 가 보이면 HyperThread가 지원</strong>되는 CPU로 알려져 있습니다. 따라서 해당 플래그의 존재 여부에 따라서 HyperThread가 되고 안되고를 많이 판단하는데 결과부터 이야기 한다면 적어도 지금의 CPU로는 ht 플래그로는 HyperThread 기능 여부를 정확히 판별할 수 없습니다.</p>

<p>개인적으로 <strong>HyperThread 기능이 없는 CPU가 ht 플래그를 가지고 있는 것</strong>을 발견하였고 이에 대한 의문점을 해결하기 위해 커널 소스를 모두 보았으나 소스의 변화나 특이점은 없었습니다. 그래서 CPU 매뉴얼과 유사 자료를 찾던 중 아래와 같은 내용이 있었습니다.</p>

<blockquote>
<p>&ldquo;ht&rdquo; in &lsquo;flags&rsquo; field of /proc/cpuinfo indicate that the processor supports the Machine Specific Registers to report back HT or multi-core capability. Additional fields (listed down below) in the CPU records of /proc/cpufinfo will give more precise information about the CPU topology as seen by the operating system.</p>

<p>&ldquo;physical id&rdquo; : Physical package id of the logical CPU
&ldquo;siblings&rdquo; : Total number of logical processors (include both threads and cores) in the physical package currently in use by the OS
&ldquo;cpu cores&rdquo; : Total number of cores in the physical package currently in use by the OS
&ldquo;core id&rdquo; : Core id of the logical CPU</p>
</blockquote>

<p>위의 내용을 핵심만을 살펴보면, ht 플래그는 <strong>멀티스레드/멀티코어에 대한 하드웨어 레지스터를 지원하느냐</strong>에 대한 플래그 값 입니다.</p>

<p>즉, 과거에는 die 하나에 1개의 코어가 올라가는게 당연하였기 때문에 ht 플래그는 곧 HyperThread를 의미했지만 요즘과 같이 die 하나에 4개의 코어 이상이 올라가는 시대에는 멀티코어에 대한 하드웨어 레지스터를 지원한다는 의미밖에 되지 않습니다.</p>

<p>따라서, HyperThread의 활성화 여부를 명확히 확인하기 위해서는 /proc/cpuinfo 파일에서 보여주는 코어개수가 sibling 코어 개수와 같으면 HyperThread가 꺼진 상태이고 2배이면 켜진 상태인 것입니다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] Thunderbird 성능 개선 팁</title>
            <link>/2014/02/05/faq-thunderbird-seongneung-gaeseon-tib/</link>
            <pubDate>Wed, 05 Feb 2014 17:39:43 +0000</pubDate>
            
            <guid>/2014/02/05/faq-thunderbird-seongneung-gaeseon-tib/</guid>
            <description>Thunderbird를 사용하다보면 컴퓨터의 리소스는 넉넉함에도 불구하고 창 이동이나 사용자 행동에 대한 반응이 조금은 느린듯한 느낌이 들 때가 있다. 무언가 그래픽적인 처리가 버벅거리는 듯한 모습인데 이 경우 아래 옵션을 적용하면 효과가 있다. 개인적으로 업무용 데스크탑은 16GB 메모리를 사용하고 있음에도 반응이 느려서 아래 옵션을 적용하였고 효과를 크게 보았다.
먼저, Thunderbird의 옵션-&amp;gt;고급 페이지로 들어가서 고급설정 항목의 &amp;lsquo;설정편집&amp;rsquo;을 눌러 아래 항목을 검색하여 값을 수정한다.
gfx.direct2d.disabled true layers.acceleration.disabled true  Direct2D 관련된 부분의 값을 수정해 주는 옵션이다.</description>
            <content type="html"><![CDATA[<p>Thunderbird를 사용하다보면 컴퓨터의 리소스는 넉넉함에도 불구하고 창 이동이나 사용자 행동에 대한 반응이 조금은 느린듯한 느낌이 들 때가 있다. 무언가 그래픽적인 처리가 버벅거리는 듯한 모습인데 이 경우 아래 옵션을 적용하면 효과가 있다. 개인적으로 업무용 데스크탑은 16GB 메모리를 사용하고 있음에도 반응이 느려서 아래 옵션을 적용하였고 효과를 크게 보았다.</p>

<p>먼저, Thunderbird의 옵션-&gt;고급 페이지로 들어가서 고급설정 항목의 &lsquo;설정편집&rsquo;을 눌러 아래 항목을 검색하여 값을 수정한다.</p>

<pre><code>gfx.direct2d.disabled true
layers.acceleration.disabled true
</code></pre>

<p>Direct2D 관련된 부분의 값을 수정해 주는 옵션이다. 만약 사용하는 데스크탑이 Linux나 Mac OS X일 경우에는 아래 옵션을 활성화 해주면 도움이 될 수 있다.</p>

<pre><code>layers.prefer-opengl true
</code></pre>

<p>나의 경우는 옵션을 적용한 후에 수기가바이트에 달하는 메일박스에서 새로운 메일을 확인할 때 마다 화면에 보여주는 속도가 많이 개선되었다.</p>

<p>그 외의 팁은 아래 페이지를 참고하면 된다.</p>

<p><a href="http://kb.mozillazine.org/Performance_-_Thunderbird" target="_blank">모질라 위키</a></p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] NIC의 순서가 부팅 때마다 바뀌는 증상</title>
            <link>/2014/02/05/faq-nicyi-sunseoga-buting-ddaemada-baggwineun-jeungsang/</link>
            <pubDate>Wed, 05 Feb 2014 17:39:30 +0000</pubDate>
            
            <guid>/2014/02/05/faq-nicyi-sunseoga-buting-ddaemada-baggwineun-jeungsang/</guid>
            <description>이미 여러 인터넷 커뮤니티에서 알려진대로 RHEL 기준으로 RHEL5 부터 서버를 리부팅 할 때 마다 네트워크 카드의 순서가 뒤 바뀌는 증상이 발생하곤 한다.
예를 들어 PCI 장치번호로 00:08.0이 eth0 였고 00:09.0가 eth1이었는데 리부팅을 하고 보니 eth0가 eth1으로 잡히고 eth1이 eth2로 잡히는 증상 또는 서로 바뀌는 증상들이 발생하는 것이다.
이것에 대한 해결 방법은 여러가지가 존재하는데 하나씩 살펴보면
NIC의 MAC 주소를 이용하는 방법 가장 정확하고 간편한 방법 중의 하나로 네트워크 장치 설정에 대해서 MAC 주소를 지정하는 방법이다.</description>
            <content type="html"><![CDATA[

<p>이미 여러 인터넷 커뮤니티에서 알려진대로 RHEL 기준으로 RHEL5 부터 서버를 리부팅 할 때 마다 네트워크 카드의 순서가 뒤 바뀌는 증상이 발생하곤 한다.</p>

<p>예를 들어 PCI 장치번호로 00:08.0이 eth0 였고 00:09.0가 eth1이었는데 리부팅을 하고 보니 eth0가 eth1으로 잡히고 eth1이 eth2로 잡히는 증상 또는 서로 바뀌는 증상들이 발생하는 것이다.</p>

<p>이것에 대한 해결 방법은 여러가지가 존재하는데 하나씩 살펴보면</p>

<h2 id="nic의-mac-주소를-이용하는-방법">NIC의 MAC 주소를 이용하는 방법</h2>

<p>가장 정확하고 간편한 방법 중의 하나로 네트워크 장치 설정에 대해서 MAC 주소를 지정하는 방법이다. Redhat 계열로 예를 들면</p>

<pre><code>/etc/sysconfig/network-scripts/ifcfg-eth0
</code></pre>

<p>위 파일에 HWADDR 항목을 추가해서 실제 NIC 장치의 MAC주소를 지정하면 몇 번을 리부팅해도 바뀌지 않는다.</p>

<pre><code>DEVICE=eth0
BOOTPROTO=static
BROADCAST=123.123.123.255
IPADDR=123.123.123.123
NETMASK=255.255.255.224
NETWORK=123.123.123.0
HWADDR=00:A0:D1:12:E7:11
</code></pre>

<p>다만, 이와 같은 방법의 경우 서버시스템의 장애로 인해 서버를 교체하게 되면 (즉, 디스크는 유지하고 서버만 교체) HWADDR이 달라지게 되므로 다시 수정해 주어야 하는 번거로움이 생길 수 있다.</p>

<p>참고가 되는 문서 : <a href="https://access.redhat.com/kb/docs/DOC-15331" target="_blank">레드햇 KBASE</a></p>

<h2 id="udev-룰셋을-통한-해결방법">udev 룰셋을 통한 해결방법</h2>

<p>최근 리눅스에서는 udev를 통해서 장치를 관리할 수 있고 원하는대로 룰셋을 지정할 수 있다. 이러한 udev의 룰셋을 이용하는 방법으로 아래와 같은 udev 룰을 추가한다.</p>

<p>파일위치 : /etc/udev/rules.d/10-local.rules (없는 파일이므로 새로생성)</p>

<pre><code>SUBSYSTEM==&quot;pci&quot;, SYSFS{class}==&quot;0x020000&quot;, OPTIONS=&quot;ignore_device&quot;
</code></pre>

<p>해당 파일에 기록한 udev의 의미는 sysfs의 해당 클래스에 대해서 룰셋 지정을 무시하라는 것이다. 기본적으로 60-net.rules 파일에 기록된 내용에 의해서 추가되기전에 이와 같은 룰셋을 지정하여 순서대로 (일반적으로 PCI 버스 순서) 인식하도록 하는 방법이다.</p>

<p>룰셋 파일 하나만 추가해주면 되고 생각보다 잘 적용되는 방법이다. 이와 유사하게 커널 부트 파라미터로 지정해서 장치인식을 Legacy 방식으로 하는 방법도 있지만 udev를 쓰는 환경이라면 이러한 방법을 더 추천한다.</p>

<p>참고할만한 링크 : <a href="https://bugzilla.redhat.com/show_bug.cgi?id=192084#c6" target="_blank">Redhat bugzilla</a></p>

<h2 id="etc-iftab-설정파일로-지정하기">/etc/iftab 설정파일로 지정하기</h2>

<p>다른 배포판은 확인을 못해봤지만 wireless-tools 패키지에 들어있는 ifrename 유틸리티를 이용하는 방법이다. /etc/iftab이란 설정파일을 아래와 같은 방식으로 작성하고</p>

<pre><code>eth0 businfo 0000:00:08.0
eth1 businfo 0000:00:09.0
</code></pre>

<p>위의 내용에서 0000:00:08.0은 lspci 등의 명령으로 확인할 수 있는 PCI 장치 번호이다. iftab을 작성했으면 아래의 rc 스크립트를 등록한다.</p>

<p>파일명 : /etc/init.d/ifrename</p>

<pre><code>#!/bin/sh

NAME=ifrename
IFRENAME=/sbin/ifrename
IFTAB=/etc/iftab

test -f $IFRENAME || exit 0
test -f $IFTAB || exit 0

case &quot;$1&quot; in
        start|reload|force-reload|restart)
        $IFRENAME
        ;;
        test)
        $IFRENAME -DV
        ;;
        stop)
        ;;
        *)
        echo &quot;Usage: $NAME {start|stop|reload|force-reload|restart}&quot;
        ;;
esac

exit 0
</code></pre>

<p>그리고, 부팅하는 런레벨에 맞추어 S09ifrename 정도로 심볼릭을 걸어준다.</p>

<pre><code>ln -s /etc/init.d/ifrename /etc/rc3.d/S09ifrename
</code></pre>

<p>이 방법은 서버가 바뀌어도 H/W 스펙이 같다면 PCI 값이 바뀌는 경우가 없기 때문에 유용한 방법이지만 대체로 udev 룰셋보다 번거롭기 때문에 잘 쓰이지는 않는다. 하지만, 별도의 멀티포트 NIC카드를 구매해서 장착한 경우라면 앞서 설명한 udev 룰셋으로 해결이 안될 수 있기 때문에 (즉, 내가 장착한 카드를 eth0로 잡고 싶지만 버스순서 때문에 온보드 NIC이 먼저 잡히는 경우 등) 이러한 방법을 사용하면 된다.</p>

<p><a href="http://www.fxp0.org.ua/2007/apr/20/swapping-interface-names-debian-etch-ifrename/" target="_blank">참고할 만한 링크</a></p>
]]></content>
        </item>
        
        <item>
            <title>검색어에 대한 구글 센스</title>
            <link>/2014/02/05/geomsaegeoe-daehan-gugeul-senseu/</link>
            <pubDate>Wed, 05 Feb 2014 17:39:18 +0000</pubDate>
            
            <guid>/2014/02/05/geomsaegeoe-daehan-gugeul-senseu/</guid>
            <description>전 세계적으로 사랑받는 크롬브라우저에서 특정 검색 키워드를 사용하면 키워드의 의미를 직접 보여주는 방식으로 동작한다. 물론 크롬만 되는 것은 아니고 HTML5를 지원하는 모든 브라우저에서 사용이 가능하다.
HTML5를 지원하는 브라우저에서 구글검색으로 들어가서 아래의 키워드를 사용하면 된다.
 tilt 또는 askew : 화면이 기울어진다. merry christmas : 성탄절 장식이 등장 let it snow : 화면에서 눈이 내리고 화면을 덮기시작 하며 마우스 클릭&amp;amp;드래그로 눈을 지울 수 있다 do a barrel roll : 화면이 데굴데굴 구른다 ascii art : 아는 사람은 아는 글자로 그림을 표현하는 방식으로 검색결과가 바뀐다.</description>
            <content type="html"><![CDATA[<p>전 세계적으로 사랑받는 크롬브라우저에서 특정 검색 키워드를 사용하면 키워드의 의미를 직접 보여주는 방식으로 동작한다. 물론 크롬만 되는 것은 아니고 HTML5를 지원하는 모든 브라우저에서 사용이 가능하다.</p>

<p>HTML5를 지원하는 브라우저에서 <a href="http://www.google.com" target="_blank">구글검색</a>으로 들어가서 아래의 키워드를 사용하면 된다.</p>

<ul>
<li>tilt 또는 askew : 화면이 기울어진다.</li>
<li>merry christmas : 성탄절 장식이 등장</li>
<li>let it snow : 화면에서 눈이 내리고 화면을 덮기시작 하며 마우스 클릭&amp;드래그로 눈을 지울 수 있다</li>
<li>do a barrel roll : 화면이 데굴데굴 구른다</li>
<li>ascii art : 아는 사람은 아는 글자로 그림을 표현하는 방식으로 검색결과가 바뀐다. (이미지검색 주목)</li>
<li>google gravity : 이건 그냥 검색하면 안되고 I&rsquo;m feeling lucky로 검색해야 하는데 순간검색을 사용하는 경우에는 google gravi 정도 입력하면 서제스트 창에서 우측에 I&rsquo;m feeling lucky라는 링크가 있으니 그걸 이용하면 된다. 효과는 구글 화면이 중력에 의해서 죄다 쏟아진다. 그리고 마우스로 집어서 던질 수도 있다. 검색하지 않고 직접 체험하고 싶다면 <a href="http://mrdoob.com/projects/chromeexperiments/google_gravity/" target="_blank">이곳</a>을 클릭해서 보면 된다.</li>
</ul>

<p>그 외에 과거에 구글로고(doodle) 중 하나였던 팩맨 페이지가 있는데 <a href="http://www.google.com/pacman/" target="_blank">이 페이지</a>로 들어가면 검색 버튼에 &lsquo;Insert Coin&rsquo;이 있고 그걸 누르면 팩맨 게임을 즐길 수 있다.</p>

<p>자칫 딱딱해보이는 구글검색화면에서 숨겨진 요소를 찾아가는 것도 나름 재밌다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] Linux 디스크 사용량의 차이 (df 명령과 du)</title>
            <link>/2014/02/05/faq-linux-diseukeu-sayongryangyi-cai-df-myeongryeonggwa-du/</link>
            <pubDate>Wed, 05 Feb 2014 17:37:49 +0000</pubDate>
            
            <guid>/2014/02/05/faq-linux-diseukeu-sayongryangyi-cai-df-myeongryeonggwa-du/</guid>
            <description>자주 받는 질문 중의 하나.. &amp;lsquo;df 명령으로 보니 디스크 사용량이 100%인데 실제 du 명령으로 파일 크기를 합산하면 100%가 되지 않는 이유가 뭔가요?&amp;rsquo;
이러한 질문을 받고 서버를 확인해보면 상당 수가 MySQL DB관련 서버인 경우가 많은데 이는 df 명령과 du 명령의 차이점에 의해서 발생하는 것이다.
df 명령은 현재 마운트 된 파일시스템의 상태를 기초로하여 사용률을 보여주는 것이고 du는 실제 디렉토리와 파일을 확인하고 그 크기를 조사하기 때문이다.
그런데 왜 다른건데?
현재 실행 중인 프로세스가 오픈한 파일에 대해서 삭제처리를 한 후에 해당 프로세스(태스크)를 종료하지 않으면 그 파일은 deleted 상태로 남게 된다.</description>
            <content type="html"><![CDATA[<p>자주 받는 질문 중의 하나.. &lsquo;df 명령으로 보니 디스크 사용량이 100%인데 실제 du 명령으로 파일 크기를 합산하면 100%가 되지 않는 이유가 뭔가요?&rsquo;</p>

<p>이러한 질문을 받고 서버를 확인해보면 상당 수가 MySQL DB관련 서버인 경우가 많은데 이는 df 명령과 du 명령의 차이점에 의해서 발생하는 것이다.</p>

<p>df 명령은 현재 마운트 된 파일시스템의 상태를 기초로하여 사용률을 보여주는 것이고 du는 실제 디렉토리와 파일을 확인하고 그 크기를 조사하기 때문이다.</p>

<p><strong>그런데 왜 다른건데?</strong></p>

<p>현재 실행 중인 프로세스가 오픈한 파일에 대해서 삭제처리를 한 후에 해당 프로세스(태스크)를 종료하지 않으면 그 파일은 deleted 상태로 남게 된다. 즉, 파일시스템에 deleted 상태정보로 유지되고 있는 것이다. 그렇기 때문에 df 명령으로 확인하게 되면 deleted 파일이 차지하는 용량까지 더해져서 du 명령과는 차이를 나타낼 수가 있다. 특히, MySQL에서 큰 데이터베이스를 날렸을 경우에는 그 차이가 크게 느껴질 수 있다.</p>

<p><strong>확인하는 방법은?</strong></p>

<p>lsof라는 명령이 있다. 이 명령을 통해서 &lsquo;deleted&rsquo; 상태에 있는 파일을 확인할 수 있으니 각각의 파일 정보에서 해당 PID를 찾아서 그 프로세스를 리셋하거나 종료하면 df에서 잠식당한 공간을 확보할 수 있다.</p>

<p><code>lsof | grep deleted</code></p>

<p>실제로는 사용하지 않아서 그냥 내버려 둬도 되지 않느냐라고 볼 수 있지만.. 파일시스템 상태정보와 관련이 있기 때문에 실제 사용량이 많지 않아도 df에서 100%로 보이면 여유공간이 없다고 파일 생성이 되 질 않는다.</p>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] nVidia 그래픽카드의 메모리 ECC 기능 비활성화</title>
            <link>/2014/02/05/faq-nvidia-graphic-disable-ecc/</link>
            <pubDate>Wed, 05 Feb 2014 07:21:31 +0000</pubDate>
            
            <guid>/2014/02/05/faq-nvidia-graphic-disable-ecc/</guid>
            <description> nVidia 그래픽카드의 메모리 ECC 기능을 비활성화 하게 되면 성능향상을 얻을 수 있는데 리눅스에서는 아래와 같은 방법으로 비활성화가 가능하다
 $ nvidia-smi -e 0
 0대신에 1을 입력하면 활성화하게 되며 위 값을 변경한 후에는 시스템 리부팅이 이루어져야 제대로 적용된다.
실행예시  root@tbox ~]# nvidia-smi -e 0
Disabled ECC support for GPU 0:6:0.
Disabled ECC support for GPU 0:14:0.
Reboot required.
 </description>
            <content type="html"><![CDATA[

<p>nVidia 그래픽카드의 메모리 ECC 기능을 비활성화 하게 되면 성능향상을 얻을 수 있는데 리눅스에서는 아래와 같은 방법으로 비활성화가 가능하다</p>

<blockquote>
<p>$ nvidia-smi -e 0</p>
</blockquote>

<p>0대신에 1을 입력하면 활성화하게 되며 위 값을 변경한 후에는 시스템 리부팅이 이루어져야 제대로 적용된다.</p>

<h3 id="실행예시">실행예시</h3>

<blockquote>
<p>root@tbox ~]# nvidia-smi -e 0</p>

<p>Disabled ECC support for GPU 0:6:0.</p>

<p>Disabled ECC support for GPU 0:14:0.</p>

<p>Reboot required.</p>
</blockquote>
]]></content>
        </item>
        
        <item>
            <title>[FAQ] SFTP 접속 오류</title>
            <link>/2014/02/05/faq-sftp-jeobsog-oryu/</link>
            <pubDate>Wed, 05 Feb 2014 05:50:12 +0000</pubDate>
            
            <guid>/2014/02/05/faq-sftp-jeobsog-oryu/</guid>
            <description> 증상  received message is too long
 위와 같은 메시지 발생하며 접속이 제대로 이루어지지 않음
원인 접속하는 사용자 계정의 쉘 환경설정 파일에 stdout으로 메시지를 출력하는 부분이 존재해서 발생 되는 문제입니다.
해결 접속하려는 사용자 계정의 .bashrc 와 .bash_profile에서 화면에 메시지를 출력하는 구문을 제거합니다. 간편하게 아래와 같은 명령으로 각 환경설정 파일의 실행 결과를 확인하시고 제거하시면 됩니다.
 $ . .bashrc
$ . .bash_profile
 </description>
            <content type="html"><![CDATA[

<h3 id="증상">증상</h3>

<blockquote>
<p>received message is too long</p>
</blockquote>

<p>위와 같은 메시지 발생하며 접속이 제대로 이루어지지 않음</p>

<h3 id="원인">원인</h3>

<p>접속하는 사용자 계정의 쉘 환경설정 파일에 stdout으로 메시지를 출력하는 부분이 존재해서 발생 되는 문제입니다.</p>

<h3 id="해결">해결</h3>

<p>접속하려는 사용자 계정의 .bashrc 와 .bash_profile에서 화면에 메시지를 출력하는 구문을 제거합니다.
간편하게 아래와 같은 명령으로 각 환경설정 파일의 실행 결과를 확인하시고 제거하시면 됩니다.</p>

<blockquote>
<p>$ . .bashrc</p>

<p>$ . .bash_profile</p>
</blockquote>
]]></content>
        </item>
        
    </channel>
</rss>
