2015년 4월 2일 목요일

임베디드 개발 환경에서 필요한 RS232 관련 지식

임베디드 개발 환경에서 아직까지도 가장 많이 사용되는 터미널 인터페이스는 RS-232이다. RS-232와 이와 관련된 내용을 모두 이해하는 것은 아주 어려운 일이므로 개발에 꼭 필요한 내용 위주로 정리한다.

임베디드 환경에서는 대부분의 경우 RX, TX, GND 3개의 연결을 사용한다. RS-232 연결에 사용되는 여러 종류의 커넥터가 있는데, 현재 가장 많이 사용되는 커넥터는 9 pin connector이다. 25 pin connector는 1990년대 이후 사용되는 것을 본 기억이 없다.  9 pin connector는  DE-9 혹은 DB-9 커넥터라고 불리는데 DE-9이 올바른 명칭이나 DB-9으로 더 많이 불린다.

9 pin connector에서 RX,TX, GND는 각각 2번, 3번, 5번 pin이므로 나머지 5핀은 대부분의 경우 사용되지 않는다. HW Flow Control이라는 기능이 필요할 경우 다른 핀을 사용 하지만 많이  사용되지는 않는다.

9 pin connector는 숫놈과 암놈이 있는데 보통 개발 호스트 컴퓨터에는 숫놈(Male) 커넥터가 타겟 보드에는 암놈(Female) 커넥터가 사용된다.  숫놈 커넥터는 (일반적으로) DTE 장치에 사용되고, 암놈 커넥터는 (일반적으로) DCE 장치에 사용한다.  즉 개발 호스트 컴퓨터를 DTE 장치로 생각하면 된다.  DTE/DCE는 전화기와 모뎀을 사용하여 아날로그 라인으로 통신하던 시절부터 사용해온 명칭으로 컴퓨터를 DTE (Data Terminal Equipment)장치, 모뎀등 통신 장치를 DCE(Data Communication Equiptment) 장치라고 불렀다.  이제와서는 별 의미 없는 명칭이지만, DTE인지 DCE인지에 따라 PIN의 데어터 방향이 결정이 되고 되고, 커넥터의 암수도 바뀌므로 개발 타겟 보드를 DTE로 설정할지 DCE로 설정할지 미리 고민해야 한다.

임베디드 개발 환경에서는 보통 타겟 보드를 호스트 PC와 연결하므로 호스트 PC가 DTE, 타겟보드를  DCE 장치로 설정하는 경우가 많다.  이 경우 DTE 장치에 사용되는 숫놈 커넥터의 3번 핀이 DCE장치의 3번 구멍에 연결되며 DTC to DCE 방향으로 데이터가 전달된다. 2번 핀은 반대 방향인 DCE to DTE로 데이터가 전달된다. (DTE-DCE 연결 시 2은 2번끼리 3번은 3번끼리 연결한다.)

DTE 장치끼리 통신이 필요할 경우 2번과 3번을 서로 꼬아서 연결해 주어야 하며 이렇게 서로 크로스하여 배선한 케이블을 널 모뎀 케이블이라고 부른다.  만일 타겟 보드가 DTE 장치로 설정되어 있다면, DTE인 호스트 PC와 연결하기 위해서 NULL모뎀 케이블이 필요하다. (타겟 장치에 숫놈 커넥터가 달렸다면 DTE장치가 달렸다고 생각하면 된다. 예전에는 이런 경우가 드물었지만, 요즘은 라즈베리 파이 같이 타겟보드에 리눅스등의 PC용 OS가 사용되는 경우가 많아 타겟의 serial포트가 DTE 로 설정된 경우가 많아졌다)

최근에는 PC에 serial port(COM포트)가 거의 장착되어 있지 않으므로 개발 보드에 연결하기 위하여 대부분의 경우 Usb2Serial 장치를 사용한다. Usb2Serial 장치는 일반적으로 커넥터가 숫놈이며 당연히 Host PC가 DTE 장치가 되지만, 암놈 커넥터를 가진 Usb2Serial장치를 사용하면 Host PC가 DCE 모드로 동작하게 된다. 따라서 개발 보드가 DTE 장치라면 널모뎀 케이블을 사용하여 Host PC와 연결(DTE to DTE) 하거나 Host PC에 DCE로 동작하는 (암놈 커넥터를 가진) Usb2Serial장치를 사용하면 된다.

간혹 대책없이 DTE 장치에 암놈 커넥터를 달거나 DCE 장치에 숫놈 커넥터를 다는 경우가 있으니 골탕먹지 않도록 주의하자.

2014년 12월 21일 일요일

Loop device

이전글 "디스크 파티션 분석"에서 램디스크를 사용하여 가상의 블럭장치를 만들었다. 가상의 블럭 장치를 만들기 위한 더 일반적인 방법은 루프 장치를 사용하는 것이다. 아래와 같은 명령으로 루프 장치를 만들 수 있다.

먼저 dd 명령으로 가상 블럭 장치로 사용할 512KB 크기의 VirtualDisk.img라는 파일을 만든다. 
# dd if=/dev/zero of=VirtualDisk.img bs=512 count=1024  
1024+0 records in
1024+0 records out
# ls -l ./VirtualDisk.img 
-rw-rw-r--   1 root      root         524288 Dec 21 15:50 ./VirtualDisk.img

이제 devb-loopback을 실행시켜 해당 파일을 루프 장치로 연결한다. (루프 장치란, 파일을 블록장치로 매핑한 pseudo-device를 일컫는다. 블록 장치이므로 포맷 후 원하는 디렉토리에 마운트하여 사용할 수 있다.  루프 장치를 루프백 장치라고도 부르는데, 루프백 장치는 네트워크 장치에서 사용하는 용어와 겹치므로 루프백 장치라고 부르면 안된다는 의견이 있으나, QNX Neutrino에서는 루프백 장치라는 용어를 사용한다)
# devb-loopback loopback blksz=512 fd=./VirtualDisk.img
# ls -l /dev/lo*
brw-------   1 root      root         9,   0 Dec 21 15:54 /dev/lo0
# df -P /dev/lo* 
Filesystem            512-blocks      Used Available Capacity  Mounted on      
/dev/lo0                    1024      1024         0     100%   

참고: 만일 리눅스에서 루프 장치를 만든다면 다음과 같이 해야 한다.
$ sudo losetup /dev/loop0 ./VirtualDisk.img

