The Linux Serial Programming HOWTO 리눅스시리얼프로그래밍하우투 Peter H. Baumann, Peter.Baumann@dir.de v1.0, 22 January 1998 전성민, schun@crypto.pe.kr 2000 년 2 월 24 일 이문서는리눅스시스템에서시리얼통신을어떻게프로그래밍하는가를설명하는하우투문서이다. 1. 소개 1.1 저작권 1.2 이문서의최신버전 1.3 Feedback 2. 시작하기 2.1 디버깅 2.2 포트세팅 2.3 시리얼장치의입력방법 3. 프로그램예제 3.1 Canonical 입력처리 (Canonical Input Processing) 3.2 Non-Canonical 입력처리 (Non-Canonical Input Processing) 3.3 비동기입력 3.4 입력장치멀티플렉싱 4. 다른유용한정보 5. 기여한사람들
1. 소개 이문서는리눅스시스템에서시리얼통신을어떻게프로그래밍하는가를설명하는하우투문서이다. 다양한시리얼통신프로그램기법들 (Canonical I/O, 비동기 I/O, I/O 멀티플렉싱 ) 을설명한다. 시리얼포트를셋업하는방법에대해서는 Greg Hankins 의 Serial-HOWTO 문서를읽어보기바란다. 나는이분야의전문가가아님을먼저밝혀둔다. 그러나프로젝트를하면서많은문제들에부딪혔다. 이문서에소개된코드예제들은 LDP(Linux Document Project) 프로그래머가이드 ( ftp://sunsite.unc.edu/pub/linux/docs/ldp/programmers-guide/lpg-0.4.tar.gz 및미러사이트들 ) 에서구할수있는 miniterm code 에근거하여만들었다. 1997 년 6 월에이문서를작성한뒤에나는고객의요구에의해 Win NT 환경으로옮기게되어서더이상깊은지식을얻을수없었다. 누구든지커맨트가있으면이문서를관리하는데에기꺼이활용하겠다.(Feedback 부분을볼것 ) 또한이문서의유지및관리를맡고싶거나 version UP 을원한다면 e-mail 을주기바란다. 이문서의모든예제는 i386 Linux Kernel 2.0.29 에서테스트를하였다. 1.1 저작권 The Linux Serial-Programming-HOWTO is copyright (C) 1997 by Peter Baumann. Linux HOWTO documents may be reproduced and distributed in whole or in part, in any medium physical or electronic, as long as this copyright notice si retained on all copies. Commercial redistribution is allowed and encouraged; however, the author would like to be notified of any such distributions. All translations, derivative works, or aggregate works incorporating any Linux HOWTO documents must be covered under this copyright notice. That is, you may not produce a derivative work from a HOWTO and impose additional restrictions on its distribution. Exceptions to thes rules may be granted under certain conditions; please contact the Linux HOWTO coordinator at the address given below. In short, we wish to promote dissemination of this information through as many channels as possible. However, we do wish to retain copyright on the HOWTO documents, and would like to be notified of any plans to redistribute the HOWTOs. If you have questions, please contact Tim Bynum, the Linux HOWTO coordinator, at linux-howto@sunsite.unc.edu via email. 1.2 이문서의최신버전 이문서의최신버전은 ftp://sunsite.unc.edu/pub/linux/docs/howto/serial- Programming-HOWTO 와미러사이트에서찾을수있다. PostScript 및 DVI 버전도다른포맷들을갖고있는디렉토리에있다. http://sunsite.unc.edu/ldp/howto/serial- Programming-HOWTO.html 에서도구할수있으며, comp.os.linux.answers 에매달포스
팅될것이다. 1.3 Feedback 오류, 질문, 커맨트, 건의사항이나그외추가할사항이있다면알려주기바란다. 이 HOWTO 를계속적으로업데이트하기를원한다. 당신이정확히이해하지못하는부분이있거나더명확해야할부분이있다면알려줘라. 내 e-mail 주소는 Peter.Baumann@dir.de 이다. 이문서에관한 e-mail 을보낼때는문서의버전도같이알려줘라. 이문서버전은 1.0 이다.
2. 시작하기 2.1 디버깅 코드를디버깅하는가장좋은방법은또하나의리눅스박스를셋업하고두리눅스박스를 null-modem 케이블로연결하는것이다. Miniterm 프로그램을이용하여문자들을전송해보라. Miniterm 은컴파일하기도쉽고, 키보드에서입력되는문자들 ( 특수문자포함 ) 을시리얼포트로전송할수있다. 컴파일할때체크해야할것은 #define MODEMDEVICE "/dev/ttys0" 문장이제대로설정되어있는가하는것이다. COM1 으로맞추려면 /dev/ttys0, COM2 로하려면 /dev/ttys1 으로수정한다. 테스팅을할때가장중요한것은문자가시리얼포트로출력될때데이터가출력데이터처리 (output processing) 을하지않고그대로 (raw) 전송이되는가를확인하는것이다. 테스트과정은다음과같다. 두대의리눅스박스에서각각 miniterm 프로그램을실행시키고키보드를쳐본다. 한곳에서타이핑한문자가다른곳에서그대로나타나는지확인한다. Null-modem 케이블의 TxD 와 RxD 가서로 cross 연결이되어야한다. 잘모르겠으면 Serial-HOWTO 문서의 7 장을본다. 위의테스트는한대의컴퓨터만갖고도가능하다. 사용할수있는시리얼포트가두개있다면케이블을각각의시리얼포트에연결하고 miniterm 을두개실행하여테스트하면된다. 2.2 포트세팅 장치파일인 /dev/ttys* 는리눅스에서터미널을연결하기위한목적으로사용된다. 이사실은시리얼통신프로그래밍을하는데반드시기억해야할사항이다. 예를들어, 시리얼포트도문자에코를하도록설정되어있다. 이설정은보통데이터전송시에바꿔야할사항이다. ( 역자주 : 시리얼장치파일도터미널장치로분류되기때문에초기설정은일반터미널에서사용되는에코가되도록설정되어있는것이다.) 모든파라미터들은프로그램코드에서쉽게설정할수있다. 파라미터들은 <asm/termbits.h> 에정의되어있는 struct termios 구조체에저장되어있다. #define NCCS 19 struct termios { tcflag_t c_iflag; /* input mode flags tcflag_t c_oflag; /* output mode flags tcflag_t c_cflag; /* control mode flags tcflag_t c_lflag; /* local mode flags cc_t c_line; /* line discipline cc_t c_cc[nccs]; /* control characters ; 이파일은모든 flag 들을정의하고있다. c_iflag( 입력모드 flag) 는모든입력처리 (input processing) 를정의한다. 입력처리란 read() 함수에의해시리얼포트로들어온데이터를 read 에의해읽기전에데이터들을 c_iflag 에정의한대로처리하는것을의마한다. c_oflag
( 출력모드 flag) 는출력처리 (output processing) 하는방법을정의한다. c_cflag( 제어모드 flag) 는 baudrate, data bits, stop bits 등의포트세팅을정의한다. c_lflag(local 모드 flag) 는 echo 를할것인지등을결정한다. 마지막으로 c_cc( 제어문자 ) 배열은 EOF(End of File), STOP 등의제어동작들을어떤문자로정의할것인가를설정한다. 제어문자의디폴트문자는 <asm/termios.h> 에정의되어있다. 위 flag 들에관한설명은 termios(3) man page 에나와있다. termios 구조체의 c_line 항목은 POSIX 호환시스템에서사용되지않는다. 2.3 시리얼장치의입력방법 이섹션에서는세가지의입력방법을기술하기로한다. 응용분야에따라서알맞은방법을사용해야한다. 한문자씩읽는루프를돌려서전체문자열을받는방법은가능하다면피해야한다. 내가이런방법으로했을때, 문자를잃어버리는경우가생긴반면, 전체문자열을한번에읽을때는에러가발생하지않았다. Canonical 입력처리 (Canonical Input Processing) Canonical 입력처리는터미널의기본처리방법이다. 이방법은한줄단위로처리하는다른프로그램과통신하는데에사용할수있다. 한줄은디폴트로 NL(New Line, ASCII 는 LF) 문자, EOF(End of File) 문자, 혹은 EOL(End of Line) 에의해종료되는문자열을의미한다. CR(Carriage Return, DOS/Windows 의디폴트 EOL 문자임 ) 문자는디폴트세팅에서한줄의종료문자로인식되지않는다. 또한 Canonical 입력처리모드에서는 ERASE, DELETE WORD, REPRINT CHARACTERS 문자들을처리할수있고, CR 문자를 NL 문자로변환처리를할수있다. Non-Canonical 입력처리 (Non-Canonical Input Processing) Non-Canonical 입력처리모드에서는한번읽을때마다정해진크기의문자만을읽어낼수있다. 또한타이머를두어서일정시간까지 read() 가리턴하지않는경우강제리턴을할수있다. 이모드는항상정해진크기의문자들만을읽어내거나대량의문자들을전송하고자할때사용한다. 비동기입력 위에서설명한두가지모드는동기방식이나비동기방식으로사용될수있다. 동기방식은 read 의조건이만족될때까지 block 되는방식으로서디폴트로설정되어있다. 비동기방식에서는 read() 함수가바로리턴되며, 호출한프로그램에게 signal 을보낸다. 이 signal 은 signal handler( 시그널처리함수 ) 로보내진다. 입력장치멀티플렉싱 위에서설명한입력모드에해당하진않지만여러개의장치들을다루고자할때유용하다. 예를들어내응용프로그램에서 TCP/IP 소켓과시리얼통신에서동시에입력을받아야했다. 아래 3.4 의예제는두개의서로다른장치로부터동시에입력을기다리는코드이다. 둘중한개의장치에서입력이들어오면처리를하고또다시새로운입력이올때까지기다린다. 아래 3.4 의예제는복잡해보일수있지만, 리눅스가 multi-processing OS 임을알고있기에매우중요하다. select() 시스템호출함수는입력을기다리는동안 CPU 에부하를주지않는다. 반면입력이들어왔는지루프를돌면서체크하는 polling 방식은시스템에부하를주게되어다른프로세스의수행속도를저하시키게된다.
3. 프로그램예제 여기의모든예제는 miniterm.c 에서따왔다. Canonical 입력처리에서처리할수있는최대길이의문자는 255 개 (<linux/limits.h> 혹은 <posix1_lim.h> 에정의됨 ) 로서버퍼의최대길이는 255 로제한된다. 여러입력처리모드의사용법에대한설명을원하면코드내의 comment 를참조하라. 코드가이해하기쉽기를바란다. Canonical 입력처리모드의예제는 comment 를가장잘해놓았다. 다른예제는 canonical 모드예제와다른부분에만 comment 를달았다. 설명이완벽하진않지만, 이예제로직접테스트를해보면당신의프로그램에적용할때최적의방법을찾을수있을것이다. 시리얼포트장치파일의선택을제대로했는가를다시한번확인하고, 파일접근허가는제대로되어있는지보기를바란다. ( 예 : chmod a+rw /dev/ttys1) 3.1 Canonical 입력처리 (Canonical Input Processing) #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <termios.h> #include <stdio.h> /* Baudrate 설정은 <asm/termbits.h> 에정의되어있다. /* <asm/termbits.h> 는 <termios.h> 에서 include 된다. #define BAUDRATE B38400 /* 여기의포트장치파일을바꾼다. COM1="/dev/ttyS1, COM2="/dev/ttyS2 #define MODEMDEVICE "/dev/ttys1" #define _POSIX_SOURCE 1 /* POSIX 호환소스 #define FALSE 0 #define TRUE 1 volatile int STOP=FALSE; main() { int fd,c, res; struct termios oldtio,newtio; char buf[255]; /* 읽기 / 쓰기모드로모뎀장치를연다.(O_RDWR) 데이터전송시에 <CTRL>-C 문자가오면프로그램이종료되지않도록하기위해 controlling tty 가안되도록한다.(O_NOCTTY) fd = open(modemdevice, O_RDWR O_NOCTTY ); if (fd <0) {perror(modemdevice); exit(-1); tcgetattr(fd,&oldtio); /* save current serial port settings bzero(&newtio, sizeof(newtio)); /* clear struct for new port settings /* BAUDRATE: 전송속도. cfsetispeed() 및 cfsetospeed() 함수로도세팅가능 CRTSCTS : 하드웨어흐름제어. ( 시리얼케이블이모든핀에연결되어있는
경우만사용하도록한다. Serial-HOWTO의 7장을참조할것.) CS8 : 8N1 (8bit, no parity, 1 stopbit) CLOCAL : Local connection. 모뎀제어를하지않는다. CREAD : 문자수신을가능하게한다. newtio.c_cflag = BAUDRATE CRTSCTS CS8 CLOCAL CREAD; /* IGNPAR : Parity 에러가있는문자바이트를무시한다. ICRNL : CR 문자를 NL 문자로변환처리한다. ( 이설정을안하면다른컴퓨터는 CR 문자를한줄의종료문자로인식하지않을수있다.) otherwise make device raw (no other input processing) newtio.c_iflag = IGNPAR ICRNL; /* Raw output. newtio.c_oflag = 0; ICANON : canonical 입력을가능하게한다. disable all echo functionality, and don't send signals to calling program newtio.c_lflag = ICANON; /* 모든제어문자들을초기화한다. 디폴트값은 <termios.h> 헤어파일에서찾을수있다. 여기 comment에도 추가로달아놓았다. newtio.c_cc[vintr] = 0; /* Ctrl-c newtio.c_cc[vquit] = 0; /* Ctrl- newtio.c_cc[verase] = 0; /* del newtio.c_cc[vkill] = 0; /* @ newtio.c_cc[veof] = 4; /* Ctrl-d newtio.c_cc[vtime] = 0; /* inter-character timer unused newtio.c_cc[vmin] = 1; /* blocking read until 1 character arrives newtio.c_cc[vswtc] = 0; /* ' 0' newtio.c_cc[vstart] = 0; /* Ctrl-q newtio.c_cc[vstop] = 0; /* Ctrl-s newtio.c_cc[vsusp] = 0; /* Ctrl-z newtio.c_cc[veol] = 0; /* ' 0' newtio.c_cc[vreprint] = 0; /* Ctrl-r newtio.c_cc[vdiscard] = 0; /* Ctrl-u newtio.c_cc[vwerase] = 0; /* Ctrl-w newtio.c_cc[vlnext] = 0; /* Ctrl-v newtio.c_cc[veol2] = 0; /* ' 0' /* 이제 modem 라인을초기화하고포트세팅을마친다. tcflush(fd, TCIFLUSH); tcsetattr(fd,tcsanow,&newtio); /* 터미널세팅이끝났고, 이제는입력을처리한다. 이예제에서는한줄의맨첫문자를 'z' 로했을때프로그램을종료한다. while (STOP==FALSE) { /* 종료조건 (STOP==TRUE) 가될때까지루프 /* read() 는라인종료문자가나올때까지 255 문자를넘어가더라도 block 된다. read 하고자하는문자개수가입력가능한문자개수보다
적은경우에는또한번의 read 를하여나머지를읽어낼수있다. res 는 read 에의해서실제로읽혀진문자의개수를갖게된다. res = read(fd,buf,255); buf[res]=0; /* set end of string, so we can printf printf(":%s:%d n", buf, res); if (buf[0]=='z') STOP=TRUE; /* restore the old port settings tcsetattr(fd,tcsanow,&oldtio); 3.2 Non-Canonical 입력처리 (Non-Canonical Input Processing) Non-Canonical 입력처리모드에서는입력이한줄단위로처리되지않는다. erase, kill, delete 등의입력처리도수행되지않는다. 이모드에서설정하는파라미터는 c_cc[vtime] 과 c_cc[vmin] 두가지이다. c_cc[vtime] 은타이머의시간을설정하고, c_cc[vmin] 은 read 할때리턴되기위한최소의문자개수를지정한다. MIN > 0, TIME = 0 MIN 은 read 가리턴되기위한최소한의문자개수. TIME 이 0 이면타이머는사용되지않는다.( 무한대로기다린다.) MIN = 0, TIME > 0 TIME 은 time-out 값으로사용된다. Time-out 값은 TIME * 0.1 초이다. Time-out 이일어나기전에한문자라도들어오면 read 는리턴된다. MIN > 0, TIME > 0 TIME 은 time-out 이아닌 inter-character 타이머로동작한다. 최소 MIN 개의문자가들어오거나두문자사이의시간이 TIME 값을넘으면리턴된다. 문자가처음들어올때타이머는동작을시작하고이후문자가들어올때마다재시작된다. MIN = 0, TIME = 0 read 는즉시리턴된다. 현재읽을수있는문자의개수나요청한문자개수가반환된다. Antonino 씨에의하면 read 하기전에 fcntl(fd, F_SETFL, FNDELAY); 를호출하면똑같은결과를얻을수있다. newtio.c_cc[vtime] 과 newtio.c_cc[vmin] 을수정하여위네가지방식을테스트할수있다. #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <termios.h> #include <stdio.h> #define BAUDRATE B38400 #define MODEMDEVICE "/dev/ttys1" #define _POSIX_SOURCE 1 /* POSIX compliant source #define FALSE 0 #define TRUE 1
volatile int STOP=FALSE; main() { int fd,c, res; struct termios oldtio,newtio; char buf[255]; fd = open(modemdevice, O_RDWR O_NOCTTY ); if (fd <0) {perror(modemdevice); exit(-1); tcgetattr(fd,&oldtio); /* 현재설정을 oldtio 에저장 bzero(&newtio, sizeof(newtio)); newtio.c_cflag = BAUDRATE CRTSCTS CS8 CLOCAL CREAD; newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; /* set input mode (non-canonical, no echo,...) newtio.c_lflag = 0; newtio.c_cc[vtime] = 0; /* 문자사이의 timer를 disable newtio.c_cc[vmin] = 5; /* 최소 5 문자받을때까진 blocking tcflush(fd, TCIFLUSH); tcsetattr(fd,tcsanow,&newtio); while (STOP==FALSE) { /* loop for input res = read(fd,buf,255); /* 최소 5 문자를받으면리턴 buf[res]=0; /* ' 0' 종료문자열 (printf 를하기위해 ) printf(":%s:%d n", buf, res); if (buf[0]=='z') STOP=TRUE; tcsetattr(fd,tcsanow,&oldtio); 3.3 비동기입력 #include <termios.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/signal.h> #include <sys/types.h> #define BAUDRATE B38400 #define MODEMDEVICE "/dev/ttys1" #define _POSIX_SOURCE 1 /* POSIX compliant source #define FALSE 0 #define TRUE 1 volatile int STOP=FALSE; void signal_handler_io (int status); /* signal handler 함수정의 int wait_flag=true; /* signal을받지않은동안은 TRUE main() { int fd,c, res; struct termios oldtio,newtio;
struct sigaction saio; /* signal action 의정의 char buf[255]; /* Non-blocking 모드로시리얼장치를연다 (read 함수호출후즉각리턴 ) fd = open(modemdevice, O_RDWR O_NOCTTY O_NONBLOCK); if (fd <0) {perror(modemdevice); exit(-1); /* install the signal handler before making the device asynchronous /* 장치를비동기모드로만들기전에 signal handler saio.sa_handler = signal_handler_io; saio.sa_mask = 0; saio.sa_flags = 0; saio.sa_restorer = NULL; sigaction(sigio,&saio,null); /* SIGIO signal 을받을수있도록한다. fcntl(fd, F_SETOWN, getpid()); /* file descriptor 를비동기로만든다. (manual page 를보면 O_APPEND 와 O_NONBLOCK 만이 F_SETFL 에사용할수있다고되어있다.) fcntl(fd, F_SETFL, FASYNC); tcgetattr(fd,&oldtio); /* save current port settings /* canonical 입력처리를위한포트세팅 newtio.c_cflag = BAUDRATE CRTSCTS CS8 CLOCAL CREAD; newtio.c_iflag = IGNPAR ICRNL; newtio.c_oflag = 0; newtio.c_lflag = ICANON; newtio.c_cc[vmin]=1; newtio.c_cc[vtime]=0; tcflush(fd, TCIFLUSH); tcsetattr(fd,tcsanow,&newtio); /* loop while waiting for input. normally we would do something useful here while (STOP==FALSE) { printf(". n");usleep(100000); /* after receiving SIGIO, wait_flag = FALSE, input is available and can be read if (wait_flag==false) { res = read(fd,buf,255); buf[res]=0; printf(":%s:%d n", buf, res); if (res==1) STOP=TRUE; /* stop loop if only a CR was input wait_flag = TRUE; /* wait for new input /* restore old port settings tcsetattr(fd,tcsanow,&oldtio); /*************************************************************************** * signal handler. sets wait_flag to FALSE, to indicate above loop that * * characters have been received. * ************************************************************************** void signal_handler_io (int status) { printf("received SIGIO signal. n"); wait_flag = FALSE;
3.4 입력장치멀티플렉싱 이섹션은간략한설명만을하겠다. 어떻게하는지에대한간단한힌트만을주기위한것이므로짧은예제코드만을담았다. 이방법은시리얼포트에만적용되는것이아니라 file descriptor 를사용하는모든입출력에사용할수있다. select() 시스템호출함수와해당하는매크로함수들은 fd_set 을사용한다. fd_set 는 bit array 로서 file descriptor 의 bit entry 로작용한다. select() 는해당하는 file descriptor 의 bit 들을세팅한 fd_set 을입력파라미터로받아서입력, 출력혹은예외발생이일어난경우리턴한다. fd_set 은 FD_ 로시작하는매크로함수들을통해서관리한다. select(2) 의 man page 를참조하라. #include <sys/time.h> #include <sys/types.h> #include <unistd.h> main() { int fd1, fd2; /* input sources 1 and 2 fd_set readfs; /* file descriptor set int maxfd; /* maximum file desciptor used int loop=1; /* loop while TRUE /* open_input_source opens a device, sets the port correctly, and returns a file descriptor fd1 = open_input_source("/dev/ttys1"); /* COM2 if (fd1<0) exit(0); fd2 = open_input_source("/dev/ttys2"); /* COM3 if (fd2<0) exit(0); maxfd = MAX (fd1, fd2)+1; /* maximum bit entry (fd) to test /* loop for input while (loop) { FD_SET(fd1, &readfs); /* set testing for source 1 FD_SET(fd2, &readfs); /* set testing for source 2 /* block until input becomes available select(maxfd, &readfs, NULL, NULL, NULL); if (FD_ISSET(fd1, &readfs)) /* input from source 1 available handle_input_from_source1(); if (FD_ISSET(fd2, &readfs)) /* input from source 2 available handle_input_from_source2(); 위의예제코드는입력이들어올때까지계속 block 되는동작을보여준다. Time-out 이필요하다면, 다음과같이바꾼다. int res; struct timeval Timeout; /* set timeout value within input loop Timeout.tv_usec = 0; /* milliseconds Timeout.tv_sec = 1; /* seconds res = select(maxfd, &readfs, NULL, NULL, &Timeout); if (res==0) /* number of file descriptors with input = 0, timeout occurred.
이예제는 1 초후에 time-out 이되는것을보여준다. Time-out 이일어나면 select() 는 0 을반환한다. 여기서주의할점은, 설정한 Timeout 값은 select() 에의해서감소하기때문에다시 select() 를호출한다면 Timeout.tv_usec 과 Timeout.tv_sec 값을다시설정해야한다. Timeout 값이 0 이되면 time-out 이발생하고 select() 는즉시리턴된다.
4. 다른유용한정보 Serial-HOWTO 문서는시리얼포트를어떻게셋업하는지를설명하고하드웨어정보를제공한다. Michael Sweet 의 Serial Programming Guide for POSIX Compliant Operating Systems. 이링크는옛날것이고최신위치를찾을수가없다. 이거찾아줄분누구없수? 이문서는매우잘정리된것이다. ( 역자주 : 다시이위치가부활했다. 예전에잠시폐쇄된적이있었다.) termios(3) man page 는 termios 구조체의모든 flag 에대해정의되어있다.
5. 기여한사람들 1 장의소개부분에서언급했듯이, 나는이분야의전문가가아니다. 그러나여러문제에부딪혔었고, 다른이들의도움을받아서문제들을해결했다. Strudthoff 씨, Michael Carter (mcarter@rocke.electro.swri.edu) 씨및 Peter Walternberg(p.waltenberg@karaka.chch.cri.nz) 씨의도움에감사한다. Antonino Ianella(antonino@usa.net) 씨는내가이문서를작성할때 Serial-Port- Programming Mini HOWTO 를썼다. Greg Hankins 씨는 Antonino 의 Mini-HOWTO 를이문서에넣으라고권했다. 이문서의구조와 SGML 포맷은 Greg Hankins 씨의 Serial-HOWTO 에서따왔다. 이글에서오류를수정할수있도록도와주신많은분들이있다. Dave Pfaltzgraff (Dave_Pfaltzgraff@patapsco.com), Sean Lincolne(slincol@tpgi.com.au), Michael Wiedmann (mw@miwie.in-berlin.de), Adrey Bonar(andy@tipas.lt) 씨에게감사한다.