Persistent Memory & DAX

 

Persistent Memory

Persistent 메모리는 다음과 같은 속성이 있다.

  • Solid State 고성능 바이트 단위 주소 접근이 가능한 디바이스로 메모리 버스위에서 동작한다.
    • DRAM과 같이 직접 addressing이 가능한 물리 주소를 지원하여 시스템 메모리로도 사용할 수 있다.
  • 전원이 차단된 상태에서도 데이터가 보존되는 비휘발성 메모리이다.
    • Flash SSD 같은 종류들보다 훨씬 access 타임이 빠르다.
    • 주의: 기존 NVRAM의 주요 특성을 가지고 있으나 혼동하면 안된다.
  • NVDIMM 버스 사용
    • DRAM이 장착되는 DIMM 슬롯을 이용하여 DRAM과 같은 대역폭을 사용한다.
    • SSD 등이 사용하는 pci/pcie 및 usb 버스 등의 IO 채널을 사용하는 것보다 훨씬 빠르다.
  • DRAM 보다 더 저렴하여 같은 비용으로 DRAM 보다 더 큰 용량을 사용할 수 있다.
  • DRAM 처럼 캐시 사용이 가능한 상태로 동작할 수 있다. (보통 그렇게 사용한다)
  • Pmem으로 줄여 표현하기도 한다.

 

NVDIMM Pmem

현재 가장 많이 사용하고 있는 DIMM 버스를 이용하는 제품 두 종류를 소개한다.

  • NVDIMM-N 타입 persistent 메모리
    • 8/16/32G 제품이 먼저 출시되었고, DRAM + NAND 플래시 구성으로 만들어졌다.
    • 장점: 읽고 쓰기 성능이 3D XPoint보다 빠르다.
    • 단점: 정전시 DRAM 데이터를 NAND에 백업하기 위해 super capacitor가 필수로 사용된다.
  • 3D XPoint persistent 메모리
    • 장점: 자체적으로 비휘발성이 유지되므로 정전을 대비하기 위해 super capacitor를 필요로 하지 않는다.
    • 단점: 읽고 쓰기 성능이 DRAM+NAND 구성보다 느리다.
    • 인텔과 마이크론이 개발하였으며, 인텔은 Optane, 마이크론은 QuantX라는 브랜드명을 사한다. 현재 8~512G 제품이 출시되어 있다.

 

참고: HBM 버스

  • DIMM 보다 더 빠른 메모리 버스로 테라 단위의 대역폭을 요구하는 고속 GPU등에 사용되었다. 다만 고속을 위해 칩 외부의 슬롯 형태가 아닌 칩에 통합되어 제작해야 하는 단점이 있다.

 

다음 그림은 NVDIMM-N 타입 persistent 메모리이다. 배터리(super capacitor)가 외부에서 공급되는 것을 확인할 수 있다.

 

다음 그림은 NVDIMM-N 타입 persistent 메모리의 블럭 다이어그램이다.

 

다음 그림은 인텔 XEON CPU와 같이 사용해야 하는 Optane DC Persistent 메모리를 보여준다.

 

NVDIMM 표준화 및 규격

  • NVDIMM 규격에 사용되는 NV(Non-Volatile) 미디어는 NAND 플래시, 3D XPoint, 자기 메모리, 상변화 메모리, 저항 변화 메모리 등이 포함된다.
  • 다음 3개의 규격이 사용된다.
    • NVDIMM-N
      • DRAM(DDR3/DDR4) + NV 미디어(최소 DRAM과 1:1 용량)로 구성하고 메모리 성격을 가진다.
      • DRAM과 바이트 단위 주소 액세스를 할 수 있으나 NV 미디어와는 직접 액세스하지 않는다.
      • NV 미디어는 DRAM의 백업 역할을 한다. 전원 fail 시에는 DRAM의 백업을 위해 추가 배터리 연결이 필요하다.
      • 세 타입 중 가장 빠른 수십 나노초(ns)의 레이튼시를 갖는다.
    • NVDIMM-F
      • NV 미디어로만 구성하고, 스토리지 성격을 가진다.
      • 윈도 매커니즘을 통해 매핑이 가능하고, 블럭 단위의 액세스를 지향한다. (마운트하여 사용하는 형태 등)
      • 세 타입 중 가장 느린 수십 마이크로초(us)의 레이튼시를 갖는다.
    • NVDIMM-P
      • NVDIMM-N과 F를 섞은 형태로 DRAM+NV 미디어(DRAM보다 훨씬 큰 용량)로 구성된 하이브리드 성격을 가진다.
      • DDR(4 or 5) 프로토콜을 사용하는 DRAM 인터페이스를 사용하지만 non-volatile 성격을 갖는 DRAM을 사용하여 배터리 백업을 필요치 않는다.
      • 세 타입 중 중간인 수백 나노초(ns)의 레이튼시를 갖는다.
      • 2018년에 규격이 확정되었고, 아직 제품화되지는 않았다.

 

