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

 

참고

 

Exception -9- (ARM64 Fault Handler)

<kernel v5.4>

Exception -9- (ARM64 Fault Handler)

 

메모리 fault 처리는 아키텍처 관련 코드를 수행한 후 아키텍쳐 공통 메모리 fault 코드를 수행한다.

  • 먼저 do_mem_abort() 함수를 호출하며, 페이지 fault 처리에 대해  do_page_fault() -> __do_page_fault() 함수 순서대로 수행한다.
  • 그 후 아키텍처 공통(generic) 메모리 fault 처리 함수인 handle_mm_fault() 함수로 진입한다.

 

다음 그림은 유저 및 커널 모드 수행 중 sync exception 발생 시 처리되는 함수 흐름이다.

 

do_mem_abort()

arch/arm64/mm/fault.c

asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
                                         struct pt_regs *regs)
{
        const struct fault_info *inf = esr_to_fault_info(esr);

        if (!inf->fn(addr, esr, regs))
                return;

        if (!user_mode(regs)) {
                pr_alert("Unhandled fault at 0x%016lx\n", addr);
                mem_abort_decode(esr);
                show_pte(addr);
        }

        arm64_notify_die(inf->name, regs,
                         inf->sig, inf->code, (void __user *)addr, esr);
}

가상 주소 @addr에 해당하는 공간에 접근하다 fault가 발생하였고, @esr(Exception Syndrom Register) 값과 스택에 저장한 context(pt_regs)를 인자로 가지고 진입하였다. 해당 가상 메모리 영역에 대한 fault 처리를 수행한다.

  • 코드 라인 4에서 @esr 값으로 static하게 정의된 fault_info[] 배열에서 구조체 fault_info를 알아온다.
  • 코드 라인 6~7에서 fault_info[] 배열에 미리 static으로 지정해둔 해당 함수를 실행한다.
    • 예) “level 1 permission fault”가 발생한 경우 do_page_fault() 함수를 호출한다.
  • 코드 라인 9~13에서 유저 영역이 아닌 가상 주소에서 fault가 발생한 경우 다음과 같은 디버그용 정보를 출력한다.
    • “Unhandled fault at 0x################” 에러 메시지
    • “Mem abort info: ESR=0x########” 등의 메모리 abort 정보
    • 데이터 abort 인 경우 “Data abort info:” 정보도 추가
    • swapper 또는 user 페이지 테이블 정보
  • 코드 라인 15~16에서 유저 영역에서의 fault는 해당 프로세스를 kill하고, 커널 영역에서의 fault인 경우 die 처리한다.

 


페이지 fault 처리

do_page_fault()

arch/arm64/mm/fault.c -1/3-

static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,
                                   struct pt_regs *regs)
{
        const struct fault_info *inf;
        struct mm_struct *mm = current->mm;
        vm_fault_t fault, major = 0;
        unsigned long vm_flags = VM_READ | VM_WRITE;
        unsigned int mm_flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;

        if (kprobe_page_fault(regs, esr))
                return 0;

        /*
         * If we're in an interrupt or have no user context, we must not take
         * the fault.
         */
        if (faulthandler_disabled() || !mm)
                goto no_context;

        if (user_mode(regs))
                mm_flags |= FAULT_FLAG_USER;

        if (is_el0_instruction_abort(esr)) {
                vm_flags = VM_EXEC;
                mm_flags |= FAULT_FLAG_INSTRUCTION;
        } else if (is_write_abort(esr)) {
                vm_flags = VM_WRITE;
                mm_flags |= FAULT_FLAG_WRITE;
        }

        if (is_ttbr0_addr(addr) && is_el1_permission_fault(addr, esr, regs)) {
                /* regs->orig_addr_limit may be 0 if we entered from EL0 */
                if (regs->orig_addr_limit == KERNEL_DS)
                        die_kernel_fault("access to user memory with fs=KERNEL_DS",
                                         addr, esr, regs);

                if (is_el1_instruction_abort(esr))
                        die_kernel_fault("execution of user memory",
                                         addr, esr, regs);

                if (!search_exception_tables(regs->pc))
                        die_kernel_fault("access to user memory outside uaccess routines",
                                         addr, esr, regs);
        }

        perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr);
  • 코드 라인 5에서 현재 태크스가 사용중인 메모리 관리에 해당하는 mm을 알아온다.
  • 코드 라인 7에서 vm 플래그 초기 값으로 VM_READ와 VM_WRITE플래그를 지정한다.
  • 코드 라인 8에서 mm 플래그 초기 값으로 FAULT_FLAG_ALLOW_RETRY와 FAULT_FLAG_KILLABLE 플래그를 지정한다.
  • 코드 라인 10~11에서 mm fault 발생 하였으므로 kprobe 디버그를 사용중이면 kprobe 상태에 따라 해당 kprobe fault 함수를 호출한다.
  • 코드 라인 17~18에서 irq context에서 fault가 발생하였거나, 해당 태스크의 fault hander를 disable한 경우 유저 fault 처리를 생략하고 곧장 커널에 대한 fault 처리 루틴을 수행하러 no_context: 레이블로 이동한다.
  • 코드 라인 20~21에서 유저 모드에서 fault가 발생한 경우 mm 플래그에 FAULT_FLAG_USER 플래그를 추가한다.
  • 코드 라인 23~25에서 유저 모드에서 명령어를 수행하다 fault가 발생한 경우 vm 플래그에 VM_EXEC를 대입하고, mm 플래그에 FAULT_FLAG_INSTRUCTION을 추가한다.
  • 코드 라인 26~29에서 쓰기 시도 중에 fault가 발생한 경우 vm 플래그에 VM_WRITE를 대입하고, mm 플래그에 FAULT_FLAG_WRITE를 추가한다.
  • 코드 라인 31~44에서 커널 모드에서 유저 영역에 접근 시 permission 문제로 fault가 발생한 경우 다음 3 가지 경우에 한하여 die 처리한다.
    • 메모리 제한이 없었던 경우
    • 커널 모드의 명령어를 처리중 fault가 발생한 경우
    • exception 테이블에 등록되지 않은 예외인 경우
  • 코드 라인 46에서 perf 디버그를 목적으로 page fault에 대한 카운팅을 알 수 있게 하기 위해 PERF_COUNT_SW_PAGE_FAULTS 카운터를 1 증가시킨다.

 

arch/arm64/mm/fault.c -2/3-

        /*
         * As per x86, we may deadlock here. However, since the kernel only
         * validly references user space from well defined areas of the code,
         * we can bug out early if this is from code which shouldn't.
         */
        if (!down_read_trylock(&mm->mmap_sem)) {
                if (!user_mode(regs) && !search_exception_tables(regs->pc))
                        goto no_context;
retry:
                down_read(&mm->mmap_sem);
        } else {
                /*
                 * The above down_read_trylock() might have succeeded in which
                 * case, we'll have missed the might_sleep() from down_read().
                 */
                might_sleep();
#ifdef CONFIG_DEBUG_VM
                if (!user_mode(regs) && !search_exception_tables(regs->pc)) {
                        up_read(&mm->mmap_sem);
                        goto no_context;
                }
#endif
        }

       fault = __do_page_fault(mm, addr, mm_flags, vm_flags);
        major |= fault & VM_FAULT_MAJOR;

        if (fault & VM_FAULT_RETRY) {
                /*
                 * If we need to retry but a fatal signal is pending,
                 * handle the signal first. We do not need to release
                 * the mmap_sem because it would already be released
                 * in __lock_page_or_retry in mm/filemap.c.
                 */
                if (fatal_signal_pending(current)) {
                        if (!user_mode(regs))
                                goto no_context;
                        return 0;
                }

                /*
                 * Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk of
                 * starvation.
                 */
                if (mm_flags & FAULT_FLAG_ALLOW_RETRY) {
                        mm_flags &= ~FAULT_FLAG_ALLOW_RETRY;
                        mm_flags |= FAULT_FLAG_TRIED;
                        goto retry;
                }
        }
        up_read(&mm->mmap_sem);

