Ctrl + w + w -> 분할된 화면 이동

Ctrl + w + = -> 분할된 화면의 크기가 동일하도록 조정

] + c -> 차이점이 있는 부분으로 이동 (Down)

[ + c -> 차이점이 있는 부분으로 이동 (Up)

d + o -> 현재 커서가 위치한 창 쪽으로 반대 창 쪽의 내용을 적용 시키기

d + p -> 현재 커서가 위치한 창 쪽의 내용을 반대 창 쪽으로 적용 시키기

z + o (or space) -> 접혀 있는 부분 열기

z + c -> 차이점 없는 부분 접기

:diffupdate -> 문서 간의 차이점을 다시 비교하도록 하는 명령 (한 쪽 창의 내용을 edit하다 보면 문서 간의 차이점을 나타내는 색깔이 없어지기도 함)

[출처] vimdiff 명령어|작성자 똥꾸용


Shell auto completion

Shell                          csh        ksh       bash
Single option completion       Esc-Esc    Esc-Esc   Tab
Unresolved reference menu      Ctrl-D     Esc =     Tab-Tab

설명 하자면
.profile 에

set -o vi

를 넣어주고

ksh:/tmp/>>ls
bin/      bin5/     include/  lib/      share/
bin2/     ftp/      info/     man/      src/
ksh:/tmp/>>

#man directory로 들어간다고 한다면

ksh:/tmp/>>cd m	#까지 치고 Esc + \ 치면

ksh:/tmp/>>cd man/	#자동 완성 됩니다

#이제 bin5로 드어가볼까요^^
ksh:/tmp/>>cd bi       #여기까지 치고 Esc + =
1) bin/
2) bin2/
3) bin5/
ksh:/tmp/>>cd bi	#여기서 \ 누르면
ksh:/tmp/>>cd bin	#이렇게 되구
ksh:/tmp/>>cd bin5	#마지막 단어를 쳐주면 됩니다.

ksh:/tmp//bin2>>

http://lists.q-linux.com/pipermail/ph-linux-newbie/2003-March/013657.htm...

추가로 BASH에서 위방향 버튼, 아래방향 버튼과 같은 역활을 하는 명령어는

Esc 를 누르면 vi 모드로 들어가고
k,j 를 이용하여 사용하면 되겠습니다.


RS-485
노이즈에 강해 원격 자동화에 많이 사용되는 RS-485 통신의 기본 원리와 회로도, 프로토콜 제작에 대해 알아본다.

RS-232는 그라운드와 데이터 신호의 전압 차이로 상대 기기에 전달된다. 그래서 통신 선로가 길수록 전압 저하로 노이즈에 약하고 고속이나 장거리에는 부적합 한 것이 단점이다. 반면 RS-422(EIA-422)는 고속 및 장거리 노이즈에 상대적으로 강한 통신 방법이다. 232가 불평형 통신 선에 의한 인터페이스라면 485는 평행형 통신 선이며, 쌍방 접속으로 최대 10Mbps로 수 Km까지 전송이 가능하다. 485는 표 1에 나타낸 것처럼 422와 전기적으로 다른 것 이외에는 유사하다.












































































항목 RS-232 RS-423 RS-422 RS-485
모드 타입 불평형 불평형 평형 평형
최대수 Driver(송신) 1 1 1 32
Receive(수신) 1 10 10 32
최대 케이블 길이(m) 15 1200 1200 1200
최대 Bps 20K 100K 10M 10M
Transmit Level Max ±15V ±6V 경우에 따라 다름 -
Min ±5V ±3.6V ±2V ±1.5V
수신감도 ±3V ±0.2V ±0.2V ±0.2V
Load Impedance(Ω) 3K∼7K 450이하 100이하 60이하
Output Current Limit 500mA∼(Vcc 또는 GND) 150mA∼GND 150mA∼GND 150mA∼GND
Driver ZOUT 최소값(Power Off) 300Ω 60kΩ 60kΩ 60kΩ






그림 1은 RS-485 IC의 내부 구조 및 연결 방법을 나타낸 것이다. LTC485와 MAX465는 Pin-to-Pin으로써 전기적 차이는 약간 있으나 기능 핀 구성이 같다. 언급된 제품 이외에 상당수 더 있지만 필자는 LTC485를 통해 실험했다. 그림1의 왼쪽은 IC 핀 구성과 내부 구조를 나타내며, 오른쪽은 IC를 이용해서 원거리를 케이블로 연결했을 경우를 매뉴얼에서 언급한 대로 나타냈다. 선로 상의 A, B 사이에 있는 RI는 종단 저항을 나타내며, 선로 임피던스마다 다르지만 보통 50~150Ω을 병렬로 연결해서 사용한다. 수신 쪽에서 정보를 보냈을 경우 반대편에 부딪혀서 메아리처럼 되돌아오는 신호를 소멸시키기 위해서이다. 빨래 줄처럼 한 개의 신호 선에 여러 대의 장비가 서로 통신할 경우에는, 장비마다 종단 저항을 전부 연결하는 것이 아니고 보내는 장비와 가장 멀리 위치한 받는 장비 한 대씩 두대만 종단 저항을 연결한다. 그래서 485 통신 보드에는 보통 100Ω의 종단 저항에 점프 핀으로 선택적인 연결을 할 수 있도록 해서, 선로 상의 마지막에 해당하는 보드에 종단 저항 설치가 가능하게끔 구성한다.
통신 선로 상에 직렬로 되어 있는 R14~R17의 1Ω 저항값은 선로로 과 전압이 들어오면 내부 기기를 보호하기 위한 보호 저항이다(신뢰성 없음). 선로 상에 이상 전압이 예상되거나 전기적으로 서로 절연(Isolation)하기 위해서는 포토 커플러를 485 드라이브 IC와 TTL 로직 IC 사이에 놓고 설계하기도 한다(그림 2 참조).


그림 2. RS-485와 RS-232 통신 설계












SYN SYN SOH Header STX Text ETX BCC







SYN : Synchronous Idle
SOH : Start Of Heading
STX : Start Of Text
ETX : End Of Text
BCC : Block Check Character
그림 3. 동기식 메시지 형식





2개 이상의 스테이션 사이에 정보를 전송하기 위한 순서 규약이 프로토콜이며, 이에 의해 통신이 제어된다. 통신 규약의 목적은 기기 사이의 연결을 시작하거나 끝내고 송?수신부를 지정하며, 오류 확인, 데이터 전송에 관련된 모든 제어 기능을 관리하는 데 있다. 232에서도 약간 언급했지만 데이터 전송에는 동기식과 비동기식이 있다.
동기식 방법에는 문자 지향적 프로토콜(Character-Oriented Protocol)과 비트 지향적 프로토콜(Bit-Oriented Protocol) 이 있다. 대부분의 통신 코드는 문자 지향적 방법의 변형을 이용하여 비동기식으로 통신하는 알고리즘이다. 데이터 전송을 담당하는 통신 제어 문자를 표 2에 나타냈는데, 이들 문자를 약속된 절차에 따라 서로 주고받으면서 통신하는것이다. 문자 지향적 형식은 헤더(Header) 영역과 텍스트(Text) 영역, 그리고 오류 검사(Error Check) 영역으로 나뉘며 이를 메시지(Message)라 한다.
그림 3은 동기식 문자 지향적 프로토콜의 전형적인 메시지 형식을 나타낸다. SYN은 상호 약속에 의해 송신부와 수신부의 동기를 돕는 것으로 0110100의 특수 문자를 사용하는데, HDLC(High-level Data Link Control)같은 비트 지향적 프로토콜에는 플래그 0111 1110을 사용한다. 필자가 구현한 통신 프로토콜은 시리얼 통신모드1을 사용한 비동기식이고, 메시지 형식은 문자 지향적인 방법을 이용했다. 특히 동기 맞추는 부분은 제외한 자작 프로토콜을 이용했다.
표 2는 서로 떨어져 있는 보드 사이를 통신하기 위해 미리 정의한(현재 통용되고 있음) 문자 기호를 나타내며 16진 Hex값이 이에 해당한다. 여기서부터는 이런 기호를 이용해서 장비와 장비간에 서로 통신하는 방법과 프로토콜을 정의하고자 한다. 궁극적인 목적은 넓은 범위에서 사각지대 없이 여러 대의 기기가 원활하게 서로 통신하며 정보를 주고받는 것이다. 데이터 수수를 위해 가장 먼저 해야 할 일은 신뢰성에 기반을 둔 통신이다. Mast, Slave의 개념을 쉽게 이해하기 위해서 이제부터는 대장/부하 개념으로 설명하겠다.
대장보드와 부하보드 상호간에는 비동기 통신, Polling, Selecting, Response 프로토콜을 사용한다. 통신 속도는 변경할 수 있지만, 기본적으로 19,200Bps에 고정되어 있다. 데이터 길이는 8비트, Stop 비트는 1, 패리티 비트는 없으며(8N1) 16진(Hex)과 ASCII 코드 또는 BCD 코드를 사용한다.
각 기기에는 고유의 UAD(Unit Address)가 Dip S/W에 의해 설정돼 있다. 그래서 사람 이름을 부르는 것과 같이 상대방의 UAD를 이용하여 통신하게 된다. 통신은 정해진 전문 형식에 의해 이루어진다. 통신할 데이터가 없더라도 일정한 시간이 되면 대장보드가 부하보드의 어드레스를 불러 주는데 이를 정기적 Polling라 한다(이하 POL). POL은 회선이 끊어졌는지 확인하며 기기가 정상적으로 동작하는지 체크하는 데도 이용된다. Selecting은(이하 SEL) 대장보드가 부하보드를 UAD로 불러 데이터를 송신할 때 이용되며, Response(이하 RSP)는 POL에서 해당 UAD가 응답할 내용을 대장보드에게 보낼 때 사용된다.




































































기호 Hex 의미 기능
STX 02 Start Of Text 블록의 시작
ETX 03 End Of Text 블록의 끝
EOT 04 End Of Transmission 전송의 끝
INQ 05 Inquiry 명령 실행응답 문의
ACK 06 Acknowledge 인식했음을 알림
DLE 10 Data Link Escape 전송 제어용 확장 코드
NACK 15 Negative Acknowledge 잘못 인식했음을 알림
ETB 17 End Of Transmission Black 전송 블록의 끝
POL 70 Polling 대장보드가 보냄
SEL 73 Selecting 부하보드 지정 정보 전달
UAD 0~F Unit Address 기기 번호
BCC 0~FF Exclusive OR Sum 블록 검사


















HDLC



HDLC(High-level Data Link Control)는 정보 전송을 위해 국제 표준위원회 ISO에서 개발한 비트 중심의 데이터 통신 프로 토콜이다. HDLC는 OSI 모형을 사용하는 컴퓨터와 컴퓨터 사이 의 데이터 통신에서 데이터 링크 계층에 대한 비트 단위의 동기 전송 방식을 정의하고 있다. 즉 데이터 프레이밍, 흐름 제어, 에 러 검출 정정 용도로 쓰이는 비트 기반 데이터 링크 계층의 동기 식 프로토콜인 것이다. HDLC에서의 순서와 정보는 Flag(8bit), Address(8bit), 제어(8bit), 데이터 정보(임의), 프레임 오류 점 검용(16bit), Flag(8bit)과 같은 프레임 형식으로 데이터가 저장 된다. HDLC는 IBM의 SDLC를 기반으로 개발됐고 ISO에 의해 표준화되었다. 표준 프로토콜 서브셋으로 구현된 HDLC의 예를 보면, 이더넷에 사용되는 802.2와 X.25에 사용되는 LAP 등이 대표적이다.
 
 
출처 : 네이버