NVDIMM pmem 드라이버

커널에서의 persistent 메모리 지원은 커널 4.2 이후부터 nvdimm 드라이버가 소개되었고, 커널 v4.4에서 안정화되었다.

  • 디바이스 트리에서 nvdimm pmem 드라이버 지원
    • compatible = “pmem-region
    • volatile 속성 여부에 따라 두 가지 타입의 메모리를 지원한다.
      • volatile region
        • 속성이 있는 경우 시스템 메모리와 같이 물리 주소가 부여되어 시스템 메모리로 관리된다. (ZONE_DEVICE)
        • 두 개 이상의 pmem을 사용하는 경우 인터리브 구성을 한다.
          • 시스템 메모리에 매핑 시 pmem을 1:1로 리니어하게 매핑하지 않고, pmem 드라이버에서 인터리브하게 매핑한다.
      • pmem region
        • volatile 속성이 지정되지 않은 이 region은 DAX를 지원하는 파일 시스템에 마운트하여 사용한다.

 

pmem region으로 등록되어 사용하는 예)

        /*
         * This node specifies one 4KB region spanning from
         * 0x5000 to 0x5fff that is backed by non-volatile memory.
         */
        pmem@5000 {
                compatible = "pmem-region";
                reg = <0x00005000 0x00001000>;
        };

 

volatile region으로 등록되어 사용하는 예)

        /*
         * This node specifies two 4KB regions that are backed by
         * volatile (normal) memory.
         */
        pmem@6000 {
                compatible = "pmem-region";
                reg = < 0x00006000 0x00001000
                        0x00008000 0x00001000 >;
                volatile;
        };

 

다음 그림은 인텔에서 보여준 nvdimm pmem을 이용하는 여러 가지 방법을 보여준다.

 

NVDIMM 블럭 매핑

다음 그림은 커널 블럭디바이스에서의 로지컬 블럭과 실제 NVDIMM의 물리 블럭이 매핑되어 사용되는 모습을 보여준다.

  • 매번 같은 블럭에 기록하는 것처럼 행동하여도, NVDIMM 내부에서는 새로운 빈 블럭을 찾아 기록한다. (SSD와 동일)

 


DAX(Direct Access)

DAX를 사용하면 Persistent 메모리 또는 블럭 장치에 저장된 파일에 직접 액세스할 수 있다. 직접 액세스하므로 커널은 페이지 캐시를 사용하지 않는다. 그러나 파일 시스템에서 DAX 지원이 없으면 Standard File API를 통해 파일 읽기 및 쓰기를 버퍼링하는데 페이지 캐시를 사용하고 추가적인 복사 작업이 소요된다.

 

다음 그림에서 파란색은 파일 API를 사용한 접근 방법과, DAX를 지원하는 파일 시스템을 통해 직접 접근하는 방법을 보여준다.

 

위의 개념을 인텔 Optane DC Persistent Memory를 사용하는 경우 앞으로 다룰 namespace와 region 개념을 포함하여 보여주고 있다.

 

Persistent memory-aware File System

운영체제에 따라 다음 파일 시스템이 DAX를 지원한다.

  •  리눅스
    • ext2, ext4, xfs 파일시스템
  • 윈도우 서버
    • ntfs 파일 시스템

 