mm->mmap_sem 읽기 락을 획득한 채로 __do_page_fault() 함수를 수행한다.

  • 코드 라인 6에서 현재 태스크의 mm에서 read 락을 획득 시도한다.
  • 코드 라인 7~8에서 만일 락을 획득하지 못했고, fault 발생한 곳이 유저 모드가 아니면서 exception 테이블에 등록된 예외도 없으면 곧장 커널에 대한 fault 처리 루틴을 수행하러 no_context: 레이블로 이동한다.
  • 코드 라인 9~10에서 retry: 레이블이다. 여기서 read 락을 획득한다.
  • 코드 라인 11~23에서 만일 읽기 락을 획득 시도하다 정상적으로 획득한 경우, 즉 race 컨디션 없이 락을 획득한 경우에 먼저 premption point를 수행해준다.
    • preemption 옵션 중 하나인 voluntry 커널 설정인 경우 더 높은 우선 순위의 다른 태스크를 먼저 처리하기 위해 sleep 할 수 있다.
  • 코드 라인 25에서 __do_page_fault() 함수를 호출하여 페이지 fault에 대한 처리를 수행한 후 fault 결과를 알아온다.
  • 코드 라인 26에서 fault 결과 중 VM_FAULT_MAJOR 플래그가 있는 경우 major에도 추가한다.
  • 코드 라인 28~50에서 fault 결과에 retry 요청이 있는 경우이다.
    • fatal 시그널이 존재하는 경우 유저 모드에서 fault가 발생한 경우이면 0을 반환하고, 커널 모드에서 fault가 발생한 경우 no_context: 레이블로 이동한다.
    • mm 플래그에 retry를 허용하는 경우에 한하여 mm 플래그에서 FAULT_FLAG_ALLOW_RETRY 플래그를 제거하고, retry를 시도했다는 뜻의 FAULT_FLAG_TRIED를 추가한 후 retry 레이블로 이동한다.
  • 코드 라인 51에서 fault 처리를 위해 획득했었던 read 락을 풀어준다.

 

arch/arm64/mm/fault.c -3/3-

        /*
         * Handle the "normal" (no error) case first.
         */
        if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP |
                              VM_FAULT_BADACCESS)))) {
                /*
                 * Major/minor page fault accounting is only done
                 * once. If we go through a retry, it is extremely
                 * likely that the page will be found in page cache at
                 * that point.
                 */
                if (major) {
                        current->maj_flt++;
                        perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, regs,
                                      addr);
                } else {
                        current->min_flt++;
                        perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, regs,
                                      addr);
                }

                return 0;
        }

        /*
         * If we are in kernel mode at this point, we have no context to
         * handle this fault with.
         */
        if (!user_mode(regs))
                goto no_context;

        if (fault & VM_FAULT_OOM) {
                /*
                 * We ran out of memory, call the OOM killer, and return to
                 * userspace (which will retry the fault, or kill us if we got
                 * oom-killed).
                 */
                pagefault_out_of_memory();
                return 0;
        }

        inf = esr_to_fault_info(esr);
        set_thread_esr(addr, esr);
        if (fault & VM_FAULT_SIGBUS) {
                /*
                 * We had some memory, but were unable to successfully fix up
                 * this page fault.
                 */
                arm64_force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)addr,
                                      inf->name);
        } else if (fault & (VM_FAULT_HWPOISON_LARGE | VM_FAULT_HWPOISON)) {
                unsigned int lsb;

                lsb = PAGE_SHIFT;
                if (fault & VM_FAULT_HWPOISON_LARGE)
                        lsb = hstate_index_to_shift(VM_FAULT_GET_HINDEX(fault));

                arm64_force_sig_mceerr(BUS_MCEERR_AR, (void __user *)addr, lsb,
                                       inf->name);
        } else {
                /*
                 * Something tried to access memory that isn't in our memory
                 * map.
                 */
                arm64_force_sig_fault(SIGSEGV,
                                      fault == VM_FAULT_BADACCESS ? SEGV_ACCERR : SEGV_MAPERR,
                                      (void __user *)addr,
                                      inf->name);
        }

        return 0;

