$ uname -m
x86_64

$ uname -mnr
hostname  2.6.18-164.el5 x86_64
 

$ lsb_release -a

 LSB Version:    :core-3.0-ia32:core-3.0-noarch:graphics-3.0-ia32:graphics-3.0-noarch
Distributor ID: CentOS
Description:    CentOS release 4.7 (Final)
Release:        4.7
Codename:       Final

'c or linux' 카테고리의 다른 글

TOP 을 통해 본 리눅스 메모리  (0) 2012.01.06
Swap 메모리  (2) 2012.01.05
ubuntu 패스워드 변경  (0) 2011.11.23
Ubuntu 11.04 패스워드 분실시 처리  (0) 2011.11.15
ubuntu를 한국에서 빨리 다운받을 수 있는 곳  (0) 2011.11.08
Posted by '김용환'
,

 

과거에 임베디드 시스템을 개발 및 포팅할 때는 컴파일과 업로드(버닝) 이렇게 두가지를 사용했다. 컴파일할 때는 크로스 컴파일러 쓰고 업로드할 때는 따로 하드웨어 업체(삼성, LG)에서 준 툴을 이용해서 업로드 했다. 이번에 아두이노를 분석해가면서 avrdude 라는 파일을 처음 보게 되어서 공부차 작성해 본다. 

avrdude는 AVR 마이크로 콘트롤러에 ROM과 EEPROM 이미지를 업로드하는 유틸리티이다. “http://www.nongnu.org/avrdude/” URL에 따르면, Braian S. Dean이라는 사람이 AVR 마이크로 시리즈를 위한 in-system 프로그램 프로젝트중의 하나였다고 한다.처음에는 FreeBSD에 포팅했었고 다양한 운영체제서도 동작하기를 원하는 요구사항이 있어서, 싸이트(http://savannah.nongnu.org/projects/avrdude)를 오픈하고 사람들이 접근해서 쓸 수 있도록 하였다.

아두이노 및 다른 ISP칩셉에 연동이 가능하다. 그 이유는 설정파일 (avrdude.conf)을 가지고 할 수 있다.

<http://savannah.nongnu.org/projects/avrdude>

image

 

기능을 알기 위해서 살펴본다.

>avrdude
Usage: avrdude [options]
Options:
  -p <partno>                Required. Specify AVR device.
  -b <baudrate>              Override RS-232 baud rate.
  -B <bitclock>              Specify JTAG/STK500v2 bit clock period (us).
  -C <config-file>           Specify location of configuration file.
  -c <programmer>            Specify programmer type.
  -D                         Disable auto erase for flash memory
  -i <delay>                 ISP Clock Delay [in microseconds]
  -P <port>                  Specify connection port.
  -F                         Override invalid signature check.
  -e                         Perform a chip erase.
  -O                         Perform RC oscillator calibration (see AVR053).
  -U <memtype>:r|w|v:<filename>[:format]
                             Memory operation specification.
                             Multiple -U options are allowed, each request
                             is performed in the order specified.
  -n                         Do not write anything to the device.
  -V                         Do not verify.
  -u                         Disable safemode, default when running from a scrip
t.
  -s                         Silent safemode operation, will not ask you if
                             fuses should be changed back.
  -t                         Enter terminal mode.
  -E <exitspec>[,<exitspec>] List programmer exit specifications.
  -x <extended_param>        Pass <extended_param> to programmer.
  -y                         Count # erase cycles in EEPROM.
  -Y <number>                Initialize erase cycle # in EEPROM.
  -v                         Verbose output. -v -v for more.
  -q                         Quell progress output. -q -q for less.
  -?                         Display this usage.

avrdude version 5.11, URL: <http://savannah.nongnu.org/projects/avrdude/>

 

아두이노 우노에 이미지를 업로드하려면 다음과 같이 사용하는데, 간단히 보면 다음과 같다.

> avrdude -p atmega328p -P com3 -c arduino -U flash:w:gds.hex:i

   
-p atmega328p    마이크로 프로세서의 타입을 지정한다.
-c arduino 통신 프로토콜을 지정한다.
-P com3 com3 시리얼 포트로 내려받는다.
-U flash:w:gds.hex:i

flash 는 flash memory를 의미
w 는 write를 의미
test.hex = 업로드할 파일
I = Intel 의 hex format을 의미

Posted by '김용환'
,

 

C 소스를 가지고 윈도우에 설치된 체인툴을 가지고 크로스 컴파일을 해보려고 시도했다.

 

소스에 가장 먼저 #define 되어야 하는 부분은 AVR 타입에 대한 정보이다. 만약 이 부분을 넣지 않으면 warning과 함께 컴파일이 되지 않는다. avr/include/avr 디렉토리의 io.h 파일에 정의되어 있다.

….

#elif defined (__AVR_ATmega328P__)
#  include <avr/iom328p.h

….

#  if !defined(__COMPILING_AVR_LIBC__)
#    warning "device type not defined"
#  endif
#endi

 

아두이노 우노(arduino uno) 라면 ATmega328 프로세스를 사용하고 있으므로, 다음과 같이 정의해야 한다.
#define __AVR_ATmega328P__

또는 avr-gcc 실행시 –mmcpu 파라미터값에 atmega328p를 추가해야 한다.

 

아주 간단한 소스를 컴파일하고 업로드하는 것을 해본다.

<test.c>

#define __AVR_ATmega328P__
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

int main() {
  // 셋업
  DDRB = 0xFF;

  while (1) {
    PORTB = 0xFF;
    _delay_loop_2(1000;
    PORTB = 0x00;
    _delay_loop_2(1000);
  }
}

 

avr 칩에서 동작하는 바이너리 파일을 만든다.

> avr-gcc -O test.c -o test.bin

 

.text와 .data 파일이 포함하는 .hex 파일을 만든다.

>avr-objcopy -j .text -j .data -O ihex test.bin test.hex

 

.hex 파일을 가지고 아두이노 uno에 업로드를 하면 된다.  [속도(b옵션) : 115200, 보드이름(p옵션) arduino 가 반드시 맞아야 한다.] 보드이름에 대한 자세한 정보는 avrdude.conf에 기록 되어 있음

>avrdude -P com3  -b 115200 -p m328p -c arduino -C../etc/avrdude.conf -F -e -U flash:w:test.hex


Posted by '김용환'
,

 

아두이노 개발 툴 1.0에 대한 소스 분석 글이다.

http://knight76.tistory.com/entry/아두이노-개발-툴-소스-10-분석을-위한-환경-셋팅 에 이어 소스 분석을 시작한다.  프로세싱 개발 툴과 너무 흡사한 UI를 가졌기 때문에 소스 분석이 조금 더 쉬운 것 같다.

먼저 빌드 순서를 파악하고, 소스를 파악한다. (짜잘한 것은 제외) 그리고 주석이 약간 틀린 부분이 있었다.ㅠ

1. 빌드 순서


(1) build/build.xml

운영체제(플랫폼)에 맞는 빌드 콜을 호출한다. 윈도우의 경우 windows-build target이 가장 먼저 실행이 된다.

    1) todo.txt 파일의 revistion 숫자, processing/app/Base.java 파일의 revision 정보와 비교해서 revision 번호를 맞춘다.

    2) 윈도우 운영체제인지 확인한다.

    3) core 디렉토리와 app 디렉토리에 ant 컴파일을 시킨다.

       -  core/build.xml
           core 디렉토리를 컴파일하고 bin/core.jar 파일을 만든다.

       -  app/build.xml
           app 디렉토리를 컴파일하고, bin/pde.jar 파일을 만든다.

4) windows/work 디렉토리를 생성 후, 실행에 필요한 libraray (jar, dll)과 driver 파일들을 복사한다. 그리고, window/work/hardware 디렉토리에 윈도우에서 동작할 수 있는 avr 툴(avr 체인) 압축 파일을 풀어둔다.

