diff options
Diffstat (limited to 'tools/vm/page-types.c')
| -rw-r--r-- | tools/vm/page-types.c | 231 | 
1 files changed, 201 insertions, 30 deletions
diff --git a/tools/vm/page-types.c b/tools/vm/page-types.c index 71c9c2511ee..c4d6d2e20e0 100644 --- a/tools/vm/page-types.c +++ b/tools/vm/page-types.c @@ -19,7 +19,8 @@   * Authors: Wu Fengguang <fengguang.wu@intel.com>   */ -#define _LARGEFILE64_SOURCE +#define _FILE_OFFSET_BITS 64 +#define _GNU_SOURCE  #include <stdio.h>  #include <stdlib.h>  #include <unistd.h> @@ -29,14 +30,19 @@  #include <getopt.h>  #include <limits.h>  #include <assert.h> +#include <ftw.h> +#include <time.h> +#include <setjmp.h> +#include <signal.h>  #include <sys/types.h>  #include <sys/errno.h>  #include <sys/fcntl.h>  #include <sys/mount.h>  #include <sys/statfs.h> +#include <sys/mman.h>  #include "../../include/uapi/linux/magic.h"  #include "../../include/uapi/linux/kernel-page-flags.h" -#include <lk/debugfs.h> +#include <api/fs/debugfs.h>  #ifndef MAX_PATH  # define MAX_PATH 256 @@ -59,12 +65,14 @@  #define PM_PSHIFT_BITS      6  #define PM_PSHIFT_OFFSET    (PM_STATUS_OFFSET - PM_PSHIFT_BITS)  #define PM_PSHIFT_MASK      (((1LL << PM_PSHIFT_BITS) - 1) << PM_PSHIFT_OFFSET) -#define PM_PSHIFT(x)        (((u64) (x) << PM_PSHIFT_OFFSET) & PM_PSHIFT_MASK) +#define __PM_PSHIFT(x)      (((uint64_t) (x) << PM_PSHIFT_OFFSET) & PM_PSHIFT_MASK)  #define PM_PFRAME_MASK      ((1LL << PM_PSHIFT_OFFSET) - 1)  #define PM_PFRAME(x)        ((x) & PM_PFRAME_MASK) +#define __PM_SOFT_DIRTY      (1LL)  #define PM_PRESENT          PM_STATUS(4LL)  #define PM_SWAP             PM_STATUS(2LL) +#define PM_SOFT_DIRTY       __PM_PSHIFT(__PM_SOFT_DIRTY)  /* @@ -83,6 +91,7 @@  #define KPF_OWNER_PRIVATE	37  #define KPF_ARCH		38  #define KPF_UNCACHED		39 +#define KPF_SOFTDIRTY		40  /* [48-] take some arbitrary free slots for expanding overloaded flags   * not part of kernel API @@ -132,6 +141,7 @@ static const char * const page_flag_names[] = {  	[KPF_OWNER_PRIVATE]	= "O:owner_private",  	[KPF_ARCH]		= "h:arch",  	[KPF_UNCACHED]		= "c:uncached", +	[KPF_SOFTDIRTY]		= "f:softdirty",  	[KPF_READAHEAD]		= "I:readahead",  	[KPF_SLOB_FREE]		= "P:slob_free", @@ -154,6 +164,7 @@ static int		opt_raw;	/* for kernel developers */  static int		opt_list;	/* list pages (in ranges) */  static int		opt_no_summary;	/* don't show summary */  static pid_t		opt_pid;	/* process to walk */ +const char *		opt_file;  #define MAX_ADDR_RANGES	1024  static int		nr_addr_ranges; @@ -249,12 +260,7 @@ static unsigned long do_u64_read(int fd, char *name,  	if (index > ULONG_MAX / 8)  		fatal("index overflow: %lu\n", index); -	if (lseek(fd, index * 8, SEEK_SET) < 0) { -		perror(name); -		exit(EXIT_FAILURE); -	} - -	bytes = read(fd, buf, count * 8); +	bytes = pread(fd, buf, count * 8, (off_t)index * 8);  	if (bytes < 0) {  		perror(name);  		exit(EXIT_FAILURE); @@ -339,8 +345,8 @@ static char *page_flag_longname(uint64_t flags)   * page list and summary   */ -static void show_page_range(unsigned long voffset, -			    unsigned long offset, uint64_t flags) +static void show_page_range(unsigned long voffset, unsigned long offset, +			    unsigned long size, uint64_t flags)  {  	static uint64_t      flags0;  	static unsigned long voff; @@ -348,14 +354,16 @@ static void show_page_range(unsigned long voffset,  	static unsigned long count;  	if (flags == flags0 && offset == index + count && -	    (!opt_pid || voffset == voff + count)) { -		count++; +	    size && voffset == voff + count) { +		count += size;  		return;  	}  	if (count) {  		if (opt_pid)  			printf("%lx\t", voff); +		if (opt_file) +			printf("%lu\t", voff);  		printf("%lx\t%lx\t%s\n",  				index, count, page_flag_name(flags0));  	} @@ -363,7 +371,12 @@ static void show_page_range(unsigned long voffset,  	flags0 = flags;  	index  = offset;  	voff   = voffset; -	count  = 1; +	count  = size; +} + +static void flush_page_range(void) +{ +	show_page_range(0, 0, 0, 0);  }  static void show_page(unsigned long voffset, @@ -371,6 +384,8 @@ static void show_page(unsigned long voffset,  {  	if (opt_pid)  		printf("%lx\t", voffset); +	if (opt_file) +		printf("%lu\t", voffset);  	printf("%lx\t%s\n", offset, page_flag_name(flags));  } @@ -417,7 +432,7 @@ static int bit_mask_ok(uint64_t flags)  	return 1;  } -static uint64_t expand_overloaded_flags(uint64_t flags) +static uint64_t expand_overloaded_flags(uint64_t flags, uint64_t pme)  {  	/* SLOB/SLUB overload several page flags */  	if (flags & BIT(SLAB)) { @@ -433,6 +448,9 @@ static uint64_t expand_overloaded_flags(uint64_t flags)  	if ((flags & (BIT(RECLAIM) | BIT(WRITEBACK))) == BIT(RECLAIM))  		flags ^= BIT(RECLAIM) | BIT(READAHEAD); +	if (pme & PM_SOFT_DIRTY) +		flags |= BIT(SOFTDIRTY); +  	return flags;  } @@ -448,11 +466,11 @@ static uint64_t well_known_flags(uint64_t flags)  	return flags;  } -static uint64_t kpageflags_flags(uint64_t flags) +static uint64_t kpageflags_flags(uint64_t flags, uint64_t pme)  { -	flags = expand_overloaded_flags(flags); - -	if (!opt_raw) +	if (opt_raw) +		flags = expand_overloaded_flags(flags, pme); +	else  		flags = well_known_flags(flags);  	return flags; @@ -545,9 +563,9 @@ static size_t hash_slot(uint64_t flags)  }  static void add_page(unsigned long voffset, -		     unsigned long offset, uint64_t flags) +		     unsigned long offset, uint64_t flags, uint64_t pme)  { -	flags = kpageflags_flags(flags); +	flags = kpageflags_flags(flags, pme);  	if (!bit_mask_ok(flags))  		return; @@ -558,7 +576,7 @@ static void add_page(unsigned long voffset,  		unpoison_page(offset);  	if (opt_list == 1) -		show_page_range(voffset, offset, flags); +		show_page_range(voffset, offset, 1, flags);  	else if (opt_list == 2)  		show_page(voffset, offset, flags); @@ -569,7 +587,8 @@ static void add_page(unsigned long voffset,  #define KPAGEFLAGS_BATCH	(64 << 10)	/* 64k pages */  static void walk_pfn(unsigned long voffset,  		     unsigned long index, -		     unsigned long count) +		     unsigned long count, +		     uint64_t pme)  {  	uint64_t buf[KPAGEFLAGS_BATCH];  	unsigned long batch; @@ -583,7 +602,7 @@ static void walk_pfn(unsigned long voffset,  			break;  		for (i = 0; i < pages; i++) -			add_page(voffset + i, index + i, buf[i]); +			add_page(voffset + i, index + i, buf[i], pme);  		index += pages;  		count -= pages; @@ -608,7 +627,7 @@ static void walk_vma(unsigned long index, unsigned long count)  		for (i = 0; i < pages; i++) {  			pfn = pagemap_pfn(buf[i]);  			if (pfn) -				walk_pfn(index + i, pfn, 1); +				walk_pfn(index + i, pfn, 1, buf[i]);  		}  		index += pages; @@ -659,7 +678,7 @@ static void walk_addr_ranges(void)  	for (i = 0; i < nr_addr_ranges; i++)  		if (!opt_pid) -			walk_pfn(0, opt_offset[i], opt_size[i]); +			walk_pfn(opt_offset[i], opt_offset[i], opt_size[i], 0);  		else  			walk_task(opt_offset[i], opt_size[i]); @@ -691,9 +710,7 @@ static void usage(void)  "            -a|--addr    addr-spec     Walk a range of pages\n"  "            -b|--bits    bits-spec     Walk pages with specified bits\n"  "            -p|--pid     pid           Walk process address space\n" -#if 0 /* planned features */  "            -f|--file    filename      Walk file address space\n" -#endif  "            -l|--list                  Show page details in ranges\n"  "            -L|--list-each             Show page details one by one\n"  "            -N|--no-summary            Don't show summary info\n" @@ -791,8 +808,157 @@ static void parse_pid(const char *str)  	fclose(file);  } +static void show_file(const char *name, const struct stat *st) +{ +	unsigned long long size = st->st_size; +	char atime[64], mtime[64]; +	long now = time(NULL); + +	printf("%s\tInode: %u\tSize: %llu (%llu pages)\n", +			name, (unsigned)st->st_ino, +			size, (size + page_size - 1) / page_size); + +	strftime(atime, sizeof(atime), "%c", localtime(&st->st_atime)); +	strftime(mtime, sizeof(mtime), "%c", localtime(&st->st_mtime)); + +	printf("Modify: %s (%ld seconds ago)\nAccess: %s (%ld seconds ago)\n", +			mtime, now - st->st_mtime, +			atime, now - st->st_atime); +} + +static sigjmp_buf sigbus_jmp; + +static void * volatile sigbus_addr; + +static void sigbus_handler(int sig, siginfo_t *info, void *ucontex) +{ +	(void)sig; +	(void)ucontex; +	sigbus_addr = info ? info->si_addr : NULL; +	siglongjmp(sigbus_jmp, 1); +} + +static struct sigaction sigbus_action = { +	.sa_sigaction = sigbus_handler, +	.sa_flags = SA_SIGINFO, +}; + +static void walk_file(const char *name, const struct stat *st) +{ +	uint8_t vec[PAGEMAP_BATCH]; +	uint64_t buf[PAGEMAP_BATCH], flags; +	unsigned long nr_pages, pfn, i; +	off_t off, end = st->st_size; +	int fd; +	ssize_t len; +	void *ptr; +	int first = 1; + +	fd = checked_open(name, O_RDONLY|O_NOATIME|O_NOFOLLOW); + +	for (off = 0; off < end; off += len) { +		nr_pages = (end - off + page_size - 1) / page_size; +		if (nr_pages > PAGEMAP_BATCH) +			nr_pages = PAGEMAP_BATCH; +		len = nr_pages * page_size; + +		ptr = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, off); +		if (ptr == MAP_FAILED) +			fatal("mmap failed: %s", name); + +		/* determine cached pages */ +		if (mincore(ptr, len, vec)) +			fatal("mincore failed: %s", name); + +		/* turn off readahead */ +		if (madvise(ptr, len, MADV_RANDOM)) +			fatal("madvice failed: %s", name); + +		if (sigsetjmp(sigbus_jmp, 1)) { +			end = off + sigbus_addr ? sigbus_addr - ptr : 0; +			fprintf(stderr, "got sigbus at offset %lld: %s\n", +					(long long)end, name); +			goto got_sigbus; +		} + +		/* populate ptes */ +		for (i = 0; i < nr_pages ; i++) { +			if (vec[i] & 1) +				(void)*(volatile int *)(ptr + i * page_size); +		} +got_sigbus: + +		/* turn off harvesting reference bits */ +		if (madvise(ptr, len, MADV_SEQUENTIAL)) +			fatal("madvice failed: %s", name); + +		if (pagemap_read(buf, (unsigned long)ptr / page_size, +					nr_pages) != nr_pages) +			fatal("cannot read pagemap"); + +		munmap(ptr, len); + +		for (i = 0; i < nr_pages; i++) { +			pfn = pagemap_pfn(buf[i]); +			if (!pfn) +				continue; +			if (!kpageflags_read(&flags, pfn, 1)) +				continue; +			if (first && opt_list) { +				first = 0; +				flush_page_range(); +				show_file(name, st); +			} +			add_page(off / page_size + i, pfn, flags, buf[i]); +		} +	} + +	close(fd); +} + +int walk_tree(const char *name, const struct stat *st, int type, struct FTW *f) +{ +	(void)f; +	switch (type) { +	case FTW_F: +		if (S_ISREG(st->st_mode)) +			walk_file(name, st); +		break; +	case FTW_DNR: +		fprintf(stderr, "cannot read dir: %s\n", name); +		break; +	} +	return 0; +} + +static void walk_page_cache(void) +{ +	struct stat st; + +	kpageflags_fd = checked_open(PROC_KPAGEFLAGS, O_RDONLY); +	pagemap_fd = checked_open("/proc/self/pagemap", O_RDONLY); +	sigaction(SIGBUS, &sigbus_action, NULL); + +	if (stat(opt_file, &st)) +		fatal("stat failed: %s\n", opt_file); + +	if (S_ISREG(st.st_mode)) { +		walk_file(opt_file, &st); +	} else if (S_ISDIR(st.st_mode)) { +		/* do not follow symlinks and mountpoints */ +		if (nftw(opt_file, walk_tree, 64, FTW_MOUNT | FTW_PHYS) < 0) +			fatal("nftw failed: %s\n", opt_file); +	} else +		fatal("unhandled file type: %s\n", opt_file); + +	close(kpageflags_fd); +	close(pagemap_fd); +	signal(SIGBUS, SIG_DFL); +} +  static void parse_file(const char *name)  { +	opt_file = name;  }  static void parse_addr_range(const char *optarg) @@ -983,15 +1149,20 @@ int main(int argc, char *argv[])  	if (opt_list && opt_pid)  		printf("voffset\t"); +	if (opt_list && opt_file) +		printf("foffset\t");  	if (opt_list == 1)  		printf("offset\tlen\tflags\n");  	if (opt_list == 2)  		printf("offset\tflags\n"); -	walk_addr_ranges(); +	if (opt_file) +		walk_page_cache(); +	else +		walk_addr_ranges();  	if (opt_list == 1) -		show_page_range(0, 0, 0);  /* drain the buffer */ +		flush_page_range();  	if (opt_no_summary)  		return 0;  | 
