8월, 2025의 게시물 표시

리눅스 커널 - Process and Interrupt Context Synchronization

이미지
프로세스 컨텍스트와 인터럽트 컨텍스트에 대한 내용은 다음 페이지에서 다루었다:  프로세스/인터럽트 컨텍스트 프로세스 컨텍스트와 인터럽트 컨텍스트에서 같은 critical section에 접근하는 시나리오에 대해서 생각해보자. 이 시나리오에서는 데드락이 발생할 수 있는데 상황은 아래와 같다. 1. 프로세스 컨텍스트에서 스핀락을 잡는다. 2. 인터럽트가 발생하여 인터럽트 핸들러가 동작하고 스핀락을 잡으려고 한다. 위와 같은 상황에서 프로세스 컨텍스트가 스핀락을 해제해야 하는데, 인터럽트 핸들러에 의해 CPU를 빼앗겨 스핀락을 해제할 수 없다. 인터럽트 핸들러는 스핀락을 잡을 수 없다. 아래 그림에서 Problem situation에 해당한다. 이러한 데드락 상황을 피하기 위해서, 프로세스 컨텍스트에서 인터럽트를 disable 후 스핀락을 얻으면 된다. 아래 그림의 Solution에 해당한다. spin_lock_irqsave() 함수로 현재 인터럽트 상태(disable이었는지 enable이었는지)를 저장하고 인터럽트를 disable 한 뒤 스핀락을 획득한다. spin_unlock_irqrestore()는 스핀락을 해제하고 저장해놨던 이전 인터럽트 상태를 복원한다. 이야기를 더 진행하기 위해 먼저 bottom half context를 소개한다. 이 컨텍스트는 인터럽트 컨텍스트의 일종인데, 인터럽트는 enable 되어 있고  블로킹 함수는 부르면 안 되는 상태이다. 인터럽트 핸들러에서 전반부 작업이 끝나고 나서 도는 softirqs와 tasklets이 bottom half context에서 도는 것이다 (이후에 ksoftirqd에 의해 도는 softirq는 프로세스 컨텍스트에서 돈다.). Bottom half context와 critical section을 공유하는 프로세스 컨텍스트의 경우 spin_lock_bh() 함수로 스핀락을 잡아야 한다 (ksoftirqd에 의해 도는 softirq는 spin_lock()으로 잡는다.). spin_lock_bh(...

리눅스 커널 - Race Condition 회피 방법 (Atomic operations, Disabling Preemption, Spin Locks)

리눅스 커널은 symmetric multi-processing (SMP)를 지원하며, 이는 race condition을 회피/대응할 수 있어야 한다는 말이다 (single core라고 하더라도 preemption 및 interrupt에 의해 동시성은 위협 받을 수 있다.). SMP: 여러 개의 코어가 같은 메인 메모리를 공유하는 구조. 즉, 공유 메모리의 같은 address에 대한 접근이 동시(race condition)에 일어날 수 있다. race condition에 대응하기 위해 critical section이 어디인지 알고 있다는 가정 하에 다음 접근 법을 선택할 수 있다. critical section을 atomic하게 처리 (atomic instruction 이용) critical section 도중 preemption을 disable lock을 잡아서 serialize 시킨다 (critical section에 한 스레드만 존재할 수 있도록 한다) Atomic Operations 하드웨어에서 제공하는 atomic operation (instruction)을 이용하면 락을 잡지 않고도 atomic operation을 보장받을 수 있다. 예시: atomic_inc(), atomic_dec(), test_bit(), set_bit(), change_bit() 등 Atomic operation은 하드웨어 레벨에서 락을 잡기 때문에 시스템 레벨에서 잡는 것 보다는 가볍지만 여전히 비싼(expensive) operation이다. Disabling Preemption local_irq_disable()로 인터럽트를 막고, preempt_disable()로 커널 스케줄러에 의한 선점을 막을 수 있다. Spin Locks 스핀락은 락을 얻을 때까지 busy waiting을 한다. 락을 잡고 critical section 수행 후에 락을 해제하여 operation을 serialize 한다. 스핀락 이용 시, 특정 메모리 주소의 값을 동시에 여러 스레드가 보고 락을 얻을...

리눅스 커널 인터럽트 흐름 (Interrupt Flow)

이미지
인터럽트 시리즈 인터럽트 처리 이해 기본 (basic understanding interrupt handling ) 인터럽트 방식 비교 - Legacy, MSI, MSI-X 리눅스 커널 인터럽트 흐름 (Interrupt Flow) Process 수행 중 device에서 인터럽트 발생 돌고 있던 process의 context가 저장됨 인터럽트 핸들러 진입. 이때부터 인터럽트 context 이다. 인터럽트 context에서는 context switch가 불가한데, 이는 현재 수행중인 작업을 그만두고 CPU를 놔버리면 안 된다("블로킹 함수를 부르면 안 된다"라고도 함)는 뜻. 인터럽트 핸들러 동작을 모두 끝내고 IRET를 통해서만 CPU를 그만 독점해야한다. IRQ disable하여 다른 인터럽트 핸들러가 수행되지 않도록 함.  해당 인터럽트에 맞는 디바이스들의 핸들러 들이 동작함 IRQ enable하여 다른 인터럽트 핸들러 동작을 허용. 여기까지가 인터럽트 전반부 작업이다 (Top half). 수행할 softirq나 tasklet이 있다면 후반부 작업을 바로 수행 (이때 IRQ는 enable 상태이므로 다른 인터럽트 핸들러가 끼어들 수 있다.) 일을 모두 마쳤다면 IRET instruction이 수행되어 저장된 context를 불러와서 기존에 돌던 process를 재시작(resume). 이때 인터럽트 context에서 process context로 전환된다. 만약 돌아야 하는 workqueue나 지연된 softirq가 있다면 스케줄링 되었을 때 수행됨. 이때 softirq는 단시간 내에 끝나지 않아서, IRET 전에 중지되었던 softirq 이다. 이런 지연된 softirq는 ksoftirqd 커널 스레드에 의해 실행되며 프로세스 컨텍스트에서 수행된다. workqueue 또한 프로세스 컨텍스트에서 수행된다. 용어 maskable: 대부분의 인터럽트, 인터럽트가 disable 되어있으면 일시적으로 interrupt handler가 도는 것을 연기...

인터럽트 방식 비교 기본 - Legacy, MSI, MSI-X

이미지
 인터럽트 시리즈 인터럽트 처리 이해 기본 (basic understanding interrupt handling ) 인터럽트 방식 비교 - Legacy, MSI, MSI-X 리눅스 커널 인터럽트 흐름 (Interrupt Flow) MSI: PCI 2.2에서 도입 MSI-X: PCI 3.0에서 도입 --> PCIe 7.0 까지도 MSI 및 MSI-X는 표준 인터럽트 방식임

인터럽트 처리 이해 기본 (basic understanding interrupt handling )

이미지
인터럽트 시리즈 인터럽트 처리 이해 기본 (basic understanding interrupt handling ) 인터럽트 방식 비교 - Legacy, MSI, MSI-X 리눅스 커널 인터럽트 흐름 (Interrupt Flow) 하드웨어 인터럽트 컨트롤러(이하 컨트롤러)로부터 소프트웨어 인터럽트 핸들러에 이르기까지에 대한 논리적인 과정을 그림으로 표현하였다. (The logical process from the hardware interrupt controller to the software interrupt handler is illustrated in the above diagram.) IRQ는 전통적으로는 물리적인 전기선으로 이해될 수 있으며, 인터럽트 소스 (예: 키보드, 마우스, 스토리지, 네트워크 카드 등) 로부터 CPU에 이르는 길로 이해하면 된다. 현대적으로는 IRQ는 논리적인 라인으로 인터럽트를 CPU에 전달하는 역할을 한다. 1. IRR은 Interrupt Request Register로서 1-bit register이다. 즉 각 인터럽트가 현재 있는지/없는지만 인식하는데, 이는 이미 쌓인 인터럽트에 대한 중복된 인터럽트 핸들링이 발생하지 않을 것임을 의미한다. 2. IRR에 쌓인 인터럽트들은 우선순위(priority)를 가지며, 우선순위가 높은 인터럽트가 먼저 선택된다. 선택된 인터럽트는 mask 된다 (Mask register에 mask되었다고 표시된다). Mask 된다는 것의 의미는 disable 된다는 것이고, 해당 인터럽트가 컨트롤러에 의해 다시 선택되는 것을 방지한다. 즉, 처리중인 인터럽트를 또 처리하려고 하지 않는다. 3. 우선순위가 높아 선택된 인터럽트 신호는 CPU에게 보내진다. MSI, MSI-X 처럼 message based interrupt 일 수도 있고, legacy interrupt (물리적인 IRQ 라인을 통해 인터럽트 전달) 일 수도 있다. 4. 인터럽트를 받은 CPU는 해당 IRQ 번호에 맞는 interrupt...

리눅스 커널 파일시스템 기본 [1]

이미지
파일시스템이란 하드디스크, SSD, 플래시 메모리와 같은 저장장치 위에 파일과 디렉터리를 체계적으로 정리(organize)하는 방법을 말합니다. 파일시스템의 종류는 매우 다양합니다 (FAT, ext4, btrfs, NTFS 등). 그리고 한 대의 컴퓨터에서 같은 파일시스템 종류가 여러 개 동시에 사용될 수도 있습니다. 파일시스템마다 파일과 디렉터리, 사용자 데이터, 그리고 내부적으로 사용하는 메타데이터를 관리하는 방식이 서로 다르긴 하지만, 거의 모든 파일시스템에 공통적으로 사용되는 몇 가지 기본 개념들이 있습니다 (이 중 일부 개념은 저장장치(storage)와 메모리 양쪽에 존재하고, 일부는 메모리에만 존재합니다.): 슈퍼블록(superblock) : 해당 파일시스템의 기본 정보를 담고 있습니다. 예를 들어 블록 크기, 루트 아이노드, 파일시스템 전체 크기 같은 정보죠. 슈퍼블록은 저장장치와 메모리 모두에 존재하는데, 메모리에는 속도 향상을 위한 캐싱 용도로 복사되어 있습니다. 파일(file) :현재 열려있는 파일에 관한 정보(예: 현재 읽기 위치 등)를 담고 있는데, 이 정보는 메모리에만 존재합니다. 아이노드(inode) : 아이노드는 디스크에 저장된 파일을 고유하게 식별하는 역할을 합니다. 아이노드는 파일 크기, 접근 권한, 파일 종류 등 다양한 속성도 함께 가지고 있죠. 이 아이노드는 저장장치와 메모리 양쪽에 존재하며, 메모리에는 캐시 목적 등으로 복사되어 있습니다. 덴트리(dentry) : 덴트리(dentry)는 파일 이름과 아이노드(inode)를 연결하는 역할을 합니다. 덴트리 역시 저장장치와 메모리 양쪽에 존재하며, 메모리에는 캐싱 목적으로 복사되어 있습니다. struct ext4_inode 내부의 i_block에서 스토리지의 각 block들을 가리킨다. EXT4_N_BLOCKS 크기는 15로 ext4가 지원하는 데이터 블록 주소 지정 방식의 표준 크기이며, 이를 통해 작은 파일부터 아주 큰 파일까지 효율적으로 저장 위치를 관리할 수 있습니...

리눅스 커널 코드 레이아웃

리눅스 소스코드의 top level directory Top level directory 들 중 주요한 것들을 살펴보자. arch - architecture specific code 이다. 각 아키텍처의 디렉토리 들이 있음 (arm, x86 등). block- block device에 대한 읽기/쓰기 관련 코드 drivers - 다양한 디바이스에 대한 커널 드라이버 fs - generic filesystem code 이다. kernel - process management code (scheduler, tracing, generic irq code, locking 등) mm - memory management code. physical/virtual memory를 모두 포함한다.

리눅스 커널 파일 시스템 [6] Folio

리눅스 커널 파일 시스템 이해를 위해 먼저 folio 개념에 대해 이해할 필요가 있다. 1. Folio의 탄생 배경과 역사 Folio는 리눅스 커널의 메모리 관리 시스템을 현대화하고, 특히 THP(Transparent Huge Pages)와 같은 대규모 페이지 관리의 복잡성을 해결하기 위해 도입되었습니다. 기존의 리눅스 커널은 메모리 관리의 기본 단위로 struct page 를 사용했습니다. struct page 는 실제 물리 메모리의 단일 페이지(일반적으로 4KB)를 나타내는 메타데이터 구조체입니다. 하지만 기술의 발전과 함께 메모리 집약적인 애플리케이션이 증가하면서, 4KB 페이지 단위로 메모리를 관리하는 것이 비효율적이라는 문제가 대두되었습니다. 예를 들어, 2MB 크기의 THP를 관리하기 위해서는 512개의 struct page 를 개별적으로 다루어야 했습니다. 이로 인해 메모리 관리 코드의 복잡성이 증가하고, 성능 저하의 원인이 되었습니다. Folio는 이러한 문제점을 해결하기 위해 2021년 리눅스 5.16 버전에 처음 도입되었습니다. Folio는 기존의 struct page 를 추상화하여, 여러 개의 연속된 물리 페이지(Contiguous Physical Pages)를 하나의 논리적 그룹으로 묶는 새로운 개념입니다. 이는 메모리 관리 코드를 단순화하고, 대규모 페이지를 더 효율적으로 다룰 수 있도록 하기 위함입니다. 2. Folio의 현재 모습과 역할 Folio는 struct page 의 상위 개념으로, 하나 이상의 연속된 페이지들을 나타내는 단일 객체입니다. 현재 리눅스 커널은 메모리 관리를 위해 대부분 Folio를 사용하며, 기존 struct page 기반의 API들을 Folio 기반으로 전환하고 있습니다. Folio의 주요 역할은 다음과 같습니다. 메모리 관리의 통일된 인터페이스 제공: Folio는 파일 시스템 캐시, 스왑, THP 등 다양한 메모리 관리 기능에 대해 일관된 인터페이스를 제공합니다. 기존에는 각 기능마다 페이지를 다루는...