5) windows/work 디렉토리의 여러 디렉토리에 셋팅을 한다.

    1) windows/work/tools 디렉토리에 Mangler.java 소스를 복사한다.

    2) window/work/library 디렉토리에 아두이노 library 파일을 복사한다.

    3) window/work/hardware 디렉토리에  아두이노 부트로더, 펌웨어등을 복사한다.

    3) window/work/example 디렉토리에는 간단히 예제들을 복사해둔다.

6) launch관련 파일들을 모은다. launch4j와 설정파일, 관련 클래스들을 모아둔다.

7) launch4j를 실행하고, 컴파일하고 링킁하고 실행파일(build\windows\work\arduino.exe)을 생성한다.

launch4j를 이용하여 exe 파일을 만드는 config.xml 파일이다.

<launch4jConfig>
  <dontWrapJar>true</dontWrapJar>
  <headerType>gui</headerType>
  <jar>lib</jar>
  <outfile>arduino.exe</outfile>
  <errTitle></errTitle>
  <cmdLine></cmdLine>
  <chdir>.</chdir>
  <priority>normal</priority>
  <downloadUrl>http://java.sun.com/javase/downloads/</downloadUrl>
  <supportUrl></supportUrl>
  <customProcName>false</customProcName>
  <stayAlive>false</stayAlive>
  <manifest></manifest>
  <icon>application.ico</icon>
  <classPath>
   <mainClass>processing.app.Base</mainClass>
    <cp>lib/pde.jar</cp>
    <cp>lib/core.jar</cp>
    <cp>lib/jna.jar</cp>
    <cp>lib/ecj.jar</cp>
    <cp>lib/RXTXcomm.jar</cp>

  </classPath>
  <jre>
    <path>java</path>
    <minVersion>1.5.0</minVersion>
    <maxVersion></maxVersion>
    <jdkPreference>preferJre</jdkPreference>
    <opt>-Xms128m -Xmx128m</opt>
  </jre>
  <splash>
    <file>about.bmp</file>
    <waitForWindow>true</waitForWindow>
    <timeout>60</timeout>
    <timeoutErr>true</timeoutErr>

  </splash>
  <messages>
    <startupErr>An error occurred while starting the application.</startupErr>
    <bundledJreErr>This application was configured to use a bundled Java Runtime Environment but the runtime is missing or corrupted.</bundledJreErr>
    <jreVersionErr>This application requires at least Java Development Kit</jreVersionErr>
    <launcherErr>The registry refers to a nonexistent Java Development Kit  installation or the runtime is corrupted.</launcherErr>
    <instanceAlreadyExistsMsg>An application instance is already running.</instanceAlreadyExistsMsg>
  </messages>
</launch4jConfig>

 

2. UI (Editor)

processing.app.Base.java 소스에 main 메서드를 따라가 본다.

OS(플랫폼)을 확인하고, preferences.txt 프로퍼티 화일(크기..)을 읽는다. theme/theme.txt 프로퍼티 설정을 읽어 editor에 대한 프로퍼티를 확인한다. look&feel 을 설정후, 임시폴더를 만들고 Base 객체를 생성한다.

Base 생성자 코드를 따라가면, examples, libraries, tools 디렉토리 패스와 프로젝트 파일로 저장할 path (sketchbook path) 를 지정해둔다. hardware 디렉토리를 잘 기억한다. Editor 클래스를 초기화하여 UI를 실행한다. 이전에 아두이노 개발 툴을 종료했을 때 사용했던 스케치 파일이 있었으면 툴 실행시 스케치 파일을 열어둔다.

UI에 관련된 대부분의 특징은 프로세싱 개발 툴과 흡사하다. processing.app 패키지 밑에 잘 들어가 있다.

운영체제에 맞는 특징에 대해서는 운영체제별로 Platform.java 소스에 dependant한 코드들이 들어가 있다. 재미있는 것은 아두이노 오픈 소스 개발자가 macosx 쪽에 Think Different 클래스를 추가했다. 오픈소스의 센스라고 해야 하나 ㅎㅎ 먼가 대단하다고 생각한 코드는 아니지만 웃음코드에 박수~

public class Platform extends processing.app.Platform {

….

public void init(Base base) {
  System.setProperty("apple.laf.useScreenMenuBar", "true");
ThinkDifferent.init(base);
  ..

}

 

public class ThinkDifferent implements ApplicationListener {

… OS X application 이벤트 (open, about 등등)에 대해서 처리하려고 만들었다.

}

아두이노 개발 툴을 UI 초기화하고 menu를 생성하고 editor를 만들면서.. serial port 정보와 타겟 보드가 나와있는데, 이는 processing.app.EditorLineStatus.java 클래스에서 설정 파일을 읽어오는 코드로 동작하게 되어 있다.

맨 처음 읽을 때는 preference.txt 파일에 있는 설정 정보를 읽는다. 이전에 Serial 포트 변경이 있을 때마다 설정값을 변경시키도록 되어 있따.

# ARDUINO PREFERENCES
board = uno
target = arduino

serial.port=COM1

 

3. 아두이노 개발 툴의 스케치 파일 읽고 쓰기

파일을 읽는 시퀀스는 그냥 평범하다.

EditorHeader.rebuildMenu() –> SkechCode.load() –> Base.loadFile() –> PApplet.loadStrings() –>PApplet.createInput() 로 이어져 있다. 파일을 line을 seperator로 String[](내부적으로는 BufferedReader(FileInputStream)클래스를 사용)타입으로 간단하게 파일을 일고, PApplet.join() 메서드에서 String[]에 line separator를 넣어서 String 객체로 리턴한다.

파일을 쓰는 구조는 추측할 수 있도록 파일을 읽는 반대로 구성되어 있다.

 

4. Sketch 파일

아두이노 개발 툴에서 저장하는 파일들을 스케치(Sketch) 라고 한다. 무엇인가를 그리고 거기에 맞게 그린다는 차원에서는 좋아 보이는 개념인 것 같다. processing.app.Sketch.java 클래스는 pde, java 확장자를 지원하고 cpp를 지원하지 않는다. 또한 여러 버그로 인한 많은 땜질 코드들이 보인다. 

processing.app.SketchCode.java 클래스는 코드 자체를 의미하는 클래스로서, 코드가 읽고, 쓰고, 변경될 때 사용되는 클래스이다.

 

 

5. Compile

빌드 Call stack은 다음과 같다.

에디터에서 빌드메뉴를 선택하면, Editor.buildSketchMenu() –>Editor.handleRun()->Editor.presentHandler()
->Thread인 headerListFromIncludePath.run() –> Sketch.build() 로 진행한다.

 

   (1) 전처리 (preprocessor)

컴파일을 하기 전에 전처리(preprocess) 과정을 거친다. Sketch.preprocess() , PdePreprocessor.preprocess()을 참조한다. 소스에 맨 끝에는 \n과 멀티 라인으로 된 주석은 정리되도록 했다. OOME 나 NPE가 발생한 적이 있는 듯 하다. unicode 설정에 되어 있으면 소스를 unicode로 바꾼다.

pde 나 ino 확장자는 가진 파일들은 include 헤더 파일 스트링을 찾아서 저장해 둔다. 소스에서 컴파일과 관련없이 주석, 전처리 지시자, 싱글/더블 따옴표를 모두 스페이스로 만들어버리고 함수 이외 지역에 사용했던 top level brace를 뺀다.  include header 와 정리된 prototype의 line수를 저장해서 나중에 소스를 만들 때 이용한다.

C++ 클래스를 하나 만들어 pde/ino파일에서 사용했던 include 헤더 파일과 #include “Arduino.h”를 추가하고, 잘 정리한 prototype을 넣은 후 cpp 파일로 만들고 컴파일 한다.

 

