Showing posts with label Embedded Linux. Show all posts
Showing posts with label Embedded Linux. Show all posts

Friday, April 24, 2015

Practice Example in Embedded Linux

@ Problem
 : 'test' 라는 application program을 작성해보자. test는 자신의 process id를 Device Driver를 이용하여 kernel로 전달할 수 있다. Kernel 에서는 process id를 받으면 Kernel Timer를 동작시켜 주기적으로 test 프로세스에게 Signal(SIGUSR1)을 전달하고, Signal을 받으면 Kernel에서 전달한 int형 data를 출력해보자.

@ Answer
  1) PID 전달
    ① character device driver 이용
    ② copy_from_user를 통해 User -> Kernel 영역으로 data 복사

  2) 주기적인 Signal 전달
    ① Kernel Timer 이용
    ② Timer Handler에서 kill_proc 함수를 통해 Kernel -> User 특정 프로세스로 signal 전달
    ③ Timer 대신 Key를 이용하는 경우 Interrupt Handler에서 kill_proc 함수를 사용하면 됨

  3) Signal 처리
    ① 전달받은 signal을 처리할 handler 등록
    ② Signal Handler에서 원하는 동작을 수행

@ Summary

Thursday, April 23, 2015

Using Signal in Embedded Linux

@ Send Signals
  : kill() 함수를 이용하여 특정 프로세스에게 원하는 신호를 전달
  : kill_proc() 함수를 이용하면 Kernel -> User 특정 프로세스로 신호 전달도 가능

@ Handle Signal
  : signal() 함수를 이용하여 특정 신호에 대하여 처리할 핸들러를 등록

@ Example
# ./signal &
# ./kill [signal_process_id]
Invoked signal_handler...

/*
 * kill.c
 */
#include <sys/types.h>
#include <signal.h>

main(int argc, char** argv)
{
kill(atoi(argv[1]), SIGINT);
}

/*
 * signal.c
 */
#include <signal.h>

void signal_handler(int signum)
{
printf("Invoked signal_handler... \n");
}

main()
{
signal(SIGINT, signal_handler);

while(1);
}

User & Kernel Memory Area in Embedded Linux

@ Memory Copy Interfaces from Kernel to User
  : copy_to_user()
  : copy_from_user()
  : put_user()
  : get_user()

@ 차이점
  : 일반적으로 아래와 같이 사용
  -. 4 byte 이하 작은 데이터를 다룰 경우 : put_user, get_user
  -. 4 byte 이상 큰 데이터를 다룰 경우 : copy_to_user, copy_from_user

@ 참고사항
  : copy_to_user, copy_from_user 함수를 사용하지 않아도
  : parameter로 넘어온 buf 포인터를 통해 sprintf 라든지 = 대입이라든지 가능함
  : 하지만 user <-> kernel 간 메모리 복사를 안전하게 하려면
  : 검증된 방법(인터페이스)를 이용하여 구현하는 것을 권장


@ Example
/*
 * @file   test_mydrv.c
 * @brief  Application Program for copy_to_user, copy_from_user
 */

typedef struct
{
    int age;
    char name[30];
    char address[20];
    int phone_number;
    char depart[20];
} __attribute__ ((packed)) mydrv_data;


int main()
{
  mydrv_data data;

  int fd = open("/dev/mydrv",O_RDWR);

  data.age = 29;
  strcpy(data.name, "seungin.cha");
  strcpy(data.address, "hwagok-dong");
  data.phone_number = 49104714;
  strcpy(data.depart, "H&A");

  // Send data from user to kernel (and then, Print data by Kernel)
  write(fd, &data, sizeof(data));

  // Receive data from kernel to user
  read(fd, &data, sizeof(mydrv_data));

  // Print data by User
  printf("\n");
  printf("u_age    : %d\n", data.age);
  printf("u_name   : %s\n", data.name);
  printf("u_address: %s\n", data.address);
  printf("u_phoneno: %d\n", data.phone_number);
  printf("u_depart : %s\n", data.depart);
  printf("\n");

  close(fd);
  return 0;
}

/*
 * @file   mydrv.c
 * @brief  Module Program for copy_to_user, copy_from_user
 */

typedef struct
{
    int age;
    char name[30];
    char address[20];
    int phone_number;
    char depart[20];
} __attribute__ ((packed)) mydrv_data;

...

