vi 명령어, vi 단축키, vi(Visual Editer), vim 윈도우이야기

vi 명령어, vi 단축키, vi(Visual Editer), vim

0. 읽기전에!!!

  • 큰따옴표 " " 로 묶인 글은 그 안의 내용을 말하는 것입니다. 만일 "가나다" 라고 하면 큰따옴표("")를 제외한 가나다만 입력하면 됩니다.
  • "", "/", "?" 표시는 입력모드에서 ESC키를 누른뒤 입력하면 됩니다.
  • filename.xxx는 아무 파일을 의미합니다. 즉 자기가 원하는 대로 쓰면 됩니다. 단, 파 일명 작성규칙에 맞는 파일명을 입력해야 합니다.
  • 대소문자는 구분합니다. x와 X는 다릅니다.
  • ""은 엔터키를 의미합니다.
  • "[n]"은 숫자를 의미 합니다.
  • "x, dl"은 x 또는 dl을 의미합니다.

1. History of vi

vi는 BSD의 C shell을 개발한 빌 조이가 1976년에 ed의 기능을 확장시킨 ex(Extended editer)편집기를 개발 하고 이를 확장 시켜서 만들었다. <<유닉스.리눅스 프로그래밍 필수 유틸리티>>
※vi는 Visual editer의 줄임이다.

2. vi의 특징

장점

  1. 키보드로 모든것을 할 수 있다.(처음 시작하는 사람들에게는 치명적인 단점이다.)
  2. 가볍다. 하지만 매우 많은 기능이 있다.
  3. 사용하면 할 수록 재미있다.

단점

  1. 키보드 만을 사용한다.
  2. 단축키가 매우 많다.
  3. 처음 사용자들은 사용하기가 매우 힘들다.

3. vi의 모드(mode of vi)

  1. 명령모드(command mode)
  2. ex모드
  3. 입력모드
  4. ex모드 <=> 명령모드 <=> 입력모드

즉 ex모드에서 입력모드로 가기위해서는 명령모드를 거쳐서 가야한다. 모드라고해서 거창한것은 없다. 단지 "ESC" 키 를 한번 누르는것이 전부다.

4. vi의 명령(command of vi)

ㄱ. 파일관련 단축키

  1. w filename.xxx : 쓰기(write)
  2. w >> filename.xxx : 파일에 덧붙여서 저장>
  3. q : 종료(quit)
  4. q! : 저장하지 않고 종료
  5. e filename.xxx : 열기(open)
  6. wq! : 저장하고 종료하기

ㄴ. 입력모드 전환 키

  1. a : 커서 위치의 다음칸부터 입력
  2. A : 커서 행의 맨 마지막부터 입력
  3. i : 커서 위치부터 입력
  4. I : 커서 행의 맨 처음부터 입력
  5. o : 커서 행의 다음행에 입력
  6. O : 커서 행의 이전행에 입력
  7. s : 커서 위치의 한 글자를 지우고 입력
  8. cc : 커서 위치의 한 행을 지우고 입력

ㄷ. 이동(move)

  1. h : 왼쪽
  2. l : 오른쪽
  3. j : 아래
  4. k : 위
  5. w : 다음 단어의 첫글자로 이동
  6. b : 이전 단어의 첫글자로 이동
  7. $ : 커서 행의 맨 마지막으로 이동
  8. ^ : 커서 행의 맨 처음으로 이동
  9. + : 다음행의 첫 글자로 이동
  10. - : 이전행의 첫 글자로 이동
  11. ( : 문장의 처음으로 이동
  12. ) : 문장의 마지막으로 이동
  13. { : 문단의 처음으로 이동
  14. } : 문단의 마지막으로 이동
  15. H : 커서를 화면의 맨 위로 이동
  16. M : 커서를 화면의 가운데로 이동
  17. L : 커서를 화면의 마지막으로 이동
  18. z : 현재 행을 화면의 맨 위로 이동
  19. z. : 현재 행을 화면의 중앙으로 이동
  20. z- : 현재 행을 화면의 최하단으로 이동
  21. [n]H : 커서를 현재 화면의 [n]행으로 이동
  22. [n]L : 커서를 현재 화면의 아래서 [n]행으로 이동
  23. ctrl + u : 반 화면 위로 스크롤
  24. ctrl + d : 반 화면 아래로 스크롤
  25. ctrl + b : 한 화면 위로 스크롤
  26. ctrl + f : 한 화면 아래로 스크롤
  27. gg : 문서의 맨 첫 행으로 이동
  28. G : 문서의 맨 마지막행으로 이동
  29. [n]G : 문서의 [n]번째 행으로 이동
  30. [n] : 문서의 [n]번째 행으로 이동

ㄹ. 삭제

  1. x, dl : 커서위치의 글자 삭제
  2. X : 커서 바로 앞의 글자 삭제
  3. dw : 한 단어를 삭제
  4. d0 : 커서 위치부터 행의 처음까지 삭제
  5. D, d$ : 커서 위치부터 행의 끝까지 삭제
  6. dd : 커서가 있는 행을 삭제
  7. dj : 커서가 있는 행과 그 다음 행을 삭제
  8. dk : 커서가 있는 행과 그 앞 행을 삭제

ㅁ. 복사 & 붙여넣기

  1. yw : 커서가 있는 단어를 복사한다.
  2. yy : 커서가 있는 줄을 복사한다.
  3. v : 현재 커서부터 블록을 시작한다.
  4. y : 블록이 되어있는 부분을 복사한다.
  5. p : 현재 커서가있는곳에 붙여 넣는다.

ㅂ. 잘라내기

잘라내기를 이해하기위해서는 vi의 레지스터를 알아야 한다. vi는 총 17개의 레지스터를 가지고 있다. 일단 삭제명령으로 지운 글자들은 순서대로 레지스터로 이동한다. 그러므로 p를 눌러주면 삭제된 글자들이 붙여넣기가 된다.

ㅅ. 레지스터

윈도우에서 말하는 클립보드와 같은것이다.

  1. reg : 레지스터에 있는 내용을 볼 수 있다.
  2. "[n]p : [n]번 레지스터에 있는 값을 현재 커서위치에 붙여넣는다.

o. 블록

  • 블록은 v키를 누른뒤 커서를 이리저리 움직이면 설정된다.
  • 직사각형의 영역을 지정하기 위해서는 "ctrl + v"를 누르면 된다.
  1. ~ : 대소문자 전환
  2. d : 삭제
  3. y : 복사
  4. c : 치환(바꾸기)
  5. < : 앞에 탭 제거
  6. > : 앞에 탭 삽입

선택된 영역에 대하여 ex명령

  1. J : 행을 합침
  2. U : 대문자로 만듦
  3. u : 소문자로 만듦

ㅈ. 되돌리기와 되살리기

  1. u : 되돌리기(undo) 윈도우에서 ctrl + z 와 같다.
  2. ctrl + r 되살리기 지워진것을 다시 살린다.>

ㅊ. 문자열 찾기

  1. /[찾고자하는 문자열] : 아래로가면서 문자열을 찾는다.
  2. ?[찾고자하는 문자열] : 위로 올라가면서 문자열을 찾는다.
    • n : 다음 문자열
    • N : 이전의 문자열

ㅋ. 문자열 치환

  1. %s/old/new/g : 아래로 가면서 문서에 있는 모든 old문자열을 new로 바꿈
  2. s/old/new/ : 아래로 가면서 제일 처음 매칭되는 행의 문자열을 치환
  3. 2,4s/old/new/g : 2행에서 4행사이에 매칭되는 문자열을 치환한다.
  4. -1,+3s/old/new/g : 현재 커서에서 위로1줄 아래로 3줄 범위내의 문자열을 치환
  5. %s/old/new/ : 문서의 전체를 조사하되 한행에서는 처음 만나는 문자열만 치환
  6. %s/old/new/gc : 치환여부를 물어본다.

Vim window split Manual

vim은 정말 알면 알수록 재미있고 신기한 에디터인것 같다. 소개할 window split기능은 여러가지로 재미있게 이용할 수 있을것이다. 아래에서 부터는 ^는 "ctrl 키와 함께 누름" 을 의미한다.

새창으로 파일 열기

:sp filename

커서의 창 이동

  1. ^ww 다음창으로 이동
  2. ^wt 최상위 top으로 이동
  3. ^wb 최하위 botton으로 이동
  4. ^wj 아래창으로 이동
  5. ^wk 위쪽 창으로 이동

창 닫기

  1. ^wc 현재창 닫기
  2. :q 현재창 닫기
  3. ^wo 현재창만 남기고 모두 닫기
  4. :only 현재창만 남기고 모두 닫기
  5. :qa 모든 창을 닫고 종료

창의 크기조절

  1. ^w_ 활성창 크기 최대화
  2. 10^w_ 활성창 크기를 10줄으로
  3. :res10 활성창 크기를 10줄으로
  4. ^w= 모든창 크기를 같게

창의 위치 이동

  1. ^wH 창을 왼쪽으로 이동
  2. ^wL 창을 오른쪽으로 이동
  3. ^wJ 창을 아래로 이동
  4. ^wK 창을 위로 이동

파일으로 바로 이동

커서를 파일 이름위에 대고 ^wf

vim 유용한 기능들, vim useful commands, functions

마우스 사용하기

:set mouse=a

html 으로 소스코드를 색을 입혀서 새 창에 띄운다.

:TOhtml

외부 명령어 실행

:!ls
쉘에서 ls를 친것과 같은 기능을 한다.

현재 커서에 파일 불러서 더하기