파일마운트 시 DAX enable

파일 시스템을 마운트 시 파일 시스템 종류별로 DAX를 enable 하는 방법이 약간 차이가 있다.

  • ext2
    • -o dax 옵션을 사용하면 마운트된 모든 파일에 적용된다. (/etc/fstab 에서는 dax 옵션을 사용한다.)
  • ext4 & xfs
    • 다음 옵션에 따라 개별 디렉토리 및 파일을 DAX 지원 상태로 변경할 수 있다.
      • -o dax=inode
        • persistent 속성(FS_XFLAG_DAX)을 사용하여 정규 파일 및 디렉토리에 적용/제거 할 수 있고, 이 옵션은 디폴트로 사용된다.
        • 예) xfs_io -c ‘chattr +x’ <dirname>
          • <dirname>부터 이후에 만들어지는 파일이나 하위 디렉토리는 dax가 enable 상태로 적용된다.
      • -o dax=never
        • 파일 및 디렉토리를 생성시 dax가 적용되지 않는다.
      • -o dax=always
        • 파일 및 디렉토리를 생성시 항상 dax가 적용된다.
      • -o dax
        • -o dax=always와 동일한 옵션으로 -o dax 만을 사용하는 경우는 제거될 예정이다.

 

DAX 파일 시스템의 블럭 사이즈

DAX를 지원하기 위한 블럭 디바이스는 파일 시스템의 블럭 사이즈를 커널의 PAGE_SIZE와 동일하게 사용해서 생성해야 한다.

# fdisk -l /dev/ndblk0.1s
Disk /dev/ndblk0.1s: 32 GiB, 34325135360 bytes, 8380160 sectors
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

 

DAX(Direct Access) 커널 옵션

다음과 같은 커널 옵션들을 살펴본다.

  • DAX: direct access to differentiated memory”
    • CONFIG_DEV_DAX
    • mmap() 사용을 통해 저수준의 접근이 가능한 캐릭터 디바이스이다.
    • /dev/daxX.Y
  • PMEM DAX: direct access to persistent memory”
    • CONFIG_DEV_DAX_PMEM
    • 유저 스페이스 헬퍼 라이브러리인 libnvdimm 서브시스템을 통해 저수준의 접근이 가능하다.
      • ndctl 유틸리티
  • KMEM DAX: volatile-use of persistent memory”
    •  CONFIG_DEV_DAX_KMEM
    • DRAM과 같은 형태로 이용하므로 어떠한 applicaton의 수정 없이 사용할 수 있다.
    • ZONE_DEVICE 및 MEMORY_HOTPLUG를 사용한다.
    • 현재 pfn 메타데이터를 DRAM에 저장하므로 Persistent 메모리가 DRAM 용량보다 훨씬 큰 시스템 환경의 경우 현재 커널 버전에는 사용하지 않아야 한다.
      • PMEM DAX 처럼 pfn 메타데이터를 Persistent 메모리에 저장할 수 있는 옵션도 개발 완료된 상태였으나, 커널에 업스트림되지 않은 상태이다.
    • 참고: device-dax: “Hotplug” persistent memory for use like normal RAM (2019, v5.1-rc1)
  • HMEM DAX: direct access to ‘specific purpose’ memory”

 

 

KMEM DAX

시스템 물리 메모리로 구성하는 KMEM DAX는 DRAM이 사용하는 ZONE_NORMAL로 통합하지 않고, 약간 느린 메모리로 별도 분류하기 위해 ZONE_DEVICE를 사용한다. 한편 물리 메모리를 관리하기 위해 각 물리 메모리의 모든 페이지에 대응하는 pfn 메타데이터를 별도로 할당해서 가장 빠른 DRAM에 상주시켜 사용해야 한다. 따라서 persistent 메모리 용량이 매우 큰 경우에는 pfn 메타데이터 용량도 전체 메모리의 약 1.5% 만큼 소요되어 매우 커진다. 이렇게 만들어진 pfn 메타데이터는 성능을 위해 커널 메모리로 사용되는 pre-mapping된 ZONE_NORMAL(빠른 DRAM)에 할당해야 하므로 DRAM의 낭비마저 발생하는 단점이 있다. 그러므로 DRAM보다 약 8배 이상 큰 용량을 가진 persistent 메모리를 가진 시스템 구성의 경우에는 이 persistent 메모리를 물리 주소 번지를 갖는 ZONE_DEVICE로의 사용을 권장하지 않는다.

 