static ssize_t mydrv_write(struct file *filp,const char __user *buf, size_t count, loff_t *f_pos)
{
    mydrv_data* k_buf, u_buf;

    k_buf = kmalloc(sizeof(mydrv_data), GFP_KERNEL);
    if (copy_from_user(k_buf, buf, sizeof(mydrv_data)))
    {
        return -EFAULT;
    }

// copy_from_user 함수를 이용하지 않아도 복사가 가능하나 권장하지 않음
//  u_buf = (mydrv_data*) buf;
//  k_buf->age = u_buf->age;
//  sprintf(k_buf->name, u_buf->name);
//  sprintf(k_buf->address, u_buf->address);
//  k_buf->phone_number = u_buf->phone_number;
//  sprintf(k_buf->depart, u_buf->depart);

    printk("\n");
    printk("k_age    : %d\n", k_buf->age);
    printk("k_name   : %s\n", k_buf->name);
    printk("k_address: %s\n", k_buf->address);
    printk("k_phoneno: %d\n", k_buf->phone_number);
    printk("k_depart : %s\n", k_buf->depart);
    printk("\n");

    kfree(k_buf);
    return 0;
}

static ssize_t mydrv_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    mydrv_data k_struct;
    k_struct.age = 31;
    sprintf(&k_struct.name, "seungkil.cha");
    sprintf(&k_struct.address, "bueon");
    k_struct.phone_number = 26401717;
    sprintf(&k_struct.depart, "shinjung");

    if (copy_to_user(buf, &k_struct, sizeof(mydrv_data)))
    {
        return -EFAULT;
    }

    return 0;
}

Using Kernel Timer in Embedded Linux

@ Kernel Timer
  ① struct timer_list 구조체 선언
  ② 맨 처음 timer를 초기화 : init_timer() 호출
  ③ timer 만료시간을 설정 : expires 설정 - jiffies=현재시각,  TIMER_INTERVAL=주기
  ④ 설정된 정보로 timer 등록 : add_timer() 호출 시 "timer start"
  ⑤ 수행 중인 timer 종료 : del_timer() 호출 시 "timer stop"

/*
 * @file   timer.c
 * @brief  Module Program for Kernel Timer
 */

#define TIMER_INTERVAL 20

static struct timer_list tm;

void timer_handler(int data)
{
    printk("Timer Expired!\n");

    tm.expires = jiffies + TIMER_INTERVAL;
    add_timer(&tm);
}

... timer_open( ... )
{
    init_timer(&tm);
    tm.expires = jiffies + TIMER_INTERVAL;
    tm.function = &timer_handler;
    add_timer(&tm);

    ...
}

... timer_close( ... )
{
   del_timer(&tm);
}

MODULE_LICENSE("GPL");


Using Interrupt in Embedded Linux

@ Button Key
  : Target Board 버튼 Press 시 Interrupt 발생
  : 발생한 Interrupt를 어떻게 처리하는지?

@ Interrupt in Arm Processor
  : Arm 프로세서에서는 Interrupt가 발생하면 IRQ Exception이 발생하는데,
  : 프로세서 내에 Exception Vector Table 이라는 것이 존재해서,
  : 발생한 Exception 타입에 따라 Table의 특정 주소에 위치한 명령을 수행하게 됨

@ Interrupt Handling
request_irq(IRQ_EINT0, (void*) key1_isr, IRQF_DISABLED | IRQF_TRIGGER_FALLING, "KEY1", NULL);
  : "request_irq()" function이 가장 중요!
  : request_irq 함수를 이용하여 해당 interrupt에 ISR(Interrupt Service Routine)을 등록
  : IRQ_EINT0 의 경우는 Target Board(HW)에서 정의된 값을 사용해야 함

free_irq(IRQ_EINT0, NULL);
  : Interrupt 사용 후에는 반드시 "free_irq()" function을 호출해 주는 게 정석

/*
 * @file   keybutton.c
 * @brief  Module Source for button key interrupt
 */

static irqreturn_t key1_isr(int irq, void* dev_id, struct pt_regs* regs)
{
    printk("KEY1 pressed.. irq_no( %d )\n", irq);
    return IRQ_HANDLED;
}

static int __init button_probe(struct device* dev)
{
    request_irq(IRQ_EINT0, (void*) key1_isr, IRQF_DISABLED | IRQF_TRIGGER_FALLING, "KEY1", NULL);
    return 0;
}

