장거리 운전할 때 두시탈출 컬투쇼 들으면 졸음예방도 되고 지루하지 않아서 좋습니다. 라디오가 항상 잘 나온다는 보장도 없고 오후 2시~4시에만 컬투쇼를 들을 수 있기에 저는 우분투 기반의 홈서버를 활용하여 매일 컬투쇼를 예약 녹음하고 있습니다.


우선 다음과 같이 필요한 패키지를 설치합니다.


$ sudo apt-get install mimms

$ sudo apt-get install mplayer

$ sudo apt-get install lame 


SBS 라디오를 녹음하기 위한 스크립트를 생성합니다. 다음 내용을 rec_knn_radio.sh로 저장해 주세요.


#!/bin/bash

RADIO_ADDR="mms://211.220.195.199/viewradio"

RADIO_NAME="knn_radio"


PROGRAM_NAME=$1

RECORD_MINS=$2

DEST_DIR=$3

NO=$4


REC_DATE=`date +%Y%m%d`

TEMP_ASX=`mktemp -u`

TEMP_WAV=`mktemp -u`

MP3_FILE_NAME=$PROGRAM_NAME"_"$REC_DATE"_"$NO.mp3


ID3_TITLE=$PROGRAM_NAME"_"$REC_DATE"_"$NO

ID3_ARTIST=$RADIO_NAME

ID3_ALBUM=$PROGRAM_NAME

ID3_YEAR=`date +%Y`


mimms -t $RECORD_MINS $RADIO_ADDR $TEMP_ASX

mplayer -ao pcm:file=$TEMP_WAV $TEMP_ASX

lame --preset voice --tt $ID3_TITLE --ta $ID3_ARTIST --tl $ID3_ALBUM --ty $ID3_YEAR $TEMP_WAV $MP3                                       _FILE_NAME


rm $TEMP_WAV

rm $TEMP_ASX


mkdir -p $DEST_DIR

chgrp user $MP3_FILE_NAME

mv $MP3_FILE_NAME $DEST_DIR


SBS에서 고릴라 앱을 제공하면서 mms 서버를 막아놔서, KNN이 제공하는 mms를 활용하고 있습니다. 가끔은 컬투쇼가 아닌 다른 방송이 녹음되기도 합니다.


스크립트 파일에 실행 권한을 부여합니다.


$ chmod a+x recEbsRadio.sh


crontab에 다음과 같이 등록합니다.


# m h  dom mon dow   command

00 14 * * * /home/reshout/bin/rec_knn_radio.sh sbs_cultwo_show 60 /data/media/라디오 1 > /dev/null 2>&1

00 15 * * * /home/reshout/bin/rec_knn_radio.sh sbs_cultwo_show 60 /data/media/라디오 2 > /dev/null 2>&1


다음과 같은 형식의 이름으로 저장됩니다.


/data/media/라디오/sbs_cultwo_show_20121107_1.mp3

/data/media/라디오/sbs_cultwo_show_20121107_2.mp3


원인은 잘 모르겠지만 2시간 분량을 한번에 녹음할 수 없어서, 1시간씩 2번에 걸쳐 녹음하도록 설정했습니다.


조금 더 자세한 설명이 필요하시면 제가 예전에 작성한 글을 참조하세요.

http://blog.reshout.com/2692460


1. ffmpeg이 포함된 패키지 설치

sudo apt-get install libav-tools

2. 코덱 라이브러리 설치
sudo apt-get install libavcodec-extra-53

3. ffmpeg 실행, 출력파일 이름(무한도전.mp4)의 확장자(mp4)에 따라 container format이 결정됩니다.
ffmpeg -i 무한도전.avi -vcodec libx264 -acodec aac -strict experimental -ab 128k -ac 2 -b:v 640k -threads 2 -partitions 0 -flags +loop -cmp +chroma -subq 1 -trellis 0 -refs 1 -coder 0 -me_range 16 -g 300 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -maxrate 10M -bufsize 10M -qcomp 0.6 -qmin 2 -qmax 51 -qdiff 4 -level 30 무한도전.mp4


토렌트에서 받은 파일을 다운로드 디렉토리에서 원하는 곳으로 일일이 옮기는 것은 정말 귀찮은 작업입니다.


다음 파일의 다운로드가 완료되었을 때,


Mnet 슈퍼스타K4.6회.120921.720p.HDTV.H264-구제역돼지.mkv


다음 디렉토리로 이동하는 작업을,


/data/media/TV/슈퍼스타K4


자동화 하기 위해 간단한 node.js 모듈을 만들어 보았습니다.


이 모듈은 현재 홈서버에서 forever로 항시 동작하고 있습니다.


var fs = require('fs');

var path = require('path');


var torrentDir = '/data/torrent/';

var completeDir = path.join(torrentDir, 'complete');

var mediaDir = '/data/media/';

var tvDir = path.join(mediaDir, 'TV');


var workingFilename = {};

var dirMap = {};


dirMap[path.join(tvDir, '개그콘서트')] = /개그.*콘서트|개콘/;

dirMap[path.join(tvDir, '무한도전')] = /무한.*도전/;

dirMap[path.join(tvDir, '힐링캠프')] = /힐링.*캠프/;

dirMap[path.join(tvDir, '슈퍼스타K4')] = /슈퍼.*스타.*[kK].*4/;

dirMap[path.join(tvDir, '대왕의꿈')] = /대왕의.*꿈/;

dirMap[path.join(tvDir, '대풍수')] = /대풍수/;


fs.watch(completeDir, function(action, filename) {

  for (dir in dirMap) {

    var regExp = dirMap[dir];

    var targetDir = dir;


    if (regExp.test(filename)) {

      if (!workingFilename[filename]) {

        var fromPath = path.join(completeDir, filename);

        var toPath = path.join(targetDir, filename);


        workingFilename[filename] = toPath;

        setTimeout(function() {

          fs.rename(fromPath, toPath, function(err) {

            if (!err)

              console.log(filename + ': success');

            else

              console.log(filename + ': fail, ' + err);

            setTimeout(function() {

              delete workingFilename[filename];

            }, 5000);

          });

        }, 5000);

      }

      break;

    }

  }

});


이 모듈을 활용하시려면 Transmission이든 Deluge든 파일을 다운로드 중에 저장하는 디렉토리와 다운로드 완료 후 저장하는 디렉토리를 다르게 설정하셔야 합니다.


코드에 대해서 간단히 설명드리자면, 동일한 파일에 대하여 중복으로 fs.watch()가 callback을 불러주는 문제를 피하기 위하여 workingFilename이라는 map을 활용하고 있고, 파일이 생성되고 저장되고 옮겨지는 등의 시간차를 감안하여 setTimeout()을 적절히 활용하였습니다.


리눅스 서버를 위한 토렌트 클라이언트의 양대 산맥은 Transmission과 Deluge라고 할 수 있습니다.