PMEM DAX

persistent 메모리의 할당 관리를 위한 pfn 메타데이터를 persistent storage에 만들어 관리를 하는 방법이 있다. 시스템 메모리에 비해 8배 이상 큰 용량의 persistent 메모리를 DAX를 지원하는 파일 시스템에 사용하여 마운트하여 사용하여 운용할 수 있다.

 

다음은 블럭 디바이스로 인식한 persistent 메모리의 파티션을 보여준다.

$ fdisk -l /dev/pmem0
Disk /dev/pmem0: 4 GiB, 4223664128 bytes, 8249344 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 10B97DA8-F537-6748-9E6F-ED66BBF7A047

Device       Start     End Sectors Size Type
/dev/pmem0p1  4096 8249310 8245215   4G Linux filesystem

 

다음은 블럭 디바이스로 인식한 persistent 메모리를 DAX를 지원하는 ext4 파일시스템으로 사용한 후 마운트한 예를 보여준다.

$ mkfs -t xfs /dev/pmem0
$ mount -o dax /dev/pmem0 /mnt/ext4-pmem0/

 

다음은 persistent 메모리들로 DAX를 지원하는 여러 파일시스템을 사용하여 마운트한 예를 보여준다.

$ lsblk
NAME                   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
pmem0                  259:0    0    16G  0 disk
├─pmem0p1              259:6    0     4G  0 part /mnt/ext4-pmem0
└─pmem0p2              259:7    0  11.9G  0 part /mnt/btrfs-pmem0
pmem1                  259:1    0    16G  0 disk /mnt/xfs-pmem1

 

DAX Driver

 

Deprecated /sys/class/dax 지원

 

필요한 커널 설정

  • CONFIG_ZONE_DEVICE=y
  • CONFIG_TRANSPARENT_HUGEPAGE=y
  • CONFIG_ACPI_NFIT=m
  • CONFIG_LIBNVDIMM=m
  • CONFIG_BLK_DEV_PMEM=m
  • CONFIG_ND_BLK=m
  • CONFIG_BTT=y
  • CONFIG_NVDIMM_PFN=y
  • CONFIG_NVDIMM_DAX=y
  • CONFIG_DEV_DAX_KMEM=m
  • CONFIG_FS_DAX=y

 


NVDIMM Pmem 관리(libndctl)

 

Region

NVDIMM들을 각각의 region 또는 N-way 인터리브 세트로 묶어 하나의 region을 만들어낸다. 각 Region은 운영 가능한 타입이 있다.

 

다음 그림은 NVDIMM을 각각의 region으로 사용하거나 N-way 인터리브 세트로 구성하여 하나로 사용하는 방법을 보여준다.

  • 좌측: 각각의 NVDIMM을 각각의 region으로 구성
  • 우측: 두 개의 NVDIMM을 2-way 인터리브 세트로 구성하여 하나의 region으로 구성

(벤더의 하드웨어 설정 및 소프트웨어 툴 사용: 예) Xeon 서버의 UEFI 펌웨어 설정 + ipmctl 툴 사용)

 

Namespace