통신 전문 형식의 예를 도식화하면 그림 4와 같다. 주기적으로 POL이 행해져서 부하보드가 송출할 정보가 없고 그냥 대답만 할 경우에는 CK(Acknowledge)만 보낸다. 보낼 전문이 있으면 RSP로 구성된 데이터 블록을 대장보드에게 보내면 된다.
한편 대장보드가 부하보드에게 정보를 보내고자 할 경우에는 부하보드의 UAD를 SEL 전문에 넣고 정보를 보낸다. 사실 POL과 SEL은 1:n의 일대 다 통신일 경우 대장보드를 포함해 RS-485 선로에 있는 부하보드 전부가 정보를 받고 해석한다. 그 후 자신의 UAD를 받았다면 해당 기기는 내부적으로 명령을 처리하고 응답한다. 응답 정보를 보낼 경우에는 RSP를 보내거나, 아니면 ACK 또는 NAK(Negative Acknowledge)를 보내면 된다. 홈 가드 보드의 통신에서 잘 받았거나 긍정 응답일 경우는 ACK, 정상적으로 통신이 수행되지 않았거나 부정 응답은 NAK를 보내어 확실한 통신이 이루어지도록 한다.
쉽게 말하자면 여러 사람들이 모여 서로 대화하는 것과 유사하다. 세 사람이 대화할 경우를 생각해보자. 서로 동시에 말하지 않기 위해 한 사람이 누군가와 대화하고 있다면 나머지 한 사람은 잠시 기다려 준다. 즉 자신의 이름이 아니면 응답하지 않고 불려진 상대의 대화가 끝날 때까지 기다리는 것이다. 하지만 기다리는 사람이 심심하지 않도록 가끔 이름을 불러 주는 에티켓을 발휘할 수 있다. 이러한 쌍방 간의 약속이 바로 프로토콜인 셈이다. 이는 기존 통신 시스템에서 사용하는 방법을 모방하여 만든 것이다.
좀더 높은 신뢰성과 암호화 기법이 필요한 독자는 스스로 개발하던지 또는 국제적으로 공인된 방법을 사용하기 바란다.
그림 4를 통해 다시 한 번 프로토콜을 설명해보겠다. 통신할 때는 3가지 모드가 있는데 POL, SEL, RSP가 그것이다. POL은 다시 정기적인 POL과 필요에 의해 사용하는 비정기적인 POL로 나눈다. 대장보드에서 부하보드에게 통신이 정상적으로 되는지, 선로에는 이상이 없는지, 그리고 부하보드가 응답할 것이 있는지 등을 POL한다. POL을 받은 부하보드는 보낼 정보가 있으면 RSP로 응답하고, 정보가 없다면 ACK를 보내 통신이 정상적으로 행해지고 있음을 대장보드에게 통보한다.


















RS-485 통신의 Echo, Non Echo 모드



멀티 포인트 버스를 사용하는 시스템은 하나의 버스에 여러 개의 마스터가 연결되어 있다. 이 때문에 하나의 마스터가 다른 마스터와 통신할 경우 반드시 출력 개폐를 해야 한다. 이것은 RS-422의 멀티 드롭 모드와 동일한 개념이다. 하지만 동시에 여러 개의 마스터가 출력하여 데이터가 충돌하는 현상이 발생하 므로 이러한 문제는 S/W에 의해 해결되어야 한다. 그러한 방법 중 하나가 자기가 보낸 정보를 자기가 받아보면 서 충돌 여부를 확인하는 것인데 이를 RS-485 Echo 모드라 부 른다. 예를 들자면 어떤 마스터가 멀티 포인트 버스에‘ABC’라 는 데이터를 보내면 이 데이터가 자동적으로 되돌아온다 (Echo). 이를 읽어와서‘ABC’여부를 확인한 후 동일한 정보가 아니거나 들어온 데이터 수가 틀리면 충돌한 것으로 보고 적절 한 시간 지연을 거친다. 그리고 다시 출력시켜 정확한 값이 되돌 아 올 때까지 되풀이하면 된다. 이 때 마스터의 RxD 신호선은 항상 멀티 포인트 버스에 접속돼 있어 자신의 데이터뿐만 아니 라 다른 마스터가 보내는 데이터도 받을 수 있다. 이러한 데이터 가 자신에게 필요한 정보인지 판단하는 것은 S/W에 의해 결정 된다. 즉 RS-485 Echo 모드에서 마스터의 RxD 신호선은 항상 멀티 포인트 버스에 접속돼 있어야 하는 반면, TxD 신호선은 데 이터를 출력할 때만 접속시켜야 하고 나머지는 반드시 단락시켜 야 한다. 만약 단락시키지 않으면 RS-422의 멀티 드롭 모드와 같이, 다른 마스터가 데이터를 보내도 충돌이 발생하여 올바른 송?수신이 되지 않는다. RS-485 Non Echo 모드는 RS-485 Echo 모드에서 자기가 보낸 데이터가 자기 자신에게 되돌아오는 기능을 없앤 것이다. 이는 TxD 신호선을 멀티 포인트 버스에 접속시키면 그 즉시 RxD 신호선이 멀티 포인트 버스에서 단락된다. 또 TxD 신호선 을 멀티 포인트 버스에서 단락시키면 바로 RxD 신호선이 멀티 포인트 버스에 접속하게 된다.
 
 
출처 : 시스템베이스





한편 대장보드가 특정 부하보드에게 직접 정보를 전달하는 SEL이 있다. SEL을 받고 부하보드가 정상적으로 명령을 처리하면 ACK를 보내고 송수신 에러가 발생하면 NACK를 보낸다. ACK는 보통 정보를 잘 받았을 경우에 사용하고 NACK는 SUM 체크나 수신 오류를 범했을 경우 사용한다.
도식화된 그림 5를 보면 3개 모드 중에서 맨 왼쪽은 정기적인 POL이다. PC에 연결되어 있는 대장보드가 부하보드에게 정기적으로 주소와 함께 정보를 보낸다. 이 때 RS-485 통신선에 연결된 기기들은 일단 분석한 후 자신의 어드레스와 받는 UAD가 동일한지 판단한다. 동일하다면 보낼 데이터 없이 대답만 할 경우 ACK를 보내고, 그림 5의 두 번째 상황처럼 응답 데이터가 있을 때는 RSP를 보내 통신한다. 전문 형식을 정상적으로 받으면 ACK를 보낸다. 반면 한번 더 송신을 요청하거나 취소할 때 또는 잘못 받았을 경우에는 NACK를 보내어 재 송신이 이루어질 수 있도록 한다. 그리고 도식화되어 있지는 않지만 정기적인 POL이나 정보를 보냈는데 일정 시간 동안 ACK 또는 NACK 응답이 없으면, 몇 번 더 재 송신한 후에 회선이 단선된 것으로 판단한다.
통신 프로토콜을 제작하는 것은 생각보다 상당히 복잡하고 여러 가지 실험을 통해 검증되어야 한다. 그러므로 LoopBack Test 프로그램에 대해 알아보면서 프로토콜을 이해하도록 하자. Loop Back Test는 통신 동작을 확인하기 위해 간단하게 이용하는 방법으로, 송신한 것을 자신이 수신하여 정확히 동작하고 있는지 파악할 때 사용한다. Loop Back Test Tool은 그림 6과 같이 제작한다. 그림 6에서 M은 Male(수컷), F는 Female(암컷)이고 10Ω은 의미 없는 보호 저항이다. 여기서 RX_H와 TX_H를 극성에 맞지 않게 잘못 연결하면 485 드라이버 칩이 내부적으로 파괴된다는 점을 기억하자.
Loop Back Test Tool의 R1과 R2에 오실로스코프 프로브와 GND 단자를 놓고 파형을 측정하면 그림 7의 하드카피 RS-485 파형을 얻을 수 있다.
이 때 DB 9핀 소켓의 1번에 스코프의 측정 단자 프로브를, 6번에는 스코프의 그라운드를 연결해야 한다. 처음 통신이 없을 때는 Stop을 유지하고 있다가 통신이 시작되면 Start(Low), 그리고 대문자‘Z’의 ASCII Code인 0x5A(0101 1010) LSB부터 전송되어 MSB를 마친 후 Stop(High) 비트, 그리고 다시 한 번 Stop(문헌에서는 State라 한다) 비트를 유지한다. 프로그램에서 통신모드1, 데이터 8비트, 패리티 없고 Stop 비트 1(8N1)로 설정해 놓았기 때문이다.
통신속도는 한 비트의 시간이 52㎲이기 때문에 역수를 취하면 19230.76≒19200Bps로 데이터가 송신된다는 것을 알 수 있다. 그리고 보면 기계는 정말 거짓말을 하지 않는다. 이 때문에 필자는 엔지니어로서, 인간의 명령에 따라 충실히 동작하는 기계를 올바르게 사용해야겠다는 의무감을 느끼기도 한다.
Source는 RS-485 통신의 기본 구조와 Loop Back Test 프로그램으로, 숫자‘2’를 누르면 영어 대문자‘Z’를 TXH 및 TXL로 485 통신 선로에 보내고 RXH 및 RXL로 다시 받는 내용이다. 통신 결과는 RS-232를 통해 하이퍼터미널에 프린트했다. 그러나 보통의 MPU는 UART 포트가 하나밖에 없어 232 통신과 485 통신을 프로그램에 의해서 이쪽 저쪽으로 스위칭 해야 한다. 그러므로 데이터를 잃어버리면 안 되는 통신 시스템을 요구하는 구조의 포트 전환은 굉장한 기술을 요하거나 아니면 적합하지 않다고 본다.










Source. 타깃 보드 커뮤니케이션 테스트 프로그램
/*
목적 : Target Board Communication Test
File name: com_test.c
Copyright Conan Kim All Rights Reserved.
*/
#include
#include // 중간생략
#include "C:₩hardware₩work₩gu_5002.h" // DS5002FP SFR 정의
#include "C:₩hardware₩work₩gu_type.h" // #include 정의
#include "C:₩hardware₩work₩gu_hard.h" // Memory Map
#define uchar unsigned char
#define uint unsigned int
/*******************/
/* function prototype */
/*******************/
// Interrupt initial
void init_serial(void); // 19200Bps setting
// Communication
void init_qbuffer(void); // Ring Buffer 초기화
bit check_pop(void); // RS-232로부터 입력 검사
// Utility
void title_print(void);
uchar xtoa_h(uchar _byteh); // Hex to ASCII Converter 상위 Nibble
uchar xtoa_l(uchar _bytel); // Hex to ASCII Converter 하위 Nibble
void putstring(char* star); // 문자열 RS-232 프린터
void putbyte(char c); // 문자(文字) 하나 출력
/*******************/
/* Variables Declarations */
/*******************/
uchar xdata get_char; // Q 버퍼에서 읽어온 입력 값
struct ring xdata q; // For Communication q-buffer
/******************/
/* Main Function */
/******************/
void main(void){
uchar xdata i;
EA=0; // Disable Interrupt
init_serial(); // 19200Bps Setting
init_qbuffer(); // Ring Buffer Setup q.rp=q.wp=0
EA=1; // Enable Interrupt
wdt(); // Watchdog Timer
switch(485){ // 참이므로 무한 루프 형성
case(485): // Endless Loop
title_print(); // 화면 프린터
putstring("₩nCMD>"); // 다음 명령 대기상태 Prompt
while(7){ // True 무한 Loop, for(;;)와 동일
wdt(); // Watchdog Timer
if(check_pop() && get_char==0x0D) // CR(Enter) Check
putstring("₩nCMD>"); // 엔터키이면 Prompt
switch(get_char){
case('h'):case('H'):case('?'): // Help Display
putstring("485_Communication(1), 485_Loop_back_Test(2)₩nCMD>");
break; // 절대 빠트리지 말자........ 필자 무지 고생했음
case('1'):case('!'): // 1일 경우, 실제 통신 프로그램 위치
putstring("Now! 485_Communication ₩nCMD>");
break;
case('2'):case('@'): // 2일 경우
putstring("Now! 485_Loop_Back_Test₩nCMD>");
putstring("Send ASCII Code 'Z' to RS-485₩nCMD>");
putstring("Receive(5) Char 'Z' to RS-485₩nCMD>");
ON_485(); // 485 Port On
// GU_TYPE.H에 다음과 같이 정의되어 있음
// #define ON_485() CON1=0;CON2=1
// #define OFF_485() CON1=1;CON2=1
// #define ON_MODEM() CON1=1;CON2=0
// #define OFF_MODEM() CON1=1;CON2=1
for(i=1;i<=5;i++){ // 5회 실행
wdt();
putbyte('Z'); // send 485 Port Z=0x5A=0101 1010
if(check_pop()){ // 485를 통해 수신 데이터 확인
OFF_485(); // 485 Port off, 수신 데이터 있음
putbyte(get_char); // 하이퍼터미널에 입력값 프린터
putstring("("); // 괄호
putbyte(xtoa_l(i)); // i 값을 ASCII로 변환
putstring(")₩nCMD>");
ON_485(); // 485 Port On
} // end if
}// end for(i∼
OFF_485(); // 485 Port Off, RS-232로 변환
putstring("("); // 괄호
putbyte(xtoa_l(i)); // ASCII로 변환
putstring(")₩nCMD>");
putstring("END RS-485 Communication ASCII Code 'Z' Send Test₩nCMD>");
putstring("If You Read Just Number '(6)' is Fail₩nCMD>");
break;
case('5'):case('%'): // this mode Output ASCII Code 'A'
putstring("Now! Modem Test₩nCMD>");
break;
default:break;
} // end switch(get_char)
} // end while(7)
break;
default: wdt(); break; // 필요하지 않음
} // end switch(485)
} // end main
// ========================================================
// ======================== Utility Routine ===============
// ========================================================
bit check_pop(void){
if(q.wp!=q.rp){
get_char=(q.buffer[q.rp]); // pop get char by check flag
q.rp++; // Increment Read Point
if(q.rp>QSIZE) q.rp=0; // 링 구조를 가지고 있음
return (1);
}
get_char=NULL; // 입력이 없으면 공(空), 여기서는 Zero
return (0);
}
void putbyte(uchar c){ // TI=1이 될 때까지 실행
SBUF = c; // 전송 인터럽트가 발생할 때 까지 대기
while(!TI); // 괄호가 참이면 계속 실행, 즉 TI=0이면 대기
TI=0; // (!TI)=0이 거짓이 되려면 TI=1
}
void putstring(char *str){ // Point 문자열 출력
unsigned int xdata i;
for(i=0;str[i]!='₩0';i++) {
if(str[i]=='₩n') {
putbyte(0x0a); // 0x0a = Line Feed(줄 바꿈)
putbyte(0x0d); // 0x0d = Carriage Return(커서를 좌측 끝)
}
else putbyte(str[i]);
}
}
uchar xtoa_h(uchar _byteh){ // 16진수를 ASCII로 변환 상위 Nibble
uchar nibble = _byteh >> 4;
return ((nibble > 9)? nibble + 'A' - 10 : nibble + '0');
}
uchar xtoa_l(uchar _bytel){ // 16진수를 ASCII로 변환 하위 Nibble
uchar nibble = _bytel & 0x0F;
return ((nibble > 9)? nibble + 'A' - 10 : nibble + '0');
}
void title_print(void){ // 사용자 타이틀 프린터
putstring("₩n******************************");
putstring("₩n* Target Board of Home Guard Communication Test *");
putstring("₩n******************************");
putstring("₩n₩n");
putstring("CMD>Push '?'or 'H' key then Commands display₩nCMD>");
}
// ========================================================
// ======================== Initial Routine ===============
// ========================================================
void init_qbuffer(void){ // 링 버퍼 초기화 루틴
q.rp=0; // Read Point value = 0
q.wp=0; // write Point value = 0
}
void init_serial(void){ // 19200
ES =1; // Enable Serial Interrupt
PS=1; // Serial Port Priority High
SCON = 0x50; // Mode=1, REN=1
PCON |= 0x80; // SMOD=1
TMOD |= 0x20; // Timer Mode=2, 8bit Auto_reload Timer
TH1 = 0xFC; // Time Value
TR1 = 1; // Run Timer 1
TI = 0; // Initial TI & RI
RI = 0; // SCON에서 이미 정의되어 있음
}
// ========================================================
// ======================== Interrupt ==================
// ========================================================
void timer0_isr(void) interrupt 1 using 1{ // auto reload 방식
timer.tick++;
if(timer.tick==0xFFFF) timer.tick=0;
}
void push_q(void) interrupt 4 using 1{ // UART 인터럽트
if(RI){ // RI=1일 때 실행
RI=0; // Empty RI(다음 인터럽트 받을 준비)
q.buffer[q.wp]=SBUF; // Write Point로 옮김
q.wp++; // Increment Write Point
if(q.wp>QSIZE) q.wp=0; // Write Point가 넘치지 않게(Ring Buffer)
} // End if(RI)
}
/*********************************/




