DB를 쓸일이 참 많았다. 그런데 DB에 대해서 전혀 모르다보니 간단히 무슨 신호를 받아서 신호에 해당하는 무슨 메시지를 띄워주거나 추가 동작을 하려 할 때도 모두다 파일에 저장해놓고 읽어서 처리하곤 했다.

 

처음에는 신호나, 처리동작이 단순하고 갯수도 몇개 안되다 보니까 파일에 저장해놓고 쓰는것이 오히려 속도도 빠르고 개발하는데 있어 이미 알고 있는 부분이라 편했는데, 점점 추가 되는것이 많아지다보니 별도의 파일로 분할해서 저장해야 하는 불편함이나 검색에 있어서 속도가 느려지는 불편함이 늘어갔다.

 

그래서 DB관련 공부를 해보자 하고 여러 DB들을 검토해봤는데, 속도면이나 기능적인 면에있어서 postgresql이 가장 우수하다는 평이 많아서 postgresql을 학습 타겟으로 잡았다. 그리고 무엇보다도 이 postgresql은 다른 opensource에 비해 상업용으로 사용해도 무료라고 하니 지금 배워서 바로 상업용 프로그램에 적용 시킬 수 있을 것 같다는 생각이 든다.

 

이제부터 본격적으로 postgresql을 공부해 봐야겠다.

 

 

 

Posted by 굿쟌
,

마일스톤의 XProtect는 Windows Service 형태인 XProtect Event Service를 가지고 있다. 이 서비스는 클래스 라이브러를 만들어 특정 폴더에 놓아 주면, 이 dll를 가져다가 추가 기능을 실행시켜 주는 플러그인 방식을 따르고 있다. 

 

이 플러그인이 실행되는 곳은 XProtect Evet Service라는 Windows Service이고 따라서 디버깅을 위해서는 이 Windows Service가 실행되는 프로세스에 Visual studio를 연결하여 디버깅을 하면된다.

 

하지만 보통 적용하고자 하는 서버에는 Visual studio가 없다보니 디버깅을 하기 어렵고 그렇다고 또 무거운 Visual studio를 깔아서 디버깅 하기는 귀찮아 간단한 로그를 파일로 남기는 기능을 추가 하여 어떤 방식으로 동작하는지 로그를 통해 검사하곤한다.

 

그런데 가끔은 이 Event Service가 실행되고, 플러그인도 적용 된 것이 확실한데 로그가 안남는 경우가 있다. 이것은 Windows Service가 실행되고 있는 사용자(?)와 관련된 문제인데, 만일 Windows Service가 파일을 만들고자 하는 경로에 대한 파일 쓰기 권한이 없는 경우 이런 문제가 발생한다.

 

(로그를 주었는데 왜 먹지를 못하니..)

 

이런 경우, 간단하게 해당 폴더의 속성에서 Windows Servicer가 가지고 있는 계정을 쓰기에 대한 권한을 가진 사용자로 추가 시켜주면 된다.

 

 

 

Posted by 굿쟌
,

32비트 CPU이니 64비트 CPU이니 하는 것은

첫째로 한번에 얼마나 많은 비트수를 CPU로 전달할 수 있느냐?

둘째로 한번에 얼마나 많은 비트수를 CPU에서 처리 할 수 있느냐?

의 문제이다.

 

예를 들자면 16비트 CPU경우, 최대 16비트짜리 정보를 가져와 처리 할 수 있는데, 이 정보라는 것은 컨트롤 유닛이 처리할 명령어 일수도, 혹은 메인 메모리 어딘가를 가리키는 주소값일 수 있다.

 

따라서 16비트 CPU의 경우에, 가지고 놀 수 있는 메모리 주소 범위는 0x0000 ~ 0xFFFF까지가 되고 컨트롤 유닛이 처리 할 수 있는 최대 명령어 크기는 16비트가 된다.

 

그럼 이제 16비트 CPU라는 가정하에 CPU, 정확히는 컨트롤 유닛이 처리 할 수 있는 16비트 짜리 명령어를 설계해보자.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Reserved       명령어         저장레지스터  flag         인자1        flag        인자2

   2 bit             3 bit                 3 bit        1bit          3bit          1bit         3bit

 

처음 2비트는 이후 사용을 위해 보류해놓은 비트다. 이후에 Indirect 모드 사용에 사용된다.

다음 3bit는 명령어 정보이다. 총 8개의 명령어를 표현한다. 000 ~ 111

인자 1, 인자 2에 들어 있는 정보를 가지고 수행 한 값을 저장할 레지스터이다.

인자 1,2 앞에 붙어 있는 값은 인자가 레지스터 정보인지 아니면 0~7사이의 값인지 표현한다.

 

예를 들어,

 

인자 1, 인자 2를 더해라 라는 명령어를 0 0 1

flag값 0을 실제 값, 1을 레지스터로

약속하면..

 

"레지스터 1과 레지스터 2를 더해서 레지스터 0에 저장해라"

"ADD r0, r1, r2" 라는 명령어는 아래와 같다.

 

1

0

 

그럼, "레지스터 1의 값과 4를 더해서 레지스터 0에 저장해라"

"ADD r0, r1, 4" 라는 명령어는? 아래와 같다.

 

1

0

0

 

사실 프로그램이라는 것은 이렇게 CPU가 수행해야 할 명령어들의 모임이라고 봐도 좋다.

이런 명령어를 컨트롤 유닛이 해석하고 그에 합당한 ALU의 동작을 지시한다.

 

이런 프로그램의 명령들은 실행하기 전에는 HDD에 저장되어 있으며, 실행시키게 되면 메인 메모리 상으로 올라오게 된다. 메인 메모리 상의 이 명령어들은 레지스터중 하나인 IR (Instruction Resister)로 전송되어 컨트롤 유닛에 의해 해석되고, 이에 합당한 ALU의 동작이 시행된다. (참고로 ALU의 동작은 무조건 레지스터를 대상으로만 이뤄진다.)

 

 

자, 그럼 이제 레지스터의 값을 메인 메모리 상에 저장하거나, 메모리상의 값을 레지스터로 불러오는 명령어를 설계해보자.

 

먼저 메모리->레지스터 LOAD 명령어는 010으로 한다.

