유저스크립트로 페이지를 뜯어고치는 방법을 이용하기 때문에 유저스크립트를 쓸 수 있어야합니다.


봉룡학사 홈페이지의 클릭이 안되던 컨텐츠를 클릭가능하게 해주고,

iCampus 로그인이 되게 해주고,

일부 동영상 강의를 볼 수 있습니다.
(고해상도 모니터 사용시 뜨는 녹화강의 모드만 지원... 제가 이쪽 강의밖에 안듣기 때문에;)
(저해상도 모니터에서는 화면창하고 카메라창하고 따로 뜨는데 이건 작은 모니터가 없어서... 미지원)
저해상도의 기준 : 1280x800는 저해상도 취급하더군요?

js파일 링크 : skku_crossbrowsing.js

참고 : 아주 허접하게 만들었습니다...(...)



userscript.org에 업로드했습니다. 여기서 설치하세요.

http://userscripts.org/users/244482/scripts




동영상 강의를 보려면 wmp 플러그인을 깔아야되요.

mac용 wmp 플러그인

windows용 wmp 플러그인(파이어폭스, 오페라, 크롬, 사파리 용)




설치방법

*파이어폭스
-부가기능 "Greasemonkey" 검색 후 설치합니다.(깔려있으면 패스.)
-js파일 링크를 걍 클릭해서 설치합니다.

-제거 방법 : [도구 > Greasemonkey > 유저 스크립트 관리] 에서 찾아서 제거합니다.


*오페라
-[환경 설정 > 고급 설정 > 콘텐트] 메뉴를 찾아가서
-[JavaScript 옵션...] 버튼을 클릭합니다.
-사용자 JavaScript 폴더를 확인합니다. (변경하고 싶은 경우에는 변경)
-js파일을 [우클릭메뉴>링크콘텐트저장]을 이용해 다운받아서 해당 폴더에 넣어둡니다.

-제거 방법 : 넣어뒀던 js파일을 딴데로 옮기거나 삭제합니다.

파이어폭스와 오페라는 잘 되는 것을 확인.


*구글 크롬
-js파일 링크를 걍 클릭해서 설치합니다.

-제거 방법 : [도구 > 확장 프로그램] 에서 찾아서 제거합니다.


*사파리(윈도우 용)
-유저스크립트 어떻게 쓰는지 모름...


*사파리(맥 용)
-검색 결과 참고 : http://www.simplehelp.net/2007/11/14/how-to-run-greasemonkey-scripts-in-safari/
-대략 요약하면...
-우선 SIMBL을 설치합니다.
-다음 GreaseKit을 설치합니다.
-js파일 링크를 클릭해서 설치합니다.(되는지 안되는지 모름...)
-제거 방법 : 모름... 맥 없음.


특히 맥 쓰시는 분 계시면 테스트좀 해주세요~ Greasemonkey에서 잘 되니 GreaseKit에서도 잘 될 것 같지만...
아니면 맥에다 파이어폭스나 오페라를 깔면 될 거 같네요.



출처 : http://www.skkulove.org/bbs/zboard.php?id=fb2010_1&page=1&category=&sn1=&divpage=46&sn=off&ss=on&sc=on&select_arrange=headnum&desc=asc&no=228970&searchtype=&keyword

WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,

virtualbox cpu 점유율 100%

QnA 2010. 12. 15. 03:56
virtualbox에서 CPU점유율이 계속 100%를 친다구요? 음.. 전 이런적이 없어서 잘 모르겠지만 의심가는 부분이 있긴합니다.
설정→일반→'VT-x/AMD-V 사용' 부분에 체크가 되어있는지 확인해보세요.
VT기능은 호스트 뿐만 아니라 게스트OS에서도 지원을 해야 사용이 가능한 것으로 알고있습니다. XP에서는 아마 지원을 못할겁니다.

WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,
학교 과제때문에 커널을 수정할일이 있었습니다.

우선 nandsim 위에서 nftl 을 사용할수 있게끔 해야하는 작업이 필요했습니다. 물론 버추얼 박스위에서.

그런데 자꾸 mkfs.ext2 /dev/nftla 쯤에서 버추얼박스가 뻗어서..