  (2) 컴파일

Compiler.compile() 메서드에서 정의하고 있는 컴파일러는 avr-gcc (호스트환경은 윈도우이지만, 타겟보드에서 실행할 수 있도록 cross 컴파일을 지원하는 컴파일러) 를 사용하고 있다.  avr base path는 hardware/tools/avr/bin/ 디렉토리를 기준으로 한다.

보드(아두이노 우노)에 맞는 core 라이브러리들이 참조될 수 있도록 라이브러리(hardware\arduino\cores\arduino 디렉토리 밑에 있는 h, cpp 파일들) 들을 잘 정리해둔다.  sketch 파일을 찾아 리스트에 추가하고 컴파일한다.   variants도 필요하면 path에 추가해준다.

    1) 스케치 파일 컴파일

    --- path 디렉토리에 s 확장자로 끝나는 파일을 컴파일

command 창에서 동작할 수 있도록 명령어를 조합하고 object 파일을 만들기 위해서 gcc 컴파일을 실행한다.

    avr-gcc –c –g -assembler-with-cpp -mmcu={$mcu} -DF_CPU={$cpu} -DARDUINO={$number}  –I{IncludePath} {$source file} –o {$object name}

    --- path 디렉토리에 c 확장자로 끝나는 파일을 컴파일

avr-gcc –c –g –Os –Wall –ffunction-section –fdata-section -mmcu={$mcu} -DF_CPU={$cpu} –MMD -DARDUINO={$number}  –I{IncludePath} {$source file} –o {$object name}

    --- path 디렉토리에 cpp 확장자로 끝나는 파일을 컴파일

avr-gcc –c –g –Os –Wall -fno-exceptions –ffunction-section –fdata-section -mmcu={$mcu} -DF_CPU={$cpu} –MMD -DARDUINO={$number}  –I{IncludePath} {$source file} –o {$object name}

 

     2) 라이브러리 컴파일

아두이노 개발 툴에서 짠 소스에서의 include 헤더에 대한 정보들을 모아 관련 디렉토리에서 컴파일한다. 컴파일 하는 방법은 s, c, cpp 확장자로 끝나는 파일로 위의 컴파일 방법과 같다.

    3) 코어 파일 컴파일

cores 디렉토리 밑에 있는 파일들을 찾아 컴파일하고 core.a 파일로 만든다.

avr-ar rcs core.a  파일들

     4) 링킹

avr-gcc –Os -Wl,--gc-sections -mmcu=atmega328p –o  {$primaryclass}.elf  {$objectfile} core.a –L{$buildpath} –lm

     5) EEPROM data 를 만들기

avr-objcopy –O –R  ihex  -j .eeprom --set-section-flags=.eeprom=alloc,load  --no-change-warnings  --change-section-lma .eeprom=0 {$primaryclass}.elf  {$primaryclass}.eep

     6) .hex 파일 만들기

avr-objcopy –O –R  ihex  .eeprom {$primaryclass}.elf  {$primaryclass}.eep

 

 

(3)  보드에 이미지 업로드

EEPROM data를 잘 구워 아두이노 보드에 넣는(upload) 작업을 할 때, processing.app.Upload 클래스, AvrdudeUploader 클래스를 사용한다. 부트 로더를 굽거나 (burn bootlaoder) 할 떄도 사용된다.

Sketch.upload() –> AvrdudeUploader.uploadUsingPreferences() –>AvrdudeUploader.avrdude() 를 호출하는 구조로 되어 있으며, avrdude 라는 명령어를 이용해서 플래쉬에 hex 파일을 굽는다. (-Uflash:w:파일.hex:i’ )

 아두이노의 부트로더를 굽는 방법은 avrdude 명령어에 -Uefuse:w:~~~ 옵션을 넣는다. 

통신방법은 여러가지가 있다. 아마도 운영체제 때문으로 생각된다. 만약 -Pusb 파라미터를 주어서 usb로 통신할 수 있으며, -P {serial port 번호 } 파라미터를 주어서 serial port로 통신할수도 있다.

-b 파라미터로 굽는 속도를 조절할 수 있다. 
 


그리고, Serial 객체를 생성해서 통신하도록 되어 있다.
Serial 클래스는 내부적으로 gnu.io 패키지의 포트 정보를 확인하는 CommPortIdentifier 클래스를 사용하고 있으며, MessageConsumer에 대한 리스너 등록, 이벤트 발생, 데이터를 읽는 형태를 가지고 있다.

 

(4) 윈도우 사용시 레지스트리 이용 - jna

처음 시작할 때, 윈도우에서 ino, pde 파일을 더블 클릭했을 때 아두이노.exe 파일을 실행시키기 위해서, 윈도우 레지스트리에 추가된다. 관련 파일은 Platform.checkAssociations(), Registry 클래스, Advapi32 클래스이다. 여기서도 processing처럼 jna를 아주 잘 쓰고 있다.

public interface Advapi32  extends StdCallLibrary  {

Advapi32 INSTANCE = (Advapi32) Native.loadLibrary("Advapi32", Advapi32.class, Options.UNICODE_OPTIONS);
..
}

 

이상 끝.. 자바로 만들어져서 소스가 어렵지 않다.


참고...
아두이노 개발 툴 없이 소스와 툴만 가지고 컴파일할 때 사용하는 방법은 다음과 같다. 소스에서 사용하는 컴파일 방법과 동일하다.

http://andrew-j-norman.blogspot.com/2011/12/bypassing-ide.html

Posted by '김용환'
,

 

프로세싱(Processing) 개발 툴을 분석하고 나면,  아두이노 개발 툴 소스 분석은 더 쉽다. 
아두이노 개발 툴은 프로세싱 개발 툴 소스 depedency가 있거나 복사된 소스들이 많다.  (정확히 역사는 어떻게 되었는지는 모르지만..^^;)

1. 소스를 다운받는다.

http://www.arduino.cc/en/Main/software  페이지에서 소스를 다운받는다.

 

2. 이클립스 프로젝트 환경 구축

이클립스 프로젝트를 하나 생성하고 다운받은 소스를 복사한다. 아두이노 개발 툴 안에 이클립스 설정이 파일이 있다.

 

아두이노 개발 툴의 build path를 살펴보면, processing-core를 참조하고 있다. (processing.core 패키지를 참조하고 있다.)

 

프로세싱 소스는 아래 위치에서 git로 다운받는다.

http://code.google.com/p/processing/source/checkout

 

프로세싱 코드는 2.0으로 다운받으면, processing.app.RunnerListener 인터페이스를 모두 상속해서 구현하지 않았다고 아두이노 개발 툴 소스의 Editor 클래스 컴파일 에러가 난다. API 구현해주면 깨끗하게 컴파일 완료된다.