Source를 컴파일 한 후 프로그램을 로딩, 윈도우의 하이퍼 터미널과 실험용 보드인 홈 가드 보드를 연결한다. 정상적으로 Loop Back Test가 되었다면 그림 8과 같은 결과를 얻을 수 있다.
만약 P2나 P3의 DB 9핀 소켓에 아무 것도 연결하지 않고 프로그램을 실행시키면 Z(1)~Z(5)가 출력되지 않고‘(6)’만 화면에 나타난다.
바탕화면에 하드 카피된 TTY 프로그램은 PC 회사에서 COM 포트를 테스트하기 위해 이용하는 실행 파일로 Microsoft Corp.에서 만든 것이다. 윈도우에서 제공되는 하이퍼터미널을 사용해도 된다.
자체적으로 RS-485의 TX를 통해 보낸 정보를 RX로 받는 것까지 됐다면 다시 프로토콜로 돌아오자. 앞에서 프로토콜을 필자가 직접 만들었다고 했다. 그런데‘프로토콜’의 사전적 의미를 찾아보니 외교상으로는 의전(儀典)이나 의례(儀禮)를 나타내며, 전산에서는 컴퓨터 상호간 또는 컴퓨터와 단말기 사이의 데이터를 송?수신하는데 필요한 통신 규약으로 쓰여 있다. 결국 프로토콜은 일상 생활에서도 사용되고 있는 것이다.
특히 많이 사용되는 전화망 통신의 예를 들어보자. 첫째, 음의 변조방식과 전화번호가 있다. 둘째, 통화하고자 하는 상대가 응답이 가능한지 이름이나 교환 및 내선 번호를 통해 확인한다. 셋째, 상대방과 접속이 되어서도 언어와 Yes or No의 대답 등 일정한 도덕 규약이 있다. 이렇게 프로토콜은 통신 문화이며 정해진 예절이다.
자작한 통신 프로토콜은 다음과 같은 사항을 생각하지 않고 만든 점에 유의하도록 하자. 우선 데이터 투명도를 DLE(Data Link Escape)로 생각하지 않았다. 또한 선로 상에서의 통신 속도를 자동으로 맞추지 못하며, 통신 상에서 각종 대기 시간 및 정기적 POL 시간 등을 실험을 통해 정확히 고려하지 않았다.
나머지는 차지하고라도 데이터 투명도는 꼭 짚어보고 넘어가야 하겠다. 초기 데이터 통신에는 ASCII 문자만으로 통신을 하다가 점점 정보 의미가 확대됨에 따라 2진 정보를 전송할 필요가 있게 됐다. 그러나 2진 정보는 문자 지향적 프로토콜에서 수신부에 의해 잘못 해석될 우려가 있다. 이를 막기 위한 작업을 투명(Transparent) 데이터라고 부른다.
데이터 투명도는 문자 지향적 프로토콜에서 각 통신 제어 문자 이전에 DLE(Data Link Escape)라는 문자를 삽입하므로 이루어진다. 그래서 텍스트를 보낼 때 DLE로 시작해서 DLE로 끝나는 것이다. 만약 DLE(0x10h=0001 0000)와 똑같은 2진 정보가 텍스트 안에 존재하고 있다면, DLE의 혼선을 막기 위해 다시 한번 DLE를 보내는데 수신 시에도 두 개를 받았다면 하나를 버린다. 그러나 이런 방법의 데이터 투명도는 프로그램 하기도 까다롭고 특히 효율이 떨어진다는 단점이 있다.
다른 방법으로는 텍스트 수를 세어 보내는 쪽에서 주고, 받는 쪽에서는 바이트 수를 세어 받는 방법도 있다. 보통 비트 지향적 프로토콜에서 많이 사용한다. 표 3은 홈 가드에서 사용하는 프로토콜의 정해진 명령어를 보여준다.
구현된 타깃 보드는 PC와 연결되어 있는 하나의 대장보드와 이곳에 연결되어 있는 여러 대의 부하보드로 구성돼 있어 서로 정해진 프로토콜에 의해 통신하면서 정보를 주고받는다. 장비가 여러 대의 기기와 접속했을 경우 어떤 모양새를 도플러지(Topology)라고 하는데, 기본적인 방법에는 Point to Point와 멀티 드롭 방식이 있다.




표 3. 프로토콜 제작 예











































































코드 명칭 설명 비고
31 초기 설정 홈 가드 초기화 대장보드가 부하보드에 보낼 때 사용
UAD가 00일 경우에는 부하보드 전부 해당
32 날짜 설정 RTC 년, 월, 일 설정
33 시간 설정 RTC 시, 분, 초 설정
34 알람 설정 RTC 알람 설정
35 모뎀 설정 모뎀 초기화 설정
36 Reset Reset
39 주소 보고 명령 기기의 DIP S/W값 요청
3A 기기 상태 보고 가드 현재 상태 보고 요청
3B 기기 제어 명령 부하보드의 기타 제어 요청
- - - -
51 가드 상태 통보 - -
59 기기 주소 통보 기기의 DIP S/W 값 -
5A 기기 상태 통보 가드 현재 상태 초음파, 온도, 방범 스위치
5B 기기 제어 통보 기기 제어 -
5F 기기 고장 통보 부하보드 고장 보드 고장 상태

















Fan Out



디지털 집적회로(IC)의 외부에 접속되는 부하에 대하여 회로 의 출력 드라이브 능력을 나타낸다. 팬 아웃이 크면 드라이브 능 력이 크다고 할 수 있다. TTL IC에서는 보통 10∼20개 정도의 팬 아웃을 가져, 동일 선상에 접속되는 부하를 능력 이상으로 연 결하면 드라이브 능력이 떨어진다. 팬 아웃 수치로 칩 세기를 평 가할 수도 있다.





P_to_P 방식은 초등학생들이 자연시간에 실험해보았듯이 컵에 실을 꿰어 일대 일로 통신하는 것이다. 멀티 드롭은 P_to_P를 계속 확장한 것으로, 흡사 빨랫줄에 여러 개의 세탁물이 집게로 고정된 모양을 하고 있는 방법이다.
보드의 연결도 멀티 드롭으로 구성되어 있어 통신하고자 하는 정보를 UAD(Unit Address)와 함께 선로에 놓으면 일단 전부 통신 선로에 연결되어 있는 대장 및 부하보드가 정보를 취득한 후 자신의 UAD가 아니면 버리는 구조로 되어 있다. 특히 대장보드가 UAD에 0X00을 넣어 선로에 놓으면 선로에 있는 모든 부하보드는 전부 자신의 UAD로 간주하고 제어 명령을 해석하고 실행한다. 이런 프로토콜을 이용하여 빠른 시간에 부하보드를 초기 설정이나 시간 설정할 수 있다.
다음은 프로토콜에서 미처 설명하지 못한 통상적인 부분을 요약한 것이다. 프로토콜을 작성할 때 염두해 두어야 할 부분이기도 하다.
.전송에 이상이 있을 경우 4회 정도 등, 정해진 회수만큼 재 시도해본다.
.부하보드가 대장보드로부터 두 번 이상 똑같은 명령을 받고 다시 한 번 정상적으로 처리했다면 ACK를 보낸다.
.NACK 수신 시에는 바로 전 상태의 전문을 다시 보낸다.
.EX-OR 또는 SUM 에러가 발생하면 NACK를 보내고 NACK를 받은 보드는 에러가 발생했던 전문을 다시 보낸다.
.대장보드는 정기적으로(시간은 상태에 따라 변할 수 있음) POL을 행한다.
.POL에서 재 시도 횟수를 넘을 때까지 부하보드가 응답하지 않는다면, 대장보드는 선로의 이상 혹은 고장으로 판단한다.
.부하보드는 대장보드의 POL에 응답하는 것 외에 자체적으로 RSP 전문을 보낼 수 없다.
.부하보드끼리는 POL, SEL, RSP를 서로 행할 수 없다.



그림 9는 대장보드와 부하보드를 연결하는 방법을 나타낸다. ‘MAST’는 대장보드를, ‘SLAVE’Keeper 1~Keeper n은 부하보드를 가리킨다. 부하보드는 통신모드3으로 할 경우 8비트의 UAD 때문에 0x0∼0xFF로 한계가 있다. 하지만 통신모드1로 했을 경우에는 이야기가 달라진다. 즉 프로토콜을 잘 만들고 Fan Out 한계가 없다면 무한대의 부하보드와 통신할 수 있는 것이다.
RX, TX는 대장보드를 기준으로 송?수신이 결정(사실 장비의 보통 기준은 PC이다)되기 때문에 대장보드가 송신이면 부하보드는 수신으로 맞춰야 한 다 . 즉 대장보드의 TX_H(TX_L)와 부하보드의 RX_H(RX_L)가 서로 연결되어야 하는 것이다. 이를 지키지 않으면 485 드라이버가 제 기능을 할 수 없다. 대장보드의 TxD 및 RxD 신호는 드라이브 RS-232를 통해 PC와 인터페이스 된다. 그러나 대장이든 부하든 같은 모듈을 사용하고 두 개의 프로그램이 같이 적재되어 있는데, 딥 스위치의 설정에 따라 대장보드와 부하보드가 결정된다.
출처 : 맥가이심(ecima)블로그
vmware-vdiskmanager.exe

- vmware 설치시 포함되어 있는 커맨드 라인 유틸리티로,

가상 하드디스크 생성
가상 하드디스크의 단편화 제거 (조각 모음..)
shrink(오그라들다?..이건 잘-_-...가상 하드를 압축한다는 이야기인지..)
가상 하드디스크 타입의 변경(고정 크기와 가변 크기)
가상 하드디스크 용량 증가 (maximum size 를 증가시킴. 감소는 않되는 듯-)

등의 기능을 지원한다.


가상 하드디스크 Maximum Size 변경하기

  vmware-vdiskmanager -x <new-capacity> <disk-name>