집 컴터에서 작업하기로 결심하곤, 지금까지 해놓은것들을 svn에다가 업데이트 시키려고했습니다.

(google code 이용.)

근데 어찌 어찌 잘못해서 include/config 의 .svn 디렉토리가 삭제되어버려서..

이거 commit도 안되고 add 도 안되고..cleanup 등등 모두 안되버리는 상태가 됐습니다.

그래서 자꾸 working directory가 아니라고 메세지가 뜹니다.

후..

그 문제가 되는 config 디렉토리에만 .svn을 생성시켜서 복구하고 싶은데.. 그 방법을 모르겠습니다

(허접이라 svn을 능숙하게 못사용하는군요 -_-)

그러다가 그냥 모든 .svn 을 삭제해서.. 다시 add 부터 하기로 마음먹었습니다 ㅋㅋ

]$ find . -name .svn -print0 | xargs -0 rm -rf


를 사용해서, recursively 모든 ".svn" 디렉토리를 삭제해주고.. 다시 add, commit 해서


해피엔딩 :))



혹시 . 실수로 .svn 을 지웠을때..그거 복구하는 방법 아시는 분 계시면

comment 달아주시길 바랍니다 :)


WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,

개인이 데스크톱용으로 사용하는 리눅스 시스템은 부팅시 매번 사용자 ID와 Password를 입력하는 것이 귀찮은 일일 수도 있다. 그래서 오늘은 리눅스 부팅시 특정 사용자로 자동으로 로그인하는 방법에 대해 알아보도록 하자.
먼저 자동 로그인을 설정하기 위해서는 아래의 항목에 유의해야 한다.

[자동 로그인 시 주의사항]
- 관리자 계정(root)로는 자동 로그인이 되지 않는다. (일반 사용자 계정에 한함)
  ;관리자는 모든 권한을 가진 슈퍼유저를 의미한다. 그러므로 관리자 계정을 자동 로그인 한다는 것은 보안상 아주 취약하게 되므로 리눅스와 같은 멀티유저 운영체제에서는 절대 용납(?)하지 않는 방법이다. 관리자 계정을 자동 로그인 설정한다는 것은 꿈도 꾸지 말자.
- 자동 로그인을 설정하게 되면, 이후 부터는 부팅시 해당 유저로 자동 로그인을 해버리게 되므로 관리자로서의 작업이 필요한 경우, 터미널에서 su 명령으로 관리자 권한을 획득하거나 로그아웃 후 관리자 계정으로 재 로그인을 해야한다.


in KDE

KDE 환경에서는 아래와 같이 KDE제어판을 통해 자동로그인 설정이 가능하다.


① 패널에 있는 "KDE제어판" 아이콘을 실행하거나 메뉴에 있는 "KDE제어판"을 실행한다.

② "시스템관리 > 로그인 관리자" 화면으로 가서, 우측 상단에 있는 "편리한 기능"탭을 클릭한다.

③ 아래와 같은 화면이 나타나면, "자동 로그인 사용"에 체크를 하고 자동으로 로그인할 사용자를 그 아래에서 찾아 선택한다. 만약 아무런 리스트도 나타나지 않는다면 이는 아직 일반 사용자가 아무도 등록되어 있지 않다는 뜻이므로, 일반 사용자를 추가한 다음에 다시 제어판을 실행한다.

④ 로그인 시에는 암호 입력 없이 자동으로 로그인이 되어야 하므로, "암호를 입력하지 않고 로그인" 부분을 체크하고 원하는 사용자 계정을 아래에서 선택한다.

 

⑤ 이제 시스템을 재부팅하면, 앞서 선택한 사용자로 자동 로그인할 수 있게 된다.



in Gnome

Gnome 환경에서도 로그인 환경을 설정하는 프로그램을 사용하여 쉽게 설정할 수 있다.


① Gnome 메뉴에서 "로그인 화면" 이라는 메뉴를 찾아 실행한다.

※ 시스템에 따라 메뉴의 위치가 다를 수 있다. 한글과컴퓨터 데스크톱 2.0 제품은 아래 이미지와 같은 위치에 해당 메뉴가 있다.