public class Editor extends JFrame implements RunnerListener {

…..


@Override
public void startIndeterminate() {
    // TODO Auto-generated method stub
   
}


@Override
public void stopIndeterminate() {
    // TODO Auto-generated method stub
   
}


@Override
public void statusHalt() {
    // TODO Auto-generated method stub
   
}


@Override
public boolean isHalted() {
    // TODO Auto-generated method stub
    return false;
}

 

컴파일 완료된 상태

<아두이노 소스>

 

빌드 연동

 

프로세싱 소스 (core/src 디렉토리의 소스만 잘 빌드되면 됨)

 

3. 소스 디렉토리 확인

UI 부분은 프로세싱과 거의 동일하다.

 

디버그 관련 패키지가 들어가 있다.  컴파일도 하고 실행했을 때 output 처리하는 것들. arduino 보드에 올리는 클래스들이 담겨 있다.

 

프로세싱 개발 툴은 java 기반이지만, 아두이노 개발 툴은 자바 기반 언어대신 C 언어의 특성을 가지고 있다.  (그래서 antlr는 사용할 필요가 없다.)
그러나 화면에 출력되는 문법에 맞는 색상을 칠해주는 패키지이다.

 

이 외. 운영체제(플랫폼)에 맞는 다양한 작업 패키지들이 있다. 여기도 프로세싱 개발 툴처럼  jna가 쓰이고 있다.

processing.app.linux, processing.app.windows, processing.app.macosx


4. 빌드

빌드는 build 디렉토리에 있는 build.xml를 가지고 ant 컴파일 하면 된다.

Buildfile: G:\workspace\arduino-1.0\build\build.xml
build:
revision-check:
windows-checkos:
subprojects-build:
compile:
....
 [launch4j] Compiling resources
 [launch4j] Linking
 [launch4j] Successfully created G:\workspace\arduino-1.0\build\windows\work\arduino.exe
BUILD SUCCESSFUL
Total time: 44 seconds


 

Posted by '김용환'
,

네이버 ndrive는 정말 속도도 좋고 안정석이 좋아서 많이 사용하면 할 수록 감탄하게 된다.

일본 ndrive 에서 무료로 30Gbyte를 제공한다.
http://ndrive.naver.jp/

웹에서는 일본어 환경인지 체크하기 때문에 가입이 쉽지 안핟.  그러나 iphone, ipad에서는 체크를 하지 않기 때문에 가입이 가능하다.  iphone 어플 의 경우는 캡챠를 이용하고, ipad 어플의 경우는 캡차를 이용하지 않는다.
일본어이긴 하지만 일본어 번역만 이용하면 누구나 가입할 수 있다.
또한 가입절차가 편해서 사용아이디, 이메일 만 가지고 쉽게 가입이 가능하다.


ndirve 한국 어플과 ndrive 일본 어플 설치 형의 경우는 중복 실행이 되지 않는다. 따라서 동시에 하나만 쓰도록 하면 된다.



그리고,아이패드, 아이폰에서 사용할 수 있다.



아. 좋다.
 
Posted by '김용환'
,

JAXB 잘 사용하기

general java 2011. 12. 21. 16:01

2010년 초에 정리한 줄 알았는데, 과거 메일을 참조로 정리한다.
이 내용은 현재 JAXB 버전과 다를 수 있다.


JAXB에 대해서..
java XML 파서중에 Xstream, JAXB 가 있다. 취향에 따라서 다양하게 쓰기는 하는데..
아직 까지는 잘만 쓴다면 JAXB가 성능이 조금 좋은 것 같았다. JAXB 에 대한 이슈와 오해를 정리하고 어떻게 해야 잘 쓸 수 있는 것인지 확인한다.


JAXB의 초기화
XML 파서인 JAXB는 DOM을 사용해서 상당히 느렸는데, 최근에는 SAX와 STAX를 사용하여 성능이 좋아지고 있으나, 주의해야 하는 것은 JAXBContext를 매번 생성하지 않고 한번 생성된 것을 재사용하는 구조로 써야 한다. QName의 String.intern을 많이 호출하는 성능 상 이슈와 GC상 perm 영역에서 minor GC가 많이 일어난다. 만약 Perm gen 영역을 작게 했을 때는 OOME가 perm gen에서 일어날 수도 있다.

많은 개발자들이 JAXB를 쓸 때 항상 조심해야 하는 것으로 한번만 JAXBContext 객체를 하라는 내용이 많으나 그 중의 hudson 개발자인 가와구치도 Hudson commitor들에게 얘기한 내용이 있다.

http://markmail.org/message/hy2tmioxev7skqkl#query:+page:1+mid:hy2tmioxev7skqkl+state:results

TIP 1. Never create the same JAXBContext more than once

-------------------------------------------------------
Always, always assign JAXBContext to a static final field. A JAXBContext object can be shared safely by multiple threads. This is a very expensive to create, so create it once, and share it.





JAXB의 Thread Safe

JAXBContext 가 Thread safe하지 않다고 알고 있는 사람이 있기도 하다. Thread safe 하다.다만..
마셜러와 언마셜러와 밸리데이터는 thread safe하지 않기 때문에 잘 써야 한다.  JAXBContext 를 단독으로 쓸 때 잘 유의할 필요는 있다. 그러나 Spring과 연계되어서 쓸 때는 내부적으로 잘 thread safe하게 해서 편하게 쓸 수 있다.

http://jaxb.java.net/guide/Performance_and_thread_safety.html


8.1. Performance and thread-safety
The JAXBContext class is thread safe, but the Marshaller, Unmarshaller, and Validator classes are not thread safe.

For example, suppose you have a multi-thread server application that processes incoming XML documents by JAXB. In this case, for the best performance you should have just one instance of JAXBContext in your whole application like this:

Singleton JAXBContext
class MyServlet extends HttpServlet {   
    static final JAXBContext context = initContext();   
    private static JAXBContext initContext() {       
        return JAXBContext.newInstance(Foo.class,Bar.class);   
    }
}

And each time you need to unmarshal/marshal/validate a document. Just create a new Unmarshaller/Marshaller/Validator from this context, like this:

Thread local Unmarshaller
    public void doGet( HttpServletRequest req, HttpServletResponse ) {       
         Unmarshaller u = context.createUnmarshaller();       
          u.unmarshal(...);   
    }

This is the simplest safe way to use the JAXB RI from multi-threaded applications.

If you really care about the performance, and/or your application is going to read a lot of small documents, then creating Unmarshaller could be relatively an expensive operation. In that case, consider pooling Unmarshaller objects. Different threads may reuse one Unmarshaller instance, as long as you don't use one instance from two threads at the same time.


 




JAXB의 Accessor
jaxb-api, jaxb-impl 2.0 library을 기준으로 사용했고, 다음의 2가지 기준이 맞아야 Permgen영역에서 OOME 가 발생한다.,

JAXB의 plain object는 다음과 같이 사용할 수 있다.




@XmlRootElement(name = "message")
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(propOrder = {"to", "from", "body"})
public class Message2 {
 private String to;

 private String from;

 private String body;
 
 @XmlElement(name = "TO")
 public String getTo() {
  return to;
 }
 public void setTo(String to) {
  this.to = to;
 }
 
 @XmlElement(name = "from")
 public String getFrom() {
  return from;
 }
 
 public void setFrom(String from) {
  this.from = from;
 }

 
 @XmlElement(name = "body")
 public String getBody() {
  return body;
 }
 
 public void setBody(String body) {
  this.body = body;
 }

}


 





원인을 잘 살펴본다.



Accessor의 GetterSetterReflection 인스턴스의 optimize가 호출되면, property에 대해서 getter와 setter 가 있는지 확인합니다.


         @Override
        public Accessor<BeanT,ValueT> optimize(JAXBContextImpl context) {
            if(getter==null || setter==null)
                // if we aren't complete, OptimizedAccessor won't always work
                return this;
            if(context!=null && context.fastBoot)
                // let's not waste time on doing this for the sake of faster boot.
                return this;

            Accessor<BeanT,ValueT> acc = OptimizedAccessorFactory.get(getter,setter);
            if(acc!=null)
                return acc;
            else
                return this;
        }
 


 

만약 getter/setter 메서드 중 하나라도 존재하지 않으면, 그냥 JAXB 내부의 Optimize 작업을 하지 않는다. 클래스 로딩 작업이 없게 된다. 만약 setter, getter가 존재하면, 내부 Optimize작업을 하는데,  Optimize 작업이 바로 내부 클래스를 직접 생성한다.

 OptimizedAccessorFactory 클래스 내부 소스를 보면, 선언된 클래스이름에 JaxbAccessorM_getter이름_setter이름_property타입의 클래스를 생성한다.

