2010년 2월 18일 목요일

2. QT 설치

(1) QT 개발환경(설치 목록)
  • QT/X11
    • x86 qt library
    • uic(User Interface Compiler for the Qt GUI toolkit) - UI 디자이너로 만든것 변환.
    • qvfb(qt virtual frame buffer) - 일종의 에뮬레이터
  • tslib
    • touch screen library
  • QT/E for x86
    • qt embedded library for x86
  • QT/E for arm
    • qt embedded library for arm


(2) QT 설치과정
  1. 임베디드 보드 툴체인 설치(크로스 컴파일 환경 구축)
  2. Xlib 개발 패키지 설치
  3. QT/X11 설치
  4. tslib 설치
  5. QT/Embedded for x86 설치
  6. QT/Embedded for arm 설치
  7. QT 동작 확인


(3) QT 설치
    1. 임베디드 보드 툴체인 설치(크로스 컴파일 환경 구축)
  • (이전에 이미 환경 구축함.)

    2. Xlib 개발 패키지 설치
  • 헤더파일 존재 여부 파악 : #ls /usr/include/X11
    
    3. QT/X11 설치
  • 다운로드 주소 : ftp://ftp.trolltech.com/qt/source/qt-x11-opensource-src-4.3.3.tar.gz
  • .bash_profile 파일 수정(path 추가)
  • qvfb 빌드

    4. tslib 설치
  • 다운로드 주소 : http://tslib.berlios.de/ , tslib-1[1].0.tar.bz2
  • config.h 파일 수정
  • input-raw.c 파일 수정
  • tslib 환경 파일 작성
  • ts.conf 파일 수정
  • tslib.env 파일 작성
  • ts device 설정
  • ts device 동작 확인
  • tslib 동작 확인

    5. QT/Embedded for x86 설치
  • 다운로드 주소 : ftp://ftp.trolltech.com/qt/source/ , qtopia-core-opensource-src-4.3.3.tar.gz
  • qte_x86_env 설정 파일 작성
  • qvfb 동작 확인으로 설치 확인

    6. QT/Embedded for arm 설치
  • 다운로드 주소 : ftp://ftp.trolltech.com/qt/source , qtopia-core-opensource-src-4.3.3.tar.gz
  • qte_arm_env 설정 파일 작성
  • tslib와 QT/E for arm 동작 확인
  • NFS를 이용한 라이브러리 설정
  • NFS를 이용한 tslib 환경 설정
  • NFS를 이용한 qt4 환경 설정 및 동작 확인
  • qt4_env 설정 파일 작성

    7. QT 동작 확인
  • tslib와 QT/E for arm 동작 확인
  • NFS를 이용한 qt4 환경 설정 및 동작 확인
  • qt4_env 설정 파일 작성

* 주의 사항
  • QT 응용 프로그램 작성 후 컴파일 시, 프로그램의 타겟 플랫폼에 따라 환경 설정을 바꿔줘야 함.
    • X11 : X11 용으로 컴파일한 qt library 패스 설정
    • x86 / qvfb : qvfb 용으로 컴파일한 qt embedded library 패스 설정
    • arm / FB : arm 용으로 컴파일한 qt embedded library 패스 설정

2010년 2월 17일 수요일

1.QT 소개

(1) QT의 특징
  • 멀티 플랫폼 C++ GUI Toolkit
  • C++ 기반의 객체지향 클래스 라이브러리(캡슐화, 상속, 다형성-오버로딩, 오버라이딩)
  • Signal / Slot 매커니즘(이벤트와 핸들러라고 생각하자.)

* 오버로딩 : 함수 이름은 같고, 함수 인자의 갯수를 다르게 정의.함
* 오버라이딩 : 함수를 상속받아 구현부만 다르게 정의함.
* Signal/Slot : 객체(Object)의 신호(Signal)를 다른 객체(Object) 슬롯(Slot)에 연결.

(2) QT 관련 사이트 : www.trolltech.com

* widget : 다이얼로그 or 클래스 같은 것.

* QT 라이센스 있음( 교육용은 무상 )

(3) QT 종류
  • QT/X11 : X11 환경
  • QT/Embedded : 임베디드 리눅스 환경
  • QT/Windows : MS-Windows 환경(여기선 Visual Studio가 강력함)