② 상단의 탭중, "보안"탭으로 이동하여 "자동 로그인 사용"에 체크를 하고, 그 아래에 자동 로그인을 원하는 사용자 계정을 입력하자. 관리자 계정(root)은 자동로그인 설정을 할 수 없으니 반드시 일반 사용자 계정을 등록해야 한다.


③ 위와 같이 설정이 완료되었다면, 재부팅 후 자동으로 로그인이 가능할 것이다.


④ 만약 위와 같이 했는데도 자동로그인이 되지 않는다면 아래와 같이 설정파일을 수동으로 수정할 수도 있다. 텍스트 에디터 프로그램으로 gdm의 custom.conf 파일을 열어서 [daemon] 단락에 아래와 같은 문구를 입력하자.

※ 로그인 여부에 true를 지정하고, 로그인 사용자에 ID를 입력한다. (예: jhsim)

[daemon]

AutomaticLoginEnable=true

AutomaticLogin=jhsim

 

※ gdm의 custom.conf 파일의 위치는 리눅스 제품에 따라 그 위치가 다를 수 있다.

  - Desktop 2 : /etc/X11/gdm/custom.conf

  - Desktop 3 : /etc/gdm/custom.conf



출처 : http://open.asianux.co.kr/mapping.php?bbsId=TIPNTECH&action=View&doc_number=72&pageNo=


WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,

GNOME KDE 패널 변경 방법

QnA 2010. 11. 28. 14:36

# vi /etc/sysconfig/desktop


DESKTOP="GNOME" <-- GNOME 사용


DESKTOP="KDE" <-- KDE 사용



WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,
통계 표준편차에서 자유도의 의미는?
평점 :




0 (0 명) 나도 평가하기 비공개 조회 :33 답변 : 1
답변이 완료된 질문입니다. (2005-11-16 00:24 작성) 신고

일반적으로 분산과 표준편차는 N으로 나누는데,

N-1로 나누는 경우는 무엇 때문인가요?

자유도 때문이라고 하는데, 자유도의 의미는 이해를 했습니다.

그런데 그 자유도가 어떤 영향이 있기 때문에 N-1로 나누는 것인가요?

사실 N으로 나누나 N-1로 나누나 의미상의 차이는 별로 없다고 생각하는데.

자유도를 꼭 신경써서 N-1로 나누어야 하는것인지요?


질문자가 선택한 답변
re: 통계 표준편차에서 자유도의 의미는?

이의제기 | 신고-->
cahn88 (2005-11-16 01:39 작성) 이의제기 | 신고


질문자 평

# 자유도가 주는 영향

N으로 나누어 구한 표본분산의 기대값은

E(표본분산) = 모분산 * N/(N-1)

즉 표본분산의 기대값이 수학적으로 모분산과 동일하지 않습니다.

(N-1)로 나누면

E(표본분산) = 모분산

이어서 표본분산이 불편추정량(unbiasted estimator)이 됩니다.

그런데 N이 커지면 사실 N/(N-1)은 1에 가까와져서 bias는 무시할수 있습니다.

그러니 이것은 표본수가 작을때 민감한 문제입니다.



WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,
Q.
신호처리에 대한 수업을 듣고 있는데
수업중에 교수님이
unit impulse 와 unit step function에 그래프를 그리고

문제 3개 정도를 낸 다음에 unit impulse와 unit step function에 의해서
이 문제가 이렇게 그래프가 그려진다. 라고만 하시네요

정확히 unit impulse와 unit step function의 정의와 어떤 역할을 하는지
그리고 어떻게 그런 그래프가 설명을 좀 해주세요
이거 때문에 미분, 적분, 라플라스까지 뒤적거렸는데...
더 모르겠네요.....

A.

unit impulse: 단위 임펄스 함수로써 흔히 들 말하는 (델타함수)입니다.

혹시 x축그래프가 time 도메인이면 (0,0)에서 위로향하는 화살표 로 표시되구요 크기는 1입니다.

       x축 그래프가 integer(정수)인 discrete 도메인이면 0,0에서 위로향하는 그래프고 동그라미로 표시됩니다 크기는 1이구요 그리고 나머지 x축좌표는 모두 0입니다.



unit step funtion: 단위계단함수로써  0,1부터 x축은 무한대까지 계속 y값이 1값을 갖습니다.

t<0 면 값이 없죠 0이죠.



