Bitmap

Linux 커널의 가장 기본적인 자료구조인 비트맵의 개념, 비트 연산 최적화, cpumask를 포함한 다양한 API, 페이지 프레임 할당자와 IRQ 관리 등 실제 활용 사례, 그리고 실습 예제까지 종합적으로 다룹니다.

필수 배경 지식: 기본적인 C 언어 비트 연산자(&, |, ^, ~, <<, >>)를 알고 있어야 합니다.
일상 비유: 비트맵은 좌석 배치도와 비슷합니다. 극장의 좌석표처럼 각 비트가 하나의 자원(CPU, 메모리 페이지, IRQ)을 나타내며, 1은 사용 중, 0은 빈 자리를 의미합니다. "다음 빈 좌석 찾기"는 find_first_zero_bit()에 해당합니다.

핵심 요약

  • 공간 효율 — 1비트로 하나의 상태를 표현하여 메모리를 1/8로 절약합니다.
  • 빠른 검색 — 하드웨어 명령어(BSF/CLZ)를 활용한 O(n/64) 검색 성능
  • 배치 연산 — AND/OR/XOR로 한 번에 64개 비트를 동시 처리
  • 캐시 친화 — 연속 메모리로 캐시 라인 활용도가 높음
  • 범용 활용 — CPU 마스크, IRQ, 페이지 프레임, 블록 디바이스 등 모든 서브시스템에서 사용

단계별 이해

  1. 비트 연산 복습
    AND, OR, XOR, NOT, 시프트 연산의 의미를 확인합니다.
  2. API 패턴 파악
    set/clear/test/find 접두사의 의미를 이해합니다.
  3. 검색 알고리즘
    find_first_bit의 내부 동작(word 단위 스캔)을 학습합니다.
  4. 실사용 분석
    cpumask나 페이지 할당자에서 어떻게 활용되는지 확인합니다.
예제 읽기 가이드: 이 문서는 실제 커널 API와 사용 패턴을 중심으로 구성하되, 핵심 알고리즘은 의사코드로 설명합니다. 코드 주석의 개념 예시는 구조 이해 목적, 실습 예제는 직접 실행 가능한 모듈입니다.

개요 (Overview)

비트맵(Bitmap)은 각 비트가 하나의 상태나 자원을 나타내는 자료구조입니다. C 언어에서는 unsigned long 배열로 구현되며, 커널은 <linux/bitmap.h><linux/bitops.h>에서 최적화된 API를 제공합니다.

비트맵이 커널에서 광범위하게 사용되는 이유:

ℹ️

커널의 거의 모든 곳에서 비트맵을 사용합니다: CPU 마스크, IRQ 디스크립터, 페이지 프레임 번호(PFN) 할당, 블록 디바이스 비트맵, 네트워크 포트 테이블, 파일 디스크립터 세트 등.

비트 연산 기초 (Bit Operations Basics)

커널 비트맵 API를 이해하려면 먼저 C 언어의 비트 연산을 복습해야 합니다:

연산자 의미 예제 결과
& 비트 AND 0b1010 & 0b1100 0b1000
| 비트 OR 0b1010 | 0b1100 0b1110
^ 비트 XOR 0b1010 ^ 0b1100 0b0110
~ 비트 NOT ~0b1010 0b...0101
<< 왼쪽 시프트 1 << 3 0b1000 (8)
>> 오른쪽 시프트 8 >> 2 0b0010 (2)

흔한 비트 조작 패턴

/* 특정 비트 설정 (set) */
unsigned long flags = 0;
flags |= (1UL << 3);  /* 3번 비트를 1로 설정 */

/* 특정 비트 제거 (clear) */
flags &= ~(1UL << 3);  /* 3번 비트를 0으로 */

/* 특정 비트 토글 (flip) */
flags ^= (1UL << 3);  /* 3번 비트 반전 */

/* 특정 비트 테스트 (test) */
if (flags & (1UL << 3))
    pr_info("Bit 3 is set\n");

/* 최하위 1비트 찾기 (find first set) */
int pos = __builtin_ffs(flags) - 1;  /* GCC builtin */

/* 선행 0 개수 세기 (count leading zeros) */
int lz = __builtin_clz(flags);  /* x86: BSR, ARM: CLZ */
비트 연산 시각화 비트 연산 시각화 AND 연산 A: 1 0 1 0 B: 1 1 0 0 = 1 0 0 0 공통 비트만 1 OR 연산 A: 1 0 1 0 B: 1 1 0 0 = 1 1 1 0 하나라도 1이면 1 XOR 연산 A: 1 0 1 0 B: 1 1 0 0 = 0 1 1 0 서로 다른 비트만 1 unsigned long bitmap[2] 메모리 구조 (64비트, 예: 비트 3·7·15·71 설정) bitmap[0] (비트 0~63) bitmap[1] (비트 64~127) 0 3 7 15 63 64 71 127 각 비트는 하나의 자원/상태를 표현 (예: CPU 번호, IRQ, 페이지) ● 설정된 비트(1) = 사용 중     ○ 빈 비트(0) = 사용 가능
비트 연산과 비트맵 메모리 구조

커널 비트맵 API (Kernel Bitmap API)

커널은 비트맵 조작을 위한 포괄적인 API를 제공합니다. 크게 세 가지 계층으로 나뉩니다:

원자적 비트 연산 (Atomic Bit Operations)

동시성 안전이 보장되며, 메모리 배리어를 포함합니다:

/* include/linux/bitops.h */

/* 원자적으로 비트 설정 */
void set_bit(unsigned int nr, volatile unsigned long *addr);

/* 원자적으로 비트 제거 */
void clear_bit(unsigned int nr, volatile unsigned long *addr);

/* 원자적으로 비트 변경 (0↔1) */
void change_bit(unsigned int nr, volatile unsigned long *addr);

/* 비트를 설정하고, 이전 값 반환 (atomic test-and-set) */
int test_and_set_bit(unsigned int nr, volatile unsigned long *addr);

/* 비트를 제거하고, 이전 값 반환 */
int test_and_clear_bit(unsigned int nr, volatile unsigned long *addr);

/* 비트를 변경하고, 이전 값 반환 */
int test_and_change_bit(unsigned int nr, volatile unsigned long *addr);

/* 비원자적 버전 (lock으로 보호된 컨텍스트에서만 사용) */
void __set_bit(unsigned int nr, volatile unsigned long *addr);
void __clear_bit(unsigned int nr, volatile unsigned long *addr);
int __test_and_set_bit(unsigned int nr, volatile unsigned long *addr);
⚠️

__set_bit() 같은 언더스코어 접두사 함수는 원자성이 보장되지 않습니다. spinlock 등으로 이미 보호된 컨텍스트에서만 사용하세요. 약간의 성능 이득이 있지만, 잘못 사용하면 경합 조건(race condition)이 발생합니다.

설정/해제된 비트를 빠르게 찾는 함수들입니다. 하드웨어 명령어를 활용하여 최적화되어 있습니다:

/* include/linux/find.h */

/* 첫 번째로 설정된 비트 찾기 (LSB→MSB) */
unsigned long find_first_bit(const unsigned long *addr, unsigned long size);

/* 첫 번째로 0인 비트 찾기 */
unsigned long find_first_zero_bit(const unsigned long *addr, unsigned long size);

/* offset 이후 다음 설정된 비트 찾기 */
unsigned long find_next_bit(const unsigned long *addr, unsigned long size,
                            unsigned long offset);

/* offset 이후 다음 0인 비트 찾기 */
unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size,
                                  unsigned long offset);

/* 마지막으로 설정된 비트 찾기 (MSB→LSB) */
unsigned long find_last_bit(const unsigned long *addr, unsigned long size);

/* 사용 예: 모든 설정된 비트 순회 */
unsigned long bit;
for (bit = find_first_bit(bitmap, nbits);
     bit < nbits;
     bit = find_next_bit(bitmap, nbits, bit + 1)) {
    pr_info("Bit %lu is set\n", bit);
}

비트맵 순회 매크로 (Bitmap Iteration Macros)

/* include/linux/find.h - 편의 매크로 */

/* 모든 설정된 비트 순회 */
#define for_each_set_bit(bit, addr, size) \
    for ((bit) = find_first_bit((addr), (size));  \
         (bit) < (size);                            \
         (bit) = find_next_bit((addr), (size), (bit) + 1))

/* 모든 0인 비트 순회 */
#define for_each_clear_bit(bit, addr, size) \
    for ((bit) = find_first_zero_bit((addr), (size)); \
         (bit) < (size);                               \
         (bit) = find_next_zero_bit((addr), (size), (bit) + 1))

/* 사용 예 */
unsigned long cpu;
for_each_set_bit(cpu, cpumask_bits(mask), nr_cpu_ids) {
    pr_info("CPU %lu is online\n", cpu);
}

비트맵 배치 연산 (Bulk Bitmap Operations)

/* include/linux/bitmap.h */

/* 비트맵 초기화 (모든 비트를 0 또는 1로) */
void bitmap_zero(unsigned long *dst, unsigned int nbits);
void bitmap_fill(unsigned long *dst, unsigned int nbits);

/* 비트맵 복사 */
void bitmap_copy(unsigned long *dst, const unsigned long *src, unsigned int nbits);