no_context:
        __do_kernel_fault(addr, esr, regs);
        return 0;
}
  • 코드 라인 4~23에서 fault 처리 결과 에러도 없고, 매핑도 정상이고 접근에 문제가 없었던 경우 major 변수 값에 따른 perf 카운터를 증가시키고, 정상 결과(0)를 반환한다.
  • 코드 라인 29~30에서 fault가 커널에서 발생한 경우는 커널을 계속 진행할 수 없어 no_context로 이동한다.
  • 코드 라인 32~40에서 OOM(메모리 부족) 상황인 경우 OOM 킬을 수행하고 정상 결과(0)를 반환한다.
    • OOM score가 높은 유저 태스크를 죽여 메모리를 확보한다.
  • 코드 라인 42에서 esr 값으로 fault_info를 알아온다.
  • 코드 라인 43에서 현재 태스크에 esr 값(내부 함수에서 조건에 따라 esr 값이 변경된다)을 기록한다.
  • 코드 라인 44~69에서 fault 주소에 접근하려 했던 프로세스에 다음과 같은 시그널을 전송한다. 참고로 해당 프로세스는 시그널을 받아 처리할 수 있는 핸들러를 추가할 수 있다. SIGBUS 및 SIGSEGV 시그널 등에 대한 처리 핸들러가 설치되지 않은 프로세스는 디폴트로 kill 된다.
    • SIGBUS fault인 경우 해당 프로세스에 SIGBUS 시그널을 전송한다.
    • HWPOISON fault 검출인 경우 메모리 에러로 프로세스에 SIGBUS 시그널을 전송한다.
    • 그 외 fault인 경우 해당 프로세스에 SIGSEGV 시그널을 전송한다.
  • 코드 라인 71에서 정상 결과(0)를 반환한다.
  • 코드 라인 73~75에서 no_context: 레이블이다. 커널이 더 이상 처리를 수행할 수 없어 fault에 대한 메시지를 출력하고 커널을 die 처리한다단 exception이 설치된 명령은 die 처리하지 않는다.

 

유저 페이지 fault 처리

__do_page_fault()

arch/arm64/mm/fault.c

static vm_fault_t __do_page_fault(struct mm_struct *mm, unsigned long addr,
                           unsigned int mm_flags, unsigned long vm_flags)
{
        struct vm_area_struct *vma = find_vma(mm, addr);

        if (unlikely(!vma))
                return VM_FAULT_BADMAP;

        /*
         * Ok, we have a good vm_area for this memory access, so we can handle
         * it.
         */
        if (unlikely(vma->vm_start > addr)) {
                if (!(vma->vm_flags & VM_GROWSDOWN))
                        return VM_FAULT_BADMAP;
                if (expand_stack(vma, addr))
                        return VM_FAULT_BADMAP;
        }

        /*
         * Check that the permissions on the VMA allow for the fault which
         * occurred.
         */
        if (!(vma->vm_flags & vm_flags))
                return VM_FAULT_BADACCESS;
        return handle_mm_fault(vma, addr & PAGE_MASK, mm_flags);
}

유저 주소에 대한 페이지 fault 처리이다.

  • 코드 라인 4~7에서 현재 태스크의 mm내에서 fault 주소로 vma 영역을 찾는다.
  • 코드 라인 13~18에서 vma 영역이 스택인 경우 스택을 확장 시도한다.
  • 코드 라인 24~25에서 vma 영역이 요청한 속성을 허용하지 않는 경우 VM_FAULT_BADACCESS 결과를 반환한다.
    • vma 공간이 vm 플래그(VM_READ, VM_WRITE, VM_EXEC 등)를 지원하지 않은 경우이다.
  • 코드 라인 26에서 아키텍처 mm fault 처리는 완료되었다. 이 다음부터는 아키텍처와 무관한 코드로 작성된 generic mm fault 루틴을 수행한다.

 