출처 : http://kin.naver.com/qna/detail.nhn?d1id=11&dirId=1118&docId=64854973&qb=aW1wdWxzZSBmdW5jdGlvbg==&enc=utf8&section=kin&rank=2&search_sort=0&spq=1&pid=g7UuIg331ylsstx1ElGssv--158305&sid=TLXCTA12tUwAADAuLkI

WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,
From: Kyle McMartin <kyle@redhat.com>

Rawhide builds are currently failing to build unifdef.c, as the next
version of glibc changes the default _POSIX_C_SOURCE level, which
exposes getline() from <stdio.h>

scripts/unifdef.c:209: error: conflicting types for 'getline'
/usr/include/stdio.h:653: note: previous declaration of 'getline' was
here
make[2]: *** [scripts/unifdef] Error 1
make[1]: *** [__headers] Error 2
make: *** [vmlinux] Error 2

Rename the symbol in unifdef.c to parseline to avoid this conflicting
declaration.

Otherwise Jakub says we could add a -D_POSIX_C_SOURCE=200112L as a
workaround to unifdef CFLAGS, but this seems like it would be less
desirable in the long term.

Signed-off-by: Kyle McMartin <kyle@redhat.com>



WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,

안녕하세요
하드디스크의 트랙마다 시작하는 섹터 찾는 방법점 가르쳐주세요
트랙마다 시작되는 섹터가 있지 않나요? 그 섹터를 찾으려고 하거든요

int main(){
fd=opne("/dev/sda",O_RDONLY);
lseek(fd,0,SEEK_SET);
gettimeofday(,);
read(fd,buf,SIZE);
gettimeofday(,);
}

핵심적인 부분만 적었어요 . 그러니깐 하드디스크에 접근해서 buf라는 변수에 SIZE(섹터의 크기)만큼 읽어 올 생각인데
read()를 수행하기전 시간이랑 수행후 시간을 gettimeofday() 를 사용하여 마이크로 세컨트의 값을 구할생각입니다
이것을 반복하면 현재 트랙에서 다음 트랙으로 헤드가 넘어갈때 약간의 지연 시간이 발생할것이고 이 시간이
다른 측정한 시간들(read()로 읽어 오는 시간) 보다 조금더 길것이라는 생각을하였고,
이부분이 트랙의 시작 섹터 라고 생각을해서 이렇게 수행할려고 하는데 ... 잘않되네여;
하드디스크의 캐시 메모리도 고려해야할것같고 그런거 고려해서 수행해봐도 찾기가 어렵네요...
제가 생각한 방법이 잘못되었거다 다른 방법이 있으면 점 가르쳐주세여
제가 초보라서 조금 자세히 설명해주시면 정말 감사하겠습니다
수고하세요 (열심히 배워야겠다...;;;=_=;;)

==================================================================================

섹터단위 접근 방법은 Block Device에 직접 접근하시면 됩니다.

다만 하실께 많아서 저도 말을 안꺼낸거고요.

예전에 구현해본적이 있는데 간단하게 설명드리자면 결국에 Page 단위 I/O는

디바이스 드라이버로 내려가면 몇번째 섹터를 읽어와라하는 sector 단위의 I/O 명령어로 바뀝니다.

bushi 님이 말씀하신데로 ATA 스펙을 보시거나 아니면 Linux Kernel 만 열어보셔도

읽을 섹터의 위치, 그 위치에서 몇개의 섹터를 읽을 것인가를 outbyte()함수를 통해 HDD로 전송하게 되어있습니다.

그래서 결국 sector 단위 접근은 Linux block device driver 를 참고해서 작성하시면 비교적 간단히 할수 있고요.

HDD geometry는 결국에는 HDD performance tunning 이랑 관련있는거라 HDD vendor쪽으로 알아보셔야 할꺼예요.

다만 예전에 제가 국내의 S모사 HDD 펌웨어 개발하는 쪽에 문의해본적이 있는데 별로 협조적이지 않았구요.

한가지 노파심에 말씀드리는건 LBA에 주소가 실제 HDD의 정확히 몇번째 sector에 mapping 된다는건 물리적인 HDD geometry에 따라

결정되지만 이는 결국 HDD 펌웨어에서 주소변환을 해줘서 가능한겁니다.