:r filename

이런방법도 있음
:r !ls

vim 옵션조정

1. 커서, 백스페이스 설정

windows 에서 vim 사용시 초기화 파일은 자기 홈디렉토리에 "_vimrc" 파일을 생성하여 넣어놓으면 된다. 즉 나의 경우는
"C:\Documents and Settings\김성환" 폴더에 "_vimrc"파일을 넣어두었다.
그렇지 않으면 vim이 설치된 폴더 c:\Program Files\Vim\ 에다가 넣어도 된다.
_vimrc 파일을 설정하는법은 쉽다.

		-----------------------_vimrc 파일의 내용-------------------------
set nu
set autoindent
set backspace=indent,eol,start
set ruler
syntax on
set incsearch
------------------------------------------------------------------

이렇게 넣어두었다.
set nu 는 라인의 번호를 출력하라는 명령이고
set autoindent 는 자동 들여쓰기기능
set backspace=indent,eol,start 는 처음에 vim을 설치했을때 backspace를 눌러도 글자가 지워지지 않고 커서만 이동했는데 이 명령후에는 일반 윈도우의 메모장이나 한글프로그램처럼 동일하게 작동한다.
set ruler 는 우측하단에 현재 커서의 위치를 표시해주게된다.
syntax on 은 자동으로 파일을 인식하여 색을 입혀주는 기능이 활성화 되는기능이다.

2. gVIM에서 color scheme 지정법

가. c:\Program Files\vim\_vimrc 파일을 연다.
나. 아래와 같이 입력

		------------------ _vimrc --------------------
: colorscheme torte
----------------------------------------------

3. gVIM에서 폰트지정

가. c:\Program Files\vim\_vimrc 파일을 연다.
나. 아래와 같이 입력

		------------------ _vimrc --------------------
set guifont = 나눔고딕코딩:h14:cHANGEUL
----------------------------------------------

4. 그외 다른 옵션들

		" ---- language-env DON'T MODIFY THIS LINE!
""" ========================================================
""" 기본적인 설정들
""" ========================================================
set nocompatible " Vim 디폴트 기능들을 사용함
set backspace=2 " 삽입 모드에서 백스페이스를 계속 허용
"set autoindent " 자동 들여쓰기
set cindent " C 언어 자동 들여쓰기
set smartindent " 역시 자동 들여쓰기
"set textwidth=76 " 76번째 칸을 넘어가면 자동으로 줄 바꿈
set nowrapscan " 찾기에서 파일의 맨 끝에 이르면 계속하여 찾지 않음
"set nobackup " 백업파일을 만들지 않음
set novisualbell " 비주얼벨 기능을 사용하지 않음
set nojoinspaces " J 명령어로 줄을 붙일 때 마침표 뒤에 한칸만 띔
set ruler " 상태표시줄에 커서 위치를 보여줌
set tabstop=4 " 간격
set shiftwidth=4 " 자동 들여쓰기 간격
"set keywordprg=edic " K를 눌렀을 때 실행할 명령어
set showcmd " (부분적인) 명령어를 상태라인에 보여줌
set showmatch " 매치되는 괄호의 반대쪽을 보여줌
set ignorecase " 찾기에서 대/소문자를 구별하지 않음
set incsearch " 점진적으로 찾기
set autowrite " :next 나 :make 같은 명령를 입력하면 자동으로 저장
set title " 타이틀바에 현재 편집중인 파일을 표시


""" ========================================================
""" 파일 인코딩을 한국어로 설정
""" ========================================================
if $LANG[0] == 'k' && $LANG[1] == 'o'
set fileencoding=korea
endif


""" ========================================================
""" 터미널에 따른 설정 : Xterm이면 16컬러 사용
""" ========================================================
if &term =~ "xterm-debian" || &term =~ "xterm-xfree86"
set t_Co=16
set t_Sf=^[[3%dm
set t_Sb=^[[4%dm
set t_kb=?
fixdel
endif


""" ========================================================
""" 문법 강조기능 사용
""" ========================================================
if has("syntax")
"syntax on
syntax off
endif