* Qtopia : QT/E에서 어플리케이션의 모음 같은 것.


2010년 2월 16일 화요일

Blob 분석

BLOB : Boot Loader OBject

 

(1) 소개

  • 네덜란드에서 개발한 Open Source의 Boot Loader 프로그램으로 Intel SA-1100 Micro-Processor를 사용한 LART(Linux Advanced Radio Terminal)라는 내장형 컴퓨터에 처음 사용됨.
  • blob-2.0.5-pre3 버전 소스 분석
  • ARM 기반에서 사용하는 대표적인 Boot Loader

    1.BLOB 기능

  • 하드웨어 초기화
  • 레지스터 설정을 통한 시스템의 활성화
  • 저장 매체로부터 Kernel을 읽어서 메모리의 적절한 위치로 적재
  • 압축을 풀고 Kernel로 제어를 옮김
  • 그 외 serial이나 ethernet 장치를 통해 Kernel이나 Ramdisk 등의 image를 다운로드

    2.BLOB 흐름도

 

     (1) Start stage : 하드웨어의 기본적인 초기화 및 레지스터 설정을 담당

     (2) Rest stage : Boot Loader의 부가적인 기능을 제공

 

 

 

    3. 단계별 분석

      3.1 Start Stage

      3.1.1 Boot Loader 시작

      3.1.2 start.S

      3.1.3 rest

      3.1.3.1 Disable All Interrupt

      3.1.3.2 CPU Speed Setup

      3.1.3.3 Memory Setup

      3.1.3.3.1 DRAM Configuration Register

      3.1.3.3.2 DRAM CAS Waveform Shift Register

      3.1.3.3.3 DRAM refresh Controller Register

      3.1.3.3.4 Enable SDRAM BANKS

      3.1.3.3.5 Static Memory Register Configuration

      3.1.3.3.6 SMROM Register Configuration

 

      3.1.3.4 LED Conguration( GPIO )

 

kkkkk

kkkk

 

2010년 2월 9일 화요일

하드웨어 통신 제어 및 인터럽트 처리

(1) 예외처리
  • 외부의 정상적인 요청이나 비정상적인 오류의 발생에 의해서 프로그램을 중지하고 발생된 요청이나 오류를 처리하는 것
  • 예외처리가 발생하면 처리해야 할 프로그램의 위치가 메모리의 특정한 위치에 지정
  • 예외 벡터 테이블


(2) 인터럽트
  • 디바이스는 프로세서와는 다른 시간 간격, 정확하게는 훨씬 느린 시간으로 수행됨.
    • 프로세서가 무작정 외부 이벤트를 기다리는 것은 바람직하지 못하므로 프로세서에게 뭔가 발생했다고 알릴 방법의 필요


(3) 인터럽트 처리
  • 인터럽트 핸들러(인터럽트 서비스 루티 - ISR)
    • 인터럽트를 처리하는 커널 함수
    • 디바이스 드라이버의 일부분
    • 인터럽트 컨텍스트에서 실행
  • 인터럽트 처리를 두 단계로 분리
    • Top half
      • 인터럽트 핸들러
      • 인터럽트가 발생하는 즉시 실행되는 타임 크리티컬한 작업들
    • Bottom half
      • 나중에 해도 무관한 작업들
      • 모든 인터럽트를 활성화시킨 상태에서 실행
        • (보통 인터럽트 핸들러가 리턴되면 실행)

(4) IRQ 인터럽트 서비스 처리 흐름
  • 처리 흐름
    • 프로세서마다 인터럽트 처리 방식이 다름
      • 발생된 IRQ 번호를 획득하여 do_IRQ() 함수 호출
    • 리눅스 커널에서는 동일한 처리를 위해 IRQ 인터럽트는 모두 do_IRQ() 함수를 호출하여 처리
  • request_irq() 함수를 이용하여 처리하고자 하는 IRQ 번호와 서비스 함수 주소를 등록
    • Ex) linux/include/asm-arm/arch-pxa/irqs.h
  • 인터럽트 발생 후
    • 아키텍처마다 고유의 IRQ 검출 루틴을 이용
    • do_IRQ() 함수는 irq_desc 전역변수에서 IRQ 번호에 맞게 등록된 인터럽트 서비스 함수를 찾고, 있으면 호출
    • 참조된 데이터의 주소나 값들은 인터럽트 서비스 함수의 매개변수인 dev_id에 전달
    • 인터럽트 처리가 필요없는 경우에는 free_irq() 함수 호출
      • irq_desc 전역변수에 등록된 인터럽트 서비스 함수를 제거