아래를 보면 Sector(Block Size)크기가 512 이며 1024개의 실린더로 생성 되었음을 알 수 있다.
# ls -l /dev/lo* 
brw-------   1 root      root         9,   0 Dec 21 15:59 /dev/lo0
# df -P /dev/lo0
Filesystem            512-blocks      Used Available Capacity  Mounted on      
/dev/lo0                    1024      1024         0     100%                  
# fdisk -z /dev/lo0

FDISK
Ignore Next Prev Change Delete Boot Unboot Restore Loader Save Quit

        _____OS_____     Start      End     ______Number_____    Size    Boot  
        name    type    Cylinder  Cylinder  Cylinders  Blocks                  

--> 1.  ______ (___)    _______   _______   _______   _________  _____
    2.  ______ (___)    _______   _______   _______   _________  _____
    3.  ______ (___)    _______   _______   _______   _________  _____
    4.  ______ (___)    _______   _______   _______   _________  _____


 Choose a partition by typing the partition number OR moving the pointer
 with the UP/DOWN arrows.
 Then, choose one of the actions on the top line of the screen.
          


Drive : /dev/lo0                    Config:     1 Heads
Size  : 0 Mbytes                                1 Sectors/track
Loader: None                                 1024 Cylinders
Blocks: 1024                                  512 Block Size

                                    Last cylinder is 1023

이전글 "디스크 파티션 분석"에서 QNX fdisk가 실린더 경계에서만 파티션 시작이 가능하여 MBR/VBR/EBR등이 위치한 실린더에 첫번째 섹터를 제외한 나머지 섹터들이 낭비되었는데, 위와 같이 실린더당 섹터가 하나씩만 가지도록 설정하면 이러한 낭비를 막을 수 있다. 물론 실제 물리 블록 장치의 경우 실린더당 섹터는 물리적으로 정해져 있으므로 낭비는 피할 수 없다. (참고: 리눅스의 fdisk는 섹터 단위로 파티션 위치를 설정할 수 있으므로 이러한 낭비가 발생하지 않는다. )

# mkdosfs /dev/lo0 

Format complete: FAT12 (4096-byte clusters), 492 kB available.

별로도 파티션을 만들지 않고 위와 같이 통째로 포맷을 하면, MBR없이 첫 섹터에 VBR이 생성되며 사용 가능한 상태가 된다. (물론 파티션을 나눌 수도 있다)
# mount -tdos /dev/lo0 /mnt/dos

# mount
/dev/hd0t179 on / type qnx6 
/dev/lo0 on /mnt/dos type dos (fat12) 

# df -P /mnt/dos
Filesystem            512-blocks      Used Available Capacity  Mounted on      
/dev/lo0                     984         0       984       0%  /mnt/dos/   



2014년 12월 18일 목요일

디스크 파티션 분석

MBR(Master Boot Record)은 블록장치의 첫512바이트로 부팅 가능한 파티션을 찾는 짧은 코드와 블록장치의 파티션 정보를 가지고 있다. 기본적으로 MBR에는 총 4개의 파티션 정보를 저장할 수 있으며, 이 4개의 파티션 정보 영역에 Primary 파티션 혹은 Extended 파티션에 대한 정보를 저장할 수 있다.

MBR에 적재되어 있는 작은 코드는 MBR 뒷부분에 포함되어 있는 파티션 테이블의 정보를 사용하여 부팅 가능한 파티션이 있는지 확인 후 해당 파티션의 VBR (Volume Boot Record)을 로드하고 실행한다. VBR의 코드는 해당 파티션에 설치된 OS를 메모리에 로드하고 실행하는 역할을 한다. 만일 블록장치 전체를 파티션 생성없이 통으로 포맷하여 사용할 경우 MBR없이 MBR자리에 VBR이 생성될 것이다.  VBR이 저장되어 있는 영역을 흔히 부트섹터(boot sector)라고 부른다.