그리고 레지스터->메모리 STORE 명령어는 011로 한다.

다음 3비트는 레지스터 정보.

그리고 마지막 8비트 0x00 ~ 0xFF는 메모리 주소

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Reserved      명령어         저장레지스터                   메인메모리 주소

 

참고로, 메인 메모리 주소는 16비트당 1개씩 올라가고, 레지스터또한 16bit라고 가정한다.

 

그런데 여기서 문제가 있다.

메인 메모리 주소는 16bit 컴퓨터에서 0x0000~0xFFFF까지 인데도,

이 명령어 구조로는 메모리 주소 표현을 위해 8bit밖에 쓰지 못하기 때문에

0x00 ~ 0xFF까지만 접근이 가능하고 그 이후의

0x0100 ~ 0xFFFF까지의 메모리는 접근을 못하는 것이다.

 

그래서 이 명령어 구조를 유지하면서도 모든 메모리 구조를 접근가능 하게 하기 위해서 Indirect mode라는 것이 나왔다.

 

Indirect mode는 아래 그림과 같이 이 명령어 구조로 사용할 수 있는 메모리 주소 0x00 ~ 0xFF의 위치에 저장되어 있는 16비트 값 (0x0000 ~ 0xFFFF)를 메모리 주소로 사용 하겠다는 아이디어다.

 

Indirect Mode는 표시하기 위해 앞으로 우리는 아껴두었던 처음 2bit Reserved bit를 11로 채움으로써 표현 하기로 약속해 보자

 

 

위에 있는 사진은

 

STORE r1,[0x01] 을 의미한다.

(참고로 과로 []는 이 주소값을 indirect모드로 사용한다는 의미이다.]

 

즉, 1번 레지스터에 저장된 10이라는 값을 메인메모리 0x0010에 저장된 값을 메모리 주소로 가지는 메모리 위치에 저장한다는 것이다.  0x0010에는 0x0030이라는 값이 저장되어 있고 0x0030을 메모리 주소로 가지는 0x0030의 16비트에 10의 값을 저장하는 것이다.

 

그런데 여기서 좀 오해를 일으킬 수 있는 여지가 있는데, 이부분을 직접 바이너리 코드를 작성해 보며 살펴 보겠다.

 

먼저 STORE r1, [0x01]을 16비트 명령어로 표현해보자

 

 1

 0

 0

 1

Reserved      명령어         저장레지스터                   메인메모리 주소

 

이 명령어 마지막 8비트로 표현된 주소는 8비트 이므로 0x01 이다. 그런데 실제로 이 값에 메칭되는 메인 메모리 주소는 0x0001로 매칭된다. 이부분이 햇갈릴 수 있으나, 명령어 상의 8비트 주소는 16비트 주소의 앞 8비트가 00이라는 가정하에 메인메모리로 매칭 된다고 생각하면 쉬울 것 같다. 왜 그런지의 문제가 아니라 그러기로 약속 했다는 의미랄까?

 

그리고 메인메모리 0x0001에 저장되어 있는 값은 사실상 0x30이 아니다. 메모리에 저장되어 있는 값 또한 16비트 이기 때문에 0x0030이라고 해야 맞다. 따라서 저장하고자 하는 레지스터 1의 값은 0x30이 아니라 0x0030에 저장된다.

 

그럼 이제, 마지막으로 다음과 같은 시나리오를 직접 컨트롤 유닛이 Decode 할 수 있는 명령어로 표현해 봄으로 써 이 포스트를 마무리 해 보자.

 

목표.

메모리 0x0010에 저장 된 값과 0x0020에 저장 된 값을 더해 0x0100에 저장한다.

 

어셈블리. & 바이너리.

1. MUL r2, 4, 4

 

 (MUL의 명령어는 1 0 0으로 한다.)

0

0

0

 

2. MUL r3, 4, 4

 

0

0

0

 

3. MUL r0, r2, r3 -> 이상태에서 r0에 256, 0x0100이 저장된다.

 

0

0

1

 

4. STORE r0, 0x30 -> 이상태에서 메인메모리 0x0030에 0x0100이 저장된다.

 

0

 0

 0

0

 

4. LOAD r1, 0x10

 

0

 0

 0

0

 

5. LOAD r2, 0x20

 

0

 0

 0

0

 

6. ADD r3, r1, r2 -> r3에 0x0010에 저장 된 값과 0x0020에 저장된 값의 합이 저장된다.

 

1

1

0

 

7. STORE r3, [0x30] -> 0x0030에 저장된 0x0100을 주소값으로 하는 16비트 메모리 위치에

                                  r3가 가지고 있는 16bit값을 저장한다.

 

1

 0

1

 0

0

 

     -> 실제로 이 r3에 저장된 값은 메인메모리 0x0030에 저장된 0x0100을 주소값으로

          가지는 메모리 주소에 저장된다.

 

이러한 indirect mode를 통해, 비록 8bit밖에 안되는 정보공간을 통해서도 16bit 메모리 주소를 관리할 수 있게 되었다. 참 세상엔 똑똑한 사람들이 많은 것 같다.

 

-끝-

'프로그래밍 > WinSysProgram' 카테고리의 다른 글

가상 메모리 (2)  (0) 2015.01.18
가상메모리 (1)  (0) 2015.01.16
간단한 CPU 구조  (0) 2014.10.25
Posted by 굿쟌
,

 먼저 사진들의 출처는 한빛미디어의 "뇌를 자극하는 윈도우 시스템 프로그래밍"에서 발췌하였고 단순히 개인 공부자료를 기록하고자 하는 목적으로 사용하였다. 문제의 여지가 있다면 언제든지 삭제하도록 하겠다.

 

1. CPU 구조

 

 

CPU는 ALU, 레지스터, 컨트롤 유닛, 버스 인터페이스.부분으로 나누어 볼 수 있다.

 

실제로 덧셈과 같은 연산을 수행하는 곳인 ALU로, 일종의 저장공간이 레지스터에 저장된 자료를 이용해서 작업을 수행하고 다시 레지스터에 저장하는 등의 일을 한다. 실제로 망치를 두들기고 못을 박아서 일을 하는 노동자라고 본다..

 