그래서 만약 어느 위치를 읽기 위해 명령어를 내렸는데 그 영역이 과거에 베드 섹터라면 HDD는 섹터 리매핑으로인해

잉여 섹터에 매핑 시키기 때문에 전혀 다른 곳에 있는 섹터일 수도 있습니다. 실제 하드디스크 제조 과정중에 발생하는 일이구요.

===================================================================================

간단하게 말해서 불가능하고 가능하다고 해도 매우 제한적이며 어렵습니다.
현재 하드디스크들은 다른 분들이 말씀하신 것처럼 내부의 정보는 철저하게 숨기고 있습니다.
외부에서 섹터 단위로 접근한다고 해봤자 LBA이며, 이는 이미 한번 재가공된 번호들이고 실제 하드디스크의 CHS구조에 대입하기 위한 정보는 업체마다 다르며 공개되어 있지 않습니다.

실제로 몇해전(최근 일로 알고 있습니다.) 관악산에 위치한 모 대학 연구실에서 이와 관련된 연구 때문에 국내 최대 전자기업에 정보제공을 요청하였으나 그곳에서 기밀사항이라는 이유로 거절하였습니다.
그 후 그 연구실에서는 각종 노가다성 실험으로 대략의 매핑 정보를 얻어내고 이를 토대로 하드디스트의 매핑 알고리즘 때려맞추기라는 주제로 논문도 발표하였습니다.
(논문 발표 후 국내 최대 전자기업에서 갑자기 친하게 지내자고 했다는 후문이 있더군요... 쿨럭...)



WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,
reentrance 를 우리말로 옮기자면 재진입성쯤 되고, thread-safety는 multithread-safety라고도 하니 다중쓰레드안전성쯤 되겠습니다. 요즘 워낙 multi-threaded programming은 일반화된 것이라 thread-safety에 대해서는 어느 정도 개념들을 챙기고 계시리라 생각하고, reentrance가 thread-safety와 어떻게 다른가를 설명하면서 그 둘간의 차이점을 설명해 보도록 하겠습니다.

어 떤 루틴 또는 프로그램이 Thread-safe하다라고 하면 여러 쓰레드에 의해 코드가 실행되더라도 실행 결과의 correctness가 보장되는 것을 뜻합니다. 이 정의에서 중요한 점은 실행 결과의 correctness가 보장되어야 한다는 것입니다. 즉, 여러 쓰레드에 의해 코드가 진짜로 동시에 수행되던 보이기에 동시에 수행되던 간에 실행 결과의 correctness가 보장되면 Thread-safe하다라고 말하는 거죠.

이에 비해 어떤 루틴 또는 프로그램이 Reentrant하다라고 하면 여러 쓰레드에 의해 코드가 동시에 수행될 수 있고, 그런 경우에도 실행 결과의 correctness가 보장되는 것을 뜻합니다.

이해가 되시나요 ? 차이점이 안 보이신다구요 ? 그럼 다시 한 번 말씀드리죠.

어떤 루틴 또는 프로그램이 Thread-safe하다라고 하면 여러 쓰레드에 의해 코드가 실행되더라도 실행 결과의 correctness가 보장되는 것을 뜻합니다.

이에 비해 어떤 루틴 또는 프로그램이 Reentrant하다라고 하면 여러 쓰레드가 코드를 동시에 수행할 수 있고, 그런 경우에도 실행 결과의 correctness가 보장되는 것을 뜻합니다.

이제 보이시죠 ? Reentrant 특성이 훨씬 강력한 제약조건이라는 것이 느껴지시나요 ? 구체적으로 코드에서는 어떻게 달라지나 예제를 통해서 알아보겠습니다.

C 언어 표준라이브러리에 strtok()이라는 문자열 함수가 있는 건 다 알고 계시겠죠(모르시는 분은 가서 엄마 젖 좀 더 먹고 오세요~ ㅋㅋㅋ 농담입니다). strtok() 의 매뉴얼에 다음과 같이 설명되어 있습니다.

     The strtok() function can be used to break the string
     pointed to by s1 into a sequence of tokens, each of which is
     delimited by one or more characters from the string pointed
     to by s2. The strtok() function considers the string s1 to
     consist of a sequence of zero or more text tokens separated
     by spans of one or more characters from the separator string
     s2. The first call (with pointer s1 specified) returns a
     pointer to the first character of the first token, and will
     have written a null character into s1 immediately following
     the returned token.