파티션 테이블은 64바이트 크기로 4개의 파티션 정보가 16바이트씩 기록되며 이 16 바이트에는 각 파티션의 시작과 끝 위치, 크기(# of sector)등이 기록되어 있다. 이 4개의 파티션은 Primary 혹은 Extended 파티션이 될 수 있다. Primary 파티션은 부팅 가능한 파티션이므로 별다른 이유(4개 이상의 파티션 필요등)가 없다면 굳이 (부팅 불가능한) Extended 파티션을 사용할 필요가 없다. Extended 파티션은 하위 파티션을 담고 있는 껍데기 파티션으로 역시 MBR에 시작과 끝 위치, 전체 파티션 총 크기(# of sector)가 기록되어 있다.  Extended 파티션 안에는 한개 이상의 Logical 파티션이 들어 있으므로,  각각의 Logical 파티션의 시작, 끝 위치, 크기를 파악하기 위해서는  Extended 파티션의 첫 512바이트의 EBR (Extended Boot Record )을 읽어야 한다.  EBR은 MBR/VBR과 동일한 구조를 가지기 때문에 4개의 파티션 저장을 위한 공간이 있으나, 이중 첫 2개의 파티션 정보만 사용한다.

MBR/VBR/EBR 의 구조                                    offset
---------------------------------------------------- 0x0000
부트 코드 :
  MBR: 부팅 가능한 파티션을 찾아 VBR을 메모리로 로드하고 실행
  VBR: OS를 메모리에 로드하고 실행.
  EBR: empty ?
---------------------------------------------------- 0x01BE
파티션 테이블:                            0x01BE ~ 0x01FD    
Signature (0xAA 0x55)                  0x01FE ~ 0x01FF
-----------------------------------------------------0x0200


EBR의 첫번째 파티션 정보에는 첫 Logical 파티션에 대한 시작위치와 끝위치가 기록되어 있으며, 두번째 파티션 정보는 다음 EBR에 대한 위치를 가지고 있다.  따라서 Logical 파티션의 갯수만큼 EBR이 존재하게 되며, 각 EBR은 1개의 logical 파티션에 대한 정보와 다음 EBR에대한 정보를 가지고 있게 된다.  (Linked List를 생각하면 된다.)

정확한 이해를 위해 QNX Neutrino OS상에서 간단한 실험을 통하여 MBR/EBR의 구조를 확인해 보자. 먼저 아래와같이 테스트를 위한 램디스크를 생성한다.

분석 편의를 위해서 24KB(48개의 512 Byte 블럭)의 작은 램 디스크를 만들었다.
# devb-ram disk name=notuse ram capacity=1,nodinit blk ramdisk=24k

참고: 리눅스에서 테스트를 한다면 램디스크 대신 루프백 장치를 사용하여 동일한 테스트를 진행할 수 있다. "루프백 장치"를  참고한다. 

생성 내용 확인 - 48개의 512Byte 블럭이 생성 되었다.
# df -P /dev/ram0    
Filesystem            512-blocks      Used Available Capacity  Mounted on      
/dev/ram0                     48        48         0     100%                  

생성된 가상의 램 디스크가 어떻게 물리 장치를 시뮬레이트하는지 보자. fdisk가 보여주는 정보에 따르면,  6개의 실린더,  8개의 sector(block), 1개의 track을 가진 물리 장치를 시뮬레이트하고 있는 것으로 보인다. 실린더당 크기는 8x512=4096=4KB이다.  

# mount -e /dev/ram0
참고: QNX Neutrino에서의 mount -e 명령은 리눅스에서 partprobe 정도에 해당한다. 



# fdisk -z /dev/ram0

FDISK
Ignore Next Prev Change Delete Boot Unboot Restore Loader Save Quit

        _____OS_____     Start      End     ______Number_____    Size    Boot  
        name    type    Cylinder  Cylinder  Cylinders  Blocks                  

    1.  ______ (___)    _______   _______   _______   _________  _____
    2.  ______ (___)    _______   _______   _______   _________  _____
    3.  ______ (___)    _______   _______   _______   _________  _____
    4.  ______ (___)    _______   _______   _______   _________  _____


 Choose a partition by typing the partition number OR moving the pointer
 with the UP/DOWN arrows.
 Then, choose one of the actions on the top line of the screen.
          


Drive : /dev/ram0                   Config:     1 Heads
Size  : 0 Mbytes                                8 Sectors/track
Loader: None                                    6 Cylinders
Blocks: 48                                    512 Block Size


최종 목적은 24KB의 작은 디스크를 아래와 같이 파티션 하는 것이다. 

------------------------------------------------- 0x0000   ---  A
cy0 : MBR    (0x0000~0x01FF)
              Boot Code       (0x0000~0x01BD)
              Partition Table (0x01BE~0x01FF)
      Unused (0x0200~0x0FFF)
------------------------------------------------- 0x1000   ---- B
cy1 : 1st Primary Partition
      VBR                     (0x1000~0x11FF)      
                              (0x1200~0x1FFF)
------------------------------------------------- 0x2000   ---- C
cy2 : EBR    (0x2000~0x21FF)
              Boot Code       (0x2000~0x21BD)
              Partition Table (0x21BE~0x21FF)
      Unused (0x2200~0x2FFF)
------------------------------------------------- 0x3000   ---- D
cy3 : 1st Logical Partition      
      VBR                     (0x3000~0x31FF)
                              (0x3200~0x3FFF)
------------------------------------------------- 0x4000   ---- E
cy4 : EBR    (0x4000~0x41FF)
              Boot Code       (0x4000~0x41BD)
              Partition Table (0x41BE~0x41FF)
      Unused (0x4200~0x4FFF)
------------------------------------------------- 0x5000   ---- F
cy5 : 2nd Logical Partition
      VBR                     (0x5000~0x51FF)
                              (0x5200~0x5FFF)
------------------------------------------------- 0x6000   ---- G

(주의!! - 실제로 Primary파티션이나 Logical 파티션은 생성 후 생성 시 지정한 파일시스템으로 포맷해야 해당 파티션을 사용할 수 있게 된다. 하지만 파일시스템별로 필요한 최소 크기가 있으므로 위와 같은 크기로 파티션하면 실제로 포맷은 불가능하게 된다. 실제 포맷까지 테스트 하려면 fdisk시 해당 파티션의 크기 - 실린더 갯수를 늘려야 한다.  위와 같은 크기로 파티션시에는 포맷시 에러가 발생할 것이다. )

이제 첫번째 파티션을 Primary파티션으로 생성해 보자. QNX Neutrino의 fdisk utility는 실린더 경계에서만 파티션이 가능하다. 첫번째 실린더(0번 실린더)에 MBR이 있고, 이 크기가 비록 512바이트 이지만, 첫 파티션을 실린더 경계에 만들려면 1번 실린더에서 시작하여야 한다. 따라서 0번 실린더는 대부분(8개 sector중 7개 sector) 낭비된다 (리눅스의 fdisk는 실린더가 아닌 섹터 단위로 파티션 경계를 설정할 수 있으므로 이러한 낭비 없이 파티션 생성이 가능하다) . 파티션의 크기 또한 실린더 크기의 배수가 되므로 최소 크기인 실린더 1개로 지정하기 위하여 시작 위치 끝위치 모두 1번 실린더로 설정한다. (-c1,1)  따라서 이 파티션은 1번 실린더 전체를 차지하며 4KB가 된다. QNX Neutriono fdisk 사용 시  파일 시스템 타입을 지정하지 않으면 기본 값인 type 77( QNX 4 file system)으로 생성된다. 

# fdisk /dev/ram0 add -s1 -c1,1 
# fdisk /dev/ram0 show

     _____OS_____     Start      End     ______Number______   Size    Boot  
     name    type    Cylinder  Cylinder  Cylinders   Blocks                 

1.   QNX       77          1          1         1          8      0 MB
2.   ------   ---   --------   --------   -------  --------  -----          
3.   ------   ---   --------   --------   -------  --------  -----          
4.   ------   ---   --------   --------   -------  --------  -----          



방금 Primary파티션을 생성한 것이므로 MBR에 해당 내용이 모두 기록되었을 것이다. 아래와 같이 od 명령으로 내용을 확인해 보자. 

# od -Ax -tx1 -v /dev/ram0 | less 
0000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
...
00001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00001C0 01 01 4d 00 08 01 08 00 00 00 08 00 00 00 00 00 
00001D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00001E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00001F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa 

512바이트의 마지막 2바이트는 55 aa로 끝나며, 이는 해당 512바이트 정보가 valid함을 의미한다. 이는 MBR뿐만 아니라 EBR이나 VBR에도 동일하게 존재한다.

파티션 정보의 시작은 0x01BE부터 이며, 각 파티션당 16바이트이다. 
따라서 첫번째 Primary파티션의 정보는 아래와 같다. 

00 00 01 01 4D 00 08 01 08 00 00 00 08 00 00 00

비트별 정보는 다음 사이트를 참조하여 해석가능하다. (http://en.wikipedia.org/wiki/Master_boot_record)

해석해보면 아래와 같다.  파티션 시작 위치 및 끝 위치는 little endian을 고려하여 해석해야 한다. 

00                    :  80이면 부팅 가능한 파티션
00 01 01          :  파티션 시작 위치 (CHS표기):  1번 실린더 1번 sector
4D                   :  filesystem type : 0x4D, QNX 4 filesystem
00 08 01          :  파티션 끝위치 (CHS표기): 1번 실린더, 8번 sector
08 00 00 00     :  파티션 시작위치 (sector단위,디스크 시작부터 8번째 sector이후 시작),  위 그림에서 A~B
08 00 00 00     :  파티션 크기 (sector단위) 8개 sector, 위 그림에서 B~C

이제 나머지 공간(실린더 2,3,4,5) 전체에 extended 파티션(-t5)을 생성한다. 

# fdisk /dev/ram0 add -s2 -t5 -c2,5 
# fdisk /dev/ram0 show

     _____OS_____     Start      End     ______Number______   Size    Boot  
     name    type    Cylinder  Cylinder  Cylinders   Blocks                 

1.   QNX       77          1          1         1          8      0 MB
2.   Extd'd     5          2          5         4         32      0 MB
2.1  Unused     0          3          5         3         24      0 MB
3.   ------   ---   --------   --------   -------  --------  -----          
4.   ------   ---   --------   --------   -------  --------  -----          

MBR을 확인하면 아래와 같이 내용이 추가된다. 

# od -Ax -tx1 -v /dev/ram0 | less 
0000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
...
00001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00001C0 01 01 4d 00 08 01 08 00 00 00 08 00 00 00 00 00 
00001D0 01 02 05 00 08 05 10 00 00 00 20 00 00 00 00 00 
00001E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00001F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa 

추가된 내용
00 00 01 02 05 00 08 05 10 00 00 00 20 00 00 00


해석해보면 아래와 같다. 
00                    :  80이면 부팅 가능한 파티션
00 01 02          :  파티션 시작 위치 (CHS표기):  2번 실린더 1번 sector
05                    :  filesystem type : extended 파티션 
00 08 05          :  파티션 끝위치 (CHS표기): 5번 실린더, 8번 sector
10 00 00 00     :  파티션 시작위치 (sector단위, MBR 시작부터 16번 sector이후 시작) ,  위 그림에서 A~C
20 00 00 00     :  파티션 크기 (sector단위)  32개 sector,  위 그림에서 C~G

앞서 설명한바와 같이 Extended 파티션의 경우 껍데기 파티션이고, 이 안에 한개 이상의 실제 파티션이 존재하며, 이 각각의 실제 파티션에 대한 정보는 EBR이라는 곳에 저장된다.  EBR은 논리 파티션의 개수만큼 생기는데, 아직 logical 파티션을 생성하지 않았으므로 EBR이 존재하지 않아야 하지만, 위에서 보듯이 Extended 파티션 생성시 Unused라는 name을 가지는 dummy logical 파티션이 자동으로 생성되어 있다.  그럼 여기에 해당하는 dummy EBR이 생성되었는지 확인해 보자. 첫번째 EBR은 Extended 파티션 시작(2번 cyliander 1번 sector) 첫 512바이트에서 찾을 수 있다.  cyliander당 크기가 4KB이므로 2번 cliandaer의 시작 위치는 8KB, 즉 0x2000이 된다.  

00021A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa 

보는바와 같이 아직 모든 data가 00이며 MBR/EBR임을 나타내는 signature "55 aa" 만 확인할 수 있다. 

그럼 실제로 첫번째 logical 파티션을 생성해보자. -s2는 MBR의 두번째 파티션, -t3는 FAT파일 시스템,  -e1은 첫번째 logical 파티션, -n1은 실린더 1개 할당을 뜻한다. 

# fdisk /dev/ram0 add -s2 -t3 -e1 -n1
# fdisk /dev/ram0 show

     _____OS_____     Start      End     ______Number______   Size    Boot  
     name    type    Cylinder  Cylinder  Cylinders   Blocks                 

1.   QNX       77          1          1         1          8      0 MB
2.   Extd'd     5          2          5         4         32      0 MB
2.1  nonQNX     3          3          3         1          8      0 MB
2.2  Unused     0          5          5         1          8      0 MB
3.   ------   ---   --------   --------   -------  --------  -----          
4.   ------   ---   --------   --------   -------  --------  -----          


첫번째 logical 파티션(2.1 nonQNX로 표기됨)에 대한 EBR은 아래와 같다. 
00021B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021C0 01 03 03 00 08 03 08 00 00 00 08 00 00 00 00 00 
00021D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa 

해석해보면 아래와 같다. 
00                    :  80이면 부팅 가능한 파티션
00 01 03          :  파티션 시작 위치 (CHS표기):  3번 실린더 1번 sector
03                   :  filesystem type : 3 (FAT 파일 시스템)
00 08 03          :  파티션 끝위치 (CHS표기): 3번 실린더, 8번 sector
08 00 00 00     :  파티션 시작위치 (sector단위, 이 EBR 시작부터 8번 sector이후 시작),  위 그림에서 C~D
08 00 00 00     :  파티션 크기 (sector단위)  8개 sector,  위 그림에서 D~E

3번 실린더까지 사용하였으므로 이제 4번, 5번 실린더가 미사용인 상태이고, logical파티션을 추가 생성하면 4번 실린더의 첫 512바이트에 다음 EBR이 기록될 것임을 추측할 수 있다.  실제 사용 가능한 파티션은 5번 실린더 하나가 될 것이다. 

실제로 두번째 logical 파티션 추가해보자. (-e2)

# fdisk /dev/ram0 add -s2 -t3 -e2 -n1 
# fdisk /dev/ram0 show                

     _____OS_____     Start      End     ______Number______   Size    Boot  
     name    type    Cylinder  Cylinder  Cylinders   Blocks                 

1.   QNX       77          1          1         1          8      0 MB
2.   Extd'd     5          2          5         4         32      0 MB
2.1  nonQNX     3          3          3         1          8      0 MB
2.2  nonQNX     3          5          5         1          8      0 MB
3.   ------   ---   --------   --------   -------  --------  -----          
4.   ------   ---   --------   --------   -------  --------  -----         



먼저 첫번째 EBR에 새로운 logical 파티션을 위한 두번째 ERD 위치 정보 추가되는 것을 확인할 수 있다. 
00021B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021C0 01 03 03 00 08 03 08 00 00 00 08 00 00 00 00 00 
00021D0 01 04 05 00 08 05 10 00 00 00 10 00 00 00 00 00 
00021E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00021F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa 


해석해보면 아래와 같다. 
00                    :  80이면 부팅 가능한 파티션
00 01 04          :  파티션 시작 위치 (CHS표기):  4번 실린더 1번 sector
05                   :  filesystem type : 5 (extended partition)
00 08 05          :  파티션 끝위치 (CHS표기): 5번 실린더, 8번 sector
10 00 00 00     :  파티션 시작위치 (sector단위, 첫번째 EBR 시작부터 16번 sector이후 시작),  위 그림에서 C~E
10 00 00 00     :  파티션 크기 (sector단위)  16개 sector,  위 그림에서 E~G


4번 실린더에 있는 두번째 EBR을 확인해 보자, 4번 실린더의 시작 주소는  16KB(0x4000)이다. 

00041B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00041C0 01 05 03 00 08 05 08 00 00 00 08 00 00 00 00 00 
00041D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00041E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00041F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa 

해석해보면 아래와 같다. 
00                    :  80이면 부팅 가능한 파티션
00 01 05          :  파티션 시작 위치 (CHS표기):  5번 실린더 1번 sector
05                   :  filesystem type : 3 (FAT 파일 시스템)
00 08 05          :  파티션 끝위치 (CHS표기): 5번 실린더, 8번 sector
08 00 00 00     :  파티션 시작위치 (sector단위, 두번째 EBR 시작부터 8번 sector이후 시작),  위 그림에서 E~F
08 00 00 00     :  파티션 크기 (sector단위)  8개 sector,  위 그림에서 F~G

참고 : 더 알기 쉽게 정리되어 있는 사이트
http://cappleblog.co.kr/131

2014년 11월 14일 금요일

QNX Neutrino APS 사용하기

항상 네트워크 throughput이 10MB/s 이상을 유지해야 하는 장치를 만든다고 가정하자. 테스트 결과 선택된 시스템의 최고 네트워크 throughput은 60MB/s다. 사용자가 네트워크 기능만 사용한다면 througput이 충분하여 별 문제가 없을 것이다.  하지만 시스템이 다른 작업을 동시에  수행하게 되면 네트워크 througput이 10MB/s 이하로 떨어지는 상황이 발생할 수 있다. 이를 해결하기 위한 간단한 방법은 네트워크 througput 관련된 프로그램의 우선 순위를 높이는 것이다. 하지만 이렇게 할 경우 단지 10MB/s 정도만 유지해도 별 문제가 없는 네트워킹 기능에 필요이상의 CPU가 할당되면서 다른 기능이 원활하게 동작하지 못하는 상황이 발생할 수도 있다. 즉  10MB/s 정도의 througput이면 충분한데 네트워킹 쪽 우선순위가 높아 항상 60MB/s의 성능을 내도록 시스템이 동작할 것이다.

위와 같은 경우 10MB/s 정도의 througput을 처리할 수있는 별도의 전용 CPU가 있으면 어떨까?  QNX의 APS(Adaptive Partitioning Scheduler) 는 한개의 CPU를 논리적으로 여러개의 파티션으로 나누어 위와 같이 특정 프로그램를 위한 전용 CPU로 사용할 수 있도록 해준다(Partitioning).

예를들어 CPU의 총 성능 중 20%만 사용하면 네트워크 througput 10MB/s가 달성된다면 CPU를 80:20로 나누어 네트워크 관련 프로그램을 20% 영역에서 실행시키는 것인다.  이 경우 나머지 프로그램은 80%영역에서 실행되며 사용자가 느끼기에는 마치 서로 영역별로 별개의 CPU를 사용하는 것처럼 스케쥴링이 이루어진다.

이 기능이 제공하는 더 좋은 점은, CPU가 100% 풀 로드가 아닌 상황에서는 파티션이 가변적(Adaptive)으로 동작한다는 점이다. 즉 시스템에 네트워크 기능이 동작하지 않을때는 80%영역에 있던 프로그램이 20%영역의 CPU를 사용할 수 있으며, 반대로 80% 영역에 여유가 많을 때에는 네트워크 관련 프로그램이 80%쪽 CPU를 끌어다 사용할 수 있다.

APS 기능을 사용하려면 IFS 이미지 빌드 스크립트에 아래와 같이 [module=aps]를 추가하여 IFS를 빌드하여야 한다.
[module=aps] PATH=/proc/boot:/bin:/usr/bin:/opt/bin  LD_LIBRARY_PATH=/proc/boot:/lib:/usr/lib:/lib/dll:/opt/lib procnto-instr
즉, 만일 현재 사용중인 IFS가 [module=aps]가 없이 빌드된 것이라면 IFS를 바꿔야 한다.

APS 기능은 아래와 같이 실행시킬 수 있다.



위 예제에서는 inetd에 20%의 CPU가 할당되었다. QNX에서 실제로 네트워킹은 io-pkt가 담당하게 되는데, 이 경우에 io-pkt는 서버, inetd는 클라이언트가 된다.  위 예에서 io-pkt가 System쪽에서 실행되었다고 가정하면 inetd의 요청을 받은 io-pkt 서버는 비록 시스템 파티션에서 실행 되었지만 Network partition에서 CPU를 사용하고 inetd 이외의 프로그램이 요청한 네트워크 요청만 System partition에서 수행이 된다.

2014년 11월 13일 목요일

QNX Neutrino에서 램 디스크를 생성하는 여러 가지 방법 (v6.6기준)

1. devb-ram을 사용한 램 디스크 만들기.

1.1 기본값으로 생성하기 
devb-ram을 사용하면 QNX 4 타입의 파일 시스템을 가지는 램 디스크를 메모리에 생성할 수 있다. 아래 그림에서 기본 2MB 용량을 가지는 QNX 4 타입의 램 디스크가 생성 되었음을 확인할 수 있다. 


hd0는 type179 형식(QNX 6 파일 시스템 형식)으로 시스템 메인 하드 디스크이며, 생성된 램 디스크는 hd1로 type 77 형식(QNX 4 파일 시스템 형식)으로 생성되었다. devb-ram이 지원하는 램디스크의 파일 시스템은 QNX 4 파일 시스템이다. 하지만 뒤에서 다른 형식의 파일 시스템을 사용하여 램 디스크를 만드는 방법도 살펴볼 것이다.  

아래 표는 파일시스템 형식별 type번호 표이다.



1.2 장치 이름 바꾸기
기본 생성 시 /dev/아래 hd* 로 생성되는 장치 이름을 disk 옵션 중 name으로 바꿀 수 있다. disk 옵션은 devb-ram이 cam-disk.so 라이브러리로 전달하는 옵션이다. 



1.3  cache 크기 설정
일반적으로 입출력이 느린 블럭 장치는 램 메모리를 cache로 사용하는데, 램 디스크일 경우 cache는 램이 램을 cache하는 것이라 불필요한 성능 저하를 발생시키므로 최소 크기(512k)로 설정하는 것이 좋다. 아쉽게도 최소 크기는 0 바이트가 아니라 512KB 이다. 하지만 설정 시 0으로 설정해도 에러가 나지는 않는다.  cache 설정을 위해서는 blk옵션 중 cache를 사용한다. blk옵션은 devb-ram에서 사용하는 io-blk.so 라이브러로 전달되는 옵션이다. 


Q.) 실제 설정된 cache의 크기는 어떻게 확인할 수 있을까 ? 0으로 설정시 최소 크기인 512k로 설정되는지 확인하는 방법은 ? 

A) blk에 verbose옵션을 추가한다. 서브옵션이 여러가지 일 경우 ,(콤마)를 사용하여 나열하면 된다.
devb-ram disk name=rdsk blk cache=0,verbose
verbose옵션으로 추가된 로그는 sloginfo 명령으로 확인할 수 있다.