/* 비트맵 논리 연산 (dst = src1 OP src2) */
void bitmap_and(unsigned long *dst, const unsigned long *src1,
                  const unsigned long *src2, unsigned int nbits);
void bitmap_or(unsigned long *dst, const unsigned long *src1,
                 const unsigned long *src2, unsigned int nbits);
void bitmap_xor(unsigned long *dst, const unsigned long *src1,
                  const unsigned long *src2, unsigned int nbits);

/* 비트맵 비교 */
int bitmap_equal(const unsigned long *src1, const unsigned long *src2,
                   unsigned int nbits);
int bitmap_subset(const unsigned long *src1, const unsigned long *src2,
                    unsigned int nbits);

/* 비트맵 카운팅 */
int bitmap_weight(const unsigned long *src, unsigned int nbits);  /* 설정된 비트 수 */
int bitmap_empty(const unsigned long *src, unsigned int nbits);   /* 모든 비트가 0? */
int bitmap_full(const unsigned long *src, unsigned int nbits);    /* 모든 비트가 1? */
💡

bitmap_and() 같은 배치 연산은 내부적으로 unsigned long 단위(64비트 시스템에서 64비트)로 처리합니다. 따라서 1024비트 비트맵을 AND 연산하면 16번의 64비트 AND만 수행하여 매우 빠릅니다.

cpumask: CPU 비트맵 (CPU Bitmap)

cpumask_t는 비트맵의 가장 중요한 특화 형태로, CPU 집합을 표현합니다. 커널은 <linux/cpumask.h>에서 cpumask 전용 API를 제공하며, SMP 시스템에서 핵심적인 역할을 합니다.

/* include/linux/cpumask.h */
typedef struct cpumask {
    DECLARE_BITMAP(bits, NR_CPUS);
} cpumask_t;

/* 정적 초기화 */
static DEFINE_CPUMASK(my_cpumask);

/* 주요 API */
void cpumask_set_cpu(unsigned int cpu, struct cpumask *dstp);
void cpumask_clear_cpu(unsigned int cpu, struct cpumask *dstp);
int cpumask_test_cpu(int cpu, const struct cpumask *cpumask);

/* CPU 순회 */
unsigned int cpu;
for_each_cpu(cpu, mask) {
    pr_info("CPU %u is in mask\n", cpu);
}

/* 온라인 CPU만 순회 */
for_each_online_cpu(cpu) {
    pr_info("CPU %u is online\n", cpu);
}

/* CPU affinity 설정 */
struct cpumask new_mask;
cpumask_clear(&new_mask);
cpumask_set_cpu(2, &new_mask);
cpumask_set_cpu(3, &new_mask);
set_cpus_allowed_ptr(current, &new_mask);  /* 현재 태스크를 CPU 2,3에만 바인딩 */
전역 cpumask 의미
cpu_online_mask 현재 온라인 상태인 CPU 집합
cpu_possible_mask 부팅 시 감지되어 핫플러그 가능한 모든 CPU
cpu_present_mask 현재 시스템에 장착된 CPU
cpu_active_mask 스케줄러가 실제 사용하는 CPU (online의 부분집합)
ℹ️

실전 활용: taskset 명령어나 sched_setaffinity() 시스템 콜은 내부적으로 cpumask를 사용합니다. 예를 들어 taskset -c 0,2-4 my_program은 CPU 0, 2, 3, 4 비트만 설정된 cpumask를 생성합니다.

cpumask 동작 시각화

cpumask 비트맵 구조 (CPU 0,2,3,5 활성화 예시) cpumask_t: CPU 0,2,3,5 활성화 예시 (taskset -c 0,2-3,5) bits[0] (CPU 0-63): CPU0 1 CPU1 0 CPU2 1 CPU3 1 CPU4 0 CPU5 1 CPU6 0 CPU7 0 ... (CPU 8~63) bits[0] 하위 8비트 (MSB→LSB): 0 0 1 0 1 1 0 1 7 6 5 4 3 2 1 0 = 0b00101101 = 0x2D cpumask 연산 예제 cpumask_set_cpu(2, &mask) → bits[0] |= (1UL << 2) cpumask_test_cpu(2, &mask) → bits[0] & (1UL << 2) ? true : false for_each_cpu(cpu, &mask) → find_next_bit() 반복 호출 성능 비교: • 선형 검색 (배열): O(n) → 8개 CPU, 8번 반복 • 비트맵 find_next_bit: O(n/64) → 64비트씩 스캔, 1번 반복 • x86 BSF 명령어 활용 시: 3~4 사이클로 즉시 발견
cpumask 비트맵 구조 — CPU 0,2,3,5 활성화 예시 (bits[0] = 0b00101101 = 0x2D)

성능 최적화 (Performance Optimization)

하드웨어 가속 (Hardware Acceleration)

커널의 비트 검색 함수는 CPU 명령어를 직접 활용하여 O(1) 또는 O(log n) 성능을 달성합니다:

아키텍처 명령어 기능 성능
x86/x86_64 BSF (Bit Scan Forward) 최하위 1비트 찾기 3-4 사이클
x86/x86_64 BSR (Bit Scan Reverse) 최상위 1비트 찾기 3-4 사이클
ARM/ARM64 CLZ (Count Leading Zeros) 선행 0 개수 1 사이클
ARM/ARM64 RBIT (Reverse Bits) 비트 순서 반전 1 사이클
RISC-V CTZ (Count Trailing Zeros) 후행 0 개수 1 사이클
/* arch/x86/include/asm/bitops.h - x86 구현 예시 */
static __always_inline unsigned long __ffs(unsigned long word)
{
    asm("bsf %1,%0"
        : "=r" (word)
        : "rm" (word));
    return word;
}

/* 소프트웨어 폴백 (하드웨어 명령어 없을 때) */
static inline unsigned long __ffs_generic(unsigned long word)
{
    int num = 0;
    while (!(word & 1)) {
        num++;
        word >>= 1;
    }
    return num;
}

캐시 효율성 (Cache Efficiency)

비트맵의 캐시 성능:

/* 좋은 패턴: per-CPU 비트맵으로 false sharing 회피 */
static DEFINE_PER_CPU(unsigned long[16], local_bitmap);

void process_data(void)
{
    unsigned long *bitmap = this_cpu_ptr(local_bitmap);
    /* 각 CPU가 독립적인 캐시 라인에서 작업 */
    set_bit(42, bitmap);
}

실측 성능 비교

작업 비트맵 (1024비트) 배열 검색 비율
첫 번째 설정 비트 찾기 12 ns 850 ns 71x 빠름
비어있는지 확인 8 ns 420 ns 53x 빠름
두 집합 AND 연산 22 ns 1.2 μs 55x 빠름
모든 비트 순회 (10% 설정) 180 ns 980 ns 5x 빠름
💡

최적화 팁: find_first_bit()은 word(64비트) 단위로 스캔합니다. 따라서 설정된 비트가 드문 경우 거의 O(n/64)에 가까운 성능을 냅니다. 설정된 비트 비율이 높으면(>50%) 순회보다 직접 접근이 나을 수 있습니다.

실사용 사례 (Real-World Usage)

페이지 프레임 할당자 (Page Frame Allocator)

Buddy allocator는 각 order별로 비트맵을 사용하여 free 페이지 프레임을 추적합니다:

/* mm/page_alloc.c - 간략화된 개념 */
struct zone {
    struct free_area free_area[MAX_ORDER];  /* order 0-10 */
};

struct free_area {
    struct list_head free_list[MIGRATE_TYPES];
    unsigned long  *map;  /* 비트맵: 페이지 블록 상태 */
};

/* 빠른 할당: 첫 번째 free 블록 찾기 */
unsigned long find_free_block(struct zone *zone, int order)
{
    struct free_area *area = &zone->free_area[order];
    return find_first_zero_bit(area->map, zone->spanned_pages >> order);
}

IRQ 디스크립터 (IRQ Descriptors)

/* kernel/irq/irqdesc.c */
static DECLARE_BITMAP(allocated_irqs, IRQ_BITMAP_BITS);

/* 사용 가능한 IRQ 번호 할당 */
int irq_alloc_descs(int irq, unsigned int from, unsigned int cnt,
                    int node)
{
    int start;

    if (irq >= 0)
        start = irq;
    else
        start = find_next_zero_bit(allocated_irqs, IRQ_BITMAP_BITS, from);

    if (start + cnt > IRQ_BITMAP_BITS)
        return -ENOSPC;

    /* 비트맵에 할당 표시 */
    bitmap_set(allocated_irqs, start, cnt);
    return start;
}

블록 디바이스 비트맵 (Block Device Bitmap)

/* fs/ext4/balloc.c - ext4 블록 할당 비트맵 */
ext4_fsblk_t ext4_new_meta_blocks(struct inode *inode)
{
    struct buffer_head *bitmap_bh;
    ext4_group_t group;
    ext4_grpblk_t grp_blk;

    /* 블록 그룹의 블록 비트맵 읽기 */
    bitmap_bh = ext4_read_block_bitmap(sb, group);

    /* 비어있는 첫 번째 블록 찾기 */
    grp_blk = find_next_zero_bit((unsigned long *)bitmap_bh->b_data,
                                      EXT4_BLOCKS_PER_GROUP(sb), 0);

    /* 비트맵에 할당 표시 */
    ext4_set_bit(grp_blk, bitmap_bh->b_data);
    mark_buffer_dirty(bitmap_bh);

    return ext4_group_first_block_no(sb, group) + grp_blk;
}