static int button_remove(struct device* dev)
{
    free_irq(IRQ_EINT0, NULL);
}

static struct device_driver button_driver = {
    .name    = "button",
    .owner   = THIS_MODULE,
    .bus     = &platform_bus_type,
    .probe   = button_probe,
    .remove  = button_remove,
}

...

MODULE_LICENSE("GPL");

Modify Ramdisk

@ 파일시스템 수정하기
# mkdir ram_tmp
# gzip -d ramdisk.gz
# mount -o loop ramdisk ram_tmp

# cd ram_tmp/root
# touch a.txt
# cd ../..

# umount ram_tmp
# gzip ramdisk

# cp ramdisk.gz /tftpboot

Wednesday, April 22, 2015

Linux Character Device Driver

@ Device Driver 동작 원리
  (1) Insert Module to Kernel
    ① $ insmod led.ko 입력 시,
    ② module_init() -> led_init() 호출
    ③ register_chrdev() 호출
      -. LED_MAJOR : LED 장치 주 번호
      -. LED_DEVNAME : LED 특수장치파일 이름, /dev/led 파일로 접근
      -. &led_fops : user 인터페이스와 kernel 루틴을 연결
    ④ LED_DEVNAME 파일을 제어하면 Kernel을 제어할 수 있게 됨

  (2) Create LED character device file
    ① $ mknod /dev/led c 240 0
    ② $ ll /dev/led
        crw-r--r--  1 root root    240,   0 2015-04-23 11:04 led

  (3) Using Device Driver in Application Program
    ① 이제 Application Layer에서 open을 호출하면 led_open 함수가 호출됨

/*
 * @file   test.c
 * @brief  Application Program for Led Control
 */

int main()
{
  int fd;
  char buf[8];
  
  fd = open("/dev/led", O_RDWR);
  read(fd , buf, MAX_BUFFER);
  write(fd, buf, MAX_BUFFER);
  close(fd);
  
  return 0;
}

/*
 * @file   led.c
 * @brief  Kernel Module Program Source, "led.ko" is created with led.c by make utility
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>

#define LED_MAJOR     240
#define LED_DEVNAME  "led"

...

static struct file_operations led_fops = {
    .open    = led_open,
    .close   = led_close,
    .read    = led_read,
    .write   = led_write,
    .release = led_release,
}

static int led_init(void)
{
    // 주 번호가 240인 "/dev/led" 파일을 이용하여 High-Level 인터페이스를 사용하면,
    // Kernel-Level에 연결된 함수가 호출될 수 있도록 Device 등록
    register_chrdev(LED_MAJOR, LED_DEVNAME, &led_fops);
    return 0;
}

static void led_exit(void)
{
    unregister_chrdev(LED_MAJOR, LED_DEVNAME);
}
module_init(led_init); // to be called when "insmod led.ko" command execute
module_exit(led_exit); // to be called when "rmmod led.ko" command execute

MODULE_LICENSE("GPL");

Linux Low-Level File I/O (저수준 파일 입출력)

Tuesday, April 21, 2015

Module Programming

@ Module Programming
  : Module은 "Kernel 계층에서 실행되는 작은 프로그램 단위" 임
  : 장점은 Kernel 동작 중인 Runtime에 동적으로 insert, remove가 가능함
  : System call 추가와 같이 매번 Kernel Rebuild가 필요하다면 매우 비효율적임

  : Module은 개발자들에게 편리한 기능으로, end-user에게는 필요가 없음
  : 그래서 개발이 완료되면 보통 Kernel에 build-in 하여 사용함

@ Version magic
  : Build된 Kernel과 Module을 build하는 kernel이 다르다면 insmod에 실패할 수 있음
  : 왜냐하면 Module이 사용하는 API가 현재 Kernel과 불일치할 수 있기 때문임
  : Module을 사용할 때는 항상 이 부분을 체크해야 함

/*
 * hello_module.c
 */
#include <linux/init.h>
#include <linux/module.h>

static int hello_init(void)
{
    printk("Hello,Linux World!!\n");

    return 0;
}

static void hello_exit(void)
{
    printk("Goodbye,Cruel World!!\n");
}

module_init(hello_init); // insmod 명령 시 수행되는 함수
module_exit(hello_exit); // rmmod 명령 시 수행되는 함수

MODULE_LICENSE("GPL");