커널 페이지 fault 처리

__do_kernel_fault()

arch/arm64/mm/fault.c

static void __do_kernel_fault(unsigned long addr, unsigned int esr,
                              struct pt_regs *regs)
{
        const char *msg;

        /*
         * Are we prepared to handle this kernel fault?
         * We are almost certainly not prepared to handle instruction faults.
         */
        if (!is_el1_instruction_abort(esr) && fixup_exception(regs))
                return;

        if (WARN_RATELIMIT(is_spurious_el1_translation_fault(addr, esr, regs),
            "Ignoring spurious kernel translation fault at virtual address %016lx\n", addr))
                return;

        if (is_el1_permission_fault(addr, esr, regs)) {
                if (esr & ESR_ELx_WNR)
                        msg = "write to read-only memory";
                else
                        msg = "read from unreadable memory";
        } else if (addr < PAGE_SIZE) {
                msg = "NULL pointer dereference";
        } else {
                msg = "paging request";
        }

        die_kernel_fault(msg, addr, esr, regs);
}

커널 페이지에 대한 fault 처리를 수행한다.

  • 코드 라인 10~11에서 커널 명령(instruction)에서 fault가 발생하지 않았거나, 커널 명령에서 fault가 발생했어도 이에 대응하는 exception 테이블이 등록되어 있는 경우 커널 페이지에 대한 fault가 아니므로 그냥 함수를 빠져나간다.
  • 코드 라인 13~15에서 거짓(spurious) 커널 페이지 변환 fault인 경우 경고 메시지를 출력한다.
    • ratelimit 방식을 사용하여 디폴트로 최대 5초 이내에 10번만 메시지를 출력하도록 제한한다.
  • 코드 라인 17~21에서 커널 permission fault인 경우의 메시지를 선택한다.
  • 코드 라인 22~23에서 첫 페이지에 대한 접근의 경우의 메시지를 선택한다.
  • 코드 라인 24~26에서 그 외의 경우 메시지를 선택한다.
  • 코드 라인 28에서 커널 context를 덤프하고 die 처리를 한다.

 

die_kernel_fault()

arch/arm64/mm/fault.c

static void die_kernel_fault(const char *msg, unsigned long addr,
                             unsigned int esr, struct pt_regs *regs)
{
        bust_spinlocks(1);

        pr_alert("Unable to handle kernel %s at virtual address %016lx\n", msg,
                 addr);

        mem_abort_decode(esr);

        show_pte(addr);
        die("Oops", regs, esr);
        bust_spinlocks(0);
        do_exit(SIGKILL);
}

커널 context를 덤프하고 die 처리를 한다. 다음과 같은 내용들을 출력한다.

  • 메모리 abort 정보
  • 데이터 abort 정보
  • 페이지 테이블 정보
  • 레지스터 덤프
  • 커널 스택 덤프

 

다음은 0x00003fc8에 접근하다 발생한 커널 fault 예제이다.