Transmission이 가볍고 단순해서 좋긴 한데, 다운받은 파일의 user와 group이 debian-transmission이라 파일을 자동으로 관리하기가 영 불편해서, 최종적으로 Deluge를 선택하게 되었습니다. (Deluge는 deluged 프로세스의 user와 group을 지정할 수 있고, 자연스럽게 deluged가 다운받은 파일의 user와 group도 그대로 따라갑니다.)


Deluge는 비교적 화려한 기능과 인터페이스를 제공하지만, Python으로 구현되어 있어 Transmission보다 비교적 메모리를 많이 사용하고 느립니다.


설치는 잘 정리되어 있는 다음 문서를 그대로 따라하시면 됩니다.


http://linuxplained.com/install-deluge-web-interface-on-ubuntu-1204/


add-apt-repository가 없으신 분은 python-software-properties 패키지를 설치해 주시면 되겠습니다.


sudo apt-get install python-software-properties


설치 및 실행이 완료되면 다음 주소로 webui에 접근할 수 있습니다.


http://localhost:8112/


토렌트 등록, 시작, 멈춤, 삭제, 설정 등 모든 작업이 webui에서 가능합니다.


기본적인 동작에는 아무런 문제가 없지만 특정 디렉토리에 토렌트 파일을 복사해 넣으면 자동으로 다운로드가 시작되는 기능이 한글 토렌트 파일명에 대하여 동작하지 않는 문제가 있습니다.


Preferences > Downloads > Folder > Autoadd .torrent files from > /data/torrent


이 문제를 해결하기 위해 /data/torrent에 한글 이름의 토렌트 파일이 추가되면 임의의 영문 이름으로 바꾸는 node.js 모듈을 작성하여 forever로 돌리고 있습니다.


var fs = require('fs');

var path = require('path');


var torrentDir = '/data/torrent/';

var workingFilename = {};


function randomString(len, charSet) {

  charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

  var randomString = '';

  for (var i = 0; i < len; i++) {

      var randomPoz = Math.floor(Math.random() * charSet.length);

      randomString += charSet.substring(randomPoz,randomPoz+1);

  }

  return randomString;

}


fs.watch(torrentDir, function(action, filename){

  var hangulRegexp = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/;

  var ext = path.extname(filename);


  if (!workingFilename[filename] && ext == '.torrent' && hangulRegexp.test(filename)) {

    var fromPath = torrentDir + filename;

    var toPath = torrentDir + randomString(8) + '.torrent';


    workingFilename[filename] = toPath;

    setTimeout(function() {

      fs.rename(fromPath, toPath, function(err) {

        if (!err) console.log(filename + ': success'); else console.log(filename + ': fail');

        setTimeout(function() {

          delete workingFilename[filename];

        }, 2500);

      });

    }, 2500);

  }

});


Samba를 통해 해당 디렉토리에 토렌트 파일을 복사한 경우, 같은 파일에 대하여 callback이 여러번 호출되기 때문에 map을 사용하여 중복을 피했고, 파일이 생성되고 내용이 저장되기 이전에 이름을 바꾸어 버리는 문제를 피하기 위해 시간차를 두었습니다.


Deluge가 기본으로 제공하는 플러그인 중에 토렌트 파일이 추가되거나 다운로드가 완료되었을 때, 특정 스크립트를 자동으로 실행하는 기능을 제공하는 Execute라는 플러그인이 있습니다.


자세한 내용은 다음 문서를 참조하세요.


http://dev.deluge-torrent.org/wiki/Plugins/Execute


참고로 플러그인을 사용하도록 설정한 후 webui에서 스크립트를 추가하려고 하면 javascript 에러가 발생하는데, deluged를 다시 시작하면 잘 됩니다.


이 역시 한글 이름의 토렌트 파일의 경우 제대로 동작하지 않습니다.


이 문제를 해결하기 위해 다음 문서를 참조하여,


http://forum.deluge-torrent.org/viewtopic.php?f=9&t=40517


Execute 플러그인의 소스코드를 수정하였습니다.


sudo vi /usr/share/pyshared/deluge/plugins/Execute-1.2.egg/execute/core.py


빨간색 부분의 코드를 추가하시면 됩니다.


122         # Go through and execute all the commands

123         for command in self.config["commands"]:

124             if command[EXECUTE_EVENT] == event:

125                 command = os.path.expandvars(command[EXECUTE_COMMAND])

126                 command = os.path.expanduser(command)

127                 log.debug("[execute] running %s", command)

128                 if isinstance(torrent_name, unicode):

129                     torrent_name = torrent_name.encode('utf-8')

130                 d = getProcessOutputAndValue(command, (torrent_id, torrent_name, save_path), env=os.environ)

131                 d.addCallback(log_error, command)


deluged를 다시 시작하면 의도한대로 added, complete 이벤트에 등록해 둔 script가 실행 됩니다.


다운로드가 완료된 후에 해당 토렌트를 삭제하고 싶다면 다음과 같은 스크립트를 사용하시면 됩니다.


#!/bin/bash

date=`date +%Y%m%d`

time=`date +%H:%M:%S`


echo \($date $time\) $1 $2 $3 >> /data/torrent/log/complete.log

deluge-console rm $1


deluge-console 패키지도 설치해 주셔야 합니다.


deluge-console를 사용하면 console에서 다운로드 상태를 보거나 토렌트를 추가하고 삭제하는 작업이 console에서 가능합니다. 자세한 내용은 다음 문서를 참조하세요.


http://whatbox.ca/wiki/Deluge_Console_Documentation


LG넷하드 N1T1을 잘 쓰다가 용량도 부족하고, 이것저것 해보기에는 여러가지로 제약이 많아서 홈서버를 구축하게 되었습니다.


가장 큰 동기는 node.js 입니다. 공부하면서 개인적으로 유용한 서비스를 몇가지 만들어 보려고 합니다.


처음에는 성능에 조금 욕심을 부려 셀레론G를 고려했는데, dc to dc 100W 파워로 감당할 수 있을지 확신이 들지 않았습니다. 그리고 전기세도 무시할 수 없을 것 같아서 저전력 CPU를 찾기에 이릅니다. 결론적으로는 TDP가 18W 밖에 되지 않으면서 Intel Atom보다 약간 성능이 나은 AMD E-350을 선택하게 되었습니다. (돈이 아주 많다면 Intel Core i3-2100T를 선택하는 것이 최선입니다.)



총 비용은 38만원 정도입니다. 기존에 사용하던 NAS를 12만원에 판매하기로 하였으니 대략 26만원이 더 들었네요.


AMD E-350이 나온지 오래 되어서 그런지 처음 선택한 메인보드가 품절이라 주문하는데 애를 좀 먹었습니다.


이번에 저전력 홈서버를 조립하면서 처음 알았는데, 저전력 CPU는 보통 메인보드에 장착된 상태로 판매됩니다.


