๏ปฟ# Readahead (์„ ํ–‰ ์ฝ๊ธฐ)

๊ด€๋ จ ์†Œ์Šค: mm/readahead.c, include/linux/pagemap.h, include/linux/fs.h, include/linux/backing-dev-defs.h

๊ฐœ์š” (Overview)

Readahead(์„ ํ–‰ ์ฝ๊ธฐ)๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ๋ช…์‹œ์ ์œผ๋กœ ์š”์ฒญํ•˜๊ธฐ ์ „์— ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ page cache์— ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด๋‹ค. readahead.c๋Š” address_space ์ˆ˜์ค€์—์„œ ๋™์ž‘ํ•˜๋ฉฐ, ๋™๊ธฐ ์ฝ๊ธฐ์™€ ๋น„๋™๊ธฐ readahead๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ๋””์Šคํฌ I/O์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฒ˜๋ฆฌ๋ฅผ ์˜ค๋ฒ„๋žฉ์‹œํ‚จ๋‹ค. ํ•ต์‹ฌ ์•„์ด๋””์–ด๋Š” "readahead pipelining"์œผ๋กœ, ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ํ˜„์žฌ readahead ์œˆ๋„์šฐ์˜ ๋งˆ์ง€๋ง‰ async_size ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•˜๋ฉด ๋‹ค์Œ readahead๋ฅผ ๋น„๋™๊ธฐ๋กœ ์‹œ์ž‘ํ•˜์—ฌ ๋””์Šคํฌ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์ˆจ๊ธฐ๋Š” ๊ฒƒ์ด๋‹ค. ๋„์„œ๊ด€์—์„œ ๋‹ค์Œ ์†๋‹˜์ด ์ฝ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์€ ์ฑ…์„ ๋ฏธ๋ฆฌ ์นดํŠธ์— ๊บผ๋‚ด ๋‘๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜๋‹ค.

Linux 7.0์—์„œ๋Š” large folio ์ง€์›์ด ๊ฐ•ํ™”๋˜์–ด page_cache_ra_order() ๊ฒฝ๋กœ์—์„œ order > 0์ธ large folio๋ฅผ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ra->order ํ•„๋“œ๋ฅผ ํ†ตํ•ด preferred folio order๊ฐ€ ๊ด€๋ฆฌ๋œ๋‹ค. readahead๋Š” ์„ธ ๊ฐ€์ง€ ํŠธ๋ฆฌ๊ฑฐ๋กœ ๋ฐœ๋™๋œ๋‹ค: (1) cache miss ์‹œ ๋™๊ธฐ readahead (page_cache_sync_ra), (2) PG_readahead ํ”Œ๋ž˜๊ทธ๊ฐ€ ์„ค์ •๋œ folio ์ ‘๊ทผ ์‹œ ๋น„๋™๊ธฐ readahead (page_cache_async_ra), (3) readahead() ์‹œ์Šคํ…œ ํ˜ธ์ถœ ๋ฐ posix_fadvise(POSIX_FADV_WILLNEED).