<new-capacity> 의 단위는 Kb, Mb, Gb 를 사용하며, 입력한 capacity 가 가상 하드디스크의 Maximum Size 가 된다.

ex) vmware-vdiskmanager.exe -x 36Gb myDisk.vmdk



- 가상 하드디스크의 크기를 16Gb 로 변경하는 중.




이제 윈도 2003 서버에 비스타를 업그레이드 시켜 봅시다...;;;;


더 자세한 사항은 이곳으로..

[http://www.vmware.com/support/ws45/doc/disks_vdiskmanager_run_ws.html#1057509]

How do Windows NT System Calls REALLY work?


Most texts that describe Windows NT system calls keep many of the important details in the dark. This leads to confusion when trying to understand exactly what is going on when a user-mode application "calls into" kernel mode. The following article will shed light on the exact mechanism that Windows NT uses when switching to kernel-mode to execute a system service. The description is for an x86 compatible CPU running in protected mode. Other platforms supported by Windows NT will have a similar mechanism for switching to kernel-mode.

By John Gulbrandsen 8/19/2004
John.Gulbrandsen@SummitSoftConsulting.com

What is kernel-mode?

Contrary to what most developers believe (even kernel-mode developers) there is no mode of the x86 CPU called "Kernel-mode". Other CPUs such as the Motorola 68000 has two processor modes "built into" the CPU, i.e. it has a flag in a status register that tells the CPU if it is currently executing in user-mode or supervisor-mode. Intel x86 CPUs do not have such a flag. Instead, it is the privilege level of the code segment that is currently executing that determines the privilege level of the executing program. Each code segment in an application that runs in protected mode on an x86 CPU is described by an 8 byte data structure called a Segment Descriptor. A segment descriptor contains (among other information) the start address of the code segment that is described by the descriptor, the length of the code segment and the privilege level that the code in the code segment will execute at. Code that executes in a code segment with a privilege level of 3 is said to run in user mode and code that executes in a code segment with a privilege level of 0 is said to execute in kernel mode. In other words, kernel-mode (privilege level 0) and user-mode (privilege level 3) are attributes of the code and not of the CPU. Intel calls privilege level 0 "Ring 0" and privilege level 3 "Ring 3". There are two more privilege levels in the x86 CPU that are not used by Windows NT (ring 1 and 2). The reason privilege levels 1 and 2 are not used is because Windows NT was designed to run on several other hardware platforms that may or may not have four privilege levels like the Intel x86 CPU.

The x86 CPU will not allow code that is running at a lower privilege level (numerically higher) to call into code that is running at a higher privilege level (numerically lower). If this is attempted a general protection (GP) exception is automatically generated by the CPU. A general protection exception handler in the operating system will be called and the appropriate action can be taken (warn the user, terminate the application etc). Note that all memory protection discussed above, including the privilege levels, are features of the x86 CPU and not of Windows NT. Without the support from the CPU Windows NT cannot implement memory protection like described above.

Where do the Segment Descriptors reside?

Since each code segment that exists in the system is described by a segment descriptor and since there are potentially many, many code segments in a system (each program may have many) the segment descriptors must be stored somewhere so that the CPU can read them in order to accept or deny access to a program that wishes to execute code in a segment. Intel did not choose to store all this information on the CPU chip itself but instead in the main memory. There are two tables in main memory that store segment descriptors; the Global Descriptor Table (GDT) and the Local Descriptor Table (LDT). There are also two registers in the CPU that holds the addresses to and sizes of these descriptor tables so that the CPU can find the segment descriptors. These registers are the Global Descriptor Table Register (GDTR) and the Local Descriptor Table Register (LDTR). It is the operating system''s responsibility to set up these descriptor tables and to load the GDTR and LDTR registers with the addresses of the GDT and LDT respectively. This has to be done very early in the boot process, even before the CPU is switched into protected mode, because without the descriptor tables no memory segments can be accessed in protected mode. Figure 1 below illustrates the relationship between the GDTR, LDTR, GDT and the LDT.

Since there are two segment descriptor tables it is not enough to use an index to uniquely select a segment descriptor. A bit that identifies in which of the two tables the segment descriptor resides is necessary. The index combined with the table indicator bit is called a segment selector. The segment selector format is displayed below.

As can be seen in figure 2 above, the segment selector also contains a two-bit field called a Requestor Privilege Level (RPL). These bits are used to determine if a certain piece of code can access the code segment descriptor that the selector points to. For instance, if a piece of code that runs at privilege level 3 (user mode) tries to make a jump or call code in the code segment that is described by the code segment descriptor that the selector points to and the RPL in the selector indicates that only code that runs at privilege level 0 can read the code segment a general protection exception occurs. This is the way the x86 CPU can make sure that no ring 3 (user mode) code can get access to ring 0 (kernel-mode) code. In fact, the truth is slightly more complicated than this. For the information-eager please see the further reading list, "Protected Mode Software Architecture" for the details of the RPL field. For our purposes it is enough to know that the RPL field is used for privilege checks of the code trying to use the segment selector to read a segment descriptor.

Interrupt gates

So if application code running in user-mode (at privilege level 3) cannot call code running in kernel-mode (at privilege level 0) how do system calls in Windows NT work? The answer again is that they use features of the CPU. In order to control transitions between code executing at different privilege levels, Windows NT uses a feature of the x86 CPU called an interrupt gate. In order to understand interrupt gates we must first understand how interrupts are used in an x86 CPU executing in protected mode.

Like most other CPUs, the x86 CPU has an interrupt vector table that contains information about how each interrupt should be handled. In real-mode, the x86 CPU''s interrupt vector table simply contains pointers (4 byte values) to the Interrupt Service Routines that will handle the interrupts. In protected-mode, however, the interrupt vector table contains Interrupt Gate Descriptors which are 8 byte data structures that describe how the interrupt should be handled. An Interrupt Gate Descriptor contains information about what code segment the Interrupt Service Routine resides in and where in that code segment the ISR starts. The reason for having an Interrupt Gate Descriptor instead of a simple pointer in the interrupt vector table is the requirement that code executing in user-mode cannot directly call into kernel-mode. By checking the privilege level in the Interrupt Gate Descriptor the CPU can verify that the calling application is allowed to call the protected code at well defined locations (this is the reason for the name "Interrupt Gate", i.e. it is a well defined gate through which user-mode code can transfer control to kernel-mode code).

The Interrupt Gate Descriptor contains a Segment Selector which uniquely defines the Code Segment Descriptor that describes the code segment that contains the Interrupt Service Routine. In the case of our Windows NT system call, the segment selector points to a Code Segment Descriptor in the Global Descriptor Table. The Global Descriptor Table contains all Segment Descriptors that are "global", i.e. that are not associated with any particular process running in the system (in other words, the GDT contains Segment Descriptors that describe operating system code and data segments). See figure 3 below for the relationship between the Interrupt Descriptor Table Entry associated with the ''int 2e'' instruction, the Global Descriptor Table Entry and the Interrupt Service Routine in the target code segment.

Back to the NT system call

Now after having covered the background material we are ready to describe exactly how a Windows NT system call finds its way from user-mode into kernel-mode. System calls in Windows NT are initiated by executing an "int 2e" instruction. The ''int'' instructor causes the CPU to execute a software interrupt, i.e. it will go into the Interrupt Descriptor Table at index 2e and read the Interrupt Gate Descriptor at that location. The Interrupt Gate Descriptor contains the Segment Selector of the Code Segment that contains the Interrupt Service Routine (the ISR). It also contains the offset to the ISR within the target code segment. The CPU will use the Segment Selector in the Interrupt Gate Descriptor to index into the GDT or LDT (depending on the TI-bit in the segment selector). Once the CPU knows the information in the target segment descriptor it loads the information from the segment descriptor into the CPU. It also loads the EIP register from the Offset in the Interrupt Gate Descriptor. At this point the CPU is almost set up to start executing the ISR code in the kernel-mode code segment.

The CPU switches automatically to the kernel-mode stack

Before the CPU starts to execute the ISR in the kernel-mode code segment, it needs to switch to the kernel-mode stack. The reason for this is that the kernel-mode code cannot trust the user-mode stack to have enough room to execute the kernel-mode code. For instance, malicious user-mode code could modify its stack pointer to point to invalid memory, execute an ''int 2e'' instruction and thereby crash the system when the kernel-mode functions uses the invalid stack pointer. Each privilege level in the x86 Protected Mode environment therefore has its own stack. When making function calls to a higher-privileged level through an interrupt gate descriptor like described above, the CPU automatically saves the user-mode program''s SS, ESP, EFLAGS, CS and EIP registers on the kernel-mode stack. In the case of our Windows NT system service dispatcher function (KiSystemService) it needs access to the parameters that the user-mode code pushed onto its stack before it called ''int 2e''. By convention, the user-mode code must set up the EBX register to contain a pointer to the user-mode stack''s parameters before executing the ''int 2e'' instruction. The KiSystemService can then simply copy over as many arguments as the called system function needs from the user-mode stack to the kernel-mode stack before calling the system function. See figure 4 below for an illustration of this.

What system call are we calling?

Since all Windows NT system calls use the same ''int 2e'' software interrupt to switch into kernel-mode, how does the user-mode code tell the kernel-mode code what system function to execute? The answer is that an index is placed in the EAX register before the int 2e instruction is executed. The kernel-mode ISR looks in the EAX register and calls the specified kernel-mode function if all parameters passed from user-mode appears to be correct. The call parameters (for instance passed to our OpenFile function) are passed to the kernel-mode function by the ISR.  

Returning from the system call

Once the system call has completed the CPU automatically restores the running program''s original registers by executing an IRET instruction. This pops all the saved register values from the kernel-mode stack and causes the CPU to continue the execution at the point in the user-mode code  next after the ''int 2e'' call.

Experiment

By examining the Interrupt Gate Descriptor for entry 2e in the Interrupt Descriptor Table we can confirm that the CPU finds the Windows NT system service dispatcher routine like described in this article. The code sample for this article contains a debugger extension for the WinDbg kernel-mode debugger that dumps out a descriptor in the GDT, LDT or IDT.    

Download the example code: ProtMode.zip

The WinDbg debugger extension is a DLL called ''protmode.dll'' (Protected Mode). It is loaded into WinDbg by using the following command: ".load protmode.dll" after having copied the DLL into the directory that contains the kdextx86.dll for our target platform. Break into the WinDbg debugger (CTRL-C) once you are connected to your target platform. The syntax for displaying the IDT descriptor for ''int 2e'' is "!descriptor IDT 2e". This dumps out the following information:

kd>!descriptor IDT 2e
------------------- Interrupt Gate Descriptor --------------------
IDT base = 0x80036400, Index = 0x2e, Descriptor @ 0x80036570
80036570 c0 62 08 00 00 ee 46 80
Segment is present, DPL = 3, System segment, 32-bit descriptor
Target code segment selector = 0x0008 (GDT Index = 1, RPL = 0)
Target code segment offset = 0x804662c0
------------------- Code Segment Descriptor --------------------
GDT base = 0x80036000, Index = 0x01, Descriptor @ 0x80036008
80036008 ff ff 00 00 00 9b cf 00
Segment size is in 4KB pages, 32-bit default operand and data size
Segment is present, DPL = 0, Not system segment, Code segment
Segment is not conforming, Segment is readable, Segment is accessed
Target code segment base address = 0x00000000
Target code segment size = 0x000fffff

The ''descriptor'' command reveals the following:

  • The descriptor at index 2e in the IDT is at address 0x80036570.
  • The raw descriptor data is C0   62 08 00 00 EE 46 80.
  •  This means that:
    • The segment that contains the Code Segment Descriptor described by the Interrupt Gate Descriptor''s Segment Selector is present.
    • Code running at least privilege level 3 can access this Interrupt Gate.
    • The Segment that contains the interrupt handler for our system call (2e) is described by a Segment Descriptor residing at index 1 in the GDT.
    • The KiSystemService starts at offset 0x804552c0 within the target segment.

The "!descriptor IDT 2e" command also dumps out the target code segment descriptor at index 1 in the GDT. This is an explanation of the data dumped from the GDT descriptor:

  • The Code Segment Descriptor at index 1 in the GDT is at address 0x80036008.
  • The raw descriptor data is FF FF 00 00 00 9B CF 00.
  • This means that:
    • The size is in 4KB pages. What this means is that the size field (0x000fffff) should be multiplied with the virtual memory page size (4096 bytes) to get the actual size of the segment described by the descriptor. This yields 4GB which happens to be the size of the full address space which can be accessed from kernel-mode. In other words, the whole 4GB address space is described by this segment descriptor. This is the reason kernel-mode code can access any address in user-mode as well as in kernel-mode.
    • The segment is a kernel-mode segment (DPL=0).
    • The segment is not conforming. See further reading, "Protected Mode Software Architecture" for a full discussion of this field.
    • The segment is readable. This means that code can read from the segment. This is used for memory protection. See further reading, "Protected Mode Software Architecture" for a full discussion of this field.
    • The segment has been accessed. See further reading, "Protected Mode Software Architecture" for a full discussion of this field.

To build the ProtMode.dll WinDbg debugger extension DLL, open the project in Visual Studio 6.0 and click build. For an introduction of how to create debugger extensions like ProtMode.dll, see the SDK that comes with the "Debugging Tools for Windows" which is a free download from Microsoft.

Further Reading

For information on the Protected Mode of the Intel x86 CPU there are two great sources:

1)      "Intel Architecture Software Developers Manual, Volume 3 - System Programming Guide". Available from Intel''s web site in PDF format.