The function keeps track of its position
     in the string between separate calls, so that subsequent
     calls (which must be made with the first argument being a
     null pointer) will work through the string s1 immediately
     following that token.
In this way subsequent calls will work
     through the string s1 until no tokens remain. The separator
     string s2 may be different from call to call. When no token
     remains in s1, a null pointer is returned.


strtok()을 사용하는 예제를 보면 그 의미가 더 명확해집니다.

#include <cstring>

using namespace std;

int
main()
{
  // strtok() 의 첫번째 argument 는 const char * 가 아닌 char * 이므로
  // 새로 할당된 메모리로 넣음
  char* str = (char*)malloc(strlen("A;B;C;D;E;F;G")+1);
  strcpy(str, "A;B;C;D;E;F;G");
  cout << str << " ==> ";

  char* tok = strtok(str, ";");
  while (tok != 0)
  {
    cout << tok << " ";
    tok = strtok(0, ";");
  }
  cout << endl;

  return 0;
}


위 와 같이 작동한다는 것은 strtok() 내부적으로 다음과 같이 다음번 호출 때 문자열 분석을 시작할 위치를 기억하고 있다는 걸 뜻합니다. 그리고 여러번의 호출에 걸쳐 이 함수가 제대로 작동하기 위해서는 그 위치는 당연히 static 으로 선언되어 있겠지요.

char* strtok(char* src, const char* delim)
{
  // src, delim 이 NULL 인지, delim 이 "" 인지 체크하는 코드는 생략
  char* tok;
  static char* next;     // 분석을 시작할 위치
  if (src != NULL)
    next = src;
  tok = next;

  // boundary condition check
  if (*next == '\0')
    return NULL;

  // 분석 시작
  for (; *next != '\0'; ++next)
  {
    if (*next in delim) // pseudo code
    {
      *next = '\0';
      ++next;
      break;
    }
  }

  return tok;
}

위 와 같은 코드는 Reentrant 하지도 않고 Thread-Safe 하지도 않을 것입니다. 위 코드가 여러 쓰레드에 의해 수행될 경우, next 라는 변수가 서로 다른 쓰레드에 위해 공유가 되므로(함수 내에서 static으로 선언된 변수는 stack에 할당되는 것이 아니라 전역 메모리 영역에 할당됩니다) next 변수가 깨질 가능성이 생기기 때문입니다. 이런 상황에 대한 구체적인 예를 다음과 같이 들 수 있을 것 같습니다.

Multi-thread 상의 strtok() 호출

Multi-thread 상의 strtok() 호출

위 그림에서 Call strtok() 앞의 숫자는 호출 순서를 뜻하는 것이고 next가 가리키는 화살표의 선은 Call strtok()을 호출한 후 next 변수의 상태값이라고 생각하시면 됩니다. 그림의 예에서는 3, 4 번까지는 원래 예상하는 결과 대로 리턴이 되겠지만 5번 호출에서는 "F"가 리턴될 것이고, 6번 호출에서는 NULL이 리턴될 것입니다(왜 그런지는 제가 작성한 strtok()을 따라가면서 알아보세요. ^^). 이런 결과가 나오는 것은 모두 next 라는 변수가 양쪽 Thread에 의해서 값이 갱신되기 때문입니다.

그렇다면 strtok()을 thread-safe 하게 만들기 위해서는 어떻게 해야할까요 ? 다음 처럼 strtok() 내부 구현을 바꿔서 내부적으로 next 변수에 대해 lock을 걸도록 만들면 될까요 ?

os_specific_lock lck;

char* strtok(char* src, const char* delim)
{
  // src, delim 이 NULL 인지, delim 이 "" 인지 체크하는 코드는 생략
  char* tok;
  lock(&lck);
  static char* next;     // 분석을 시작할 위치
  if (src != NULL)
    next = src;
  tok = next;

  // boundary condition check
  if (*next == '\0')
  {
    unlock(&lck);
    return NULL;
  }

  // 분석 시작
  ......

  unlock(&lck);
  return tok;
}