그리고 이 노동자 ALU에게 실제로 어떤 일을 할지 설계도면을 보고 일을 시키는 것은 컨트롤 유닛 이다. 10101000 이런식으로 되어 있는 명령어를 해석 (Decode)해서 일꾼인 ALU에게 지시를 한다.

 

다음으로는 버스 인터페이스다. CPU가 다른 부분과 정보를 주고 받을 수 있게 해주는 부분이다. 버스 인터페이스는 IO 버스와 소통을 하고 IO 버스가 키보드, 모니터, 메인 메모리 등과 소통하는 구조로 되어 있어 CPU가 여러가지 IO장치에 일일히 직접 소통하지 않아도 되는 잇점이 있다. 통신담당자가 전령과만 이야기 하고 전령이 각각 다른곳에 정보를 교환하게 해주는 것이라 생각하면 된다.

 

간단히 요약하자면, ALU-노동자, 컨트롤 유닛-설계시공자, 레지스터-작업장, 버스 인터페이스-통신담당자, IO버스-전령 이라고 할 수 있다.

 

 

'프로그래밍 > WinSysProgram' 카테고리의 다른 글

가상 메모리 (2)  (0) 2015.01.18
가상메모리 (1)  (0) 2015.01.16
16비트 CPU 명령어 설계  (0) 2014.10.25
Posted by 굿쟌
,

1. 비 단일 바이트 캐릭터 셋

 

 알파벳 이나 숫자, 몇가지 특수 기호 같은 경우는 다 합해 봐야 전체 문자 수가 256개를 넘지 않기 때문에, 1바이트 (8 bit)를 가지고서도 충분히 모두 표현이 가능했다. 예를들어 ASCII코드에서는, A는 0x41, a는 0x61 /는 0x2F로 표현이 가능했다. 그러나 컴퓨터가 한자나 한글 혹은 여러가지 문자들을 처리해야 함에 따라서 1바이트로 문자를 처리 하는 방식의 단점이 드러나기 시작했다.

 

 1바이트 만 가지고 256개를 넘어가는 여러 문자들을 관리 할 수 없게 된 것이다. 그래서 이 문제를 해결하고자 2바이트 이상으로 각 문자를 표현하고자 하는 캐릭터 셋이 제안되었다. 캐릭터 셋은 표현하고자 하는 문자와, 그 문자를 어떻게 bit로 표현할지 미리 정해 놓은 테이블이다.

 

대표적으로 문자를 2바이트로 대신하여 표현하는 유니코드가 있다. A는 0x0041, a는 0x0061와 같이 1바이트로 표현할 수 있었던 알파벳도 2바이트로 표현하고 그외의 모든 문자도 모두 2바이트로 표현하는 캐릭터 셋이다. 이외에도 아스키 코드는 1바이트 기타 문자는 3바이트로 표현하는 UTF-8, 아스키 코드는 1바이트로 한글은 2바이트로 표현하는 KCS5601과 같이 저마다 다른 방식으로 문자를 표현하고자 하는 캐릭터셋들이 있다.

 

2. 수신 Byte의 프레이밍 혹은 파싱

 

그런데 이런 문자의 인코딩 방식이 TCP/IP 통신에서는 골치 아픈 걸림돌이 될 수 있다. 예를들어, 내가 "한국화" 라는 문자열을 가상의 HKH-1이라는 캐릭터 셋을 이용해서 인코딩 하고 TCP/IP를 이용하여 상대편에 전송한다고 생각해보자. 아래와 같은 바이트 배열을 연결된 반대쪽에 보낸다고 생각해보자.

 

 한

 0x0001

 국

 0x0203

 화

 0x0405

 ...

 ...

<HKH-1 캐릭터 셋의 일부. 글자를 2바이트로 인코딩한다.>

 

 {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}

<인코딩 된 바이트>

 

리틀 엔디언이니, 빅엔디언이니 전송하는 순서가 바뀌긴 하지만 어찌되었든 간에 보낸 순서대로 받는다고 가정하고 수신하는 쪽에서 이 바이트들을 받아서 어떻게 처리 할지 생각해보자. 단, 받는 쪽에서는 위에 인코딩 된 6바이트를 순서대로 받기는 하나 한번에 받지 못할 수도, 혹은 받을 수도 있다. 왜냐하면 TCP/IP로 전송할 때 전송하기 가장 좋은 싸이즈로 조각내어 전송하기 때문이다.

 

즉, 받는 쪽에서는 {0x00, 0x01, 0x02, 0x03, 0x04, 0x05} 와 같이 한 무더기로 한번에 받을 수도, 혹은 아래와 같이 각 문자를 두번에 나누어, 혹은 문자를 표현하는 2바이트 중 각각의 바이트를 따로 받을 수도 있다.

 

{0x00, 0x01}

{0x02, 0x03, 0x04, 0x05}

 

<나눠서 받는 경우 1>

 

 {0x00, 0x01, 0x02}

 {0x03, 0x04, 0x05}

<나눠서 받는 경우 2>

 

나눠서 받는 경우 1의 경우를 보자. 받는 쪽에서도 HKH-1 캐릭터 셋을 알고 있으니까 이 경우 각각 "한", "국화" 라는 글자를 받아낸 바이트를 통해 알 수 있다. 그런데 문제가 있다. 받는 쪽에서 보낸 사람이 "한국화" 라는 문자열을 보낸건지 "한국화장품" 이라는 문자열을 보낸건지 알 수 있는 방도가 없다. 일단 이 문제를 어떻게 처리 할 것인지 나중에 다시 언급하고 나눠서 받는 경우 2의 경우를 보자.

 

나눠서 받는 경우 2에서는 문제가 더 심각하다. 1의 경우가 가지는 문제에 더불어 "국"을 의미했던 0x02 0x03이 따로 떨어져 있어서 한번 소켓으로 바이트를 받고 받은 바이트를 바로 HKH-1로 디코딩 (바이트를 문자로 바꿈) 할 수가 없다.

 

그럼 이 문제들을 어떻게 처리 해야 할까?

먼저 2번의 경우 생기는 문제는 아래와 같은 방법으로 풀 수 있다.

 

받은 바이트 데이터를 바이트 배열에 계속 차곡 차곡 쌓는다. 

 