1.4 용량 변경
기본으로 생성되는 ramdisk는 512Byte의 blksize와 4096Byte의 capacity를 가진다. 실제 용량은 이 두 값의 곱으로 결정된다. (512 x 4096 = 2MB). 이 두 값은 ram 옵션을 사용하여 변경 가능하다.  아래와 같이 설정시 10MB(10240 x 1024)크기의 ramdisk가 생성된다. 



2. io-blk 내장 기능을 사용하여 램 디스크 만들기 

2.1 기본 사용 방법
devb-ram을 사용할 경우 기본적으로 cache를 사용하게 되므로 램 디스크에서는 불필요한 메모리 복사가 발생하게 된다. 이를 해결하기 위해서 io-blk.so에 내장된 ramdisk 기능을 사용할 수 있다. (이 ramdisk는 cache를 사용하지 않는다.)  io-blk.so는 어떤 블럭 장치 드라이버(devb-xxx)와도 함께 사용이 가능하므로 devb-ram이 아닌 다른 devb-xxxx를 사용하고 있다면, 굳이 devb-ram을 사용할 필요는 없다. 

아래 예에서는 devb-ram과 함께 사용하였는데, 이때 기본적으로 devb-ram이 생성하는 ramdisk를 사용하지 않을 것이므로 이름을 notuse로, 용량은 최소 용량(1 x 512 = 512 byte)으로 설정하였다. 또한 ram 옵션 중 nodinit을 설정하여 생성한 램 디스크를 파티션 및 포맷하지 않도록 한다.  이런한 설정들은 devb-ram이 아닌 다른 블럭 장치 드라이버를 사용할 경우에는 필요가 없다.  ( 여기서는 io-blk.so의 램 디스크 기능을 사용하는 경우이기 때문에 devb-ram의 램 디스크 기능을 최소화 하기 위해서 필요한 설정임) 이번 예에서도 io-blk가 생성하는 램 디스크 용량은 10MB로 하였다.  io-blk.so가 제공하는 램 디스크 기능을 사용 시에는 램 디스크 장치 이름은 변경할 수 없으며 항상 /dev/ram* 로 고정된다. 