위에서 구성한 Region을 1개 이상의 Namespace로 나누어 사용할 수 있다.

  • 1개 이상의 pmem 장치가 “/dev/*”에 연결되어 사용되는 단위이다.
  • 1개 또는 여러 개의 pmem을 인터리브로 연결하여 하나의 /dev/*에 사용할 수 있는 입출력 단위이다.
  • 각 Namespace는 아래 타입과 모드를 지정하여 구성한다.
  • Namespace를 구성하는 툴은 ndctl이다.

 

다음 그림에서 두 개의 Region위에 각각 두 개의 Namespace를 만들어 총 4개의 Namespace를 구성한 모습을 볼 수 있다.

 

Type

Namespace의 액세스 타입이다. pmem은 물리적으로 아래 두 타입 중 하나만을 지원하기도 하고, 둘 다 지원하는 경우도 있다. 두 가지 타입을 모두 지원하는 경우엔 두 개 이상의 Namespace에 각각의 타입을 섞어 구성할 수도 있다.

  • PMEM
    • 휘발성 DRAM과 같은 형태이다.
    • 유저 모드 및 커널 모드에서 가상 주소를 통해 직접 액세스가 가능하다.
  • BLK
    • 슬라이딩 윈도우를 통해 수 페이지를 액세스할 수 있고, 블럭 윈도우를 이동(슬라이딩)하는 방법으로 컨트롤한다.
    • 유저 모드에서는 가상 주소를 통해 직접 액세스가 가능하지만 커널 모드에서는 직접 대응하지 않는다.

 

다음 그림은 SPA(System Physical Address)로 DPA(DIMM Physicall Address)로 향하는 모습을 보여준다.

  • PMEM 타입은 직접 액세스가 가능하지만 BLK 타입은 직접 액세스가 불가능하며 블럭 윈도우를 이동하는 방법으로 접근할 수 있다.

 

Mode

각 Namespace를 위해 다음 모드들 중 하나를 선택한다.

  • fsdax (aka. memory)
  • devdax (aka. dax)
  • raw
  • sector

 

다음 그림은 Namespace를 생성할 때 사이즈, 타입 및 모드를 지정하는 모습을 보여준다.

 

Map

PFN 메타 데이터(memmap, struct page 배열)의 저장 장소를 선택한다. (fsdax & devdax만 유효)

  • mem
    • DRAM에 저장한다.
  • dev
    • persistent storage에 저장한다.

 


각 모드별 특징과 구성 방법

fsdax (aka. memory)

  • 이 모드는 DAX를 지원하는 ext2, ext4 및 xfs 파일시스템을 사용한다.
  • ndctl create-namespace 명령을 사용할 때 옵션을 지정하지 않으면 지정되는 디폴트 모드이다.
  • mmap을 사용하여 가상 주소에 매핑할 수 있다.
  • 페이지 캐시를 제거한다.
  • /dev/pmemN[.M]
  • 블럭 디바이스 – 파일시스템
  • DAX
  • PFN 메타데이터 필요
  • Label 메타데이터

 

다음 그림은 두 개의 namespace를 fsdax 모드로 구성하는 모습을 보여준다. DAX 지원하는 파일 시스템에서 유저 application이 이 디바이스를 표준  파일 시스템을 통한 접근과 mmap()을 통한 직접 액세스  모두 사용할 수 있다.

 

devdax (aka. dax)

  • fsdax와 유사하게 이 디바이스를 mmap을 사용하여 가상 주소에 매핑할 수 있다.
  • 페이지 캐시를 제거한다.
  • /dev/daxN.M
  • 캐릭터 디바이스
  • DAX
  • PFN 메타데이터 필요
  • Label 메타데이터

 

다음 그림은 두 개의 namespace를 devdax 모드로 구성하는 모습을 보여준다. 파일 시스템이 없는 캐릭터 디바이스 형태로 유저 application이 이 디바이스를 mmap() 하여 직접 액세스할 수 있다.

 

sector

  • DAX를 지원하지 않는 모든 파일시스템에도 마운트하여 사용할 수 있다.
  • 섹터/블럭 단위를 atomic하게 수정한다.
  • /dev/pmemNs
  • 블럭 디바이스 – 파일시스템
  • no-DAX
  • no-PFN 메타데이터
  • Label 메타데이터

 

다음 그림은 두 개의 namespace를 sector 모드로 구성하는 모습을 보여준다. DAX를 지원하는 파일 시스템이라도 유저 application이 이 디바이스를 표준  파일 시스템을 통한 접근만을 허용한다.

 

다음 그림은 3개의 NVDIMM을 3-way 인터리브 세트로 구성하여 하나의 region으로 만들고, 그 위에 1 개의 namespace를 sector 모드로 구성하는 모습을 보여준다. 유저 application이 이 namespace에는 DAX를 지원하지 않는 파일 시스템을 통해 접근을 허용한다.

 

raw

  • 메모리 디스크처럼 동작한다.
  • /dev/pmemN
  • 블럭 디바이스 – 파일시스템
  • no-DAX
  • no-PFN 메타데이터
  • no Label 메타데이터

 

다음 그림은 복합 구성한 예를 보여준다.

  • 구성 정보는 Labels에 저장된다.
    • Legacy NVDIMM은 Label 저장을 지원하지 않는다.
  • 3개의 NVDIMM 유닛을 3-way 인터리브 세트로 묶어 전체 영역을 1개의 Region으로 사용한다.
  • 그리고 3 개의 Region 각각을 BLK 타입 – 섹터 모드로 구성하고, 1 개의 PMEM 타입 – fsdax 모드로 구성한다.

 

다음은 위의 그림과 동일한 설정으로 NVDIMM 컨트롤 유틸리티인 ndctl 툴로 구성을 하는 모습을 보여준다.

  • 섹터 사이즈는 운영할 리눅스 커널의 PAGE_SIZE와 동일해야 한다.
# ndctl disable-namespace all
disabled 7 namespaces

# ndctl destroy-namespace all
destroyed 7 namespaces

# ndctl create-namespace --type=blk --size=32g
{
  "dev":"namespace2.1",
  "mode":"sector",
  "uuid":"37c254cd-b123-4b13-b5b0-cd06c30e4efb",
  "sector_size":4096,
  "blockdev":"ndblk2.1s"
}

# ndctl create-namespace --type=blk --size=32g
{
  "dev":"namespace1.1",
  "mode":"sector",
  "uuid":"e1f5fa9f-4820-42f4-b8a3-be90fa00fe79",
  "sector_size":4096,
  "blockdev":"ndblk1.1s"
}

# ndctl create-namespace --type=blk --size=32g
{
  "dev":"namespace0.1",
  "mode":"sector",
  "uuid":"1f84a98c-8dac-4a29-966a-42a5ac78d78f",
  "sector_size":4096,
  "blockdev":"ndblk0.1s"
}

# ndctl create-namespace --type=pmem --mode=memory
{
  "dev":"namespace3.0",
  "mode":"memory",
  "size":99881058304,
  "uuid":"33311d73-487d-4d27-8f2a-9d682570e312",
  "blockdev":"pmem3"
}

 

다음은 fdisk를 사용하여 각 NVDIMM 디바이스들의 블럭 장치 구성 정보를 보여준다.

# fdisk -l /dev/pmem3 /dev/ndblk*
Disk /dev/pmem3: 93 GiB, 99881058304 bytes, 195080192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

Disk /dev/ndblk0.1s: 32 GiB, 34325135360 bytes, 8380160 sectors
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

Disk /dev/ndblk1.1s: 32 GiB, 34325135360 bytes, 8380160 sectors
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

Disk /dev/ndblk2.1s: 32 GiB, 34325135360 bytes, 8380160 sectors
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

 

참고

 

2 thoughts to “Persistent Memory & DAX”

  1. 안녕하세요.
    저는 현대제이콤 김충열 수석이라고 합니다.
    intel 기반 cpu와 smbus로 연결된 fpga 제어 및
    lpc버스와 연결된 온도, 팬센서를 모니터링을 위한 방법을
    찾던 중 위 블로그를 방문하게 되었습니다.

    혹시 위에 언급한 device 제어를 위한 방법에 대해
    도움을 받을 수 있다면 비용이 발생되더라도 받고자 합니다.

    답변 부탁드립니다. 감사합니다.

    1. 안녕하세요? 모든 질의에 대한 도움은 최대한 빠르게 답변드리겠습니다.
      그리고 질문은 관련 디바이스 글에 해주시면 좋겠습니다. (i2c 등등)

      만일 공개를 원하지 않고 복잡한 경우에는 제 메일로 문의바랍니다. (jakeisname@gmail.com)

      감사합니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다