/*
 * Makefile
 */
obj-m  := hello_module.o

KDIR  := /root/elayer/EmbeddedLinuxSystem/linux/kernel/linux-2.6.24-el2440-0504-final

PWD    := $(shell pwd)

default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean :
rm -rf *.ko
rm -rf *.mod.*
rm -rf .*.cmd
rm -rf *.o


/*
 * hello_module.ko 파일 생성
 */
# make
  ...
  CC [M]  /root/hello_module/hello_module.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /root/hello_module/hello_module.mod.o
  LD [M]  /root/hello_module/hello_module.ko
  ...


in Target Board,
$ insmod hello_module.ko
Hello, Module!

$ lsmod
Module                  Size  Used by    Not tainted
hello_module            1088  0          0xbf000000

$ rmmod hello_module.ko
Goodbye, Module!

Monday, April 20, 2015

System Call

@ System Call

(1) linux/arm/kernel/calls.S 수정
    -> 아래와 같이 new system call 선언 추가

...
CALL(sys_timerfd)    /* 350 */
CALL(sys_eventfd)
CALL(sys_fallocate)
CALL(sys_mycall)     /* 353, new system call */
...

(2) linux/kernel/mycall.c 생성
    -> 아래와 같이 mycall.c 작성

"mycall.c"
#include <linux/kernel.h>

/* asmlinkage는 assembly어(calls.S)와 연결하겠다는 의미의 keyword임 */
/* 함수명 sys_mycall은 calls.S의 CALL(sys_mycall) 과 동일해야 함 */
asmlinkage int sys_mycall(void)
{
printk("Hello, Kernel!\n");
return 2015;
}

(3) linux/kernel/Makefile 수정
    -> obj-y 에 mycall.o 추가
obj-y     = sched.o fork.o exec_domain.o panic.o printk.o profile.o \
            exit.o itimer.o time.o softirq.o resource.o \
            sysctl.o capability.o ptrace.o timer.o user.o user_namespace.o \
            signal.o sys.o kmod.o workqueue.o pid.o \
            rcupdate.o extable.o params.o posix-timers.o \
            kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o \
            hrtimer.o rwsem.o latency.o nsproxy.o srcu.o \
            utsname.o notifier.o \
            mycall.o

(4) linux/ 디렉터리에서 make zImage 수행
# cd linux-2.6.24-el2440-0504-final
# make zImage

(5) 새로운 kernel image로 Target Board 부팅
    -> tftp를 이용하여 새로운 kernel로 부팅

(6) Host PC에서 새로운 system call을 이용하는 application 작성
"myapp.c"
#include <stdio.h>

int main(void)
{
    /* calls.S에 추가한 index No. 가 사용됨 */
int ret = syscall(353);
printf(" ret = %d\n", ret);
    return 0;
}

(7) arm-linux-gnu-gcc를 가지고 cross-compile 수행
# arm-linux-gnu-gcc myapp.c

(8) nfs를 통해 a.out을 Target Board에서 실행
$ mount -t nfs 192.168.100.2:/tftpboot /mnt/nfs
$ cd /mnt/nfs
$ ls
a.out
$ ./a.out
Hello, Kernel!
ret = 2015

Gcc Make (Advanced)

@ Macro
OBJF = test1.o test2.o test3.o

test: $(OBJF)
gcc -o test $(OBJF)
test1.o: test1.c a.h
gcc -c test1.c
test2.o: test2.c a.h b.h
gcc -c test2.c
test3.o: test3.c b.h c.h
gcc -c test3.c
clean:
rm $(OBJF)


@ Inner Macro
# $@ : Current Rule, Target Name
# $* : Current Rule, Target Name (Except extension)
# $^ : Current Rule, All dependency Files
# $< : Current Rule, First dependency File Name

OBJF = test1.o test2.o test3.o

test: $(OBJF)
gcc -o $@ $^
test1.o: test1.c a.h
gcc -c $<
test2.o: test2.c a.h b.h
gcc -c $*.c
test3.o: test3.c b.h c.h
gcc -c $*.c
clean:
rm $(OBJF)


@ Macro Modification
OBJF = test1.o test2.o
OBJF := $(OBJF) test3.o

test: $(OBJF)
gcc -o $@ $^
test1.o: test1.c a.h
gcc -c $<
test2.o: test2.c a.h b.h
gcc -c $*.c
test3.o: test3.c b.h c.h
gcc -c $*.c
clean:
rm $(OBJF)


