#include #include #include #include #include #include #include #include #include "parse.h" #include "pagemap.h" #include "pidlib.h" static struct maps_list *alloc_maplist(void) { struct maps_list *map; map = malloc(sizeof *map); if (map == NULL) goto err; memset(map, 0, sizeof(*map)); INIT_LIST_HEAD(&map->list); err: return map; } static struct maps *alloc_map(void) { struct maps *map; map = malloc(sizeof *map); if (map == NULL) goto err; memset(map, 0, sizeof(*map)); INIT_LIST_HEAD(&map->list); err: return map; } static struct maps *parse_maps(FILE *file, int pid, int tid) { struct maps *the_map = NULL; char line[1024]; int ret; while (fgets(line, sizeof(line), file)) { struct maps *map = alloc_map(); unsigned long start, end; char name[1024]; if (map == NULL) return 0; if (the_map == NULL) the_map = map; ret = sscanf(line, "%lx-%lx %*s %*s %*s %*s %s", &start, &end, name); if (ret < 2) { printf("Error reading input: %s\n", line); break; } map->start = start; map->end = end; map->size = end - start; map->pid = pid; map->tid = tid; if (ret >= 3) strncpy(map->name, name, sizeof(map->name)); list_add_tail(&map->list, &the_map->list); } return the_map; } static struct pageframe *alloc_pageframe(void) { struct pageframe *pageframe; pageframe = malloc(sizeof *pageframe); if (pageframe == NULL) goto err; clear_pageframe(pageframe); err: return pageframe; } static int compare_pageframe(struct bintree *at, struct bintree *bt) { struct pageframe *a, *b; a = tree_to_pageframe(at); b = tree_to_pageframe(bt); return a->pf - b->pf; } struct bintree_ops pageframe_ops = { .compare = compare_pageframe, }; static int should_scan_process(struct parse_opts *opts, struct process *process) { struct pidlist *pid; int match = 0; if (is_parse_option(opts, PARSE_PID)) { list_for_each_entry(pid, &opts->pidlist, list) { if (pid->pid == process->pid) { match = 1; break; } } } if (is_parse_option(opts, PARSE_MAP_NAME)) match = 1; if (is_parse_option(opts, PARSE_NOADD_TREE)) match = !match; return match; } static int should_scan_mapping(struct parse_opts *opts, struct maps *map) { int match = 0; if (is_parse_option(opts, PARSE_MAP_NAME)) { if (strstr(map->name, opts->name)) match = 1; if (is_parse_option(opts, PARSE_NOADD_TREE)) match = !match; } else match = 1; return match; } static int should_add_to_tree(struct parse_opts *opts, struct pageframe *pf, struct maps *map) { if (is_parse_option(opts, PARSE_NOADD_TREE)) return 0; return 1; } /* Read data from the /proc/pid/pagemap file */ static int parse_pageframe(FILE *file, struct pageframe *pf_tree, struct maps *maps, struct parse_opts *opts) { struct maps *map; struct maps_list *tmp; struct pageframe *match, *pageframe = NULL; long start, len, i; unsigned long long pf[10240]; int ret, error; if (maps == NULL) return 0; /* Go through the list of allocated memory areas */ list_for_each_entry(map, &maps->list, list) { start = map->start >> (PAGE_SHIFT - 3); len = map->size >> (PAGE_SHIFT); if (!should_scan_mapping(opts, map)) continue; ret = fseek(file, start, SEEK_SET); if (ret) { error = errno; fprintf(stderr, "Error seeking to %lx: %s\n", start, strerror(error)); continue; } for (i = 0; i < len; i++) { if (!ret) { ret = fread(&pf, 1, MIN(sizeof(pf), (len - i) * 8), file); } if (ret < 0) { error = errno; continue; } if (!pageframe) pageframe = alloc_pageframe(); ret -= sizeof(pf[0]); /* ignore unused pages */ if (!pf[ret / sizeof(pf[0])]) continue; pageframe->pf = (pf[ret / sizeof(pf[0])]); /* ignore unused pages */ if (!(page_swapped(pageframe) || page_present(pageframe))) continue; if (should_add_to_tree(opts, pageframe, map)) { match = tree_to_pageframe( bintree_add(&pf_tree->tree, &pageframe->tree, &pageframe_ops)); } else { match = tree_to_pageframe( bintree_find(&pf_tree->tree, &pageframe->tree, &pageframe_ops)); } if (match == NULL) continue; if (match == pageframe) pageframe = NULL; match->refcount++; /* * Add a link from the physical page to this * process's page map */ tmp = alloc_maplist(); tmp->map = map; list_add(&tmp->list, &match->ml); if (page_present(match)) map->pages_present++; else if (page_swapped(match)) map->pages_swapped++; } } return 0; } static int read_pageframe(int pid, int tid, struct pageframe *pageframe, struct process *process_list, struct parse_opts *opts) { struct maps *maps; struct process *process; struct pidlist *pidl, *n; FILE *file; char path[512]; process = malloc(sizeof(*process)); memset(process, 0, sizeof(*process)); INIT_LIST_HEAD(&process->list); process->pid = pid; process->tid = tid; if (!should_scan_process(opts, process)) goto free; snprintf(path, sizeof(path), "/proc/%d/task/%d/maps", pid, tid); file = fopen(path, "rb"); if (!file) goto free; maps = parse_maps(file, pid, tid); fclose(file); process->maps = maps; snprintf(path, sizeof(path), "/proc/%d/task/%d/pagemap", pid, tid); file = fopen(path, "rb"); if (!file) goto free; parse_pageframe(file, pageframe, maps, opts); fclose(file); if (read_cmdline(pid, tid, process->name, sizeof(process->name))) goto free; if (maps != NULL) { list_for_each_entry(maps, &process->maps->list, list) { process->pages_present += maps->pages_present; process->pages_swapped += maps->pages_swapped; } } if (!is_parse_option(opts, PARSE_NOADD_TREE)) process->is_initial_pid = 1; list_add_tail(&process->list, &process_list->list); return 1; free: free(process); /* * Remove the pid from the list. It is no longer an * interesting pid, since we can't access its data */ list_for_each_entry_safe(pidl, n, &opts->pidlist, list) { if (pidl->pid == pid) { list_del(&pidl->list); free(pidl); break; } } return 0; } static int read_pageframe_with_threads(int pid, struct pageframe *pageframe, struct process *process_list, struct parse_opts *opts) { DIR *dir = NULL; int tid; int count = 0; while (1) { if (opts->with_threads) tid = get_next_tid(pid, &dir); else tid = pid; if (tid <= 0) return count; count += read_pageframe(pid, tid, pageframe, process_list, opts); if (!opts->with_threads) break; } return count; } int scan_all_pids(struct pageframe *pf, struct process *process_list, struct parse_opts *opts) { struct pidlist *pidlist, *n; DIR *dir = NULL; int pid; int count = 0; if (is_parse_option(opts, PARSE_PID)) { list_for_each_entry_safe(pidlist, n, &opts->pidlist, list) { count += read_pageframe_with_threads(pidlist->pid, pf, process_list, opts); } } if ((count == 0) && !(is_parse_option(opts, PARSE_MAP_NAME))) { printf("Failed to find any matching processes " "with given arguments\n"); return -1; } if (is_parse_option(opts, PARSE_DUMP)) return 0; if (is_parse_option(opts, PARSE_MAP_NAME)) { while (1) { pid = get_next_pid(&dir); if (pid <= 0) break; read_pageframe_with_threads(pid, pf, process_list, opts); } } /* Do not add new pages in the tree after the initial scan */ opts->parse_mask |= PARSE_NOADD_TREE; while (1) { pid = get_next_pid(&dir); if (pid <= 0) break; read_pageframe_with_threads(pid, pf, process_list, opts); } return 0; } struct kpageflag_data { struct bintree_ops ops; int kpageflags_fd; int kpagecount_fd; }; #define bintree_ops_to_kpfd(bintree_ops) \ container_of((bintree_ops), struct kpageflag_data, ops) static void _update_kpageflags(struct bintree *bt, struct bintree_ops *ops) { struct pageframe *pf = tree_to_pageframe(bt); struct kpageflag_data *kpfd = bintree_ops_to_kpfd(ops); int ret, error; long int pfnn = pfn(pf) * sizeof(pf->kpageflags); ret = lseek(kpfd->kpageflags_fd, pfnn, SEEK_SET); if (ret < 0) { error = errno; fprintf(stderr, "Error seeking to %lx: %s\n", pfnn, strerror(error)); return; } ret = read(kpfd->kpageflags_fd, &pf->kpageflags, sizeof(pf->kpageflags)); if (ret < 0) { error = errno; fprintf(stderr, "Error reading from %llx: %s\n", pf->pf * sizeof(pf->kpageflags), strerror(error)); return; } ret = lseek(kpfd->kpagecount_fd, pfnn, SEEK_SET); if (ret < 0) { error = errno; fprintf(stderr, "Error seeking to %lx: %s\n", pfnn, strerror(error)); return; } ret = read(kpfd->kpagecount_fd, &pf->kpagecount, sizeof(pf->kpagecount)); if (ret < 0) { error = errno; fprintf(stderr, "Error reading from %llx: %s\n", pf->pf * sizeof(pf->kpagecount), strerror(error)); return; } } int update_kpageflags(struct pageframe *pf) { struct kpageflag_data kpfd = { .ops = { .callback = _update_kpageflags, }, .kpageflags_fd = open("/proc/kpageflags", O_RDONLY), .kpagecount_fd = open("/proc/kpagecount", O_RDONLY), }; if (kpfd.kpageflags_fd == -1 || kpfd.kpagecount_fd == -1) return -1; bintree_walk(&pf->tree, &kpfd.ops); close(kpfd.kpageflags_fd); close(kpfd.kpagecount_fd); return 0; }