- dinit는 생성 된 램 디스크를 파티션하고 QNX 4 타입의 파일 시스템으로 포맷하여 준다.
- cache=0으로 설정 되었지만 512KB가 최소 값이므로 512KB로 설정된다.  이는 램 디스크가 사용하는 것이 아닌, devb-ram이 사용하는 것이다. 위 예에서는 devb-ram이 제공하는 램 디스크를 사용하지 않으므로 이 cache는 그냥 낭비되는 공간이 된다. 하지만 다른 devb-xxx 드라이버를 사용하여 io-blk.so의 램 디스크 기능을 사용한다면, 이 캐시는 해당 블럭 장치가 사용하게 될 것이다. 

This approach has superior performance because it eliminates the memory-to-memory copies of devb-ram, it bypasses cache lookups, and the 4 KB sectors have smaller overheads.


2.2 다른 type의 파일 시스템 사용하기
devb-ram가 제공하는 램 디스크는 QNX 4 형식의 파일 시스템만 사용이 가능하지만, io-blk.so의 내장 램 디스크는 qnx4 파일 시스템 이외에 다른 파일 시스템을 사용할 수 있다. 그러기 위해서는 dinit대신 fdisk를 사용하여 파티션을 수행한 후 원하는 파일 시스템으로 포맷하면 된다. 아래는 dos 파일 시스템을 사용한 예이다. 


