2023년 12월 31일 일요일

AARCH64 bare metal 로 동작 시키기 - 2

디버깅:

BSP작성시 Trace32같은 장비를 사용하기도 하지만, qemu상에서 개발하므로 gdb를 사용하여 개발이 가능하다.  gdb사용을 위해 아래와 같이 qemu를 시작한다.

$ qemu-system-aarch64 -M virt,virtualization=on,gic-version=3 -m 4G -smp 2 -kernel my_bl3.elf -S -gdb tcp::1234,ipv4 -nographic

-S옵션을 주었으므로 virt machine은 시작되자마자 suspend상태로 gdb를 기다린다.  이제 다른 터미널에서 "gdb-multiarch"를 실행하여 gdb를 연결한다. 


$ gdb-multiarch -q ./my_bl33.elf

Reading symbols from ./my_bl33.elf...

(gdb) target remote:1234

Remote debugging using :1234

_start () at ./src/_start.s:10

10              mrs     x0, mpidr_el1       // get the CPU ID


참고 자료:

아래 책들을 참조하였다. 

1. ARM 64-Bit Assembly Language by Pyeatt Ph.D., Larry D (amazon.com)

이 책에서 부팅 후 초기 ASM code 와 PL011 UART 기기를 사용한  입출력 방법에 관한 정보를 얻을 수 있었다. 

하지만, 이 책에서는 ARM RaspberryPI를 기준으로 설명하므로 GIC를 사용한 SPI/PPI 처리 방법에 대한 정보가 없었고, generic timer 처리 관련 정보 또한 얻을 수 없었다. 

2. 임베디드 OS 개발 프로젝트 | 이만우 - 교보문고 (kyobobook.co.kr)

내가 구현하고자 하는 것과 거의 동일한 작업을 armv7 에서 구현하고 설명한 책으로 개발 진행에 큰 도움이 되었다.  특히 printf 구현 관련 부분은 나의  주된 관심사가 아니었지만 개발 진행에 꼭 필요한 부분이었기 때문에 재구현하지 않고 이만우님의 코드를 사용하여 개발을 진행 하였다. 

3. 시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리 | 김동현 - 교보문고 (kyobobook.co.kr)

Armv8 및 GICv3 관련 정보를 얻을 수 있었다. 

4. ARM 공식 문서

GICv2 : ARM Generic Interrupt Controller Architecture Specification - Version 2.0 (B)
GICv3 : Arm A-profile Architecture Registers


Exception Vector table:

1. aarch64에서는 exception vector table주소가 고정되어 있지 않고, VBAR_ELx 레지스터에 설정하면 된다.  다만 이 레지스터들은 Bit 63:11 만 사용되므로, 이 주소에 맞도록 시작 위치가 align만 되면 된다. 

         .align      11 

2. exception vector는 EL별로 존재하는데, 우리는 EL1만 관심이 있으므로 EL1용 만 작성한다. 

3. exception vector는 아래와 같이 4가지 부분으로 나뉜다. 

- exception from sp0 : 사용 안함.
- exception from current el : 현재 el에서 exception 발생 시 사용.
- exception from lower el(aarch64) : 현재 el보다 낮은 el에서 64bit모드로 실행 중 exception발생 시 사용
- exception from lower el(aarch32) : 현재 el보다 낮은 el에서 32bit모드로 실행 중 excepton발생 시 사용. 

boot loader를 항상 el1에서 동작하도록 작성한다면, "exception from current el" 부분에만 실제로 의미 있는 코드를 작성하면 된다. 

4. exception은 아래와 같이 4가지 종류로 나뉜다

- sync
- irq
- fiq
- serror

일반적으로 fiq는 secure world에서 사용한다. 우리의 목표를 구현하기 위해서는 irq만 처리하면 된다. 


프로그램 시작 주소

qemu실행 시 -M virt, virtualization=on,dumpdtb=my_bl.dtb ... 와 같이 dumpdtb를 지정하여 virt machine의 dtb파일을 얻을 수 있음. 

- dtb파일을 text형식인 dts로 변환할 수 있다. 

$ dtb -I dtb -O dts -o my_bl.dts ./my_bl.dtb

아래와 같이 메모리 주소가 0x40000000으로 표시됨. 

 18 »   memory@40000000 {
 19 »   »   reg = <0x00 0x40000000 0x01 0x00>;
 20 »   »   device_type = "memory";
 21 »   };