언 뜻 생각하면 위와 같이 구현된 strtok()이 thread-safe하다고 생각할 수도 있겠지만 실상은 전혀 그렇지 않습니다. 위와 같이 구현된 strtok()은 thread 가 동시에 next 변수를 수정하는 것을 막아주기 하지만 그림에서 나타낸 예를 제대로 처리해주지 못합니다. strtok()이 thread-safe 하기 위해서는 strtok() 호출 한번에 대해 thread-safe하면 되는 것이 아니라 전체 tokenizing 과정이 모두 thread-safe 해야 하기 때문입니다. 아~ 그렇군요. 그렇다면 tokenizing 과정 전체에 대해 lock을 걸면 되겠네요.

#include <cstring>

using namespace std;

os_specific_lock lck;

void* tokenizer(void* s)
{
  char* str = (char*)s;
  cout << str << " ==> ";

  lock(&lck);
  char* tok = strtok(str, ";");
  while (tok != 0)
  {
    cout << tok << " ";
    tok = strtok(0, ";");
  }
  unlock(&lck);
  cout << endl;

  return 0;
}

int
main()
{
  pthread_t thr1, thr2;
  char* str1, str2;      // 어찌어찌해서 초기화됐다고 가정
  pthread_create(&thr1, NULL, tokenizer, str1);
  pthread_create(&thr2, NULL, tokenizer, str2);

  return 0;
}

어 때요? 맘에 드시나요 ? tokenizing 과정 전체에 대해 lock을 걸어 버렸으니 제가 그림에서 제시한 상황이 발생하지 않겠네요. 그래도 어쩐지 꺼림직하지 않으세요 ? 마치 구더기 한 마리 잡으려고 불도저 쓰는 격이라고나 할까요. 위에 있는 tokenizer는 아주 간단해서 그렇지 만약에 token 하나 하나에 대해서 복잡한 처리를 한다면 어떻게 될까요 ? 그리고 복잡한 처리 과정 중에 그 쓰레드가 I/O 이벤트를 기다린다면 어떻게 될까요 ? 갈수록 태산이네요. 그죠 ? CPU가 아무리 빨라도 아무리 많은 Multi Core 들을 가지고 있어도 전혀 그런 성능을 활용하지 못하는 코드가 되어 버립니다. 이 사태를 어떻게 해결해야 하나요. 여러분이 진정한 엔지니어라면 여기서 멈춰서는 안돼죠. 해결책을 생각할 시간을 드리겠습니다.
1초.
2초.
3초.
4초.
5초.
6초.
7초.
8초.
9초.
10초 삐~~~~~!!!.

생각나셨나요 ? 멋진 해결책을 가지고 계신 분들이 있으리라 생각합니다. 자~ 그럼 다음과 같이 strtok() 인터페이스 및 구현을 바꾸면 어떨까요 ?

char* strtok(char* src, const char* delim, char** start)
{
  // src, delim 이 NULL 인지, delim 이 "" 인지 체크하는 코드는 생략
  char* tok;
  char* next;     // 분석을 시작할 위치. static 을 없애고 start 라는 입력
                  // 으로 초기화함
  if (src != NULL)
    next = src;
  else
    next = *start;


  // boundary condition check
  if (*next == '\0')
    return NULL;

  // 분석 시작
  tok = next;
  for (; *next != '\0'; ++next)
  {
    if (*next in delim) // pseudo code
    {
      *next = '\0';
      ++next;
      break;
    }
  }

  *start = next;
  return tok;
}


이 렇게 구현할 경우 thread-safe 하기도 하지만 reentrant하기도 합니다. 위와 같은 strtok() 내에서 사용되는 모든 변수는 stack에 할당되는 자동변수이므로 각각 독립적인 stack을 갖는 thread가 동시에 위와 같은 strtok()을 수행한다해도 전혀 문제가 없습니다. 실상 위와 같이 구현한 strtok()은 POSIX에 의해 표준화되어 있는 strtok_r()이나 MS Visual C++ 에서 제안한 safe string library에 표함되어 있는 strtok_s()와 동일합니다.