By default in QNX Neutrino 6.5 and later, io-blk.so allocates the filesystem buffer cache (blk cache=) on affected ARM platforms from a global memory region (SHMCTL_ANON | SHMCTL_GLOBAL) to avoid the per-process 32 MB limitation. To override this and make the allocation from the normal devb-* process heap, specify blk memory=sysram.
In QNX Neutrino 6.5 and later, io-blk.so by default allocates the filesystem buffer cache (blk cache=) on affected ARM platforms from a global memory region (SHMCTL_ANON | SHMCTL_GLOBAL) to avoid the per-process 32 MB limitation. To override this and make the allocation from the normaldevb-* process heap, specify blk memory=sysram.
 
3. devf-ram을 사용한 램 디스크
devf-ram은 ram상에 QNX의 NOR flash용 파일 시스템인 ffs3를 만들어 준다. 아래와 같이 /dev/fs* 가 생성되며, flashctl 유틸리티를 사용하여 파티션, 포맷 및 파일 시스템에 mount할 수 있다. devf 파일 시스템은 mount 명령은 지원하지 않는다. 
flashcltl에서 -p 옵션으로 마운트할 장치를 선택하고, -e -f 는 각각  erase및 format을 뜻한다. -n으로 mount할 위치를 정하며, -m이 mount 옵션이다. 




주의: 디스크의 크기는 실제 NOR flash의 크기와 마찬가지로 2의 n제곱 단위로 설정해야 한다. (1MB, 2MB,4MB,8MB ...)  예를들어 10MB로 2의 n제곱이 아닌 값으로 설정할 경우에는 10MB가 할당되지 않는다.

4. io-fs-media
io-fs-media를 사용하면 아래와 같이 사용이 가능하다. 

io-fs-media -d tmp,mount=/full_path_to/your_desired_mountpoint

5. /dev/shmem
기본적으로 QNX Netrion는  /dev/shmem에 RAM-based filesystem을 제공한다. 하지만 이는 완전한 파일 시스템이 아니며, 100% POSIX 호환이 되지는 않는다. 따라서 몇몇 utility사용에 제약이 있으며, sub-directory도 지원하지 않는다.  일반적으로 아래와 같이 /dev/shmem을 /tmp에 링크시켜 사용한다. 
ln -sP /dev/shmem /tmp

2013년 9월 30일 월요일

stack frame 해석하기

GDB에서 출력되는 frame 정보 해석