따라서 작성하는 프로그램을 0x40000000 이후에 로드하면 실행 가능함.  이때 해당 프로그램의 link script에 로드할 주소를 지정하여야 함. 

  6 SECTION
  7 {
  8  . = 0x40400000;
  9  . = ALIGN(8);

위와 같이 링커 스크립트 파일을 설정하여 빌드한 후 qemu에서 elf포맷으로 로드하면 (-kernel my_bl3.elf ) qemu가 알아서 해당 위치에 로드시켜 준다. 

- u-boot의 경우 0x40000000에 dtb를 로드하고 그 뒷부분에 u-boot코드가 로드되며 u-boot가 실행하는 프로그램은 0x40400000 이후에 로드해야만 실행 가능하다.(go addr)  추후 u-boot 를 사용하는 경우를 고려하여 0x40000000대신 0x40400000으로 시작 주소를 설정하였다. 

ARM GIC interrupts:

SGI : S/W generated interrrupt (INTID #0 ~ #15) : IPI

PPI : Private Peripheral interrupt (INTID #16 ~ #31) : 특정 core용

SPI : Shared Peripheral interrupt (INTID #32 ~ #1019) : 일반 interrupts

LPI : Local Peripheral interrupt (INTID #8192 ~) - MSI : for PCI device

 

GIC 설정:

매뉴얼을 보면 너무 많은 register들이 있어 어떤 순서로 어떤 레지스터를 설정하면 좋을지 알기 어렵다.  하지만 virt machine 동작에 꼭 필요한 레지스터들은 많지 않았다. 다만 실제 HW에서는 추가 설정이 필요할 수도 있을 것 같다. 

설정 순서는 gicd->gicr->gicc로 하였다. 

1. Distributor / gicd설정

a. Disable GICD_CTRL

b. (GICv3 only) SPI에 대해 Secure/Non-secure group을 Non-secure group 1로 설정

    group status=1 GICD_IGROUPR

    group modifier=0, GICD_IGRPMODR

c. Route SPI interrupts to CPU0 ( GICD_ITARGETSR )

d. Interrpt trigger type설정 ( GICD_ICFGR ) 

    : SPI는 모두 level trigger로 설정, PPI는 모두 edge trigger로 설정

e. Enable GICD_CTRL


2. Redistributor / gicr설정(GICv3 or later only) 

a. SGI/PPI에 대해 Secure/Non-secure group을 Non-secure group1로 설정

b. Enable SGI/PPI : GICR_ISENABLER0

c. PPI Interrup trigger type설정 (GICR_ICFGR1)

Redistributor는 GICv3에서 도입되었기 때문에 GICv2에서는 설정이 필요하지 않다. 어떤 CPU로도 라우팅이 가능한 SPI는 gicd에서 설정하고, 실행 core가 결정된 SGI/PPI는 gicr에서 설정하는 것으로 보인다.  

3. CPU interface / gicc설정

a. GICC_PMR for GICv2, ICC_PMR_EL1 for GICv3  : Priority Mask설정

b. GICC_CTRL = 1 for GICv2,  ICC_IGRPEN1_EL1 = 1 for GICv3

- Priority Mask로 허용할 최저 interrupt 우선순위를 설정한다. 우선순위는 0~255인데 0이 가장 높다. 0xFF로 설정하면 대부분의 우선순위를 가지는 인터럽트가 모두 허용된다. 

- CPU interface register라고 불리는 gicc 레지스터는 GICv2에서는 memory mapped레지스터였지만, GICv3에서는 system register가 되어 access 하는 방법이 달라졌다. (mrs/msr사용)

- gcc compiler에서는 aarch64의 cpu interface register이름을 해석하지 못하므로 이를 적절히 해석해주는 매크로가 필요하다. 

ex)
#define ICC_IGRPEN1_EL1     S3_0_C12_C12_7


Timer Interrupt

- ARM generic timer의 INTID = 30이다. (PPI)
- Trigger type을 edge로 설정해주어야 동작함 - Redistributor설정 시 일괄 설정함

아래는 gic-version=3 옵션을 사용한 virt machine의 dts파일의 일부이다. 

385 »   timer {
386 »   »   interrupts = <0x01 0x0d 0x04 0x01 0x0e 0x04 0x01 0x0b 0x04 0x01 0x0a 0x04>;
387 »   »   always-on;
388 »   »   compatible = "arm,armv8-timer\0arm,armv7-timer";
389 »   };

- 매뉴얼 및 dtb파일에는 level trigger(0x4)로 표시되어 있음.  확인 필요. 
- Interrupt가 0x0d, 0x0e, 0x0b, 0x0a 4개가 표시되어 있는데, 이 번호가 PPI시작 주소인 16을 더해주면 29,30,27,26 이 된다. generic timer의 INTID는 30인데, 왜 DTS에는 4개 의 interrupt가 표시되는지 확인 필요. 

구현은 아래 내용을 참조하였다.
https://lowenware.com/blog/aarch64-gic-and-timer-interrupt

UART interrupt:

dtb파일에서  UART장치가 pl011이라는 것과 시작 주소가 0x9000000이라는 것,  INTID가 33이라는 것을 알 수 있음.   

308 »   pl011@9000000 {
309 »   »   clock-names = "uartclk\0apb_pclk";
310 »   »   clocks = <0x8000 0x8000>;
311 »   »   interrupts = <0x00 0x01 0x04>;
312 »   »   reg = <0x00 0x9000000 0x00 0x1000>;
313 »   »   compatible = "arm,pl011\0arm,primecell";
314 »   };

- dtb파일의 interrupt번호는 SPI의 번호이므로 INTID로 변환을 위해 32를 더해주어야 함.( 1 + 32 = 33(INTID)
- interrupt는 level trigger방식임. (0x04)


그 외 필요해 보이는 추가 설정:

gicd:

GICD_ICENABLER

GICD_ICPENDR

GICD_PRIORITY

GICD_CTLR( GICD_CTLR_ARE_NS | GICD_CTLR_EN_GR1_NS )

GICD_I ROUTER


gicr:

GICR_TYPER

GICR_IPRIORITY0

GICR_WAKER

GICR_ICPENDR0

GICR_ICENABLER0




AARCH64 bare metal 로 동작 시키기 - 1

2023년 연말을 최대한 유의미하게 보내고자 아래와 같이 목표를 세우고 달성하고자 노력하였다. 

목표: 

aarch64 시스템을 최소 설정으로 OS없이 동작하도록 초기화. 

  1. System 동작 확인
  2. GIC를 사용한 (SPI, PPI) 설정법 확인
  3. UART를 사용한 입출력 - interrupt로 처리(SPI)
  4. timer interrupt처리 (PPI)

대부분의 경우 새로운 BSP는 기존 BSP를 수정하여 만들고, u-boot등 기성 boot loader를 사용하기 때문에 주의 깊게 살펴볼 기회가 없었던 aarch64 시스템 초기화 방법 및 GIC 사용법을 정리하고자 한다. 


준비: 

( ubuntu or WSL/Windows )

1. cross-compiler설치

$ sudo apt-get update
$ sudo apt-get install gcc-aarch64-linux-gnu

2. qemu설치

$ sudo apt-get install qemu-system-aarch64

우선 테스트를 위한 HW를 선정해야 한다.  rpi4 등 실제 HW에서도 진행할 수 있지만,  qemu환경을 사용하면, 실제 HW없이 구현 및 테스트가 가능하므로 일단 qemu의 aarch64환경에서 진행하기로 결정하였다. 

qemu는 여러가지 HW의 emulation을 지원하는데, 그중 "virt" machine을 선정하였다. 

‘virt’ generic virtual platform (virt) — QEMU documentation

- 2023년 말 기준 qemu정식 버전은 rpi4를 지원하지 않지만, rpi4 emulation을 지원하는 qemu를 쉽게 구할 수 있었고, 빌드도 쉽게 가능하였지만 일단 "virt"에서 먼저 구현하기로 하였다.  rpi는 ARM의 GIC interrupt controller가 아닌 다른 interrupt controller를 사용하기 때문에 study의 목적에 부합하지 못하였다. 

- qemu virt machine은 GICv2를 지원하지만, 옵션을 사용하여 GICv3를 선택할 수 도 있다.  먼저 GICv2에서 동작을 확인한 후 GICv3까지 동작시키는 것을 목표로 하였다. 


aarch64 부팅 과정 고찰:

aarch64는 아래와 같은 복잡한 부팅 과정을 가진다.  이 과정을 이해해야 어떤 부분부터 직접 작성할지 결정할 수 있었다. 

BL1 -> BL2  -> BL31

                -> BL32

                -> BL33

BL1 : 1st stage boot loader

BL2 : 2ed stage boot loader

BL3x: 3rd stage boot loader

BL31 : EL3 -runtime f/w (Secure monitor)
BL32 : Secure EL1 f/w : TEE OS
BL33 : Non-Secure EL1 Non-trusted f/w ( u-boot etc) 

최종적으로 일반 OS를 구동 시키고자 한다면, BL33에 해당하는 부분을 작성하는 것을 목표로 잡을 수 있었다. 즉 기존의 u-boot를 대신하여 실행할 수 있는 f/w작성하고자 한다. 


qemu virt machine을 사용해서 일단 u-boot를 실행해 보자. 

$ qemu-system-aarch64 -M virt,virtualization=on -m 4G -smp 2 -kernel u-boot -nographic


위에서 "u-boot"는 elf 형식의 u-boot 파일명이다. 

이 경우  BL1/BL2/BL31/BL32가 사용되지 않았다. BL33(여기서는 u-boot)만 사용하여 동작이 가능하도록 qemu가 emulation해준다.  따라서 다른 BL을 신경 쓰지 않고 작업 진행이 가능하다. 


만약 굳이 BL1, BL3, BL31, BL32, BL33을 사용해서 동작 시킨다면  아래와 같이 할 수 있다. 

$ qemu-system-aarch64 -M virt,secure=on -smp 2 -m 4G -bios bl1.bin -d unimp -semihosting-config enable=on -nographic

단 이때 실행 디렉토리에 bl1.bin, bl2.bin, bl31.bin bl33.bin이 있어야 한다. 

bl1.bin, bl2.bin, bl31.bin은 아래에서 구할 수 있었다. 

GitHub - ARM-software/arm-trusted-firmware: Read-only mirror of Trusted Firmware-A

bl33.bin은 u-boot.bin의 symbolic link로 만들었다. 

실제 하드웨어와 유사하게 u-boot를 실행한 상태에서 내가 작성한 프로그램을 메모리에 로드하여 go addr 명령으로 실행하는 것이 가능하지만, 굳이 그렇게 할 필요는 없다.  또한 bl1이나 bl2, bl31등의 개발에 관심이 있는 것이 아니라면 굳이 secure=on을 사용하여 EL3를 사용할 이유도 없다 (일반적으로 HW 업체에서 자신들의 CPU에 맞는 BL파일들을 제공한다).  따라서 이후에서는 virtualization=on을 사용하여 EL2에서 시스템을 시작 시키며, u-boot 대신 직접 작성하는 바이너리를 올려서 진행하기로 하였다. 

 

빌드

aarch64-linux-gnu-as -march=armv8-a -mcpu=cortex-a72 -g -c ...
aarch64-linux-gnu-gcc -DGICV3 -march=armv8-a -mcpu=cortex-a72 -g -I. -fno-stack-protector -mgeneral-regs-only -c ...
aarch64-linux-gnu-ld -n ... -T virt.ld -o my_bl33.elf

개발 중 unexpected sync. exception이 발생 하는 문제가 있었다.  다행이 최근에 회사 동료가 동일한 문제를 해결한 적이 있어 해결책을 알고 있었다.  -mgeneral-regs-only 추가. 


- 계속 -




2015년 12월 27일 일요일

임베디드 시스템에서 ssh 설정 및 사용하기

임베디드 시스템에 원격 연결을 위해서 시리얼 통신 다음으로 많이 사용되는 telnet은 사용이 편리하고 설정이 간단 하지만 데이터 전송이 암호화되지 않기 때문에 보안 문제를 가지고 있다. 제품 출시 후 이더넷 포트를 통한 리버스 엔지니어링을 막아야 하거나 연결 상태에서 통신 데이터가 외부에 노출되지 않도록 하기 위해서는 telnet 대신 ssh 연결을 사용하여야 한다. (알려진 바로는 미국 NSA에서는 ssh도 뚫을 수 있다고 하는데 어떤 방법을 사용하는지는 공개가 되지 않았다.  )

ssh는 telnet에 비해서 설정이 조금 복잡해서 제대로 설정을 하기 위해서는 약간의 지식이 필요하다. ssh는 인증을 위해서 한쌍의 public/private keys로 이루어진 SSH key-pairs를 사용하는데, 아래 글이 public/private key pair를 이해하는데 도움이 될 것이다.

공개키 암호와 안드로이드 서명


패스워드 인증 방식

일단 연결하고자 하는 target 임베디드 장치가 server(혹은 Host)가 되며 target 장치에 연결하고자 하는 시스템(주로 사용자 pc)이 client가 된다. 먼저 server는 client가 접속을 시도할 때 자신의 public keys를 client에게 전달된다. 이때 client에서는 Host의 RSA key fingerprint라고 부르는 값이 출력되며 접속 진행 여부를 사용자에게 묻는다. 이 key값이 server에서 생성한 key의 fingerprint와 일치한다면 yes를 선택하면 된다.  yes 선택 시 client는 Host의 public key를 가져다 보관한다.  (일반적으로 ~/.ssh/known_hosts 에 보관됨) fingerprint값은  ssh-keygen를 사용해서 확인할 수 있다.  서버의 공개키 전달 및 이에 따른 fingerprint 확인은 최초 접속 시에만 수행된다. 이 fingerprint값을 통하여 client는 자신이 접속하는 서버를 확인하게 된다.

임베디드 시스템에서는 client와 server가 보통 바로 연결되어 있고,  두장치 모두 동일 사용자가 관리하므로 fingerprint확인이 바로 가능하며, 대부분의 경우 그렇게 중요하지도 않다.

client는 공유키 생성(접속시마다 변경됨)하여 이를 server의 public key를 사용하여 암호화한 후 이를 server에 전달하고, 이후 통신은 이 공유키를 사용하여 암호화 된다.

공유키를 전달한 후 첫번째 통신은 사용자가 입력한 패스워드가 될 것이다. 패스워드는 공유키로 암호화 하여 서버에 전달되므로 중간에 누군가가 패킷을 가로채어도 패스워드가 노출되지 않는다.  - 패스워드 이후의 모든 통신도 이 공유키를 통하여 암호화 된다.

여기서 굳이 기 공유된 server의 공개키를 사용하지 않고, 별도의 공유키를 사용하는 이유는 공유키를 사용하여 암호화 하는것이 공개키를 사용하여 암호화하는 것보다 연산처리의 부담이 적기 때문이라고 한다.

한줄 정리:  패스워드 인증 방식에서는 server가 공개키 생성, client가 공유키 생성

공개키 인증방식

공개키 인증방식을 사용하면 매번 접속시 비밀번호를 입력하지 않고 접속이 가능하다.  이를 위해서 server가 아닌 client에서 SSH key-pairs를 생성해야 한다. 생성한 key중 공개키를 server로 전달하여 저장하여 놓으면(최초 1회) 이후 client 접속 시 server는 이를 사용하여 공유키를 생성하여 client에 공유키를 전달하고, 향후 통신을 이 공유키를 통하여 암호화 한다.  client의 공개키를 통하여 암호화된 공유키(client 접속시마다 server에서 난수를 사용하여 생성함)는 해당 공개키의 pair인 비밀키로만 복호화 가능하기 때문에 매번 접속시마다 비밀번호를 사용하지 않고도 client를 인증할 수 있다.

아래 명령으로 client에서 생성한 공개키를 server에 등록할 수 있다. (key 생성 방법은 이후 설명함) 이 과정에서는 server의 sshd_config 파일의 "PasswordAuthentication" 가 일단 "yes"로 설정되어 있어야 한다. (변경 시 설정이 반영되려면 sshd가 재시작되어야 한다.)

$ cat ~/.ssh/id_rsa.pub | ssh user@192.168.11.14 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys”

보통 임베디드 시스템 개발을 위해서 접속 시에는 root로 접속하므로 위에서 user 대신 root를, 123.45.56.78 대신 target 시스템의 ip를 넣으면 된다.  ssh-copy-id를 사용하는 방법도 있지만, ssh-copy-id를 지원하지 않는 시스템도 많으므로 여기서는 소개하지 않는다.

참고 사이트 : How To Set Up SSH Keys

이후 "PasswordAuthentication" 를 "no"로 변경하고 sshd를 재실행시키면 client에서 비밀번호 대신 SSH key-pairs을 사용해서 로그인하게 된다.

아래는 SSH key-pairs을 사용해서 root로 로그인하기 위한 명령이다.

$  ssh -v -i ~/.ssh/id_rsa root@192.168.11.14

key 파일의 위치는 위에서와 같이 ssh 실행 시 -i 옵션으로 지정하거나 아래와 같이 ~/.ssh/config 파일에 지정하면 된다.

$ cat ~/.ssh/config
IdentityFile ~/.ssh/id_rsa

서버에 등록하는 파일은 public key(id_rsa.pub)이며 접속 시 사용하는 파일은 private key(id_rsa)임에 주의한다.

~/.ssh/config 파일은 ssh client의 개인 설정 파일로 전체 시스템에 적용되는 /etc/ssh/ssh_config 파일보다 우선하여 적용된다.  따라서 설정 변경이 필요하다면 /etc/ssh/ssh_config을 직접 수정하지 말고 ~/.ssh/config을 수정하도록 한다.

Server에서 Private/Public key pair 생성하기 (QNX 시스템에서 예)

ssh-keygen을 사용한다. 아래는 server(target)에서 key 생성 예이다. (Tested on QNX OS)

# ssh-keygen -t dsa -b 1024 -f /etc/ssh/ssh_host_dsa_key -N ''
# ssh-keygen -t rsa -b 1024 -f /etc/ssh/ssh_host_rsa_key -N ''

위와 같이 실행 시 SSH key-pairs는 /etc/ssh/ 아래에 생성된다.

생성된 Private/Public key의 fingerprint 확인하기
아래와 같이 생성된 SSH key-paris의 fingerprint를 확인할 수 있다.

# ssh-keygen -l   <== 소문자 el
Enter file in which the key is (/root/.ssh/id_rsa): /etc/ssh/ssh_host_rsa_key
1024 1e:f7:b2:e2:ec:9d:b9:e4:bd:51:ea:d9:26:65:c0:0b  root@localhost (RSA)


Client 에서 Private/Public key pair 생성하기

ssh-keygen을 사용한다. 아래는 client( 개발자 pc : Linux or Mac)에서 key를 생성하는 방법이다.

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/dooeui/.ssh/id_rsa): [Enter]
Enter passphrase (empty for no passphrase): [Enter]
Enter same passphrase again: [Enter]
Your identification has been saved in test.
Your public key has been saved in test.pub.
The key fingerprint is:
...


key pair는 default로 ~/.ssh/ 아래에 생성된다. (id_rsa, id_rsa.pub) 이름에서 유추 가능하듯이 id_rsa 는 private key이며, id_rsa.pub가 publick key이다.


Server 설정( QNX OS )

client로 부터 접속 요청이 들어 올 경우 inetd이 sshd를 실행한다. Server쪽 sshd 설정은 /etc/ssh/sshd_config 파일에 저장된다. 최초 로그인 시에는 client의 비밀번호 접속을 허용해야 한다. 계속해서 비밀번호를 사용해서 접속해도 되지만, 보안을 위해서는 비밀번호 로그인을 disable하고 SSH key-pairs를 사용한 로그인을 사용하는 것이 좋다. 비밀번호 로그인 on/off는 아래와 같이 PasswordAuthentication을 yes나 no로 설정하여 제어할 수 있다.

/etc/ssh/sshd_config
------------------------------------------------------
...
PasswordAuthentication yes
...
------------------------------------------------------


서버에는 최소한 아래 파일들이 필요하다.
  • libcrypto.so.2
  • libz.so.2
  • sshd
  • ssh-keygen
  • sftp-server

ssh접속을 위해서 서버에서는아래 파일이 실행되어고 있어야 한다. 
  • inetd
  • random -p

inetd가 ssh접속 요청을 받아 sshd를 실행시키도록 하기 위해서 inetd.conf에 아래 내용이 추가되어야 한다.

/etc/inetd.conf
------------------------------------------------------
...
ssh stream tcp nowait root /usr/sbin/sshd in.sshd -i
------------------------------------------------------

포트 번호 및 프로토콜 설정

/etc/services
------------------------------------------------------
...
ssh 22/tcp
------------------------------------------------------

sshd 설정

/etc/ssh/sshd_config
------------------------------------------------------
Protocol 2
LoginGraceTime 600
PermitRootLogin yes
PasswordAuthentication yes
PermitEmptyPasswords yes
Subsystem    sftp    /usr/libexec/sftp-server
------------------------------------------------------
 
  • / 아래에 쓰기 가능한 파일 시스템이 mount되어있는지 확인
  • 아래 폴더 생성 (없으면)
# mkdir /etc    (to create writeable /etc directory for /etc/passwd and /etc/ssh/)
# mkdir /etc/ssh (for the keys to be generated)
# mkdir /root
  • /etc/passwd에 내용 추가
echo root::0:0::/root:/bin/sh > /etc/passwd (now root user "exists" from rights management
perspective and can create keys)
  • passwd 설정
# passwd   (give a password)

  •  /etc/passwd에 내용 추가
echo sshd:x:15:6:sshd:/var/chroot/sshd:/bin/false >> /etc/passwd (creates entry for "privilege separation user sshd", appends to passwd file)
  • 디렉토리 설정 및 소유권 설정

# mkdir /var
# mkdir /var/chroot
# mkdir /var/chroot/sshd
# chmod 700 /var/chroot/sshd



Client 설정 ( Linux or MAC OS X )

일반적으로 /etc/ssh/ssh_config 파일에 시스템 전체적으로 적용되는 설정파일이 있으며, 이 파일의 내용중 변경하고 싶은 항목이 있을 경우 ~/.ssh/config 파일에 변경된 내용을 적으면 된다. (동일 항목에 대해서는 ~/.ssh/config의 값이 우선하여 적용된다. )

참고 사이트

Understanding the SSH Encryption and Connection Process





2015년 10월 24일 토요일

오디오 드라이버 개발에 필요한 오디오 포맷 기본

다양한 오디오 포맷이 존재하지만 오디오 드라이버를 작성할 경우에는 wav와 pcm 포맷만 이해하면 별다른 문제가 없다. 다른 오디오 포맷은 오디오 코덱 담당자 혹은 멀티미디어 담당자들에게 맡기자.

먼저 wav포맷은 압축하지 않은 오디오를 저장하는 포맷으로 대부분의 OS에서 제공하는 기본  Player로 손쉽게 재생이 가능하기 때문에 많이 사용된다. 오디오 데이터는 sampling rate, bit size, endian, channels 등에 따라 데이터가 저장되는 순서가 바뀌므로 wav파일은 헤더에 이러한 정보를 저장하게 된다.  각각에 대한 상세 정보는 아래 사이트에서 확인 가능하다. (http://www.topherlee.com/software/pcm-tut-wavformat.html)

wav파일은 RIFF라는 오디오 포맷 스펙을 구현것으로 wav포맷은 RIFF포맷과 동일한 포맷이라고 생각하면 된다. RIFF와 유사한 포맷으로 RIFX 포맷이 있는데, RIFX 포맷과 RIFF포맷은 서로 data의 byte order 즉, endian이 다르다. RIFF 포맷은 little endian으로 저장되고, RIFX포맷은 big endian으로 저장된다.

pcm audio file은 header가 없이 raw audio data만 저장한 파일이다.   따라서 pcm포맷으로 저장된 오디오는 별도로 오디오에 대한 정보를 가지고 있지 않으면 제대로 play할 수 없다. 오디오 드라이버에서 오디오 데이터를 캡춰하였다면, 당연히 header가 없는 pcm 포맷이며 이때 오디오 드라이버가 어떤 상태(sampling rate, bit size, endian, channels)로 동작중이었는지에 따라 pcm 포맷을 해석해야 한다.  만일 데이터가 little endian이라면 RIFF포맷이므로 wav header만 붙여주면 wav player에서 play가 가능해 진다. 데이터가 big endian이라면 RIFX 포맷이므로, wav header를 붙여 주고 play하면 이상한 노이즈가 play 될 것이다. 따라서 데이터가 big endian이라면 header 추가 뿐만 아니라 오디오 데이터에 대한 endian 변환까지 해주어야 한다.

sox라는 프로그램을 사용하면 간편하게 이러한 변환을 간단하게 수행할 수 있다. (http://sox.sourceforge.net)

아래는 header가 없는 RIFX 포맷의 48kHz/16bit/2 channel pcm데이터를 wav 파일로 변환하는 명령이다.

sox -traw -esigned -r48k -b16 -c2 --endian big foo.pcm foo.wav

-t : type - file type (raw | wav | ... ) 
-e : encoding (signed | unsigned)
-r : rate - sampling rate of audio (8k | 44.1k | 48k | ... )
-b: bits - encoded sample size in bits ( 8 | 16 | ... )


-c: channel (1 | 2 |... )
--endian : Encoded byte-order (little | big)


2015년 4월 14일 화요일

로그 출력하며 저장하기

임베디드 시스템 설계 시 유지/보수 및 디버깅을 위한 로깅은 일반적으로 serial interface를 사용하여 구현된다.  이는 시스템의 특정 serial interface를 표준 입출력 및 에러로 지정하는 것으로 printf를 사용한 로그 메세지가 serial 로 출력되며 따라서 serial interface가 연결된 상태에서만 로깅이 가능하므로 개발이나 유지 보수에 불편한 점이 많다.

만일 기기 내부에 임시로 로그를 저장하거나, 로그의 양을 조절하기 위한 우선순위 지정등의 추가 기능이 필요할 경우 이를 위한 자체 로깅 API를 작성하거나 혹은 시스템에서 제공하는 syslog()등의 사용을 고려할 수 있다.  하지만 재빌드가 불가능한 3rd party library등에서 printf를 사용하고 있다면 여기에 새 로깅 API를 적용할 수는 없다.

위와 같이 별도의 로깅 시스템이 고려되지 못한 상황에서 표준 출력과 표준 에러에만 의존하는 프로그램을 로깅하기 위해서 아래와 같은 방법을 사용할 수 있다.

# program 2>&1 | tee /tmp/log.txt
  • program : 프로그램명
  • 2>&1 : 표준 에러를 표준 출력과동일한 곳으로 설정
  • | :표준 출력을다음 프로그램의 표준 입력으로전달 
  • tee : 표준입력을 표준출력으로 보내면서 동시에 인자로 지정 된 경로에 전달

2015년 4월 6일 월요일

네트워크상의 기기간 시간 동기화 (NTP & PTP)

여러 임베디드 시스템이 네트워크로 연결되어 있을때 기기간 시간 동기화가 필요한 경우가 많다. 시간 동기화에서 가장 먼저 고려되는 방법은 ntp를 사용하는 것인데 아마 여러분의 PC에서 동작하고 있는 OS도 ntp를 사용하여 현재 시간을 인터넷상의 타임 서버와 동기화 하고 있을 것이다.

개발 하고자 하는 장치가 인터넷 프로토콜을 지원한다면 ntp를 사용하여 시간을 맞출 수 있으며 인터넷 연결 여부와 상관 없이 동일 네트워크안에서 기기간 시간 동기도 가능하다.  여러분이 기기간 시간 동기에 ntp를 사용하고자 한다면 아래 두가지를 고려해야 한다. 

1. 동기화 시간 : 일반적으로 10분 이상이 지나야 동기화가 완료된다. 처음 ntp를 사용하는 사람들이 시간 동기가 되지 않는다고 어려워하는 대부분의 이유가 이 시간이 필요한 것을 모르기 때문에다. burst/iburst옵션을 사용하여 이 시간을 단축할 수 있다. (십분 이상에서 수분이내로)

2. 동기화 정확도 : ntp는 보통 ms단위의 정확성을 가지는 것으로 알려져 있다. 더 높은 정확성이 필요하다면 다른 방법을 고려해야 한다.  ( 아래 설명하는 ptp를 사용할 경우 HW 지원여부에  따라 us에서 ns까지 정확도를 높일 수 있다.) 

기기간 시간 동기를 위해서 Time Master와 Time Slave 장치를 정한다. RTC가 있는 장치 중 가장  정확한 시간을 가지는 장치를 Time Master로 정하면 된다. ntp의 Time Master의 설정 방법은 아래와 같다. 

ntp.conf   (/etc or /etc/ntp)
server 127.127.1.1 prefer burst iburst
fudge 127.127.1.1 stratum 10

- ntp.conf파일의 server 주소를 127.127.x.x 로 설정할 경우 자신을 Time Master로 인식한다. 
- burst/iburst 옵션을 사용하면 time sync 시간을 많이 단축할 수 있다. 

Time Master서 위와 같은 옵션으로 ntpd (daemon)을 실행시키고 Time Slave에서 "ntpdate server_ip"를 실행시키면 수분내에 time sync. 가 이루어진다.  Time Slave에서 지속적으로 Time Master와 시간을 동기화해야 한다면 ntpd (daemon)을 실행시킨다. Time Slave와 Master에 사용된 오실레이터나 크리스털의 클럭이 서로 미세하게 다를 수 밖에 없으므로(부품 공차) 시간이 지나면 clock drift등으로 인해서 기기간 시간오차가 점정 커지게 된다. 장시간 사용되는 네트워크라면 (Time Slaver 에서)ntpd를 실행하여 지속적으로 오차를 보정해주는 것이 좋다.(당연히 Time Master에는 ntpd가 항상 실행되어 있어야 한다) 

소규모 네트워크에서 동기화가 필요한 경우 수분~십분 이상 시간이 걸리는 ntp는 불편하기 짝이 없다. 네트워크상 장치들이 거의 동시에 부팅이 되고 동작을 시작해야 하는 경우 사용자를 몇분씩 기다리게 할 수는 없기 때문이다. 이런 경우 ptp를 사용하면 된다. ptp는 실행 즉시 동기화가 이루어지고 HW적으로 지원하는 경우 us단위 이상의 정확성을 가진다. (HW적으로 ptp를 지원하는 경우 ethernet 장치가 패킷에  time stamp를 넣어주는 기능을 가지고 있다. HW적으로 이를 지원하지 않을 경우 SW적으로 해당 값을 넣어주게 되는데, 실제로 값을 넣어주는 시간과 패킷이 전송되는 시간이 차이가 나기 때문에 HW적으로 지원하는 경우보다 시간에 오차가 발생하게 된다.)

사용 방법은 아래와 같다.

1. Time Master에 ptpd를 master 모드로실행
# ptpd -W

2. Time Slave 에 ptpd를client모드로실행
# ptpd –g &

사실 ntp대신 ptp를 사용하는 보다 일반적인 목적은 동기화 시간이 아닌, 정확도 이다. ntp는 ms단위까지 동기화가 가능한 반면 ptp는 HW지원 여부 및 HW에서 지원하는 정확도에 따라 us에서 ns까지도 정확도를 높일 수 있다.

참고로  sync되는과정을확인하시고싶으면아래와같이 client를실행시키시면다.
# ptpd -g –c –D &
ptpd의 몇가지 옵션 설명:
-W : run as a master without NTP
-c : console mode
-g: slave mode only
-b en0: 어떤network interface를 사용할지 지정
-h: end to end mode,  HW적으로고정밀 clock을지원하지않는 switch사용시에는end to end모드로만동작.
-D : display status in .csv format
-d: display status

ptpd는 아래에서 구할 수 있다.
http://ptpd.sourceforge.net

ptpd 관련 참고 사이트
http://grepjuice.com/tag/ubuntu/






























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에서 수행이 된다.