2)      "Protected Mode Software Architecture" by Tom Shanley. Available from Amazon.com (published by Addison Wesley).

For more programming details about the x86 CPU, must-haves are:

1)              Intel Architecture Software Developers Manual, Volume 1 - Basic Architecture.

2)              Intel Architecture Software Developers Manual, Volume 2 - Instruction Set Reference Manual.

      Both these books are available in PDF format on the Intel web site (you can also get a free hardcopy of these two books. Volume 3 is however only available in PDF format).

About the Author

John Gulbrandsen is the founder and president of Summit Soft Consulting. John has a formal background in Microprocessor-, digital- and analog- electronics design as well as in embedded and Windows systems development. John has programmed Windows since 1992 (Windows 3.0). He is as comfortable with programming Windows applications and web systems in C++, C# and VB as he is writing and debugging Windows kernel mode device drivers in  SoftIce.  

To contact John drop him an email: John.Gulbrandsen@SummitSoftConsulting.com

About Summit Soft Consulting

Summit Soft Consulting is a Southern California-based consulting firm specializing in Microsoft''s operating systems and core technologies. Our specialty is Windows Systems Development including kernel mode and NT internals programming.

To visit Summit Soft Consulting on the web: http://www.summitsoftconsulting.com/

char* strcpy(char* p, const char *q); // q를 p로 복사한다(종료문자까지포함)

char* strcat(char* p, const char *q); // q를 p에 추가한다(종료문자까지 포함)

char* strncpy(char* p, const char *q, int n); // q에서 p로 n문자를 복사한다.

char* strncat(char* p, const char *q, int n); //q에서 p에 n문자를 추가한다.



size_t strlen(const char* p); //  p의 길이(종료문자는 세지 않는다)

int strcmp(const char* p, cosnt char* q); // 비교: p및 q

int strncmp(const char* p, const char* q, int n) //처음 n문자를 비교한다.


char* strchr(char *p, int c); // p에서 처음 c를 찾는다.

const char* strchr(const char* p, int c);

char* strrchr(char* p, int c); // p 에서 마지막 c를 찾는다.

const char* strrchr(const char* p, int c);

char* strstr(char* p, const char* q); // p에서 처음 q를 찾는다

const char* strstr(const char* p, const char* q);


char* strpbrk(char* p, const char* q); // q안에 있는 아무 문자와 일치하는 p 내의 첫 위치를 찾는다.

const char* strpbrk(const char* p, const char* q);


size_t strspn(const char* p, const char* q); // q에 없는 문자가 처음으로 p에 나타날 때까지의 문자개수

size_t strcspn(const char*p, const char * q); // q에 있는 문자가 처음으로 p에 나타날 때까지의 문자개수

vi는 크게 세 가지 모드로 나뉘어집니다. 우선 명령모드로 키 입력이 바로 명령이 되는 모드, 다음은 입력모드로 실제 문서를 편집하는 모드, 마지막으로 ex 모 드로 ex명령을 실행시키는 모드입니다. vi를 실행시키면 처음에는 명령모드 상태에 있게 됩니다. 명령모드에서 : 키를 누르면 ex모드로, a, i 등의 키를 누르면 입력모 드로 전환되어 문서를 편집할 수 있는 상태로 됩니다. 본격적인 내용으로 들어가기 전에 기본적인 편집에 대해서 설명을 하겠습니 다. 특정 파일을 편집하기 위해 열려면 vi [filename]을 shell prompt에서 실행시킵 니다. 이렇게 연 파일을 편집모드로 들어가 편집하려면 i를 입력, 원하는 내용을 입 력한 후 종료하려면 [ESC]ZZ 나 [ESC]:wq를 누르면 저장 후 종료하게 됩니다. 이 정 도는 기본적으로 알아두셔야 지금부터 하는 내용을 따라해 보실 수 있겠죠? 또 기본 적으로 알아두셔야 할 명령이 명령모드에서 u 즉 undo명령입니다. 그래야 잘못 실행 된 명령을 바로 undo할 수 있겠죠...^^;

현재위치에서 삽입 i
현재줄의 처음 위치에서 삽입 I
현재위치에서 추가 a
현재줄의 끝에서 추가 A
새로운 한 줄을 커서 아래줄에 연다. o
새로운 한 줄을 커서 위줄에 연다. O
줄을 지우고 삽입모드로 S
현재 위치에서 Relpace 모드로 R
다음줄과 현재줄을 합친다. J
대문자<->소문자 ~
마지막 명령을 반복한다. .
마지막 수정한 것을 취소한다. u
줄을 처음 상태로 복구한다. U

2.1 편집
2.1.1 복사, 붙이기, 삭제, 문자 치환
텍스트 편집기를 사용하면서 가장 유용한 기능 중에 하나가 반복되는 문자열 을 복사해서 다른 곳에 붙이는 기능이라고 생각합니다. vi 역시 이러한 기능을 가지 고 있고 vi의 명령어의 특성상 매우 많은 방법으로 이러한 명령을 수행할 수 있습니 다. 명령모드에서 yy라고 입력하면 현재 커서가 위치한 한 줄을 카피(Yanking)하게 됩니다. 또 dd는 현재 행을 삭제하게 되는데 이 역시 삭제가 이루어지면서 버퍼에 복 사가 되어있습니다. yy나 dd로 버퍼에 들어간 내용을 붙이려면 붙이고자 하는 곳 한 줄 위에서 p를 입력하면 됩니다. 짐작하시듯이 y키가 복사를 하는데 사용되는 키이고 d가 삭제에, p가 붙이기에 사용되는 키입니다. 명령모드에서 사용되는 명령들은 명령 앞에 숫자를 입력한 후 명령을 내리면 그 횟수만큼 명령을 반복하게 됩니다. 쉽게 2yy 라고 입력하면 2줄을 복사하리라는 것을 생각할 수 있습니다. 뒤에서 나오게 되 는 이동에 관련된 명령과 조합하면 복사나 삭제를 원하는 영역에서 해 낼 수 있습니 다. 우선 여기서는 복사에 사용되는 키가 y키, 삭제에 사용되는 키가 d, 붙이기에 사 용되는 키가 p키라는 것만 숙지하고 넘어갑시다. 참 한가지 빠뜨리고 넘어갈 뻔했군 요. X 나 x키가 명령모드에서 삭제에 사용됩니다. 문자 위에서 x를 누르면 위치한 문 자가 삭제되고 X를 누르면 [bs]키처럼 동작을 합니다.

Copy delete yanking
한 단어 cw dw yw
두 단어 2cw or c2w 2dw or d2w 2yw or y2w
한 행 cc dd yy
커서 위치에서 행의 끝까지 c$ or C d$ or D y$ or Y
커서 위치에서 행의 처음까지 c0 d0 y0
한 문자 변경 r x or X y1 or yh
한 파일에서 문자치환
 : 전체영역의 문자치환 -> :s/old/new/g
 : 특정 줄의 문자 치환  -> :s,d s/old/new
 : 특정라인의 특정문자위치의 문자치환 -> :[s,d]s/old/new


2.1.2 Named 버퍼 사용
보통의 에디터의 경우에는 어떤 특정 문자열을 새로 복사하면 이전에 복사했 던 문자열이 지워지게 됩니다. vi는 a-z까지의 이름을 갖는 버퍼에 각각을 유지하여 복사 해 둘 수 있는 매우 편리한 기능을 제공합니다. 이러한 기능을 named buffer라 하고 사용방법은 모든 명령모드에서의 명령 앞에 a~z까지의 버퍼명 "a ~ "z을 붙여주 는 것입니다. 위에서 복사를 했던 것을 다시 해보면 "ayy를 누르면 현재 줄이 "a 버 퍼에 복사가 됩니다. 이것을 다시 붙이기를 하려면 "ap를 입력하면 되겠지요. 이미 있는 버퍼에 내용을 추가하거나 처음부터 버퍼에 써 넣는 방법을 설명 하겠습니다. 이미 어떤 버퍼에 내용이 들어가 있고 그 뒤에 내용을 추가하고 싶은 경 우가 생기죠. 이런 경우 추가는 버퍼 이름의 대문자를 사용합니다. "Ayy와 같이 현재 줄을 버퍼에 추가하거나, ex모드에서 특정 문자열이 있는 줄을 모두 복사해두고 싶은 경우 :g/string/y A 이런 식으로 사용하는 것이죠. 복사등을 하지 않고 버퍼에 직접 써넣으려면 q명령을 사용합니다. q 다음에 버퍼명을 누르면 화면하단에 recording이라는 표시가 나오고 지금부터 하는 키 입력 은 선택한 버퍼명에 들어가게 됩니다. 키입력이 끝나면 다시 q를 누르면 됩니다. 뒤에서 ex모드에서 사용하는 방법이나 @-function에 사용할 수 있는 방법을 설명하겠습니다. 여기서는 이 정도만 알고 넘어가도록 하죠. (주: vim에서는 register라고 하는군요.)
2.1.3 여러 문서의 편집
vi에서 여러 문서를 동시에 열기 위해서는 vi [filename1] [filename2]...의 형태로 열고자 하는 파일을 뒤에 이어서 써 줍니다. 그럼 동시에 여러 문서를 편집하 게 되죠. 그리고 각 문서 사이의 전환은 :n키와 :N을 이용해서 합니다. 즉 vi test1 test2로 두 파일을 열어서 작업을 하고 있다면 :n을 입력하면 test2로 편집이 넘어가 게 됩니다. 다시 :N 또는 :prev을 입력하면 test1을 편집할 수 있습니다. 또 현재 어 떤 파일을 편집중인지 확인하려면 :args를 입력하면 현재 열린 모든 파일 중에 현재 편집중인 파일이 대괄호로 둘러싸여 표시됩니다. 열린 파일이 많아서 여러 문서를 건 너뛰어 편집해야 하는 경우에는 n이나 N앞에 skip할 파일 개수를 써주면 됩니다. :4n 이런 식이 되겠죠.
2.2 이동
문서내용을 수정하려면 편집하고자 하는 위치로 먼저 이동하여야 편집이가 능하겠지요. vi는 다른 편집기와는 비교도 되지 않을 만큼 많은 이동 방법을 제공합 니다. 이동명령은 다른 명령(복사, 삭제, 붙이기)등과 조합해서 사용하기 때문에 확 실히 알아두시는 것이 좋습니다. 제 나름대로 분류를 가까운 거리의 이동에서 원거리 이동까지 분류를 해 보았습니다.
2.2.1 짧은 이동
가장 기본적인 이동이 명령모드 상에서 h, j, k, l키를 이용한 이동입니다. 차례로 좌, 하, 상, 우로의 이동을 나타냅니다. 역시 다른 명령모드 명령과 마찬가지 로 회수를 명령 앞에 넣어서 반복할 수 있습니다. 요즘은 대부분 커서 키를 지원하고 있지만 손가락이 오가는 거리 상 사용하지 않으시는 것이 좋습니다. 단어간의 이동은 w, b, e, E키로 합니다. w키는 다음 단어의 첫 번째 문자로 이동을 합니다. b는 반대로 앞 단어의 첫 번째 문자로의 이동을 나타냅니다. e, E는 첫 번째 문자로의 이동이 아닌 단어의 마지막 문자로의 이동입니다. 역시 회수를 앞 에 넣어서 몇 단어를 한 번이 이동할 수 있습니다. 이 밖에도 현재행 상에서 이동을 하는 명령들이 많이 있습니다. 0키를 누르 면 현재 행의 맨 앞으로 이동합니다. ^ 이나 | 역시 행의 맨 처음으로 이동합니다. 0 키는 행의 처음에 공백이 나오다 문자열이 나오는 경우에는 공백을 무시하고 맨 처음 나타나는 문자로 이동합니다. 하지만 ^ 이나 |는 0번째 열로 이동하게 됩니다. 특정 열로 이동하려면 <숫자>| 즉 10번째 열로 이동하려면 10|을 입력하면 10번째 열로 이 동을 하게 됩니다. 현재 행의 끝으로 이동하려면 $기호를 사용합니다. 이제 행간의 이동입니다. 한 행 밑으로 이동은 + 또는 [enter], 위로의 이동 은 - 키를 사용합니다. 10[enter]라고 입력해 보십시오. 엔터를 한 번 입력하면 한 행 밑으로 이동하지만 한 번에 10 행씩 팍팍 떨어지지 않습니까? 10+도 같은 결과를 보여줍니다.