Sample Program
------------------------------------------------------------------------------------------------------
1  #include <stdio.h>
2
3  int func1(int arg1, int arg2);
4  int func2(int a1,int a2);
5
6  int func1(int arg1, int arg2)   //시작주소는 0x804841C, gdb에서 x func1명령으로 확인 가능
7  {
8      int temp=3;
9      temp = func2(arg1, arg2); // 이 명령 위치는 0x0804843b, 본문 참조
10    return temp;
11 }
12
13
14 int func2(int a1, int a2)   //시작주소는 0x8048443, gdb에서 x func2명령으로 확인 가능
15 {
16     int tmp=9;
17     return a1+a2+tmp;
18 }
19
20 int main(void)   //시작주소는 0x804845f, gdb에서 x main명령으로 확인 가능
21 {
22     int a=10, b=40,c=0;
23  
24     c = func1 (a,b); //이 명령의 위치는  0x08048494, 본문 참조
25
26     printf(" c = %d\n");
27
28     return 0;
29 }
30
------------------------------------------------------------------------------------------------------
(gdb) bt
#0 func2 (a1=10,a2=40) at a.c:17
#1 0x0804843b in func1 (arg1=10,arg2=40) at a.c:9
#2 0x08048494 in main ( ) at a.c:24
------------------------------------------------------------------------------------------------------

#0 func2 (a1=10,a2=40) at a.c:17 
   - 현재 sample program의 파일 이름이 a.c
   - program이 a.c파일의 17번째 라인이 실행 될 차례임.
   - func2가 호출될 때 a1=10, a2=40으로 호출되었음  위 내용은 frame 명령으로 재확인 가능
----------------------------------------------
(gdb) frame
 #0 func2(a1=10, a2=40) at a.c:17
17                       return a1+a2+tmp;
----------------------------------------------

#1 0x0804843b in func1 (arg1=10,arg2=40) at a.c:9
   - func2는 func1에서 호출되었고 이는 a.c파일 Line 9에서 호출됨

#2 0x08048494 in main ( ) at a.c:24
   - func1은 a.c파일에 있는 main 함수의 Line 24 에서 호출됨

------------------------------------------------------------------------------------------------------
(gdb) info frame 1
Stack frame at 0xbffff160
  eip = 0x804843b in func1 (a.c:9); saved eip 0x8048494
  called by frame at 0xbffff190, caller of frame at 0xbffff130
  source language c.
  Arglist at 0xbffff158, args: arg1=10, arg2=40
  Locals at 0xbffff158, Previous frame's sp is 0xbffff160
  Saved registers:
    ebp at 0xbffff158, eip at 0xbffff15c
------------------------------------------------------------------------------------------------------

(gdb) info frame 1
   - func1번의 stack frame 정보 요청

Stack frame at 0xbffff160
   - func1의 stack frame은 0xbffff160부터 아래쪽(낮은 주소)임.
   - 0xbffff160 - 4 부터 낮은 주소쪽으로 func1의 stack frame임.  (stack은 높은 주소에서 낮은 주소 방향으로 자라남)

eip = 0x804843b in func1 (a.c:9); saved eip 0x8048494
   - func1에서 func2를 호출한 func1의 코드 위치는 a.c파일의 9라인이며 이 주소는 0x0804843b(eip)임.  stack frame상의 최하위 프레임(주소가 낮은)의 경우 이 값이 현재의 cpu의 instruction pointer값임.
   - saved eip는 func1이 호출된 후 실행되어야 할 명령이 있는 main함수상에서 func1호출 다음에 있는 명령의 주소임.

called by frame at 0xbffff190, caller of frame at 0xbffff130
   - func1를 호출한 함수(main)의 stack frame은 0xbffff190이며, func1이 호출한 함수(func2)의 stack fame은 0xbffff130임.  호출할수록 stack frame의 주소가 작아짐.

source language c.
    
Arglist at 0xbffff158, args: arg1=10, arg2=40
    - func2의 argument는 2개 이며 각각 10, 40의 값을 가지고 호출 되었음.
    - 0xbffff158은 func2의 bp이며 arg1은 bp +8, arg2는 bp+12
    - bp(base pointer)과 arg1사이(bf+4)에는 return address가 있음.

Locals at 0xbffff158, Previous frame's sp is 0xbffff160
     
Saved registers:
    ebp at 0xbffff158, eip at 0xbffff15c
   - func2의 ebp는 0xbffff158임.  ebp의 역할은 해당k 함수에서 기준 위치로 첫번째 변수는 ebp-4, 두번째 변수는 ebp-8등으로 stack상의 지역변수를 ebp로 부터의 offset으로 표시함.  (꼭 첫번째 변수가 ebp - 4가 되는 것은 아님)
   - func2의 eip는 0xbffff15C에  저장되어있으며 이는 func1에서 func2를 호출한 다음 명령의 주소임. 이 값은 위에 saved eip값으로 확인 할 수 있음.
    
0xBFFFF190 : 이 아래부터 main의 stack frame
------------------------------------------------------------
0xBFFFF18C : main의 return address
0xBFFFF188 : main의 bp
...
0xBFFFF17C : main의 변수 c  =>  -12(%ebp)와 같이 표시됨 bp로 부터 12바이트 아래 있음.
0xBFFFF178  : main의 변수 b =>  -16(%ebp)
0xBFFFF174  : main의 변수 a
...
0xBFFFF164  : arg2 = 40
0xBFFFF160  : arg1 = 10  : 이 아래부터 func1의 stack frame
------------------------------------------------------------
0xBFFFF15C : func1의 return address : main함수에서 func1이 return되면 다음에 실행할 명령의 주소(ip:instruction pointer)
0xBFFFF158 : func1의 bp
...
0xBFFFF14c : func1의 변수 temp  => -12(%ebp)와 같이 표시됨 bp로 부터 12바이트 아래 있음.
..
0xBFFFF134 : a2 = 40
0xBFFFF130 : a1 = 10 : 이 아래부터 func2의 stack frame
------------------------------------------------------------
0xBFFFF12C: func2의 return address
0xBFFFF128: func2의 bp
0xBFFFF124: func2의 첫번째 변수 tmp  -4(%ebp)k


각 함수의 시작 주소는 아래와 같이 확인 가능
------------------------------------------------------------------------------------------------------
(gdb) x func1
0x804841C
(gdb) x func2
0x8048443
(gdb) x main
0x804845f

------------------------------------------------------------------------------------------------------
(gdb) x/20i func2
  ==>  func2시작부터 20줄의 역어셈블 코드가 출력됨  i는 역 어셈블하라는 뜻