 String newClassName = toVMClassName(getter.getDeclaringClass())+"$JaxbAccessorM_"+getter.getName()+'_'+setter.getName()+'_'+typeName;

 

* 참고 : Accessor에 대한 개념 파악에 도움되는 글

Improving field get and set performance with ASM or Javassist

http://stackoverflow.com/questions/3022741/improving-field-get-and-set-performance-with-asm-or-javassist


public class AccessorGenerator { 
 
   
private final ClassPool pool; 
 
   
public PropertyGenerator() { 
        pool
= new ClassPool(); 
        pool
.appendSystemPath(); 
   
} 
 
   
public Map<String, PropertyAccessor> createAccessors(Class<?> klazz) throws Exception { 
       
Field[] fields = klazz.getDeclaredFields(); 
 
       
Map<String, PropertyAccessor> temp = new HashMap<String, PropertyAccessor>(); 
       
for (Field field : fields) { 
           
PropertyAccessor accessor = createAccessor(klazz, field); 
            temp
.put(field.getName(), accessor); 
       
} 
 
       
return Collections.unmodifiableMap(temp); 
   
} 
 
   
private PropertyAccessor createAccessor(Class<?> klazz, Field field) throws Exception { 
       
final String classTemplate = "%s_%s_accessor"; 
       
final String getTemplate = "public Object get(Object source) { return ((%s)source).%s; }"; 
       
final String setTemplate = "public void set(Object dest, Object value) { return ((%s)dest).%s = (%s) value; }"; 
 
       
final String getMethod = String.format(getTemplate,  
                                               klazz
.getName(), 
                                               field
.getName()); 
       
final String setMethod = String.format(setTemplate,  
                                               klazz
.getName(),  
                                               field
.getName(),  
                                               field
.getType().getName()); 
 
       
final String className = String.format(classTemplate, klazz.getName(), field.getName()); 
 
       
CtClass ctClass = pool.makeClass(className); 
        ctClass
.addMethod(CtNewMethod.make(getMethod, ctClass)); 
        ctClass
.addMethod(CtNewMethod.make(setMethod, ctClass)); 
        ctClass
.setInterfaces(new CtClass[] { pool.get(PropertyAccessor.class.getName()) }); 
       
Class<?> generated = ctClass.toClass(); 
       
return (PropertyAccessor) generated.newInstance(); 
   
} 
 
   
public static void main(String[] args) throws Exception { 
       
AccessorGenerator generator = new AccessorGenerator(); 
 
       
Map<String, PropertyAccessor> accessorsByName = generator.createAccessors(PurchaseOrder.class); 
 
       
PurchaseOrder purchaseOrder = new PurchaseOrder("foo", new Customer()); 
 
        accessorsByName
.get("name").set(purchaseOrder, "bar"); 
       
String name = (String) accessorsByName.get("name").get(purchaseOrder); 
       
System.out.println(name); 
   
} 
} 


jaxb가 reflection을 사용하면 속도가 너무 느리기 때문에 Accessor 클래스를 만들어서 속도를 향상시키고 private 멤버도 쉽게 접근하는 구조를 가지고 있다. 따라서 메소드를 확인하는 작업이 줄어들어서 속도를 향상시키는 패턴을 가지고 있다.로그를 보면 구체적으로 알 수 있다.


Plain Object인  Message2 를 만들고 JAXBContext로 클래스를 매번 생성하게 하였다.

@XmlRootElement(name = "message")
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(propOrder = {"to", "from", "body"})
public class Message2 {
 private String to;

 private String from;

 private String body;
 
 @XmlElement(name = "x")

 public String getTo() {
  return to;
 }
 public void setTo(String to) {
  this.to = to;
 }

 




jvm 출력 로그는 다음과 같다. AccessroM_ 클래스가 생성되었음을 확인할 수 있다.

[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getBody_setBody_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getBody_setBody_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getFrom_setFrom_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getFrom_setFrom_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getTo_setTo_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getTo_setTo_java_lang_String from __JVM_DefineClass__]


 

만약 getter/setter 메서드를 없앤 클래스를 테스트해보았다.

@XmlRootElement(name = "message")
@XmlAccessorType(XmlAccessType.FIELD)
public class Message2 {
 public String to;

 public String from;

 public String body;
}




출력되는 jvm 로그는 다음처럼 Accessor가 생기는 것을 볼 수 있다.

Loaded com.google.spring.bean.Message2$JaxbAccessorF_to from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_to from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_from from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_from from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_body from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_body from __JVM_DefineClass__]

 
JAXB OOME 테스트

매번 JAXBContext를 생성하면 과연 OOME가 일어날까?
jvm 의 perm gen 영역과 heap 영역을 작게 했다.

-XX:PermSize=2500k
-XX:MaxPermSize=2500k
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+TraceClassUnloading
-XX:+TraceClassLoading
-Xms10m
-Xmx10m





모니터링시
생성된 자바 loaded class 가 계속 상승하여 jvm에 부하를 주지만, heap, perm gen 영역은 그리 메모리가 크게 늘어나지 않았다.



아마도 OOME가 나는 이유는 web에서 session 단위로 hashmap으로 jsp로 전달되는 과정에서 (서블릿 스펙상 session은 최소 1초로 사용) 일어난 것으로 생각된다. 일반 Application에서는 큰 영향을 없을 수 있다.


마치며..
JAXB는 성능이 좋아 사람들에게 많이 쓰이고 있다. 매번 클래스를 Reflection 해서 정보를 얻어오기 보다  Accessor클래스를 생성하고 있다. 만약 JAXBContext 를 매번 호출하면 Accessor 클래스 로딩이 호출시마다 일어나 jvm, gc에 부하를 줄 수 있다. JAXBContext는 한번만 초기화하고 객체를 재사용할 필요가 있다.


Posted by '김용환'
,

 

첫번째 정리 : http://knight76.tistory.com/entry/프로세싱-언어와-PDE-코드-살펴보기-1

 

2번째 내용 시작

process/app/src 소스를 보면서 접근했다. JDI 코드 부분을 제외한 전반적인 코드를 분석한다.

 

1. Base, Editor 클래스

main 클래스는 processing.app.Base.java 이다.

Base 클래스의 createAndShowGUI() 메서드는 간단하게 버전 정도를 확인하고 swing component를 생성하고 나서 jdk 의 버전을 확인한다. 그리고, 설정 정보(윈도우 크기 등)을 읽는다. OS에 맞는 환경(platform)을 잘 생성하여 어디에서도 문제없이 동작하게 한다. 임시 디렉토리도 확인한 후, Base 클래스의 생성자를 호출한다.

 

* Base 클래스의 main 함수와 관련 메서드

public class Base {


