|
22 | 22 | #include <linux/mm.h>
|
23 | 23 | #include <linux/swap.h>
|
24 | 24 | #include <linux/swapops.h>
|
| 25 | +#include <linux/syscalls.h> |
25 | 26 | #include <linux/mman.h>
|
26 | 27 | #include <linux/pagemap.h>
|
27 | 28 | #include <linux/file.h>
|
|
58 | 59 |
|
59 | 60 | #include <asm/mman.h>
|
60 | 61 |
|
| 62 | +#include "swap.h" |
| 63 | + |
61 | 64 | /*
|
62 | 65 | * Shared mappings implemented 30.11.1994. It's not fully working yet,
|
63 | 66 | * though.
|
@@ -4119,3 +4122,171 @@ bool filemap_release_folio(struct folio *folio, gfp_t gfp)
|
4119 | 4122 | return try_to_free_buffers(folio);
|
4120 | 4123 | }
|
4121 | 4124 | EXPORT_SYMBOL(filemap_release_folio);
|
| 4125 | + |
| 4126 | +#ifdef CONFIG_CACHESTAT_SYSCALL |
| 4127 | +/** |
| 4128 | + * filemap_cachestat() - compute the page cache statistics of a mapping |
| 4129 | + * @mapping: The mapping to compute the statistics for. |
| 4130 | + * @first_index: The starting page cache index. |
| 4131 | + * @last_index: The final page index (inclusive). |
| 4132 | + * @cs: the cachestat struct to write the result to. |
| 4133 | + * |
| 4134 | + * This will query the page cache statistics of a mapping in the |
| 4135 | + * page range of [first_index, last_index] (inclusive). The statistics |
| 4136 | + * queried include: number of dirty pages, number of pages marked for |
| 4137 | + * writeback, and the number of (recently) evicted pages. |
| 4138 | + */ |
| 4139 | +static void filemap_cachestat(struct address_space *mapping, |
| 4140 | + pgoff_t first_index, pgoff_t last_index, struct cachestat *cs) |
| 4141 | +{ |
| 4142 | + XA_STATE(xas, &mapping->i_pages, first_index); |
| 4143 | + struct folio *folio; |
| 4144 | + |
| 4145 | + rcu_read_lock(); |
| 4146 | + xas_for_each(&xas, folio, last_index) { |
| 4147 | + unsigned long nr_pages; |
| 4148 | + pgoff_t folio_first_index, folio_last_index; |
| 4149 | + |
| 4150 | + if (xas_retry(&xas, folio)) |
| 4151 | + continue; |
| 4152 | + |
| 4153 | + if (xa_is_value(folio)) { |
| 4154 | + /* page is evicted */ |
| 4155 | + void *shadow = (void *)folio; |
| 4156 | + bool workingset; /* not used */ |
| 4157 | + int order = xa_get_order(xas.xa, xas.xa_index); |
| 4158 | + |
| 4159 | + nr_pages = 1 << order; |
| 4160 | + folio_first_index = round_down(xas.xa_index, 1 << order); |
| 4161 | + folio_last_index = folio_first_index + nr_pages - 1; |
| 4162 | + |
| 4163 | + /* Folios might straddle the range boundaries, only count covered pages */ |
| 4164 | + if (folio_first_index < first_index) |
| 4165 | + nr_pages -= first_index - folio_first_index; |
| 4166 | + |
| 4167 | + if (folio_last_index > last_index) |
| 4168 | + nr_pages -= folio_last_index - last_index; |
| 4169 | + |
| 4170 | + cs->nr_evicted += nr_pages; |
| 4171 | + |
| 4172 | +#ifdef CONFIG_SWAP /* implies CONFIG_MMU */ |
| 4173 | + if (shmem_mapping(mapping)) { |
| 4174 | + /* shmem file - in swap cache */ |
| 4175 | + swp_entry_t swp = radix_to_swp_entry(folio); |
| 4176 | + |
| 4177 | + shadow = get_shadow_from_swap_cache(swp); |
| 4178 | + } |
| 4179 | +#endif |
| 4180 | + if (workingset_test_recent(shadow, true, &workingset)) |
| 4181 | + cs->nr_recently_evicted += nr_pages; |
| 4182 | + |
| 4183 | + goto resched; |
| 4184 | + } |
| 4185 | + |
| 4186 | + nr_pages = folio_nr_pages(folio); |
| 4187 | + folio_first_index = folio_pgoff(folio); |
| 4188 | + folio_last_index = folio_first_index + nr_pages - 1; |
| 4189 | + |
| 4190 | + /* Folios might straddle the range boundaries, only count covered pages */ |
| 4191 | + if (folio_first_index < first_index) |
| 4192 | + nr_pages -= first_index - folio_first_index; |
| 4193 | + |
| 4194 | + if (folio_last_index > last_index) |
| 4195 | + nr_pages -= folio_last_index - last_index; |
| 4196 | + |
| 4197 | + /* page is in cache */ |
| 4198 | + cs->nr_cache += nr_pages; |
| 4199 | + |
| 4200 | + if (folio_test_dirty(folio)) |
| 4201 | + cs->nr_dirty += nr_pages; |
| 4202 | + |
| 4203 | + if (folio_test_writeback(folio)) |
| 4204 | + cs->nr_writeback += nr_pages; |
| 4205 | + |
| 4206 | +resched: |
| 4207 | + if (need_resched()) { |
| 4208 | + xas_pause(&xas); |
| 4209 | + cond_resched_rcu(); |
| 4210 | + } |
| 4211 | + } |
| 4212 | + rcu_read_unlock(); |
| 4213 | +} |
| 4214 | + |
| 4215 | +/* |
| 4216 | + * The cachestat(2) system call. |
| 4217 | + * |
| 4218 | + * cachestat() returns the page cache statistics of a file in the |
| 4219 | + * bytes range specified by `off` and `len`: number of cached pages, |
| 4220 | + * number of dirty pages, number of pages marked for writeback, |
| 4221 | + * number of evicted pages, and number of recently evicted pages. |
| 4222 | + * |
| 4223 | + * An evicted page is a page that is previously in the page cache |
| 4224 | + * but has been evicted since. A page is recently evicted if its last |
| 4225 | + * eviction was recent enough that its reentry to the cache would |
| 4226 | + * indicate that it is actively being used by the system, and that |
| 4227 | + * there is memory pressure on the system. |
| 4228 | + * |
| 4229 | + * `off` and `len` must be non-negative integers. If `len` > 0, |
| 4230 | + * the queried range is [`off`, `off` + `len`]. If `len` == 0, |
| 4231 | + * we will query in the range from `off` to the end of the file. |
| 4232 | + * |
| 4233 | + * The `flags` argument is unused for now, but is included for future |
| 4234 | + * extensibility. User should pass 0 (i.e no flag specified). |
| 4235 | + * |
| 4236 | + * Currently, hugetlbfs is not supported. |
| 4237 | + * |
| 4238 | + * Because the status of a page can change after cachestat() checks it |
| 4239 | + * but before it returns to the application, the returned values may |
| 4240 | + * contain stale information. |
| 4241 | + * |
| 4242 | + * return values: |
| 4243 | + * zero - success |
| 4244 | + * -EFAULT - cstat or cstat_range points to an illegal address |
| 4245 | + * -EINVAL - invalid flags |
| 4246 | + * -EBADF - invalid file descriptor |
| 4247 | + * -EOPNOTSUPP - file descriptor is of a hugetlbfs file |
| 4248 | + */ |
| 4249 | +SYSCALL_DEFINE4(cachestat, unsigned int, fd, |
| 4250 | + struct cachestat_range __user *, cstat_range, |
| 4251 | + struct cachestat __user *, cstat, unsigned int, flags) |
| 4252 | +{ |
| 4253 | + struct fd f = fdget(fd); |
| 4254 | + struct address_space *mapping; |
| 4255 | + struct cachestat_range csr; |
| 4256 | + struct cachestat cs; |
| 4257 | + pgoff_t first_index, last_index; |
| 4258 | + |
| 4259 | + if (!f.file) |
| 4260 | + return -EBADF; |
| 4261 | + |
| 4262 | + if (copy_from_user(&csr, cstat_range, |
| 4263 | + sizeof(struct cachestat_range))) { |
| 4264 | + fdput(f); |
| 4265 | + return -EFAULT; |
| 4266 | + } |
| 4267 | + |
| 4268 | + /* hugetlbfs is not supported */ |
| 4269 | + if (is_file_hugepages(f.file)) { |
| 4270 | + fdput(f); |
| 4271 | + return -EOPNOTSUPP; |
| 4272 | + } |
| 4273 | + |
| 4274 | + if (flags != 0) { |
| 4275 | + fdput(f); |
| 4276 | + return -EINVAL; |
| 4277 | + } |
| 4278 | + |
| 4279 | + first_index = csr.off >> PAGE_SHIFT; |
| 4280 | + last_index = |
| 4281 | + csr.len == 0 ? ULONG_MAX : (csr.off + csr.len - 1) >> PAGE_SHIFT; |
| 4282 | + memset(&cs, 0, sizeof(struct cachestat)); |
| 4283 | + mapping = f.file->f_mapping; |
| 4284 | + filemap_cachestat(mapping, first_index, last_index, &cs); |
| 4285 | + fdput(f); |
| 4286 | + |
| 4287 | + if (copy_to_user(cstat, &cs, sizeof(struct cachestat))) |
| 4288 | + return -EFAULT; |
| 4289 | + |
| 4290 | + return 0; |
| 4291 | +} |
| 4292 | +#endif /* CONFIG_CACHESTAT_SYSCALL */ |
0 commit comments