[  110.965771] Unable to handle kernel paging request at virtual address 00003fc8
[  110.968799] Mem abort info:
[  110.969351]   Exception class = DABT (current EL), IL = 32 bits
[  110.969828]   SET = 0, FnV = 0
[  110.969988]   EA = 0, S1PTW = 0
[  110.970142] Data abort info:
[  110.970560]   ISV = 0, ISS = 0x00000006
[  110.970758]   CM = 0, WnR = 0
[  110.971357] user pgtable: 4k pages, 39-bit VAs, pgd = ffffffc06527e000
[  110.972077] [0000000000003fc8] *pgd=00000000a6761003, *pud=00000000a6761003, *pmd=0000000000000000
[  110.972846] Internal error: Oops: 96000006 [#1] PREEMPT SMP
[  110.973711] Modules linked in:
[  110.974527] CPU: 3 PID: 1034 Comm: sleep Not tainted 4.14.67-v8-qemu+ #74
[  110.974840] Hardware name: linux,dummy-virt (DT)
[  110.975749] task: ffffffc0650bd700 task.stack: ffffff800b0d8000
[  110.976622] PC is at cpuacct_charge+0x34/0xa8
[  110.977911] LR is at update_curr+0x98/0x228
[  110.978260] pc : [<ffffff80080ec54c>] lr : [<ffffff80080d5030>] pstate: a00001c5
[  110.978803] sp : ffffff800b0dba60
[  110.978985] x29: ffffff800b0dba60 x28: ffffffc06ffb0480 
[  110.979768] x27: ffffff80092aa7c0 x26: ffffffc06d57d780 
[  110.980104] x25: ffffff80092aa000 x24: 0000000000000008 
[  110.980295] x23: ffffffc06ffb0480 x22: 0000000001c8e1f0 
[  110.980460] x21: 0000000000000001 x20: 0000000001c8e1f0 
[  110.980858] x19: ffffffc0650b9d00 x18: 0000000000000000 
[  110.981157] x17: 0000000000000000 x16: ffffff8008111500 
[  110.981402] x15: 0000000000000000 x14: 00000000004bb6d4 
[  110.981611] x13: 00000000ff9cea5c x12: 0000000000000000 
[  110.981902] x11: 00000000ff9cea94 x10: 00000000004ce000 
[  110.982104] x9 : 000000000000075a x8 : 0000000000000400 
[  110.982268] x7 : 000000000000075a x6 : 0000000002739d7c 
[  110.982424] x5 : 00ffffffffffffff x4 : 0000000000000015 
[  110.983490] x3 : 0000000000000000 x2 : ffffffffff76abc0 
[  110.983734] x1 : 0000000000003ec0 x0 : 0000000000003ec0 
[  110.983940] Process sleep (pid: 1034, stack limit = 0xffffff800b0d8000)
[  110.984213] Call trace:
[  110.984388] Exception stack(0xffffff800b0db920 to 0xffffff800b0dba60)
[  110.984723] b920: 0000000000003ec0 0000000000003ec0 ffffffffff76abc0 0000000000000000
[  110.984970] b940: 0000000000000015 00ffffffffffffff 0000000002739d7c 000000000000075a
[  110.985185] b960: 0000000000000400 000000000000075a 00000000004ce000 00000000ff9cea94
[  110.985396] b980: 0000000000000000 00000000ff9cea5c 00000000004bb6d4 0000000000000000
[  110.985624] b9a0: ffffff8008111500 0000000000000000 0000000000000000 ffffffc0650b9d00
[  110.985878] b9c0: 0000000001c8e1f0 0000000000000001 0000000001c8e1f0 ffffffc06ffb0480
[  110.986092] b9e0: 0000000000000008 ffffff80092aa000 ffffffc06d57d780 ffffff80092aa7c0
[  110.986308] ba00: ffffffc06ffb0480 ffffff800b0dba60 ffffff80080d5030 ffffff800b0dba60
[  110.986528] ba20: ffffff80080ec54c 00000000a00001c5 0000000000000003 ffffffc06d553e00
[  110.986825] ba40: 0000007fffffffff ffffff80092a9b20 ffffff800b0dba60 ffffff80080ec54c
[  110.987497] [<ffffff80080ec54c>] cpuacct_charge+0x34/0xa8
[  110.987842] [<ffffff80080d5030>] update_curr+0x98/0x228
[  110.988085] [<ffffff80080d6108>] dequeue_task_fair+0x68/0x528
[  110.988251] [<ffffff80080ce440>] deactivate_task+0xa8/0xf0
[  110.988406] [<ffffff80080da9f4>] load_balance+0x454/0x960
[  110.988693] [<ffffff80080db2a4>] pick_next_task_fair+0x3a4/0x6c8
[  110.988987] [<ffffff8008b00bac>] __schedule+0x104/0x898
[  110.989226] [<ffffff8008b01374>] schedule+0x34/0x98
[  110.989459] [<ffffff8008b05084>] do_nanosleep+0x7c/0x168
[  110.989739] [<ffffff80081113f4>] hrtimer_nanosleep+0xa4/0x128
[  110.990058] [<ffffff8008111570>] compat_SyS_nanosleep+0x70/0x90
[  110.990300] Exception stack(0xffffff800b0dbec0 to 0xffffff800b0dc000)
[  110.990825] bec0: 00000000ff9cea6c 0000000000000000 00000000f7b82590 00000000ff9cea6c
[  110.991237] bee0: 0000000005f5e100 0000000000000000 00000000004b8e80 00000000000000a2
[  110.991768] bf00: 0000000000000000 0000000000000000 00000000004ce000 00000000ff9cea94
[  110.992084] bf20: 0000000000000000 00000000ff9cea5c 00000000004bb6d4 0000000000000000
[  110.992521] bf40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[  110.993118] bf60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[  110.993655] bf80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[  110.994227] bfa0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[  110.995517] bfc0: 00000000f7ab4a10 0000000060040010 00000000ff9cea6c 00000000000000a2
[  110.995998] bfe0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[  110.996235] [<ffffff8008083b18>] __sys_trace_return+0x0/0x4
[  110.996618] Code: 52800035 f9401260 8b010000 b4000080 (f9408400) 
[  110.997850] ---[ end trace cdf6df9c4e5b5c95 ]---
[  110.998316] note: sleep[1034] exited with preempt_count 2

 


테이블 변환 fault 처리

do_translation_fault()

arch/arm64/mm/fault.c

static int __kprobes do_translation_fault(unsigned long addr,
                                          unsigned int esr,
                                          struct pt_regs *regs)
{
        if (is_ttbr0_addr(addr))
                return do_page_fault(addr, esr, regs);

        do_bad_area(addr, esr, regs);
        return 0;
}

가상 주소를 물리 주소로 변환하는 페이지 테이블을 사용한 변환에 fault가 발생하였을 수행을 한다.

  • 코드 라인 5~6에서 fault 발생 주소가 유저 영역에 접근하는 경우 페이지 fault 처리로 이동한다.
  • 코드 라인 8에서 영역 침범과 관련한 fault 처리를 수행하도록 이동한다.

 

영역 침범 fault 처리

do_bad_area()

arch/arm64/mm/fault.c

static void do_bad_area(unsigned long addr, unsigned int esr, struct pt_regs *regs)
{
        /*
         * If we are in kernel mode at this point, we have no context to
         * handle this fault with.
         */
        if (user_mode(regs)) {
                const struct fault_info *inf = esr_to_fault_info(esr);

                set_thread_esr(addr, esr);
                arm64_force_sig_fault(inf->sig, inf->code, (void __user *)addr,
                                      inf->name);
        } else {
                __do_kernel_fault(addr, esr, regs);
        }
}

영역 침범에 대한 fault 처리를 수행한다.

  • 코드 라인 7~12에서 유저 모드에서 동작 중에 fault 발생한 경우 해당 프로세스에 시그널을 전송한다.
    • 해당 프로세스가 die 한다. 만일 프로세스에 시그널 핸들러가 설치된 경우 해당 핸들러 코드가 동작하다.
  • 코드 라인 13~15에서 커널 fault 처리를 수행하고 die 한다.

 


정렬 fault 처리

do_alignment_fault()

arch/arm64/mm/fault.c

static int do_alignment_fault(unsigned long addr, unsigned int esr,
                              struct pt_regs *regs)
{
        do_bad_area(addr, esr, regs);
        return 0;
}

정렬 fault가 발생한 겨우 영역 침범과 동일한 코드를 호출하여 처리한다.

 

참고