reentrant 한 코드는 thread 간에 공유하는 자원 자체가 없어야만 하는 코드입니다. 쓰레드간에 동기화 메커니즘 자체가 필요 없게 만드는 코드이고, 따라서 multi threading 환경에서 여러 쓰레드가 해당 코드를 진짜로 동시에 실행하더라도-동시에 실행되는 것처럼 보이기만 하는 것이 아니라-아무런 문제가 없습니다. 그렇지만 thread-safe 하다는 것은 단지 여러 쓰레드에 의해 실행되더라도 문제만 없으면 된다는 완화된 조건이므로 공유하는 자원이 있더라도 이것을 여러 쓰레드가 동시에 접근하지 못하도록 locking mechanism 같은 것으로 막아주기만 하면 됩니다. 결국 thread-safe한 코드는 multi threading 환경에서 reentrant 코드보다는 효율성이 떨어질 가능성이 높습니다. 해당 코드를 수행하고 있는 thread 가 공유 자원에 대한 lock 이 풀리기를 기다리는 동안은 다른 thread 의 수행을 막아버리기 때문입니다.

또 다른 간단한 예를 통해 thread-safe와 reentrant 의 차이점을 살펴 보겠습니다.

// 출처: Wikipedia
int g_var = 1;

int f()
{
  g_var = g_var + 2;
  return g_var;
}

int g()
{
  return f() + 2;
}

위 와 같은 코드에서 g() 또는 f()를 호출하는 코드는 모두 thread-safe 하지 않습니다. thread-safe 하지 않으면 reentrant 하지도 않습니다. 위 코드를 thread-safe 하게 하려면 어떻게 해야할까요 ? 다음과 같이 해야겠죠.

int g_var = 1;
os_specific_lock lck;

int f()
{
  lock(&lck);
  g_var = g_var + 2;
  unlock(&lck);
  return g_var;
}

int g()
{
  return f() + 2;
}

이 제 thread-safe하게 됐습니다. reentrant 할까요 ? 아니올시다입니다. 여러 쓰레드가 f() 함수를 동시에 수행할 수 없기 때문입니다. 다시 말하면 한 쓰레드가 lock 을 걸고 있다면 다른 쓰레드는 lock 이 풀릴 때까지 기다려야 하므로 reentrant 하지 않은 것입니다. 위 코드를 reentrant하게 고치려면 어떻게 하면 될까요 ?

// 출처: Wikipedia
int f(int i)
{
  int priv = i;
  priv = priv + 2;
  return priv;
}

int g(int i)
{
  int priv = i;
  return f(priv) + 2;
}

아 예 전역 변수를 없애 버려서 쓰레드간 동기화가 필요 없게 만들어 버렸습니다. 위 코드에서는 워낙 lock이 걸리는 시간이 적을 것이므로 성능에 거의 영향을 미치지 않겠지만, lock이 걸리는 기간이 길어진다면 성능에 상당한 영향을 미치겠지요. 특히 요즘 유행하는 Multi-Core CPU에서는 그냥 thread-safe 코드와 reentrant 코드간의 성능 차이가 더 많이 발생할 것입니다.

보통은 thread-safe 코드를 만드는 것보다는 reentrant한 코드를 만드는 게 더 어렵습니다. 그리고, thread-safe 한 코드는 내부 구현만 바꾸면 되는 경우가 많지만 reentrant한 코드를 만드는 것은 위에서 제가 제시한 두 가지 예(strtok(), f())처럼 아예 인터페이스 자체를 재설계해야 하는 경우가 많습니다.

가 능하다면 그냥 thread-safe한 코드를 만드는 것보다는 reentrant한 코드를 만드는 것이 성능상 훨씬 좋은 선택입니다. 물론 reentrant 한 코드를 만드는 것이 불가능한 경우도 있겠지만, 고민해보면 reentrant한 코드를 만들 수 있는 경우가 상당히 있습니다. 요즘처럼 Multi-Core CPU가 갈수록 일반화되고 있는 상황에서 Reentrance는 다시 한 번 주목을 받아야 할 것입니다.

그럼 Coding Guideline스러운 멘트로 이번 글을 마무리 하도록 하겠습니다. ^^

"Thread-safe한 코드보다는 Reentrant한 코드로 작성하라"



출처 : http://yesarang.tistory.com/214

WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,