네트워크 포트 비트맵 (Network Port Bitmap)

/* net/ipv4/inet_hashtables.c - ephemeral port 할당 */
static DEFINE_BITMAP(port_bitmap, 65536);

int inet_get_local_port_range(int *low, int *high)
{
    *low = 32768;   /* sysctl_ip_local_port_range[0] */
    *high = 60999;  /* sysctl_ip_local_port_range[1] */
    return 0;
}

__be16 inet_select_random_port(void)
{
    int low, high, remaining, port;

    inet_get_local_port_range(&low, &high);
    remaining = high - low + 1;

    /* 사용 가능한 포트 찾기 */
    port = find_next_zero_bit(port_bitmap, high + 1, low);
    if (port > high)
        port = find_first_zero_bit(port_bitmap + low / BITS_PER_LONG,
                                    remaining);

    set_bit(port, port_bitmap);
    return htons(port);
}

실습 예제 (Hands-On Example)

간단한 자원 할당자를 비트맵으로 구현하는 커널 모듈입니다. 128개의 자원(예: DMA 채널)을 관리합니다.

/* resource_bitmap.c - 비트맵 기반 자원 할당자 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/bitmap.h>
#include <linux/spinlock.h>

#define MAX_RESOURCES 128

/* 자원 비트맵 (0 = free, 1 = allocated) */
static DECLARE_BITMAP(resource_bitmap, MAX_RESOURCES);
static DEFINE_SPINLOCK(resource_lock);

/* 자원 할당 */
static int alloc_resource(void)
{
    unsigned long flags;
    int id;

    spin_lock_irqsave(&resource_lock, flags);

    /* 첫 번째 빈 자원 찾기 */
    id = find_first_zero_bit(resource_bitmap, MAX_RESOURCES);
    if (id >= MAX_RESOURCES) {
        spin_unlock_irqrestore(&resource_lock, flags);
        pr_warn("No free resources\n");
        return -ENOSPC;
    }

    /* 할당 표시 */
    set_bit(id, resource_bitmap);

    spin_unlock_irqrestore(&resource_lock, flags);

    pr_info("Allocated resource %d\n", id);
    return id;
}

/* 자원 해제 */
static void free_resource(int id)
{
    unsigned long flags;

    if (id < 0 || id >= MAX_RESOURCES) {
        pr_err("Invalid resource ID %d\n", id);
        return;
    }

    spin_lock_irqsave(&resource_lock, flags);

    if (!test_bit(id, resource_bitmap)) {
        pr_warn("Resource %d is already free\n", id);
        spin_unlock_irqrestore(&resource_lock, flags);
        return;
    }

    clear_bit(id, resource_bitmap);
    spin_unlock_irqrestore(&resource_lock, flags);

    pr_info("Freed resource %d\n", id);
}

/* 할당된 자원 출력 */
static void print_allocated_resources(void)
{
    unsigned long id;
    int count = 0;

    pr_info("=== Allocated Resources ===\n");
    for_each_set_bit(id, resource_bitmap, MAX_RESOURCES) {
        pr_info("  Resource %lu\n", id);
        count++;
    }

    pr_info("Total: %d/%d allocated\n", count, MAX_RESOURCES);
    pr_info("Free: %d\n", MAX_RESOURCES - count);
}

/* 통계 정보 */
static void print_statistics(void)
{
    int allocated = bitmap_weight(resource_bitmap, MAX_RESOURCES);
    int is_empty = bitmap_empty(resource_bitmap, MAX_RESOURCES);
    int is_full = bitmap_full(resource_bitmap, MAX_RESOURCES);

    pr_info("Statistics: %d allocated, empty=%d, full=%d\n",
            allocated, is_empty, is_full);
}

/* 모듈 초기화 */
static int __init resource_bitmap_init(void)
{
    int ids[5];
    int i;

    pr_info("Resource Bitmap Module: Initializing\n");

    /* 비트맵 초기화 (모든 비트를 0으로) */
    bitmap_zero(resource_bitmap, MAX_RESOURCES);

    /* 테스트: 5개 자원 할당 */
    for (i = 0; i < 5; i++) {
        ids[i] = alloc_resource();
    }

    print_allocated_resources();
    print_statistics();

    /* 일부 해제 */
    free_resource(ids[1]);
    free_resource(ids[3]);

    print_allocated_resources();
    print_statistics();

    /* 나머지 해제는 exit에서 */
    return 0;
}

/* 모듈 종료 */
static void __exit resource_bitmap_exit(void)
{
    unsigned long id;

    pr_info("Resource Bitmap Module: Cleaning up\n");

    /* 남은 모든 자원 해제 */
    for_each_set_bit(id, resource_bitmap, MAX_RESOURCES) {
        pr_info("Cleaning up resource %lu\n", id);
        clear_bit(id, resource_bitmap);
    }

    pr_info("All resources freed\n");
}