""" ========================================================
""" GUI 모드로 실행할 경우
""" ========================================================
if has("gui_running")
set visualbell " 비주얼벨 기능 사용
set hlsearch " 찾는 단어를 하이라이팅
set guifontset=-*-fixed-medium-r-normal--14-*-75-75-*-70-iso8859-1,
-*-gulim-medium-r-normal--14-140-75-75-*-140-ksc5601.1987-0
endif

스티브 잡스 연설, “Stay Hungry. Stay Foolish" 하처이야기

감사합니다. 먼저 세계 최고의 명문으로 꼽히는 이 곳에서 여러분들의 졸업식에 참석하게 된 것을 영광으로 생각합니다. 사실 저는 대학을 졸업하지 못했습니다. 태어나서 대학교 졸업식을 이렇게 가까이서 보는 것은 처음이네요. 오늘 저는 여러분께 제 인생의 세 가지 이야기를 해볼까 합니다. 그게 전부입니다. 그저 세 가지 이야기일 뿐입니다.

먼저 인생의 점들을 잇는 것(connecting the dots)에 대해서입니다.

전 리드칼리지에 입학한지 6개월 만에 자퇴했습니다. 그래도 일년 반 정도는 도강을 하다가 정말로 그만뒀습니다. 왜 자퇴했을까요?

이야기는 제가 태어나기 전으로 거슬러 올라갑니다. 제 생모는 대학원생인 젊은 미혼모였습니다. 그래서 저를 입양보내기로 결심했던 거지요. 그녀는 제 미래를 위해 대학을 나온 양부모를 원했습니다. 그래서 저는 태어나자마자 변호사 가정에 입양되기로 되었죠. 하지만 제가 태어난 순간에 변호사 부부는 마지막 순간에 여자 아이를 입양하기로 마음을 바꿨습니다. 대기자 명단에 있던 제 양부모들은 한밤중에 이런 전화를 받게 됩니다. "예정에 없던 사내아이가 태어났는데 입양하시겠습니까?" 양부모님은 대답했습니다. "물론이죠."

그런데 양어머니는 대졸도 아니고 양아버지는 고등학교도 안 나와서 친어머니는 입양동의서 쓰기를 거부했습니다. 몇 달 후 양부모님이 저를 대학까지 가르치겠다고 약속한 후에야 친어머니는 입양에 동의했습니다. 이것이 제 인생의 시작이었습니다.

17년 후 저는 대학에 입학했습니다. 그러나 저는 순진하게도 바로 이 곳 스탠퍼드대의 학비와 맞먹는 값비싼 학교를 선택했습니다. 평범한 노동자였던 부모님이 힘들게 모아뒀던 돈이 모두 제 학비로 들어갔습니다. 6개월 후 대학생활은 그만한 가치가 없어보였습니다. 내가 무엇을 하고 싶은지 또 대학교육이 그것에 얼마나 도움이 될 것인가 알 수 없었습니다. 양부모님들이 평생토록 모은 재산을 쏟아붓고 있었습니다. 그래서 모든 것이 잘 될 거라 믿고 자퇴를 결심했습니다. 당시에는 두려웠지만 뒤 돌아 보았을 때 제 인생 최고의 결정 중 하나였던 것 같습니다. 자퇴 후엔 재미없던 필수과목들을 듣는 것은 그만두고 보다 더 흥미 있어 보이는 강의를 들을 수 있었습니다.

그렇다고 꼭 낭만적인 것만은 아니었습니다. 기숙사에 머물 수 없었기 때문에 친구 집 마룻바닥에 자기도 했고 5센트짜리 콜라병을 팔아 끼니를 때우기도 했습니다. 일요일이면 단 한번이라도 제대로 된 음식을 먹기 위해 7마일을 걸어 하레 크리슈나 사원의 예배에 참석하기도 했습니다. 정말 맛있었습니다. 순전히 호기심과 직감만을 믿고 저지른 일들이 훗날 정말 값진 경험이 됐습니다.

예를 하나 들어 드리죠. 당시 리드 칼리지는 미국 최고의 서체 교육을 제공했던 것 같습니다. 학교 곳곳에 붙어있는 포스터와 서랍에 붙어있는 상표들…. 손으로 아름답게 그린 서체 예술이었습니다. 정규과목을 들을 필요가 없으므로 서체 수업을 들었습니다. 그 때 저는 세리프와 산세리프체를, 다른 글씨의 조합 간의 그 여백의 다양함을 무엇이 위대한 글자체의 요소인지에 대해 배웠습니다. 그것은 '과학적'인 방식으로는 도저히 표현해낼 수 없는 아름답고, 유서 깊고, 예술적으로 미묘한 것이어서 전 매료되고 말았습니다. 이 중 어느 하나라도 제 인생에 실질적 도움이 될 것 같지는 않았습니다. 그러나 10년 후 우리가 첫 번째 매킨토시를 구상할 때 그것들은 고스란히 빛을 발했습니다. 우리가 설계한 매킨토시에 그 기능을 모두 집어넣었으니까요. 그것은 아름다운 서체를 가진 최초의 컴퓨터였습니다.

만약 제가 그 서체 수업을 듣지 않았다면 매킨토시의 복수서체  기능이나 자동 자간 맞춤 기능은 없었을 것이고 맥을 따라한 윈도우도 그런 기능이 없었을 것이고 결국 개인용 컴퓨터에는 이런 기능이 탑재될 수 없었을 겁니다. 만약 학교를 자퇴하지 않았다면 서체 수업을 듣지 못했을 것이고 PC에는 오늘날처럼 뛰어난 글씨체가 없었을 것입니다.

물론 제가 대학에 있을 때 그 순간들이 내 인생의 전환점이라는 것을 알아챌 수 없었습니다. 그러나 10년이 지난 지금에서야 모든 것이 분명하게 보입니다. 달리 말하자면, 지금 여러분은 미래를 알 수 없습니다. 다만 현재와 과거의 사건들만을 연관시켜 볼 수 있을 뿐이죠. 그러므로 여러분들은 현재가 미래와 어떻게든 연결된다는 걸 알아야 합니다. 배짱, 운명, 인생, 카르마(업) 등 그 무엇이든 믿음을 가져야만 합니다. 왜냐하면 현재가 미래로 연결된다는 믿음이 여러분의 가슴을 따라 살아갈 자신감을 줄 것이기 때문입니다. 아무리 험한 길이라 하더라도 말입니다. 그리고 그것이 인생의 모든 차이를 빚어냅니다.

두 번 째 이야기는 사랑과 상실에 관한 것입니다. 저는 운 좋게도 인생에서 정말 하고 싶은 일을 일찍 발견했습니다. 워즈(스티브 워즈니악)와 제가 차고에서 애플사를 세운 것은 제가 20세 때 일입니다. 우리는 열심히 일해서 차고에서 2명으로 시작한 애플은 10년 후에 4000명의 종업원을 거느린 2백억 달러짜리 기업이 되었습니다. 우리는 최고의 작품, 매킨토시를 출시했고 전 30세가 되었습니다. 그리고 곧 저는 해고당했습니다. 어떻게 자기 회사에서 해고당할 수 있냐고요?

당시 애플이 점점 성장하면서 저는 저와 함께 회사를 경영할 유능한 경영자를 데려와야겠다고 생각했습니다. 처음 1년 정도는 그런대로 잘 돌아갔습니다. 그런데 언젠가부터 우리의 비전은 서로 어긋나기 시작했고 결국 우리 둘의 사이도 어긋나기 시작했습니다. 이 때 우리 회사의 경영진들은 존 스컬리의 편을 들었고 저는 30살에 쫓겨나야만 했습니다. 그것도 아주 공공연하게 말이죠.

저는 인생의 초점을 잃어버렸고 참담한 심정이었습니다. 전 정말 말 그대로, 몇 개월 동안 아무 것도 할 수가 없었습니다. 선배 벤처 세대의 명예를 실추시킨 것 같았습니다. 제게 넘겨진 배턴을 놓쳐버린 것 같았습니다. 데이비드 패커드(HP의 공동창업자)와 밥 노이스(인텔 공동창업자)를 만나 이렇게까지 실패한 것에 대해 사과하고자 했습니다. 저는 실패의 본보기였고 실리콘 밸리에서 도망치고 싶었습니다.

그러나 제 맘속에는 뭔가가 천천히 다시 일어나기 시작했습니다. 전 여전히 제가 했던 일을 사랑했습니다. 애플에서 겪었던 일들조차도 그런 마음들을 꺾지 못했습니다. 전 해고당했지만 여전히 일에 대한 사랑은 식지 않았습니다. 그래서 전 다시 시작하기로 결심했습니다. 당시에는 몰랐지만 애플에서 해고당한 것은 제 인생 최고의 사건이었습니다. 성공이란 중압감 대신 찾아온 초심자의 가벼움, 불확실함. 내 인생의 최고의 창의력을 발휘하는 시기로 갈 수 있게 됐습니다. 이후 5년 동안 저는 'NeXT'와 'Pixar'를 세우고 지금은 아내가 되어준 그녀와 사랑에 빠졌습니다.

Pixar는 세계 최초의 3D 애니메이션 토이스토리를 시작으로 지금은 세계에서 가장 성공한 애니메이션 제작사가 되었습니다. 세기의 사건으로 평가되는 애플의 NeXT 인수와 저의 애플로 복귀 후 NeXT 시절 개발했던 기술들은 현재 애플의 르네상스의 중추적인 역할을 하고 있습니다. 또한 로렌과 저는 행복한 가정을 꾸리고 있습니다. 애플에서 해고당하지 않았다면 이 많은 일들이 일어나지 않았을 것입니다. 입에 쓴 약이었지만 제게는 필요했던 것 같습니다. 때로는 인생이 배신하더라도 결코 믿음을 잃지 마십시오. 저를 계속 움직이게 했던 힘은 제 일을 사랑하는 것뿐이었습니다.

여러분이 사랑하는 일을 찾아야 합니다. 여러분의 연인이 여러분에게 의미하는 것처럼 일도 마찬가지입니다. 일은 여러분의 삶의 많은 부분을 채울 것이고 여러분이 위대하다고 믿는 그 일을 하는 것만이 진정한 만족을 줄 것입니다. 위업을 달성하는 것은 당신의 일을 사랑하는 것 입니다. 그 일을 아직 찾지 못했다면 계속 찾으세요. 현실에 안주하지 마십시오. 전심을 다해서 찾아내면 그 때는 알게 될 것입니다. 모든 위대한 관계들이 그러한 것처럼 시간이 갈수록 더 나아질 것입니다. 그러므로 계속 추구하십시오. 안주하지 마십시오.

세 번 째는 죽음에 관한 것입니다. 17살 때 이런 경구를 읽은 적이 있습니다. "매일 인생의 마지막 날처럼 산다면 언젠가는 위인이 되어있을 것이다." 이 글에 감명 받은 저는 그 이후로 지난 33년 간 매일 아침 거울을 보면서 제 자신에게 묻곤 했습니다. '오늘이 내 인생의 마지막 날이라면 지금 하려고 하는 일을 할 것인가?" 며칠 연속 'No'라는 답을 얻을 때마다 나는 변화가 필요하다는 걸 알게 됩니다. '곧 죽는다'는 생각은 인생의 결단을 내릴 때마다 가장 중요한 도구였습니다. 모든 외부의 기대. 자부심, 수치스러움과 실패의 두려움은 '죽음' 앞에선 모두 떨어져나가고 오직 진실로 중요한 것들만이 남기 때문입니다. 죽음을 생각하는 것은 무엇을 잃을지도 모른다는 두려움에서 벗어나는 최고의 길입니다. 여러분은 죽을 몸입니다. 그러므로 가슴을 따라 살아야 합니다.

저는 1년 전쯤 암 진단을 받았습니다. 아침 7시 반에 검사를 받았는데 췌장에 악성종양이 보였습니다. 그때까진 췌장이 뭔지도 몰랐죠. 의사들은 거의 치료할 수 없는 종류의 암이라고 했습니다. 또 길어야 3개월에서 6개월 밖에 살 수 없다고 했습니다. 주치의는 집으로 돌아가 신변정리를 하라고 했습니다. 죽음을 준비하라는 뜻이었죠. 그것은 내 아이들에게 10년 동안 해줄 것을 단 몇 달 안에 다 해내야 된다는 말이었고 가족들이 임종할 때 쉬워지도록 매사를 정리하란 말이었고 작별인사를 준비하라는 말이었습니다. 그렇게 시한부 인생을 살고 있었습니다. 어느 날 저녁 조직검사를 받았는데 위장을 지나 장까지 내시경을 넣어 췌장에서 암세포를 채취하는 조직검사였습니다. 저는 마취상태였는데 후에 아내가 말해주길 의사들이 현미경으로 세포를 분석하면서 갑자기 울먹거리기 시작했답니다. 수술로 치료가 가능한 희귀한 종류의 췌장암이었기 때문입니다. 저는 수술을 받았고 감사하게도 지금은 완치되었습니다. 그 때만큼 제가 죽음에 가까이 가 본 적은 없는 것 같습니다. 또한 앞으로도 수 십 년간은 그렇게 가까이 가지 않길 바랍니다. 이런 경험을 해보니 죽음이 때론 유용하단 것을 머리로만 알고 있을 때보다 더 자신 있게 말할 수 있습니다.

아무도 죽길 원하지 않습니다. 천국에 가고 싶다는 사람들조차도 죽어서까지 가고 싶어 하진 않죠. 그리고 여전히 죽음은 우리 모두의 숙명입니다. 아무도 피할 수 없죠. 그리고 그래야만 합니다. 왜냐하면 삶이 만든 최고의 발명이 '죽음'이니까요. 죽음은 삶을 대신하여 변화를 만듭니다. 죽음은 구세대를 대신하도록 신세대에게 길을 터줍니다. 지금 이 순간 여러분이 곧 신세대입니다. 그러나 머지않아서 여러분도 구세대가 되어 사라져 갈 것입니다. 너무 극적으로 들렸다면 죄송하지만 엄연한 사실입니다. 여러분의 시간은 한정되어 있습니다. 따라서 다른 사람의 삶을 사느라 시간을 낭비하지 마십시오. 타인의 생각의 결과물에 불과한 도그마에 빠지지 마십시오. 타인의 견해가 여러분 내면의 목소리를 삼키지 못하게 하세요. 또 가장 중요한 것은 가슴과 영감을 따르는 용기를 내는 것입니다. 이미 여러분의 가슴과 영감은 여러분이 되고자 하는 바를 알고 있습니다. 그 외의 모든 것은 부차적인 것이죠.

제가 어렸을 때 '지구백과'라고 하는 놀라운 책이 있었는데 저희 세대에게는 바이블과 같은 것이었습니다. 여기서 그리 멀지 않은 먼로 파크에 사는 스튜어트 브랜드란 사람이 쓴 책인데 시적 감각으로 살아있는 책이었지요. PC나 전자출판이 존재하기 전인 1960년대 후반이었기 때문에 타자기, 가위, 폴라로이드로 제작된 책이었습니다.

Google이 등장하기 35년 전 책으로 된 Google같은 거였죠. 그 책은 위대한 의지와 아주 간단한 도구만으로 만들어진 역작이었습니다. 스튜어트와 친구들은 몇 번의 개정판을 내놓았고 수명이 다할 때쯤엔 최종판을 내놓았습니다. 그 때가 70년대 중반, 제가 여러분 나이 때였죠. 최종판 뒤쪽 표지에는 이른 아침 시골길 사진이 있었는데 겁 없는 사람이나 히치하이킹 할 수 있는 풍경입니다. 그 사진 밑에는 이런 말이 있었습니다.

'계속 갈망하라 여전히 우직하게'(Stay Hungry Stay Foolish) 그것이 그들의 마지막 작별 인사였습니다. '계속 갈망하라 여전히 우직하게' 제 자신에게도 항상 그러하기를 바랐습니다. 그리고 지금, 새로운 시작을 위해 졸업을 하는 여러분에게 동일한 바람을 가집니다.
"Stay Hungry Stay Foolish!"
대단히 감사합니다.

failover & switchover

 

Failover

불시의 시스템 정지로 primary 서버와 stanby 서버 간의 임무 교대

데이터 손실이 있을 수 있다.

1 시스템이 고장 또는 정기 유지 보수 등의 이유로 이용할 수 없는 상태가 되었을 때, 2 시스템이 즉시 임무를 넘겨 받아 시스템 구성 요서의 기능들이 중단 없이 유지될 수 있는 백업 운전 모드

 

Switch over

계획된 유지 보수로 primary 서버와 stanby 서버 간의 OS 정비 기타 수행을 위해 하는 작업

데이터 손실이 없다.

 

 

개인 적으로 두 작업을 분석한 결과 두 가지 작업은 거의 비슷한 뜻으로 생각된다. Failover 그대로 갑작스런 서버 장애라든지 통신 장애 등의 상황에서 사용자에게 연속적으로 서비스 하기 위한 장애 복구 작업이라 생각이 들고, switch over 서버 점검이나 기타 보수 작업을 위해 잠시 stanby 서버가 primary 서버의 역할을 하는 작업이라 생각한다.


디바이스 드라이버 기초(펌) 윈도우이야기

이상섭·곽태진 (devguru, 마이크로소프트웨어)
2002/09/27
이달부터 3회에 걸쳐 연재할 내용은 특별한 하드웨어 장치 없이도 구현할 수 있는 RAM 디스크다. 이번 회에서는 드라이버에 대한 개념과 프로그래밍 개발환경, 드라이버 프로그래밍에 대한 기초적인 내용을 다루겠다.

장치 드라이버란
장치 드라이버는 ‘장치를 구동하는 프로그램’으로 줄여서 드라이버라고도 부른다. 드라이버는 하드웨어와 소프트웨어(운영체제) 중간에 위치하며, 프로그램 중에서 가장 저수준 레벨에서 처리하는 프로그램으로 볼 수 있다. 일반적으로 운영체제에서 많은 부분이 드라이버로 되어 있다고 봐도 될 것이다.

드라이버는 기본적으로 하드웨어를 제어하고 운영하는 프로그램으로 볼 수 있다. 하드웨어 자원인 메모리, I/O, 인터럽트, DMA를 처리해 사용자가 장치를 사용할 수 있게 한다. 그리고 일반적인 애플리케이션에서는 할 수 없는 일이나 운영체제의 기능을 확장할 때도 드라이버는 필요하다. 예를 들면 바이러스 백신, 보안을 위한 파일 암복호화나 네트워크 패킷 필터링을 하기 위해 드라이버를 이용하기도 한다.

드라이버는 운영체제와 밀접하게 연관되어 작동하기 때문에 운영체제에 따라 다르게 만들어진다. 즉, 윈도우·리눅스·Mac OS마다 드라이버를 새로 만들어야 한다. 윈도우에서도 윈도우 9x 계열(95·98·ME)과 윈도우 NT 계열(NT·2000·XP)은 드라이버를 다르게 만들어야 한다. 따라서 장치를 만드는 하드웨어 업체에서는 실제 소비자를 위해 모든 운영체제에 드라이버를 지원해야 하다보니 개발기간이 길어질 수밖에 없어진다.

윈도우 2000 드라이버의 종류
우리는 이번 연재에서 윈도우 2000용 드라이버를 만든다. 먼저 윈도우 2000에서는 어떤 종류의 드라이버가 있는지 알아보자.


<그림 1>은 윈도우 2000의 드라이버 종류를 나타낸 것이다. 각각의 드라이버가 어떤 것인지 간단히 설명해 보겠다.

◆ 가상 장치 드라이버 : x86 플랫폼에서 하드웨어를 액세스하는 DOS 기반 응용 프로그램을 돌아가게 하는 사용자 모드 요소다. 가상 장치 드라이버(VDD, Virtual Device Driver)는 윈도우 98의 VxD와는 다른 것이니 혼돈하지 말기 바란다.

◆ 커널 모드 드라이버 : 일반적인 장치 드라이버를 총칭한다고 보면 된다.

◆ WDM 드라이버 : 전력 관리와 PnP를 처리하는 커널 모드 드라이버다. 윈도우 98과 2000에서 소스 호환되어 드라이버 개발을 쉽게 할 수 있다. WDM 드라이버는 클래스 드라이버와 미니 드라이버로 나뉜다. 클래스 드라이버는 주로 장치를 클래스라는 종류로 나눠 처리하고, 미니 드라이버는 클래스 드라이버에 하드웨어 장치들마다 다르게 처리할 수 있는 도움을 제공한다.

◆ 비디오 드라이버 : 디스플레이와 프린터를 위한 드라이버다.

◆ 파일 시스템 드라이버 : 일반적인 로컬 하드 디스크나 네트워크로 연결된 파일 시스템을 처리한다.

◆ 레거시 드라이버 : 다른 드라이버의 도움 없이 하드웨어 장치를 직접 제어하는 커널 모드 드라이버다. 이 드라이버는 윈도우 2000에서도 작동하는 예전의 윈도우 NT 드라이버를 포함한다.

이번 연재에서는 윈도우 2000용 장치 드라이버에 중에서 WDM 드라이버에 대한 내용보다는 윈도우 NT 계열의 레거시 드라이버에 대한 내용을 다룰 것이다. WDM 드라이버도 레거시 드라이버 기반 위에 PnP와 전력 관리 부분이 추가된 것이기 때문에 기본적인 내용은 레거시 드라이버만 알아도 이해하는 데 충분하다.

윈도우 2000의 내부구조
장치 드라이버는 운영체제와 밀접하게 연관되어 있다고 말했다. 따라서 운영체제 내부를 알고 있어야 드라이버를 개발하는 데 필요한 내용을 이해하기 더 쉽다. 지금부터 윈도우 2000의 전체적인 구조를 살펴보자. 윈도우 2000의 내부구조를 보면, <그림 2>와 같다.



윈도우의 내부는 크게 커널 모드와 사용자 모드로 나눌 수 있다.

◆ 커널 모드 : 프로세서의 특권 레벨(privileged level)로 프로세서의 모든 명령을 처리할 수 있고, 시스템 자원이나 하드웨어들을 직접 액세스할 수 있다. 장치 드라이버나 운영체제의 코드들이 작동하는 모드이기도 하다.

◆ 사용자 모드 : 일반 응용 프로그램이 동작하는 모드로 커널 모드와는 달리 비특권 레벨(nonprivileged level)이기 때문에 하드웨어나 시스템 자원을 직접 이용할 수 없고 시스템 서비스(API)를 이용해 접근해야 한다. 그렇지 않으면 예외(exception)가 발생해 프로그램이 종료한다.

커널 모드는 다시 Executive, 커널, HAL(Hardware Abstraction Layer) 부분으로 나눌 수 있다.

◆ Executive : 윈도우의 기본이 되는 서비스들을 제공한다. 메모리·프로세스·쓰레드를 관리한다. 특히, Executive를 구성하는 것 중에서 I/O 관리자는 장치 드라이버와 관련해서 많은 부분을 처리한다. 그리고 나머지 구성요소도 드라이버에서 사용하는 서비스를 많이 가지고 있다.

◆ 커널 : 주로 저수준 운영체제 기능을 제공한다. 쓰레드 스케쥴링, 인터럽트와 예외 처리, 멀티 프로세서 동기화 등을 맡는다.

◆ HAL : 하드웨어 추상화 계층으로 플랫폼이 어떤 것이든지 상관없이 운영체계가 작동할 수 있게 한다. 이 계층이 있기 때문에 윈도우 NT 계열 운영체제가 멀티 플랫폼을 지원할 수 있게 된다.


장치 드라이버 개발환경
기본적으로 다음의 개발도구들로 개발한다.

◆ 비주얼 C++ 6.0 : 비주얼 C++의 개발 환경을 이용하기 위해 설치하는 것이 아니라 C 컴파일러나 링커를 이용하기 위해 설치한다.

◆ DDK(Driver Development Kit) : 장치 드라이버 개발에 필수적인 툴로 드라이버 개발에 관련된 라이브러리, 헤더 파일, 예제, 문서(도움말도 포함), 개발에 필요한 프로그램 등이 포함되어 있다. DDK는 OS 버전마다 나오기 때문에 개발하려는 OS에 맞는 DDK를 설치해 개발한다. 우리는 이번 연재에서 윈도우 2000용 드라이버를 만들기 때문에 윈도우 2000 DDK를 설치한다. http://www.microsoft.com/ddk에서 내려받을 수 있다(<화면 1>).



◆ 디버거(WinDbg, Soft-ice) : WinDbg는 DDK에 포함되어 있어서 무료로 사용할 수 있다(http://www.microsoft.com/ddk/debugging/). Soft-ice는 컴퓨웨어 누메가(Compuware Numega)에서 나오는 드라이버스튜디오(DriverStudio)에 포함되어 있다.

◆ 에디터 : 소스를 편집할 에디터가 필요하다.


이제 개발환경이 갖춰졌으면, 실제 드라이버 개발에 필요한 내용으로 들어가자.

 
IRQL이란

IRQL(Interrupt Request Levels)은 윈도우 NT부터 나온 개념으로 단일 CPU에서 동기화를 위한 방법으로 사용하고 있다. IRQL은 0~31까지의 값으로 할당한다. 값이 클수록 높은 레벨을 나타내며 현재 실행되고 있는 플랫폼에 따라서 값들이 정해진다. IRQL은 현재 CPU가 실행되고 있는 레벨이 PASSIVE_LEVEL이라면, 그보다 더 높은 레벨의 IRQL만이 인터럽트될 수 있는 규칙을 가진다. 하드웨어 IRQL은 DIRQL(Device IRQL)로 소프트웨어는 로우 레벨 IRQL(PASSIVE_LEVEL=0, APC_LEVEL=1, DISPATCH_LEVEL=2)로 구분할 수 있다.

소프트웨어 IRQL 레벨이 드라이버와 관련이 있기 때문에 그 부분들에 대해 간단히 알아보면 다음과 같다.

◆ DPC 레벨 : 쓰레드 스케쥴링과 DPC(Deferred Procedure Call) 실행 레벨
◆ APC 레벨 : APC(Asynchronous Procedure Call) 레벨
◆ PASSIVE 레벨 : 일반적인 쓰레드의 실행 레벨
 


장치 드라이버 기초 프로그래밍
커널 모드 드라이버는 응용 프로그램과는 많은 부분에서 다르게 작동한다. 간단하게 살펴보면 드라이버는 운영체제(I/O 관리자)에 의해 호출 받는 루틴의 집합이라고 볼 수 있다. 드라이버의 루틴은 I/O 관리자에 의해 호출 받을 때까지 기다린다. I/O 관리자는 주로 다음과 같은 상황에서 드라이버의 루틴을 호출한다.

◆ 드라이버가 로드될 때(DriverEntry 루틴)
◆ 드라이버가 언로드될 때와 시스템이 셧다운될 때
◆ 응용 프로그램이 I/O 시스템 서비스를 호출했을 때
◆ 공유하는 하드웨어 자원이 드라이버에서 사용될 때
◆ 실제 장치가 동작하는 동안에 다양한 곳에서

앞의 상황 중에서 드라이버가 기본적으로 관심을 가져야 할 상황에 처리해야 할 루틴들을 설명하겠다.

DriverEntry 루틴
모든 프로그램은 제일 처음 실행되는 부분이 있어야 한다. C로 프로그램을 짜면 처음 호출을 받아 실행되는 부분이 main 함수다. 드라이버에서도 main 함수처럼 가장 처음 호출을 받는 부분이 DriverEntry가 된다. DriverEntry는 I/O 관리자에 의해 드라이버 로딩시에 호출된다. DriverEntry 루틴에서 기본적으로 처리해야 할 몇 가지 내용을 알아보자.

① I/O 요청을 처리할 장치 객체 생성
② 처리할 IRP(I/O Request Packet)의 MajorFunction에 해당하는 디스패치 루틴들을 등록
③ 응용 프로그램에서 읽기와 쓰기시에 메모리 전략 선택
④ Win32 응용 프로그램이 드라이버로 접근하기 위해 Win32 서브시스템에 심볼릭 링크 생성

<리스트 1> DriverEntry 루틴
NTSTATUS DriverEntry
( IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
......

   status = IoCreateDevice ( DriverObject, sizeof(MYDE), → ①
     &uniNtNameString, FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject);
......
......
   if ( NT_SUCCESS(status) )
   {
     DriverObject->MajorFunction[IRP_MJ_CREATE] = SAMPLECreate; → ②

     // MajorFunction에 디스패치 루틴 등록
     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=SAMPLEDeviceControl;

     deviceObject->Flags |= DO_BUFFERED_IO; → ③
......
     status = IoCreateSymbolicLink( &uniWin32NameString, → ④
      &uniNtNameString );
   }

   // 인터럽트 사용시 DPC 초기화 및 사용 객체 초기화 및 메모리 사용을 위함
   // 메모리 할당

   return status;
}

언로드 루틴
일반적으로 드라이버는 로드된 후 시스템이 리부트하기 전까지는 남아 있다. 그러나 드라이버를 사용하다가 언로드해야 할 경우엔 언로드 루틴이 필요하며 DriverEntry에서 언로드 루틴을 등록해야 한다. 언로드 루틴은 I/O 관리자가 드라이버를 메모리에서 제거하기 전에 호출된다. 언로드 루틴에서 기본적으로 처리해야 할 몇 가지 내용을 살펴보자.

◆ 심볼릭 링크를 제거한다.
◆ DeviceObject를 제거한다.

VOID Sample1UnLoad( IN PDRIVER_OBJECT DriverObject )

{
......
// 장치 객체와의 심볼릭 링크를 해제한다.
IoDeleteSymbolicLink( &uniWin32NameString );
IoDeleteDevice( DriverObject->DeviceObject );
}

디스패치 루틴
윈도우 2000에서 I/O는 패킷 드리븐 방식으로 이루어진다. I/O 요청이 있을 때 먼저 I/O 관리자가 그 요청에 해당하는 내용을 가지고 IRP를 만든다. I/O 관리자는 응용 프로그램의 요청(읽기, 쓰기 등)을 받았을 때, 그 요청에 맞는 함수 코드로 바꾼다. 그리고 처리할 요청에 대한 드라이버를 선택하고 적절한 드라이버 내의 디스패치 루틴을 호출한다.

디스패치 루틴은 요청한 내용을 보고 알맞은 처리를 한 후 결과를 I/O 관리자에 반환한다. <표 1>은 응용 프로그램에서 호출하는 함수와 대응되는 드라이버 내의 주요 함수 코드들을 보여준다.

 Win32 API 함수 코드 
 CreateFile IRP_MJ_CREATE  
 CloseHandle IRP_MJ_CLOSE
IRP_MJ_CLEANUP
 
 ReadFile IRP_MJ_READ  
 WriteFile IRP_MJ_WRITE  
 DeviceIoControl IRP_MJ_DEVICE_CONTROL  


<표 1> 응용 프로그램에서 호출하는 함수와 대응되는 드라이버 내의 주요 함수 코드

디스패치 루틴에서 기본적으로 처리해야 할 몇 가지 내용은 다음과 같다.

① 드라이버와 관련 있는 IRP 스택 위치에 포인터를 얻기 위해 IoGetCurrentIrpStackLocation 함수를 호출한다.

② I/O 요청에 대한 매개변수들을 가져온다.

③ IoStatus에 반환할 값들을 IRP에 채운다. Status에는 에러 코드를 채우고, Information에는 적절한 값을 채운다. 일반적으로 읽기시에는 응용 프로그램으로 복사할 데이터 크기를 알려준다.

④ I/O 요청에 대한 모든 처리를 끝내고, IRP를 더 이상 사용하지 않기 위해 IoCompleteRequest를 호출한다.

NTSTATUS Sample1Read( IN PDEVICE_OBJECT pDeviceObject, IN PIRP Irp )
{
......
   // 현재 StackLoacation을 구한다.
   pCurrentStack = IoGetCurrentIrpStackLocation( Irp ); → ①

   // 읽을 바이트 수
   usize = pCurrentStack->Parameters.Read.Length; → ②
......
   Irp->IoStatus.Status = STATUS_SUCCESS; → ③
   Irp->IoStatus.Information = usize;

   IoCompleteRequest( Irp, IO_NO_INCREMENT ); → ④
   return STATUS_SUCCESS;
}

앞서 설명한 루틴들은 일반적인 드라이버에서 기본적으로 쓰이는 것들이다. 이외에도 다른 루틴들도 있지만 나머지 루틴들은 다음 기회에 설명하겠다.

장치 드라이버에는 많은 자료구조가 나온다. 장치 드라이버를 공부하는 데 기본적으로 알아야 할 자료들은 DriverObject, DeviceObject, IRP가 있다. 이런 자료구조들을 아는 것이 드라이버를 이해하는 데 밑거름이 되기 때문이다. 그 중에서 이번 회에는 IRP에 대한 내용을 살펴보겠다.


 

 
DriverObject와 DeviceObject의 연관 관계

직렬 드라이버를 예로 들어 DriverObject와 DeviceObect의 연관 관계를 잠시 설명해보겠다. 우리가 직렬 드라이버를 만든다고 가정해 보자. 다들 알다시피 직렬 드라이버는 COM1, COM2, COM3 등의 이름을 가지고 응용 프로그램에서 접근한다. 그러나 실제로 윈도우 2000에는 COM 개수에 따라 직렬 드라이버가 각각 다르게 존재하는 것이 아니다. 하나의 드라이버가 여러 개의 COM 포트를 관리하고 처리한다. 즉, 드라이버에서는 DriverObject가 하나 존재하고, 대신 COM 포트 개수만큼 DeviceObject를 생성하는 것이다. DriverObject가 같다는 것은 IRP 처리 루틴을 같이 공유한다는 뜻이다.

예를 들어 응용 프로그램에서 COM1을 읽는 명령이 내려왔다고 가정해 보자. Win32 서브시스템(API)을 통해 커널 모드로 진입할 것이다. 그리고 명령은 I/O 관리자에 전달될 것이며, I/O 관리자는 해당 명령을 실행할 수 있는 IRP를 생성해 해당 드라이버, 즉 직렬 드라이버에 전달한다. 그리고 DriverObject의 MajorFunction에 해당하는 디스패치 루틴 함수로 분기해 처리하고, 처리 결과를 I/O 관리자에 돌려준다. 여기서 읽기 처리 루틴이 다음과 같다고 하자.

Read(…)
{
......
// 선행 처리 과정
data = InPort ( 0x378 );
......
return data;


이와 같이 코딩되어 있다면 읽기 디스패치 루틴 실행시 0x378(COM1 포트의 주소) 값을 읽어올 것이고 응용 프로그램은 원하던 결과를 얻을 것이다. 그렇다면 응용 프로그램이 COM2 또는 COM3의 값을 읽게 하는 명령이 직렬 드라이버에 내려온다면 어떻게 될까?

그럼 COM 포트들은 같은 DriverObject를 사용하기 때문에 IRP에 관한 루틴을 공유한다. 그럼 앞과 같은 루틴이라면 COM2, COM3 포트의 값을 읽었을 때도 COM1의 값이 얻어질 것이다. 이런 문제를 해결하기 위해 DeviceExtension을 사용한다. DeviceExtension은 DeviceObject만을 위한 메모리 공간이다. 문제가 되던 data = InPort( 0x378 );을 다음과 같이 바꾸면 될 것이다.

data = InPort( DeviceObject->DeviceExtension->Port );

DeviceExtension에는 Port라는 변수가 있을 것이고, 그 변수에는 각 포트 주소가 기억되어 있다. 이와 같이 처리하면, 읽기 디스패치 루틴을 공유해도 각각의 포트 값을 읽어 올 수 있다.
 


IRP
패킷 드리븐 방식으로 I/O 요청을 처리하는 윈도우 NT 계열 운영체제는 처리해야 할 I/O 요청을 IRP를 이용해 처리한다. IRP 자료구조는 다음과 같이 크게 두 부분으로 나눌 수 있다.

◆ 헤더 : I/O 요청에 대한 다양한 정보가 있다. 요청한 I/O의 타입, 크기, 상태 등을 저장한다. buffered I/O를 할 경우에 Associatedirp에 버퍼 포인터, 다이렉트 I/O를 할 경우엔 MdlAddress에 MDL 포인터 관련 정보를 가지고 있다.

◆ I/O 스택 위치 : 함수 코드와 매개변수들을 담고 있다. I/O 스택은 I/O 요청이 어떤 처리를 하느냐에 따라 스택 크기가 결정된다.

예를 들어 플로피 디스크에 파일을 쓰는 I/O 요청이 있으면 적어도 스택 크기는 두 개가 될 것이다. 하나는 파일 시스템 드라이버를 위한 것이고, 다른 하나는 플로피 디스크 드라이버를 위한 것이기 때문이다.

IRP 버퍼 관리
사용자 응용 프로그램들이 읽기나 쓰기 같은 요청을 했을 경우 사용자 쪽의 데이터 버퍼를 처리하는 방법에 따라서 다음과 같이 분류할 수 있다.

◆ Buffered I/O : I/O 관리자는 먼저 호출한 응용 프로그램의 사용자 버퍼와 같은 크기의 Non-paged 풀(pool) 버퍼를 할당한다. 응용 프로그램에서 쓰기시 I/O 관리자는 IRP를 만들 때 할당한 버퍼로 호출한 사용자 버퍼 데이터를 복사한다. 읽기시에 I/O 관리자는 IRP가 끝났을 때 I/O 관리자가 할당한 버퍼에서 사용자 버퍼로 복사한다. 그리고 I/O 관리자가 할당한 버퍼는 해제한다.

◆ 다이렉트 I/O : I/O 관리자는 먼저 사용자 버퍼에 해당하는 물리 메모리 페이지를 잠근다(lock). 그리고 잠근 페이지에 대한 내용을 설명하기 위한 MDL을 만든다. MDL에는 버퍼에 의해 할당한 물리적인 메모리를 지정한다. 그리고 만약 드라이버가 버퍼의 내용을 접근하려고 하면, 시스템 주소 공간으로 버퍼를 맵해 사용할 수 있다.

◆ Neither I/O : I/O 관리자는 어떤 버퍼 관리도 처리하지 않는다. 대신 버퍼 관리는 장치 드라이버의 discreation으로 남겨지고, 장치 드라이버는 I/O 관리자가 다른 버퍼 관리 타입에서 처리하는 과정을 손수 처리할 수 있도록 선택할 수 있다.

꼭 그런 것은 아니지만 일반적으로 드라이버는 호출한 부분의 데이터의 양이 한 페이지(4KB)보다 작으면 Buffered I/O 방식을 사용하고, 그것보다 클 경우엔 다이렉트 I/O 방식을 사용한다. 그리고 파일 시스템 드라이버는 Neither I/O를 주로 사용한다. 데이터를 파일 시스템 캐시에서 사용자의 버퍼로 복사할 때 버퍼 처리에 오버헤드가 없기 때문이다.

드라이버 컴파일과 실행
드라이버를 만들기 위해서는 다음 파일들이 필요하다.

◆ Makefile : DDK에서 사용하는 실제 makefile을 호출하는 내용이 있다. 내용을 수정할 필요는 없다. 일반적으로 DDK 샘플에 있는 파일을 가져다 쓴다.
◆ Sources : 드라이버 컴파일 환경 및 관련 옵션을 정해준다.
◆ 드라이버 소스 파일 : 장치 드라이버 실제 소스
◆ RC(resource) 파일 : 드라이버 버전 정보를 담은 파일

컴파일
이 파일들이 구성됐으면 DDK에 있는 개발환경 Command 창을 띄운다(<화면 2>).



① 빌드 명령을 내린다. 그리고 나면 원하는 폴더에 *.SYS 파일이 생성된다. 그 파일을 \WINNT\System32\Drivers 폴더에 복사한다.

② 드라이버를 로드하려면 레지스트리에 정보를 기록해야 한다. 레지스트리에 직접 기록하든지, 아니면 간단한 *.reg 파일을 생성해 설치해도 된다. 레지스트리에 들어가는 키 위치를 보면 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services 밑에 서브키를 생성한다. 키 이름은 드라이버 이름과 같은 이름으로 생성한다. 예를 들면 드라이버 이름이 SAMPLE.sys라면, SAMPLE이라는 키를 만든다. 키가 만들어 졌으면, 이제 키 밑에 값들을 적는다.

Start = 3
ErrorControl = 1
Type = 1

이 값은 드라이버가 윈도우가 시작되면서 올라오는 게 아니고 우리가 언제라도 올리고 내릴 수 있는, 즉 동적으로 작동하기 위해 정한 값이다.

③ 재부팅한다.

장치 드라이버 실행
Command 창을 하나 띄운다. 그리고 net start sample1을 실행한다. ‘서비스를 시작합니다’라는 메시지가 나오면 드라이버가 제대로 로드됐다는 것을 알 수 있다(<화면 3>). 드라이버를 내리고 싶다면 net stop sample1을 실행한다

 


‘이달의 디스켓’에 수록된 CallApp1 프로그램은 드라이버와 응용 프로그램이 통신하는 예를 보여준다. MFC를 이용해 만들었으며 프로그램이 하는 내용은 스피커포트(0x61)에 데이터를 읽고 쓰기 위해 드라이버와 통신하고, 명령에 따라 드라이버에서는 소리를 나게 하거나 나지 않게 처리한다. 컴파일 방법이나 기타 내용은 ‘이달의 디스켓’ 소스를 참고하기 바란다.


윈도에서 리눅스로 디바이스 제어 응용 이식하기

 

디바이스 제어를 위한 아키텍처 비교

윈도와 리눅스를 비교해 보면 디바이스 제어 방식이 다르다.

윈도 디바이스 제어 아키텍처

윈도에서, I/O 하위 시스템은 사용자 응용이 디바이스 드라이버로 접속하도록 디바이스 드라이버를 지원하는 기반 구조를 정의한다. 디바이스 드라이버는 특정 디바이스 연결을 지원하는 외부 통로인 I/O 인터페이스를 제공한다(그림 1 참조).

그림 1. 윈도 디바이스 제어 아키텍처

디바이스 제어 과정에서, I/O 연산은 IRP(I/O Request Packet)로 캡슐화되어 있다. I/O 관리자는 IRP를 생성해 스택 상위로 보낸다. 그러면 디바이스 드라이버가 I/O 요청을 위한 매개변수가 담긴 IRP를 가리키는 스택 위치를 얻는다. IRP 요구사항에 따르면(create, read, write, devioctl, cleanup, close)) 각 드라이버는 하드웨어 인터페이스를 통해 작업을 수행한다.

리눅스 디바이스 제어 아키텍처

리눅스에서 디바이스 제어 아키텍처는 조금 다르다. 주요 차이점은 일반 파일, 디렉터리, 디바이스, 소켓이 모두 파일이라는 사실이다. 리눅스에서는 모든 것이 파일이다. 디바이스 접근을 위해, 리눅스 커널은 파일 시스템을 통해 디바이스 연산 호출을 디바이스 드라이버로 사상한다. 리눅스에는 I/O 관리자가 없다. 모든 I/O 요청은 처음부터 파일 시스템으로 간다(그림 2 참조).

그림 2. 리눅스 디바이스 제어 아키텍처

 

 

디바이스 파일 이름과 경로 이름 비교

개발 관점에서 바라보면, 디바이스 핸들을 얻는 과정은 디바이스 제어를 위한 선행 조건이다. 하지만 디바이스 제어 아키텍처가 다르므로, 디바이스 핸들을 얻는 방법도 윈도를 사용하느냐 리눅스를 사용하느냐에 따라 이야기가 완전히 다르게 전개된다.

일반적으로 말하자면, 디바이스 핸들은 특정 디바이스 드라이버 이름이 결정한다.

윈도에서는 디바이스 드라이버 파일 이름은 일반 파일 형식과 다르다. 이를 일반적으로 디바이스 경로 이름이라고 하며, \.DeviceName과 같은 고정 형식을 따른다. C/C++ 프로그래밍에서, 이 문자열은 \\.\DeviceName 같이 표현해야 한다. 코드에서는 \\\\.\\DeviceName과 같이 사용한다. DeviceName은 대응하는 디바이스 드라이버 프로그램에서 정의한 디바이스 이름과 똑같아야 한다.

몇몇 디바이스 이름은 마이크로소프트가 정의했으며, 바뀌지 않는다(표 1 목록 참조).

표 1. 윈도에서 디바이스 이름(x = 0, 1, 2 등)

 

디바이스

경로 이름

플로피 드라이브

A: B:

하드 디스크

C: D: E: . . .

물리 드라이브

PhysicalDrivex

CD-ROM, DVD/ROM

CdRomx

테이프 드라이브

Tapex

COM 포트

COMx

예를 들어, C/C++ 프로그래밍에서 \\\\.\\PhysicalDrive1, \\\\.\\CdRom0, \\\\.\\Tape0 같은 디바이스 경로 이름을 사용한다. 상기 목록에 나와 있지 않은 다른 디바이스에 대한 세부 내역은 이 기사 뒷부분에 나오는 참고자료 절을 참조하자.

리눅스에서 디바이스는 파일로 기술하므로 ./dev 디렉터리에서 디바이스 파일 전부를 찾을 수 있다. 이 디렉터리에 있는 디바이스 드라이버는 다음을 포함한다.

  • IDE(Integrated Drive Electronics) 하드 드라이브: /dev/hda, /dev/hdb
  • CD-ROM 드라이브: 몇몇은 IDE, 몇몇은 SCSI 디바이스(/dev/scd0와 같이) 에뮬레이션으로 동작하는 CD-RW(CD Read/Write)
  • 직렬 포트: /dev/ttyS0(COM1), /dev/ttyS1(COM2)
  • 포인팅 디바이스: /dev/input/mice
  • 프린터: /dev/lp0

대다수 공통 디바이스 파일은 위에서 설명한 내용에 따라 찾을 수 있다. 다른 디바이스 파일 이름과 세부 디바이스 정보는 dmesg 명령으로 살펴보기 바란다.

 

주요 시스템 호출 비교

디바이스 제어를 위한 주요 시스템 호출은 open, close, ioctl, read/write 같은 연산을 포함한다. 표 2에 정리한 윈도/리눅스 비교 내용을 살펴보자.

표 2. 디바이스 제어 함수 비교

 

윈도

리눅스

CreateFile

open

CloseHandle

close

DeviceIoControl

ioctl

ReadFile

read

WriteFile

write

이제 가장 일반적인 함수인 create, close, devioctl을 좀 더 깊숙히 파고 들자.

윈도에서 디바이스 여닫기

지금부터는 윈도에서 CreateFileCloseHandle을 설명한다. 디바이스를 열기 위해 함수 CreateFile을 사용한다. 이 함수는 객체 접근을 위해 사용하는 핸들을 반환한다. Listing 1을 살펴보자.

Listing 1. 윈도에서 CreateFile 함수

 

               
HANDLE CreateFile (LPCTSTR lpFileName,          // 디바이스 파일 이름
                                                  (디바이스 경로 이름)
   DWORD dwDesiredAccess,                       // 객체에 대한 접근 모드(읽기, 쓰기, 둘 다)
   DWORD dwShareMode,                           // 객체 공유 모드(읽기, 쓰기, 둘 다)
   LPSECURITY_ATTRIBUTES lpSecurityAttributes,  // 자식 프로세스가 반환된 핸들을 상속받을
                                                // 수 있는지를 판단하는 보안 속성
   DWORD dwCreationDisposition,                 // 파일 존재 유무에 따라 적용할 연산
   DWORD dwFlagsAndAttributes,                  // 파일 속성과 플래그
   HANDLE hTemplateFile);                       // 임시 파일에 대한 핸들

매개변수 lpFileName은 직전에 명세한 디바이스 경로 이름이다. 일반적으로 디바이스를 열려면 dwDesiredAccess를 0이나 GENERIC_READ|GENERIC_WRITE로, dwShareModeFILE_SHARE_READ|FILE_SHARE_WRITE로, dwCreationDispositionOPEN_EXISTING으로, dwFlagsAndAttributeshTemplateFile을 0이나 NULL로 설정한다. 반환되는 핸들은 나중에 디바이스 제어 연산에 사용한다.

디바이스를 닫으려면 함수 CloseHandle을 사용한다. 매개변수 hObject는 디바이스를 열 때 반환된 핸들이다. BOOL WINAPI CloseHandle (HANDLE hObject); 형식을 따른다.

리눅스에서 디바이스 여닫기

리눅스에서 openclose를 설명한다. 직전에 언급했듯이, 디바이스 열기는 일반 파일 열기와 동일하다. Listing 2는 디바이스 핸들을 얻기 위한 open 사용법을 보여준다.

Listing 2. 리눅스에서 open 함수

 

               
int open (const char *pathname,
       int flags,
       mode_t mode);

open 호출이 성공했을 때 반환되는 파일 기술자는 프로세스에서 현재 열리지 않은 가장 낮은 파일 기술자 번호가 될 것이다. 호출에 실패하면, -1을 반환한다. 파일 기술자는 디바이스 핸들로 쓰인다.

매개변수 플래그는 O_RDONLY, O_WRONLY, O_RDWR 중 하나를 포함해야 한다. 나머지 플래그는 옵션이다. 인수 mode는 새 파일을 생성할 경우에 접근 권한을 명세한다.

리눅스에서 함수 close는 파일과 마찬가지로 디바이스를 닫는다. int close(int fd); 형식을 따른다.

윈도에서 DeviceIoControl

윈도에서 DeviceIoControl, 리눅스에서 ioctl은 디바이스 제어를 위해 가장 흔히 사용되는 함수이며 디바이스 접근, 정보 인출, 명령 전송, 자료 교환 같은 작업을 수행한다. Listing 3은 DeviceIoControl을 보여준다.

Listing 3. 윈도에서 DeviceIoControl 함수

 

               
BOOL DeviceIoControl (HANDLE hDevice,
      DWORD dwIoControlCode,
      LPVOID lpInBuffer,
      DWORD nInBufferSize,
      LPVOID lpOutBuffer,
      DWORD nOutBufferSize,
      LPDWORD lpBytesReturned,
      LPOVERLAPPED lpOverlapped);

이 시스템 호출은 제어 코드와 기타 자료를 지정된 디바이스로 보낸다. 대응하는 디바이스 드라이버는 제어 코드인 dwIoControlCode가 지시하는 방식대로 동작한다. 예를 들어, IOCTL_DISK_GET_DRIVE_GEOMETRY를 사용해 물리 드라이버에서 구조 매개변수를 얻을 수 있다(매체 유형, 실린더 개수, 각 실린더에 존재하는 트랙 개수, 각 트랙에 존재하는 섹터 개수 등). 제어 코드 정의, 헤더 파일, 기타 세부 정보는 MSDN 웹 사이트에서 얻을 수 있다(참고자료 절에서 링크를 살펴보자).

입/출력 버퍼가 필요한지 구조체와 크기가 무엇인지는 실제 관련 ioctl 과정과 관련이 있는 디바이스와 연산에 따라 달라진다. 이런 사항은 호출 시에 명세한 dwIoControlCode가 결정한다.

중첩 연산을 위한 포인터를 NULL로 설정하면, DeviceIoControl은 차단 방식(동기식)으로 동작한다. 그렇지 않으면 비동기식으로 동작할 것이다.

리눅스에서 ioctl

리눅스에서는 특정 디바이스에 제어 정보를 전달하는 데 ioctl을 사용한다. int ioctl(int fildes, int request, /* arg */ ...); 형식을 따른다. 첫 번째 매개변수인 fildesopen() 함수가 반환한 열린 파일 기술자이며 특정 디바이스를 가리킨다.

대응하는 DeviceIOControl 시스템 호출과는 달리, ioctl 입력 매개변수 목록은 고정되어 있지 않다. 매개변수 목록은 ioctl에서 매개변수 request가 지정한 내용과 윈도 DeviceIOControl에서 dwIoControlCode와 같이 ioctl이 수행하는 요청 유형에 따라 달라진다. 하지만 이식 과정에서 올바른 request 매개변수를 선택할 때 주의해야 한다. DeviceIOControl에서 dwIoControlCodeioctl에서 request는 동일한 값이 아니며, dwIoControlCode/request를 위한 명시적인 사상 방법도 없기 때문이다. 일반적으로 헤더 파일에서 정의를 살펴보는 방법으로 매개변수 request 값을 선택한다. 모든 제어 코드 정의 내용은 /usr/include/{asm,linux}/*.h에 실려있다.

매개변수 arg는 필요한 작업을 수행하도록 특정 디바이스가 필요한 세부 명령어 정보를 전송하기 위해 남겨져 있다. arg 자료 유형은 제어 요청에 따라 달라진다. 이 인수를 사용해 세부 명령을 보내고 반환 자료를 받을 수 있다.

 

이식 예제

지금부터 윈도에서 리눅스로 이식하는 과정을 예를 들어 살펴본다. 이 예제는 개인용 컴퓨터에 장착된 주 IDE 하드 드라이브에서 SMART 로그를 읽는 방법을 보여준다.

1단계: 디바이스 유형 파악

이미 설명했듯이 리눅스는 각 디바이스를 파일로 취급한다. 첫 단계는 리눅스에서 디바이스 파일 이름을 찾아내는 작업이다. 이 파일 이름을 사용해야 디바이스 제어에 필요한 디바이스 핸들을 얻을 수 있다.

이 예제에서 대상 디바이스는 IDE 하드 드라이브다. 리눅스에서 IDE 하드 드라이브는 /dev/hda, /dev/hdb로 기술한다. 우리가 이식할 예제에서 하드 디스크 디바이스 경로 이름은 \\\\.\\PhysicalDrive0이다. 리눅스에서 대응하는 디바이스 파일 이름은 /dev/hda다.

2단계: 인클루드 헤더 변경

#include 헤더 파일을 리눅스 형식으로 변경해야 한다(표 3 참조).

표 3. #include 헤더 파일

 

윈도

리눅스

#include <windows.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <devioctl.h>

#include <sys/ioctl.h>

#include <ntddscsi.h>

#include <linux/hdreg.h>

windows.h는 디바이스를 여닫는 함수(CreateFileCloseHandle)를 위해 인클루드한다. 여기에 대응하도록 리눅스에서 open()close()를 위해 필요한 헤더 파일을 인클루드해야 한다. 헤더 파일로 sys/types.h, sys/stat.h, and fcntl.h가 있다.

윈도에서 devioctl.h는 함수 DeviceIoControl을 정의하며, 이 파일을 sys/ioctl.h로 변경해 함수 ioctl이 동작하도록 만든다.

(DDK에 들어있는 헤더 파일인) ntddscsi.h는 디바이스 제어를 위한 제어 코드 집합을 정의한다. 이 예제는 IDE 하드 드라이브만 다루고 있기에 리눅스 프로그램에서는 linux/hdreg.h를 추가하기만 하면 끝난다.

다른 상황에서는 필요한 제어 코드 정의가 들어있는 모든 헤더 파일을 인클루드해야 한다. 예를 들어, 하드 드라이브가 아니라 CD-ROM에 접근하려면 linux/cdrom.h을 대신 인클루드한다.

3단계. 함수와 매개변수 수정

이제 코드를 상세히 들여다보자. Listing 4는 명령어 세부 사항을 보여준다.

Listing 4. 명령어 세부 사항

 

               
unsigned char cmdBuff[7];
cmdBuff[0] = SMART_READ_LOG;  // SMART "명령"을 명세하기 위해 쓰인다.
cmdBuff[1] = 1;               // IDE 섹터 계수 레지스터
cmdBuff[2] = 1;               // IDE 섹터 번호 레지스터
cmdBuff[3] = SMART_CYL_LOW;   // IDE 실린더 값(LOW)
cmdBuff[4] = SMART_CYL_HI;    // IDE 실린더 값(HI)
cmdBuff[5] = 0xA0 | (((Dev->Id-1) & 1) * 16); // IDE 드라이브/헤더 레지스터
cmdBuff[6] = SMART_CMD;       // 실제 IDE 명령

명령어 정보는 ATA 명령어 명세에서 가져왔다. 리눅스로 코드를 이식하기 위해 변경이 필요하지 않으므로 추가 분석은 여기서 마친다.

Listing 5에 제시한 코드는 윈도에서 주 하드 드라이브를 연다.

Listing 5. 윈도에서 주 하드 드라이브를 열기

 

               
HANDLE devHandle = CreateFile("\\\\.\\PhysicalDrive0",           // 경로 이름
                             GENERIC_WRITE|GENERIC_READ,         // 접근 모드
                             FILE_SHARE_READ|FILE_SHARE_WRITE,   // 공유 모드
                             NULL,OPEN_EXISTING,0,NULL);

리눅스에서 여닫기에는 매개변수 두 개가 필요하다는 사실을 기억하자(파일 경로 이름과 디바이스에 대한 접근 모드). 직전에 살펴본 원본 코드에서 첫 번째는 /dev/hda이며 두 번째는 O_RDONLY|O_NONBLOCK이다. 변경된 코드는 다음과 같다. HANDLE devHandle = open("/dev/hda", O_RDONLY | O_NONBLOCK); 또한 CloseHandle(devHandle);close(devHandle);로 바꾼다.

특정 디바이스에 접근해 원하는 정보를 가져오기 위한 ioctl 용법이 핵심이다. Listing 6에 원본 윈도 코드를 제시한다.

Listing 6. 윈도에서 DeviceIoControl 원시 코드

 

               
typedef struct _Buffer{
       UCHAR   req[8];              // 제어 코드 이외 세부 명령 정보
       ULONG   DataBufferSize;      // DataBuffer 크기, 여기서는 512
       UCHAR   DataBuffer[512];     // 자료를 담을 버퍼
} Buffer;

Buffer regBuffer;
memcpy(regBuffer.req, cmdBuff, 7);  // req[7]은 장래 사용을 위해 예약되어 있으며 0이다.
regBuffer.DataBufferSize = 512;
unsigned int size = 512+12;         // regBuffer 크기
                                    // req를 위해 8, DataBufferSize를 위해 4, 자료를 위해 512
DWORD bytesRet = 0;                 // 반환되는 바이트 개수
int retval;                         // 반환값

retval = DeviceIoControl(devHandle,
                         IOCTL_IDE_PASS_THROUGH,  // 제어 코드
                         regBuffer, // 입력 버퍼, 세부 명령 크기를 포함한다.
                         size,
                         regBuffer, // 출력 버퍼, 여기서는 동일한 버퍼를 사용한다.
                         size,
                         &bytesRet, NULL);
if (!retval)
        cout<<"DeviceIoControl failed."<<endl;
else
memcpy(data, retBuffer.DataBuffer, 512);

DeviceIoControlioctl보다 많은 매개변수를 요구한다. 디바이스 핸들은 양쪽 플랫폼에서 첫 번째 매개변수이며, 리눅스에서는 open(), 윈도에서는 CreateFile에서 반환되는 값이다. 하지만 윈도에서 dwIoControlCode와 리눅스에서 request는 다른 방식으로 정의되기에, 직전에 설명했듯이 두 매개변수 사이에 사상 관계를 정의하는 고정 규칙은 없다. IOCTL_IDE_PASS_THROUGH는 헤더 파일 ntddscsi.h에 CTL_CODE (IOCTL_SCSI_BASE, 0x040a, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)로 정의되어 있다. /usr/include/linux/hdreg.h 헤더 파일에서 정의를 찾아서 리눅스용 대응 제어 코드인 HDIO_DRIVE_CMD를 선택한다.

추가적으로 특정 과업을 수행하기 위해 디바이스가 세부 명령어 정보를 요구한다. 이 명령어는 프로세스에서 커널로 전달 자료를 저장하는 버퍼에 넣는다. 또한 이 버퍼로 반환 자료가 넘어온다. 명령어를 보내고 필요한 로그 정보를 얻기 위해 동일한 버퍼를 사용한다. 리눅스에서 자료 버퍼를 지정하는 크기 매개변수는 제거할 수 있다. 이 예제에서는 (윈도처럼) 명령어 8바이트를 모두 사용하는 대신 (리눅스에서는) 명령어 4바이트만 사용한다.

대응하는 리눅스 코드(Listing 7)가 상당히 단순해 보이는 이유는 윈도보다 구조체와 함수 인수가 더 간단하기 때문이다.

Listing 7. 리눅스에서 ioctl 원시 코드

 

               
int retval;
unsigned char req[4+512]; // 세부 제어 정보와 반환 자료를 위해 충분한 공간
req[0]= cmdBuff[6];       // 이 예제에 필요한 요구사항을 고려할 때 4바이트만 사용한다.
req[1]= cmdBuff[2];
req[2]= cmdBuff[0];
req[3]= cmdBuff[1];

retval = ioctl(devHandle, HDIO_DRIVE_CMD, &req);
if(ret)
        cout<<"ioctl failed."<<endl;
else
memcpy(data, &req[4], 512);

4단계. 리눅스 환경에서 테스트하기

헤더 파일, 함수, 매개변수를 수정했다면 프로그램은 리눅스에서 동작할 준비가 끝났다. 이제 리눅스 플랫폼에서 컴파일한 다음에 남아있는 구문 오류를 수정한다. 리눅스 배포판과 컴파일 환경에 따라 몇 가지 추가 수정이 필요할지도 모른다.

 

참고자료

교육

 

원본 위치 <http://www.ibm.com/developerworks/kr/library/l-devctrl-migration/index.html>


1 2 3 4 5