이 방법을 통해 인코딩된 2바이트가 따로 수신되더라도 두 바이트를 연계해서 의미해석이 가능해진다. 1 경우 생기는 문제를 해결 하는 방법으로는 아래의 방법이 있다.

 

1. 미리 보내고 받는 쪽 모두 몇바이트를 보낼 것인지 사전에 약속하기 

2. 몇바이트를 보낼 것인지 먼저 전송하기 (starter)

3. 문자가 끝났음을 알리는 바이트 전송하기 (sperator, delimiter 혹은 지시자)

 

먼저, 1은 미리 3글자만 보낸다고 알려주는 것이다. 그러면 받는 쪽에서 무조건 6바이트만 HKH-1로 디코딩하여 글자로 만들면 된다. 그런데 실제 TCP를 이용하는 곳에서는 써먹기가 어렵다. 보내고자 하는 정보가 3글자로 제한되기 때문이다.

 

2번째 방법은 미리 '<', '>' 와 같은 지시자를 이용하여 몇바이트를 보낼지 전송하는 것이다. 전송받은 바이트를 차곡차곡 쌓아가면서 이 바이트 배열에 '<' , '>' 사이에 들어있는 값을 HKH-1로 디코딩 하여 앞으로 추가로 더 오게되는 정보가 몇바이트 인지 알려주는 방법이다. 예를 들어, 아래와 같이 "한국화"를 전송할 수 있다.

 

"<6>한국화" == { 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05}

 <

0x0607 

 6

0x0809

 >

0x0A0B

 한

0x0001 

 국

0x0203

 화

0x0405

 ...

 ...

 

 

 

 

 

 

 

 

 

 

 

그럼 수신하는 쪽에서는 0x0607과 0x0A0B를 찾아서 그 사이에 있는 값을 HKH-1로 디코딩하여 앞으로 오는 몇바이트가 실제로 송신단에서 보낸 문자열인지 알 수 있게 된다.

 

3번째 방법은 보내는 문자열 다음에 문자열의 끝을 알리는 지시자를 전송하는 방법이다. 예를 들어 지시자로 "<>"를 쓴다고 한다면 전체 전송하는 문자열은 "한국화<>" 가 되고 실제 전송되는 바이트는 아래와 같다.

 

한 

 0x0001 

0x0203

0x0405

<

0x0607

>

0x0809

...

 ...

 

이렇게 2, 3번을 통해 실제 의미있는 데이터 범위를 정하는 과정을 프레이밍 또는 파싱 작업이라고 하는데, 이걸 실제로 구현하려고 하면 썩 생각만큼 녹록치가 않다. 아래와 같은 기능들을 직접 구현해야 하기 때문이다.

 

1. 나눠서 받는 데이터를 저장할 수 있는 byte 구조

2. byte구조에서 starter 혹은 delimiter를 검색할 수 있는 기능

3. 필요한만큼 byte구조에서 데이터를 빼고 나머지 데이터를 다시 byte구조에 옮기는 기능

   Queue의 Dequeue 기능과 비슷하나 1바이트가 아닌 복수개의 바이트를 움직이는 점이

   다르다.

 

링크를 통해 연습삼아 구현해 본 코드를 살펴 볼 수 있다. (비공개)

Posted by 굿쟌
,

비동기 OVERLAPPED 읽기 수행하기

 

일단 비동기 read부터 알아보자. 역시나 백문이 불여일견이니 코드부터 보자

 