@ Implicit Rules
OBJF = test1.o test2.o test3.o

test: $(OBJF)
gcc -o $@ $(OBJF)
clean:
rm $(OBJF)


@ Suffix Rules
OBJF = test1.o test2.o test3.o

test: $(OBJF)
gcc -o $@ $(OBJF)
.c.o:
gcc -c $(CFLAGS) $<
clean:
rm $(OBJF)


@ Pattern Rules
OBJF = test1_obj.o test2_obj.o test3_obj.o

test: $(OBJF)
gcc -o $@ $(OBJF)
%_obj.o: %.c
gcc -c $< -o $@
clean:
rm $(OBJF)

Gcc Make

@ make utility
  : make는 Linux에서 대량의 소스를 쉽고 효율적으로 컴파일하기 위한 툴임

@ make grammar
  [target] : [dependencies]
  [tab][command]

@ makefile (or Makefile)
# this is a comment
test: test1.o test2.o test3.o
  gcc -o test \
        test1.o test2.o test3.o
test1.o: test1.c a.h
  gcc -c test1.c
test2.o: test2.c a.h b.h
  gcc -c test2.c
test3.o: test3.c b.h c.h
  gcc -c test3.c

# phony target, make utility regards it as what to be up-to-date (NO execute!)
clean:
rm test1.o test2.o test3.o

@ Examples
# make test
gcc -c test1.c
gcc -c test2.c
gcc -c test3.c
gcc -o test test1.o test2.o test3.o

# make test
make: `test'는 이미 갱신되었습니다.

# make clean
rm test1.o test2.o test3.o


# make clean
rm test1.o test2.o test3.o
rm: `test1.o'를 지울 수 없음: 그런 파일이나 디렉터리가 없습니다
rm: `test2.o'를 지울 수 없음: 그런 파일이나 디렉터리가 없습니다
rm: `test3.o'를 지울 수 없음: 그런 파일이나 디렉터리가 없습니다
make: *** [clean] 오류 1

# make test1.o // partial make
gcc -c test1.c

# make test2.o
gcc -c test2.c

# make test3.o
gcc -c test3.c

# make test
gcc -o test test1.o test2.o test3.o

Gcc GDB

@ Compile Option
 -g : Add debugging info into executable file

# gcc -g main.c

# gdb a.out
GNU gdb (GDB) 7.2-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/debug2/test2...done.
(gdb) 

@ Command Examples
(gdb) run
(gdb) break 12 // b 12
(gdb) break 20
(gdb) break 99
(gdb) delete 99 // d 99
(gdb) info break // show break info
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x08048411 in main at test2.c:12
2       breakpoint     keep y   0x0804847c in sum at test2.c:20

(gdb) continue // c, continue to next break
(gdb) next // n, move to next line (not into a function)
(gdb) step // s, step into a function
(gdb) print i // p i, print value of 'i'
(gdb) list // l, list source code around current line

(gdb) x $pc // show the value of register, $pc
(gdb) x 0x8048445 // show the value of address, 0x8048445
(gdb) info reg // show register info

(gdb) quit

Gcc Library

@ Static Library
# gcc -c fred.c bill.c
# ar -crv libfoo.a fred.o bill.o // libfoo.a created
r - fred.o
r - bill.o
# ar -t libfoo.a (or nm libfoo.a) // check including object files in libfoo.a
bill.o
fred.o
# gcc main.c -L/usr/lib -lfoo

@ Option
 -L : Location of library directories
 -l : library name

Gcc Build

@ Build Process
main.c
-- Preprocess --> main.i
-- Compile --> main.s
-- Assemble --> main.o
-- Link --> a.out

To check this,
# gcc -v --save-temps main.c // main.i, main.s, main.o created

@ Seperated Compilation
----------------------------------------
"main.c"
#include <stdio.h>
void main()
{
    hi();
}

"hi.c"
void hi()
{
    printf("Hi\n");
}
----------------------------------------

# gcc main.c // link error !
# gcc hi.c // link error !

Like this,
# gcc -c main.c // main.o created
# gcc -c hi.c // hi.o created
# gcc -o program main.o hi.o // execute linking, executable binary named 'program' created

@ Add Including Directory
# gcc main.c -I/usr/inc

Kernel Build