좌, 하, 상, 우 h, j, k, l
다음줄의 첫번째 문자로 + or [enter]
이전줄의 첫번째 문자로 -
단어의 끝으로 e, E
다음 단어로 w, W
이전 단어로 b, B
행의 끝으로 $
행의 처음으로 0 ('A' 인 경우 A앞으로 커서이동)
행의 처음으로 ^ ('A' 인 경우에도 맨앞으로 커서이동)
다음, 이전 문장의 처음으로 ), (
다음, 이전 문단의 처음으로 }, {
다음, 이전 구절의 처음으로 ]], [[


2.2.2 원거리 이동
이제 조금 원거리 이동으로 넘어갑니다. 문서가 20000행 정도 된다고 합시 다. 처음에 문서를 열게 되면 커서는 1행에 위치합니다. 에러가 10000행 정도에 발생 한 소스코드를 수정한다고 치면, 어떻게 이동하면 좋을까요? 물론 뒤에서 파일 열기 에서 배우겠지만 현재까지 저희가 언급한 바로는 10000+를 하든지 해야겠지요. 물론 이렇게 해도 되겠지만 특정 행으로 직접 이동하는 명령이 있습니다. 바로 G명령입니 다. 10G라고 입력하면 10번째 행으로 이동하게 되는 것이죠. 다른 방법으로는 ex명령 에서 :10라고 입력하고 엔터를 입력하면 그 행으로 이동하게 됩니다. 맨 처음 줄로의 이동은 gg 나 1G 나 :1 등을 사용하고 마지막 줄로의 이동은 G 나 :$ 등이 가능합니 다. 여기서 $는 뒤에서 언급을 하겠지만 ex모드에서는 마지막 줄을 나타냅니다. 명령 모드에서는 행의 끝으로 이동에 사용되었죠.

한 화면 앞으로 스크롤 ^F (means CTRL-F)
한 화면 뒤로 스크롤 ^B
반 화면 앞으로 스크롤 ^D
반 화면 뒤로 스크롤 ^U
한 줄 앞으로 스크롤 ^E
한 줄 뒤로 스크롤 ^Y
화면의 맨 위줄로 H nH인 경우 맨 위에서 n행 밑으로
화면의 중간 줄로 M
화면의 맨 아래줄로 L nL인 경우 맨 밑에서 n행 위로


2.2.3 찾기로 이동
자세한 내용의 찾기는 3장에서 다를 것이고 여기서는 이동에 사용되는 찾기 명령에 대해서 설명하도록 하겠습니다. 현재 행에서 순방향으로의 특정 문자를 찾을 때는 f 키를 사용합니다. 현재 행에서 커서위치에서 첫 번째 나타나는 o 문자까지 이 동하고 싶다면 fo를 입력하면 됩니다. 반대 방향이라면 Fo를 입력하면 되겠죠. 이 명 령의 반복은 어떻게 할까요? 다른 명령처럼 앞에 반복회수를 명시해도 되지만 찾은 후에 다시 찾기를 같은 방향으로 하려면 ; 키를 반대방향으로 하려면 , 키를 사용해 서 명령을 반복합니다. 한 행을 떠나서 순방향으로 어떤 패턴이 나타나는 곳으로 이동하려면 /pattern을 역시 반대 방향이라면 ?pattern을 입력하면 됩니다. 이 역시 같은 방향으 로 반복을 하려면 n, 반대 방향은 N키를 사용합니다.

문자열의 처음으로 앞으로 검색 /pattern
문자열의 처음으로 뒤로 검색 ?pattern
검색을 다시 반복 (같은 방향) n
검색을 다시 반복 (반대 방향) N
현재 줄에서 x가 있는 곳으로 이동 fx
현재 줄에서 x가 있는 곳으로 이동 Fx
n행 밑에서 x가 있는 곳으로 이동 tx
n행 위에서 x가 있는 곳으로 이동 Tx
줄에서 찾기를 같은 방향으로 반복 ;
줄애서 찾기를 반대 방향으로 반복 ,
행 이동 n번째 줄로 이동 nG (n이 생략되면 마지막 줄로)
:n
열 이동 n| (n이 생략되면 처음 열로)
현재 커서가 위치한 단어 찾기 * (앞 방향으로 찾는다.)
현재 커서가 위치한 단어 찾기 # (뒤로 찾는다.)

2.2.4 마크를 이용한 이동
책을 읽다 북마크를 하는 것처럼 문서를 편집하다가 특정 부분을 마크할 수 있으면 편리하겠지요. vi에서는 역시 알파벳 개수만큼의 마크를 하고 마크한 곳이나 그 줄로 바로 이동할 수 있습니다. 우선 마크를 하려면 ma ~ mz를 마크 할 곳에서 입 력합니다. 마크한 곳으로 이동은 두 가지가 가능합니다. 즉 마크한 줄과 열이 일치하 는 이동은 `a ~ `z 으로 마크한 줄의 처음으로 이동은 'a ~ 'z를 이용합니다.

현재 위치를 x 이름의 마크로 저장 mx
마크한 위치(행, 열)로 이동 `x
마크한 줄로 이동 'x
이전에 마크한 위치로 이동 ``
이전에 마크한 줄로 이동 ''

2.3 파일 저장, 열기, 종료
파일을 열 때 여러 가지 옵션을 주어서 열 수 있습니다. 즉 특정 행에 커서 가 위치하도록 열 수 있는데, 마지막 줄에 위치하려면 vi + [filename] 과 같이 열 고, n번째 행에 위치시키려면 vi +n [filename] 과 같은 방식으로 열면 됩니다. 특정 문자열이 있는 줄에 커서를 위치시키려면 vi +/pattern [filename]로 엽니다. vi +R [filename] 은 파일을 읽기 전용으로 엽니다. 파일의 저장을 살펴볼까요. 파일을 저장하면서 종료하는 방법은 맨 처음에 두 가지를 설명했었습니다. :wq 와 ZZ입니다. 다른 저장방법이라면 :x 이 명령은 파 일이 변경되었을 때에는 저장하고 종료를 하지만 아니면 그냥 종료를 합니다. ZZ와 같은 기능이 같죠. 저장과 열 때 ex모드에서 사용하는 몇 가지 기호를 알아두시면 편 리합니다. 즉 %는 작성중인 파일명을 나타냅니다. 따라서 현재 파일을 편집 중에 백 업파일을 저장하고 싶다면 :w %.bak 이런 식으로 하시면 쉽게 백업을 만들 수 있습니 다. 그리고 #는 alternative 파일을 나타낸다고 하는데 이전에 열어서 작성한 파일을 나타냅니다. vi file1 으로 편집을 하다가 :e file2로 편집 파일을 새로 연 경우 다 시 먼저의 file1을 열려면 :e # 라고 하시면 됩니다.

file 열기 vi [filename]
여러 파일 열기 vi [filename1] [filename2]
읽기 전용으로 열기 view [filename] 또는 vi -R [filename]
열고 마지막 행에 위치 vi + [filename]
파일을 열고 n 번째 행에 위치 vi +n [filename]
패턴이 나타나는 곳에 행 위치 vi +/pattern [filename]
변경된 파일이면 저장하고 종료 ZZ 또는 :x
저장하고 종료 :wq
파일의 저장 w
특정 범위만 저장 :<범위>w [filename]
특정 범위를 다른 파일에 덧붙임 :<범위>w >> [filename]
 컴파일

 

작성한 코드에 구문검사를 해서 구문에 이상이 있다면 오류를 내주어 작성한 유저에게 알려주고 만약 검사에 이상이 없다면 프로그램이 시작되기 전에 메모리에 올라간다. 이처럼 컴파일의 역할은 유저가 작성한 코드에 이상이 없는지 검사하고 코드에 작성된 변수나 함수들을 메모리에 올려주는 역할을 한다.

 

 링크

 

프로그램을 만드는 마지막 작업이 바로 링크(Link)라는 과정이다. 필요한 조각들을 모두 모으거나 어떤 부분이 빠져 있는지 알아보기 위한 과정이다. 정적 라이브러리(Static Library)를 사용할 때, 링커는 프로그램이 필요로 하는 부분을 라이브러리에서 찾아서 그냥 실행파일에다 카피해버린다. 공유 라이브러리(또는 동적 라이브러리)의 경우에는 이렇게 하는 것이 아니라 실행파일에다가 단지 "실행될 때 우선 이 라이브러리를 로딩시킬 것"이라는 메시지만을 남겨놓는다. 당연히 공유 라이브러리를 사용하면 실행파일의 크기가 작아진다. 그들은 메모리도 또한 적게 차지하며, 하드 디스크의 용량도 적게 차지한다.

링킹(linking)은 여러가지 코드와 데이터를 묶어 메모리로 로드될 수 있는 하나의 실행 가능한 파일을 만드는 작업이다.

링킹은 컴파일-타임때 행해질 수도 있고, 로드-타임(로더에 의해), 혹은 런-타임(응용 프로그램에 의해)때도 행해질 수 있다.

 

Cygwin

 

윈도우 환경에서 GNU 프로그램을 사용할 수 있는 환경을 제공하는 프로그램

http://falinux.com/win/study/cygwin/cygwin1.html

Make Utility

1. 들어가기

 

프로그래머들은 작은 프로그램을 작성할 때는 단순히 수정하고 나서 모든 파일을 다시 컴파일 하여 새로 프로그램을 만들게 된다. 그러나 좀더 규모가 큰 프로그램에서는 이런 간단한 방법이 약간의 문제를 가져오게 될 것이다. 하나의 파일만 변경하게 되더라도 다시 컴파일 해야 하는 등의 수정하고 컴파일하고 테스트하는데 많은 시간을 소모하게 될 것이다. 이것 같은 문제를 해결하기 위해서 만들어 진 것이 make 유틸리티이다. 다시 정리하자면 이 유틸리티는 큰 프로그램을 유지하는 필요한 유틸리티이며, 프로그램 중 어느 부분이 새롭게 컴파일 되어야 하는지를 자동적으로 판단해서 커맨드(컴파일러, 쉘등)를 이용하여 프로그램을 재 컴파일을 하게 된다.

 

2. 왜 사용하지?

 

프로그램을 개발하게 되면 보통 라인 수가 증가하게 되어 하나의 파일만으로 프로그램을 구성하는 것은 어려운 일이다. 그래서 보통은 각각의 기능을 작은 모듈로 나누어 여러 파일로 나누어 개발을 하게 된다. 이 파일들은 서로 관계를 가지고 있는데, 어느 하나를 필요에 의해 바꾸게 되었을 때 그 파일에 있는 모듈(함수)를 이용하는 다른 파일도 새롭게 컴파일 되어야 한다. 하지만 파일 수가 많은 경우 이를 일일이 컴파일을 하게 될 때, 그 불편함과 함께 컴파일 하지 않아도 될 것도 컴파일을 하게 될 수 있고, 컴파일 해야 할 것도 미쳐 못하게 되어 링크시 에러를 발생하게 되는 경우도 있게 된다. 이런 상황에서 자동적으로 관계 있는 것만 새롭게 컴파일 하거나, 여러 개의 입력 파일로부터 출력 파일을 생성할 때 make 유틸리티는 필요하게 된다.

3. 어떻게 사용할까?

 

make 유틸리티는 많은 기능을 제공하지만, 자동으로 입력 파일을 인식하고, 컴파일 하여 출력 파일을 생성하지는 못하기 때문에 이 것에 대한 정보를 make가 알 수 있도록 파일을 제공해야 하는데 이것이 Makefile이라는 파일이다. 이것은 make가 이해할 수 있도록 일정한 형식을 가지고 있는 스크립트 언어와 흡사하다. Makefile에서는 시스템의 쉘을 사용할 수 있으며 컴파일러등의 프로그램들을 실행할 수 있다. Makefile은 대개 프로젝트의 다른 소스 파일과 같은 디렉토리에 존재하며, 큰 프로그램에서는 각 각의 기능들을 위해 독립적인 Makefile을 사용하여 프로그램을 관리한다.

4. Makefile에 대하여… …

 

메이크파일은 기본적으로 대상(Target), 의존 관계(dependency), 명령(command)등에 대한 규칙들(rules) 이루어져 있다. 아래의 list 4-1 기본적인 메이크파일의 플랫폼이다.

 

 

Target . . .  :  Dependency . . .

 

               Command  . . .

 

.                         . . .

. . .

List 4-1. Makefile 플랫폼

Note1

 

 

 


· Target : Command에 의해서 수행 되어서 나온 결과 파일을 지정하므로, 일반적으로 목적 파일(Object file)이나 실행 파일이다.

 

· Dependency : 생성되는 파일인 대상(Target)과 이것이 의존하는 파일들의 관계를 나타낸다.

 

· Command : Command 부분에 정의된 명령들은 의존 관계(Dependency)부분에 정의된 파일의 내용이 바뀌었거나, 대상(Target) 부분에 해당하는 파일이 없을 때 이곳에 정의된 것들이 차례대로 실행된다. 일반적으로 쉘에서 쓸 수 있는 모든 명령들을 사용할 수 있으며 Bash에 기반한 쉘 스크립트도 지원한다.

 

 

4.1. Makefile 예제 보기

 

· 간단한 Makefile 만들기

 

간단하게 Makefile 만들어 보자. 우리가 만들려고 하는 프로그램은 main.c, read.c, write.c 구성되어 있고 모두 io.h을 사용하고 read.c defs.h을 더 포함하고, wirte.c buffer.h라는 라는 헤더 파일을 사용한다고 가정한다. 이들을 각각 컴파일해서 test 라는 실행 파일을 생성시킨다 아래의 list 4-2 상에서 실행 파일을 만든 것이고, List 4-3 Makefile 작성하여 만든 것이다.

 

 

 $gcc -c main.c

 $gcc -c read.c

 $gcc -c write.c

 $gcc -o test main.o read.o write.o

List 4-2. 상에서 실행 파일 만들기

 

test: main.o read.o write.o

       gcc -o test main.o read.o write.o

 

  main.o : io.h main.c

       gcc -c main.c

 

  read.o : io.h defs.h read.c

       gcc -c read.c

 

  write.o : io.h buffer.h write.c

       gcc -c write.c

 

 

  $make

  gcc -c main.c

gcc -c main.c

gcc -c main.c

gcc -o test main.o read.o write.o

 

List 4-3. Makefile 작성하여 실행 파일 만들기와 실행 화면

Note2

 

 


· 쉘 상에서 make명령을 내리면 먼저 make는 먼저 현재 디렉토리에서 makefile이라는 파일을 찾는다. 이 파일이 존재하지 않으면 다시 Makefile이라는 파일을 찾는다.전통적인 유닉스 프로그래머는 makefile을 사용한다.

 

· make Makefile의 내용을 보고, 내부적으로 어떻게 파일들이 의존하고 있는지 조사한다.

 

· 위의 예제에서는 test라는 실행 파일을 만들기 위해서는 main.o io.h, read.o io.h defs.h, 그리고 write.o io.h buffer.h의 헤더 파일과 모두 자신의 소스에 의존 관계임을 알 수 있다. 만약에 main.c를 고친다면 main.o 컴파일되어 다시 생기고, test 다시 링크되어 갱신된다. 만약 io.h 바뀌었다고 가정하면 모든 파일들이 컴파일되어서 목적 파일이 생기고, 그것들이 링크가 되어 test 생긴다

 

· 변수를 사용하여 더 간단하게 만들기

 

위의 예제에서 변수를 사용하여 더 간단하게 Makefile을 작성한 것이 List 4-4 이다.

 

# Variables make Makefiles simpler

Objects = main.o read.o write.o

 

test: $(Objects)

       gcc -o test $(Objects)

 

  main.o : io.h main.c

       gcc -c main.c

 

  read.o : io.h defs.h read.c

       gcc -c read.c

 

  write.o : io.h buffer.h write.c

       gcc -c write.c

 

List 4-4. 변수을 이용하여 Makefile만들기

Note3

 

 


· Makefile에서의 주석문은 #으로 시작하고, 한 줄의 끝까지 계속된다.

 

· Makefile에서는 변수를 사용하기 위해서는 값을 대입하기 위해서는 “=”을 사용하고 $(변수), ${변수}의 형식을 사용하여 값을 쓸 수 있게 된다.

 

· Makefile에서 사용되는 모든 규칙은 탭으로 시작하는 줄에 있어야 한다. 빈 칸은 허용되지 않는다. 다수의 빈 칸과 탭은 같은 것으로 보이고, 유닉스 프로그래밍의 다른 모든 경우에 빈 칸과 탭 사이에는 거의 차이가 없으므로 이것은 문제를 일으킬 수 있다. 또한, Makefile에서 줄의 마지막에 있는 빈 칸은 make명령이 실패하게 할 수 있다.

 

· 명령들을 추론하여 만들기

 

make명령은 C 소스인 경우 .o을 이용하여 .c를 추론하여 명령을 내부 규칙에 따라 gcc -c을 수행하게 된다. 아래의 List 4-5는 이것을 보여 주고 있다.

 

 

#Letting make deduce the commands

OBJECTS = main.o read.o write.o

 

test: $( OBJECTS)

       gcc -o test $( OBJECTS)

 

  main.o : io.h

 

  read.o : io.h defs.h

 

write.o : io.h buffer.h

 

.PHONY : clean

clean:

      -rm –f $( OBJECTS)

      @rm –f test

     

List 4-5. 명령을 추론하여 만든 Makefile

Note4

 

 


· 위의 clean처럼 대상(Target) 부분에 해당하는 부분이 그냥 레이블과 같이 사용될 수 있다. 이것을 Phony Target이라고 하면 위의 형식처럼 사용하는데 .PHONY는 생략하여도 된다.

 

· 위의 예제에서 rm명령 앞에 있는“-“ make가 에러를 무시하게 한다. 예를 들어, 디렉토리를 만들기 원하고 디렉토리가 이미 존재할 경우에 에러를 무시하기 원한다면, mkdir 앞에 “-“를 추가하면 된다.

 

· 위의 예제에서 rm명령 앞에 있는 @ make가 명령을 실행하는 것을 표준 출력 장치로 출력하지 않도록 지시한다.

 

 

· 또 다른 형태의 Makefile 만들기

 

내부 규칙에 따라 Target대신에 의존성(dependency)만을 사용하여 만들 것이 List 4-6이다.

 

# Alternative sytle of makefile

 

OBJECTS = main.o read.o write.o

 

test: $( OBJECTS)

       gcc -o test $( OBJECTS)

 

  $(Objects) : io.h

 

  read.o : defs.h

 

  write.o : buffer.h

 

List 4-6. 의존성을 이용하여 Makefile만들기

 

4.2. Makefile의 기능을 향상시키 주는 매크로 와 확장자 규칙

 

· 매크로(Macro)란 무엇일까?

 

매크로는 특정한 코드를 간단하게 표현하기 위해 사용되며,프로그램을 작성할 때 변수를 지정하는 것처럼 하면된다. 매크로를 Makefile에서 사용하기 위해서는 $(..) ${..}, $를 사용하는데, 일반적으로 $(..)을 사용하기를 권고한다. 아래의 List 4-7은 매크로의 사용 방법을 보여 주고 있다.

 

 

# Macro makes Makefile happy.

 

OBJECTS = main.o read.o write.o

 

test: $( OBJECTS)

       gcc -o test $( OBJECTS)

 

  $(OBJECTS) : io.h

 

  read.o : defs.h

 

  write.o : buffer.h

 

List 4-7. 간단한 매크로 예제

 

· 미리 정해져 있는 매크로(Macro)들의 무엇이 있을까?

 

프로그래머가 매크로를 만들어 사용할 수 있지만, 미리 정의된 매크로들이 있다. 이 매크로들을 보기 위해서는 make p라고 입력해 보면 make에서 미리 정의 되어 있는 모든 값들(매크로, 환경 변수등등)을 볼 수 있다. 아래의 List 4-8은 미리 정의된 매크로를 보여 주고 있으며, List 4-9는 미리 정의 된 매크로를 사용하여 만든 예제이다.

 

 

ASFLAGS = (as 명령어의 옵션 세팅)
AS = gas
CFLAGS = (gcc 의 옵션 세팅)
CC = gcc 
CPPFLAGS = (g++ 의 옵션)
CPP = g++
LDFLAGS = (ld 의 옵션 세팅)
LD = ld

List 4-8. 미리 정의된 매크로

Note5

 

 


· 매크로는 관습적으로 대문자를 작성된다.

 

· 쉘 상에서 정의한 환경 변수 값들은 그대로 이용된다.

 

 

 
OBJECTS = main.o read.o write.o
SRCS = main.c read.c write.c
 
CC = gcc
CFLAGS = -g -c
 
TARGET = test 
 
$(TARGET) : $(OBJECTS)
$(CC) -o $(TARGET) $(OBJECTS)
 
clean : 
rm -f $(OBJECTS) $(TARGET)
 
main.o : io.h main.c
read.o : io.h read.c
write.o: io.h write.c
 
$make
gcc -g -c main.c -o main.o
gcc -g -c read.c -o read.o
gcc -g -c write.c -o write.o
gcc -o test main.o read.o write.o
 
$make clean
rm -rf main.o read.o write.o test
 

List 4-9. 미리 정의된 매크로를 사용한 예제 와 실행 화면

Note6

 

 


· CFLAGS 같은 매크로는 make 파일의 내부에서 이용하여 .c파일을 .o파일로 바꾸어 주는 부분이 없어도 CFLAGS에 값을 세팅하면 make가 알아서 컴파일을 수행하여 .o파일을 생성한다.

 

 

· 확장자 규칙(Suffix rule)란 무엇인가?

 

파일의 확장자를 보고, 그에 따라 적절한 연산을 수행시키는 규칙이다. 가령 가령 .c 파일은 일반적으로 C 소스 코드를 가리키며, .o 파일은 목적 파일(Object file) 말하고 있다. 그리고 당연히 .c 파일은 컴파일되어서 .o 파일이 되어야 하는 것이다. .SUFFIXS라는 매크로를 사용하여 확장자를 등록하여 있다. 아래의 List 4-10 .SUFFIXES 매크로를 이용한 예제이다.

 

 
 .SUFFIXES : .c .o
 
 OBJECTS = main.o read.o write.o
 SRCS = main.c read.c write.c
 
 CC = gcc
 CFLAGS = -g –c
 
 TARGET = test
 
 $(TARGET) : $(OBJECTS)
      $(CC) –o $(TARGET) $(OBJECTS)
 
 clean:
      rm –f $(OBJECTS) $(TARGET)
 
 main.o : io.h main.c
 read.o : io.h defs.h read.c
 write.o : io.h buffer.h write.c
 
 
 $make
 gcc -g -c main.c -o main.o
 gcc -g -c read.c -o read.o
 gcc -g -c write.c -o write.o
 gcc -o test main.o read.o write.o
 

List 4-10. .SUFFIXES를 사용한 예제와 실행 화면

 

 

Note7

 

 


· 확장자 규칙에 의해서 make는 파일들 간의 확장자를 자동으로 인식해서 필요한 작업을 자동으로 수행한다. 위의 예제에서는 자동적으로 아래와 같은 루틴이 자동적으로 동작하게 된다.

 

.c .o :

$(CC) $(CFLAGS) -c $< -o $@

 

· make 내부에서 기본적으로 서비스를 제공해 주는 확장자는 다음과 같다.

.out .a .ln .o .c .cc .C .p .f .F .r .y .l .s .S .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo

 

· Makefile내부에서 .SUFFIXES 매크로의 값을 세팅해 주면 내부적으로 정의된 확장자의 연산이 동작을 하게 된다. 따라서 확장자 규칙은 make가 어느 확장자를 가진 파일들을 처리할 것인가를 정해 주는 것이다.

 

 

· 내부 매크로(Internal macro)무엇일까?

 

make에서는 내부 매크로라는 것이 있다. 이것은 우리가 맘대로 정할 수 있는 매크로는 절대 아니다. 대신 매크로를 연산, 처리하는데 쓰이는 매크로라고 하는 것이 더 적당할 것이다. 아래의 Table 4-1은 내부 매크로에 대해 설명하고 있으며, List 4-11은 내부 매크로를 사용한 예를 보여 주고 있다.

 

내부 매크로

         

$*

확장자가 없는 현재의 대상(Target) 파일

$@

현재의 대상(Target) 파일

$<

현재의 대상(Target) 파일보다 더 최근에 갱신된 파일 이름

(dependency중에서 처음에 위치한 파일 이름)

$?

현재의 대상(Target) 파일보다 더 최근에 갱신된 파일 이름

(모든 dependency 파일의 이름)

Table 4-1. 내부 매크로

 

 
 main.o : main.c io.h
     gcc -c $*.c ($* 는 확장자가 없는 현재의 대상 파일이므로 $* 는 결국 main에 해당된다.)
 
 test : $(OBJECTS)
     gcc –o $@ $*.c ($@ 는 현재의 대상 파일이므로 $* 는 결국 test에 해당된다.)
 
 .c .o :
     gcc -c $< (또는 gcc -c $*.c)
($< 는 현재의 대상 파일보다 더 최근에 갱싱된 파일 이름이므로, .o 파일보다 더 최근에 갱신된 .c 파일은 자동적으로 컴파일이 된다. 가령 main.o를 만들고 난 다음에 main.c를 갱신하게 되면 main.c $<의 작용에 의해 새롭게 컴파일 된다.)

List 4-11은 내부 매크로를 사용한 예제

4.3 Makefile를 작성할 때 알면 좋은 것들

 

· 긴 명령어를 여러 라인으로 표시하기

 

Makefile을 작성할 때 명령어가 한 줄을 넘어간다면, \문자를 이용해서 여러 라인으로 나타낼 수 있다. 아래의 List 4-12은 긴 명려어를 여러 라인으로 나타내는 예제이다.

 

 

OBJECTS = shape.o \

          rectangle.o \

          circle.o \

          line.o \

          main.o \

          read.o \

          write.o \

List 4-12. 여러 라인으로 명령어 나타내는 예제

 

· 매크로 치환(Macro substitution)

 

매크로를 지정하고, 그것을 이용하는 것을 이미 알고 있다. 그런데 필용에 의해 매크로의 내용을 조그만 바꾸어야 할 때가 있다. 매크로 내용의 일부만 바꾸기 위해서는 $(MACRO_NAME:OLD=NEW)과 같은 형식을 이용하면 된다. 아래의 List 4-13은 매크로 치환에 대한 예제이다.

 

 

MY_NAME = Hello World

YOUR_NAME = $(MY_NAME:Hello=Hi)

 

(Jack이란 부분이 Jook으로 바뀌게 된다. YOUR_NAME이란 매크로의 값은 Hi World이 된다.)

 

 

OBJS = main.o read.o write.o

SRCS = $(OBJS:.o=.c)

 

(SRCS에서는 OBJS에서 .o .c로 바뀌게 된다. 다음과 같이 변경되는 것이다.

SRCS = main.c read.c write.c)

List 4-13. 매크로 치환에 대한 예제

 

· 자동 의존 관계 생성(Automatic dependency)

 

일반적인 make 구조는 대상(target), 의존 관계(dependency), 명령(command)이 연속적으로 정의 되어 있는데 실행되는데, 명령이 없이 대상과 의존 관계만 표시가 되면 이는 대상이 어는 파일에 의존하고 있는지 표시해 주는 정보 역할을 하게 됩니다. Makefile을 작성할 때 없어서는 안되는 부분입니다. 그런데 일일이 이런 정보를 만든다는 것은 쉬운 일이 아닙니다. 이런 단조롭고 귀찮은 일을 자동으로 해주기 위해서 gcc M XX.c의 형식을 사용하여 의존 관계를 만들게 된다. 프로그램을 설치할 때 make dep라는 것을 친 기억이 있을 것이다. 파일들의 의존 관계를 작성해 준다는 의미이다. List 4-14는 자동 의존 관계를 생성하는 예제이다.

 

 

.SUFFIXES : .c .o 
CFLAGS = -O2 -g
 
OBJS = main.o read.o write.o 
SRCS = $(OBJS:.o=.c)
 
test : $(OBJS)
          $(CC) -o test $(OBJS)
 
dep :
$(CC) –M $(SRCS)
 
 $make dep
 $vi Makefile
 
main.o: main.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/libio.h \
/usr/include/_G_config.h io.h
read.o: read.c io.h defs.h
write.o: write.c io.h buffer.h
  (Makefile의 뒷부분에 위에 내용이 첨가 된다.)

 

List 4-14. 자동 의존 관계 생성 예제 및 실행화면

 

 

 

 

 

 

 

 

 

 

 

 

 

· 다중 타켓(Multiple Target)

 

하나의 Makefile에서 꼭 하나의 결과만 만들어 내라는 법은 없다. 결과 파일이 3개가 필요한 경우도 있다. 아래의 List 4-15는 다중 대상(Target)에 대한 예제이다.

 

 

.SUFFIXES : .c .o 
CC = gcc
CFLAGS = -O2 -g
 
OBJS1 = main.o test1.o
OBJS2 = main.o test2.o 
OBJS3 = main.o test3.o 
SRCS = $(OBJS1:.o=.c) $(OBJS2:.o=.c) $(OBJS3:.o=.c) 
 
all : test1 test2 test3
 
test1 : $(OBJS1)
        $(CC) -o test1 $(OBJS1) 
 
test2 : $(OBJS2)
        $(CC) -o test2 $(OBJS2)
 
test3 : $(OBJS3)
        $(CC) -o test3 $(OBJS3)
 
deep :
        $(CC) -M $(SRCS)
 
$make all (또는 make)
gcc -O2 -g -c main.c -o main.o
gcc -O2 -g -c test1.c -o test1.o
gcc -o test1 main.o test1.o ( test1 의 생성 ) 
gcc -O2 -g -c test2.c -o test2.o
gcc -o test2 main.o test2.o ( test2 의 생성 )
gcc -O2 -g -c test3.c -o test3.o
gcc -o test3 main.o test3.o ( test3 의 생성 )
 

 

List 4-15. 다중 대상(Target)에 대한 예제 및 실행 화면.

 

 

 

 

· 순환 make(Recursive make)

 

규모가 큰 프로그램들은 파일들이 하나의 디렉토리에 있지 않는 경우가 많다. 여러 개의 서브시스템이 전체 시스템을 구성한다고 가정하면 각 서브시스템에 Makefile이 존재한다. (서브시스템 = 서브디렉토리) 따라서 여러 개의 Makefile을 동작시킬 필요가 있도록 Makefile을 고쳐 보자. 서브디렉토리에 있는 Makefile을 동작시키는 방법은 의외로 간단하다. 아래의 List 4-16은 순환 make에 대한 예를 보여 주고 있다.

 

subsystem:
                cd subdir; $(MAKE) .....(1)
 
subsystem:
                $(MAKE) -C subdir .....(2)

 

(위의 예제에서 (1) (2)는 동일한 명령을 수행한다 우리가 만들 시스템의 타겟이 subsystem이다. 우선 subdir이라는 곳으로 가서, 거기에 있는 Makefile을 동작시키게 된다. MAKE라는 것은 그냥 make라는 명령어를 표시하는 매크로일 뿐이다.)

 

SUFFIXES : .c .o
CC = gcc
CFLAGS = -O2 -g
 
all : DataBase Test <- 요기에 집중.
 
DataBase:
    cd db ; $(MAKE)  (db 로 이동해서 make 실행 )
 
Test: 
    cd test ; $(Make)  ( db 로 이동해서 make 실행 )

 

$make
cd db ; make
make[1]: Entering directory`/home/raxis/TEST/src'
gcc -O2 -g -c DBopen.c -o DBopen.o
gcc -O2 -g -c DBread.c -o DBread.o
gcc -O2 -g -c DBwrite.c -o DBwrite.o
make[1]: Leaving directory `/home/windows/TEST/src'
cd test ; make
make[1]: Entering directory `/home/raxis/TEST/test'
gcc -O2 -g -c test.c -o test.o
make[1]: Leaving directory `/home/windows/TEST/test'

(make뒤의 대괄호의 1이라고 나타난 것은 현재의 레벨을 의미한다. 원래 디렉토리의 레벨이 0이고, 여기서는 레벨이 하나 더 내려갔으므로 1이라고 표시된 것이다.)

List 4-16. 순환 make 대한 예제 및 실행 화면

· 불필요한 재컴파일 막기

 

의존 관계 규칙에 의해 하나가 바뀌면 그에 영향받는 모든 파일이 바뀐다고 앞에서 말했다. 그러나 다른 파일들에게 아무 영향을 주지 않도록 수정하였는데도 재컴파일을 시도한다면 시간 낭비가 될 수도 있다. 가령 모든 .c 파일에서 include 하는 헤더 파일에서 새로운 #define PI 3.14 라고 정의를 했다고 가정하자. 그리고 PI라는 것은 아무 곳에서도 사용을 하지 않는다. 이때는 'make -t' 라고 해보자. -t touch를 의미하는 옵션으로써 컴파일을 하지 않는 대신 파일의 생성 날짜만 가장 최근으로 바꾸어 놓는다. 새로 컴파일 된 것처럼 처리를 하는 것이다. touch유틸리티 명렁어에 익숙한 사람이라면 이해할 것이다. touch는 파일의 생성 날짜를 현재로 바꾸어 주는 간단한 유틸리티이다.

4.4 Makefile에 사용되는 옵션

make에서 거의 모든 것은 Makefile내부에서 모두 지정을 할 수 있다. 그중 일부를 make의 실행 시에 옵션으로 통해서 줄 수도 있다. List 4-17은 수많은 옵션중에서 가장 많이 사용 옵션을 보여 주고 있다.

-C dir

위에서도 밝혔듯이 Makefile을 계속 읽지 말고 우선은 dir로 이동하라는 것이다. 순환 make에 사용된다.

 

-d

Makefile을 수행하면서 각종 정보를 모조리 출력해 준다. (-debug) 결과를 파일로 저장해서 읽어보면 make 의 동작을 대충 이해할 수 있다.

 

-h  옵션에 관한 도움말을 출력한다. (-help)

 

-f file  file 에 해당하는 파일을 Makefile로써 취급한다. (-file)

 

-r

내장하고 있는 각종 규칙(Suffix rule )을 없는 것으로 (-no-builtin-rules)간주한다. 따라서 사용자가 규칙을 새롭게 정의해 주어야 한다.

 

-t  파일의 생성 날짜를 현재 시간으로 갱신한다. (-touch)

 

-v  make의 버전을 출력한다. ( GNU make 3.73 을 씁니다.) (-version)

 

-p  make에서 내부적으로 세팅되어 있는 값들을 출력한다. (-print-data-base)

 

-k  

make는 에러가 발생하면 도중에 실행을 포기하게 되는데 (-keep-going) -k 는 에러가 나더라도 멈추지 말고 계속 진행하라는 뜻.

List 4-17. Makefile의 옵션들

4.5 Makefile의 실제 예제

아래의 List 4-18 List 4-19 Makefile 예를 보여 주고 있다.

 

HOSTARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \

           -e s/arm.*/arm/ -e s/sa110/arm/ -e s/macppc/ppc/)

 

HOSTOS := $(shell uname -s | tr A-Z a-z)

 

ifeq ($(HOSTARCH),ppc)

CROSS_COMPILE =

else

CROSS_COMPILE =

endif

 

export CROSS_COMPILE HOSTARCH

 

TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)

 

export TOPDIR

 

include $(TOPDIR)/config.mk

          

SUBDIRS = common driver

 

#OBJS = ascu/libascu.a

OBJS += driver/libdriver.a

#OBJS += net/libnet.a

OBJS +=        common/libcommon.a

 

all:   ascu

       @for dir in $(SUBDIRS); \

       do \

               $(MAKE) -C $$dir || exit 1 ; \

       done

 

ascu:  depend subdirs $(OBJS) $(LDSCRIPT)

               $(CC) -o ascu_prog $(OBJS)  -D_REENTRANT -lpthread

 

subdirs:

               @for dir in $(SUBDIRS) ; \

               do \

                        $(MAKE) -C $$dir || exit 1 ; \

               done

 

depend dep:

               @for dir in $(SUBDIRS) ; \

               do \

                        $(MAKE) -C $$dir .depend ; \

               done    

 

clean:

       rm -f `find . -type f \

               \( -name '*.o' -o -name '*.a' \) -print`

       rm       -f ascu_prog ascu.elf ascu.map

 

clobber:       clean

       rm -f `find . -type f \

               \( -name .depend -name '*.o' -o -name '*.a' \) \

               -print`

       rm -f ascu_prog ascu.elf ascu.map

List 4-18  첫번째 Makefile 예제

 

include $(TOPDIR)/config.mk

 

LIB = ascu

 

LIBDIR = lib$(LIB).a

 

OBJS   = $(patsubst %.c,%.o,$(wildcard *.c))

 

$(LIBDIR):     .depend $(OBJS)

       $(AR) crv $@ $^

 

#########################################################################

 

.depend:       Makefile $(SOBJS:.o=.S) $(OBJS:.o=.c)

               $(CC) -M $(CFLAGS) $(SOBJS:.o=.S) $(OBJS:.o=.c) > $@

 

sinclude .depend

 

#########################################################################

 

List 4-19  두번째 Makefile 예제

아래의 List 4-20 List 4-19 Makefile에 의해 생성된 dependency 파일을 보여 주고 있다.

 

 

main.o: main.c /usr/include/stdio.h /usr/include/features.h \

 /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h \

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stdarg.h \

 /usr/include/bits/types.h /usr/include/libio.h \

 /usr/include/_G_config.h /usr/include/bits/stdio_lim.h \

 /usr/include/bits/stdio.h

List 4-20  생성된 dependency 파일(.depend)

 

+ Recent posts