DWORD CSerialz::ReadData(BYTE *pBuff, DWORD nToRead)
{
 DWORD dwRead;
 DWORD dwError;

 //COMSTAT은 현재 읽기, 쓰기 버퍼등 통신에 관련된 상태를 담는 구조체
 DWORD dwErrorFlags;
 COMSTAT comstat;

 

//장치를 사용 할 수 있도록 오류플래그를 받아오고 오류 상태를 해제한다.

//그리고 장치 상태를 얻어온다.
ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
 
 // 읽기버퍼에 들어와있지만 아직 읽지 못하고 남아있는 바이트 수
 dwRead = comstat.cbInQue;

 

//읽기 버퍼에 아직 읽지 못하고 남아있는 바이트가 남아 있는 경우
 if(dwRead > 0)
 {
    //읽기 버퍼를 nToRead만큼 읽어 들여온다. 그리고 읽기 성공한 바이트 수를 

    //dwRead에 다시 저장 비동기 함수라 바로 FALSE 반환
    if(!ReadFile(m_hIDComDev, pBuff, nToRead, &dwRead, &m_OverlappedRead))
   {
       //정상적으로 읽기 작업이 작업 큐에 저장
       if(GetLastError() == ERROR_IO_PENDING)
      { 
          // timeouts에 정해준 시간만큼 기다려준다.

          //COMMTIMEOUTS에 읽기는 기다리지 않도록 설정한 상태
          while (! GetOverlappedResult(m_hIDComDev,

                                                    &m_OverlappedRead,

                                                    &dwRead,

                                                    TRUE))
          {
               //읽을 때까지 기다렸는데 미리 설정한 타임아웃 이내에 읽지 못한 경우 
               dwError = GetLastError();
               if (dwError != ERROR_IO_INCOMPLETE)
               {

                   //장치의 에러 상태를 해제한다.
                   ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
                   break;

                }
           }
       }

       //작업큐에 읽기 작업이 정상적으로 저장되지 못한 경우
       else
       {
            dwRead = 0;

             //에러상태를 해제한다.
            ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
        }
     }
 }

 

 //실제로 읽은 바이트 수 반환
 return(dwRead); 

 

뭐 딱히 별다른 설명이 필요하지 않다. 읽는 것 자체는 Overlapped write와 크게 다르지 않다. 다르다고 할 만한게 있다면 ClearCommError의 사용 정도가 있겠다. ClearCommError 함수는 파일디스크립터 (핸들) 이 가리키는 IO Device를 사용 할 수 있도록 에러 상황을 받아오고서는 에러 상태를 해제한다. 그리고 IO Device관련된 정보를 받아오는 기능을 한다.

 

이제 우리는 읽기 버퍼로부터 바이트를 읽어 올 수 있게 되었다. 그렇다면 이제 남아있는 문제는 "어떻게 읽기 버퍼로 읽을 수 있는 바이트가 저장되는 것을 알아 차릴 수 있는 가?"이다. 방법은 쓰레드를 이용해 읽기 버퍼에 데이터가 들어올 때 발생하는 이벤트를 감시하고 있는 것인데 쓰레드로 돌릴 함수는 아래와 같다.

 

//COM 포트에서 이벤트가 일어나는지 감시하는 스레드 함수 DWORD CSerialz::THWatchEvPort(CSerialz *pComm)
{
   DWORD  dwEvent;
   OVERLAPPED os;
   BOOL  bOk = TRUE;  
   BYTE  buff[4096]; 
   DWORD  dwBufferCounter;   

 

   // Overap Structure 설정
   memset(&os, 0, sizeof(OVERLAPPED));
   if(!(os.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) bOk = FALSE;

 

   // m_hIDComDev COM 포트의 읽기 버퍼에 데이터가 들어왔을 때 발생하는

   //이벤트및 몇가지 에러 상황을 감지하도록 설정
   if(! SetCommMask(pComm->m_hIDComDev, 

                             EV_RXCHAR  |  EV_BREAK  |  EV_ERR )) bOk = FALSE;

 

   // OVERLAPPED 구조체 설정및 COM포트의 감지 이벤트 설정 중에 문제가

   //발생했으면 return FALSE
   if(!bOk) { 

      AfxMessageBox(_T("Error while Receive Waiting") + pComm->szPort); 

      return FALSE; 

   }

 

   // port가 열려 있는 동안.....포트가 PortClose에 의해 닫히면 m_bPortOnOff

   // 가 FALSE가 되어 종료.
   while(pComm->m_bPortOnOff)  
   {         
      dwEvent = 0;
    
       //이미 이벤트가 발생해 있는 상태라면 TRUE 반환, 이벤트 미발생 상태로

       //펜딩되면 FALSE 반환. OVERLAPPED의 이벤트는 함수 호출시 자동 RESET   
       BOOL result = WaitCommEvent(pComm->m_hIDComDev, &dwEvent, &os);  
  
       //WaitCommEvent함수 호출시 이벤트 발생된 상태로 바로 이벤트 감지
       if(result)
       {
          //DoNothing
        }
        else
       {
           //WaitCommEvent가 PENDING된 경우
           if(GetLastError() == ERROR_IO_PENDING)
          {

              //이벤트가 발생하면 다음단계로 진행
              WaitForSingleObject(os.hEvent,INFINITE);
           }  

          //에러로 인한 이벤트 발생시 스레드 종료
          else
          {
                break;
           }
      }     

      

      //발생 이벤트가 읽기 버퍼에 데이터 수신으로 인한 이벤트인 경우
      if((dwEvent & EV_RXCHAR) == EV_RXCHAR)
      {   // 포트에서 읽을 수 있는 만큼 읽는다.
          do

          {

                //읽기 버퍼에서 4096 (전체 버퍼 싸이즈) 만큼 읽어 들여와서 읽기

                //성공한 바이트 수를 dwBufferConter에 저장한다.
                dwBufferCounter = pComm->ReadData(buff, 4096);    // 데이터 읽기. 
                pComm->FrmTHRcvData(buff, (BYTE)dwBufferCounter);  //data 사용   
           }while (dwBufferCounter); // 읽을 수 있는 data가 없을 때까지 읽어온다.   
       }
       else
      {
          //에러 상황으로 인해 이벤트가 발생한 경우는 그냥 이 스레드를 종료시킴
          break;
       }
    }

 //OVERLAPPED 구조체의 이벤트 핸들을 닫고 스레드 포인터를 NULL로 한다.
 CloseHandle(os.hEvent);
 pComm->m_ThrdEvPort = NULL;

 return(TRUE);
}

 

주석만으로 전체 코드 구성을 이해 하는데 충분하다고 생각한다. 위의 함수를 CreateThread함수나 AfxBeginThread를 이용하여 돌리면, 읽기 버퍼에 데이터가 들어오는 경우 읽기 버퍼에서 더이상 읽을 버퍼가 없을 때까지 읽어들여 온다.

 

이상 RS232를 이용한 시리얼 통신 코드는 링크를 통해 볼 수 있다.(비공개)

 

Posted by 굿쟌
,

OVERLAPPED 구조체를 이용한 비동기 쓰기

 

시리얼 COM 포트 통신을 이용해 데이터를 읽고 쓰는 방법에 대해 알아보자. 이미 이전포스트에서 주욱 설명해 오고 있는것과 마찬가지로 비동기(Overlapped) 읽기, 쓰기에 대해 다룰 것인데 먼저 쓰기부터 알아보려 한다. 왜냐하면 읽기가 좀더 어렵기 때문이다. ㅎㅎ

 

쓰기는 내가 데이터를 모두 가지고 있고 어디부터 어디까지 언제 쓸 것인지를 선택 할 수 있다. 그러나 쓰기에 비해 읽기는 언제 상대방이 나에게 데이터를 전송할지 알 수 없다. 따라서 상대방이 데이터를 전송하여 내가 읽을만한 데이터가 읽기 버퍼에 들어오는 이벤트를 감지하는 쓰레드를 만들어 돌려야 하기때문에 상대적인 난이도가 쓰기에 비해 높다.

 

어찌됐든, 쓰기는 아래와 같은 WriteFile함수를 이용한다.

 

 BOOL WINAPI WriteFile(
  __in         HANDLE hFile,
  __in         LPCVOID lpBuffer,
  __in         DWORD nNumberOfBytesToWrite,
  __out_opt    LPDWORD lpNumberOfBytesWritten,
  __inout_opt  LPOVERLAPPED lpOverlapped
);

 

쓰기가 성공하면 TRUE를 반환하고, 지금 설명하고 있는 비동기 쓰기의 경우에는 무조건 FALSE를 반환한다. 그러나 GetLastError()를 통해 얻어낸 에러가 ERROR_IO_PENDING이면 함수가 실패 한 것은 아니다.

 

인자를 설명 하자면 처음부터, 파일디스크립터 (핸들), 쓸 데이터가 들어있는 버퍼, 몇바이트를 쓸지, 실제로 쓴 바이트 수, OVERLAPPED 구조체의 주소 이다.

 

이전전 포스트에서 설명한 OVERLAPPED가 여기서 쓰이는 것이다.

백문이 불여일견이니 1바이트를 시리얼 통신으로 쓰는 함수를 살펴보자

 

 

BOOL CSerialz::WriteCommByte(unsigned char ucByte)
{
 BOOL bWriteStat;
 DWORD dwBytesWritten;

 

//1바이트를 쓰고 다 쓰면 m_OverlappedWirte.hEvent를 set시킨다. 

bWriteStat = WriteFile(m_hIDComDev,

                              (LPSTR) &ucByte,

                              1,

                              &dwBytesWritten,

                              &m_OverlappedWrite);  

 

//overlapped write는 FALSE를 바로 반환하고 LASTERROR가 ERROR_IO_PENDING

if(!bWriteStat && (GetLastError() == ERROR_IO_PENDING))
{
      if(WaitForSingleObject(m_OverlappedWrite.hEvent, 1000))
          dwBytesWritten = 0;
      else
     {
          GetOverlappedResult(m_hIDComDev,

                                        &m_OverlappedWrite,

                                        &dwBytesWritten,

                                        FALSE);

       

           //사실 이 부분은 잘못된 예이다. 시리얼 통신을 비롯한 모든

           //통신 디바이스 에서는 overlapped.offset이 0으로 설정되어야 한다. 빼도좋다.
           m_OverlappedWrite.Offset += dwBytesWritten;   

      }
 }

 

//의미있는 반환값을 가지려면 쓰기성공한 바이트 수가 0 이상인지 체크해야 한다.

 return(TRUE);

 

 

위 코드에서 쓰인 GetOverlappedResult함수는 write 명령어를 수행할 때 넣었던 OVERLAPPED 구조체를 인수로 하여 해당 write명령어의 결과를 얻을 수 있게 해주는 함수이다.

 

 BOOL WINAPI GetOverlappedResult(
  _In_   HANDLE hFile,
  _In_   LPOVERLAPPED lpOverlapped,
  _Out_  LPDWORD lpNumberOfBytesTransferred,
  _In_   BOOL bWait
);

 

순서별로 인수는 COM 포트 파일핸들, OVERLAPPED 구조체 포인터, 더블워드(4바이트) 값으로 실제로 쓰기 된 바이트 수를 받을 변수에 대한 포인터, 그리고 write가 종료되기까지 기다릴지 말지에 대한 결정을 위한 BOOL 변수이다.

 

마지막 변수 bWait는 아래와 같은 규칙으로 쓰인다.

 

 

1. bWait == TRUE  && OVERLAPPED.internal == STATUS_PENDING

 

   bWait가 TRUE인 경우에 미리 COMMTIMEOUTS에 설정한 타임아웃 값

   만큼 기다린다. 기다리는 도중 완료되면 TRUE 반환 TIMEOUT되면 FALSE 반환

   GetLastError() 함수는 ERROR_IO_INCOMPLETE 반환

 

2. bWait == FALSE : && OVERLAPPED.internal == STATUS_PENDING

 

   bWait가 FALSE인 경우에 완료되길 기다리지 않고 바로 쓰기한 바이트를 넣고 반환된다.

   아직 쓰기작업이 끝나지 않았다면 FALSE를 반환하고 GetLastError() 함수는

   ERROR_IO_INCOMPLETE 반환

 

저 쓰기 구조를 요약해 보자면 아래와 같다.  

 

1. Overlapped로 비동기 write명령을 호출한다.

2. write명령어의 반환이 FALSE이고 GetLastError()의 반환이 ERROR_IO_PENDING이다.

3. write명령어에 쓰인 OVERLAPPED의 이벤트가 SET되길 1000ms 동안 기다린다.

   (참고로 wrtie명령어를 호출 할 때 OVERLAPPED의 이벤트 핸들은 자동으로 RESET됨)

4. write작업이 완료되고 이벤트가 SET되어 다음 단계로 넘어간다.

5. GetOverlappedResult를 이용하여 실제로 쓰기된 바이트 수를 얻는다.

6. 불필요한 통신 IO 이용 파일 핸들에서 OVERLAPPED의 offset 재조정

 

마지막으로 6번이 중요한데 위 코드에서 빨간색으로 색을 칠한 구문은 지난번 포스트에서도 언급한바와 같이 파일핸들이 열고 있는 IO Device가 통신 디바이스 일때는 사실상 쓸 필요도 없거니와 써서도 안된다.

 

어쨋든 요런식으로 1byte를 시리얼 통신으로 쓸 수 있으며, 이 1바이트 쓰기를 반복하여 d아래와 같이 원하는 데이터를 전송 할 수가 있다.

 

// 버퍼로부터 설정된 데이터를 사이즈만큼 쓴다.

int CSerialz::SendData(char *buffer, int size) 

{

    // port가 제대로 설정되어 있지 않을 때 - 쓰기 실패
    if(!m_bPortOnOff || m_hIDComDev == NULL) return(0); 

 

    DWORD dwBytesWritten = 0;

    for(int i=0; i<size; i++)
    {  

        // 여러 바이트를 쓰기 위하여 한 바이트씩 쓰는 함수를 중복 호출
        WriteCommByte(buffer[i]); // 쓰기 위한 함수 호출

 

        //요부분에 writeCommByte의 TRUE FALSE를 분간하는 코드가 있음좋다.
        dwBytesWritten++;
    }

    return((int)dwBytesWritten);  // 쓰기에 성공한 바이트 수 리턴

코드는 지금 공부하고 있는 코드를 복사해서 올리다 보니깐 좀 틀린 부분이 있긴 하지만

일단은 틀린 부분과 추가해야 할 사항들을 추가 하기위해 틀린 코드도 그대로 올리고 있으니 참고하여 코드를 봐야 한다.

 

 

Posted by 굿쟌
,

 이전에 올린 포스트에서 어떻게 시리얼 COM 포트를 여는지, 또 어떻게 OVERLAPPED 구조체를 만드는지와 포트를 이용한 읽기 쓰기 작업에 타임아웃을 거는 방법에 대해 알아보았다. 그런데 뭔가 허전하지 않은가? 맞다. 시리얼 COM 포트를 열긴 열었는데 실제로 이 시리얼 통신이 어떤 특성을 가질 것인지는 설정하지 않았다.

 

그래서 요번에는 DCB (Device Control Block)를 이용한 COM 포트의 시리얼 통신 특성을 설정해 볼 것이다.

 

먼저 시리얼 통신 특성 설정에는 DCB 구조체가 사용된다. 시리얼 통신에 사용되는 여러가지 통신특성들이 설정되어 있는 구조체로 자세한 사항은 아래를 참조하면 되겠다.

 

 

매우 많은 필드와 설명이 있지만, 알아야 되는 필드는 몇개 되지 않는다. 대부분의 설정값은 시스템이 초기에 설정해 놓은 값을 그대로 사용한다. 사용자는 GetCommState함수를 이용해 시스템이 설정해 놓은 COM 포트의 설정값을 가지고 오고 몇몇 변수를 고친다음에 SetCommState로 다시 COM 포트의 설정값을 변경하면 된다.

 

 BOOL GetCommState (HANDLE hFile, LPDCB lpDCB)

 BOOL SetCommState (HANDLE hFile, LPDCB lpDCB)

 

실제 사용은 아래와 같이 한다.

 

 

 

 

 

Get으로 가지고 와서 버드레이트, 패리티, 바이트로 뭉쳐서 쓰기 위해 받는 비트 수, 그리고 스톱 비트를 몇개 사용할 것인지를 결정하여 Set으로 설정했다.

(코드를 그대로 복사하다보니..-_- 위에 접어놓은 DCB 설명을 참조하여 설정할 것)

 

이제 거의 모든 설정이 다 끝났다. 마지막으로 아래 SetupComm 함수를 이용하여 읽기 쓰기에 사용할 큐의 크기를 지정하고 각 큐를 깔끔하게 비워낸다.

 

//읽기 쓰기 큐 4096바이트설정 

SetupComm(m_hIDComDev, 4096, 4096)

//COM 포트를 , 2번째 인자 왼쪽부터 현재 전송작업 취소, 쓰기버퍼 비움, 현재

//읽기 작업 취소, 읽기 버퍼 비움 한다. 라는 의미 

PurgeComm(m_hIDComDev, PURGE_TXABORT | PURGE_TXCLEAR | 

                    PURGE_RXABORT | PURGE_RXCLEAR);

 

비로소 COM 포트를 이용한 시리얼 통신을 할 수 있는 상태가 되었다.

Posted by 굿쟌
,

OVERLAPPED 구조체 설정

 

 오버랩, 비오버랩에 관한 이야기는 이전 포스트에서 한번 언급을 했었다. 요번에는 overlapped 읽기 쓰기에 사용되는 OVERLAPPED 구조체가 무엇인지, 그리고 어떤 의미를 가지고 있는지에 대해 알아 보겠다.

 

 그런데 구조체 내부 구조와 사용 방법에 대해 알아보기에 앞서, 먼저 OVERLAPPED 구조체를 왜 쓰는 것인가에 대해 생각해 볼 필요가 있다. 이전 포스트에서 overlapped 방식의 통신에서는 read와 write가 실제 작업 종료여부와는 상관 없이 호출하자 마자 끝나버린다고 했었다.

 

 그럼 자연히 드는 한가지 의문점은 언제 끝나는지 어떻게 아냐 라는 것이다. 일단 호출해 버리면 바로 반환되어 버리기 때문에, 언제 읽기 쓰기 작업이 종료되었는지 그리고 얼만큼을 성공적으로 썻는지에 대한 정보를 알기가 어렵다.

 

이에 대한 해답으로 나온 것이 바로 OVERLAPPED 구조체로, C#으로 따지자면 비동기 메소드의 ASyncResult 객체와 쓰임새가 비슷하다고 생각해도 좋다.

 

그럼이제 OVERLAPPED 구조체에 대해 살펴보자. OVERLAPPED 구조체는 아래와 같다.

 

typedef struct _OVERLAPPED{

 ULONG_PTR internal;

 ULONG_PTR internalHigh;

 DWORD Offset;

 DWORD OffsetHigh;

 HANDLE hEvent;

}OVERLAPPED;

 

여러가지 인자가 있지만, 시리얼 통신에서 알아 두어야만 하는 인자는 hEvent 뿐이다. 이 이벤트는 overlapped 읽기 쓰기 작업이 종료됨과 동시에 set상태가 되어 이 이벤트 객체를 waitForSingleObject와 같은 함수로 기다릴 수도 있다. (이 이벤트는 bManualReset을 TRUE로 설정하여 생성해도 read, write함수에 인자로 넣는 순간 자동으로 Reset되는 특이성(?) 이 있다.)

 

참고로 시리얼 통신을 공부하면서 가장 헷갈렸던게 바로 OVERLAPPED 구조체의 Offset 인자인데 결론적으로 이야기 하자면 통신 Device에서는 전혀 몰라도 되긴 하다.

 

설명을 좀 하자면.. 먼저 이 OVERLAPPED 구조체가 쓰이는 read write 함수가 어디에 쓰이는지에 대해 알아야 한다. 바로 파일의 비동기 입출력에 쓰인다. 앞서 올린 포스트에서 CreateFile함수가 이름에 충실하게 실제 파일, 그리고 통신 Device까지도 우리가 가지고 작업 할 수 있도록 파일디스크립터 ( 핸들 )을 제공 한다고 했었다.

 

만약 내가 실제로 하드디스크에 100KB 짜리 파일을 쓴다고 생각해 보자. 동기 (NonOverlapped) 쓰기 작업이라고 하면 항상 내가 파일에 쓴만큼 파일포인터는 앞으로 전진할테고 계속해서 나는 그냥 쓰기만 하면 된다. 예를 들어 넓은 논에 모내기를 하는데 나 혼자 심게 되면 어디서 부터 심을지 생각하지 않고, 그냥 처음부터 모를 심을 때 마다 바로 이전에 마지막으로 심었던 모자리 바로 앞에다가 심으면 그만이다.

 

그러나 비동기 (Overlapped) 쓰기 작업은 다르다. 여러 쓰레드가 동시에 읽거나 쓰기 때문에 현재 파일 포인터를 기준으로 쓰거나 읽으려고 하면 차질이 생긴다. 모내기를 예로 들면 2명이서 모내기를 하려고 하는 것과 같다. "미리 나는 0부터 심어 나갈 테니까 너는 50부터 심어나가" 라고 심어나갈 초기 위치를 협의 해야 100짜리 논에 모를 심어 낼 수 있다.

 

따라서 진짜로 파일 쓰기나 읽기에서는 직접 수동으로 읽은만큼 offset을 변경해야 한다.

 

그러나 통신장치에서는 다르다. 그냥 내가 쓰기버퍼에 쓰면 바로바로 FIFO(First In First Out)으로 상대방 읽기 버퍼로 전송되어 쌓이게 된다. (QEUE)  따라서 항상 통신 장치를 Overlapped방식으로 읽기 쓰기 할 때는 이 offset이 0이어야 하며, 0이 아닌 값이 들어가 있어도 무시된다. (0으로 놔두는 것이 좋다.)

 

마지 막으로 OVERLAPPED 구조체의 hEvent를 만들어 주는 것을 보도록 하겠다.

 

 m_OverlappedRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

 

참고로 CreateEvent는 이벤트 핸들을 반환하며 아래와 같은 함수원형을 가진다.

 

HANDLE WINAPI CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
                                          BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
 

 

보통 인자중에 처음과 마지막 인자는 null을 넣어서 사용한다. 첫 인자는 자식 프로세스에 이 이벤트가 상속되지 않음을 명시하는 거라는데, 다른 프로세스까지 이 이벤트가 영향을 미치지 않도록 하는 의미인것 같다. 보통 다중 프로세스 프로그램은 안짜고 다중 스레드만 짜기 때문에 난 그냥 무시하고 NULL을 넣어서 쓴다. 마지막 인자는 이 이벤트의 이름을 의미하는 인자로 그냥 이름 없는 이벤트를 쓰기위해 NULL을 넣는다.

 

두번째 인자 bManualReset은 좀 중요하다. 이벤트는 상태가 Set, Reset 두 가지가 있는데 WaitForSingleObject와 같은 함수로 이 이벤트를 기다리면 Set상태가 될 때까지 블록 상태가 된다. 그리고 Set 상태가 되면 블록이 해제되고 WaitForSingleObject 이후 단계의 코드로 넘어 가는데 이때 bManualReset이 TRUE인 이벤트는 자동으로 다시 Reset상태가 되지 않는다. FALSE인 이벤트는 지가 자동으로 다음 코드로 넘어갈때 다시 Reset상태로 변경된다.

 

세번째 인자는 이 이벤트를 만들 때, 초기 상태를 SET으로 만들꺼냐 말꺼냐를 묻는 인자다 TRUE이면 초기 상태가 SET이 된다.

Posted by 굿쟌
,

타임아웃 설정하기

 

 시리얼 통신 포트에 읽기 또는 쓰기 작업을 수행하려고 하는데 무언가 문제가 생겨서 작업이 진행되지 않는다면 어떻게 해야 할까? 마냥 하염없이 기다리다보면 프로그램은 영영 돌아오지 못하게 된다. 그래서 시리얼 통신에서는 COMMTIMEOUTS구조체를 제공하여 언제까지 작업을 시도해 볼 것인지를 정 할 수 있게 해놓았다.

 

일단 COMMTIMEOUTS 구조체는 아래와 같다.

 

 typedef struct _COMMTIMEOUTS{
  DWORD ReadIntervalTimeout;
  DWORD ReadTotalTimeoutMultiplier;
  DWORD ReadTotalTimeoutConstant;
  DWORD WriteTotalTimeoutMultiplier;
  DWORD WriteTotalTimeoutConstant;
}COMMTIMEOUTS、*LPCOMMTIMEOUTS;

 

인수를 맨 위에꺼 부터 설명하자면,

 

ReadIntervalTimeout은 한바이트를 읽고 다음 바이트를 읽는데 기다릴 시간으로 ms단위

 

ReadTotalTimeoutMultiplier & ReadTotalTimeoutConstant 는 묶어서 설명 하자면

기다릴 시간=읽기요청 바이트 수*ReadTotalTimeoutMultiplier+ ReadTotalTimeoutConstanct

위와 같은 계산식을 따라 기다릴 시간을 설정한다.

 

WrtieTotalTimeoutMultiplier & TotalTimeoutConstant도 묶어서 설명하자면,

기다릴 시간=쓰기요청 바이트 수*WriteTotalTimeoutMultiplier+ WriteTotalTimeoutConstanct

위와 같은 계산식을 따라 기다릴 시간을 설정한다.

 

참고로 여기서 아주 중요한 것이 하나 있는데 ReadIntervalTimeout이 0xFFFFFFFF 즉, MAX_DWORD이고 ReadTotalTimeoutMultiplier와 Constant가 0인 경우에는 재밋게도 현재 읽으력고 하는 버퍼에서 그냥 있는 만큼 읽고 바로 그냥 작업을 종료해 버리게 된다.

 

예를 들어 아래와 같이 타임아웃 구조체를 설정했다고 하면

 

 

 

Read에는 없으면 없는데로 있으면 있는데로 읽기 버퍼에 작업을 수행하는 그때에 읽고 작업을 마친게 된다. 그리고 쓰기 작업에는 얼마나 많이 쓰든지 상관 없이 5000ms 즉, 5초까지는 계속 쓰기 작업을 시도한다.

 

그럼 이제 남은 것은 이 타임아웃 구조체를 시리얼 통신 포트에 적용 하거나, 이미 적용되어 있는 정보를 가져 오는 것인데 이 작업에는 아래의 두가지 함수가 사용된다.

 

 BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeOuts )

 BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts )

 

인자는 뭐 딱 봐도 통신에 사용할 시리얼 포트의 파일디스크립터 (핸들)과 앞서 설명한 COMMTIMEOUTS객체의 포인터 이다. 실패시 0을 반환한다.

 


    지정된 통신 장치에서 모든 읽기, 쓰기 작업에 대해 타임아웃 매개변수를 설정

 

Posted by 굿쟌
,