  static public void main(final String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
          createAndShowGUI(args);
        }
    });
  }

  static private void createAndShowGUI(String[] args) {
    try {
      File versionFile = getContentFile("lib/version.txt");
      if (versionFile.exists()) {
        String version = PApplet.loadStrings(versionFile)[0];
        if (!version.equals(VERSION_NAME)) {
          VERSION_NAME = version;
          RELEASE = true;
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    initPlatform();

    // Use native popups so they don't look so crappy on osx
    JPopupMenu.setDefaultLightWeightPopupEnabled(false);

    // Don't put anything above this line that might make GUI,
    // because the platform has to be inited properly first.

    // Make sure a full JDK is installed
    initRequirements();

    // run static initialization that grabs all the prefs
    Preferences.init(null);

//    String filename = args.length > 1 ? args[0] : null;
    if (!SingleInstance.exists(args)) {
      SingleInstance.createServer(platform);

      // Set the look and feel before opening the window
      try {
        platform.setLookAndFeel();
      } catch (Exception e) {
        String mess = e.getMessage();
        if (mess.indexOf("ch.randelshofer.quaqua.QuaquaLookAndFeel") == -1) {
          System.err.println("Non-fatal error while setting the Look & Feel.");
          System.err.println("The error message follows, however Processing should run fine.");
          System.err.println(mess);
        }
      }

      // Create a location for untitled sketches
      try {
        untitledFolder = Base.createTempFolder("untitled", "sketches");
        untitledFolder.deleteOnExit();
      } catch (IOException e) {
        //e.printStackTrace();
        Base.showError("Trouble without a name",
                       "Could not create a place to store untitled sketches.\n" +
                       "That's gonna prevent us from continuing.", e);
      }

//  System.out.println("about to create base...");
      new Base(args);
//  System.out.println("done creating base...");
    }
  }

 

Base 클래스의 생성자에서는 스케치 북 (프로세싱 디렉토리) 폴더를 확인한다. mode 디렉토리 안에 있는  jar나 zip으로 끝나는 파일들을 모아서 classpath에 넣어서 컴파일시 유용하게 사용할 수 있도록 한다. java/android/javascript를 지원하고 있다.

OS에 맞는 UI (look & Feel)에 맞게 Editor 클래스(JFrame)을 초기화한다. 프로세싱 pde 툴을 작업하고 종료했으면, 기존에 저장된 소스를 불러온다. 만약 그런 경우가 없다면 빈 화면만 보이게 한다.

만약  PDE 개발툴의 업데이트가 존재하면 알려준다.

public Base(String[] args) {
    // Get the sketchbook path, and make sure it's set properly
    determineSketchbookFolder();

    // Delete all modes and tools that have been flagged for deletion before
    // they are initialized by an editor.
    ArrayList<InstalledContribution> contribs = new ArrayList<InstalledContribution>();
    contribs.addAll(ModeContribution.list(this, getSketchbookModesFolder()));
    contribs.addAll(ToolContribution.list(getSketchbookToolsFolder(), false));
    for (InstalledContribution contrib : contribs) {
      if (ContributionManager.isFlaggedForDeletion(contrib)) {
        removeDir(contrib.getFolder());
      }
    }

    buildCoreModes();
    rebuildContribModes();

    libraryManagerFrame = new ContributionManagerDialog("Library Manager",
                                                        new ContributionListing.Filter() {
      public boolean matches(Contribution contrib) {
        return contrib.getType() == Contribution.Type.LIBRARY
            || contrib.getType() == Contribution.Type.LIBRARY_COMPILATION;
      }
    });
    toolManagerFrame = new ContributionManagerDialog("Tool Manager",
                                                     new ContributionListing.Filter() {
      public boolean matches(Contribution contrib) {
        return contrib.getType() == Contribution.Type.TOOL;
      }
    });
    modeManagerFrame = new ContributionManagerDialog("Mode Manager",
                                                     new ContributionListing.Filter() {
      public boolean matches(Contribution contrib) {
        return contrib.getType() == Contribution.Type.MODE;
      }
    });
    updateManagerFrame = new ContributionManagerDialog("Update Manager",
                                                       new ContributionListing.Filter() {
      public boolean matches(Contribution contrib) {
        if (contrib instanceof InstalledContribution) {
          return ContributionListing.getInstance().hasUpdates(contrib);
        }

        return false;
      }
    });

    // Make sure ThinkDifferent has library examples too
    defaultMode.rebuildLibraryList();

    // Put this after loading the examples, so that building the default file
    // menu works on Mac OS X (since it needs examplesFolder to be set).
    platform.init(this);

    toolsFolder = getContentFile("tools");

//    // Get the sketchbook path, and make sure it's set properly
//    determineSketchbookFolder();

//    // Check if there were previously opened sketches to be restored
//    boolean opened = restoreSketches();
    boolean opened = false;

    // Check if any files were passed in on the command line
    for (int i = 0; i < args.length; i++) {
      String path = args[i];
      // Fix a problem with systems that use a non-ASCII languages. Paths are
      // being passed in with 8.3 syntax, which makes the sketch loader code
      // unhappy, since the sketch folder naming doesn't match up correctly.
      // http://dev.processing.org/bugs/show_bug.cgi?id=1089
      if (isWindows()) {
        try {
          File file = new File(args[i]);
          path = file.getCanonicalPath();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if (handleOpen(path) != null) {
        opened = true;
      }
    }

    // Create a new empty window (will be replaced with any files to be opened)
    if (!opened) {
//      System.out.println("opening a new window");
      handleNew();
//    } else {
//      System.out.println("something else was opened");
    }

    // check for updates
    if (Preferences.getBoolean("update.check")) {
      new UpdateCheck(this);
    }
  }

 

Editor 클래스는 JFrame을 상속했으며, 화면 UI를 담당한다.

public abstract class Editor extends JFrame implements RunnerListener

에디터의 기본 내용들은 processing.app 패키지에 담겨져 있다.

 

2. Contribution

여기에 기존 library외에 특정 폴더에 lib 또는 zip파일을 복사하면 프로세싱 pde 툴이 시작하면서 그 파일들을 모두 읽는다. 이런 파일들을 processing 툴에서는 contribution이라는 단어를 써서 하고 있다. 파일을 다운받고 설치할 수 있기도 하고, 메뉴에서 refresh 해서 다시 library 정보를 취합할 수 있다. 관련 내용은 processing.app.contrib.ContributionManager.java 과 processing.app.contrib.Contribution.java 에 담겨있다.

 

3. Process

안드로이드환경에서 avd를 생성하거나 출력, adb, keytool, mv 명령을 내릴 때 필요한 정보들은 processing.app.exec 패키지에 담겨 있다.

 

4. Platform

linux, mac, window 에 맞는 UI를 실행시키거나 OS-dependent한 코드들을 실행할 때 필요한 내용들이 담겨 있다. 예를 들어 윈도우의 경우 폴더를 실행시킬 때, explorer를 실행시켜야 한다. 또한 msvcrt.dll(printf와 같은 stdio/stdout에 해당되는 c 런타임 라이브러리로서 sta)를 jna로 연결한다. 그리고, 마우스 오른쪽 버튼 연동을 하여 자바 Swing 어플에서 폴더를 열 때, 윈도우에서 폴더 열어서 이것 저것(예, 폴더 생성)을 할 수 있게 처리하는 코드가 담겨 있다.

관련 패키지는 processing.app.windows, processing.app.linux, processing.app.macosx  이다.

 

5. Syntax

processing.app.DefaultInputHander 클래스 소스에서는 Editor를 사용하기 때문에 키보드로부터 다양한 입력이 들어오는데. 미리 저장해둔 키보드 액션 값(Ctrl + C와 같은 복사)인지 코드(Code)인지를 분간한다. 또한, 예전 JEdit Syntax (syntax.jedit.org) 의 Code highlight 형식을 적용하여 예쁘게 색칠해 준다. 키워드일 때는 색깔을 입혀 코드 작성자가 깜박하지 않도록 도와준다.

코드를 토큰을 줄 단위로 나눈 후 토큰을 토큰 리스트(linked list)로 연결했다.

 

관련 코드는 processing.app.syntax에 있다.

 

6. tools

소스를 압축하고, 색깔을 선택하거나, 인코딩, 폰트 설정과 같은 Tool 성격의 클래스가 담겨져 있다.

 

7, mode

프로세싱은 android, javascript, java 중 세가지 모드로 export할 수 있는 코드로 구성되어 있다.

이 중 특별하게 javascript mode는 javascript 코드를 작성하면, 이를 processing.js 파일로 만든 후, 소스는 그냥 PApplet 클래스로 해서  웹 브라우져로 보여주도록 코드 작성이 되어 있다. processing.mode.javascript 패키지에 내용이 담겨 있다.

그리고, android mode는 avd도 연동할 수 있도록 편리한 클래스를 제공하고 있다. processing.mode.android 패키지에 잘 설명이 되어 있다. 완전히 안드로이드 향에서 동작되어 있는 코드로 되어 있다. 자세한 것은 안보고 패쓰.. 중요한 것은 java에 있으니 그곳으로 집중.

마지막으로 남은  java mode를 살펴본다. java mode에서는 antlr과 java debug interface를 사용하고 있었다. (사실 이 것 때문에 소스 분석한 것이긴 하다.)

 

먼저 빌드(app/build.xml) 소스를 확인해 본다.

가정 먼저 antlr 문법에 맞게 전처리 작업을 전행한다. processing/mode/java/preporc/java15.g 파일을 읽어서 소스 generate 한다. 그 다음 같은 디렉토리에 있는 pde.g 파일을 읽고 소스를 generate 한다.

 

 

  <property name="generated"
        value="${basedir}/generated/processing/mode/java/preproc" />
  <mkdir dir="${generated}" />

  <property name="grammars"
        value="${basedir}/src/processing/mode/java/preproc" />

 

……………..

<target name="preproc" description="Compile ANTLR grammar">
    <antlr target="${grammars}/java15.g" outputdirectory="${generated}">
      <classpath path="${antlrjar}" />
    </antlr>
    <antlr target="${grammars}/pde.g"
       outputdirectory="${generated}"
       glib="${grammars}/java15.g">
      <classpath path="${antlrjar}" />
    </antlr>
  </target>

  <target name="compile" depends="preproc" description="Compile sources">
    <condition property="core-built">
      <available file="../core/core.jar" />
    </condition>
    <fail unless="core-built"
      message="Please build the core library first and make sure it sits in ../core/core.jar" />

    <mkdir dir="bin" />

 

java15.g는 antlr이 이해할 수 있는 java1.5 언어 문법 파일이다. 이 파일이 antlr에 의해서 생성되는 클래스는 파서(class JavaRecognizer extends Parser) 과 스캐너 (class JavaLexer extends Lexer) 이다. 그리고, pde.g 은 antlr에 의해서 파서(class PdeRecognizer extends JavaRecognizer)와 스캐너 (class PdeLexer extends JavaLexer)와 토큰에 대한 정의 정보 (interface PdeTokenTypes)가 생성된다.

컴파일된 클래스는 아래 위치에서 확인해 볼 수 있었다.

G:\android\workspace\processing\app\bin\processing\mode\java\preproc

 

프로세싱 PDE에서 application 또는 applet으로 export 할 때, 빌드가 일어나고 실행할 수 있게 되어 있다. processing.mode.java.JavaBuild.java가 중요 핵심 클래스가 된다. 

processing.mode.java.JavaBuild 클래스의 exportApplication() 메서드에서 application빌드하고  exportApplication(File destFolder, int exportPlatform,int exportBits) 메서드에서 실행을 한다.

만약 윈도우라면 아래와 같이 자바가 실행이 된다.


if (exportPlatform == PConstants.WINDOWS) {
      if (exportBits == 64) {
        // We don't yet have a 64-bit launcher, so this is a workaround for now.
        File batFile = new File(destFolder, sketch.getName() + ".bat");
        PrintWriter writer = PApplet.createWriter(batFile);
        writer.println("@echo off");
        writer.println("java -Djava.ext.dirs=lib -Djava.library.path=lib " + sketch.getName());
        writer.flush();
        writer.close();
      } else {
        Base.copyFile(mode.getContentFile("application/template.exe"),
                      new File(destFolder, sketch.getName() + ".exe"));
      }
    }

 

빌드과정을 조금 따라가 본다.

processing.mode.java.JavaBuild.java 파일의 build 파일이 실행되는 시점부터 코드를 따라간다. 먼저 전처리 과정을 한 후, 컴파일을 한다.

  public String build(File srcFolder, File binFolder) throws SketchException {
    this.srcFolder = srcFolder;
    this.binFolder = binFolder;

//    Base.openFolder(srcFolder);
//    Base.openFolder(binFolder);
   
    // run the preprocessor
    String classNameFound = preprocess(srcFolder);

    // compile the program. errors will happen as a RunnerException
    // that will bubble up to whomever called build().
//    Compiler compiler = new Compiler(this);
//    String bootClasses = System.getProperty("sun.boot.class.path");
//    if (compiler.compile(this, srcFolder, binFolder, primaryClassName, getClassPath(), bootClasses)) {
    if (Compiler.compile(this)) {
      sketchClassName = classNameFound;
      return classNameFound;
    }
    return null;
  }

 

processing.mode.java.preproc.PdePreprocessor.preprocess() 메서드가 호출되면, 파서가 가장 먼저 동작한다. 파서가 만든 AST tree를 traverse할 수 있는 pdeEmitter를 사용한다. PrintWriter 객체에 java로 컴파일가능한 소스를 생성한다. 그리고 classpath 에 library를 추가하고, 임시 파일을 생성(Base.saveFile()호출)한다. 그리고, 원래대로 돌아가 컴파일 하는 구조이다.


8. JDI (java debugger interface)
JPDA의 하이 레벨의 인터페이스인 JDI를 정의하는 코드가 processing.mode.java.runner 패키지에 담겨있다.

어플리케이션에서 run 또는 present를 실행시 JavaMode클래스의 handleRun메서드를 실행하고,
Runner  클래스의   launchVirtualMachine()메소드 실행으로 이어진다.
실제 어플리케이션에서 어플리케이션을 실행시키면서 jpda로 연결해서 테스트가 가능할 수 있도록 한다.


 Runner 클래스의 launch 메서드는 다음처럼 먼저 vm을 실행시키고, trace 를 생성한다.

public void launch(boolean presenting) {
    this.presenting = presenting;
   
    // all params have to be stored as separate items,
    // so a growable array needs to be used. i.e. -Xms128m -Xmx1024m
    // will throw an error if it's shoved into a single array element
    //Vector params = new Vector();

    // get around Apple's Java 1.5 bugs
    //params.add("/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Commands/java");
    //params.add("java");
    //System.out.println("0");

    String[] machineParamList = getMachineParams();
    String[] sketchParamList = getSketchParams();

    vm = launchVirtualMachine(machineParamList, sketchParamList);
    if (vm != null) {
      generateTrace(null);
//      try {
//        generateTrace(new PrintWriter("/Users/fry/Desktop/output.txt"));
//      } catch (Exception e) {
//        e.printStackTrace();
//      }
    }
  }




VM 실행에 대한 코드이다.  debug에 대한 정보가 보인다.


  protected VirtualMachine launchVirtualMachine(String[] vmParams,
                                                String[] classParams) {
    //vm = launchTarget(sb.toString());
    LaunchingConnector connector = (LaunchingConnector)
      findConnector("com.sun.jdi.RawCommandLineLaunch");
    //PApplet.println(connector);  // gets the defaults

    //Map arguments = connectorArguments(connector, mainArgs);
    Map arguments = connector.defaultArguments();

    Connector.Argument commandArg =
      (Connector.Argument)arguments.get("command");
    // Using localhost instead of 127.0.0.1 sometimes causes a
    // "Transport Error 202" error message when trying to run.
    // http://dev.processing.org/bugs/show_bug.cgi?id=895
    String addr = "127.0.0.1:" + (8000 + (int) (Math.random() * 1000));
    //String addr = "localhost:" + (8000 + (int) (Math.random() * 1000));
    //String addr = "" + (8000 + (int) (Math.random() * 1000));

    String commandArgs =
      "java -Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y ";
    if (Base.isWindows()) {
      commandArgs =
        "java -Xrunjdwp:transport=dt_shmem,address=" + addr + ",suspend=y ";
    } else if (Base.isMacOS()) {
      commandArgs =
        "java -d" + Base.getNativeBits() + //Preferences.get("run.options.bits") + 
        " -Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y ";
    }

    for (int i = 0; i < vmParams.length; i++) {
      commandArgs = addArgument(commandArgs, vmParams[i], ' ');
    }
    if (classParams != null) {
      for (int i = 0; i < classParams.length; i++) {
        commandArgs = addArgument(commandArgs, classParams[i], ' ');
      }
    }
    commandArg.setValue(commandArgs);

    Connector.Argument addressArg =
      (Connector.Argument)arguments.get("address");
    addressArg.setValue(addr);

    //PApplet.println(connector);  // prints the current
    //com.sun.tools.jdi.AbstractLauncher al;
    //com.sun.tools.jdi.RawCommandLineLauncher rcll;

    //System.out.println(PApplet.javaVersion);
    // http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#sunlaunch

      return connector.launch(arguments);






trace를 생성하는 메서드이다. VM에 대해서 debug trace를 걸고, event thread 를 생성한다.
VM을 띄운 후, 어플리케이션 출력코드를 다 받아와 editor에 화면에 보여줄 수 있도록 한다.

  /**
   * Generate the trace.
   * Enable events, start thread to display events,
   * start threads to forward remote error and output streams,
   * resume the remote VM, wait for the final event, and shutdown.
   */
  protected void generateTrace(PrintWriter writer) {
    vm.setDebugTraceMode(debugTraceMode);

    EventThread eventThread = null;
    //if (writer != null) {
    eventThread = new EventThread(this, vm, excludes, writer);
    eventThread.setEventRequests(watchFields);
    eventThread.start();
    //}

    //redirectOutput();

    Process process = vm.process();

//  processInput = new SystemOutSiphon(process.getInputStream());
//  processError = new MessageSiphon(process.getErrorStream(), this);

    // Copy target's output and error to our output and error.
//    errThread = new StreamRedirectThread("error reader",
//        process.getErrorStream(),
//        System.err);
    MessageSiphon ms = new MessageSiphon(process.getErrorStream(), this);
    errThread = ms.getThread();

    outThread = new StreamRedirectThread("output reader",
        process.getInputStream(),
        System.out);

    errThread.start();
    outThread.start();

    vm.resume();
    //System.out.println("done with resume");

    // Shutdown begins when event thread terminates
    try {
      if (eventThread != null) eventThread.join();
//      System.out.println("in here");
      // Bug #852 tracked to this next line in the code.
      // http://dev.processing.org/bugs/show_bug.cgi?id=852
      errThread.join(); // Make sure output is forwarded
//      System.out.println("and then");
      outThread.join(); // before we exit
//      System.out.println("out of it");

      // At this point, disable the run button.
      // This happens when the sketch is exited by hitting ESC,
      // or the user manually closes the sketch window.
      // TODO this should be handled better, should it not?
      if (editor != null) {
        editor.deactivateRun();
      }
    } catch (InterruptedException exc) {
      // we don't interrupt
    }
    //System.out.println("and leaving");
    if (writer != null) writer.close();
  }




전형적인 JPDA FE 쓰레드를 사용하고 있으며, 어플리케이션에서 어플리케이션을 띄우고 관련 로그를 부모 어플리케이션으로 보내거나 할 때 많이 유용하다. eclipse 의 어플리케이션 디버그도 이와 비슷하다.



(출처 : http://openjdk.java.net/groups/serviceability/jpdaFEThreads.jpg)
                                               

Posted by '김용환'
,

<서론>

image

프로세싱 언어는 오픈 소스이며, 기존 언어보다 쉽게 이미지나 애니메이션 를 렌더링하거나 소통(interaction)할 수 있도록 설계된 언어이다. 기존의 개발 언어를 사용하는 사람들이 학생, 예술가, 취미로 하는 사람들이 언어를 쉽게 쓸 수 있도록 만들어졌다. 마인드스톰처럼 MIT 에서 시작되었다.

이 언어는 자바언어를 바탕으로 만들어진 DSL(Domain Specific Language) 중의 하나이다. 기존의 자바를 바탕으로 만들어진 언어이다.

현재 프로세싱(processing.org) 싸이트에서 프로세싱 언어와 PDE라는 개발 도구를 제공하고 있다. 해당 소스가 자바로 만들어져 있었음을 확인하고, 쉽게 소스 파악이 될 것 같아서 분석해 본다.

 

<본론>

1. 소스 다운받기

2011년 12월 현재 프로세싱은 1.5이나, trunk 소스는 2.0으로 개발이 되고 있다. 소스는 “http://code.google.com/p/processing/” 에서 다운받을 수 있다.공식 svn  주소는  http://processing.googlecode.com/svn/trunk/ 이다.

svn 소스를 checkout 받는다.

image

 

프로세싱 소스는 processing 디렉토리에 있다. 이 디렉토리를 이클립스에 복사한다.

 

image

 

2. 컴파일하기

컴파일은 여러 단계로 나누어진다.

첫번째, core 디렉토리에 있는 build.xml 파일을 컴파일한다. core.jar가 output으로 나오는 것이 core 디렉토리의 중요한 목표이다.

Buildfile: G:\android\workspace\processing\core\build.xml
compile:
  [methods] No changes to PApplet API.
    [javac] Compiling 15 source files to G:\android\workspace\processing\core\bin
build:
      [jar] Building jar: G:\android\workspace\processing\core\core.jar
BUILD SUCCESSFUL
Total time: 1 second

 

두번째, app 디렉토리에 있는 build.xml 파일을 컴파일 한다. pde(processing developement environment) 파일(pde.jar)이 생성된다.

Buildfile: G:\android\workspace\processing\app\build.xml
preproc:
compile:
    [javac] Compiling 2 source files to G:\android\workspace\processing\app\bin
build:
      [jar] Building jar: G:\android\workspace\processing\app\pde.jar
BUILD SUCCESSFUL
Total time: 4 seconds

 

세번째, build 디렉토리 밑에 있는 build.xml 파일을 컴파일한다. JAVA_HOME 환경변수 설정이 중요하다. 컴파일이 완료되면, processing.exe 파일이 생성된다. 이 파일은 processing을 실행시키는 녀석으로 간단히 링킹만 한다.

…..

[launch4j] Compiling resources
[launch4j] Linking
[launch4j] Successfully created G:\android\workspace\processing\build\windows\work\processing.exe
BUILD SUCCESSFUL
Total time: 55 seconds

image

 

G:\android\workspace\processing\build\windows\launcher 디렉토리 밑에는 java 클래스를 특정 OS에서 실행 가능하게 만들어주는있는 launch4j (http://launch4j.sourceforge.net/) 가 있는 것을 확인할 수 있다.

How to use Launch4

 

3. 실행해 보기

G:\android\workspace\processing\build\windows\work 디렉토리에는 실행 가능한 processing.exe 파일이 생성되고, 바로 PDE를 실행할 수 있다.

image

실행하면, Processing Splash 윈도우가 보이며, 정적 리소스 로딩이 완료되면, PDE 화면이 바로 다음에 보인다.

image

 

image

 

3. 이클립스 빌드 설정

image

image

 

이클립스 소스 일부가 에러로 되어 있는 것은 antlr 관련 소스들이거나 파일 type이 utf-8이 아니라서 나는 것이다.아래 코드는 antlr 관련 코드라서 antlr에 의해서 generated 된 코드를 예상하고 만들어진 것이라 에러가 아니다.

image

 

자세한 것은 소스를 보면 ant 소스를 보면서 분석해야지

 

<결론>

프로세싱 언어와 PDE에 대해서 컴파일 / 이클립스 개발 환경을 구축했다.  다음부터는 소스 레벨로 깊이 들어가 보자.

Posted by '김용환'
,

 

몸이 너무 덥고, 전자파가 나오는 선풍기는 싫고 해서, 어떻게 하면 좋을까 고민했다.

작년에 미국 출장가신 분으로부터 받은 선풍기가 떠올랐다. 이 분이 선물을 주셨는데, 전원 어댑터를 주시지 않아서, 집안에서 쳐박혀 있었다. 어제 자세히 전원정보를 보니. 3v 직류에 500mA로 연결하면 된다.

IMG_1079

아두이노가 3V, 5V 지원이 되니. 한번 해봐야겠다는 생각이 들었다.

이렇게 한번 연결해보았다.

IMG_1077

 

잘 연결하니 잘 돌아간다.~ 하하하~

IMG_1078

여기에 온도센서와 시간정보를 프로세싱으로 처리하면 일과시간에만 돌아갈 수 있는 선풍기를 개발할 수 있을 것 같다.

Posted by '김용환'
,