(5) 인터럽트 서비스 함수의 형식
  • irqreturn_t int_interrupt(int irq, void *dev_id, struct pt_regs *regs)
  • 커널 2.4 - 반환 값은 void
  • 커널 2.6 - 반환 값은 IRQ_HANDLED
  • 매개변수
    • int irq - 인터럽트 번호(irqs.h에 정의)
    • void *dev_id - 인터럽트 서비스 함수를 수행할 때 필요한 정보가 저장된 메모리 주소
    • struct pt_regs *regs - 인터럽트가 발생했을 당시의 레지스터 값(디버깅용)

(6) 인터럽트 서비스 함수 내의 메모리 할당
  • 주로 사용하는 메모리 할당 및 해제
    • kmalloc()
      • GFP_ATOMIC 인자를 사용한 방식만을 사용
      • vmalloc() 함수로 인터럽트가 발생하기 전에 미리 할당한 메모리는 아무런 제약 사항이 없음.
      • kmalloc 할당 후 리턴값 확인 필수

irqreturn_t int_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
char *data;
...
data = kmalloc(128, GFP_ATOMIC);
if( data != NULL ){
...
kfree(data);
}
...
return IRQ_HANDLED;
}


(7)  인터럽트 서비스 함수 등록
  • #include <linux/interrupt.h>를 포함
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long flags, const char *device, void *dev_id);

  • 매개변수
    • irq - 인터럽트 번호
    • (*handler) - 해당 인터럽트 번호를 위한 인터럽트 서비스 함수 포인터
    • flags - 인터럽트 공유나 빠른 인터럽트 처리 등의 속성 지정
      • SA_INTERRUPT - 다른 인터럽트를 허용하지 않음
      • SA_SHIRQ - 동일한 인터럽트 번호를 공유
      • SA_SAMPLE_RANDOM - 랜덤 값 처리에 영향을 줌
    • device - 디바이스 이름(/proc/interrupt 에 쓰임)
    • dev_id - 인터럽트 공유시 구분인자, handler 함수가 참조하는 데이터의 주소 지정
  • request_irq() 함수
    • 정상적 수행시 0 리턴
    • 비정상적 수행시 음수 값 리턴
    • 인터럽트 함수를 등록하는 예
irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
return IRQ_HANDLED;
}

int xxx_interrupt(struct inode *inode, struct file *filp)
{
if( ! request_irq(XXX_IRQ, xxx_ineterrupt, SA_INETRRUPT, "xxx", NULL) )
{
// 정상 등록 후 처리
}

return 0;
}


(8) 인터럽트 서비스 함수 해제
  • free_irq() 함수
    • 커널 2.4와 2.6 사용 동일
    • 등록되어 있던 인터럽트 서비스 함수를 제거
void free_irq(unsigned int irq, void *dev_id);

    • 인터럽트 서비스 함수를 제거하는 예
int xxx_release(struct inode *inode, struct file *filp)
{
// 하드웨어 인터럽트 금지 처리 루틴
...
free_irq(XXX_IRQ, NULL);
return 0;
}



(7) 인터럽트 서비스 등록과 해제 시점
  • 하나의 응용 프로그램만이 디바이스 파일을 사용하는 경우
    • 모듈 초기화, open() 함수 모두 ISR 등록 가능
  • 두 개 이상의 응용 프로그램이 디바이스 파일을 사용하는 경우
    • 모듈 초기화에서 ISR 등록을 주로 함
    • 이유
      • open()에서 등록할 경우 동일한 인터럽트 서비스 루틴이 두 번 등록 -> 구현이 복잡해지며, 에러 처리 고려
      • open()에서 등록할 경우 먼저 해제를 시도한 쪽에서 인터럽트 서비스 함수를 제거하는 경우 다른 쪽 응용 프로그램에서 사용하는 디바이스 드라이버는 인터럽트 서비스 함수가 동작하지 않음.
  • 결론
    • PC 같은 범용 시스템에 사용되는 디바이스 드라이버
      • open()과 close()에서 처리하는 것이 효율적
    • Embedded System에 사용되는 디바이스 드라이버
      • 모듈 초기화 및 모듈 해제 시점에서 처리하는 것이 효율적