2013년 7월 1일 월요일

printf와 시스템 성능 그리고 UART baud rate

CPU 사용율이 높지 않음에도 불구하고 이유없이 프로그램 수행 속도가 극도로 느려지는 문제가 발생. 예상되는 원인은 과다한 로그 출력. 임베디드 환경에서는 로그 출력이 UART를 통하여 이루어 지는 경우가 많은데, 이 때 UART의 baud rate이 프로그램 동작의 병목으로 작용하는 것이다. 비록 버퍼가 있다고 하지만, 로그양이 버퍼양을 넘어서면 printf등의 함수도 바로 리턴하지 못하고, 대기할 수 밖에 없을 것이다.  하지만 로그가 많다고 무턱대도 이를 줄이는 것도 쉽지 않은 일이다. 각각의 로그는 각 개발자들이 문제 발생을 확인하고 해결하기 위하여 최적화하여 넣어놓은 것으로 이를 제거하면 나중에 디버깅이 어려워지거나 불가능해 지기 때문이다.

가설 검증을 위해 로그 출력에 사용되는 stderr/stdout용 serial(UART)장치 즉 /dev/ser1의 baud rate을 올려보았다. 문제 해결!!!  수정된 이미지를 배포하지만 얼마 지나지 않아서 생산기술에서 연락이 온다. Baud rate 변경으로 인해 생산 테스트 장비를 모두 바꿔야하고, Baud rate 상승으로 인한 통신 에러 증가가 예상되어 절대로 Baud rate를 변경하면 안된단다.   다시 원점.

Baud rate이 115200 bps라면 대충 계산해도 115,200/8=14,400 character 이상의 로그는 (1초에)출력이 불가능하다. 실제로는 UART 전송 시 설정에 따라  Start/Stop bit 및 1 char당 bit수 그리고 char간 delay 등을 고려해야하고 이 경우 실제 전송 가능한 char수는 14,400보다 훨씬 적어지게 된다.

따라서 Baud rate을 올리는 방법으로 문제 개선이 가능하지만, 예상치 못한 문제로 인하여 Baud rate는 올리지 못하는 것으로 결론.

어림 계산으로 1초에 10,000 글자가 전송 가능하다고 하고, 라인당 100글자를 넘지 않는다고 하면, 1초에 100줄 정도의 로그가 출력 가능. 로그가  1초에 100줄이 넘지 않는다는 가정하에 로그 출력을 담당하는 전담 thread를 만든다면 다른 thread는 baud rate과 상관 없이 동작 가능할 것 이다.

로그를 출력하는 매크로를 사용하여 printf 대신 매크로를 사용하여 메모리에 로그를 기록하고, 메모리는 mutex로 보호한다.  로그 출력 thread에서는 계속해서 메모리에서 로그를 읽어와 puts로 출력하도록 하여 문제 해결. (단 1초에 100줄 이상의 로그가 지속적으로 발생한다면, 로그용 메모리 버퍼가 full나면서 mutex block이 걸리는 시간이 늘어나 다시 시스템 성능이 느려질 수 있다. )


Linux Vs QNX Neutrino 파일 디스크립터 관련

리눅스에서는 한 프로세스에서 파일 디스크립터가 할당 될 때마다 그 값이 증가하게 된다. 예를 들어 어떤 프로세스가 처음으로 파일을 open할 경우 3이 할당되며, 이후 4,5,6 순으로 파일 디스크립터가 할당되어 나간다. (0,1,2는 각각 stdin, stdout, stderr에 할당되어 있다) 프로그램이 실행하여 나가면서 파일이 close되는 경우 해당 파일 디스크립터가 반환되게 되는데, 파일 디스크립터가 할당 될 때에는 값이 항상 증가만 하므로, 중간에 반환된 이러한 파일 디스크립터들은 바로 재사용되지 않게 된다. 계속된 할당으로 파일 디스크립터가 사용 가능한 최대 값에 이르면 비로서 다시 0 부터 시작하여 반환된 파일 디스크립터를 찾아서 할당에 사용하게 된다.

최근에 경험한 바에 의하면, QNX Neutrino OS에서는 파일 디스크립터가 반환되면, 바로 다음 open 요청에 재사용되게 된다. 아마도 파일 디스크립터 할당 시, 항상 가장 작은 사용 가능한 파일 디스크립터를 반환하는 식으로 구현이 되어 있는 것으로 추정된다.  이 경우 user application의 상당한 주의가 요구된다. 만일 어떤 파일 디스크립터를 close 한 후에 실수로 한번 더 close를 할 경우, 두번째 close하는 파일 디스크립터가 이미 다른 thread에서의 open에 재사용되는 경우가 발생할 수 있기 때문이다. 이 경우 영문을 모른채 file이 open되자마자 close되어 버리는 문제가 발생하고, 이는 꽤 원인을 파악하기 어려운 문제가 된다.

리눅스에서는 파일 디스크립터를 계속 증가시키는 방법으로 이러한 문제를 영리하게 피해간 것으로 보이는데, QNX Neutrino에서는 관련해서 수정 계획이 없다고 하니, 계속해서 주의가 필요할 것으로 보인다.


2013년 5월 9일 목요일

ISO26262관련 정리

ISO26262에는 5개의 등급이 있으며, 등급별 FIT(Failure In Time)는 아래와 같다.
즉 1 FIT는 10^9 시간(11만 4155년)동안 에러가 발생하는 횟수를 의미한다.

                 QM         ASIL A       ASIL B       ASIL C       ASIL D
--------------------------------------------------------------------------------------
FIT      1000이상   1000이하   100이하    100이하     10이하
  • FIT = Failure in Time = 1 failure / 10^9 hours
  • 1 year MTTF = 109 / (24 * 365) FIT = 114,155 FIT
  • SER FIT = SDC FIT + DUE FIT               

가령 1000년동안 어떤 HW 장치가 동작중 1번만 failure가 발생하여도 114 FIT이므로, ASIL level B를 만족시키지 못하게 된다.

표에서 알 수 있듯이 ASIL D를 만족시킨다는 것은 10 FIT이하아며 이는 10^8 시간 동안 에러가 한번 이하로 발생하는 것이다. (1만 1415년동안 1회 이하의 에러발생)

추가 정보:
  • SDC = Silent Data Corruption
  • DUE = Detected + Unrecoverable Error
  • SER = Soft Error Rate =  SDC + DUE 
  • MTTF = Mean Time to Failure
  • MTTR = Mean Time to Repair
  • MTBF = Mean Time Between Failures = MTTF + MTTR
  • Availability = MTTF / MTBF