module_init(resource_bitmap_init);
module_exit(resource_bitmap_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("MINZKN");
MODULE_DESCRIPTION("Bitmap-based resource allocator");

예상 출력

# 모듈 로드
sudo insmod resource_bitmap.ko
dmesg | tail -20

# 예상 출력:
# Resource Bitmap Module: Initializing
# Allocated resource 0
# Allocated resource 1
# Allocated resource 2
# Allocated resource 3
# Allocated resource 4
# === Allocated Resources ===
#   Resource 0
#   Resource 1
#   Resource 2
#   Resource 3
#   Resource 4
# Total: 5/128 allocated
# Free: 123
# Statistics: 5 allocated, empty=0, full=0
# Freed resource 1
# Freed resource 3
# === Allocated Resources ===
#   Resource 0
#   Resource 2
#   Resource 4
# Total: 3/128 allocated
# Free: 125
# Statistics: 3 allocated, empty=0, full=0
💡

연습 과제: 위 모듈을 확장하여 다음 기능을 추가해보세요.

  1. 범위 할당: alloc_resource_range(int count)로 연속된 N개 자원 할당
  2. proc 인터페이스: /proc/resource_bitmap에서 비트맵 상태를 읽을 수 있도록 구현
  3. 성능 측정: ktime_get_ns()로 할당/해제 시간 측정

주의사항 (Common Pitfalls)

1. 비트 인덱스 범위 초과

/* 잘못된 코드 */
DECLARE_BITMAP(my_bitmap, 64);
set_bit(100, my_bitmap);  /* BUG: 범위 초과 → 메모리 손상 */

/* 올바른 코드 */
#define BITMAP_SIZE 64
DECLARE_BITMAP(my_bitmap, BITMAP_SIZE);
int bit = 100;
if (bit < BITMAP_SIZE)
    set_bit(bit, my_bitmap);

2. 원자성 오해

/* 잘못된 가정: test_bit()도 원자적일 것이다? */
if (!test_bit(id, bitmap))  /* ← 원자적이지만... */
    set_bit(id, bitmap);      /* ← 여기 사이에 다른 CPU가 개입 가능! */

/* 올바른 패턴: test_and_set_bit 사용 */
if (!test_and_set_bit(id, bitmap)) {
    /* 이 CPU가 비트를 설정했음을 보장 */
    pr_info("Successfully allocated %d\n", id);
}

3. BITS_PER_LONG 가정

/* 나쁜 코드: 64비트를 가정 */
unsigned long bitmap[2];  /* 128비트? 32비트에서는 64비트! */

/* 좋은 코드: BITS_TO_LONGS 사용 */
#define NBITS 128
unsigned long bitmap[BITS_TO_LONGS(NBITS)];  /* 아키텍처 독립 */

4. 초기화 누락

/* BUG: 초기화 없이 사용 */
unsigned long bitmap[4];
if (find_first_zero_bit(bitmap, 256))  /* 쓰레기 값! */

/* 올바른 코드 */
unsigned long bitmap[4];
bitmap_zero(bitmap, 256);
int bit = find_first_zero_bit(bitmap, 256);  /* OK: 0 반환 */

Bitmap 운영 플레이북

비트맵은 빠르지만 경계/원자성 실수에 취약합니다. 특히 멀티코어 환경에서는 test-then-set 패턴을 반드시 원자 연산으로 묶어야 합니다.

# 비트맵 관련 핵심 점검 패턴
git grep -n "test_bit(.*)\\s*.*set_bit" -- "*.c"
git grep -n "find_first_zero_bit" -- "*.c"
git grep -n "BITS_TO_LONGS\\|DECLARE_BITMAP" -- "*.c" "*.h"
실수영향대응
범위 초과 비트 접근메모리 손상상한 체크 + 정적 크기 매크로 사용
test/set 분리경합 시 중복 할당test_and_set_bit 사용
초기화 누락랜덤 상태bitmap_zero 또는 정적 초기화

cpumask/nodemask 레이아웃 심화

대규모 서버 시스템에서는 128개, 256개, 심지어 4096개 이상의 CPU를 관리해야 합니다. cpumask_t는 이런 환경에서도 효율적으로 CPU 집합을 표현하며, NUMA 시스템에서는 nodemask_t가 노드 단위 메모리 토폴로지를 비트맵으로 관리합니다.

ℹ️

규모 감각: 128-CPU 시스템에서 cpumask는 unsigned long[2](16바이트)만 사용합니다. 4096-CPU 시스템에서도 512바이트(8개 캐시 라인)에 불과합니다. 이는 배열 기반 구현(4096 × 4 = 16KB)과 비교하면 32배 절약입니다.

cpumask/nodemask 멀티코어 레이아웃 cpumask_t 레이아웃: 128-CPU 시스템 (NR_CPUS=128) bits[0] — CPU 0~63 (unsigned long #0) bit0=CPU0 bit1=CPU1 bit2=CPU2 ... bit62=CPU62 bit63=CPU63 bits[1] — CPU 64~127 (unsigned long #1) bit0=CPU64 bit1=CPU65 bit2=CPU66 ... bit62=CPU126 bit63=CPU127 cpumask_set_cpu(70, &mask) → bits[70/64] |= (1UL << 70%64) → bits[1] |= (1UL << 6) cpumask_test_cpu(3, &mask) → bits[0] & (1UL << 3) nodemask_t: NUMA 노드 비트맵 (MAX_NUMNODES=64) bits[0] — Node 0~63 (단일 unsigned long) Node0 Node1 Node2 Node3 ... (Node 4~63은 미사용) for_each_cpu() 내부 동작 Step 1: word = bits[0]을 로드 → __ffs(word)로 첫 설정 비트 찾기 Step 2: 찾은 비트 반환 → 콜백 실행 (예: 해당 CPU에 IPI 전송) Step 3: word에서 찾은 비트 제거 → word가 0이면 bits[1]로 이동 Step 4: 모든 word 소진 시 루프 종료 → 총 반복 = popcount(mask)회 성능: 128-CPU에서 10개 CPU만 활성 → word 2개 스캔 + __ffs 10회 ≈ 50 사이클
cpumask_t 128-CPU 레이아웃과 nodemask_t NUMA 노드 비트맵

cpumask 내부 구현

/* include/linux/cpumask.h - 핵심 구현 */

/* cpumask_t는 DECLARE_BITMAP의 래퍼 */
typedef struct cpumask {
    DECLARE_BITMAP(bits, NR_CPUS);
} cpumask_t;

/* cpumask_set_cpu: 특정 CPU 비트 설정 */
static inline void cpumask_set_cpu(unsigned int cpu,
                                     struct cpumask *dstp)
{
    set_bit(cpumask_check(cpu), cpumask_bits(dstp));
}

/* cpumask_test_cpu: 특정 CPU 비트 테스트 */
static inline int cpumask_test_cpu(int cpu,
                                    const struct cpumask *cpumask)
{
    return test_bit(cpumask_check(cpu), cpumask_bits(cpumask));
}

/* for_each_cpu: 매크로 확장 */
#define for_each_cpu(cpu, mask)                            \
    for ((cpu) = -1;                                      \
         (cpu) = cpumask_next((cpu), (mask)),              \
         (cpu) < nr_cpu_ids;)

/* cpumask_next: 다음 설정된 CPU 찾기 */
static inline unsigned int cpumask_next(int n,
                                        const struct cpumask *srcp)
{
    if (n != -1)
        cpumask_check(n);
    return find_next_bit(cpumask_bits(srcp), nr_cpu_ids, n + 1);
}

nodemask API

/* include/linux/nodemask.h - NUMA 노드 비트맵 */

typedef struct {
    DECLARE_BITMAP(bits, MAX_NUMNODES);
} nodemask_t;

/* 주요 API */
void node_set(int node, nodemask_t *dstp);
void node_clear(int node, nodemask_t *dstp);
int  node_isset(int node, nodemask_t src);

/* 노드 순회 */
int nid;
for_each_online_node(nid) {
    struct pglist_data *pgdat = NODE_DATA(nid);
    pr_info("Node %d: %lu pages\n", nid,
            pgdat->node_present_pages);
}

/* NUMA 친화적 메모리 할당 */
nodemask_t mask = node_to_cpumask_map[target_node];
struct page *page = alloc_pages_node(nid, GFP_KERNEL, 0);

cpumask와 nodemask의 관계

특성 cpumask_t nodemask_t
최대 크기 NR_CPUS (일반적으로 128~8192) MAX_NUMNODES (일반적으로 64~1024)
대표 전역 변수 cpu_online_mask node_online_map
순회 매크로 for_each_cpu() for_each_online_node()
변환 함수 cpumask_of_node(nid) — 특정 노드의 CPU 집합 반환
주요 용도 스케줄링, IRQ 어피니티 메모리 할당 정책, mbind/numactl

비트맵 탐색 알고리즘 심화

커널의 비트맵 탐색 함수는 단순한 비트 스캔이 아니라, word 단위 건너뛰기와 하드웨어 가속을 결합한 다층 최적화 알고리즘입니다. 이 섹션에서는 find_first_bit()부터 bitmap_find_next_zero_area()까지 내부 구현을 상세히 분석합니다.

bitmap_find_next_zero_area() 스캔 알고리즘 bitmap_find_next_zero_area() 스캔 과정 연속 4비트 빈 영역 탐색 예시 (size=32, align=1) 1 1 0 0 0 1 1 1 0 0 1 0 0 0 1 1 0 0 0 0 1 1 0 0 0 1 0 0 0 1 1 1 0 1 2 3 4 5 6 7 8 9 . . . . . . . . . . . . . . . . . . . . . . 10 12 14 16 18 20 22 24 26 28 30 Step 1: find_next_zero_bit(bmp, 32, 0) → bit 0은 1, bit 1은 1, bit 2는 0 → start = 2 Step 2: find_next_bit(bmp, 32, 2) — 다음 설정된 비트 검색 → bit 5 발견 → 연속 빈 영역 = [2,3,4] = 3비트 < 4 → 부족! Step 3: find_next_zero_bit(bmp, 32, 5+1=8) → bit 8은 0 → start = 8 Step 4: find_next_bit(bmp, 32, 8) — 다음 설정된 비트 → bit 10 발견 → 연속 빈 영역 = [8,9] = 2비트 < 4 → 부족! Step 5: find_next_zero_bit(bmp, 32, 10+1=11) → bit 11은 0 → start = 11 Step 6: find_next_bit(bmp, 32, 11) — 성공! → bit 14 발견 → 연속 빈 영역 = [11,12,13,14) = 3비트... 아직 부족 계속 탐색 → bit 16~19 연속 빈 영역 발견! → [16,17,18,19] = 4비트 연속 ≥ 4 → 반환값: 16 총 word 접근: 1회(32비트 ≤ 64비트), __ffs 호출: 6회
bitmap_find_next_zero_area() — 연속 빈 영역 탐색 알고리즘

find_first_bit / find_next_bit 구현

/* lib/find_bit.c - find_first_bit 구현 (간략화) */

unsigned long _find_first_bit(const unsigned long *addr,
                              unsigned long size)
{
    unsigned long idx;

    /* word 단위로 스캔 — 0이 아닌 word를 찾을 때까지 */
    for (idx = 0; idx * BITS_PER_LONG < size; idx++) {
        if (addr[idx]) {
            /* word 내에서 첫 번째 설정 비트 찾기 */
            unsigned long val = addr[idx];
            unsigned long bit = idx * BITS_PER_LONG + __ffs(val);
            return min(bit, size);
        }
    }
    return size;  /* 설정된 비트 없음 */
}

/* find_next_bit: offset부터 탐색 (부분 word 처리 포함) */
unsigned long _find_next_bit(const unsigned long *addr,
                             unsigned long size,
                             unsigned long offset)
{
    unsigned long tmp;

    if (offset >= size)
        return size;

    /* 첫 번째 부분 word: offset 이전 비트를 마스킹 */
    tmp = addr[offset / BITS_PER_LONG];
    tmp &= BITMAP_FIRST_WORD_MASK(offset);

    while (!tmp) {
        offset = round_up(offset + 1, BITS_PER_LONG);
        if (offset >= size)
            return size;
        tmp = addr[offset / BITS_PER_LONG];
    }

    return min(offset + __ffs(tmp), size);
}

하드웨어 가속: __ffs / __fls / ffz

/* arch/x86/include/asm/bitops.h */

/* __ffs: 최하위 1비트 위치 (BSF 명령어) */
static __always_inline unsigned long __ffs(unsigned long word)
{
    asm volatile("rep; bsf %1,%0"
        : "=r" (word)
        : "rm" (word));
    return word;
}

/* __fls: 최상위 1비트 위치 (BSR 명령어) */
static __always_inline unsigned long __fls(unsigned long word)
{
    asm("bsr %1,%0"
        : "=r" (word)
        : "rm" (word));
    return word;
}

/* ffz: 최하위 0비트 위치 = __ffs(~word) */
static inline unsigned long ffz(unsigned long word)
{
    return __ffs(~word);
}

/* ARM64: CLZ 기반 구현 */
/* arch/arm64/include/asm/bitops.h */
static inline unsigned long __ffs(unsigned long word)
{
    return __builtin_ctzl(word);  /* GCC builtin → CLZ+RBIT */
}

/* RISC-V: Zbb 확장 활용 */
/* arch/riscv/include/asm/bitops.h */
static inline unsigned long __ffs(unsigned long word)
{
    return __builtin_ctzl(word);  /* → ctz 명령어 */
}

bitmap_find_next_zero_area 구현

/* lib/bitmap.c - 연속된 빈 영역 탐색 */
unsigned long bitmap_find_next_zero_area_off(
    unsigned long *map,
    unsigned long size,
    unsigned long start,
    unsigned int  nr,       /* 필요한 연속 비트 수 */
    unsigned long align_mask,
    unsigned long align_offset)
{
    unsigned long index, end, i;

again:
    index = find_next_zero_bit(map, size, start);

    /* 정렬 조건 적용 */
    index = __ALIGN_MASK(index + align_offset, align_mask)
            - align_offset;

    end = index + nr;
    if (end > size)
        return end;  /* 공간 부족 */

    /* index~end 범위에 설정된 비트가 있는지 확인 */
    i = find_next_bit(map, end, index);
    if (i < end) {
        /* 중간에 설정된 비트 발견 → 그 뒤부터 재탐색 */
        start = i + 1;
        goto again;
    }

    return index;  /* 성공: 연속 nr비트 빈 영역 */
}
💡

bitmap_find_next_zero_area()는 CMA(Contiguous Memory Allocator), IRQ 번호 할당, GPIO 범위 할당 등에서 사용됩니다. align_mask 매개변수로 2의 거듭제곱 정렬을 보장할 수 있어 DMA 버퍼 할당에 유용합니다.

비트맵 메모리 레이아웃

비트맵은 unsigned long 배열로 구현됩니다. 아키텍처에 따라 BITS_PER_LONG이 32 또는 64이며, 이에 따라 메모리 레이아웃이 달라집니다. 이 차이를 이해하지 못하면 이식성 버그가 발생합니다.

비트맵 unsigned long 배열 메모리 레이아웃 DECLARE_BITMAP(bmp, 192) 메모리 레이아웃 64비트 시스템 (BITS_PER_LONG = 64) bmp[0] bit 0 ~ bit 63 bmp[1] bit 64 ~ bit 127 bmp[2] bit 128 ~ bit 191 BITS_TO_LONGS(192) = 3 → unsigned long[3] = 24바이트 32비트 시스템 (BITS_PER_LONG = 32) bmp[0] bit 0~31 bmp[1] bit 32~63 bmp[2] bit 64~95 ... bmp[3] bmp[4] bmp[5] BITS_TO_LONGS(192) = 6 → unsigned long[6] = 24바이트 비트 순서: Little-Endian Bit Ordering word 내부: bit 0 = LSB (최하위 비트), bit 63 = MSB (최상위 비트) word 순서: bmp[0]이 가장 낮은 비트 범위, bmp[N-1]이 가장 높은 비트 범위 비트 N 접근: bmp[N / BITS_PER_LONG] & (1UL << (N % BITS_PER_LONG)) DECLARE_BITMAP 매크로 확장 매크로 정의: #define DECLARE_BITMAP(name, bits) unsigned long name[BITS_TO_LONGS(bits)] BITS_TO_LONGS: #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_LONG) 확장 결과: DECLARE_BITMAP(bmp, 192) → unsigned long bmp[3] (64비트) DECLARE_BITMAP(bmp, 192) → unsigned long bmp[6] (32비트)
DECLARE_BITMAP(bmp, 192)의 메모리 레이아웃 — 64비트 vs 32비트

비트맵 레이아웃 관련 핵심 매크로

/* include/linux/bits.h */
#define BIT(nr)           (1UL << (nr))
#define BIT_ULL(nr)       (1ULL << (nr))
#define BIT_MASK(nr)      (1UL << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr)      ((nr) / BITS_PER_LONG)

/* include/linux/bitmap.h */
#define BITS_PER_BYTE     8
#define BITS_PER_TYPE(type) (sizeof(type) * BITS_PER_BYTE)
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_LONG)
#define BITS_TO_BYTES(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE)

/* 정적 비트맵 선언 */
#define DECLARE_BITMAP(name, bits) \
    unsigned long name[BITS_TO_LONGS(bits)]

/* 동적 비트맵 할당 */
unsigned long *bitmap = bitmap_zalloc(nbits, GFP_KERNEL);
if (!bitmap)
    return -ENOMEM;

/* 사용 후 해제 */
bitmap_free(bitmap);

/* 주의: 비트 수가 BITS_PER_LONG의 배수가 아닐 때 */
/* 마지막 word의 사용하지 않는 상위 비트는 0이어야 함 */
DECLARE_BITMAP(bmp, 100);  /* unsigned long bmp[2] */
/* bmp[1]의 bit 36~63은 항상 0으로 유지해야 함 */
/* bitmap_zero()는 이를 자동 처리 */
⚠️

이식성 주의: unsigned long 크기는 아키텍처마다 다릅니다. 직접 sizeof(unsigned long)을 가정하지 말고 항상 BITS_PER_LONG, BITS_TO_LONGS() 매크로를 사용하세요. 특히 32비트 ARM과 64비트 ARM64를 동시 지원하는 드라이버에서 흔한 버그 원인입니다.

원자적 비트 연산

커널은 동일한 비트 조작에 대해 원자적(atomic) 버전과 비원자적(non-atomic) 버전을 모두 제공합니다. 성능과 안전성 사이의 올바른 선택은 코드의 동시성 모델에 달려 있습니다.

원자적 vs 비원자적 비트 연산 비교 원자적 vs 비원자적 비트 연산 원자적 (Atomic) set_bit(nr, addr) clear_bit(nr, addr) change_bit(nr, addr) test_and_set_bit(nr, addr) test_and_clear_bit(nr, addr) x86: LOCK BTS/BTR/BTC ARM64: LDSET/LDCLR (LSE) 일반: LDXR/STXR 루프 비용: ~20-40 사이클 비원자적 (Non-atomic) __set_bit(nr, addr) __clear_bit(nr, addr) __change_bit(nr, addr) __test_and_set_bit(nr, addr) __test_and_clear_bit(nr, addr) x86: OR/AND (비잠금) ARM64: ORR/BIC (비잠금) 일반: 단순 read-modify-write 비용: ~1-3 사이클 (but 경합 위험) 경합 시나리오: CPU A와 CPU B가 동시에 같은 word 접근 set_bit() 사용 — 안전 CPU A: LOCK BTS bit3 CPU B: LOCK BTS bit7 → 결과: bit3=1, bit7=1 (둘 다 올바름) __set_bit() 사용 — 경합! CPU A: read word → OR bit3 → write CPU B: read word → OR bit7 → write → 결과: bit3 또는 bit7 중 하나 손실! 안전한 __set_bit() 사용 조건: 1. spinlock/mutex로 보호된 컨텍스트 2. 단일 CPU에서만 접근하는 per-CPU 데이터 3. 초기화 단계 (다른 CPU가 아직 활성화되지 않음) 4. 서로 다른 word에 접근하는 경우 (같은 word 내 다른 비트도 위험)
원자적 vs 비원자적 비트 연산 — 성능과 안전성 트레이드오프

x86 원자적 비트 연산 구현

/* arch/x86/include/asm/bitops.h */

/* set_bit: 원자적으로 비트 설정 */
static __always_inline void
arch_set_bit(long nr, volatile unsigned long *addr)
{
    if (__builtin_constant_p(nr)) {
        /* 컴파일 시점 상수: 최적화된 경로 */
        asm volatile(LOCK_PREFIX "orb %b1,%0"
            : CONST_MASK_ADDR(nr, addr)
            : "iq" (CONST_MASK(nr))
            : "memory");
    } else {
        /* 런타임 변수: BTS 명령어 */
        asm volatile(LOCK_PREFIX "bts %1,%0"
            : BITOP_ADDR(addr)
            : "Ir" (nr)
            : "memory");
    }
}

/* __set_bit: 비원자적 버전 — LOCK 접두사 없음 */
static __always_inline void
arch___set_bit(unsigned long nr, volatile unsigned long *addr)
{
    asm volatile("bts %1,%0"  /* LOCK 없음! */
        : BITOP_ADDR(addr)
        : "Ir" (nr)
        : "memory");
}

/* test_and_set_bit: 원자적 test-and-set */
static __always_inline bool
arch_test_and_set_bit(long nr, volatile unsigned long *addr)
{
    return GEN_BINARY_RMWcc(LOCK_PREFIX "bts",
                             *addr, "Ir", nr, "%0", c);
    /* Carry flag가 이전 비트 값을 나타냄 */
}

Lock-free 플래그 관리 패턴

/* 패턴 1: 원자적 플래그로 일회성 초기화 */
static unsigned long init_flags;
#define FLAG_INITIALIZED 0

void lazy_init(void)
{
    /* 여러 CPU가 동시에 호출해도 정확히 한 번만 초기화 */
    if (test_and_set_bit(FLAG_INITIALIZED, &init_flags))
        return;  /* 이미 다른 CPU가 초기화함 */

    /* 이 CPU만 여기에 도달 — 초기화 수행 */
    do_initialization();
}

/* 패턴 2: 비트 플래그로 상태 머신 구현 */
#define DEV_RUNNING    0
#define DEV_SUSPENDED  1
#define DEV_ERROR      2

struct my_device {
    unsigned long flags;
};

int device_suspend(struct my_device *dev)
{
    /* RUNNING → SUSPENDED 전환 (원자적) */
    if (!test_and_clear_bit(DEV_RUNNING, &dev->flags))
        return -EINVAL;  /* 실행 중이 아님 */

    set_bit(DEV_SUSPENDED, &dev->flags);
    return 0;
}

/* 패턴 3: wait_on_bit — 비트 기반 대기 */
#define PAGE_LOCKED  0

void lock_page(struct page *page)
{
    while (test_and_set_bit(PAGE_LOCKED, &page->flags))
        wait_on_bit(&page->flags, PAGE_LOCKED,
                    TASK_UNINTERRUPTIBLE);
}

void unlock_page(struct page *page)
{
    clear_bit_unlock(PAGE_LOCKED, &page->flags);
    wake_up_bit(&page->flags, PAGE_LOCKED);
}
ℹ️

메모리 순서(Memory Ordering): set_bit()은 full memory barrier를 포함합니다. 성능이 중요한 경우 set_bit() 대신 __set_bit() + 명시적 배리어를 사용할 수 있습니다. clear_bit_unlock()은 release 의미론을 제공하여 unlock 패턴에 최적화되어 있습니다.

IRQ 어피니티 비트맵

IRQ 어피니티(affinity)는 특정 인터럽트를 어떤 CPU에서 처리할지 결정하는 cpumask입니다. 네트워크 성능 최적화, NUMA 친화 I/O, 실시간 시스템 격리 등에서 핵심적인 역할을 합니다.

IRQ 어피니티 마스크와 CPU 분배 IRQ 어피니티 비트맵: NIC 큐별 CPU 분배 NIC (4 큐) RX Queue 0 (IRQ 30) RX Queue 1 (IRQ 31) RX Queue 2 (IRQ 32) RX Queue 3 (IRQ 33) smp_affinity 마스크 IRQ30: 0x01 = CPU 0 IRQ31: 0x02 = CPU 1 IRQ32: 0x04 = CPU 2 IRQ33: 0x08 = CPU 3 CPU 처리 분배 CPU 0: RX Q0 CPU 1: RX Q1 CPU 2: RX Q2 CPU 3: RX Q3 L1/L2 로컬 캐시 친화 무경합 병렬처리 IRQ 어피니티 제어 경로 사용자 공간: echo 0f > /proc/irq/30/smp_affinity VFS 계층: proc_write → irq_affinity_proc_write() 커널 IRQ: irq_set_affinity() → cpumask 파싱 → irq_chip->irq_set_affinity() 하드웨어: APIC/GIC 레지스터에 대상 CPU 기록 → 인터럽트 라우팅 변경 NUMA 최적화: irq_set_affinity_hint()로 IRQ 컨트롤러가 같은 NUMA 노드의 CPU를 선호하도록 힌트 제공
IRQ 어피니티 비트맵 — NIC 큐별 CPU 분배와 제어 경로

IRQ 어피니티 커널 API

/* kernel/irq/manage.c - IRQ 어피니티 설정 */

int irq_set_affinity(unsigned int irq,
                     const struct cpumask *mask)
{
    struct irq_desc *desc = irq_to_desc(irq);
    struct irq_chip *chip = desc->irq_data.chip;
    int ret;

    /* 온라인 CPU와 교집합 */
    struct cpumask *effective = irq_data_get_effective_affinity(
                                    &desc->irq_data);

    if (!cpumask_intersects(mask, cpu_online_mask))
        return -EINVAL;

    /* 하드웨어 칩에 어피니티 전달 */
    ret = chip->irq_set_affinity(&desc->irq_data, mask, false);

    if (ret == IRQ_SET_MASK_OK) {
        cpumask_copy(desc->irq_common_data.affinity, mask);
    }

    return ret;
}

/* 드라이버에서의 사용 예: MSI-X 큐별 어피니티 */
static void setup_queue_affinity(struct my_device *dev)
{
    int i;

    for (i = 0; i < dev->num_queues; i++) {
        struct cpumask mask;
        int target_cpu = i % num_online_cpus();

        cpumask_clear(&mask);
        cpumask_set_cpu(target_cpu, &mask);
        irq_set_affinity_hint(dev->irqs[i], &mask);
    }
}

/proc/irq 인터페이스 실습

# IRQ 목록과 현재 어피니티 확인
cat /proc/interrupts | head -20

# 특정 IRQ의 어피니티 확인 (비트마스크 형식)
cat /proc/irq/30/smp_affinity
# 출력 예: 0f (CPU 0-3 모두)

# CPU 리스트 형식으로 확인
cat /proc/irq/30/smp_affinity_list
# 출력 예: 0-3

# IRQ 30을 CPU 0에만 바인딩
echo 1 > /proc/irq/30/smp_affinity

# IRQ 31을 CPU 2,3에 바인딩
echo 0c > /proc/irq/31/smp_affinity

# 리스트 형식으로 설정 (더 직관적)
echo 2-3 > /proc/irq/31/smp_affinity_list

# NIC 큐별 분산 설정 스크립트
for irq in $(grep eth0 /proc/interrupts | awk '{print $1}' | tr -d ':'); do
    cpu=$((irq % $(nproc)))
    echo $cpu > /proc/irq/$irq/smp_affinity_list
    echo "IRQ $irq -> CPU $cpu"
done

스케줄러 비트맵 활용

리눅스 스케줄러는 비트맵을 활용하여 O(1) 시간에 가장 높은 우선순위의 실행 가능한 태스크를 찾습니다. 특히 RT(Real-Time) 스케줄러에서 비트맵은 핵심 자료구조입니다.

RT 스케줄러 우선순위 비트맵 RT 스케줄러 우선순위 비트맵 (rt_prio_array) bitmap[BITS_TO_LONGS(MAX_RT_PRIO)] — 100개 우선순위 비트 0 1 3 ... 10 ... (대부분 0) ... 90 ... 99 sched_find_first_bit() → 0 (최고 우선순위) queue[MAX_RT_PRIO] — 우선순위별 태스크 리스트 queue[0] (최고) task_A (SCHED_FIFO) task_B (SCHED_RR) bitmap bit 0 = 1 queue[3] task_C (SCHED_FIFO) bitmap bit 3 = 1 queue[10] task_D (SCHED_RR) task_E (SCHED_RR) bitmap bit 10 = 1 queue[99] (최저) (비어있음) bitmap bit 99 = 0 O(1) 스케줄링 동작 1. 태스크 선택: idx = sched_find_first_bit(bitmap) → __ffs(bitmap[0]) = 0 2. 큐 접근: next = list_first_entry(&queue[0]) → task_A 선택 3. 큐가 비면: task_A, task_B 모두 완료 → __clear_bit(0, bitmap) 4. 다음 선택: sched_find_first_bit(bitmap) → 3 (queue[3]의 task_C) 핵심: 태스크 수에 관계없이 항상 O(1) — 100비트 비트맵은 2개 unsigned long이므로 최대 2번 __ffs 호출
RT 스케줄러 우선순위 비트맵 — O(1) 태스크 선택

rt_prio_array 구조

/* kernel/sched/rt.c - RT 스케줄러 우선순위 배열 */

struct rt_prio_array {
    DECLARE_BITMAP(bitmap, MAX_RT_PRIO + 1);
    struct list_head queue[MAX_RT_PRIO];
};

/* 가장 높은 우선순위 태스크 찾기 */
static inline int sched_find_first_bit(const unsigned long *b)
{
    /* 100비트 비트맵에서 첫 번째 설정된 비트 찾기 */
    return find_first_bit(b, MAX_RT_PRIO);
}

/* RT 태스크 enqueue 시 비트맵 업데이트 */
static void enqueue_rt_entity(struct sched_rt_entity *rt_se)
{
    struct rt_prio_array *array = &rt_rq->active;
    int prio = rt_se->prio;

    list_add_tail(&rt_se->run_list, &array->queue[prio]);
    __set_bit(prio, array->bitmap);  /* 비원자적: rq lock 보호 */
}

/* RT 태스크 dequeue 시 비트맵 업데이트 */
static void dequeue_rt_entity(struct sched_rt_entity *rt_se)
{
    struct rt_prio_array *array = &rt_rq->active;
    int prio = rt_se->prio;

    list_del_init(&rt_se->run_list);

    /* 해당 우선순위 큐가 비면 비트 해제 */
    if (list_empty(&array->queue[prio]))
        __clear_bit(prio, array->bitmap);
}

CPU 선택 비트맵

/* kernel/sched/fair.c - CFS 로드 밸런싱에서의 비트맵 활용 */

/* select_task_rq_fair: 태스크를 실행할 CPU 선택 */
static int select_task_rq_fair(struct task_struct *p, int prev_cpu,
                              int wake_flags)
{
    struct cpumask *cpus = this_cpu_cpumask_var_ptr(select_rq_mask);
    int new_cpu;

    /* 태스크의 허용 CPU와 온라인 CPU의 교집합 */
    cpumask_and(cpus, p->cpus_ptr, cpu_active_mask);

    /* 같은 LLC(Last Level Cache)를 공유하는 CPU 선호 */
    struct cpumask *llc_mask = per_cpu(sd_llc_shared->span, prev_cpu);
    cpumask_and(cpus, cpus, llc_mask);

    /* idle CPU 우선 선택 */
    new_cpu = cpumask_any_and_distribute(cpus, idle_mask);
    if (new_cpu < nr_cpu_ids)
        return new_cpu;

    /* idle CPU가 없으면 가장 부하가 낮은 CPU */
    return find_idlest_cpu(cpus);
}

메모리 관리 비트맵

리눅스 메모리 관리 서브시스템은 비트맵을 광범위하게 사용합니다. Buddy allocator의 페이지 추적, CMA(Contiguous Memory Allocator)의 연속 영역 관리, 그리고 각 페이지의 플래그(PG_locked, PG_dirty 등)까지 모두 비트맵 기반입니다.

Buddy Allocator 프리 페이지 비트맵 Buddy Allocator: order별 프리 페이지 비트맵 Order 0 (4KB) ... 1=free pair, 0=both allocated or both free → buddy 쌍의 XOR 상태 추적 Order 1 (8KB) ... (페이지 수의 1/2) Order 10 (4MB) ... (페이지 수의 1/1024) CMA (Contiguous Memory Allocator) 비트맵 cma->bitmap: 페이지 단위 할당 추적 ... 빨강=할당됨 비어있음=free bitmap_find_next_zero_area()로 연속 빈 영역 탐색 struct page→flags: 페이지별 비트 플래그 unsigned long flags 비트 배치: bit 0: PG_locked — 페이지 잠금 (I/O 진행 중) bit 4: PG_dirty — 수정됨 (디스크 기록 필요) bit 5: PG_lru — LRU 리스트에 포함됨 bit 7: PG_slab — SLAB 할당자가 관리하는 페이지 bit 10: PG_reserved — 예약됨 (해제 불가) 접근: test_bit(PG_xxx, &page->flags) 설정: set_bit(PG_xxx, &page->flags) 래퍼: PageDirty(page), SetPageDirty(page) 원자적: test_and_set_bit(PG_locked, ...) → 매크로가 자동 생성 (PAGEFLAG 매크로)
메모리 관리 비트맵 — Buddy Allocator, CMA, 페이지 플래그

Buddy Allocator 비트맵

/* mm/page_alloc.c - 페이지 할당 시 비트맵 활용 */

/* buddy 상태 토글: 비트 XOR로 추적 */
static inline void __free_one_page(
    struct page *page,
    unsigned long pfn,
    struct zone *zone,
    unsigned int order)
{
    unsigned long buddy_pfn;
    struct page *buddy;

    while (order < MAX_ORDER - 1) {
        buddy_pfn = __find_buddy_pfn(pfn, order);
        buddy = page + (buddy_pfn - pfn);

        /* buddy 비트맵으로 양쪽 블록 상태 확인 */
        if (!page_is_buddy(page, buddy, order))
            break;

        /* 병합: 두 블록을 상위 order로 합침 */
        list_del(&buddy->lru);
        zone->free_area[order].nr_free--;

        /* 비트맵 업데이트 */
        __change_bit(pfn >> (order + 1),
                     zone->free_area[order].map);

        order++;
        pfn &= ~(1UL << order);  /* 정렬된 PFN */
    }
}

/* 페이지 할당: 빈 블록 탐색 */
static struct page *__rmqueue_smallest(
    struct zone *zone,
    unsigned int order,
    int migratetype)
{
    unsigned int current_order;
    struct free_area *area;
    struct page *page;

    for (current_order = order;
         current_order < MAX_ORDER; current_order++) {
        area = &zone->free_area[current_order];
        page = get_page_from_free_area(area, migratetype);
        if (!page)
            continue;

        del_page_from_free_list(page, zone, current_order);
        expand(zone, page, order, current_order, area);
        return page;
    }
    return NULL;
}

CMA 비트맵

/* mm/cma.c - CMA 연속 메모리 할당 */

struct cma {
    unsigned long  base_pfn;
    unsigned long  count;        /* 총 페이지 수 */
    unsigned long *bitmap;       /* 할당 추적 비트맵 */
    unsigned int   order_per_bit;
    struct mutex   lock;
};

/* CMA 영역에서 연속 페이지 할당 */
struct page *cma_alloc(struct cma *cma, size_t count,
                      unsigned int align, bool no_warn)
{
    unsigned long bitmap_count = cma_bitmap_pages_to_bits(cma, count);
    unsigned long start;

    mutex_lock(&cma->lock);

    /* 연속된 빈 비트 영역 탐색 */
    start = bitmap_find_next_zero_area_off(
        cma->bitmap,
        cma_bitmap_maxno(cma),
        0,              /* 탐색 시작 위치 */
        bitmap_count,   /* 필요한 비트 수 */
        mask,           /* 정렬 마스크 */
        offset);

    if (start >= cma_bitmap_maxno(cma)) {
        mutex_unlock(&cma->lock);
        return NULL;  /* 연속 공간 부족 */
    }

    /* 비트맵에 할당 표시 */
    bitmap_set(cma->bitmap, start, bitmap_count);
    mutex_unlock(&cma->lock);

    /* PFN → struct page 변환 */
    return pfn_to_page(cma->base_pfn + (start << cma->order_per_bit));
}

페이지 플래그 비트맵

/* include/linux/page-flags.h - 페이지 플래그 매크로 */

/* 플래그 정의 */
enum pageflags {
    PG_locked,        /* bit 0: 페이지 잠금 */
    PG_referenced,    /* bit 1: 최근 접근됨 */
    PG_uptodate,      /* bit 2: 최신 데이터 */
    PG_dirty,         /* bit 4: 수정됨 */
    PG_lru,           /* bit 5: LRU 리스트 */
    PG_active,        /* bit 6: 활성 리스트 */
    PG_slab,          /* bit 7: SLAB 페이지 */
    PG_reserved,      /* bit 10: 예약됨 */
    PG_compound,      /* bit 14: 복합 페이지 */
    /* ... */
};

/* PAGEFLAG 매크로: 접근 함수 자동 생성 */
#define PAGEFLAG(uname, lname, policy)              \
static __always_inline                             \
bool folio_test_##lname(struct folio *folio)     \
{ return test_bit(PG_##lname, folio_flags(folio)); } \
static __always_inline                             \
void folio_set_##lname(struct folio *folio)      \
{ set_bit(PG_##lname, folio_flags(folio)); }       \
static __always_inline                             \
void folio_clear_##lname(struct folio *folio)    \
{ clear_bit(PG_##lname, folio_flags(folio)); }

/* 사용 예 */
PAGEFLAG(Dirty, dirty, PF_HEAD)
/* → folio_test_dirty(), folio_set_dirty(), folio_clear_dirty() 생성 */

ftrace/perf 디버깅 실습

비트맵 관련 커널 동작을 추적하고 성능을 분석하는 실전 기법입니다. ftrace, bpftrace, perf를 활용하여 비트맵 연산의 호출 빈도와 지연 시간을 측정합니다.

ftrace로 비트맵 연산 추적

# ftrace: cpumask 관련 함수 추적
cd /sys/kernel/debug/tracing

# function tracer 활성화
echo function > current_tracer

# cpumask 관련 함수 필터
echo 'cpumask_*' > set_ftrace_filter
echo 'irq_set_affinity' >> set_ftrace_filter

# 추적 시작
echo 1 > tracing_on

# IRQ 어피니티 변경으로 트리거
echo 3 > /proc/irq/30/smp_affinity_list

# 추적 결과 확인
cat trace | head -30
# 예상 출력:
#  irq/30-xxx  [003] ... irq_set_affinity
#  irq/30-xxx  [003] ... cpumask_test_cpu
#  irq/30-xxx  [003] ... cpumask_set_cpu

# 추적 중지 및 정리
echo 0 > tracing_on
echo nop > current_tracer

bpftrace로 cpumask 모니터링

# bpftrace: set_bit 호출 빈도 모니터링
sudo bpftrace -e '
kprobe:irq_set_affinity {
    @irq_affinity_calls = count();
    @irq_nums = hist(arg0);
}

interval:s:10 {
    print(@irq_affinity_calls);
    print(@irq_nums);
}
'

# bpftrace: 비트맵 할당 추적 (CMA)
sudo bpftrace -e '
kprobe:cma_alloc {
    @cma_allocs = count();
    @alloc_sizes = hist(arg1);  /* 요청 페이지 수 */
    printf("CMA alloc: %d pages\n", arg1);
}

kretprobe:cma_alloc {
    @cma_results[retval != 0 ? "success" : "fail"] = count();
}
'

# bpftrace: RT 스케줄러 비트맵 활동
sudo bpftrace -e '
kprobe:enqueue_rt_entity {
    @rt_enqueue = count();
}
kprobe:dequeue_rt_entity {
    @rt_dequeue = count();
}
interval:s:5 {
    printf("RT enqueue: %lld, dequeue: %lld\n",
           @rt_enqueue, @rt_dequeue);
}
'

perf probe 예제

# perf: find_first_bit 성능 프로파일링
sudo perf probe --add 'find_first_bit addr size'
sudo perf record -e probe:find_first_bit -a -- sleep 5
sudo perf report

# perf stat: 비트맵 관련 캐시 성능
sudo perf stat -e cache-misses,cache-references,instructions \
    -p $(pgrep my_bitmap_module) -- sleep 10

# perf: IRQ 처리 지연 시간
sudo perf trace -e irq:irq_handler_entry,irq:irq_handler_exit \
    --duration 100 -- sleep 5

# probe 정리
sudo perf probe --del find_first_bit
💡

디버깅 팁: CONFIG_DEBUG_PER_CPU_MAPS=y를 활성화하면 cpumask 범위 초과 접근을 런타임에 감지합니다. 또한 /proc/softirqs, /proc/interrupts로 IRQ 분배 상태를 간단히 확인할 수 있습니다.

성능 분석과 최적화

비트맵은 범용적으로 빠르지만, 사용 패턴에 따라 성능 차이가 크게 납니다. 이 섹션에서는 비트맵, 비트필드, 플래그 변수의 비교와 대규모 비트맵 최적화 기법을 다룹니다.

비트맵 성능 최적화 전략 비트맵 성능 최적화 전략 캐시 라인 정렬 64바이트 캐시 라인 = 512비트 → 512개 상태를 1회 메모리 접근으로 로드 캐시 라인 1: bit 0~511 캐시 라인 2: bit 512~1023 최적화: ____cacheline_aligned 속성 사용 hot 비트를 같은 캐시 라인에 배치 per-CPU 비트맵으로 false sharing 방지 벡터화/SIMD 최적화 대규모 비트맵 연산 (bitmap_and 등) → 컴파일러 자동 벡터화 활용 일반 루프: for (i=0; i<n; i++) dst[i] = s1[i] & s2[i]; GCC -O2 최적화: → SSE2/AVX2: 128/256비트 단위 → 4096비트 AND: 16회 → 2회 연산 자료구조 비교: 언제 비트맵을 선택할 것인가? 기준 비트맵 비트필드 플래그 변수 크기 가변 (n비트) 고정 (구조체) unsigned long 1개 원자성 set_bit() 가능 불가능 set_bit() 가능 배치 연산 bitmap_and() 등 불가능 불가능 검색 find_first_bit() 수동 검사 __ffs(flags) 이식성 우수 컴파일러 의존 우수 적합 용도 CPU/IRQ/페이지 집합 HW 레지스터 상태 머신 (<64개)
비트맵 성능 최적화 — 캐시 정렬, SIMD, 자료구조 비교

상세 성능 비교

연산 비트맵 (1024비트) bool 배열 (1024개) 해시맵 (1024 엔트리)
메모리 사용 128바이트 1024바이트 ~16KB
단일 비트 설정 ~5 ns (원자적) ~3 ns ~50 ns
첫 번째 설정 원소 찾기 ~12 ns (__ffs) ~850 ns (선형) ~200 ns
두 집합 교집합 ~22 ns (bitmap_and) ~1.2 μs ~5 μs
전체 순회 (10% 활성) ~180 ns ~980 ns ~400 ns
캐시 라인 사용 2개 16개 ~250개

캐시 최적화 코드 패턴

/* 캐시 라인 정렬된 비트맵 */
struct optimized_bitmap {
    DECLARE_BITMAP(hot_bits, 512);    /* 자주 접근: 1 캐시 라인 */
    DECLARE_BITMAP(cold_bits, 4096);  /* 드물게 접근 */
} ____cacheline_aligned;

/* per-CPU 비트맵: false sharing 완전 방지 */
struct per_cpu_bitmap {
    DECLARE_BITMAP(local, 256);
    unsigned long stats[4];
} ____cacheline_aligned_in_smp;

static DEFINE_PER_CPU(struct per_cpu_bitmap, cpu_bitmaps);

/* 벌크 연산으로 lock 횟수 최소화 */
void batch_allocate(int *ids, int count)
{
    unsigned long flags;
    int i;

    spin_lock_irqsave(&lock, flags);
    for (i = 0; i < count; i++) {
        ids[i] = find_first_zero_bit(bitmap, MAX);
        if (ids[i] < MAX)
            __set_bit(ids[i], bitmap);  /* lock 내부: 비원자적 OK */
    }
    spin_unlock_irqrestore(&lock, flags);
}

/* prefetch 활용 (대규모 비트맵 순회) */
void scan_large_bitmap(unsigned long *bitmap, int nbits)
{
    unsigned long bit;
    int word_idx = 0;

    for_each_set_bit(bit, bitmap, nbits) {
        int new_word = bit / BITS_PER_LONG;
        if (new_word != word_idx) {
            word_idx = new_word;
            /* 다음 캐시 라인 미리 로드 */
            prefetch(&bitmap[word_idx + 8]);
        }
        process_bit(bit);
    }
}

비트맵 활용 패턴 모음

커널 전반에서 반복적으로 사용되는 비트맵 패턴을 정리합니다. 이 패턴들을 이해하면 새로운 서브시스템을 분석할 때 비트맵 활용 방식을 빠르게 파악할 수 있습니다.

비트맵 활용 패턴 결정 플로차트 비트맵 활용 결정 플로차트 자원/상태 관리 필요 원소 수 > 64? (관리할 상태 개수) No unsigned long flags Yes 집합 연산 필요? (AND/OR/순회) No 해시/rbtree 고려 Yes CPU 집합? (SMP 관련) Yes cpumask_t 사용 No 동적 크기? Yes bitmap_zalloc() No DECLARE_BITMAP()
비트맵 활용 결정 플로차트 — 적합한 자료구조 선택

커널 서브시스템별 비트맵 패턴

서브시스템 비트맵 용도 핵심 API 크기
스케줄러 RT 우선순위 큐 활성 추적 sched_find_first_bit() 100비트
메모리 관리 Buddy allocator 빈 페이지 find_first_zero_bit() zone 크기 의존
CMA 연속 메모리 영역 추적 bitmap_find_next_zero_area() CMA 영역 크기
IRQ IRQ 번호 할당 find_next_zero_bit() IRQ_BITMAP_BITS
GPIO GPIO 핀 할당 추적 bitmap_find_next_zero_area() 칩별 GPIO 수
블록 I/O ext4 블록 할당 비트맵 find_next_zero_bit() 블록 그룹 크기
네트워크 포트 번호 할당 find_next_zero_bit() 65536비트
IPC IPC ID 할당 (semaphore, shm) find_first_zero_bit() IPCMNI
IOMMU IOVA 할당 추적 bitmap_find_next_zero_area() DMA 주소 범위
PID PID 번호 할당 find_next_zero_bit() PID_MAX_LIMIT

흔한 안티패턴과 해결책

안티패턴 문제 올바른 패턴
for (i=0; i<n; i++) if (test_bit(i, bmp)) O(n) 순회, 빈 word도 검사 for_each_set_bit(i, bmp, n)
if (!test_bit(x, b)) set_bit(x, b); test와 set 사이 경합 test_and_set_bit(x, b)
unsigned long bmp[4]; (직접 크기 지정) 32/64비트 이식성 버그 DECLARE_BITMAP(bmp, 256)
memset(bmp, 0xff, sizeof(bmp)) 마지막 word 잔여 비트 문제 bitmap_fill(bmp, nbits)
큰 비트맵을 스택에 선언 스택 오버플로 (커널 스택 8~16KB) bitmap_zalloc(n, GFP_KERNEL)
__set_bit()을 인터럽트 컨텍스트에서 사용 공유 데이터 경합 set_bit() 또는 lock 사용

비트맵 코드 리뷰 체크리스트

/*
 * 비트맵 코드 리뷰 시 확인할 사항:
 *
 * 1. 초기화: bitmap_zero() 또는 DEFINE_BITMAP()으로 초기화했는가?
 * 2. 범위 검사: set_bit(nr, bmp)에서 nr < 비트맵_크기인가?
 * 3. 원자성: 멀티코어 접근 시 set_bit() vs __set_bit() 올바른가?
 * 4. 이식성: DECLARE_BITMAP() 사용했는가? (unsigned long[] 직접 X)
 * 5. 순회: for_each_set_bit() 사용했는가? (수동 루프 X)
 * 6. 해제: bitmap_zalloc()이면 bitmap_free() 짝 맞는가?
 * 7. 캐시: 대규모 비트맵은 cacheline_aligned인가?
 * 8. 스택: 큰 비트맵을 스택에 두지 않았는가?
 */

/* 올바른 비트맵 관리 템플릿 */
struct my_subsystem {
    DECLARE_BITMAP(resource_map, MAX_RESOURCES);
    DEFINE_SPINLOCK(lock);
    int max_resources;
};

static int my_alloc(struct my_subsystem *sys)
{
    unsigned long flags;
    int id;

    spin_lock_irqsave(&sys->lock, flags);
    id = find_first_zero_bit(sys->resource_map, sys->max_resources);
    if (id < sys->max_resources)
        __set_bit(id, sys->resource_map);
    spin_unlock_irqrestore(&sys->lock, flags);

    return (id < sys->max_resources) ? id : -ENOSPC;
}

static void my_free(struct my_subsystem *sys, int id)
{
    if (WARN_ON(id < 0 || id >= sys->max_resources))
        return;
    clear_bit(id, sys->resource_map);  /* 원자적: lock 없이 안전 */
}
💡

실전 요약: 비트맵은 "작은 정수 ID 집합"을 관리하는 데 최적입니다. CPU 번호, IRQ 번호, PID, GPIO 핀, 블록 번호 등 연속된 정수 식별자의 할당/해제/순회에 사용하세요. 원소가 수천 개 이하이고 집합 연산(교집합, 합집합)이 필요하다면 비트맵이 거의 항상 최선의 선택입니다.

Bitmap과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.