| 0/0 |
2026/02/23 14:04 |
flow |
assessment-kcsan |
2m
Results: map[Benign:true Confident:true Explanation:The data race occurs in `shrink_readahead_size_eio` within `mm/filemap.c`. This function is called when a page fault or a read operation encounters an I/O error (EIO) on a file-backed folio. Its purpose is to aggressively scale down the readahead size (`ra_pages`) to avoid repeated, potentially slow or frustrating, retries on bad media or near the end of a device.
The race happens because multiple threads performing I/O on the same `struct file` (or shared file descriptor) can concurrently encounter errors and attempt to update the `file_ra_state` without any mutual exclusion. In the reported case, two tasks were faulting on the same file and both executed `ra->ra_pages /= 4;` simultaneously.
This data race is **benign** for several reasons:
1. **Heuristic Nature**: The `file_ra_state` structure is designed to be a set of hints for the readahead algorithm. It is intentionally accessed without locks in many places (including `filemap_read` and `page_cache_sync_ra`) to avoid performance bottlenecks on hot I/O paths.
2. **No Correctness Impact**: The `ra_pages` field only limits the maximum number of pages to be read ahead. If a race causes it to be slightly larger than intended (e.g., two threads both seeing 32 and writing 8, instead of one writing 8 and the next writing 2), the only consequence is that the readahead algorithm might be slightly more aggressive than desired in the face of errors. This does not lead to memory corruption, crashes, or incorrect data being returned to userspace.
3. **Safe Values**: The field is a 4-byte `unsigned int`. A division by 4 cannot cause an overflow or wrap-around to a dangerous value. Even if the write was not atomic (though on x86-64, 4-byte aligned writes are), the resulting value would still be used only as a readahead limit.
4. **Expected Concurrency**: The kernel documentation and comments in `mm/readahead.c` explicitly acknowledge that concurrent streams on the same file descriptor can "invalidate each other's readahead state" and that the algorithm checks "loosely" for patterns.
The "attempt to access beyond end of device" message in the report is the *trigger* for this code (the I/O error), not a result of the data race itself. The race is a side effect of multiple threads hitting the same device limit simultaneously.
Final verdict: The data race is a harmless inaccuracy in a performance heuristic.]
|
| 1/1 |
2026/02/23 14:04 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/src/fa725823f1b6fb62604bf25d86258d5085fef9ad]
|
| 2/1 |
2026/02/23 14:04 |
action |
kernel-builder |
0m
Results: map[KernelObj:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/build/dab4906ce5fbba253540b2b64dbd756a1c50fe92]
|
| 3/1 |
2026/02/23 14:04 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/02/23 14:04 |
agent |
expert |
2m
Model:
Results: map[Benign:true Confident:true]
Instruction:
You are an experienced Linux kernel developer tasked with determining if the given kernel
data race is benign or not. The data race report is from KCSAN tool.
It contains 2 stack traces of the memory accesses that constitute a data race.
A "benign" data races are on a simple int/bool variable or similar field,
and the accesses are not supposed to be protected by any mutual exclusion primitives.
Common examples of such "benign" data races are accesses to various flags fields,
statistics counters, and similar. A "benign" data race does not lead to memory corruption/crash
with a conservative compiler that compiles memory accesses to primitive types
effectively as atomic.
A non-benign (or "harmful" data race) can lead to corruption/crash even with
a conservative compiler that compiles memory accesses to primitive types
effectively as atomic. A common example of a "harmful" data races is race on
a complex container (list/hashmap/etc), where accesses are supposed to be protected
by a mutual exclusion primitive.
Also consider races that happen at the same time with the given one.
If there is no synchronization in between, other memory accesses in the involved threads
race with each other if they access the same memory. For example, if both threads execute:
some_struct->have_elements = true;
list_add(new_node, &some_struct->list_head);
the race on some_struct->have_elements may appear benign, however it also implies there
is a race on some_struct->list_head which is not benign, since the list is not thread-safe.
Take into account that on 32-bit systems 64-bit memory accesses may be split into two accesses,
and thus even with a conservative compiler may not be fully atomic. However, such races may
still be benign depending on how writes are done, and how read data is used.
In the final reply explain why you think the given data race is benign or is harmful.
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
In particular, don't make assumptions about the kernel source code,
use codesearch tools to read the actual source code.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The data race report is:
syz.1.7451: attempt to access beyond end of device
loop1: rw=8388608, sector=2065, nr_sectors = 8 limit=128
==================================================================
BUG: KCSAN: data-race in filemap_read_folio / filemap_read_folio
read-write to 0xffff8881670352e0 of 4 bytes by task 1786 on cpu 1:
shrink_readahead_size_eio mm/filemap.c:2438 [inline]
filemap_read_folio+0xea/0x110 mm/filemap.c:2508
filemap_fault+0x5c8/0xb90 mm/filemap.c:3641
__do_fault+0xbc/0x200 mm/memory.c:5355
do_read_fault mm/memory.c:5790 [inline]
do_fault mm/memory.c:5924 [inline]
do_pte_missing mm/memory.c:4469 [inline]
handle_pte_fault mm/memory.c:6308 [inline]
__handle_mm_fault mm/memory.c:6446 [inline]
handle_mm_fault+0x11d7/0x3020 mm/memory.c:6615
faultin_page mm/gup.c:1126 [inline]
__get_user_pages+0x1023/0x1ea0 mm/gup.c:1428
populate_vma_page_range mm/gup.c:1860 [inline]
__mm_populate+0x242/0x390 mm/gup.c:1963
mm_populate include/linux/mm.h:3689 [inline]
vm_mmap_pgoff+0x23b/0x2d0 mm/util.c:586
ksys_mmap_pgoff+0x267/0x310 mm/mmap.c:604
x64_sys_call+0x14df/0x3020 arch/x86/include/generated/asm/syscalls_64.h:10
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x12c/0x370 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
read-write to 0xffff8881670352e0 of 4 bytes by task 1814 on cpu 0:
shrink_readahead_size_eio mm/filemap.c:2438 [inline]
filemap_read_folio+0xea/0x110 mm/filemap.c:2508
filemap_fault+0x5c8/0xb90 mm/filemap.c:3641
__do_fault+0xbc/0x200 mm/memory.c:5355
do_read_fault mm/memory.c:5790 [inline]
do_fault mm/memory.c:5924 [inline]
do_pte_missing mm/memory.c:4469 [inline]
handle_pte_fault mm/memory.c:6308 [inline]
__handle_mm_fault mm/memory.c:6446 [inline]
handle_mm_fault+0x11d7/0x3020 mm/memory.c:6615
do_user_addr_fault+0x3fd/0x1050 arch/x86/mm/fault.c:1385
handle_page_fault arch/x86/mm/fault.c:1474 [inline]
exc_page_fault+0x62/0xa0 arch/x86/mm/fault.c:1527
asm_exc_page_fault+0x26/0x30 arch/x86/include/asm/idtentry.h:618
rep_movs_alternative+0x4a/0x90 arch/x86/lib/copy_user_64.S:68
copy_user_generic arch/x86/include/asm/uaccess_64.h:126 [inline]
raw_copy_from_user arch/x86/include/asm/uaccess_64.h:141 [inline]
_inline_copy_from_user include/linux/uaccess.h:185 [inline]
_copy_from_user+0x6f/0xb0 lib/usercopy.c:18
copy_from_user include/linux/uaccess.h:223 [inline]
copy_mount_options fs/namespace.c:4028 [inline]
__do_sys_mount fs/namespace.c:4343 [inline]
__se_sys_mount+0x10d/0x2e0 fs/namespace.c:4325
__x64_sys_mount+0x67/0x80 fs/namespace.c:4325
x64_sys_call+0x2d61/0x3020 arch/x86/include/generated/asm/syscalls_64.h:166
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x12c/0x370 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
value changed: 0x00000020 -> 0x00000008
Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 1814 Comm: syz.1.7451 Tainted: G W syzkaller #0 PREEMPT(full)
Tainted: [W]=WARN
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/24/2026
==================================================================
syz.1.7451: attempt to access beyond end of device
loop1: rw=8388608, sector=2065, nr_sectors = 8 limit=128
Reply: The data race occurs in `shrink_readahead_size_eio` within `mm/filemap.c`. This function is called when a page fault or a read operation encounters an I/O error (EIO) on a file-backed folio. Its purpose is to aggressively scale down the readahead size (`ra_pages`) to avoid repeated, potentially slow or frustrating, retries on bad media or near the end of a device.
The race happens because multiple threads performing I/O on the same `struct file` (or shared file descriptor) can concurrently encounter errors and attempt to update the `file_ra_state` without any mutual exclusion. In the reported case, two tasks were faulting on the same file and both executed `ra->ra_pages /= 4;` simultaneously.
This data race is **benign** for several reasons:
1. **Heuristic Nature**: The `file_ra_state` structure is designed to be a set of hints for the readahead algorithm. It is intentionally accessed without locks in many places (including `filemap_read` and `page_cache_sync_ra`) to avoid performance bottlenecks on hot I/O paths.
2. **No Correctness Impact**: The `ra_pages` field only limits the maximum number of pages to be read ahead. If a race causes it to be slightly larger than intended (e.g., two threads both seeing 32 and writing 8, instead of one writing 8 and the next writing 2), the only consequence is that the readahead algorithm might be slightly more aggressive than desired in the face of errors. This does not lead to memory corruption, crashes, or incorrect data being returned to userspace.
3. **Safe Values**: The field is a 4-byte `unsigned int`. A division by 4 cannot cause an overflow or wrap-around to a dangerous value. Even if the write was not atomic (though on x86-64, 4-byte aligned writes are), the resulting value would still be used only as a readahead limit.
4. **Expected Concurrency**: The kernel documentation and comments in `mm/readahead.c` explicitly acknowledge that concurrent streams on the same file descriptor can "invalidate each other's readahead state" and that the algorithm checks "loosely" for patterns.
The "attempt to access beyond end of device" message in the report is the *trigger* for this code (the I/O error), not a result of the data race itself. The race is a side effect of multiple threads hitting the same device limit simultaneously.
Final verdict: The data race is a harmless inaccuracy in a performance heuristic.
|
| 5/2 |
2026/02/23 14:04 |
llm |
expert |
0m
Model:
Tokens:
input: 4684
output: 44
thoughts: 120
Thoughts: **Analyzing a Data Race in the Filemap Read Path**
Okay, the report indicates a nasty data race within `shrink_readahead_size_eio`, specifically when called from `filemap_read_folio`. The trace is clear: two tasks are duking it out over the same memory location, `0xffff8881670352e0`. And the values are ping-ponging, from 32 down to 8. We're clearly seeing a concurrent read-write situation, which is precisely the kind of thing that can lead to subtle corruption or performance issues.
My immediate next step is to head straight to the source. Let's get that `mm/filemap.c:2438` line of code. I need to understand the exact context of `shrink_readahead_size_eio` and what's happening around that memory address. I'll be looking for shared data structures, lock usage (or lack thereof), and how these two functions are interacting. I'm guessing this memory location holds some form of readahead window size or a similar control variable. Identifying the critical section and the potential for a race condition within that readahead logic will be key.
|
| 6/2 |
2026/02/23 14:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/filemap.c IncludeLines:true Name:shrink_readahead_size_eio]
Results: map[SourceCode:2421: /*
2422: * CD/DVDs are error prone. When a medium error occurs, the driver may fail
2423: * a _large_ part of the i/o request. Imagine the worst scenario:
2424: *
2425: * ---R__________________________________________B__________
2426: * ^ reading here ^ bad block(assume 4k)
2427: *
2428: * read(R) => miss => readahead(R...B) => media error => frustrating retries
2429: * => failing the whole request => read(R) => read(R+1) =>
2430: * readahead(R+1...B+1) => bang => read(R+2) => read(R+3) =>
2431: * readahead(R+3...B+2) => bang => read(R+3) => read(R+4) =>
2432: * readahead(R+4...B+3) => bang => read(R+4) => read(R+5) => ......
2433: *
2434: * It is going insane. Fix it by quickly scaling down the readahead size.
2435: */
2436: static void shrink_readahead_size_eio(struct file_ra_state *ra)
2437: {
2438: ra->ra_pages /= 4;
2439: }
SourceFile:mm/filemap.c]
|
| 7/2 |
2026/02/23 14:04 |
llm |
expert |
0m
Model:
Tokens:
input: 5519
output: 41
thoughts: 58
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience:
**Analyzing `shrink_readahead_size_eio` in the File Read Path**
Right, the race is on: `ra->ra_pages /= 4;`. This is likely a key point where read-ahead size is being adjusted, probably in response to something significant, potentially an I/O error. I need to understand this better. `ra` is a `struct file_ra_state`, of course. It's the read-ahead state for a particular file.
My primary concern right now is to understand the context and rationale behind this modification, specifically the divisor of 4. Where is `shrink_readahead_size_eio` actually being called from? The function name strongly suggests that it's related to shrinking the read-ahead window due to an I/O error (`_eio`). Given the context, I need to trace its call sites within `filemap_read_folio`. The goal here is to determine under what specific conditions read-ahead is being throttled and understand the implications for overall I/O performance. This dive will give me the contextual clues needed to interpret the change in read-ahead window size, especially how it handles edge cases in file access patterns.
|
| 8/2 |
2026/02/23 14:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/filemap.c IncludeLines:true Name:filemap_read_folio]
Results: map[SourceCode:2486: static int filemap_read_folio(struct file *file, filler_t filler,
2487: struct folio *folio)
2488: {
2489: bool workingset = folio_test_workingset(folio);
2490: unsigned long pflags;
2491: int error;
2492:
2493: /* Start the actual read. The read will unlock the page. */
2494: if (unlikely(workingset))
2495: psi_memstall_enter(&pflags);
2496: error = filler(file, folio);
2497: if (unlikely(workingset))
2498: psi_memstall_leave(&pflags);
2499: if (error)
2500: return error;
2501:
2502: error = folio_wait_locked_killable(folio);
2503: if (error)
2504: return error;
2505: if (folio_test_uptodate(folio))
2506: return 0;
2507: if (file)
2508: shrink_readahead_size_eio(&file->f_ra);
2509: return -EIO;
2510: }
SourceFile:mm/filemap.c]
|
| 9/2 |
2026/02/23 14:04 |
llm |
expert |
0m
Model:
Tokens:
input: 6271
output: 41
thoughts: 430
Thoughts: **Analyzing a Potential Race Condition in File Readahead Logic**
Okay, so I've got this report about a potential race condition and I need to figure out if it's actually a problem. It seems to be happening when multiple threads are accessing the same `struct file` and are hitting errors during reads. Specifically, the race involves `file->f_ra`, which is the readahead state for the file.
The scenario is this: multiple threads are calling `filemap_read_folio` on the same file, and they encounter an error, maybe a bad block. They then all try to adjust the readahead size, specifically by shrinking it. The offending line of code is `ra->ra_pages /= 4;` – a classic read-modify-write situation.
The report highlights that this division isn't atomic. So, here's the race: Thread 1 reads the current value of `ra_pages` (let's say it's 32). Thread 2 *also* reads the same value (32). Thread 1 calculates 32 / 4 = 8, and writes that back. Thread 2 calculates 32 / 4 = 8, and overwrites that same memory location again with 8. If they had worked serially, the value would have been 32 -> 8 -> 2. The report shows the value changed from 32 to 8. This implies one thread wrote successfully, while the other effectively overwrote the same value.
The question then is, "Is this harmful?". `ra_pages` is just a *hint* for the readahead. Aggressively shrinking the readahead size in the face of I/O errors is a heuristic, designed to avoid "going insane", as the comment says. It doesn't seem like we're risking memory corruption or crashes here, because it's just a hint. However, I need to be *sure* about this.
My next step is to examine whether `struct file_ra_state` *should* be protected by a lock. Is `file->f_ra` typically accessed under `file->f_lock`, or is it left unprotected because it's just a hint? I need to check the structure definition and the code around its usage to be certain. I'm going to look for any mutexes or spinlocks that might be protecting access to it. It's time to dig into the details.
|
| 10/2 |
2026/02/23 14:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/fs.h IncludeLines:true Name:file_ra_state]
Results: map[SourceCode:1197: /**
1198: * struct file_ra_state - Track a file's readahead state.
1199: * @start: Where the most recent readahead started.
1200: * @size: Number of pages read in the most recent readahead.
1201: * @async_size: Numer of pages that were/are not needed immediately
1202: * and so were/are genuinely "ahead". Start next readahead when
1203: * the first of these pages is accessed.
1204: * @ra_pages: Maximum size of a readahead request, copied from the bdi.
1205: * @order: Preferred folio order used for most recent readahead.
1206: * @mmap_miss: How many mmap accesses missed in the page cache.
1207: * @prev_pos: The last byte in the most recent read request.
1208: *
1209: * When this structure is passed to ->readahead(), the "most recent"
1210: * readahead means the current readahead.
1211: */
1212: struct file_ra_state {
1213: pgoff_t start;
1214: unsigned int size;
1215: unsigned int async_size;
1216: unsigned int ra_pages;
1217: unsigned short order;
1218: unsigned short mmap_miss;
1219: loff_t prev_pos;
1220: };
SourceFile:include/linux/fs.h]
|
| 11/2 |
2026/02/23 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 7705
output: 39
thoughts: 45
Thoughts: Okay, here's my take on those thoughts, framed from my own perspective as someone familiar with file system internals:
**Diving into the File Read-Ahead Structure**
Right, let me see... I'm thinking about the `file_ra_state`. It's a key element within the `struct file` structure, and it holds information vital for read-ahead operations. I need to get a clear understanding of its role. To refresh my memory, I'm going to track how `file->f_ra` is used across the codebase. Specifically, I'm immediately drawn to the `filemap_read` function. That seems like a logical place to start digging deeper, given its obvious connection to the core reading mechanism within the file system. I anticipate that examining its interaction with `f_ra` will illuminate how the read-ahead logic actually functions, and helps improve read performance.
|
| 12/2 |
2026/02/23 14:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/filemap.c IncludeLines:true Name:filemap_read]
Results: map[SourceCode:2750: /**
2751: * filemap_read - Read data from the page cache.
2752: * @iocb: The iocb to read.
2753: * @iter: Destination for the data.
2754: * @already_read: Number of bytes already read by the caller.
2755: *
2756: * Copies data from the page cache. If the data is not currently present,
2757: * uses the readahead and read_folio address_space operations to fetch it.
2758: *
2759: * Return: Total number of bytes copied, including those already read by
2760: * the caller. If an error happens before any bytes are copied, returns
2761: * a negative error number.
2762: */
2763: ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
2764: ssize_t already_read)
2765: {
2766: struct file *filp = iocb->ki_filp;
2767: struct file_ra_state *ra = &filp->f_ra;
2768: struct address_space *mapping = filp->f_mapping;
2769: struct inode *inode = mapping->host;
2770: struct folio_batch fbatch;
2771: int i, error = 0;
2772: bool writably_mapped;
2773: loff_t isize, end_offset;
2774: loff_t last_pos = ra->prev_pos;
2775:
2776: if (unlikely(iocb->ki_pos < 0))
2777: return -EINVAL;
2778: if (unlikely(iocb->ki_pos >= inode->i_sb->s_maxbytes))
2779: return 0;
2780: if (unlikely(!iov_iter_count(iter)))
2781: return 0;
2782:
2783: iov_iter_truncate(iter, inode->i_sb->s_maxbytes - iocb->ki_pos);
2784: folio_batch_init(&fbatch);
2785:
2786: do {
2787: cond_resched();
2788:
2789: /*
2790: * If we've already successfully copied some data, then we
2791: * can no longer safely return -EIOCBQUEUED. Hence mark
2792: * an async read NOWAIT at that point.
2793: */
2794: if ((iocb->ki_flags & IOCB_WAITQ) && already_read)
2795: iocb->ki_flags |= IOCB_NOWAIT;
2796:
2797: if (unlikely(iocb->ki_pos >= i_size_read(inode)))
2798: break;
2799:
2800: error = filemap_get_pages(iocb, iter->count, &fbatch, false);
2801: if (error < 0)
2802: break;
2803:
2804: /*
2805: * i_size must be checked after we know the pages are Uptodate.
2806: *
2807: * Checking i_size after the check allows us to calculate
2808: * the correct value for "nr", which means the zero-filled
2809: * part of the page is not copied back to userspace (unless
2810: * another truncate extends the file - this is desired though).
2811: */
2812: isize = i_size_read(inode);
2813: if (unlikely(iocb->ki_pos >= isize))
2814: goto put_folios;
2815: end_offset = min_t(loff_t, isize, iocb->ki_pos + iter->count);
2816:
2817: /*
2818: * Once we start copying data, we don't want to be touching any
2819: * cachelines that might be contended:
2820: */
2821: writably_mapped = mapping_writably_mapped(mapping);
2822:
2823: /*
2824: * When a read accesses the same folio several times, only
2825: * mark it as accessed the first time.
2826: */
2827: if (!pos_same_folio(iocb->ki_pos, last_pos - 1,
2828: fbatch.folios[0]))
2829: folio_mark_accessed(fbatch.folios[0]);
2830:
2831: for (i = 0; i < folio_batch_count(&fbatch); i++) {
2832: struct folio *folio = fbatch.folios[i];
2833: size_t fsize = folio_size(folio);
2834: size_t offset = iocb->ki_pos & (fsize - 1);
2835: size_t bytes = min_t(loff_t, end_offset - iocb->ki_pos,
2836: fsize - offset);
2837: size_t copied;
2838:
2839: if (end_offset < folio_pos(folio))
2840: break;
2841: if (i > 0)
2842: folio_mark_accessed(folio);
2843: /*
2844: * If users can be writing to this folio using arbitrary
2845: * virtual addresses, take care of potential aliasing
2846: * before reading the folio on the kernel side.
2847: */
2848: if (writably_mapped)
2849: flush_dcache_folio(folio);
2850:
2851: copied = copy_folio_to_iter(folio, offset, bytes, iter);
2852:
2853: already_read += copied;
2854: iocb->ki_pos += copied;
2855: last_pos = iocb->ki_pos;
2856:
2857: if (copied < bytes) {
2858: error = -EFAULT;
2859: break;
2860: }
2861: }
2862: put_folios:
2863: for (i = 0; i < folio_batch_count(&fbatch); i++) {
2864: struct folio *folio = fbatch.folios[i];
2865:
2866: filemap_end_dropbehind_read(folio);
2867: folio_put(folio);
2868: }
2869: folio_batch_init(&fbatch);
2870: } while (iov_iter_count(iter) && iocb->ki_pos < isize && !error);
2871:
2872: file_accessed(filp);
2873: ra->prev_pos = last_pos;
2874: return already_read ? already_read : error;
2875: }
SourceFile:mm/filemap.c]
|
| 13/2 |
2026/02/23 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 9978
output: 41
thoughts: 113
Thoughts: **Analyzing Readahead and Locking in the File System**
Okay, so I'm digging into this `filemap_read` function, and I see this line, `ra->prev_pos = last_pos;` (line 2873). The thing that jumps out is that `ra->prev_pos` is being updated without any apparent locking. Now, that's interesting. My immediate assumption, given the context, is that `file_ra_state` (the readahead state) isn't generally protected by locks. The reason is likely for performance. It’s just hints, right? It's optimization, not crucial data. We want it fast.
But, hang on a second. I need to make sure I haven't missed something. I need to trace this back. `filemap_get_pages` is a likely candidate. It calls either `page_cache_sync_readahead` or `page_cache_async_readahead`. I need to check those calls. Let's dig into those and see if there is any locking being done there that might indirectly synchronize access. Gotta be sure I haven't missed anything.
|
| 14/2 |
2026/02/23 14:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/filemap.c IncludeLines:true Name:filemap_get_pages]
Results: map[SourceCode:2662: static int filemap_get_pages(struct kiocb *iocb, size_t count,
2663: struct folio_batch *fbatch, bool need_uptodate)
2664: {
2665: struct file *filp = iocb->ki_filp;
2666: struct address_space *mapping = filp->f_mapping;
2667: pgoff_t index = iocb->ki_pos >> PAGE_SHIFT;
2668: pgoff_t last_index;
2669: struct folio *folio;
2670: unsigned int flags;
2671: int err = 0;
2672:
2673: /* "last_index" is the index of the folio beyond the end of the read */
2674: last_index = round_up(iocb->ki_pos + count,
2675: mapping_min_folio_nrbytes(mapping)) >> PAGE_SHIFT;
2676: retry:
2677: if (fatal_signal_pending(current))
2678: return -EINTR;
2679:
2680: filemap_get_read_batch(mapping, index, last_index - 1, fbatch);
2681: if (!folio_batch_count(fbatch)) {
2682: DEFINE_READAHEAD(ractl, filp, &filp->f_ra, mapping, index);
2683:
2684: if (iocb->ki_flags & IOCB_NOIO)
2685: return -EAGAIN;
2686: if (iocb->ki_flags & IOCB_NOWAIT)
2687: flags = memalloc_noio_save();
2688: if (iocb->ki_flags & IOCB_DONTCACHE)
2689: ractl.dropbehind = 1;
2690: page_cache_sync_ra(&ractl, last_index - index);
2691: if (iocb->ki_flags & IOCB_NOWAIT)
2692: memalloc_noio_restore(flags);
2693: filemap_get_read_batch(mapping, index, last_index - 1, fbatch);
2694: }
2695: if (!folio_batch_count(fbatch)) {
2696: err = filemap_create_folio(iocb, fbatch);
2697: if (err == AOP_TRUNCATED_PAGE)
2698: goto retry;
2699: return err;
2700: }
2701:
2702: folio = fbatch->folios[folio_batch_count(fbatch) - 1];
2703: if (folio_test_readahead(folio)) {
2704: err = filemap_readahead(iocb, filp, mapping, folio, last_index);
2705: if (err)
2706: goto err;
2707: }
2708: if (!folio_test_uptodate(folio)) {
2709: if (folio_batch_count(fbatch) > 1) {
2710: err = -EAGAIN;
2711: goto err;
2712: }
2713: err = filemap_update_page(iocb, mapping, count, folio,
2714: need_uptodate);
2715: if (err)
2716: goto err;
2717: }
2718:
2719: trace_mm_filemap_get_pages(mapping, index, last_index - 1);
2720: return 0;
2721: err:
2722: if (err < 0)
2723: folio_put(folio);
2724: if (likely(--fbatch->nr))
2725: return 0;
2726: if (err == AOP_TRUNCATED_PAGE)
2727: goto retry;
2728: return err;
2729: }
SourceFile:mm/filemap.c]
|
| 15/2 |
2026/02/23 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 11519
output: 42
thoughts: 53
Thoughts: Okay, let's dive into this. Here's how I see this playing out:
**Diving into the File System Readahead Mechanism**
Alright, so I'm thinking about the readahead mechanisms in the kernel, specifically how `page_cache_sync_ra` and `filemap_readahead` are implemented. It's clear that they both leverage the `&filp->f_ra` structure. My instinct now is to start by focusing on `page_cache_sync_ra`. I want to understand how it's using the file readahead data structure. Let's see what's happening under the hood here. I'll need to confirm how the data structure is being used by the former to manage file metadata and read requests.
|
| 16/2 |
2026/02/23 14:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/readahead.c IncludeLines:true Name:page_cache_sync_ra]
Results: map[SourceCode: 557: void page_cache_sync_ra(struct readahead_control *ractl,
558: unsigned long req_count)
559: {
560: pgoff_t index = readahead_index(ractl);
561: bool do_forced_ra = ractl->file && (ractl->file->f_mode & FMODE_RANDOM);
562: struct file_ra_state *ra = ractl->ra;
563: unsigned long max_pages, contig_count;
564: pgoff_t prev_index, miss;
565:
566: trace_page_cache_sync_ra(ractl->mapping->host, index, ra, req_count);
567: /*
568: * Even if readahead is disabled, issue this request as readahead
569: * as we'll need it to satisfy the requested range. The forced
570: * readahead will do the right thing and limit the read to just the
571: * requested range, which we'll set to 1 page for this case.
572: */
573: if (!ra->ra_pages || blk_cgroup_congested()) {
574: if (!ractl->file)
575: return;
576: req_count = 1;
577: do_forced_ra = true;
578: }
579:
580: /* be dumb */
581: if (do_forced_ra) {
582: force_page_cache_ra(ractl, req_count);
583: return;
584: }
585:
586: max_pages = ractl_max_pages(ractl, req_count);
587: prev_index = (unsigned long long)ra->prev_pos >> PAGE_SHIFT;
588: /*
589: * A start of file, oversized read, or sequential cache miss:
590: * trivial case: (index - prev_index) == 1
591: * unaligned reads: (index - prev_index) == 0
592: */
593: if (!index || req_count > max_pages || index - prev_index <= 1UL) {
594: ra->start = index;
595: ra->size = get_init_ra_size(req_count, max_pages);
596: ra->async_size = ra->size > req_count ? ra->size - req_count :
597: ra->size >> 1;
598: goto readit;
599: }
600:
601: /*
602: * Query the page cache and look for the traces(cached history pages)
603: * that a sequential stream would leave behind.
604: */
605: rcu_read_lock();
606: miss = page_cache_prev_miss(ractl->mapping, index - 1, max_pages);
607: rcu_read_unlock();
608: contig_count = index - miss - 1;
609: /*
610: * Standalone, small random read. Read as is, and do not pollute the
611: * readahead state.
612: */
613: if (contig_count <= req_count) {
614: do_page_cache_ra(ractl, req_count, 0);
615: return;
616: }
617: /*
618: * File cached from the beginning:
619: * it is a strong indication of long-run stream (or whole-file-read)
620: */
621: if (miss == ULONG_MAX)
622: contig_count *= 2;
623: ra->start = index;
624: ra->size = min(contig_count + req_count, max_pages);
625: ra->async_size = 1;
626: readit:
627: ra->order = 0;
628: ractl->_index = ra->start;
629: page_cache_ra_order(ractl, ra);
630: }
SourceFile:mm/readahead.c]
|
| 17/2 |
2026/02/23 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 13012
output: 17
thoughts: 1006
Thoughts: **Analyzing the Read-Ahead Size Data Race**
Okay, so I'm looking at this data race report concerning the `file_ra_state` struct, specifically the `ra_pages` field. It seems that `ra->start`, `ra->size`, and `ra->async_size` are also updated without locks, but the report explicitly highlights the race on `ra->ra_pages /= 4` within the `shrink_readahead_size_eio` function. This function kicks in when a read operation encounters an I/O error (EIO).
The core of the issue is that multiple threads, potentially reading from the same file, might simultaneously trigger `shrink_readahead_size_eio`. Each thread attempts to reduce `ra_pages` to avoid retry loops on bad media.
Thinking this through, if `ra_pages` is initially 32, Thread 1 could read 32, calculate 32/4 = 8 and write 8. Simultaneously, Thread 2 could do the same, reading 32, computing 32/4 = 8, and *also* writing 8. If the threads were serialized, the second thread would have seen 8 as the current value and written 2. The race here means the reduction might be less aggressive than intended.
I'm wondering, could `ra_pages` end up in some weird state? It's an unsigned integer, and the `/= 4` is a read-modify-write. On x86, perhaps this could be a single instruction, but KCSAN flagged it as a data race, which is probably a good sign that the compiler isn't making it atomic.
The process is: load `ra->ra_pages`, shift it right by 2 (or divide by 4), and then store it back. If two threads hit this simultaneously, we definitely risk losing one of the divisions. However, the worst-case scenario is `ra_pages` becoming 0, effectively disabling readahead.
But here is where things get interesting. In the code, `page_cache_sync_ra` handles the case where `ra_pages == 0`:
```c
if (!ra->ra_pages || blk_cgroup_congested()) {
if (!ractl->file)
return;
req_count = 1;
do_forced_ra = true;
}
```
If `ra_pages` is 0, it forces a single-page read. So, even if the race causes `ra_pages` to become 0, the code handles it gracefully, effectively reverting to a more conservative strategy.
Looking at the report, the address `0xffff8881670352e0` is accessed with a 4-byte read-write. The report says the value changed from 0x20 (32) to 0x08 (8), which aligns with the division-by-four logic. In this example, both threads did the division independently (and were very unlucky to write the same value).
The fact that the race is occurring on a shared `struct file` is expected given multiple threads potentially using the same file descriptor.
Where else is `ra_pages` modified? It's initialized from the BDI (Block Device Information) during `file_ra_state_init`:
```c
void file_ra_state_init(struct file_ra_state *ra, struct address_space *mapping)
{
ra->ra_pages = inode_to_bdi(mapping->host)->ra_pages;
ra->prev_pos = -1;
}
```
And `shrink_readahead_size_eio` looks to be the only function that decreases it like this.
Okay, let's grep to be absolutely certain...
|
| 18/2 |
2026/02/23 14:05 |
tool |
grepper |
0m
Args:
Results: map[Output:
Full output is too long, showing 500 out of 553 lines.
Use more precise expression if possible.
[arch/x86/xen/enlighten_pvh.c=70=static void __init pvh_reserve_extra_memory(void)
--
arch/x86/xen/enlighten_pvh.c-72- struct boot_params *bootp = &boot_params;
arch/x86/xen/enlighten_pvh.c:73: unsigned int i, ram_pages = 0, extra_pages;
arch/x86/xen/enlighten_pvh.c-74-
--
arch/x86/xen/enlighten_pvh.c-83- /* Max amount of extra memory. */
arch/x86/xen/enlighten_pvh.c:84: extra_pages = EXTRA_MEM_RATIO * ram_pages;
arch/x86/xen/enlighten_pvh.c-85-
--
arch/x86/xen/enlighten_pvh.c-89- */
arch/x86/xen/enlighten_pvh.c:90: for (i = 0; i < bootp->e820_entries && extra_pages; i++) {
arch/x86/xen/enlighten_pvh.c-91- struct boot_e820_entry *e = &bootp->e820_table[i];
--
arch/x86/xen/enlighten_pvh.c-96-
arch/x86/xen/enlighten_pvh.c:97: pages = min(extra_pages,
arch/x86/xen/enlighten_pvh.c-98- PFN_DOWN(e->addr + e->size) - PFN_UP(e->addr));
--
arch/x86/xen/enlighten_pvh.c-117- e->type = E820_TYPE_RAM;
arch/x86/xen/enlighten_pvh.c:118: extra_pages -= pages;
arch/x86/xen/enlighten_pvh.c-119-
--
arch/x86/xen/setup.c=789=char * __init xen_memory_setup(void)
--
arch/x86/xen/setup.c-796- unsigned long max_pages;
arch/x86/xen/setup.c:797: unsigned long extra_pages = 0;
arch/x86/xen/setup.c-798- unsigned long maxmem_pages;
--
arch/x86/xen/setup.c-881- if (max_pages > ini_nr_pages)
arch/x86/xen/setup.c:882: extra_pages += max_pages - ini_nr_pages;
arch/x86/xen/setup.c-883-
--
arch/x86/xen/setup.c-891- maxmem_pages = EXTRA_MEM_RATIO * min(ini_nr_pages, PFN_DOWN(MAXMEM));
arch/x86/xen/setup.c:892: extra_pages = min3(maxmem_pages, extra_pages, max_pages - ini_nr_pages);
arch/x86/xen/setup.c-893- i = 0;
--
arch/x86/xen/setup.c-907- chunk_size = min(size, mem_end - addr);
arch/x86/xen/setup.c:908: } else if (extra_pages) {
arch/x86/xen/setup.c:909: chunk_size = min(size, PFN_PHYS(extra_pages));
arch/x86/xen/setup.c-910- pfn_s = PFN_UP(addr);
arch/x86/xen/setup.c-911- n_pfns = PFN_DOWN(addr + chunk_size) - pfn_s;
arch/x86/xen/setup.c:912: extra_pages -= n_pfns;
arch/x86/xen/setup.c-913- xen_add_extra_mem(pfn_s, n_pfns);
--
block/blk-settings.c=63=void blk_apply_bdi_limits(struct backing_dev_info *bdi,
--
block/blk-settings.c-81-
block/blk-settings.c:82: bdi->ra_pages = max3(bdi->ra_pages,
block/blk-settings.c-83- io_opt * 2 >> PAGE_SHIFT,
--
block/blk-sysfs.c=170=static ssize_t queue_ra_show(struct gendisk *disk, char *page)
--
block/blk-sysfs.c-174- mutex_lock(&disk->queue->limits_lock);
block/blk-sysfs.c:175: ret = queue_var_show(disk->bdi->ra_pages << (PAGE_SHIFT - 10), page);
block/blk-sysfs.c-176- mutex_unlock(&disk->queue->limits_lock);
--
block/blk-sysfs.c=182=queue_ra_store(struct gendisk *disk, const char *page, size_t count)
--
block/blk-sysfs.c-191- /*
block/blk-sysfs.c:192: * The ->ra_pages change below is protected by ->limits_lock because it
block/blk-sysfs.c-193- * is usually calculated from the queue limits by
--
block/blk-sysfs.c-195- *
block/blk-sysfs.c:196: * bdi->ra_pages reads are not serialized against bdi->ra_pages writes.
block/blk-sysfs.c:197: * Use WRITE_ONCE() to write bdi->ra_pages once.
block/blk-sysfs.c-198- */
block/blk-sysfs.c-199- mutex_lock(&q->limits_lock);
block/blk-sysfs.c:200: WRITE_ONCE(disk->bdi->ra_pages, ra_kb >> (PAGE_SHIFT - 10));
block/blk-sysfs.c-201- mutex_unlock(&q->limits_lock);
--
block/ioctl.c=645=static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode,
--
block/ioctl.c-699- return -EACCES;
block/ioctl.c:700: bdev->bd_disk->bdi->ra_pages = (arg * 512) / PAGE_SIZE;
block/ioctl.c-701- return 0;
--
block/ioctl.c=744=long blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg)
--
block/ioctl.c-763- return put_long(argp,
block/ioctl.c:764: (bdev->bd_disk->bdi->ra_pages * PAGE_SIZE) / 512);
block/ioctl.c-765- case BLKGETSIZE:
--
block/ioctl.c=804=long compat_blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg)
--
block/ioctl.c-824- return compat_put_long(argp,
block/ioctl.c:825: (bdev->bd_disk->bdi->ra_pages * PAGE_SIZE) / 512);
block/ioctl.c-826- case BLKGETSIZE:
--
drivers/char/tpm/xen-tpmfront.c=129=static size_t shr_data_offset(struct vtpm_shared_page *shr)
drivers/char/tpm/xen-tpmfront.c-130-{
drivers/char/tpm/xen-tpmfront.c:131: return struct_size(shr, extra_pages, shr->nr_extra_pages);
drivers/char/tpm/xen-tpmfront.c-132-}
--
drivers/gpu/drm/ttm/tests/ttm_tt_test.c=12=struct ttm_tt_test_case {
--
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-14- u32 size;
drivers/gpu/drm/ttm/tests/ttm_tt_test.c:15: u32 extra_pages_num;
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-16-};
--
drivers/gpu/drm/ttm/tests/ttm_tt_test.c=18=static const struct ttm_tt_test_case ttm_tt_init_basic_cases[] = {
--
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-25- .size = SZ_4K,
drivers/gpu/drm/ttm/tests/ttm_tt_test.c:26: .extra_pages_num = 1,
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-27- },
--
drivers/gpu/drm/ttm/tests/ttm_tt_test.c=39=static void ttm_tt_init_basic(struct kunit *test)
--
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-45- enum ttm_caching caching = ttm_cached;
drivers/gpu/drm/ttm/tests/ttm_tt_test.c:46: u32 extra_pages = params->extra_pages_num;
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-47- int num_pages = params->size >> PAGE_SHIFT;
--
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-54-
drivers/gpu/drm/ttm/tests/ttm_tt_test.c:55: err = ttm_tt_init(tt, bo, page_flags, caching, extra_pages);
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-56- KUNIT_ASSERT_EQ(test, err, 0);
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-57-
drivers/gpu/drm/ttm/tests/ttm_tt_test.c:58: KUNIT_ASSERT_EQ(test, tt->num_pages, num_pages + extra_pages);
drivers/gpu/drm/ttm/tests/ttm_tt_test.c-59-
--
drivers/gpu/drm/ttm/ttm_tt.c=154=static void ttm_tt_init_fields(struct ttm_tt *ttm,
--
drivers/gpu/drm/ttm/ttm_tt.c-157- enum ttm_caching caching,
drivers/gpu/drm/ttm/ttm_tt.c:158: unsigned long extra_pages)
drivers/gpu/drm/ttm/ttm_tt.c-159-{
drivers/gpu/drm/ttm/ttm_tt.c:160: ttm->num_pages = (PAGE_ALIGN(bo->base.size) >> PAGE_SHIFT) + extra_pages;
drivers/gpu/drm/ttm/ttm_tt.c-161- ttm->page_flags = page_flags;
--
drivers/gpu/drm/ttm/ttm_tt.c=170=int ttm_tt_init(struct ttm_tt *ttm, struct ttm_buffer_object *bo,
drivers/gpu/drm/ttm/ttm_tt.c-171- uint32_t page_flags, enum ttm_caching caching,
drivers/gpu/drm/ttm/ttm_tt.c:172: unsigned long extra_pages)
drivers/gpu/drm/ttm/ttm_tt.c-173-{
drivers/gpu/drm/ttm/ttm_tt.c:174: ttm_tt_init_fields(ttm, bo, page_flags, caching, extra_pages);
drivers/gpu/drm/ttm/ttm_tt.c-175-
--
drivers/gpu/drm/xe/xe_bo.c=472=static struct ttm_tt *xe_ttm_tt_create(struct ttm_buffer_object *ttm_bo,
--
drivers/gpu/drm/xe/xe_bo.c-478- struct ttm_tt *tt;
drivers/gpu/drm/xe/xe_bo.c:479: unsigned long extra_pages;
drivers/gpu/drm/xe/xe_bo.c-480- enum ttm_caching caching = ttm_cached;
--
drivers/gpu/drm/xe/xe_bo.c-488-
drivers/gpu/drm/xe/xe_bo.c:489: extra_pages = 0;
drivers/gpu/drm/xe/xe_bo.c-490- if (xe_bo_needs_ccs_pages(bo))
drivers/gpu/drm/xe/xe_bo.c:491: extra_pages = DIV_ROUND_UP(xe_device_ccs_bytes(xe, xe_bo_size(bo)),
drivers/gpu/drm/xe/xe_bo.c-492- PAGE_SIZE);
--
drivers/gpu/drm/xe/xe_bo.c-534-
drivers/gpu/drm/xe/xe_bo.c:535: err = ttm_tt_init(tt, &bo->ttm, page_flags, caching, extra_pages);
drivers/gpu/drm/xe/xe_bo.c-536- if (err) {
--
drivers/mtd/mtdcore.c=2560=static struct backing_dev_info * __init mtd_bdi_init(const char *name)
--
drivers/mtd/mtdcore.c-2567- return ERR_PTR(-ENOMEM);
drivers/mtd/mtdcore.c:2568: bdi->ra_pages = 0;
drivers/mtd/mtdcore.c-2569- bdi->io_pages = 0;
--
fs/9p/vfs_super.c=34=static int v9fs_fill_super(struct super_block *sb)
--
fs/9p/vfs_super.c-58- if (!v9ses->cache) {
fs/9p/vfs_super.c:59: sb->s_bdi->ra_pages = 0;
fs/9p/vfs_super.c-60- sb->s_bdi->io_pages = 0;
fs/9p/vfs_super.c-61- } else {
fs/9p/vfs_super.c:62: sb->s_bdi->ra_pages = v9ses->maxdata >> PAGE_SHIFT;
fs/9p/vfs_super.c-63- sb->s_bdi->io_pages = v9ses->maxdata >> PAGE_SHIFT;
--
fs/btrfs/disk-io.c=3283=int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_devices)
--
fs/btrfs/disk-io.c-3451-
fs/btrfs/disk-io.c:3452: sb->s_bdi->ra_pages *= btrfs_super_num_devices(disk_super);
fs/btrfs/disk-io.c:3453: sb->s_bdi->ra_pages = max(sb->s_bdi->ra_pages, SZ_4M / PAGE_SIZE);
fs/btrfs/disk-io.c-3454-
--
fs/ceph/addr.c=168=static void ceph_netfs_expand_readahead(struct netfs_io_request *rreq)
--
fs/ceph/addr.c-172- struct ceph_file_layout *lo = &ci->i_layout;
fs/ceph/addr.c:173: unsigned long max_pages = inode->i_sb->s_bdi->ra_pages;
fs/ceph/addr.c-174- loff_t end = rreq->start + rreq->len, new_end;
--
fs/ceph/addr.c-183- else
fs/ceph/addr.c:184: max_pages = priv->file_ra_pages;
fs/ceph/addr.c-185-
--
fs/ceph/addr.c=458=static int ceph_init_request(struct netfs_io_request *rreq, struct file *file)
--
fs/ceph/addr.c-480-
fs/ceph/addr.c:481: priv->file_ra_pages = file->f_ra.ra_pages;
fs/ceph/addr.c-482- priv->file_ra_disabled = file->f_mode & FMODE_RANDOM;
--
fs/ceph/super.c=1273=static int ceph_setup_bdi(struct super_block *sb, struct ceph_fs_client *fsc)
--
fs/ceph/super.c-1281-
fs/ceph/super.c:1282: /* set ra_pages based on rasize mount option? */
fs/ceph/super.c:1283: sb->s_bdi->ra_pages = fsc->mount_options->rasize >> PAGE_SHIFT;
fs/ceph/super.c-1284-
--
fs/ceph/super.h=500=struct ceph_netfs_request_data {
--
fs/ceph/super.h-504- * Maximum size of a file readahead request.
fs/ceph/super.h:505: * The fadvise could update the bdi's default ra_pages.
fs/ceph/super.h-506- */
fs/ceph/super.h:507: unsigned int file_ra_pages;
fs/ceph/super.h-508-
--
fs/erofs/dir.c=47=static int erofs_readdir(struct file *f, struct dir_context *ctx)
--
fs/erofs/dir.c-54- unsigned int ofs = erofs_blkoff(sb, ctx->pos);
fs/erofs/dir.c:55: pgoff_t ra_pages = DIV_ROUND_UP_POW2(
fs/erofs/dir.c-56- EROFS_I_SB(dir)->dir_ra_bytes, PAGE_SIZE);
--
fs/erofs/dir.c-72- /* readahead blocks to enhance performance for large directories */
fs/erofs/dir.c:73: if (ra_pages) {
fs/erofs/dir.c-74- pgoff_t idx = DIV_ROUND_UP_POW2(ctx->pos, PAGE_SIZE);
fs/erofs/dir.c:75: pgoff_t pages = min(nr_pages - idx, ra_pages);
fs/erofs/dir.c-76-
--
fs/exfat/balloc.c=73=static int exfat_allocate_bitmap(struct super_block *sb,
--
fs/exfat/balloc.c-104- sector = exfat_cluster_to_sector(sbi, sbi->map_clu);
fs/exfat/balloc.c:105: max_ra_count = min(sb->s_bdi->ra_pages, sb->s_bdi->io_pages) <<
fs/exfat/balloc.c-106- (PAGE_SHIFT - sb->s_blocksize_bits);
--
fs/f2fs/f2fs.h=1672=struct f2fs_sb_info {
--
fs/f2fs/f2fs.h-1910-
fs/f2fs/f2fs.h:1911: unsigned long seq_file_ra_mul; /* multiplier for ra_pages of seq. files in fadvise */
fs/f2fs/f2fs.h-1912-
--
fs/f2fs/file.c=5283=static int f2fs_file_fadvise(struct file *filp, loff_t offset, loff_t len,
--
fs/f2fs/file.c-5301- bdi = inode_to_bdi(mapping->host);
fs/f2fs/file.c:5302: filp->f_ra.ra_pages = bdi->ra_pages *
fs/f2fs/file.c-5303- F2FS_I_SB(inode)->seq_file_ra_mul;
--
fs/fat/fatent.c=647=static void fat_ra_init(struct super_block *sb, struct fatent_ra *ra,
--
fs/fat/fatent.c-654- /*
fs/fat/fatent.c:655: * This is the sequential read, so ra_pages * 2 (but try to
fs/fat/fatent.c-656- * align the optimal hardware IO size).
--
fs/fat/fatent.c-658- */
fs/fat/fatent.c:659: unsigned long ra_pages = sb->s_bdi->ra_pages;
fs/fat/fatent.c-660- unsigned int reada_blocks;
--
fs/fat/fatent.c-664-
fs/fat/fatent.c:665: if (ra_pages > sb->s_bdi->io_pages)
fs/fat/fatent.c:666: ra_pages = rounddown(ra_pages, sb->s_bdi->io_pages);
fs/fat/fatent.c:667: reada_blocks = ra_pages << (PAGE_SHIFT - sb->s_blocksize_bits + 1);
fs/fat/fatent.c-668-
--
fs/fuse/inode.c=1319=static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
--
fs/fuse/inode.c-1329- else {
fs/fuse/inode.c:1330: unsigned long ra_pages;
fs/fuse/inode.c-1331- unsigned int timeout = 0;
--
fs/fuse/inode.c-1340-
fs/fuse/inode.c:1341: ra_pages = arg->max_readahead / PAGE_SIZE;
fs/fuse/inode.c-1342- if (flags & FUSE_ASYNC_READ)
--
fs/fuse/inode.c-1459- } else {
fs/fuse/inode.c:1460: ra_pages = fc->max_read / PAGE_SIZE;
fs/fuse/inode.c-1461- fc->no_lock = 1;
--
fs/fuse/inode.c-1466-
fs/fuse/inode.c:1467: fm->sb->s_bdi->ra_pages =
fs/fuse/inode.c:1468: min(fm->sb->s_bdi->ra_pages, ra_pages);
fs/fuse/inode.c-1469- fc->minor = arg->minor;
--
fs/fuse/inode.c=1485=static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
--
fs/fuse/inode.c-1493- ia->in.minor = FUSE_KERNEL_MINOR_VERSION;
fs/fuse/inode.c:1494: ia->in.max_readahead = fm->sb->s_bdi->ra_pages * PAGE_SIZE;
fs/fuse/inode.c-1495- flags =
--
fs/ntfs3/file.c=345=static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
--
fs/ntfs3/file.c-377- /* Turn off readahead for compressed files. */
fs/ntfs3/file.c:378: file->f_ra.ra_pages = 0;
fs/ntfs3/file.c-379- }
--
fs/ntfs3/file.c=924=static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
--
fs/ntfs3/file.c-941- /* Turn off readahead for compressed files. */
fs/ntfs3/file.c:942: file->f_ra.ra_pages = 0;
fs/ntfs3/file.c-943- }
--
fs/ntfs3/file.c=961=static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos,
--
fs/ntfs3/file.c-973- /* Turn off readahead for compressed files. */
fs/ntfs3/file.c:974: in->f_ra.ra_pages = 0;
fs/ntfs3/file.c-975- }
--
fs/smb/client/cifsfs.c=227=cifs_read_super(struct super_block *sb)
--
fs/smb/client/cifsfs.c-288- if (cifs_sb->ctx->rasize)
fs/smb/client/cifsfs.c:289: sb->s_bdi->ra_pages = cifs_sb->ctx->rasize / PAGE_SIZE;
fs/smb/client/cifsfs.c-290- else
fs/smb/client/cifsfs.c:291: sb->s_bdi->ra_pages = 2 * (cifs_sb->ctx->rsize / PAGE_SIZE);
fs/smb/client/cifsfs.c-292-
--
fs/smb/server/vfs.c=896=void ksmbd_vfs_set_fadvise(struct file *filp, __le32 option)
--
fs/smb/server/vfs.c-907- } else if (option & FILE_SEQUENTIAL_ONLY_LE) {
fs/smb/server/vfs.c:908: filp->f_ra.ra_pages = inode_to_bdi(mapping->host)->ra_pages * 2;
fs/smb/server/vfs.c-909- spin_lock(&filp->f_lock);
--
fs/ubifs/super.c=2131=static int ubifs_fill_super(struct super_block *sb, struct fs_context *fc)
--
fs/ubifs/super.c-2164- *
fs/ubifs/super.c:2165: * Read-ahead will be disabled because @sb->s_bdi->ra_pages is 0. Also
fs/ubifs/super.c-2166- * @sb->s_bdi->capabilities are initialized to 0 so there won't be any
--
fs/ubifs/super.c-2172- goto out_close;
fs/ubifs/super.c:2173: sb->s_bdi->ra_pages = 0;
fs/ubifs/super.c-2174- sb->s_bdi->io_pages = 0;
--
fs/vboxsf/super.c=111=static int vboxsf_fill_super(struct super_block *sb, struct fs_context *fc)
--
fs/vboxsf/super.c-158- goto fail_free;
fs/vboxsf/super.c:159: sb->s_bdi->ra_pages = 0;
fs/vboxsf/super.c-160- sb->s_bdi->io_pages = 0;
--
include/drm/ttm/ttm_tt.h=191=int ttm_tt_create(struct ttm_buffer_object *bo, bool zero_alloc);
--
include/drm/ttm/ttm_tt.h-199- * @caching: the desired caching state of the pages
include/drm/ttm/ttm_tt.h:200: * @extra_pages: Extra pages needed for the driver.
include/drm/ttm/ttm_tt.h-201- *
--
include/drm/ttm/ttm_tt.h=207=int ttm_tt_init(struct ttm_tt *ttm, struct ttm_buffer_object *bo,
include/drm/ttm/ttm_tt.h-208- uint32_t page_flags, enum ttm_caching caching,
include/drm/ttm/ttm_tt.h:209: unsigned long extra_pages);
include/drm/ttm/ttm_tt.h-210-int ttm_sg_tt_init(struct ttm_tt *ttm_dma, struct ttm_buffer_object *bo,
--
include/linux/backing-dev-defs.h=168=struct backing_dev_info {
--
include/linux/backing-dev-defs.h-172- /* max readahead in PAGE_SIZE units */
include/linux/backing-dev-defs.h:173: unsigned long __data_racy ra_pages;
include/linux/backing-dev-defs.h-174-
--
include/linux/fs.h=1188=struct fown_struct {
--
include/linux/fs.h-1203- * the first of these pages is accessed.
include/linux/fs.h:1204: * @ra_pages: Maximum size of a readahead request, copied from the bdi.
include/linux/fs.h-1205- * @order: Preferred folio order used for most recent readahead.
--
include/linux/fs.h=1212=struct file_ra_state {
--
include/linux/fs.h-1215- unsigned int async_size;
include/linux/fs.h:1216: unsigned int ra_pages;
include/linux/fs.h-1217- unsigned short order;
--
include/trace/events/readahead.h=43=TRACE_EVENT(page_cache_ra_order,
--
include/trace/events/readahead.h-54- __field(unsigned int, async_size)
include/trace/events/readahead.h:55: __field(unsigned int, ra_pages)
include/trace/events/readahead.h-56- ),
--
include/trace/events/readahead.h-64- __entry->async_size = ra->async_size;
include/trace/events/readahead.h:65: __entry->ra_pages = ra->ra_pages;
include/trace/events/readahead.h-66- ),
--
include/trace/events/readahead.h-68- TP_printk(
include/trace/events/readahead.h:69: "dev=%d:%d ino=%lx index=%lu order=%u size=%u async_size=%u ra_pages=%u",
include/trace/events/readahead.h-70- MAJOR(__entry->s_dev), MINOR(__entry->s_dev), __entry->i_ino,
include/trace/events/readahead.h-71- __entry->index, __entry->order, __entry->size,
include/trace/events/readahead.h:72: __entry->async_size, __entry->ra_pages
include/trace/events/readahead.h-73- )
--
include/trace/events/readahead.h=76=DECLARE_EVENT_CLASS(page_cache_ra_op,
--
include/trace/events/readahead.h-88- __field(unsigned int, async_size)
include/trace/events/readahead.h:89: __field(unsigned int, ra_pages)
include/trace/events/readahead.h-90- __field(unsigned int, mmap_miss)
--
include/trace/events/readahead.h-101- __entry->async_size = ra->async_size;
include/trace/events/readahead.h:102: __entry->ra_pages = ra->ra_pages;
include/trace/events/readahead.h-103- __entry->mmap_miss = ra->mmap_miss;
--
include/trace/events/readahead.h-108- TP_printk(
include/trace/events/readahead.h:109: "dev=%d:%d ino=%lx index=%lu req_count=%lu order=%u size=%u async_size=%u ra_pages=%u mmap_miss=%u prev_pos=%lld",
include/trace/events/readahead.h-110- MAJOR(__entry->s_dev), MINOR(__entry->s_dev), __entry->i_ino,
include/trace/events/readahead.h-111- __entry->index, __entry->req_count, __entry->order,
include/trace/events/readahead.h:112: __entry->size, __entry->async_size, __entry->ra_pages,
include/trace/events/readahead.h-113- __entry->mmap_miss, __entry->prev_pos
--
include/xen/interface/io/tpmif.h=41=struct vtpm_shared_page {
--
include/xen/interface/io/tpmif.h-47-
include/xen/interface/io/tpmif.h:48: uint8_t nr_extra_pages; /* extra pages for long packets; may be zero */
include/xen/interface/io/tpmif.h:49: uint32_t extra_pages[]; /* grant IDs; length in nr_extra_pages */
include/xen/interface/io/tpmif.h-50-};
--
kernel/kexec_core.c=330=static struct page *kimage_alloc_normal_control_pages(struct kimage *image,
--
kernel/kexec_core.c-345- */
kernel/kexec_core.c:346: struct list_head extra_pages;
kernel/kexec_core.c-347- struct page *pages;
--
kernel/kexec_core.c-350- count = 1 << order;
kernel/kexec_core.c:351: INIT_LIST_HEAD(&extra_pages);
kernel/kexec_core.c-352-
--
kernel/kexec_core.c-367- kimage_is_destination_range(image, addr, eaddr)) {
kernel/kexec_core.c:368: list_add(&pages->lru, &extra_pages);
kernel/kexec_core.c-369- pages = NULL;
--
kernel/kexec_core.c-390- */
kernel/kexec_core.c:391: kimage_free_page_list(&extra_pages);
kernel/kexec_core.c-392-
--
kernel/kexec_core.c=519=static int kimage_add_page(struct kimage *image, unsigned long page)
--
kernel/kexec_core.c-526-
kernel/kexec_core.c:527:static void kimage_free_extra_pages(struct kimage *image)
kernel/kexec_core.c-528-{
--
kernel/kexec_core.c=576=void kimage_free(struct kimage *image)
--
kernel/kexec_core.c-590-
kernel/kexec_core.c:591: kimage_free_extra_pages(image);
kernel/kexec_core.c-592- for_each_kimage_entry(image, ptr, entry) {
--
mm/backing-dev.c=260=static ssize_t read_ahead_kb_store(struct device *dev,
--
mm/backing-dev.c-271-
mm/backing-dev.c:272: bdi->ra_pages = read_ahead_kb >> (PAGE_SHIFT - 10);
mm/backing-dev.c-273-
--
mm/backing-dev.c=285=static DEVICE_ATTR_RW(name);
mm/backing-dev.c-286-
mm/backing-dev.c:287:BDI_SHOW(read_ahead_kb, K(bdi->ra_pages))
mm/backing-dev.c-288-
--
mm/backing-dev.c=1022=struct backing_dev_info *bdi_alloc(int node_id)
--
mm/backing-dev.c-1034- bdi->capabilities = BDI_CAP_WRITEBACK;
mm/backing-dev.c:1035: bdi->ra_pages = VM_READAHEAD_PAGES;
mm/backing-dev.c-1036- bdi->io_pages = VM_READAHEAD_PAGES;
--
mm/fadvise.c=31=int generic_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
--
mm/fadvise.c-79- case POSIX_FADV_NORMAL:
mm/fadvise.c:80: file->f_ra.ra_pages = bdi->ra_pages;
mm/fadvise.c-81- spin_lock(&file->f_lock);
--
mm/fadvise.c-90- case POSIX_FADV_SEQUENTIAL:
mm/fadvise.c:91: file->f_ra.ra_pages = bdi->ra_pages * 2;
mm/fadvise.c-92- spin_lock(&file->f_lock);
--
mm/filemap.c=2436=static void shrink_readahead_size_eio(struct file_ra_state *ra)
mm/filemap.c-2437-{
mm/filemap.c:2438: ra->ra_pages /= 4;
mm/filemap.c-2439-}
--
mm/filemap.c=3300=static struct file *do_sync_mmap_readahead(struct vm_fault *vmf)
--
mm/filemap.c-3323-
mm/filemap.c:3324: if (!ra->ra_pages)
mm/filemap.c-3325- return fpin;
--
mm/filemap.c-3328- fpin = maybe_unlock_mmap_for_io(vmf, fpin);
mm/filemap.c:3329: page_cache_sync_ra(&ractl, ra->ra_pages);
mm/filemap.c-3330- return fpin;
--
mm/filemap.c-3383- ra->start = max(ra->start, start);
mm/filemap.c:3384: ra_end = round_up(ra->start + ra->ra_pages, 1UL << ra->order);
mm/filemap.c-3385- ra_end = min(ra_end, end);
--
mm/filemap.c-3391- */
mm/filemap.c:3392: ra->start = max_t(long, 0, vmf->pgoff - ra->ra_pages / 2);
mm/filemap.c:3393: ra->size = ra->ra_pages;
mm/filemap.c:3394: ra->async_size = ra->ra_pages / 4;
mm/filemap.c-3395- ra->order = 0;
--
mm/filemap.c=3409=static struct file *do_async_mmap_readahead(struct vm_fault *vmf,
--
mm/filemap.c-3418- /* If we don't want any read-ahead, don't bother */
mm/filemap.c:3419: if (vmf->vma->vm_flags & VM_RAND_READ || !ra->ra_pages)
mm/filemap.c-3420- return fpin;
--
mm/filemap.c-3435- fpin = maybe_unlock_mmap_for_io(vmf, fpin);
mm/filemap.c:3436: page_cache_async_ra(&ractl, folio, ra->ra_pages);
mm/filemap.c-3437- }
--
mm/readahead.c=142=file_ra_state_init(struct file_ra_state *ra, struct address_space *mapping)
mm/readahead.c-143-{
mm/readahead.c:144: ra->ra_pages = inode_to_bdi(mapping->host)->ra_pages;
mm/readahead.c-145- ra->prev_pos = -1;
--
mm/readahead.c=342=void force_page_cache_ra(struct readahead_control *ractl,
--
mm/readahead.c-356- */
mm/readahead.c:357: max_pages = max_t(unsigned long, bdi->io_pages, ra->ra_pages);
mm/readahead.c-358- nr_to_read = min_t(unsigned long, nr_to_read, max_pages);
--
mm/readahead.c=542=static unsigned long ractl_max_pages(struct readahead_control *ractl,
--
mm/readahead.c-545- struct backing_dev_info *bdi = inode_to_bdi(ractl->mapping->host);
mm/readahead.c:546: unsigned long max_pages = ractl->ra->ra_pages;
mm/readahead.c-547-
--
]
]
|
| 19/2 |
2026/02/23 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 25651
output: 41
thoughts: 119
Thoughts: Okay, let's break this down.
**Analyzing Read-Ahead Page Updates in `mm/fadvise.c`**
Alright, I'm digging into this `mm/fadvise.c` file and I'm seeing something that immediately sparks a question. On lines 80 and 91, I notice the `file->f_ra.ra_pages` value is being updated. Specifically, the read-ahead page count is getting assigned the value of `bdi->ra_pages`. The interesting bit is how this value changes - there's a simple assignment at line 80 and then a multiplication by two at line 91.
But now I'm starting to think about concurrency... there's no explicit protection on these assignments! I see `f_lock` being acquired *after* the assignment. It's a bit of a head-scratcher. I'd expect to see some kind of mutex or atomic operation involved to prevent race conditions on this shared data. This could lead to a variety of issues, depending on how this variable is used. I need to understand the implications of this. I should revisit the context of these updates. The lack of locking here could be a problem in a multi-threaded environment. This bears a closer inspection.
|
| 20/2 |
2026/02/23 14:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/fadvise.c IncludeLines:true Name:generic_fadvise]
Results: map[SourceCode: 31: int generic_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
32: {
33: struct inode *inode;
34: struct address_space *mapping;
35: struct backing_dev_info *bdi;
36: loff_t endbyte; /* inclusive */
37: pgoff_t start_index;
38: pgoff_t end_index;
39: unsigned long nrpages;
40:
41: inode = file_inode(file);
42: if (S_ISFIFO(inode->i_mode))
43: return -ESPIPE;
44:
45: mapping = file->f_mapping;
46: if (!mapping || len < 0)
47: return -EINVAL;
48:
49: bdi = inode_to_bdi(mapping->host);
50:
51: if (IS_DAX(inode) || (bdi == &noop_backing_dev_info)) {
52: switch (advice) {
53: case POSIX_FADV_NORMAL:
54: case POSIX_FADV_RANDOM:
55: case POSIX_FADV_SEQUENTIAL:
56: case POSIX_FADV_WILLNEED:
57: case POSIX_FADV_NOREUSE:
58: case POSIX_FADV_DONTNEED:
59: /* no bad return value, but ignore advice */
60: break;
61: default:
62: return -EINVAL;
63: }
64: return 0;
65: }
66:
67: /*
68: * Careful about overflows. Len == 0 means "as much as possible". Use
69: * unsigned math because signed overflows are undefined and UBSan
70: * complains.
71: */
72: endbyte = (u64)offset + (u64)len;
73: if (!len || endbyte < len)
74: endbyte = LLONG_MAX;
75: else
76: endbyte--; /* inclusive */
77:
78: switch (advice) {
79: case POSIX_FADV_NORMAL:
80: file->f_ra.ra_pages = bdi->ra_pages;
81: spin_lock(&file->f_lock);
82: file->f_mode &= ~(FMODE_RANDOM | FMODE_NOREUSE);
83: spin_unlock(&file->f_lock);
84: break;
85: case POSIX_FADV_RANDOM:
86: spin_lock(&file->f_lock);
87: file->f_mode |= FMODE_RANDOM;
88: spin_unlock(&file->f_lock);
89: break;
90: case POSIX_FADV_SEQUENTIAL:
91: file->f_ra.ra_pages = bdi->ra_pages * 2;
92: spin_lock(&file->f_lock);
93: file->f_mode &= ~FMODE_RANDOM;
94: spin_unlock(&file->f_lock);
95: break;
96: case POSIX_FADV_WILLNEED:
97: /* First and last PARTIAL page! */
98: start_index = offset >> PAGE_SHIFT;
99: end_index = endbyte >> PAGE_SHIFT;
100:
101: /* Careful about overflow on the "+1" */
102: nrpages = end_index - start_index + 1;
103: if (!nrpages)
104: nrpages = ~0UL;
105:
106: force_page_cache_readahead(mapping, file, start_index, nrpages);
107: break;
108: case POSIX_FADV_NOREUSE:
109: spin_lock(&file->f_lock);
110: file->f_mode |= FMODE_NOREUSE;
111: spin_unlock(&file->f_lock);
112: break;
113: case POSIX_FADV_DONTNEED:
114: filemap_flush_range(mapping, offset, endbyte);
115:
116: /*
117: * First and last FULL page! Partial pages are deliberately
118: * preserved on the expectation that it is better to preserve
119: * needed memory than to discard unneeded memory.
120: */
121: start_index = (offset+(PAGE_SIZE-1)) >> PAGE_SHIFT;
122: end_index = (endbyte >> PAGE_SHIFT);
123: /*
124: * The page at end_index will be inclusively discarded according
125: * by invalidate_mapping_pages(), so subtracting 1 from
126: * end_index means we will skip the last page. But if endbyte
127: * is page aligned or is at the end of file, we should not skip
128: * that page - discarding the last page is safe enough.
129: */
130: if ((endbyte & ~PAGE_MASK) != ~PAGE_MASK &&
131: endbyte != inode->i_size - 1) {
132: /* First page is tricky as 0 - 1 = -1, but pgoff_t
133: * is unsigned, so the end_index >= start_index
134: * check below would be true and we'll discard the whole
135: * file cache which is not what was asked.
136: */
137: if (end_index == 0)
138: break;
139:
140: end_index--;
141: }
142:
143: if (end_index >= start_index) {
144: unsigned long nr_failed = 0;
145:
146: /*
147: * It's common to FADV_DONTNEED right after
148: * the read or write that instantiates the
149: * pages, in which case there will be some
150: * sitting on the local LRU cache. Try to
151: * avoid the expensive remote drain and the
152: * second cache tree walk below by flushing
153: * them out right away.
154: */
155: lru_add_drain();
156:
157: mapping_try_invalidate(mapping, start_index, end_index,
158: &nr_failed);
159:
160: /*
161: * The failures may be due to the folio being
162: * in the LRU cache of a remote CPU. Drain all
163: * caches and try again.
164: */
165: if (nr_failed) {
166: lru_add_drain_all();
167: invalidate_mapping_pages(mapping, start_index,
168: end_index);
169: }
170: }
171: break;
172: default:
173: return -EINVAL;
174: }
175: return 0;
176: }
SourceFile:mm/fadvise.c]
|
| 21/2 |
2026/02/23 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 28182
output: 21
thoughts: 2332
Thoughts: **Benign Data Race in `shrink_readahead_size_eio` and `file_ra_state`**
Alright, let's break this down. It seems the KCSAN report highlights a potential data race involving `file->f_ra.ra_pages` within the `shrink_readahead_size_eio` function. This isn't the first time I've seen concurrent updates to `file_ra_state`, and given that `ra_pages` primarily functions as a performance hint for the readahead algorithm – which is heuristic by nature, not requiring strict consistency for correctness – this race is likely benign.
The critical question here is, could this "corruption" lead to a crash? No, because `ra_pages` is a simple 4-byte `unsigned int`, and on x86-64, 4-byte aligned writes are atomic. Even if they weren't, the writes are merely divisions, so the possibility of introducing an absurd value isn't there. Initially, I thought `ra->ra_pages /= 4;` might be a read-modify-write, but even then, it's just an `unsigned int` so it would still just divide and not be wildly off.
The report also mentions other fields in `file_ra_state` being updated concurrently. Fields like `start`, `size`, and `async_size` are often updated together in `page_cache_sync_ra`, and if those become inconsistent, that is okay because they are just hints for which pages to readahead.
The specific race on `ra_pages` is to limit the readahead size. The danger is, could it become very large due to corruption? Unlikely given the division operation. Plus, `ra_pages` is typically a modest number (e.g., 32 pages), so large readahead requests wouldn't be an issue.
The race in `shrink_readahead_size_eio` means the value is *decreasing*. In a race scenario, the reduction might be less aggressive than if the threads were serialized. For example, we might see 32 -> 8 (thread 1) and 32 -> 8 (thread 2) instead of the intended 32 -> 8 -> 2. This means we could get more "frustrating retries" but no real correctness issue.
Wait, there's a comment in `include/linux/backing-dev-defs.h`:
```c
173: unsigned long __data_racy ra_pages;
```
That's for `struct backing_dev_info`, not `struct file_ra_state`.
The "attempt to access beyond end of device" warning in the report is interesting. It typically happens around the same time as `shrink_readahead_size_eio` is called. It signals that readahead is attempting to read past the device boundary, which `shrink_readahead_size_eio` *is designed to prevent*. If there's a bad block or we're near the end of the device, readahead might read past it, and the driver fails. If the race causes `ra_pages` to stay larger than it should be, we might get more of these warnings.
Could `ra_pages` somehow end up as something like `0xFFFFFFFF`? No, unlikely with the `/= 4` division.
I also checked how `ra_pages` is used to see if it could cause a buffer overflow, but it's used to calculate `nr_to_read` in `force_page_cache_ra` and similar. The number of pages is used to allocate a folio batch, but other limits (like `max_pages` from BDI) are in place. In `page_cache_sync_ra`, `max_pages` is computed, but `ra->size` is also limited by other values. The readahead code will only read as many pages as it can.
The race is specifically on `ra_pages`, an `unsigned int`, and the operation is `ra_pages /= 4`, a read-modify-write. The example change in the report was `0x20 -> 0x08`, a perfectly reasonable division.
This looks like a classic benign data race on a performance-related field. The kernel often allows these kinds of races, favoring performance.
One more thing to consider: `shrink_readahead_size_eio` is called after `folio_test_uptodate(folio)` returns false *after* `folio_wait_locked_killable(folio)`. This means the read failed, and if multiple threads are reading the same file, they might all hit the same error
|
| 22/2 |
2026/02/23 14:06 |
tool |
read-file |
0m
Args:
Results: map[Contents:// SPDX-License-Identifier: GPL-2.0-only
/*
* mm/readahead.c - address_space-level file readahead.
*
* Copyright (C) 2002, Linus Torvalds
*
* 09Apr2002 Andrew Morton
* Initial version.
*/
/**
* DOC: Readahead Overview
*
* Readahead is used to read content into the page cache before it is
* explicitly requested by the application. Readahead only ever
* attempts to read folios that are not yet in the page cache. If a
* folio is present but not up-to-date, readahead will not try to read
* it. In that case a simple ->read_folio() will be requested.
*
* Readahead is triggered when an application read request (whether a
* system call or a page fault) finds that the requested folio is not in
* the page cache, or that it is in the page cache and has the
* readahead flag set. This flag indicates that the folio was read
* as part of a previous readahead request and now that it has been
* accessed, it is time for the next readahead.
*
* Each readahead request is partly synchronous read, and partly async
* readahead. This is reflected in the struct file_ra_state which
* contains ->size being the total number of pages, and ->async_size
* which is the number of pages in the async section. The readahead
* flag will be set on the first folio in this async section to trigger
* a subsequent readahead. Once a series of sequential reads has been
* established, there should be no need for a synchronous component and
* all readahead request will be fully asynchronous.
*
* When either of the triggers causes a readahead, three numbers need
* to be determined: the start of the region to read, the size of the
* region, and the size of the async tail.
*
* The start of the region is simply the first page address at or after
* the accessed address, which is not currently populated in the page
* cache. This is found with a simple search in the page cache.
*
* The size of the async tail is determined by subtracting the size that
* was explicitly requested from the determined request size, unless
* this would be less than zero - then zero is used. NOTE THIS
* CALCULATION IS WRONG WHEN THE START OF THE REGION IS NOT THE ACCESSED
* PAGE. ALSO THIS CALCULATION IS NOT USED CONSISTENTLY.
*
* The size of the region is normally determined from the size of the
* previous readahead which loaded the preceding pages. This may be
* discovered from the struct file_ra_state for simple sequential reads,
* or from examining the state of the page cache when multiple
* sequential reads are interleaved. Specifically: where the readahead
* was triggered by the readahead flag, the size of the previous
* readahead is assumed to be the number of pages from the triggering
* page to the start of the new readahead. In these cases, the size of
* the previous readahead is scaled, often doubled, for the new
* readahead, though see get_next_ra_size() for details.
*
* If the size of the previous read cannot be determined, the number of
* preceding pages in the page cache is used to estimate the size of
* a previous read. This estimate could easily be misled by random
* reads being coincidentally adjacent, so it is ignored unless it is
* larger than the current request, and it is not scaled up, unless it
* is at the start of file.
*
* In general readahead is accelerated at the start of the file, as
* reads from there are often sequential. There are other minor
* adjustments to the readahead size in various special cases and these
* are best discovered by reading the code.
*
* The above calculation, based on the previous readahead size,
* determines the size of the readahead, to which any requested read
* size may be added.
*
* Readahead requests are sent to the filesystem using the ->readahead()
* address space operation, for which mpage_readahead() is a canonical
* implementation. ->readahead() should normally initiate reads on all
* folios, but may fail to read any or all folios without causing an I/O
* error. The page cache reading code will issue a ->read_folio() request
* for any folio which ->readahead() did not read, and only an error
* from this will be final.
*
* ->readahead() will generally call readahead_folio() repeatedly to get
* each folio from those prepared for readahead. It may fail to read a
* folio by:
*
* * not calling readahead_folio() sufficiently many times, effectively
* ignoring some folios, as might be appropriate if the path to
* storage is congested.
*
* * failing to actually submit a read request for a given folio,
* possibly due to insufficient resources, or
*
* * getting an error during subsequent processing of a request.
*
* In the last two cases, the folio should be unlocked by the filesystem
* to indicate that the read attempt has failed. In the first case the
* folio will be unlocked by the VFS.
*
* Those folios not in the final ``async_size`` of the request should be
* considered to be important and ->readahead() should not fail them due
* to congestion or temporary resource unavailability, but should wait
* for necessary resources (e.g. memory or indexing information) to
* become available. Folios in the final ``async_size`` may be
* considered less urgent and failure to read them is more acceptable.
* In this case it is best to use filemap_remove_folio() to remove the
* folios from the page cache as is automatically done for folios that
* were not fetched with readahead_folio(). This will allow a
* subsequent synchronous readahead request to try them again. If they
* are left in the page cache, then they will be read individually using
* ->read_folio() which may be less efficient.
*/
#include <linux/blkdev.h>
#include <linux/kernel.h>
#include <linux/dax.h>
#include <linux/gfp.h>
#include <linux/export.h>
#include <linux/backing-dev.h>
#include <linux/task_io_accounting_ops.h>
#include <linux/pagemap.h>
#include <linux/psi.h>
#include <linux/syscalls.h>
#include <linux/file.h>
#include <linux/mm_inline.h>
#include <linux/blk-cgroup.h>
#include <linux/fadvise.h>
#include <linux/sched/mm.h>
#define CREATE_TRACE_POINTS
#include <trace/events/readahead.h>
#include "internal.h"
/*
* Initialise a struct file's readahead state. Assumes that the caller has
* memset *ra to zero.
*/
void
file_ra_state_init(struct file_ra_state *ra, struct address_space *mapping)
{
ra->ra_pages = inode_to_bdi(mapping->host)->ra_pages;
ra->prev_pos = -1;
}
EXPORT_SYMBOL_GPL(file_ra_state_init);
static void read_pages(struct readahead_control *rac)
{
const struct address_space_operations *aops = rac->mapping->a_ops;
struct folio *folio;
struct blk_plug plug;
if (!readahead_count(rac))
return;
if (unlikely(rac->_workingset))
psi_memstall_enter(&rac->_pflags);
blk_start_plug(&plug);
if (aops->readahead) {
aops->readahead(rac);
/* Clean up the remaining folios. */
while ((folio = readahead_folio(rac)) != NULL) {
folio_get(folio);
filemap_remove_folio(folio);
folio_unlock(folio);
folio_put(folio);
}
} else {
while ((folio = readahead_folio(rac)) != NULL)
aops->read_folio(rac->file, folio);
}
blk_finish_plug(&plug);
if (unlikely(rac->_workingset))
psi_memstall_leave(&rac->_pflags);
rac->_workingset = false;
BUG_ON(readahead_count(rac));
}
static struct folio *ractl_alloc_folio(struct readahead_control *ractl,
gfp_t gfp_mask, unsigned int order)
{
struct folio *folio;
folio = filemap_alloc_folio(gfp_mask, order, NULL);
if (folio && ractl->dropbehind)
__folio_set_dropbehind(folio);
return folio;
}
/**
* page_cache_ra_unbounded - Start unchecked readahead.
* @ractl: Readahead control.
* @nr_to_read: The number of pages to read.
* @lookahead_size: Where to start the next readahead.
*
* This function is for filesystems to call when they want to start
* readahead beyond a file's stated i_size. This is almost certainly
* not the function you want to call. Use page_cache_async_readahead()
* or page_cache_sync_readahead() instead.
*
* Context: File is referenced by caller, and ractl->mapping->invalidate_lock
* must be held by the caller at least in shared mode. Mutexes may be held by
* caller. May sleep, but will not reenter filesystem to reclaim memory.
*/
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);
/*
* Partway through the readahead operation, we will have added
* locked pages to the page cache, but will not yet have submitted
* them for I/O. Adding another page may need to allocate memory,
* which can trigger memory reclaim. Telling the VM we're in
* the middle of a filesystem operation will cause it to not
* touch file-backed pages, preventing a deadlock. Most (all?)
* filesystems already specify __GFP_NOFS in their mapping's
* gfp_mask, but let's be explicit here.
*/
unsigned int nofs = memalloc_nofs_save();
lockdep_assert_held(&mapping->invalidate_lock);
trace_page_cache_ra_unbounded(mapping->host, index, nr_to_read,
lookahead_size);
index = mapping_align_index(mapping, index);
/*
* As iterator `i` is aligned to min_nrpages, round_up the
* difference between nr_to_read and lookahead_size to mark the
* index that only has lookahead or "async_region" to set the
* readahead flag.
*/
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;
}
nr_to_read += readahead_index(ractl) - index;
ractl->_index = index;
/*
* Preallocate as many pages as we will need.
*/
while (i < nr_to_read) {
struct folio *folio = xa_load(&mapping->i_pages, index + i);
int ret;
if (folio && !xa_is_value(folio)) {
/*
* Page already present? Kick off the current batch
* of contiguous pages before continuing with the
* next batch. This page may be the one we would
* have intended to mark as Readahead, but we don't
* have a stable reference to this page, and it's
* not worth getting one just for that.
*/
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);
ractl->_index += min_nrpages;
i = ractl->_index + ractl->_nr_pages - index;
continue;
}
if (i == mark)
folio_set_readahead(folio);
ractl->_workingset |= folio_test_workingset(folio);
ractl->_nr_pages += min_nrpages;
i += min_nrpages;
}
/*
* Now start the IO. We ignore I/O errors - if the folio is not
* uptodate then the caller will launch read_folio again, and
* will then handle the error.
*/
read_pages(ractl);
memalloc_nofs_restore(nofs);
}
EXPORT_SYMBOL_GPL(page_cache_ra_unbounded);
/*
* do_page_cache_ra() actually reads a chunk of disk. It allocates
* the pages first, then submits them for I/O. This avoids the very bad
* behaviour which would occur if page allocations are causing VM writeback.
* We really don't want to intermingle reads and writes like that.
*/
static void do_page_cache_ra(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);
loff_t isize = i_size_read(mapping->host);
pgoff_t end_index; /* The last page we want to read */
if (isize == 0)
return;
end_index = (isize - 1) >> PAGE_SHIFT;
if (index > end_index)
return;
/* Don't read past the page containing the last byte of the file */
if (nr_to_read > end_index - index)
nr_to_read = end_index - index + 1;
filemap_invalidate_lock_shared(mapping);
page_cache_ra_unbounded(ractl, nr_to_read, lookahead_size);
filemap_invalidate_unlock_shared(mapping);
}
/*
* Chunk the readahead into 2 megabyte units, so that we don't pin too much
* memory at once.
*/
void force_page_cache_ra(struct readahead_control *ractl,
unsigned long nr_to_read)
{
struct address_space *mapping = ractl->mapping;
struct file_ra_state *ra = ractl->ra;
struct backing_dev_info *bdi = inode_to_bdi(mapping->host);
unsigned long max_pages;
if (unlikely(!mapping->a_ops->read_folio && !mapping->a_ops->readahead))
return;
/*
* If the request exceeds the readahead window, allow the read to
* be up to the optimal hardware IO size
*/
max_pages = max_t(unsigned long, bdi->io_pages, ra->ra_pages);
nr_to_read = min_t(unsigned long, nr_to_read, max_pages);
while (nr_to_read) {
unsigned long this_chunk = (2 * 1024 * 1024) / PAGE_SIZE;
if (this_chunk > nr_to_read)
this_chunk = nr_to_read;
do_page_cache_ra(ractl, this_chunk, 0);
nr_to_read -= this_chunk;
}
}
/*
* Set the initial window size, round to next power of 2 and square
* for small size, x 4 for medium, and x 2 for large
* for 128k (32 page) max ra
* 1-2 page = 16k, 3-4 page 32k, 5-8 page = 64k, > 8 page = 128k initial
*/
static unsigned long get_init_ra_size(unsigned long size, unsigned long max)
{
unsigned long newsize = roundup_pow_of_two(size);
if (newsize <= max / 32)
newsize = newsize * 4;
else if (newsize <= max / 4)
newsize = newsize * 2;
else
newsize = max;
return newsize;
}
/*
* Get the previous window size, ramp it up, and
* return it as the new window size.
*/
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;
if (cur <= max / 2)
return 2 * cur;
return max;
}
/*
* On-demand readahead design.
*
* The fields in struct file_ra_state represent the most-recently-executed
* readahead attempt:
*
* |<----- async_size ---------|
* |------------------- size -------------------->|
* |==================#===========================|
* ^start ^page marked with PG_readahead
*
* To overlap application thinking time and disk I/O time, we do
* `readahead pipelining': Do not wait until the application consumed all
* readahead pages and stalled on the missing page at readahead_index;
* Instead, submit an asynchronous readahead I/O as soon as there are
* only async_size pages left in the readahead window. Normally async_size
* will be equal to size, for maximum pipelining.
*
* In interleaved sequential reads, concurrent streams on the same fd can
* be invalidating each other's readahead state. So we flag the new readahead
* page at (start+size-async_size) with PG_readahead, and use it as readahead
* indicator. The flag won't be set on already cached pages, to avoid the
* readahead-for-nothing fuss, saving pointless page cache lookups.
*
* prev_pos tracks the last visited byte in the _previous_ read request.
* It should be maintained by the caller, and will be used for detecting
* small random reads. Note that the readahead algorithm checks loosely
* for sequential patterns. Hence interleaved reads might be served as
* sequential ones.
*
* There is a special-case: if the first page which the application tries to
* read happens to be the first page of the file, it is assumed that a linear
* read is about to happen and the window is immediately set to the initial size
* based on I/O request size and the max_readahead.
*
* The code ramps up the readahead size aggressively at first, but slow down as
* it approaches max_readahead.
*/
static inline int ra_alloc_folio(struct readahead_control *ractl, pgoff_t index,
pgoff_t mark, unsigned int order, gfp_t gfp)
{
int err;
struct folio *folio = ractl_alloc_folio(ractl, gfp, order);
if (!folio)
return -ENOMEM;
mark = round_down(mark, 1UL << order);
if (index == mark)
folio_set_readahead(folio);
err = filemap_add_folio(ractl->mapping, folio, index, gfp);
if (err) {
folio_put(folio);
return err;
}
ractl->_nr_pages += 1UL << order;
ractl->_workingset |= folio_test_workingset(folio);
return 0;
}
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);
pgoff_t index = start;
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 nofs;
int err = 0;
gfp_t gfp = readahead_gfp_mask(mapping);
unsigned int new_order = ra->order;
trace_page_cache_ra_order(mapping->host, start, ra);
if (!mapping_large_folio_support(mapping)) {
ra->order = 0;
goto fallback;
}
limit = min(limit, index + ra->size - 1);
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);
ra->order = new_order;
/* See comment in page_cache_ra_unbounded() */
nofs = memalloc_nofs_save();
filemap_invalidate_lock_shared(mapping);
/*
* If the new_order is greater than min_order and index is
* already aligned to new_order, then this will be noop as index
* aligned to new_order should also be aligned to min_order.
*/
ractl->_index = mapping_align_index(mapping, index);
index = readahead_index(ractl);
while (index <= limit) {
unsigned int order = new_order;
/* Align with smaller pages if needed */
if (index & ((1UL << order) - 1))
order = __ffs(index);
/* Don't allocate pages past EOF */
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);
filemap_invalidate_unlock_shared(mapping);
memalloc_nofs_restore(nofs);
/*
* If there were already pages in the page cache, then we may have
* left some gaps. Let the regular readahead code take care of this
* situation below.
*/
if (!err)
return;
fallback:
/*
* ->readahead() may have updated readahead window size so we have to
* check there's still something to read.
*/
if (ra->size > index - start)
do_page_cache_ra(ractl, ra->size - (index - start),
ra->async_size);
}
static unsigned long ractl_max_pages(struct readahead_control *ractl,
unsigned long req_size)
{
struct backing_dev_info *bdi = inode_to_bdi(ractl->mapping->host);
unsigned long max_pages = ractl->ra->ra_pages;
/*
* If the request exceeds the readahead window, allow the read to
* be up to the optimal hardware IO size
*/
if (req_size > max_pages && bdi->io_pages > max_pages)
max_pages = min(req_size, bdi->io_pages);
return max_pages;
}
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;
trace_page_cache_sync_ra(ractl->mapping->host, index, ra, req_count);
/*
* Even if readahead is disabled, issue this request as readahead
* as we'll need it to satisfy the requested range. The forced
* readahead will do the right thing and limit the read to just the
* requested range, which we'll set to 1 page for this case.
*/
if (!ra->ra_pages || blk_cgroup_congested()) {
if (!ractl->file)
return;
req_count = 1;
do_forced_ra = true;
}
/* be dumb */
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;
/*
* A start of file, oversized read, or sequential cache miss:
* trivial case: (index - prev_index) == 1
* unaligned reads: (index - prev_index) == 0
*/
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;
}
/*
* Query the page cache and look for the traces(cached history pages)
* that a sequential stream would leave behind.
*/
rcu_read_lock();
miss = page_cache_prev_miss(ractl->mapping, index - 1, max_pages);
rcu_read_unlock();
contig_count = index - miss - 1;
/*
* Standalone, small random read. Read as is, and do not pollute the
* readahead state.
*/
if (contig_count <= req_count) {
do_page_cache_ra(ractl, req_count, 0);
return;
}
/*
* File cached from the beginning:
* it is a strong indication of long-run stream (or whole-file-read)
*/
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);
}
EXPORT_SYMBOL_GPL(page_cache_sync_ra);
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;
/* no readahead */
if (!ra->ra_pages)
return;
/*
* Same bit is used for PG_readahead and PG_reclaim.
*/
if (folio_test_writeback(folio))
return;
trace_page_cache_async_ra(ractl->mapping->host, index, ra, req_count);
folio_clear_readahead(folio);
if (blk_cgroup_congested())
return;
max_pages = ractl_max_pages(ractl, req_count);
/*
* It's the expected callback index, assume sequential access.
* Ramp up sizes, and push forward the readahead window.
*/
expected = round_down(ra->start + ra->size - ra->async_size,
folio_nr_pages(folio));
if (index == expected) {
ra->start += ra->size;
/*
* In the case of MADV_HUGEPAGE, the actual size might exceed
* the readahead window.
*/
ra->size = max(ra->size, get_next_ra_size(ra, max_pages));
goto readit;
}
/*
* Hit a marked folio without valid readahead state.
* E.g. interleaved reads.
* Query the pagecache for async_size, which normally equals to
* readahead size. Ramp it up and use it as the new readahead 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; /* old async_size */
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);
}
EXPORT_SYMBOL_GPL(page_cache_async_ra);
ssize_t ksys_readahead(int fd, loff_t offset, size_t count)
{
struct file *file;
const struct inode *inode;
CLASS(fd, f)(fd);
if (fd_empty(f))
return -EBADF;
file = fd_file(f);
if (!(file->f_mode & FMODE_READ))
return -EBADF;
/*
* The readahead() syscall is intended to run only on files
* that can execute readahead. If readahead is not possible
* on this file, then we must return -EINVAL.
*/
if (!file->f_mapping)
return -EINVAL;
if (!file->f_mapping->a_ops)
return -EINVAL;
inode = file_inode(file);
if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
return -EINVAL;
if (IS_ANON_FILE(inode))
return -EINVAL;
return vfs_fadvise(fd_file(f), offset, count, POSIX_FADV_WILLNEED);
}
SYSCALL_DEFINE3(readahead, int, fd, loff_t, offset, size_t, count)
{
return ksys_readahead(fd, offset, count);
}
#if defined(CONFIG_COMPAT) && defined(__ARCH_WANT_COMPAT_READAHEAD)
COMPAT_SYSCALL_DEFINE4(readahead, int, fd, compat_arg_u64_dual(offset), size_t, count)
{
return ksys_readahead(fd, compat_arg_u64_glue(offset), count);
}
#endif
/**
* readahead_expand - Expand a readahead request
* @ractl: The request to be expanded
* @new_start: The revised start
* @new_len: The revised size of the request
*
* Attempt to expand a readahead request outwards from the current size to the
* specified size by inserting locked pages before and after the current window
* to increase the size to the new window. This may involve the insertion of
* THPs, in which case the window may get expanded even beyond what was
* requested.
*
* The algorithm will stop if it encounters a conflicting page already in the
* pagecache and leave a smaller expansion than requested.
*
* The caller must check for this by examining the revised @ractl object for a
* different expansion than was requested.
*/
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 code should have aligned the ractl->_index to
* min_nrpages before calling readahead aops.
*/
VM_BUG_ON(!IS_ALIGNED(ractl->_index, min_nrpages));
/* Expand the leading edge downwards */
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 apparently present */
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);
/* Expand the trailing edge upwards */
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 apparently present */
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);
]
|
| 23/2 |
2026/02/23 14:06 |
llm |
expert |
0m
Model:
Tokens:
input: 40570
output: 25
thoughts: 183
Thoughts: **My Assessment of the Readahead Concurrency**
Okay, so the comment in `mm/readahead.c` explicitly states that concurrent streams accessing the same file descriptor are expected to potentially interfere with each other's readahead state. This confirms what I suspected: the `file_ra_state` structure is designed to handle this "loose" concurrency. Knowing that, I'm already inclined to believe this isn't a critical issue.
Specifically, I'm thinking about the `ra_pages` field within that structure. It's just *one* piece of the overall readahead state. Generally speaking, I'm feeling pretty confident that this is a benign situation. However, I always double-check.
Let me just make sure though: I should scan the code for any potential vulnerabilities. It occurs to me that `ra_pages` is an `unsigned int`, which means a divide-by-zero is still a theoretical concern. The line `ra_pages /= 4` is safe in this case, but what if it's used as a divisor elsewhere? I'll do a quick grep search for occurrences of `/ ra_pages` or `% ra_pages` in the source, just to be completely certain.
|
| 24/2 |
2026/02/23 14:06 |
tool |
grepper |
0m
Args: map[Expression:/.*ra_pages|%.*ra_pages]
Results: map[Output:fs/ceph/super.c=1273=static int ceph_setup_bdi(struct super_block *sb, struct ceph_fs_client *fsc)
--
fs/ceph/super.c-1281-
fs/ceph/super.c:1282: /* set ra_pages based on rasize mount option? */
fs/ceph/super.c-1283- sb->s_bdi->ra_pages = fsc->mount_options->rasize >> PAGE_SHIFT;
--
fs/f2fs/f2fs.h=1672=struct f2fs_sb_info {
--
fs/f2fs/f2fs.h-1910-
fs/f2fs/f2fs.h:1911: unsigned long seq_file_ra_mul; /* multiplier for ra_pages of seq. files in fadvise */
fs/f2fs/f2fs.h-1912-
--
include/trace/events/readahead.h=43=TRACE_EVENT(page_cache_ra_order,
--
include/trace/events/readahead.h-68- TP_printk(
include/trace/events/readahead.h:69: "dev=%d:%d ino=%lx index=%lu order=%u size=%u async_size=%u ra_pages=%u",
include/trace/events/readahead.h-70- MAJOR(__entry->s_dev), MINOR(__entry->s_dev), __entry->i_ino,
--
include/trace/events/readahead.h=76=DECLARE_EVENT_CLASS(page_cache_ra_op,
--
include/trace/events/readahead.h-108- TP_printk(
include/trace/events/readahead.h:109: "dev=%d:%d ino=%lx index=%lu req_count=%lu order=%u size=%u async_size=%u ra_pages=%u mmap_miss=%u prev_pos=%lld",
include/trace/events/readahead.h-110- MAJOR(__entry->s_dev), MINOR(__entry->s_dev), __entry->i_ino,
--
include/xen/interface/io/tpmif.h=41=struct vtpm_shared_page {
--
include/xen/interface/io/tpmif.h-48- uint8_t nr_extra_pages; /* extra pages for long packets; may be zero */
include/xen/interface/io/tpmif.h:49: uint32_t extra_pages[]; /* grant IDs; length in nr_extra_pages */
include/xen/interface/io/tpmif.h-50-};
]
|
| 25/2 |
2026/02/23 14:06 |
llm |
expert |
0m
Model:
Tokens:
input: 41713
output: 19
thoughts: 957
Thoughts: **Benign Race Condition in `shrink_readahead_size_eio` with EIO**
Okay, so the grep didn't turn up any direct divisions by `ra_pages` that looked problematic, which is good. It's mostly used as a limiter or for initialization, which doesn't immediately scream race condition. Let's dig deeper.
I see in `mm/filemap.c` that `ra->ra_pages` is used to calculate `ra->start`, `ra->size` and `ra->async_size`. If it's zero, then everything zeroes out, which is inherently safe. No immediate issues there. Also, the division by 4 in `ra->async_size` appears safe. We also have `ra->ra_pages /= 4;` later. This seems like the potential point, but I need to make sure `ra` isn't NULL at that point.
Checking the `filemap_read_folio` function reveals a crucial check: `if (file)`. We pass `&file->f_ra` to `shrink_readahead_size_eio`. Since `file` is checked, the subsequent dereference to `f_ra` is also safe. I'm reasonably confident at this point, but let's consider the reported context.
The report details two tasks calling `filemap_read_folio` on the same `file` (or rather, the same `f_ra`). Task 1814 is in a `mount` system call. The stack trace for 1814 shows a page fault during `copy_from_user` within the `mount` process. This is interesting: It's a file-backed page fault, presumably related to the options passed to the mount call.
Task 1786 appears to be running `mm_populate` during an `mmap` call. Again, a file-backed page fault, likely related to `MAP_POPULATE` or similar. They both relate to a file.
Both tasks, therefore, seem to be faulting on the *same* file. If that file is on a device that’s returning EIO—like the `loop1` device mentioned in the original report—they're both going to trigger the error path and try to shrink readahead.
The race is, therefore, benign. The apparent race in `shrink_readahead_size_eio` is just a side effect of two threads hitting the same EIO error and calling `shrink_readahead_size_eio` simultaneously. The root cause is the EIO, not the concurrency.
Finally, regarding the "attempt to access beyond end of device" message, that's almost certainly because the test case is trying to read past the end of a small loop device. This triggers EIO, which then correctly calls `shrink_readahead_size_eio`. So, in conclusion, the "race" is just a symptom of a larger issue.
I'm marking this as benign and feel pretty confident about it.
|
| 26/2 |
2026/02/23 14:06 |
tool |
set-results |
0m
Args: map[Benign:true Confident:true]
Results: map[Benign:true Confident:true]
|
| 27/2 |
2026/02/23 14:06 |
llm |
expert |
0m
Model:
Tokens:
input: 43362
output: 570
thoughts: 201
Thoughts: **Analyzing the Benign Data Race**
Okay, so I'm looking at this data race, specifically the one involving `ra->ra_pages`. My initial instinct is to dig into why this doesn't lead to any real problems. Let's break this down systematically, as an expert would.
First, I need to remind myself that `ra->ra_pages` is part of the file readahead state, managed by `file_ra_state`. The key thing here is that `file_ra_state` is designed for lockless access. It's built for performance, and it's intended to act as a *hint* for the readahead algorithm, not a strict, critical path.
Specifically, the suspect operation is `ra->ra_pages /= 4`. That's a heuristic, a way to scale down the readahead aggressively when we're hitting I/O errors. A race here *could* mean `ra_pages` isn't reduced as quickly as it "should" be.
But here's the kicker: the outcome isn't kernel corruption. It's only a change in efficiency of the readahead heuristic. The worst-case scenario is that we might be a bit more aggressive in the readahead behavior, but it won't affect the correctness of the kernel's overall operation. Also, `ra_pages` is an `unsigned int` so even a very bad result wouldn't be able to generate undefined behavior or overflow. The value will always be within safe bounds.
Finally, I have to clarify one more point: the "access beyond end of device" isn't a *result* of the race. The race is *triggered* by the underlying EIO, because we're encountering an out-of-bounds read error.
|