ํ•ต์‹ฌ ์†Œ์Šค ํŒŒ์ผ:

  • mm/readahead.c โ€” readahead ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ „์ฒด ๊ตฌํ˜„ (841์ค„)
  • include/linux/pagemap.h โ€” struct readahead_control, DEFINE_READAHEAD, readahead_folio() ๋“ฑ accessor
  • include/linux/fs.h โ€” struct file_ra_state (readahead ์œˆ๋„์šฐ ์ƒํƒœ ์ถ”์ )
  • include/linux/backing-dev-defs.h โ€” struct backing_dev_info (ra_pages, io_pages)
  • include/trace/events/readahead.h โ€” tracepoint 4์ข…

  • ๋น ๋ฅธ ์ ๊ฒ€ ๋ช…๋ น

    # readahead ๊ด€๋ จ ์ปค๋„ ์‹ฌ๋ณผ ํ™•์ธ
    cat /proc/kallsyms | grep -E 'page_cache_sync_ra|page_cache_async_ra|page_cache_ra_unbounded|page_cache_ra_order|readahead_expand'
    
    # readahead tracepoint ํ™œ์„ฑํ™”
    echo 1 > /sys/kernel/debug/tracing/events/readahead/enable
    cat /sys/kernel/debug/tracing/trace_pipe | head -20
    
    # readahead tracepoint๋ฅผ ํ•ญ๋ชฉ๋ณ„๋กœ ํ™•์ธ
    echo 1 > /sys/kernel/debug/tracing/events/readahead/page_cache_sync_ra/enable
    echo 1 > /sys/kernel/debug/tracing/events/readahead/page_cache_async_ra/enable
    echo 1 > /sys/kernel/debug/tracing/events/readahead/page_cache_ra_order/enable
    echo 1 > /sys/kernel/debug/tracing/events/readahead/page_cache_ra_unbounded/enable
    cat /sys/kernel/debug/tracing/trace_pipe
    
    # ํŠน์ • BDI์˜ readahead ์œˆ๋„์šฐ ํฌ๊ธฐ ํ™•์ธ (ra_pages)
    cat /sys/block/sda/queue/read_ahead_kb
    
    # ํŒŒ์ผ ์‹œ์Šคํ…œ๋ณ„ readahead ์„ค์ • ํ™•์ธ
    cat /sys/block/<dev>/queue/read_ahead_kb
    
    # readahead ๊ด€๋ จ procfs ํ†ต๊ณ„
    cat /proc/diskstats | grep -E 'read_ahead|ra_'
    
    # readahead ์‹œ์Šคํ…œ ํ˜ธ์ถœ ์ถ”์ 
    echo 1 > /sys/kernel/debug/tracing/events/syscalls/sys_enter_readahead/enable
    cat /sys/kernel/debug/tracing/trace_pipe
    
    # ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ์‹คํŒจ ์‹œ readahead ์ค‘๋‹จ ํ™•์ธ
    dmesg | grep -i 'readahead\|oom'
    
    # ํŽ˜์ด์ง€ ์บ์‹œ ์ƒํƒœ ํ™•์ธ
    cat /proc/meminfo | grep -E 'Cached|Buffers|Active'
    
    # ํŒŒ์ผ๋ณ„ readahead ๋™์ž‘ ๊ด€์ฐฐ (strace)
    strace -e trace=readahead,read,pread64 -p <PID>

    ํ•ต์‹ฌ ์ž๋ฃŒ๊ตฌ์กฐ

    file_ra_state โ€” ํŒŒ์ผ๋ณ„ readahead ์œˆ๋„์šฐ ์ƒํƒœ

    // include/linux/fs.h:1212-1220
    struct file_ra_state {
    	pgoff_t start;              // ๊ฐ€์žฅ ์ตœ๊ทผ readahead ์‹œ์ž‘ ํŽ˜์ด์ง€ ์ธ๋ฑ์Šค
    	unsigned int size;          // ๊ฐ€์žฅ ์ตœ๊ทผ readahead์—์„œ ์ฝ์€ ์ด ํŽ˜์ด์ง€ ์ˆ˜
    	unsigned int async_size;    // ๋น„๋™๊ธฐ ์˜์—ญ ํฌ๊ธฐ (์ด ์ˆ˜๋งŒํผ ๋‚จ์•˜์„ ๋•Œ ๋‹ค์Œ readahead ํŠธ๋ฆฌ๊ฑฐ)
    	unsigned int ra_pages;      // ์ตœ๋Œ€ readahead ์œˆ๋„์šฐ ํฌ๊ธฐ (bdi->ra_pages์—์„œ ๋ณต์‚ฌ)
    	unsigned short order;       // large folio order (0์ด๋ฉด ์ผ๋ฐ˜ 4KB ํŽ˜์ด์ง€)
    	unsigned short mmap_miss;   // mmap ์ ‘๊ทผ ์‹œ page cache miss ํšŸ์ˆ˜
    	loff_t prev_pos;           // ๊ฐ€์žฅ ์ตœ๊ทผ ์ฝ๊ธฐ ์š”์ฒญ์˜ ๋งˆ์ง€๋ง‰ ๋ฐ”์ดํŠธ ์œ„์น˜
    };
  • async_size: readahead ์œˆ๋„์šฐ ๋‚ด์—์„œ "๋ฏธ๋ฆฌ ์ฝ์€" ์˜์—ญ์˜ ํฌ๊ธฐ. ์ฒซ ๋ฒˆ์งธ async ํŽ˜์ด์ง€์— PG_readahead ํ”Œ๋ž˜๊ทธ๊ฐ€ ์„ค์ •๋จ
  • ra_pages: backing_dev_info.ra_pages์—์„œ ๋ณต์‚ฌ. ์ผ๋ฐ˜์ ์œผ๋กœ read_ahead_kb / 4 (์˜ˆ: 128KB โ†’ 32ํŽ˜์ด์ง€)
  • order: Linux 7.0์˜ large folio ์ง€์›์œผ๋กœ page_cache_ra_order()์—์„œ ์‚ฌ์šฉ
  • readahead_control โ€” readahead ์š”์ฒญ ์ปจํ…์ŠคํŠธ

    // include/linux/pagemap.h:1347-1358
    struct readahead_control {
    	struct file *file;                  // ๋Œ€์ƒ ํŒŒ์ผ (๋„คํŠธ์›Œํฌ FS์—์„œ ์ธ์ฆ์šฉ, ๋‚ด๋ถ€ ํ˜ธ์ถœ ์‹œ NULL)
    	struct address_space *mapping;      // readahead ๋Œ€์ƒ address_space
    	struct file_ra_state *ra;           // ํŒŒ์ผ๋ณ„ readahead ์ƒํƒœ (์„ ํƒ์ )
    /* private: readahead_* accessor ์‚ฌ์šฉ */
    	pgoff_t _index;                     // ํ˜„์žฌ readahead ์‹œ์ž‘ ํŽ˜์ด์ง€ ์ธ๋ฑ์Šค
    	unsigned int _nr_pages;             // ๋‚จ์€ readahead ํŽ˜์ด์ง€ ์ˆ˜
    	unsigned int _batch_count;          // ํ˜„์žฌ ๋ฐฐ์น˜์—์„œ ์ฒ˜๋ฆฌ๋œ ํŽ˜์ด์ง€ ์ˆ˜
    	bool dropbehind;                    // dropbehind ํ”Œ๋ž˜๊ทธ (ํŒŒ์ผ ๋์—์„œ ์ฝ์„ ๋•Œ ๊ณผ๊ฑฐ ํŽ˜์ด์ง€ ํ•ด์ œ)
    	bool _workingset;                   // workingset ํ”Œ๋ž˜๊ทธ ๊ฐ์ง€ ์—ฌ๋ถ€
    	unsigned long _pflags;              // PSI ๋ฉ”๋ชจ๋ฆฌ ์Šคํ†จ ํ”Œ๋ž˜๊ทธ
    };

    backing_dev_info โ€” BDI readahead ์„ค์ •

    // include/linux/backing-dev-defs.h:168-175
    struct backing_dev_info {
    	u64 id;
    	/* ... */
    	unsigned long __data_racy ra_pages; /* ์ตœ๋Œ€ readahead ํฌ๊ธฐ (ํŽ˜์ด์ง€ ๋‹จ์œ„) */
    	unsigned long io_pages;             /* ํ—ˆ์šฉ ์ตœ๋Œ€ IO ํฌ๊ธฐ */
    	/* ... */
    };
    readahead ์ž๋ฃŒ๊ตฌ์กฐ ๊ด€๊ณ„๋„

    ํ•ต์‹ฌ ํ•จ์ˆ˜

    page_cache_sync_ra() โ€” ๋™๊ธฐ readahead ์ง„์ž…์ 

    // mm/readahead.c:557-631
    void page_cache_sync_ra(struct readahead_control *ractl,
    		unsigned long req_count)
    {
    	pgoff_t index = readahead_index(ractl);
    	bool do_forced_ra = ractl->file && (ractl->file->f_mode & FMODE_RANDOM);
    	struct file_ra_state *ra = ractl->ra;
    	unsigned long max_pages, contig_count;
    	pgoff_t prev_index, miss;
    
    	// readahead ๋น„ํ™œ์„ฑํ™” ๋˜๋Š” ๋ธ”๋ก cgroup ํ˜ผ์žก ์‹œ ๊ฐ•์ œ ์ฝ๊ธฐ๋กœ ํด๋ฐฑ
    	if (!ra->ra_pages || blk_cgroup_congested()) {
    		if (!ractl->file) return;
    		req_count = 1; do_forced_ra = true;
    	}
    
    	// FMODE_RANDOM ํŒŒ์ผ (์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)์€ force_page_cache_ra๋กœ ์ฒ˜๋ฆฌ
    	if (do_forced_ra) {
    		force_page_cache_ra(ractl, req_count);
    		return;
    	}
    
    	max_pages = ractl_max_pages(ractl, req_count);
    	prev_index = (unsigned long long)ra->prev_pos >> PAGE_SHIFT;
    
    	// ์บ์‹œ ๋ฏธ์Šค ๋˜๋Š” ์ˆœ์ฐจ ์ฝ๊ธฐ ๊ฐ์ง€: (index - prev_index) <= 1
    	if (!index || req_count > max_pages || index - prev_index <= 1UL) {
    		ra->start = index;
    		ra->size = get_init_ra_size(req_count, max_pages);
    		ra->async_size = ra->size > req_count ?
    			ra->size - req_count : ra->size >> 1;
    		goto readit;
    	}
    
    	// page cache์—์„œ ์ด์ „ ์บ์‹œ ์ด๋ ฅ ๊ฒ€์ƒ‰
    	rcu_read_lock();
    	miss = page_cache_prev_miss(ractl->mapping, index - 1, max_pages);
    	rcu_read_unlock();
    	contig_count = index - miss - 1;
    
    	// ๋…๋ฆฝ์  ์ž‘์€ ๋žœ๋ค ์ฝ๊ธฐ: readahead ์ƒํƒœ ์˜ค์—ผ ๋ฐฉ์ง€
    	if (contig_count <= req_count) {
    		do_page_cache_ra(ractl, req_count, 0);
    		return;
    	}
    
    	// ํŒŒ์ผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์บ์‹œ๋œ ๊ฒฝ์šฐ: ๊ฐ•ํ•œ ์ˆœ์ฐจ ์ŠคํŠธ๋ฆผ ์ง€์‹œ
    	if (miss == ULONG_MAX) contig_count *= 2;
    	ra->start = index;
    	ra->size = min(contig_count + req_count, max_pages);
    	ra->async_size = 1;
    readit:
    	ra->order = 0;
    	ractl->_index = ra->start;
    	page_cache_ra_order(ractl, ra);
    }

    ์—ญํ• : cache miss ์‹œ readahead ์œˆ๋„์šฐ๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  large folio ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด I/O๋ฅผ ์‹œ์ž‘ํ•œ๋‹ค.

    ๋ถ„๊ธฐ ๋กœ์ง:

  • ra->ra_pages == 0 ๋˜๋Š” blk_cgroup_congested() โ†’ req_count = 1๋กœ ๊ฐ•์ œ ์ฝ๊ธฐ
  • FMODE_RANDOM โ†’ force_page_cache_ra()๋กœ 2MB ์ฒญํฌ ๋‹จ์œ„ ์ฝ๊ธฐ
  • index - prev_index <= 1 โ†’ ์ˆœ์ฐจ ์ฝ๊ธฐ โ†’ get_init_ra_size()๋กœ ์ดˆ๊ธฐ ์œˆ๋„์šฐ
  • contig_count <= req_count โ†’ ๋žœ๋ค ์ฝ๊ธฐ โ†’ readahead ์ƒํƒœ ๋ณ€๊ฒฝ ์—†์ด ์ฝ๊ธฐ
  • miss == ULONG_MAX โ†’ ํŒŒ์ผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์บ์‹œ โ†’ contig_count *= 2
  • page_cache_async_ra() โ€” ๋น„๋™๊ธฐ readahead ํŠธ๋ฆฌ๊ฑฐ

    // mm/readahead.c:633-702
    void page_cache_async_ra(struct readahead_control *ractl,
    		struct folio *folio, unsigned long req_count)
    {
    	unsigned long max_pages;
    	struct file_ra_state *ra = ractl->ra;
    	pgoff_t index = readahead_index(ractl);
    	pgoff_t expected, start, end, aligned_end, align;
    
    	if (!ra->ra_pages) return;
    
    	// PG_readahead์™€ PG_reclaim ๋น„ํŠธ ๊ณต์œ  โ†’ writeback ์ค‘์ด๋ฉด ๊ฑด๋„ˆ๋œ€
    	if (folio_test_writeback(folio)) return;
    
    	folio_clear_readahead(folio);
    
    	if (blk_cgroup_congested()) return;
    
    	max_pages = ractl_max_pages(ractl, req_count);
    
    	// ์ˆœ์ฐจ ์ ‘๊ทผ ์˜ˆ์ƒ ์œ„์น˜ ํ™•์ธ
    	expected = round_down(ra->start + ra->size - ra->async_size,
    			folio_nr_pages(folio));
    	if (index == expected) {
    		// ์ˆœ์ฐจ ํžˆํŠธ: ์œˆ๋„์šฐ๋ฅผ ์•ž์œผ๋กœ ๋ฐ€๊ณ  ํฌ๊ธฐ ์ฆ๊ฐ€
    		ra->start += ra->size;
    		ra->size = max(ra->size, get_next_ra_size(ra, max_pages));
    		goto readit;
    	}
    
    	// ๋น„์ˆœ์ฐจ ํžˆํŠธ (interleaved reads ๋“ฑ): page cache์—์„œ async_size ์ถ”์ •
    	rcu_read_lock();
    	start = page_cache_next_miss(ractl->mapping, index + 1, max_pages);
    	rcu_read_unlock();
    
    	if (!start || start - index > max_pages) return;
    
    	ra->start = start;
    	ra->size = start - index;
    	ra->size += req_count;
    	ra->size = get_next_ra_size(ra, max_pages);
    readit:
    	ra->order += 2;
    	align = 1UL << min(ra->order, ffs(max_pages) - 1);
    	end = ra->start + ra->size;
    	aligned_end = round_down(end, align);
    	if (aligned_end > ra->start)
    		ra->size -= end - aligned_end;
    	ra->async_size = ra->size;
    	ractl->_index = ra->start;
    	page_cache_ra_order(ractl, ra);
    }

    ์—ญํ• : PG_readahead ํ”Œ๋ž˜๊ทธ๊ฐ€ ์„ค์ •๋œ folio์— ์ ‘๊ทผํ–ˆ์„ ๋•Œ ๋‹ค์Œ readahead ์œˆ๋„์šฐ๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ์‹œ์ž‘ํ•œ๋‹ค.

    ๋ถ„๊ธฐ ๋กœ์ง:

  • folio_test_writeback(folio) โ†’ PG_reclaim ๋น„ํŠธ์™€ ๋™์ผ โ†’ writeback ์ค‘์ด๋ฉด ์Šคํ‚ต
  • index == expected (์ˆœ์ฐจ ํžˆํŠธ) โ†’ ์œˆ๋„์šฐ ํฌ๊ธฐ ์ฆ๊ฐ€ (get_next_ra_size)
  • index != expected (๋น„์ˆœ์ฐจ ํžˆํŠธ) โ†’ page cache์—์„œ ์ด์ „ ๋น„์—ฐ์† ๊ตฌ๊ฐ„ ๊ฒ€์ƒ‰ ํ›„ ์œˆ๋„์šฐ ์žฌ์„ค์ •
  • page_cache_ra_unbounded() โ€” ๊ธฐ๋ณธ readahead ๋ฃจํ”„

    // mm/readahead.c:211-307
    void page_cache_ra_unbounded(struct readahead_control *ractl,
    		unsigned long nr_to_read, unsigned long lookahead_size)
    {
    	struct address_space *mapping = ractl->mapping;
    	unsigned long index = readahead_index(ractl);
    	gfp_t gfp_mask = readahead_gfp_mask(mapping);
    	unsigned long mark = ULONG_MAX, i = 0;
    	unsigned int min_nrpages = mapping_min_folio_nrpages(mapping);
    	unsigned int nofs = memalloc_nofs_save();
    
    	index = mapping_align_index(mapping, index);
    
    	// lookahead ํฌ๊ธฐ ๊ฒฐ์ •: lookahead ์˜์—ญ ์‹œ์ž‘ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ
    	if (lookahead_size <= nr_to_read) {
    		unsigned long ra_folio_index;
    		ra_folio_index = round_up(readahead_index(ractl) +
    					  nr_to_read - lookahead_size,
    					  min_nrpages);
    		mark = ra_folio_index - index;
    	}
    
    	// ํŽ˜์ด์ง€ ์บ์‹œ์— folio ์‚ฌ์ „ ํ• ๋‹น ๋ฃจํ”„
    	while (i < nr_to_read) {
    		struct folio *folio = xa_load(&mapping->i_pages, index + i);
    
    		if (folio && !xa_is_value(folio)) {
    			// ์ด๋ฏธ ์กด์žฌํ•˜๋Š” folio โ†’ ํ˜„์žฌ ๋ฐฐ์น˜ ํ”Œ๋Ÿฌ์‹œ ํ›„ ๊ฑด๋„ˆ๋œ€
    			read_pages(ractl);
    			ractl->_index += min_nrpages;
    			i = ractl->_index + ractl->_nr_pages - index;
    			continue;
    		}
    
    		folio = ractl_alloc_folio(ractl, gfp_mask,
    					mapping_min_folio_order(mapping));
    		if (!folio) break;
    
    		ret = filemap_add_folio(mapping, folio, index + i, gfp_mask);
    		if (ret < 0) {
    			folio_put(folio);
    			if (ret == -ENOMEM) break;
    			read_pages(ractl);
    			/* ... continue */
    		}
    		if (i == mark) folio_set_readahead(folio);  // PG_readahead ํ”Œ๋ž˜๊ทธ ์„ค์ •
    		ractl->_nr_pages += min_nrpages;
    		i += min_nrpages;
    	}
    
    	read_pages(ractl);
    	memalloc_nofs_restore(nofs);
    }

    ์—ญํ• : ํŒŒ์ผ ์‹œ์Šคํ…œ์˜ readahead ๊ฒฝ๋กœ์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๊ธฐ๋ณธ readahead ๊ตฌํ˜„. ํŽ˜์ด์ง€๋ฅผ ๋จผ์ € ํ• ๋‹นํ•œ ํ›„ I/O๋ฅผ ์ œ์ถœํ•˜์—ฌ ์ฝ๊ธฐ-์“ฐ๊ธฐ ๊ฐ„์˜ ๊ต์ฐจ๋ฅผ ๋ฐฉ์ง€ํ•œ๋‹ค.

    ๋ถ„๊ธฐ ๋กœ์ง:

  • xa_load๋กœ ๊ธฐ์กด folio ์กด์žฌ ํ™•์ธ โ†’ ์žˆ์œผ๋ฉด ํ˜„์žฌ ๋ฐฐ์น˜ ํ”Œ๋Ÿฌ์‹œ ํ›„ ์Šคํ‚ต
  • filemap_add_folio ์‹คํŒจ โ†’ -ENOMEM์ด๋ฉด ์ค‘๋‹จ, ๊ธฐํƒ€ ์—๋Ÿฌ์ด๋ฉด ํ”Œ๋Ÿฌ์‹œ ํ›„ ๊ณ„์†
  • i == mark โ†’ lookahead ์˜์—ญ ์‹œ์ž‘ โ†’ folio_set_readahead()๋กœ PG_readahead ์„ค์ •
  • page_cache_ra_order() โ€” large folio readahead

    // mm/readahead.c:467-540
    void page_cache_ra_order(struct readahead_control *ractl,
    		struct file_ra_state *ra)
    {
    	struct address_space *mapping = ractl->mapping;
    	pgoff_t start = readahead_index(ractl);
    	unsigned int min_order = mapping_min_folio_order(mapping);
    	pgoff_t limit = (i_size_read(mapping->host) - 1) >> PAGE_SHIFT;
    	pgoff_t mark = index + ra->size - ra->async_size;
    	unsigned int new_order = ra->order;
    
    	if (!mapping_large_folio_support(mapping)) {
    		ra->order = 0;
    		goto fallback;
    	}
    
    	new_order = min(mapping_max_folio_order(mapping), new_order);
    	new_order = min_t(unsigned int, new_order, ilog2(ra->size));
    	new_order = max(new_order, min_order);
    
    	while (index <= limit) {
    		unsigned int order = new_order;
    		// ์ธ๋ฑ์Šค ์ •๋ ฌ์— ๋”ฐ๋ผ order ์กฐ์ •
    		if (index & ((1UL << order) - 1))
    			order = __ffs(index);
    		// EOF๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ์•Š๋„๋ก order ์ถ•์†Œ
    		while (order > min_order && index + (1UL << order) - 1 > limit)
    			order--;
    		err = ra_alloc_folio(ractl, index, mark, order, gfp);
    		if (err) break;
    		index += 1UL << order;
    	}
    
    	read_pages(ractl);
    fallback:
    	if (ra->size > index - start)
    		do_page_cache_ra(ractl, ra->size - (index - start),
    				 ra->async_size);
    }

    ์—ญํ• : large folio ์ง€์› ์‹œ higher-order folio๋กœ readahead. ra->order๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํŽ˜์ด์ง€ ์ธ๋ฑ์Šค ์ •๋ ฌ๊ณผ EOF ์ œํ•œ์— ๋งž์ถฐ order๋ฅผ ๋™์  ์กฐ์ •ํ•œ๋‹ค.

    get_next_ra_size() โ€” ๋‹ค์Œ readahead ์œˆ๋„์šฐ ํฌ๊ธฐ ๊ณ„์‚ฐ

    // mm/readahead.c:394-404
    static unsigned long get_next_ra_size(struct file_ra_state *ra,
    				      unsigned long max)
    {
    	unsigned long cur = ra->size;
    
    	if (cur < max / 16)
    		return 4 * cur;    // ์ž‘์„ ๋•Œ: 4๋ฐฐ ์ฆ๊ฐ€ (๊ณต๊ฒฉ์  ๋žจํ”„์—…)
    	if (cur <= max / 2)
    		return 2 * cur;    // ์ค‘๊ฐ„: 2๋ฐฐ ์ฆ๊ฐ€
    	return max;                // ์ถฉ๋ถ„ํžˆ ํฌ๋ฉด: ์ตœ๋Œ€๊ฐ’ ์œ ์ง€
    }

    ์—ญํ• : ์ˆœ์ฐจ readahead์—์„œ ์œˆ๋„์šฐ ํฌ๊ธฐ๋ฅผ ์ ์ง„์ ์œผ๋กœ ์ฆ๊ฐ€์‹œํ‚ค๋Š” ramp-up ํ•จ์ˆ˜.

    readahead_expand() โ€” readahead ์ฐฝ ํ™•์žฅ

    // mm/readahead.c:766-841
    void readahead_expand(struct readahead_control *ractl,
    		      loff_t new_start, size_t new_len)
    {
    	struct address_space *mapping = ractl->mapping;
    	struct file_ra_state *ra = ractl->ra;
    	pgoff_t new_index, new_nr_pages;
    	gfp_t gfp_mask = readahead_gfp_mask(mapping);
    	unsigned long min_nrpages = mapping_min_folio_nrpages(mapping);
    	unsigned int min_order = mapping_min_folio_order(mapping);
    
    	new_index = new_start / PAGE_SIZE;
    	/* readahead ์ฝ”๋“œ๋Š” ํ˜ธ์ถœ ์ „์— ractl->_index๋ฅผ min_nrpages๋กœ ์ •๋ ฌํ•ด์•ผ ํ•œ๋‹ค. */
    	VM_BUG_ON(!IS_ALIGNED(ractl->_index, min_nrpages));
    
    	/* ์•ž์ชฝ ๊ฒฝ๊ณ„๋ฅผ ์•„๋ž˜๋กœ ํ™•์žฅํ•œ๋‹ค. */
    	while (ractl->_index > new_index) {
    		unsigned long index = ractl->_index - 1;
    		struct folio *folio = xa_load(&mapping->i_pages, index);
    
    		if (folio && !xa_is_value(folio))
    			return; /* folio๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค */
    
    		folio = ractl_alloc_folio(ractl, gfp_mask, min_order);
    		if (!folio)
    			return;
    
    		index = mapping_align_index(mapping, index);
    		if (filemap_add_folio(mapping, folio, index, gfp_mask) < 0) {
    			folio_put(folio);
    			return;
    		}
    		if (unlikely(folio_test_workingset(folio)) && !ractl->_workingset) {
    			ractl->_workingset = true;
    			psi_memstall_enter(&ractl->_pflags);
    		}
    		ractl->_nr_pages += min_nrpages;
    		ractl->_index = folio->index;
    	}
    
    	new_len += new_start - readahead_pos(ractl);
    	new_nr_pages = DIV_ROUND_UP(new_len, PAGE_SIZE);
    
    	/* ๋’ค์ชฝ ๊ฒฝ๊ณ„๋ฅผ ์œ„๋กœ ํ™•์žฅํ•œ๋‹ค. */
    	while (ractl->_nr_pages < new_nr_pages) {
    		unsigned long index = ractl->_index + ractl->_nr_pages;
    		struct folio *folio = xa_load(&mapping->i_pages, index);
    
    		if (folio && !xa_is_value(folio))
    			return; /* folio๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค */
    
    		folio = ractl_alloc_folio(ractl, gfp_mask, min_order);
    		if (!folio)
    			return;
    
    		index = mapping_align_index(mapping, index);
    		if (filemap_add_folio(mapping, folio, index, gfp_mask) < 0) {
    			folio_put(folio);
    			return;
    		}
    		if (unlikely(folio_test_workingset(folio)) && !ractl->_workingset) {
    			ractl->_workingset = true;
    			psi_memstall_enter(&ractl->_pflags);
    		}
    		ractl->_nr_pages += min_nrpages;
    		if (ra) {
    			ra->size += min_nrpages;
    			ra->async_size += min_nrpages;
    		}
    	}
    }
    EXPORT_SYMBOL(readahead_expand);

    ์—ญํ• : filesystem ->readahead() ๊ฒฝ๋กœ์—์„œ ํ˜„์žฌ ์š”์ฒญ ์ฐฝ์„ ์•ž๋’ค๋กœ ๋„“ํ˜€์•ผ ํ•  ๋•Œ ์“ฐ๋Š” ๋ณด์กฐ ํ•จ์ˆ˜๋‹ค. ์ค‘๊ฐ„์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” folio๋ฅผ ๋งŒ๋‚˜๋ฉด ๋” ์ด์ƒ ํ™•์žฅํ•˜์ง€ ์•Š๊ณ  ๋ฉˆ์ถ”๋ฉฐ, THP๊ฐ€ ๋ผ์–ด๋“ค๋ฉด ์š”์ฒญํ•œ ๊ธธ์ด๋ณด๋‹ค ๋” ํฌ๊ฒŒ ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค.


    ํ˜ธ์ถœ ํ๋ฆ„

    readahead ํ˜ธ์ถœ ํ๋ฆ„
    read() / mmap() / fadvise()
    โ”‚
    โ”œโ”€ [cache miss] โ†’ page_cache_sync_readahead()
    โ”‚                    โ””โ†’ page_cache_sync_ra()
    โ”‚                         โ”œโ”€ FMODE_RANDOM? โ†’ force_page_cache_ra()
    โ”‚                         โ”‚                    โ””โ†’ do_page_cache_ra() โ†’ page_cache_ra_unbounded()
    โ”‚                         โ”œโ”€ ์ˆœ์ฐจ ์ฝ๊ธฐ ๊ฐ์ง€ โ†’ get_init_ra_size() โ†’ page_cache_ra_order()
    โ”‚                         โ””โ”€ ๋žœ๋ค ์ฝ๊ธฐ โ†’ do_page_cache_ra()
    โ”‚
    โ”œโ”€ [PG_readahead ์ ‘๊ทผ] โ†’ page_cache_async_readahead()
    โ”‚                          โ””โ†’ page_cache_async_ra()
    โ”‚                               โ”œโ”€ ์ˆœ์ฐจ ํžˆํŠธ โ†’ get_next_ra_size() โ†’ page_cache_ra_order()
    โ”‚                               โ””โ”€ ๋น„์ˆœ์ฐจ ํžˆํŠธ โ†’ page_cache_next_miss() โ†’ get_next_ra_size()
    โ”‚
    โ””โ”€ readahead() ์‹œ์Šคํ…œ ํ˜ธ์ถœ โ†’ ksys_readahead()
    โ””โ†’ vfs_fadvise(POSIX_FADV_WILLNEED)
    โ””โ†’ page_cache_sync_readahead()
    
    page_cache_ra_order()
    โ”œโ”€ mapping_large_folio_support() ํ™•์ธ
    โ”œโ”€ ra_alloc_folio() ๋ฃจํ”„ (large folio ํ• ๋‹น)
    โ”‚    โ””โ†’ ractl_alloc_folio() โ†’ filemap_alloc_folio()
    โ”‚    โ””โ†’ filemap_add_folio()
    โ””โ”€ fallback: do_page_cache_ra() (์ผ๋ฐ˜ readahead ๊ฒฝ๋กœ)
    
    page_cache_ra_unbounded()
    โ”œโ”€ memalloc_nofs_save() (VM ์žฌํ• ๋‹น ๋ฐฉ์ง€)
    โ”œโ”€ folio ์‚ฌ์ „ ํ• ๋‹น ๋ฃจํ”„
    โ”‚    โ”œโ”€ xa_load() โ†’ ๊ธฐ์กด folio ํ™•์ธ
    โ”‚    โ”œโ”€ ractl_alloc_folio() โ†’ filemap_alloc_folio()
    โ”‚    โ””โ”€ filemap_add_folio() โ†’ page cache์— ์ถ”๊ฐ€
    โ””โ”€ read_pages()
    โ”œโ”€ aops->readahead() โ†’ ํŒŒ์ผ์‹œ์Šคํ…œ readahead ํ•ธ๋“ค๋Ÿฌ
    โ”‚    โ””โ†’ readahead_folio() ๋ฐ˜๋ณต
    โ””โ”€ aops->read_folio() โ†’ ๊ฐœ๋ณ„ folio ์ฝ๊ธฐ (ํด๋ฐฑ)
    
    filesystem ->readahead() ๊ตฌํ˜„
    โ””โ”€ ํ•„์š” ์‹œ readahead_expand()๋กœ ์š”์ฒญ ์ฐฝ์„ ์•ž๋’ค๋กœ ํ™•์žฅ

    ์กฐ๊ฑด๋ณ„ ๋น„๊ต

    readahead ํŠธ๋ฆฌ๊ฑฐ ๋น„๊ต

    ์กฐ๊ฑด๋™๊ธฐ readahead๋น„๋™๊ธฐ readahead๊ฐ•์ œ ์ฝ๊ธฐ
    **์ง„์ž…์ **`page_cache_sync_ra()``page_cache_async_ra()``force_page_cache_ra()`
    **ํŠธ๋ฆฌ๊ฑฐ**cache missPG_readahead ํ”Œ๋ž˜๊ทธ ์ ‘๊ทผFMODE_RANDOM ๋˜๋Š” ra_pages=0
    **์œˆ๋„์šฐ ๊ณ„์‚ฐ**`get_init_ra_size()``get_next_ra_size()`์š”์ฒญ ํฌ๊ธฐ ๊ทธ๋Œ€๋กœ
    **async_size**`size - req_count``size` (์ „์ฒด)0
    **large folio**์ง€์›์ง€์›๋น„ํ™œ์„ฑ (์ผ๋ฐ˜ 4KB)
    **์ƒํƒœ ๋ณ€๊ฒฝ**ra->start/size ์„ค์ •์œˆ๋„์šฐ ์ „์ง„์—†์Œ

    readahead ์œˆ๋„์šฐ ramp-up ๋น„๊ต

    ํ˜„์žฌ ํฌ๊ธฐ๋ฐฐ์œจ์„ค๋ช…
    `cur < max/16`ร—4์ดˆ๊ธฐ ๊ณต๊ฒฉ์  ๋žจํ”„์—…
    `cur โ‰ค max/2`ร—2์ ์ง„์  ์ฆ๊ฐ€
    `cur > max/2`max์ตœ๋Œ€ ์œˆ๋„์šฐ ์œ ์ง€

    folio ์ฒ˜๋ฆฌ ๊ฒฝ๋กœ ๋น„๊ต

    ์‹œ๋‚˜๋ฆฌ์˜ค์ฒ˜๋ฆฌ ๋ฐฉ์‹
    ๊ธฐ์กด folio ์กด์žฌ (`xa_load` ์„ฑ๊ณต)ํ˜„์žฌ ๋ฐฐ์น˜ ํ”Œ๋Ÿฌ์‹œ ํ›„ ์Šคํ‚ต
    ์ƒˆ folio ํ• ๋‹น ์„ฑ๊ณตfilemap_add_folio๋กœ page cache ์ถ”๊ฐ€
    ํ• ๋‹น ์‹คํŒจ (`-ENOMEM`)readahead ์ค‘๋‹จ
    filemap_add_folio ์‹คํŒจ (๊ธฐํƒ€)๋ฐฐ์น˜ ํ”Œ๋Ÿฌ์‹œ ํ›„ ๊ณ„์†
    PG_readahead ๋งˆํฌ ๋„๋‹ฌ`folio_set_readahead()` ์„ค์ •

    readahead pipelining ์›๋ฆฌ

    |==================#===========================|
    ^start             ^PG_readahead               ^end
    |<----- async_size -------->|
    |<------------- size ----------------------->|
    
    1. ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด start ~ # ์‚ฌ์ด๋ฅผ ์ฝ์Œ (๋™๊ธฐ)
    2. # ์— ๋„๋‹ฌํ•˜๋ฉด ๋‹ค์Œ readahead ์‹œ์ž‘ (๋น„๋™๊ธฐ)
    3. ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ๋‚˜๋จธ์ง€ async ์˜์—ญ์„ ์ฝ๋Š” ๋™์•ˆ
    ๋””์Šคํฌ์—์„œ ๋‹ค์Œ ์œˆ๋„์šฐ ๋กœ๋”ฉ
    4. ๋‹ค์Œ ์œˆ๋„์šฐ ๋กœ๋”ฉ ์™„๋ฃŒ ์‹œ๊นŒ์ง€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด
    ํ˜„์žฌ ์œˆ๋„์šฐ๋ฅผ ์†Œ๋น„ํ•˜๋ฉด I/O ๋Œ€๊ธฐ ์—†์Œ
  • async_size๊ฐ€ ํด์ˆ˜๋ก ํŒŒ์ดํ”„๋ผ์ด๋‹ ํšจ๊ณผ๊ฐ€ ํฌ์ง€๋งŒ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€
  • ra_pages๊ฐ€ max ์—ญํ• ์„ ํ•˜์—ฌ ์œˆ๋„์šฐ ์ƒํ•œ ์ œํ•œ
  • ์ˆœ์ฐจ ์ŠคํŠธ๋ฆผ์ด ํ™•๋ฆฝ๋˜๋ฉด ๋™๊ธฐ ์„ฑ๋ถ„์ด ์ œ๊ฑฐ๋˜๊ณ  ์ „์ฒด๊ฐ€ ๋น„๋™๊ธฐ๋กœ ๋™์ž‘

  • ๊ด€๋ จ ๋ฌธ์„œ

  • ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ฐœ์š”
  • Buddy Allocator
  • Folio / Page Cache
  • Workingset
  • Readahead (์ด ๋ฌธ์„œ)
  • DAMON โ€” ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ํŒจํ„ด ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์—ฐ๊ณ„
  • ํŽ˜์ด์ง€ ํšŒ์ˆ˜ โ€” readahead์™€ ํŽ˜์ด์ง€ ํ•ด์ œ์˜ ์ƒํ˜ธ์ž‘์šฉ