(8) 인터럽트 함수와 디바이스 드라이버 간 데이터 공유
  • 인터럽트 서비스 함수는 프로세스 문맥과 별개로 동작하므로 인터럽트 서비스 함수와 디바이스 드라이버 함수 간에 데이터를 공유하는 방법이 필요함.
  • 공유 방법
    • 전역 변수
      • 인터럽트 하나에 인터럽트 서비스 함수 하나가 동작하는 경우
      • 동일한 인터럽트 서비스 함수로 여러 디바이스를 제어할 경우엔 사용 못함.
    • devi_id 매개 변수
      • 여러 디바이스를 하나의 인터럽트 서비스 함수에서 사용 가능
      • 다중 프로세스 환경에 매우 적합함.
/* dev_id를 이용한 데이터 공유 */
typedef struct {
unsigned long ioaddress;
unsigned long count;
} __attribute__((packed)) R_INT_INFO;

irqreturn_t count_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
R_INT_INFO *ptrInfo;
ptrInfo = (R_INT_INFO *)dev_id;
printk("INT READ I/O %02X\n", inb(ptrInfo->ioaddress));
ptrInfo->count++;

return IRQ_HANDLED;
}

int int_open(struct inode *inode, struct file *filp)
{
R_INT_INFO *ptrInfo;

ptrInfo = kmalloc(sizeof(R_INT_INFO), GFP_KERENL);
filp->private_data = ptrInfo;
switch( MINOR(inode->i_rdev) ){
case 0 : ptrInfo->ioaddress = 0x378; break;
case 1 : ptrInfo->ioaddress = 0x278; break;
}

if( ! request_irq(XXX_IRQ, count_interrupt, SA_INTERRUPT, "test", ptrInfo) ){
// 커널 함수가 아니고 임의의 인터럽트 제어 함수
enable_hardware_init(ptrInfo);
}

return 0;
}

int int_release(struct inode *inode, struct file *filp){
R_INT_INFO *ptrInfo = (R_INT_INFO)filp->private_data;

disable_hardware_int(ptrInfo);
free_irq(XXX_IRQ, ptrInfo);
kfree(ptrInfo);
return 0;
}


(9) 인터럽트 공유
  • 같은 인터럽트 번호에 다른 인터럽트 서비스 함수 등록 가능
    • request_irq() 함수의 매개변수에서 flags를 SA_SHIRQ 포함
    • dev_id : 0이 아닌 값 사용
  • 같은 인터럽트 번호에 대해 여러 인터럽트 서비스 함수가 동작할 경우
    • 인터럽트 서비스 함수는 자신이 관리하는 디바이스에서 인터럽트가 발생했는지를 확인하는 과정이 필요
    • 커널 2.6에서는 위의 오류 해결을 위해 인터럽트 서비스 함수의 반환 값을 확인하는 과정을 거침
      • 오류가 발생하면 해당 인터럽트를 금지, IRQ_NONE을 리턴
irqreturn_t xxx_inerrupt(int irq, void *dev_id, struct pt_regs *regs)
{
인터럽트_관리_구조체 * ptrMng = (인터럽트_관리_구조체 *)dev_id;
if( 자신의 인터럽트 처리 함수(ptrMng) )
{
// 인터럽트 처리
...
return IRQ_HANDLED;
}

return IRQ_NONE;
}