케이스로 i7-BOOK을 선택한 이유는 3.5인치 HDD를 2개까지 장착할 수 있는 모델이기 때문입니다. 그런데 실제로 조립해보니  3.5인치 HDD를 2개 장착하려면 공간이 부족해서 선 정리하기가 쉽지 않을 것 같습니다.


i7-BOOK가 그리 작은 케이스는 아니기 때문에 조립 과정은 무난했습니다.



메인보드가 들어갈 자리를 잡기 위해 케이블을 밖으로 뺀 상태입니다. 메인보드 메뉴얼을 펼쳐 각 케이블이 장착되어야 할 위치를 미리 파악해 두었습니다.



ASRock E350M1/USB3은 AMD E-350을 장착한 메인보드 중에 사양이 좋은 편입니다. eSATA 포트와 USB 3.0 포트를 제공합니다. 



HDD를 장착하지 않은 상태에서 일단 부팅이 잘 되는지 확인해 보았습니다. 그런데 케이스에 달린 팬 두개 중 하나가 불량이라 엄청난 소음이 발생하여 그냥 사용하지 않기로 결정했습니다. 이틀째 아무런 문제 없이 잘 동작하고 있고, 저전력 CPU를 사용하고 있으니 앞으로도 큰 탈은 없을 것 같습니다.



HDD를 장착하고 케이블 정리까지 마쳤습니다. 나중에 HDD를 추가할 것에 대비하여 HDD를 한쪽 끝에 장착하였는데, 반대쪽에 HDD를 하나 더 장착하기에는 케이블 뭉치 때문에 공간이 허락될지 잘 모르겠습니다. 2.5인치 HDD는 무난히 추가로 장착할 수 있을 것 같습니다.



조립을 완료하니 이런 모습입니다. 깔끔하죠?


BIOS를 적절히 설정한 후, 미리 준비한 USB로 우분투 리눅스 12.04 LTS 서버를 설치하였습니다.


파티션은 이렇게 잡았습니다.



마지막으로 홈서버를 도메인에 연결하기 위한 작업을 했습니다. 

1. 공유기 설정에서 홈서버에 IP를 할당하고 DMZ로 설정

2. DNSEver에서 DDNS 추가

3. DDNS 정보를 업데이트하는 스크립트 파일을 cron에 등록


기존에 사용하던 NAS와 비슷한 전력을 소모하면서도, 훨씬 나은 사양을 갖추고 있고, 리눅스로 할 수 있는 모든 것을 시도해 볼 수 있는 홈서버를 구축해 보았습니다. 리눅스에 어느정도 경험을 가지고 계신 분이라면 과감히 도전해 보시기를 권합니다. 궁금한 점이 있으시면 댓글 남겨 주세요. 제가 아는 범위에서 성심 성의껏 답변해 드리겠습니다.


그동안 LG넷하드 N1T1을 잘 써왔지만 용량도 부족하고, SSH를 뚫었지만 성능과 자유도가 떨어져서 홈서버 구축을 준비하고 있습니다.  (N1T1의 메모리는 128MB, ARM 계열의 CPU를 사용하고 있어 최신 소스코드를 빌드하는데 어려움이 있습니다.)


N1T1의 소비 전력은 44W 입니다. 이 수준을 맞추기 위해 선택할 수 있는 제품은 많지 않습니다. 저는 저전력 CPU 중에서 Intel Atom D525보다 약간 성능이 나은 AMD E-350을 선택하였습니다. AMD E-350의 TDP는 18W이며, 참고로 지금 제가 사용하고 있는 Intel Core i5 750의 TDP는 95W입니다.



  • AMD E-350은 라데온 HD 6310을 포함하고 있습니다.
  • 저전력 CPU는 메인보드에 장착된 상태로 판매됩니다.
  • 100W의 dc to dc 전원을 사용하기 때문에 소음이 없습니다.
  • i7-BOOK은 3.5 HDD를 2개까지 장착할 수 있습니다.
  • SSD를 사용한다면 저전력, 무소음 PC로도 괜찮을 것 같네요.


AMD E-350의 전력소비, 성능이 궁금하신 분은 다음 링크를 참조하세요.

http://blog.naver.com/PostView.nhn?blogId=vobavoba&logNo=80124902643&viewDate=&currentPage=1&listtype=0


케이스에 관심 있으신 분은 여기로...

http://lovenabi93.blog.me/162705044

저전력 홈서버 구축을 준비하고 있어서 컴퓨터의 소비전력에 관심이 많은 요즘입니다.