1. Install cross-compiler
  1) Install gcc
  2) Compile Kernel

Target Board Booting

@ 1st step, Bootloader Loading
  1) Serial Port 연결
    -. 목적 : Debugging 용도
    -. 사용 툴 : DNW 실행
    -. 연결방법 : 메뉴에서 [Serial Port] - [Connect] 수행하여 PC와 Serial Port 연결
    -. 특징 : RS-232 port, 9 pin cable

  2) Target Board 부팅 시 Bootloader Prompt 진입
    -. Prompt 에서 어떤 명령을 내려야 하지?
    -. Bootloader 역할 : "OS Loading" 을 위한 SW
 
  3) 어떻게 OS Loading 하는가?
    -. Linux는 부팅을 위해 Bootloader, Kernel, File System이 필요
    -. 먼저, OS(Linux Kernel) Loading을 해야 하는데...
    -. Host PC에 존재하는 zImage(Kernel Image)를 어떻게 Target Board로 옮기지?

    --------------------
        File System
    --------------------
       Linux Kernel
    --------------------
        Bootloader        <-- 현재 위치, Host에 존재하는 Linux Kernel을 끌어올 방법이 필요...
    --------------------
            H/W
     

@ 2nd step, Kernel & FileSystem Loading
  ① TFTP는 source IP, destination IP, filename 만 있으면 파일 전송이 가능한 간단한 프로토콜임
  ② Host PC에서 build한 Kernel과 File System을 Target Board에 Loading하기 위해 사용
  ③ Host PC에 TFTP Server를 구동
  ④ Target Board는 TFTP Client를 이용하여 Server에 파일 전송을 요청

@ TFTP Server 설정 (Host PC)
  # apt-get install nfs-kernel-server tftpd tftp xinetd // nfs, tftp 설치
  # vi /etc/xinetd.d/tftpd // tftp scripter 파일 편집
service tftp
{
protocol        = udp
port            = 69
socket_type     = dgram
wait            = yes
user            = nobody
server          = /usr/sbin/in.tftpd
server_args     = /tftpboot
disable         = no
}
  # /etc/init.d/xinetd restart // xinetd 데몬 재시작
  # netstat -au // tftp server 동작 확인

  서버 설정 시 주의할 사항들
  1) Virtual Machine 네트워크 설정 : NAT -> Bridged 변경 후 Reboot
  2) eth0 인터페이스 up : ifconfig eth0 192.168.100.2 up
  3) Windows 네트워크 설정 : 사용할 '로컬 연결 영역' 제외하고 나머지 '사용 안함'으로 변경

  Reference
  http://askubuntu.com/questions/201505/how-do-i-install-and-run-a-tftp-server

@ TFTP Client 요청 (Target Board)
  -. DNW.exe 실행 후 Serial Port Connect 수행
  -. Target Board 전원 On
  -. 서버로 Kernel과 File System 전송 요청
    # tftp 30c00000 zImage
    # tftp 30800000 ramdisk.gz
    # go linux // or go 30c00000, Linux는 자체 부팅 능력이 없으므로 이렇게 실행해 줌


@ 3rd step, File Transfer from Host PC to Target 
  ① NFS는 원격의 물리 장치를 로컬 물리 장치처럼 사용하는 방법
  ② Host PC와 Target이 파일을 공유하기 위해 사용
  ③ Host PC에 NFS Server를 구동
  ④ Target Board는 Server의 특정 디렉터리를 NFS 타입으로 mount 하여 사용

@ NFS Server 설정 (Host PC)
  # vi /etc/exports // nfs 설정파일 편집
...

/tftpboot       *(rw,no_root_squash,no_all_squash,async)

  # exportfs -r // nfs 설정 확인
  # /etc/init.d/nfs-kernel-server restart // nfs server 재시작
  # netstat -au // nfs server 동작 확인

@ NFS Client 설정 (Target Board)
  -. Target Board 전원 On
  -. Kernel, File System Loading 하여 shell까지 진입
  -. NFS Server로 mount 요청
    # mount -t nfs 192.168.100.2:/tftpboot /mnt/nfs
  -. PC와 Target에서 동기화되는지 확인

@ TFTP와 NFS의 차이점
  ① NFS는 Linux 부팅 완료 후에만 가능
  ② 하지만 PC작업과 Target 작업이 그대로 동기화되는 장점이 있음