(10) 인터럽트 금지와 해제
  • 인터럽트 서비스 함수가 동작 중에 다른 인터럽트가 발생하지 못하게 막는 경우
    • request_irq() 함수의 flags를 SA_INTERRUPT를 사용함.
      • 커널이 프로세서의 인터럽트를 금지 시킨 후 호출
      • 인터럽트 두 개가 동시에 발생하면 SA_INTERRUPT가 설정된 함수 먼저 수행
  • 일반적인 함수 수행 중에 데이터 처리를 보호하기 위해 인터럽트를 강제로 막는 경우
    • #include <asm/irq.h> 포함
    • void disable_irq(int irq) : 인터럽트l 금지
    • void enable-irq(int irq) : 인터럽트 허가
    • void disable_irq_nosync(int irq) : 현재 핸들러가 종료할 때까지 기다리지 않음.
    • synchronize_irq(unsigned int irq) : 특정 인터럽트 핸들러가 실행 중일 경우 기다렸다가 실행이 끝나면 리턴
  • 프로세서 전체의 인터럽트를 금지하고 해제할 경우
    • #include <asm/system.h> 포함
    • local_irq_disable(void) : 프로세서의 인터럽트 처리를 금지
    • local_irq_enable(void) : 프로세서의 인터럽트 처리를 허가
    • local_save_flags(unsigned long flags) : 현재의 프로세스 상태 저장
    • local_restore_flags(unsigned long flags) : 저장된 프로세스 상태 복구
  • 보편적인 인터럽트 방지법
unsigned long flags;

local_save_flags(flags);
local_irq_disable();

// 인터럽트 호출에서 보호하고자 하는 루틴

local_restore_flags(flags);
  • local_save_flags() 함수에 의해 저장된 flags 변수에 이미 인터럽트 허가 상태가 포함되어 있기 때문에 local_resstore_flags() 함수에 의해 local_irq_enable() 호출 효과 발생.


(11) 인터럽트 시스템의 현재 상태 체크
  • irqs_disabled()
    • 로컬 프로세서의 인터럽트가 비활성화된 경우 0이 아닌 값을, 활성화된 경우 0을 리턴.
    • #include <asm/system.h> 포함
  • in_interrupt()
    • 커널이 현재 인터럽트 컨텍스트에 있는 경우 0이 아닌 값을 리턴
    • 커널이 인터럽트 핸들러 혹은 Bottom half 핸들러를 실행중인 상태를 포함
    • #include <asm/hardirq.h> 포함
  • in_irq()
    • 커널이 인터럽트 핸들러를 실행 중일 때만 0이 아닌 값을 리턴
  • 프로세스 컨텍스트인가를 체크해야 할 경우
    • 휴면과 같은 프로세스 컨텍스트에서만 가능한 작업을 수행하기 위해
    • #include <asm/hardirq.h> 포함

디바이스 드라이버의 소개

(1) 장치 파일

 

  • 디바이스 드라이버를 접근하는 통로
  • 장치 파일의 inode는 장치 유형(type), 주번호, 부번호로 구성됨.
  • 장치 파일 생성

    • # mknod /dev/mydev [b|c] 240 0

 

 

(2) 디바이스 드라이버의 구성

  1. 초기화 인터페이스 - init_module() 함수
    • register_chrdev()
    • register_blkdev()
    • request_irq()
    • ...
  2. 시스템 인터페이스 - 문자/블록의 경우 file_operations 이용
    • 문자 드라이버 - open, release, read, write, ioctl
    • 블록 드라이버 - open, release, request, ioctl
    • 네트워크 드라이버 - open, close, transmit, receive, ioctl
  3. 하드웨어 인터페이스
    • in(), out()

 

 

(3) 새로운 디바이스 드라이버 작성 단계

 

  1. 디바이스 드라이버 커널 인터페이스 구현
    • file_operations
  2. 디바이스 드라이버 초기화 인터페이스 구현
    • 주 번호 할당 및 디바이스 드라이버 루틴 등록
  3. 디바이스 하드웨어 인터페이스 구현
    • 레지스터 접근
  4. 장치 파일 생성
    • #mknod /dev/mydev [b|c] major_number minor_number
  5. 응용 프로그램 작성
  6. 커널 컴파일 및 리부팅

 

 

(4) 자원 처리 요청 방법

  • 커널에 자원 처리를 요청하는 방법
    • 응용 프로그램이 커널에게 자원 처리를 요청하는 방법
      • 시스템 호출
      • 파일 입출력 형식을 이용한 디바이스 드라이버 사용

 

  • 시스템 호출 방법
    • Software Interrupt를 이용, 응용 프로그램에서 요청하는 처리를 커널이 수행

 

  •  파일 형식의 디바이스 드라이버
    • 일반 파일을 제어하듯이 디바이스 파일을 통해 하드웨어 입출력 시도
    • 커널 내의 해당 디바이스 파일에 연결된 디바이스 드라이버의 서비스 루틴이 호출되어 디바이스에 대한 처리
    • 모든 처리가 끝나면 커널이 제어 흐름을 다시 응용 프로그램에 넘기는 구조

 

 