다음 사이트를 통해 조립해서 사용하고 있는 데스크탑(http://reshout.com/2692472)의 소비전력을 측정해 보았습니다.

http://www.power-on.com/autocheck.html



역시 일반 데스크탑의 소비전력은 굉장하네요.


$ sudo apt-get install build-essential libssl-dev make scons git-core

$ git clone git://github.com/ry/node.gitgit clone git://github.com/ry/node.git

$ cd node

$ ./configure --without-snapshot

$ vi deps/v8/SConstruct


다음과 같이 수정합니다.


80 'gcc': {

81      'all': {

82        'CCFLAGS': ['$DIALECTFLAGS', '$WARNINGFLAGS', '-march=armv5t'],

83        'CXXFLAGS': ['-fno-rtti', '-fno-exceptions'],

84      },


1081  'armeabi': {

1082    'values': ['hard', 'softfp', 'soft'],

1083    'default': 'soft',

1084    'help': 'generate calling conventiont according to selected ARM EABI variant'


$ make

$ make install

$ node

>


Reference: https://github.com/joyent/node/issues/2131#issuecomment-3208846

First


if [ "$WINDOW" != "" ]; then
    export PS1='\nS:$WINDOW \e[1;32m\u\e[0m@\e[1;31m\h\e[0m:\e[1;33m\w\e[0m\n\$ '
else
    export PS1='\n\e[1;32m\u\e[0m@\e[1;31m\h\e[0m:\e[1;33m\w\e[0m\n\$ '
fi

Second


DEFAULT="[37;40m"

PINK="[35;40m"

GREEN="[32;40m"

ORANGE="[33;40m"


hg_dirty() {

    hg status --no-color 2> /dev/null \

    | awk '$1 == "?" { unknown = 1 }

           $1 != "?" { changed = 1 }

           END {

             if (changed) printf "!"

             else if (unknown) printf "?"

           }'

}


hg_branch() {

    hg branch 2> /dev/null | \

        awk '{ printf "\033[37;0m on \033[35;40m" $1 }'

    hg bookmarks 2> /dev/null | \

        awk '/\*/ { printf "\033[37;0m at \033[33;40m" $2 }'

}


export PS1='\n\e${PINK}\u \e${DEFAULT}at \e${ORANGE}\h \e${DEFAULT}in \e${GREEN}\w $(hg_branch)\e${GREEN}$(hg_dirty) \e${DEFAULT}\n$ '





LG넷하드(N1T1)의 SSH를 활성화하는 방법은 생각보다 간단합니다.


그러나 상당히 위험한 작업임에는 분명합니다. SSH 접근이 절실하신 분만 도전!


다음 웹문서를 참조하였습니다.

http://forum.nas-portal.org/archive/index.php/t-14744.html


New UI로 이미 업그레이드가 되어있는 N1T1에만 적용할 수 있습니다.


원리는 간단합니다. 펌웨어 업데이트 후 실행되는 script 파일에 다음 내용을 추가하는 방식입니다.

1. ssh 서버 패키지를 설치 (dropbear)

2. 부팅시 ssh 서버가 실행되도록  설정

3. root 권한을 가지는 계정을 하나 생성하고 비밀번호 설정 (lgroot:admin)


1. 다음 주소에서 9595 펌웨어 파일을 다운 받습니다.

http://www.lgservice.co.kr/cs_lg/download/SoftwareDownloadDetailCmd.laf?parentCode=1014,1017,1001


2. firmware-nt1_9595rfke.bin 파일을 7zip(http://www.7-zip.org)으로 열고, postinst.sh 파일을 에디터로 엽니다.


3. 파일 끝에 다음 내용을 붙이고 저장합니다. 저장하고 나오면 7zip이 압축파일을 업데이트합니다.

#install dropbear

apt-get update

apt-get -y install dropbear 


#change dropbear config

sed 's/^NO_START=1/NO_START=0/' /etc/default/dropbear > /tmp/db.$$ 

mv /tmp/db.$$ /etc/default/dropbear


#modify startup

update-rc.d -f dropbear remove

update-rc.d dropbear start 20 S . stop 20 0 6


#add alternative root user

useradd -o -u 0 -g 0 -m lgroot

echo lgroot:admin | chpasswd


4. NAS 웹페이지에 관리자모드로 로그인 후, 수정한 펌웨어 파일로 펌웨어 업데이트를 수행합니다.


5. SSH로 접속해 봅니다. (아이디: lgroot, 비밀번호: admin)

퀄컴(Qualcomm)의 올조인(AllJoyn) 기술에 대하여 설명드리고자 합니다. 피상적인 설명 보다는 개발자의 시각으로 이해한 바를 차분히 풀어 보겠습니다.


AllJoyn 사이트는 다음과 같습니다.

https://www.alljoyn.org/


사이트에 이런 문구가 있는데,


AllJoyn™ is a peer-to-peer technology that enables ad hoc, proximity-based, device-to-device communication without the use of an intermediary server.


AllJoyn 기술에 대하여 간결하게 설명하고 있는 듯 하네요. 중계서버 없이 device와 device가 direct로 통신할 수 있도록 도와주는 프레임워크 또는 라이브러리로 이해하시면 됩니다.


일단 데모를 한번 보겠습니다.



안드로이드 Device끼리 연결되어서 각 Device에 저장된 사진을 공유하는 시나리오와 PC용 게임을 안드로이드 Device로 제어하는 시나리오를 보여주고 있습니다.


이 시나리오를 직접 구현해야 한다면, AllJoyn과 같은 기기간 연동를 도와주는 기술을 한번 쯤은 관심있게 검토하게 되겠죠?


Q&A 형식을 빌려 AllJoyn 기술을 조금 더 깊이 들여다 보도록 하겠습니다.


AllJoyn을 활용할 수 있는 플랫폼은?


윈도우, 리눅스, 안드로이드에서 AllJoyn 기반의 애플리케이션을 개발 할 수 있습니다.


사용할 수 있는 프로그래밍 언어는?


현재는 Java와 C++ API를 제공하고 있습니다. 차후에는 Browser Plugin을 설치하는 방식으로 JavaScript API도 제공할 것으로 알고 있습니다.


각 Device들은 어떻게 연결되어 있을까요?


답은 Wi-Fi 입니다. 같은 AP에 Wi-Fi로 물려 있는 Device간에 통신이 가능하도록 도와주는 것이 AllJoyn 기술입니다. 사이트에서는 블루투스와 Wi-Fi Direct도 지원한다고 설명하고 있지만, Wi-Fi Direct는 아직 구현 중이고, 블루투스도 안드로이드 커널, 프레임워크 수정을 피할 수 없기 때문에 현재로서는 활용하지 못한다고 보는 것이 맞을 것 같습니다.



AllJoyn이 장점으로 내세우는 것 중에 하나가 애플리케이션 개발자가 네트워크 레이어에서 어떤 기술을 통신 사용하는지 신경쓰지 않아도 된다는 것입니다. 개념적으로는 AllJoyn 프레임워크가 알아서 Wi-Fi든 Bluetooth든 상황에 맞게 선택한다고 이해하시면 됩니다. 그러나 앞서 말씀드린 것 처럼 현실적으로는 동일 Network내에 있는 Device끼리 Wi-Fi로 통신하는 방법밖에 없습니다.


연결된 Device들은 어떤 방식으로 데이터를 전달할까요?


답은 Remote Method Invocation(RMI) 입니다. Annotation을 추가한 인터페이스 파일을 서로 공유하고, proxy를 통해 해당 인터페이스의 메서드를 호출하는 방식으로 데이터를 전달합니다. 소켓 프로그래밍처럼 주고 받을 데이터의 순서와 타입에 대한 약속을 하고 힘들게 예외처리 할 필요 없이 우아하게 메서드를 정의하고 호출하면 그만입니다.


각 Device는 서로를 어떻게 찾을 수 있을까요?


지금까지 AllJoyn 프레임워크라고 뭉뚱그려 설명했는데, 이제는 AllJoyn Daemon의 개념을 제대로 설명해 드려야 할 것 같습니다.



AllJoyn 기반의 애플리케이션이 정상적으로 다른 Device와 통신하기 위해서는 각 Device에 한개 이상의 AllJoyn Daemon이 동작하고 있어야 합니다. 애플리케이션 개발자는 AllJoyn 라이브러리를 링크하고 여기에 정의된 API를 호출하는 방식으로 Daemon에 Service를 등록하거나 Client로서 Service를 찾고 연결하여 메서드를 호출할 수 있습니다.


Daemon이 설치된 Device가 같은 네트워크안에 연결되면, Daemon끼리 서로의 존재를 인지하고 정보를 교환합니다.

- 자신에게 어떤 Service가 연결되어 있는지

- 자신에게 어떤 이름의 Service를 찾는 Client가 연결되어 있는지

개념적으로는 같은 네트워크 안에 연결된 Device가 하나로 연결되어 가상의 AllJoyn Bus가 만들어 진다고 이해할 수 있습니다. 자연스럽게 Device의 경계는 무너지는거죠. 가상의 AllJoyn Bus에 누가 어떤 이름의 Service를 하고 있고 누가 어떤 이름의 Service를 찾고 있는지만 중요하게 됩니다.


Service는 누군가 자신을 유일하게 구별할 수 있도록 이름(well-known name)을 광고(advertise)해야 합니다. 이 이름은 Java 세상에서 패키지 이름처럼 도메인을 거꾸로 쓰는 표기법을 사용합니다.


예를 들어 다음과 같은 채팅 애플리케이션이 만들어낸 Service의 이름에서,


org.alljoyn.sample.chat.bob

org.alljoyn.sample.chat.carol


특정 Service를 나타내는 prefix는 org.alljoyn.sample.chat이고 이를 Bus에서 유일하게(Unique) 만들어주는 suffix는 bob과 carol이 됩니다. Client는 prefix로 Service를 탐색합니다. 이 예제에서 Client는 org.alljoyn.sample.chat로 Service를 탐색하게 되고 탐색(discovery) API는 org.alljoyn.sample.chat.bob와 org.alljoyn.sample.chat.carol를 모두 반환합니다. bob이 만든 채팅방과 carol이 만든 채팅방을 모두 찾은 셈이죠. 사용자의 선택에 따라 선택된 특정 Service(채팅방)와 연결하고 Service가 제공하는 메서드를 호출하면 통신은 간단히 이루어 집니다.


제대로 공부해 보고 싶으신 분은 다음 문서를 차분히 읽어 보시면 되겠습니다.

https://www.alljoyn.org/content/introduction-alljoyn



우분투 리눅스를 활용하여 EBS 라디오를 예약 녹음하는 방법을 소개합니다.

우선 다음과 같이 필요한 패키지를 설치합니다.

$ sudo apt-get install mimms
$ sudo apt-get install mplayer
$ sudo apt-get install lame 

EBS 라디오를 녹음하기 위한 스크립트를 생성합니다. 

다음 내용을 recEbsRadio.sh로 저장해 주세요.

#!/bin/bash
RADIO_ADDR="mms://211.218.209.124/L-FM_300k"
RADIO_NAME="ebs_radio"

PROGRAM_NAME=$1
RECORD_MINS=$2
DEST_DIR=$3

REC_DATE=`date +%m%d`
TEMP_ASX=`mktemp -u`
TEMP_WAV=`mktemp -u`
MP3_FILE_NAME=$PROGRAM_NAME"_"$REC_DATE.mp3

ID3_TITLE=$REC_DATE"_"$PROGRAM_NAME
ID3_ARTIST=$RADIO_NAME
ID3_ALBUM=$PROGRAM_NAME
ID3_YEAR=`date +%Y`

mimms -t $RECORD_MINS $RADIO_ADDR $TEMP_ASX
mplayer -ao pcm:file=$TEMP_WAV $TEMP_ASX
lame --preset voice --tt $ID3_TITLE --ta $ID3_ARTIST --tl $ID3_ALBUM --ty $ID3_YEAR $TEMP_WAV $MP3_FILE_NAME

rm $TEMP_WAV
rm $TEMP_ASX

mkdir -p $DEST_DIR
mv $MP3_FILE_NAME $DEST_DIR

다음과 같이 recEbsRadio.sh 파일에 실행 권한을 부여합니다. 

$ chmod a+x recEbsRadio.sh

첫번째 인자는 프로그램 이름 
두번째 인자는 녹음 시간(분)
세번째 인자는 mp3 파일 저장 디렉토리

사용 예제는 다음과 같습니다. 

$ ~/recEbsRadio.sh EarEng 30 ~/ebs 

지금부터 30분 동안 EBS 라디오를 녹음하여 ~/ebs/EarEng_0201.mp3로 저장하겠다는 의미입니다.

cron을 활용하여 월요일~토요일 오전 6시부터 30분 동안 귀트영을 녹음하는 설정을 해보겠습니다. 

cron에 스크립트 실행을 등록하기 위해 다음 명령어를 실행해 주세요.

$ crontab -e

에디터가 실행되면 다음 한 줄을 추가 후, 저장하고 나오시면 됩니다. 

00 06 * * 1-6 ~/recEbsRadio.sh EarEng 30 ~/ebs &> /dev/null

이는 월요일~토요일(1-6) 매일 오전 6시(06) 00분(00)에 recEbsRadio.sh를 실행하겠다는 의미입니다. 

귀트영, 운트영, 입트영을 모두 녹음하시려면 다음 3줄을 추가해 주세요. 

00 06 * * 1-6 ~/recEbsRadio.sh EarEng 30 ~/ebs &> /dev/null 2>&1
30 06 * * 1-6 ~/recEbsRadio.sh LuckEng 20 ~/ebs &> /dev/null 2>&1
00 07 * * 1-6 ~/recEbsRadio.sh MouthEng 20 ~/ebs &> /dev/null 2>&1

다음 명령을 통해 cron에 제대로 등록되었는지 확인할 수 있습니다. 

$ crontab -l

이 글은 다음 URL을 참조 및 보완하여 작성되었습니다. 
Android의 debuggerd의 코드를 읽던 중 재밌는 이름의 함수를 발견했습니다!

Android에서 동작하고 있는 application이 특정 시그널을 발생시키며 죽게 되면, debuggerd가 이를 받아서 처리하게 됩니다. 

여기서 각종 디버깅용 정보를 남기기 위해 호출하는 함수의 이름은 바로 

engrave_tombstone() 

입니다. 개발자의 재치가 느껴지네요.
안드로이드폰 개발 프로젝트에서 일하기 시작한지 어느덧 3달이 다 되어갑니다. 순수 소프트웨어를 개발하다 스마트폰개발 분야에 뛰어드니 처음에는 어리둥절하고 용어도 익숙치 않았습니다. 대기업의 프로세스도 적응이 잘 되지 않았구요. 

지금은 어느정도 적응이 되어서 조금씩 성과도 내면서 즐겁게 일하고 있습니다. 직접 개발하면서 점점 좋아지는 제품의 상태를 보면, 안드로이드폰을 구매해서 사용해 보고 싶은 욕구가 발동합니다. 새로운 소스코드를 저장소에서 내려받아 다운로드를 하고 부팅해서 확인하는 순간은 늘 설레입니다. 어떤 부분이 더 좋아졌을까 하면서...

안드로이드 시스템은 크게 3파트로 나눌 수 있습니다. 

커널(디바이스 드라이버)
플랫폼
애플리케이션

저는 플랫폼 파트에서 일하고 있습니다. 다양한 언어(C, C++, Java)로 구성되어 있어 다소 복잡하긴 하지만, 안드로이드 시스템의 전체적인 구조와 특성을 파악할 수 있고, Java로 구성된 영역은 애플리케이션 개발에 사용하는 API를 그대로 사용하기 때문에 자연스럽게 애플리케이션 개발 기술도 익힐 수 있습니다. 

고생스럽더라도 프로젝트가 꼭 성공해서 양산 되었으면 하는 바램을 가져봅니다.
아주 오랜만에 새 컴퓨터를 조립했습니다. 지금까지 조립한 컴퓨터는 대체로 가성비 위주의 저렴한 구성이었죠. 듀론, 애슬론XP 1800+ CPU를 사용했습니다. 인텔 제품을 사용하여 조립하는 것은 이번이 처음!


AMD 애슬론II-X4 620 프로부스에서부터 고민을 시작해서 결국은 Intel Core i5 750 린필드를 선택하게 되었습니다. '기왕이면 좋은거로' 병이 도져, 메인보드도 파워도 그럭저럭 괜찮은 제품으로 골랐습니다. 


직접 조립할까 말까 고민했었는데, 손수 정성스럽게 조립한 시스템이 안정적으로 잘 돌아가니 보람이 느껴집니다. 1년 넘게 이어온 노트북 생활을 청산하고 빵빵한 사양의 데스크탑에 23인치 Full HD 모니터를 사용하니 가슴이 뻥 트이는 느낌이네요. 

회사에서 안드로이드폰 개발 일을 하다보니, 자연스럽게(?) 안드로이드 애플리케이션 개발에 대한 지식도 쌓게 되었습니다. 노트북에서는 에뮬레이터 한번 띄우는게 일이었는데, 이제 쾌적한 환경에서 애플리케이션 개발을 해볼 수 있겠네요!


컴퓨터로 EBS 라디오 듣기에 대한 포스팅에 이어, mp3로 녹음하는 방법을 소개해 드리고자 합니다. 거원제트오디오와 같은 별도의 어플리케이션을 설치해야 될 줄 알았는데, 다음 팟 플레이어로 간단히 되더군요. 

다음 팟 플레이어의 소리 설정 > 소리 캡처(Shift + G) 기능을 활용하면 됩니다. 현재 플레이 되고 있는 소리를 mp3, ogg 등의 포맷으로 녹음해주는 기능입니다. 


mp3로 녹음한 경우, 보시는 것 처럼 1분당 대략 1MB 근처의 용량을 사용하게 됩니다. 귀트영 하루분량 30분을 녹음하면 대략 30MB 정도 되겠네요.


2010년의 소박한(?) 목표는 다음과 같습니다.

평일은 하루도 빠짐없이,

1. 6시 이전 기상
2. 6시부터 시작하는 EBS 라디오 귀가 트이는 영어(귀트영) 청취 

아직까지는 잘 지키고 있습니다. 

매일 새벽에 일어나 MP3P로 라디오를 듣다보면, 기기의 위치에 따라 잡음이 끼는 경우가 있어 영 불편하더라구요. 그래서 컴퓨터로 EBS 라디오를 듣는 방법을 찾아 보니, 의외로 너무 간단하다는...

다음 링크를 클릭하시던지...


윈도우키 + R을 눌러 실행창으로 들어간 후, 다음 URL을 입력하고 확인 버튼을 누르시면...
(미디어 플레이어에서 Ctrl + U를 눌러 URL 열기를 하셔도 좋습니다.) 

mms://211.218.209.124/L-FM_300k

아주 깨끗한 음질의 EBS 라디오를 들을 수 있습니다.
루비를 제대로 공부하진 않았지만, 언어가 간결한 덕분에 여러가지 상황에서 유용하게 사용하고 있습니다. 그 동안 만들어서 사용했던 몇 가지 루비 예제 코드를 소개합니다. 여기에 소개하는 코드를 조금만 수정하면 다양한 경우에 활용할 수 있을 것 같네요.

1. 소스코드에서 32자 이상의 심볼을 사용한 라인을 찾아 출력하기

HITACHI 메인프레임 C 컴파일러가 32자 이상의 심볼을 지원하지 않더군요. 일일이 찾기 힘들어서 이를 찾기 위해 작성한 프로램입니다. 현재 디렉토리에 존재하는 소스코드를 읽어 32자 이상의 심볼을 찾은 경우 해당 라인을 라인번호와 함께 출력합니다.

file_array = Array.new

Dir.foreach(".") { |x|
  if x.include?(".c") or x.include?(".h") or x.include?(".y") or x.include?(".l")
    file_array.push(x)
  end
}

file_array.each { |fname|
  File.open(fname) { |fp|
    lineno = 1
    while line = fp.gets
        line.scan(/[1-9a-zA-Z_]+/) { |x|
          if x.length > 32
            puts "#{fname}:#{lineno}:+#{x.length - 32}:#{x}"
          end           
        }
        lineno = lineno + 1
    end
  }
}

2. 파일의 라인 뒤집기

매년 말 블로그에 독서 리스트를 정리할 때 사용하기 위해 작성한 프로그램입니다. 티스토리에서 목록을 뽑아 파일에 저장한 후 이를 뒤집어 독서 리스트를 읽은 순서대로 뽑아냅니다.

line_array = Array.new
File.open("ReadingList.txt") do |file|
    while line = file.gets
        line_array.push(line)
    end
end
ofile = File.new("ReadingList.rev.txt", "w")
line_array.reverse!
cnt = 1
line_array.each do |reversed_line|
    ofile.puts("#{cnt}. #{reversed_line}")
    cnt = cnt + 1
end
ofile.close

3. 프로그램 수행 및 stderr, stdout 얻기

루비에서 다른 프로그램을 실행하고 stderr, stdout을 추출하는 예제 코드입니다. 루비를 사용하여 배치 스크립트를 작성 할 때 유용할 것 같네요.

require 'session'

t=Thread.new do
  sh = Session.new
  sh.execute( 'ruby /home/stefano/documenti/scripts/prova.rb' ) do |out, err|
    puts "Msg: #{out}" if out
    puts "Err: #{err}" if err
  end
end
t.join
요즘 회사들어와서 처음으로 제품 메뉴얼을 작성하고 있습니다. XML 형태로 작성되는 메뉴얼 파일은 SVN을 통해 형상관리 되고 있는데, 윈도우 비스타에 설치한 TortoiseSVN가 자꾸 commit 할때마다 죽어서 대안을 찾던 중에 Slik SVN이라는 것을 발견하게 되었습니다.


간단히 설치만 하면 윈도우 cmd에서 svn 관련 커맨드(svn, svnadmin, ...)를 사용할 수 있습니다.

http://www.sliksvn.com/en/download

이런 복잡한 상황을 맞이할때마다 유닉스 기반의 Mac OSX가 그리워지는군요...
dlopen()으로 shared object를  로딩하는 과정에서 에러가 발생하여 dlerror() 함수를 통해 에러의 원인을 확인해 보았습니다.

'aaa.so' is not a valid load module: Bad magic number

구글에서 에러 메세지를 이용해 검색하다가 해결의 실마리를 찾았습니다! file 명령어로 파일의 타입을 확인해 보니 executable과 shared library의 파일 타입이 다르더라구요.

$ file aaa.so
aaa.so:     ELF-64 shared object file - IA64
$ file a.out
a.out:     ELF-32 executable object file - IA64
C프로그래밍을 하다보면 memory leak에 대한 걱정 때문에 메모리를 동적으로 할당(malloc)하는 것이 부담스럽습니다.

만약 동적으로 할당한 메모리가 함수내에서만 사용된다면 함수 끝에서 free할 필요 없이 malloc 대신 alloca로 메모리를 할당해 사용하면 됩니다. alloca를 사용하면 activation record(stack)에 메모리가 할당되거든요.

NAME
       alloca - allocate memory that is automatically freed

SYNOPSIS
       #include <alloca.h>

       void *alloca(size_t size);

DESCRIPTION
       The  alloca() function allocates size bytes of space in the stack frame
       of the caller. This temporary space is automatically  freed  when  the
       function that called alloca() returns to its caller.
회사에서 제가 사용하는 개발 환경을 소개합니다. 제가 하는 일은 프로그래밍 언어의 compiler, interpreter, traslator를 개발하는 것 입니다. 주로 사용하는 툴은 gcc, flex, bison, subversion, vi, eclipse 등등 이구요.

한동안 console + screen + vi 환경을 고집하다 eclipse + vi plugin으로 넘어온지 몇달 되었습니다.


자주 들여다보는 flex, bison description의 syntax highlighting이 안된다는 것을 제외하고는 매우 만족스럽습니다. 특히 eclipse를 사용할때 가장 좋은 점은 Refactor > Rename 기능을 제공하기 때문에 변수, 함수 등의 이름을 바꾸기가 편리하다는 것 입니다.

제가 사용하는 컴퓨터에는 Vista가 설치되어 있고, VMWARE 환경에서 Ubuntu를 부팅해 놓고 개발환경으로 사용하고 있습니다. 2GB의 메모리를 할당해서 개발환경으로 부족함 없이 잘 동작합니다. 이렇게 설정해 놓은 덕분에 윈도우와 리눅스를 아주 편리하게 동시에 사용하고 있습니다.

console 환경에서 기본적인 툴을 다루는 능력이 뒷받침 된다는 전제하에, 다양한 개발 도구, 개발 환경에 대하여 열린 자세를 지니는 것은 개발자가 가져야할 중요한 덕목이라고 생각합니다. 좀 더 효율적으로 일할 수 있는 방법에 대한 끊임 없는 고민이 우리의 퇴근 시간을 앞당겨 줄 수 있을테니까요.
맥을 사용하면서 가장 불편한 것은 바로 마우스의 움직임 입니다! 가속도가 적용되는 방식에 적응하기가 쉽지 않네요. 더군다나 회사에서는 윈도우, 리눅스를 쓰고 집에서 잠깐 맥을 사용하다보면 맥의 마우스 방식이 너무 불편하게 느껴집니다.

맥에서는 마우스가 같은 거리를 움직이더라도 빠르게 움직이면 포인터가 많이 이동하고, 느리게 움직이면 적게 이동합니다. 윈도우의 경우에는 빠르던지 느리던지 동일하게 이동하기 때문에, 이런 습관대로 맥에서 마우스를 움직이면 정교한 컨트롤을 하기 어렵고 손목이 아파옵니다.

게다가 기본적인 마우스 속도도 굉장히 느립니다. 덕분에 24인치 아이맥 화면을 기본 마우스 설정으로 돌아 다니다 보면 속에서 천불이 나죠! 그래서 마우스 속도를 높이고자 주로 사용되는 프로그램이 MouseZoom 입니다. 그러나 여전히 가속도를 반영하는 움직임으로 인한 불편함은 해결되지 않죠. 이 때 이와 같은 문제를 어느정도 개선해 주는 프로그램이 바로 SteerMouse 입니다.

http://www.apple.com/downloads/macosx/drivers/steermouse.html
약간의 설정을 해주니 윈도우랑 별차이를 못 느끼겠군요. 맥에서 마우스 사용에 불편함을 겪고 계신 분이라면 꼭 한번 설치해 보세요.
NHN에서 프로그래머를 위한 나눔고딕 코딩글꼴을 배포하였습니다! 회사에서(Vista) 집에서(Mac) 나눔고딕을 사용해왔는데 코딩글꼴까지 나와서 정말 반가웠습니다.

http://dev.naver.com/projects/nanumfont

백문이 불여일견! 다양한 환경에 적용된 스크린 샷을 보여드리겠습니다.


Window Vista: Putty




Ubuntu Linux: Eclipse




Ubuntu Linux: Gnome Terminal




Mac OS X Leopard: Terminal



윈도우 환경에서는 지금까지 Dina를 주로 사용했습니다. 그래서 그런지 Putty에서 나눔고딕 코딩글꼴을 적용해 보니 비교적 선명하지 않은 느낌이 들어 아쉬웠습니다. 그러나 리눅스나 맥의 경우에는 아주 만족스러웠어요. 당분간은 이 폰트만 사용하게 될 것 같습니다. 그러나 약간의 문제가 있습니다.

리눅스의 경우, 그놈 터미널에 적용했을때 커서가 두글자 크기로 잡히면서 다음 글자를 가려버리는 현상이 발생하더라구요. 해결 방법은 다음 URL에 잘 정리되어 있습니다.
http://dev.naver.com/wiki/nanumfont/index.php/TroubleShooting

맥의 경우, 터미널의 Preference에서 나눔고딕 코딩글꼴로 설정한 후 터미널을 완전히 종료하고 다시 실행하면 다른 폰트로 설정이 바뀌어 있습니다. 그래도 맥에는 Monaco라는 예쁜 고정폭 글꼴이 있어서 그리 아쉽지는 않네요.

Putty에 적용했을때를 제외하고는 대체로 매우 만족스럽습니다. 프로그래머라면 한번 사용해 보세요.
본격적으로 iPhone 어플리케이션을 개발해 보려고, 기초가 되는 Objective-C 언어를 공부중입니다. 컴파일러팀에서 일하다보면 다양한 언어를 접하게 되는데 이녀석도 만만찮게 독특한 개성을 지니고 있는 듯 합니다. 특히나 Smalltalk에서 가져온 복수의 파라메터를 가지는 메서드의 정의 및 호출 방식은 굉장히 독특하네요.


Objective-C를 공부하면서 본격적으로 Xcode를 사용하고 있습니다. vi 에디터 기능을 쓸 수 없다는 것을 빼고는 훌륭합니다! 화면에 노란 줄이 Warning 메세지 입니다. 소스코드에 바로 Error, Warning을 표시해 주네요. 블로그에 공부한 지식을 정리할 수 있는 수준히 되도록 틈틈히 부지런히 공부해야겠습니다.
저는 주로 해피해킹프로2 키보드를 사용합니다. 이 키보드는 일반 키보드와 키 배열이 조금 다른데, Caps Lock키 자리에 Control키가 위치하고 있습니다. 일반 키보드에 왼손을 가만히 가져다 놓으면 아래에 위치한 Ctrl키보다 가운데 위치한 큼직한 Caps Lock키를 새끼 손가락으로 누르는 것이 훨씬 편하다는 것을 알 수 있습니다. 해피해킹 키보드를 사용하면서 습관이 되서 그런지 저는 일반 키보드에서도 Caps Lock키를 Ctrl키로 바꿔서 사용하곤 합니다. 실제로 훨씬 편하기도 하구요. 각 운영체제 별로 그 요령을 알아보도록 하겠습니다.



System Preferences > Keyboard & Mouse에 가셔서 Keyboard 탭의 아래쪽을 보시면 Modifier Keys... 버튼이 있습니다. 이걸 누르시면 Caps Lock, Control, Option, Command Key에 대한 키매핑을 수정할 수 있습니다.  Caps Lock을 Control로 매핑해 주시면 설정이 완료됩니다!

리눅스

일회성 방법
쉘에 다음 내용을 입력하세요.
xmodmap -e 'keycode 66 = Control_L'
xmodmap -e 'clear Lock'
xmodmap -e 'add Control = Control_L'

xmodmap -e 'keycode 117 = Caps_Lock'
xmodmap -e 'add Lock = Caps_Lock

영구적인 방법
~/.Xmodmap에 다음 내용을 추가하세요.
keycode 66 = Control_L
clear Lock
add Control = Control_L
keycode 117 = Caps_Lock
add Lock = Caps_Lock
참고: http://efod.se/writings/linuxbook/html/caps-lock-to-ctrl.html

윈도우

첨부파일을 열어보시면 2개의 레지스트리 파일이 있습니다. 하나는 Caps Lock키를 Control키로 매핑하고 다른 하나는 원래 상태로 돌려 놓습니다. 실행시키고 재부팅 하시면 설정이 완료됩니다.

Eclipse 에디터에서 vi의 기능을 사용할 수 있는 플러그인을 소개합니다.

http://www.satokar.com/viplugin/

이 플러그인은 15유로를 주고 구입해야 합니다. 저는 그나마 환율이 상식적이였던 시기에 구입했습니다. 꾸준히 업데이트 되고 있어 믿을만 하고, Help > Software Updates에서 손쉽게 설치 및 업데이트를 할 수 있습니다.

Mac OSX 환경에서 Eclipse에 이 플러그인을 설치해보니 CommandMode 창에 글씨가 보이지 않아서, 홈페이지에 어줍잖은 영어 실력으로 문제를 호소했더니, 몇일 후에 패치가 올라왔습니다! 사용자를 위한 세심한 배려에 감동했죠.

오랜기간 사용해 본 소감을 말씀드리면 부족함을 느끼지 못할 정도로 vi가 제공하는 대부분의 기능을 사용할 수 있습니다. 최근에는 Eclipse + CDT + viPlugin을 사용하여 파싱 기반의 정확한 assist를 받으면서 vi의 기능을 활용하여 작업을 수행하고 있습니다.
맥미니, 맥북, 아이맥 사이에서 방황하다 결국 미친척 하고 아이맥 24인치를 구입해 버렸습니다! 회사에 들어온 이후로 제 자신에게 가장 큰 선물이 되겠네요. ^^; 다소 성급한 구매였지만 몇일 사용해본 결과 매우 만족스럽습니다. 


작년에 맥북을 사용했던 전력이 있기 때문에 mac osx에 금방 익숙해 질 수 있었습니다. mac osx의 사용자 인터페이스가 워낙 훌륭하기 때문이기도 하겠죠. 넓고 선명한 화면도 훌륭하지만, 가장 만족스러운 부분은 소음이 거의 없다는 것입니다. 얼마전에 처분한 조립PC는 정말 시끄러웠거든요! 컴퓨터로 음악을 듣는게 바보스럽게 느껴질 정도로...

Objective-C 공부 후에, mac 어플리케이션 혹은 iPhone/iPod Touch 어플리케이션을 개발해보려고 합니다. 어느정도 진행이 되면 블로그에 관련 글을 쓸 수 있겠죠. ^^
decNumber Library(http://speleotrove.com/decimal/)를 사용하는 코드에서 Call stack을 깨먹는 버그가 발생하여 지난 일주일동안 마음이 편치 않았는데, 결국 해결했습니다! 워낙 사소한 실수에서 비롯된 일이라 부끄럽기 그지 없지만, 유사한 버그로 머리를 쥐어뜯고 있을 누군가에게 도움이 될지도 모른다는 바램을 가지고 용기를 내어 보겠습니다!

Call stack을 깨는 코드는 다음과 같습니다.

이 함수가 호출되면 sementation fault가 발생하면서 프로그램이 죽는 현상이 발생했습니다. gdb에서 bt를 실행해보니 call stack이 깨졌다는 사실을 알 수 있었습니다. 곰곰히 생각해보니 로컬변수에 값을 쓰다가 activation record의 return address 영역을 엎어 쓰는 것 같더라구요. 이를 확인하기 위해 left 변수 앞에 임의로 char buf[100]; 변수 선언을 넣었더니 call stack이 깨지는 현상은 사라졌습니다.

decNumber 라이브러리를 사용하여 실제 계산을 수행하는 코드는 다음과 같습니다.

여기서 눈여겨 보아야 할 것은 DECNUMDIGITS라는 상수(constant)입니다. 항상 decNumber를 사용해 계산을 수행하기 위해서는 decContext를 설정해서 전달해 주어야 하는데 이때 DECNUMDIGITS을 참조하게 되죠. 이 값을 별도로 설정하지 않으면 decNumber.h에서 1로 설정하기 때문에 정상적인 연산을 수행할 수 없습니다.

따라서 decNumber로 표현 및 계산하고자하는 최대 자리수를 #define으로 미리 지정해 주어야 합니다.  문제는 여기에 있었습니다. decNumber를 사용하여 계산하는 함수를 따로 분리하는 과정에서 decNumber.h를 include 하는 문장 뒤에 #define DECNUMDIGITS를 포함하는 헤더파일의 include 문장이 존재하게 되었던거죠! 결과적으로 decNumber.h가 확장될때는 DECNUMDIGITS가 기본값인 1로 결정되고, 제가 작성한 코드에서는 프로젝트 전역 헤더파일에서 정의한 값(30)이 참조되었습니다. 변수와 정의와 변수의 참조 사이에 괴리(?)가 발생했던 겁니다. 

decNumber.h를 살펴보면 문제는 좀 더 명확해 집니다.

decNumber.h를 include하는 문장 앞에 DECNUMDIGITS을 정의한 프로젝트 전역 헤더파일을 include하도록 수정함으로써 문제는 간단히 해결되었습니다. 대부분의 버그가 비슷하겠지만, 문제를 찾아서 해결하고 나니 참으로 허무한 기분이 들었습니다. 한동안 제 자신이 미워지더군요. ^^;

이번 경험을 통해 얻은 교훈은 다음과 같습니다.

1. macro 사용에 유의하자.
2. 헤더파일의 include 순서에 유의하자.
3. call stack을 깨는 경우는 문자열을 비롯한 array의 잘못된 사용 및 참조로 발생하는 경우가 많다.

+ Recent posts