(5) 모듈

  • 모듈은 커널이 부팅되어 동작 중인 상태에서 디바이스 드라이버를 동적으로 추가하거나 제거할 수 있게 하는 개념
    • 개발 기간 단축
    • PCI, USB, PCMICIA에 연계된 디바이스의 PNP 기능을 지원하기 위해 모듈 방식이 필수
      • 모듈 방식은 MMU가 있는 CPU에서만 지원
      • 동일한 커널 버전 사용 요구

 

 

(6) 디바이스 파일

  • 리눅스는 모든 하드웨어를 파일로 추상화
    • /dev/ : 디렉토리에 있는 파일들로 실제 하드웨어를 표현
  • 시스템 또는 하드웨어 정보 제공
    • 디바이스 타입정보(문자, 블록), 주번호, 부번호

(7) 디바이스 드라이버의 종류
  1. 문자 디바이스 드라이버 - 버퍼 없음
  2. 블록 디바이스 드라이버 - 버퍼 있음
  3. 네트워크 디바이스 드라이버
    • 커널 내부에 있는 네트워크 프로토콜 스택과 연동


(8) 디바이스 드라이버 계층
  • 시리얼, tty
  • 사운드
  • USB
  • Frame Buffer
  • IDE
  • SCSI
  • PCI


* 원하는 디바이스 드라이버 소스 검색 방법
  • 디바이스 모델명을 찾는다.
    • PCI : lspci
    • USb : lsusb
  • drivers 디렉토리 검색
    • ex) # grep cmd680 * -r
  • Documentation/Configure.help 탐색
    • CONFIG_XXX 변수가 무엇인지 확인
    • 해당 옵션을 사용하는 Makefile 검색

리눅스 디바이스 드라이버 개요

(1) 리눅스 특징

 

  • 모놀리틱 커널 구조(monolithic) - 다양한 컴포넌트로 구성된 거대하고 복잡한 프로그램
  • 모듈 지원 - 동적 로딩 및 제거 가능(디바이스 드라이버)
  • 커널 스레드 제공
  • 다중 프로세서 지원  - SMP(Symmetric Multiprocessing) 지원

 

 

(2) 리눅스의 장점

 

  • 목적에 맞게 컴포넌트 커스터마이징 가능
  • 저가의 하드웨어 플랫폼에서 수행
  • 다른 운영체제와 호환성이 뛰어남 - POSIX 준수

 

 

(3) 리눅스의 단점

 

  • 책임자가 없음 - 개발 및 사후 관리가 어려움

 

 

(4) 임베디드 리눅스를 선택하는 이유

 

  • 코드 품질과 신뢰성
  • 모듈성과 구조, 수정 편의성, 확장성
  • 코드 가용성
  • 하드웨어 지원
  • 통신 프로토콜과 소프트웨어 표준
  • 사용할 수 있는 툴
  • 커뮤니티 지원
  • 가격

 

 

(5) 임베디드 리눅스 시스템 구성

 

  1. 부트 소프트웨어를 포팅하고 설정
  2. 시스템 컴포넌트를 결정
  3. 커널을 설정하고 빌드
  4. 루트 파일시스템을 빌드

 

 

(6) 임베디드 리눅스 시스템 구축 시 고려사항

 

  • 부트로더의 선택은 어떤 것으로 할 것인가?
  • 최신 커널을 사용할 것인가? 충분히 검증된 커널 사용
  • 루트 파일 시스템에 포함할 것은 무엇인가?
    • 파일 시스템의 크기 고려
    • 전체 시스템 가동에 필요한 최소한의 파일만 포함

 

RTOS 소개

(1) Real Time System

  • 불시에 발생하는 이벤트에 대하여 정해진 시간 내에 정확한 결과를 제공
  • (응답시간이 예측가능하고 일정해야 한다.)
  • 시간에 적시성(timeliness)을 가지며 예측 가능한(predictable) 시스템
  • Deadline 이내에 결과값 산출
  • Deadline 넘으면 심각한 결과 발생

 

(2)

 

(3)