/* Segments de memoire lineaire */ /* Copyright (C) 2008 Emmanuel Varoquaux This file is part of XOS. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Toutes les maps sont privees et bloquent les ecritures sur le fichier * sous-jacent (drapeaux POSIX MAP_PRIVATE et MAP_DENYWRITE). */ #include "map_struct.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static char zero_page[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE))); /* Segments */ /* Les segments ne devraient etre accedes qu'en lecture seule en dehors de * cette section. * start doit etre aligne sur PAGE_SIZE. * end doit etre superieur a start et congru a -1 modulo PAGE_SIZE. */ static void init_segment(struct segment_struct *segment, unsigned long start, unsigned long end) { segment->start = start; segment->end = end; } /* laddr doit etre inferieur a segment->start. */ static void grow_down_segment(struct segment_struct *segment, unsigned long laddr) { segment->start = PAGE(laddr); } static void resize_segment(struct segment_struct *segment, unsigned long end) { segment->end = end; } static int compare_segment_laddr(const struct segment_struct *segment, unsigned long laddr) { if (segment->end < laddr) return -1; if (segment->start > laddr) return 1; return 0; } static int compare_segments(const struct segment_struct *segment1, const struct segment_struct *segment2) { if (segment1->end < segment2->start) return -1; if (segment1->start > segment2->end) return 1; return 0; } static int intersect_segments(const struct segment_struct *segment1, const struct segment_struct *segment2, struct segment_struct *inter_segment) { if (segment1->end < segment2->start) return -1; if (segment1->start > segment2->end) return 1; inter_segment->start = max(segment1->start, segment2->start); inter_segment->end = min(segment1->end, segment2->end); return 0; } static int merge_segments(const struct segment_struct *segment1, const struct segment_struct *segment2, struct segment_struct *merge_segment) { if (segment2->start == 0 || segment1->end != segment2->start - 1) return 0; merge_segment->start = segment1->start; merge_segment->end = segment2->end; return 1; } /* Projections */ /* [start, end[ ne doit pas couvrir tout l'espace d'adressage, de maniere a ce * que length = end - start + 1 soit toujours calculable. */ static void init_map(struct map_struct *map, unsigned long start, unsigned long end, int prot, int grows_down, const struct map_operations_struct *operations) { init_segment(&map->segment, start, end); map->prot = prot; map->grows_down = grows_down; map->operations = operations; } /* mem_map */ static inline unsigned long mem_map_get_mem_page_frame(const struct map_struct *mem_map, unsigned long page) { return (unsigned long)(mem_map->mem_start + (page - mem_map->segment.start)); } static void mem_map_do_destroy(struct map_struct *mem_map) {} static void mem_map_do_clone(const struct map_struct *mem_map, struct map_struct *dest_map) { dest_map->mem_start = mem_map->mem_start; } static int mem_map_do_is_mergeable(const struct map_struct *mem_map, const struct map_struct *mem_map1) { return mem_map1->mem_start == mem_map->mem_start; } static void mem_map_do_merge(struct map_struct *mem_map, struct map_struct *mem_map1) {} static void mem_map_do_grow_down(struct map_struct *mem_map) {} static int mem_map_do_fill_page(const struct map_struct *mem_map, unsigned long page) { memcpy((void *)page, (void *)mem_map_get_mem_page_frame(mem_map, page), PAGE_SIZE); return 1; } static unsigned long mem_map_do_get_clean_page_frame(const struct map_struct *mem_map, unsigned long laddr) { return mem_map_get_mem_page_frame(mem_map, PAGE(laddr)); } static void mem_map_do_set_clean_page_frame(const struct map_struct *mem_map, unsigned long laddr, unsigned long page_frame) {} static void mem_map_do_dirty_page(const struct map_struct *mem_map, unsigned long laddr, unsigned long page_frame) {} static int mem_map_get_info(const struct map_struct *mem_map, struct map_info_struct *map_info) { map_info->offset = 0; map_info->dev = 0; map_info->id = 0; map_info->pathname = NULL; return 0; } static const struct map_operations_struct mem_map_operations = { .do_destroy = mem_map_do_destroy, .do_clone = mem_map_do_clone, .do_is_mergeable = mem_map_do_is_mergeable, .do_merge = mem_map_do_merge, .do_grow_down = mem_map_do_grow_down, .do_fill_page = mem_map_do_fill_page, .do_get_clean_page_frame = mem_map_do_get_clean_page_frame, .do_set_clean_page_frame = mem_map_do_set_clean_page_frame, .do_dirty_page = mem_map_do_dirty_page, .do_get_info = mem_map_get_info }; static void init_mem_map(struct map_struct *mem_map, unsigned long start, unsigned long length, int prot, void *mem_start) { init_map(mem_map, start, start + (length - 1), prot, 0, &mem_map_operations); mem_map->mem_start = mem_start; } /* anon_map */ static void anon_map_do_destroy(struct map_struct *anon_map) {} static void anon_map_do_clone(const struct map_struct *anon_map, struct map_struct *dest_map) { dest_map->name = anon_map->name; } static int anon_map_do_is_mergeable(const struct map_struct *anon_map, const struct map_struct *anon_map1) { if (!anon_map1->name || !anon_map->name) return !anon_map1->name && !anon_map->name; return !strcmp(anon_map1->name, anon_map->name); } static void anon_map_do_merge(struct map_struct *anon_map, struct map_struct *anon_map1) {} static void anon_map_do_grow_down(struct map_struct *anon_map) {} static int anon_map_do_fill_page(const struct map_struct *anon_map, unsigned long page) { memset((void *)page, 0, PAGE_SIZE); return 1; } static unsigned long anon_map_do_get_clean_page_frame(const struct map_struct *anon_map, unsigned long laddr) { return (unsigned long)zero_page; } static void anon_map_do_set_clean_page_frame(const struct map_struct *anon_map, unsigned long laddr, unsigned long page_frame) {} static void anon_map_do_dirty_page(const struct map_struct *anon_map, unsigned long laddr, unsigned long page_frame) {} static int anon_map_get_info(const struct map_struct *anon_map, struct map_info_struct *map_info) { unsigned int size; map_info->offset = 0; map_info->dev = 0; map_info->id = 0; if (anon_map->name) { size = strlen(anon_map->name) + 3; if (!(map_info->pathname = kmalloc(size))) return -ENOMEM; snprintf(map_info->pathname, size, "[%s]", anon_map->name); } else map_info->pathname = NULL; return 0; } static const struct map_operations_struct anon_map_operations = { .do_destroy = anon_map_do_destroy, .do_clone = anon_map_do_clone, .do_is_mergeable = anon_map_do_is_mergeable, .do_merge = anon_map_do_merge, .do_grow_down = anon_map_do_grow_down, .do_fill_page = anon_map_do_fill_page, .do_get_clean_page_frame = anon_map_do_get_clean_page_frame, .do_set_clean_page_frame = anon_map_do_set_clean_page_frame, .do_dirty_page = anon_map_do_dirty_page, .do_get_info = anon_map_get_info }; static void init_anon_map(struct map_struct *anon_map, unsigned long start, unsigned long length, int prot, int grows_down, const char *name) { init_map(anon_map, start, start + (length - 1), prot, grows_down, &anon_map_operations); anon_map->name = name; } /* file_map */ static inline unsigned long file_map_get_file_offset(const struct map_struct *file_map, unsigned long page) { return file_map->offset + (page - file_map->segment.start); } static void file_map_do_destroy(struct map_struct *file_map) { node_release_write_access(file_map->file); node_release(file_map->file); } static void file_map_do_clone(const struct map_struct *file_map, struct map_struct *dest_map) { node_hold(file_map->file); node_deny_write_access(file_map->file); /* ne peut pas echouer car file_map->file est deja bloque en ecriture */ dest_map->file = file_map->file; dest_map->offset = file_map_get_file_offset(file_map, dest_map->segment.start); } static int file_map_do_is_mergeable(const struct map_struct *file_map, const struct map_struct *file_map1) { /* file_map->segment.end + 1 est valide car egal a file_map1->segment.start. */ return (file_map1->file == file_map->file && file_map_get_file_offset(file_map, file_map->segment.end + 1) == file_map1->offset); } static void file_map_do_merge(struct map_struct *file_map, struct map_struct *file_map1) { node_release_write_access(file_map1->file); node_release(file_map1->file); } static void file_map_do_grow_down(struct map_struct *file_map) {} static int file_map_do_fill_page(const struct map_struct *file_map, unsigned long page) { unsigned long foff; unsigned int count; int rv; foff = file_map_get_file_offset(file_map, page); if ((rv = read_node(file_map->file, foff, (void *)page, PAGE_SIZE)) < 0) return 0; count = rv; if (count < PAGE_SIZE) memset((void *)(page + count), 0, PAGE_SIZE - count); return 1; } static unsigned long file_map_do_get_clean_page_frame(const struct map_struct *file_map, unsigned long laddr) { return mapping_get_page_frame(node_get_mapping(file_map->file), file_map_get_file_offset(file_map, laddr)); } static void file_map_do_set_clean_page_frame(const struct map_struct *file_map, unsigned long laddr, unsigned long page_frame) { mapping_set_page_frame(node_get_mapping(file_map->file), file_map_get_file_offset(file_map, laddr), page_frame); /* on ignore le code de retour */ } static void file_map_do_dirty_page(const struct map_struct *file_map, unsigned long laddr, unsigned long page_frame) { mapping_delete_page_frame(node_get_mapping(file_map->file), file_map_get_file_offset(file_map, laddr), page_frame); } static int file_map_get_info(const struct map_struct *file_map, struct map_info_struct *map_info) { struct stat_struct statbuf; int rv; if ((rv = stat_node(file_map->file, &statbuf)) < 0) return rv; map_info->offset = file_map->offset; map_info->dev = statbuf.dev; map_info->id = statbuf.id; if ((rv = get_path(file_map->file, &map_info->pathname)) < 0) return rv; return 0; } static const struct map_operations_struct file_map_operations = { .do_destroy = file_map_do_destroy, .do_clone = file_map_do_clone, .do_is_mergeable = file_map_do_is_mergeable, .do_merge = file_map_do_merge, .do_grow_down = file_map_do_grow_down, .do_fill_page = file_map_do_fill_page, .do_get_clean_page_frame = file_map_do_get_clean_page_frame, .do_set_clean_page_frame = file_map_do_set_clean_page_frame, .do_dirty_page = file_map_do_dirty_page, .do_get_info = file_map_get_info }; static int init_file_map(struct map_struct *file_map, unsigned long start, unsigned long length, int prot, struct file_struct *file, unsigned long offset) { int retval; init_map(file_map, start, start + (length - 1), prot, 0, &file_map_operations); node_hold(file); if ((retval = node_deny_write_access(file)) < 0) goto error_release; file_map->file = file; file_map->offset = offset; return 0; error_release: node_release(file); return retval; } /* Interface - initialisation */ void init_zero_page() { memset(zero_page, 0, PAGE_SIZE); } /* Interface - creation/destruction */ /* Pour toutes les fonctions de l'interface : * start, base doivent etre aligne sur PAGE_SIZE. * length, size doivent etre non nuls et multiples de PAGE_SIZE. * [start, start + length[, [base, base + size[ doivent etre valides. */ int alloc_mem_map(unsigned long start, unsigned long length, int prot, void *mem_start, struct map_struct **mem_map) { if (!(*mem_map = kmalloc(sizeof (struct map_struct)))) return -ENOMEM; init_mem_map(*mem_map, start, length, prot, mem_start); return 0; } int alloc_anon_map(unsigned long start, unsigned long length, int prot, int grows_down, const char *name, struct map_struct **anon_map) { if (!(*anon_map = kmalloc(sizeof (struct map_struct)))) return -ENOMEM; init_anon_map(*anon_map, start, length, prot, grows_down, name); return 0; } /* file doit etre un fichier regulier ou un fichier special. */ int alloc_file_map(unsigned long start, unsigned long length, int prot, struct file_struct *file, unsigned long offset, struct map_struct **file_map) { int retval; if (!(*file_map = kmalloc(sizeof (struct map_struct)))) { retval = -ENOMEM; goto error; } if ((retval = init_file_map(*file_map, start, length, prot, file, offset)) < 0) goto error_kfree; return 0; error_kfree: kfree(*file_map, sizeof (struct map_struct)); error: return retval; } void free_map(struct map_struct *map) { map->operations->do_destroy(map); kfree(map, sizeof (struct map_struct)); } /* Interface - creation/destruction a partir d'une projection */ /* Clone une projection. * Si l'intersection entre la projection et le segment [start, start + length[ * est vide, status est mis a -1 si la projection est inferieure au segment et * a 1 sinon. * Si la projection chevauche le segment, status est mis a 0 et new_map est * construit comme clone de l'intersection de la projection et du segment. */ int map_clone(const struct map_struct *map, unsigned long start, unsigned long length, int *status, struct map_struct **new_map) { struct segment_struct seg, inter_seg; int st; init_segment(&seg, start, start + (length - 1)); st = intersect_segments(&map->segment, &seg, &inter_seg); if (st) { *status = st; return 0; } if (!(*new_map = kmalloc(sizeof (struct map_struct)))) return -ENOMEM; init_map(*new_map, inter_seg.start, inter_seg.end, map->prot, map->grows_down, map->operations); map->operations->do_clone(map, *new_map); *status = 0; return 0; } /* Divise une projection. * start doit se trouver dans la projection. * map est modifiee pour contenir la projection inferieure. La projection * superieure est mise dans new_map, sauf si start est l'adresse de debut de * map, auquel cas map n'est pas modifiee et new_map est mise a NULL. */ int map_split(struct map_struct *map, unsigned long start, struct map_struct **new_map) { if (start == map->segment.start) { *new_map = NULL; return 0; } if (!(*new_map = kmalloc(sizeof (struct map_struct)))) return -ENOMEM; init_map(*new_map, start, map->segment.end, map->prot, map->grows_down, map->operations); map->operations->do_clone(map, *new_map); resize_segment(&map->segment, start - 1); return 0; } /* Fusionne les projections si possible. * En cas de reussite, le resultat est mis dans map et map1 est liberee. */ int map_merge(struct map_struct *map, struct map_struct *map1) { struct segment_struct merge_seg; if (!merge_segments(&map->segment, &map1->segment, &merge_seg)) return 0; if (map1->prot != map->prot) return 0; if (map1->operations != map->operations) return 0; if (!map->operations->do_is_mergeable(map, map1)) return 0; map->segment = merge_seg; map->operations->do_merge(map, map1); kfree(map1, sizeof (struct map_struct)); return 1; } /* Etend la projection vers le bas de maniere a ce qu'elle contienne laddr. */ void map_grow_down(struct map_struct *map, unsigned long laddr) { grow_down_segment(&map->segment, laddr); map->operations->do_grow_down(map); } /* Change la protection d'une projection. */ void map_protect(struct map_struct *map, int prot) { map->prot = prot; } /* Interface - fonctions sur les projections */ int map_verify_access(const struct map_struct *map, int access) { switch (access) { case PF_READ: return 1; case PF_WRITE: default: return map->prot == PROT_RDWR; } } int map_grows_down(const struct map_struct *map) { return map->grows_down; } int map_fill_page(const struct map_struct *map, unsigned long page) { return map->operations->do_fill_page(map, page); } unsigned long map_get_clean_page_frame(const struct map_struct *map, unsigned long laddr) { return map->operations->do_get_clean_page_frame(map, laddr); } void map_set_clean_page_frame(const struct map_struct *map, unsigned long laddr, unsigned long page_frame) { map->operations->do_set_clean_page_frame(map, laddr, page_frame); } void map_dirty_page(const struct map_struct *map, unsigned long laddr, unsigned long page_frame) { map->operations->do_dirty_page(map, laddr, page_frame); } int map_get_info(const struct map_struct *map, struct map_info_struct *map_info) { map_info->start_addr = map->segment.start; map_info->end_addr = map->segment.end + 1; switch (map->prot) { case PROT_RDONLY: map_info->perms.read = 1; map_info->perms.write = 0; break; case PROT_RDWR: map_info->perms.read = 1; map_info->perms.write = 1; break; } return map->operations->do_get_info(map, map_info); } void map_info_destroy(struct map_info_struct *map_info) { if (map_info->pathname) kfree(map_info->pathname, strlen(map_info->pathname) + 1); } /* Interface - utilitaires sur les projections */ /* Retourne : * - -1 si map est avant laddr ; * - 0 si map contient laddr ; * - 1 si map est apres laddr. */ int compare_map_laddr(const struct map_struct *map, unsigned long laddr) { return compare_segment_laddr(&map->segment, laddr); } int compare_map_prot(const struct map_struct *map, int prot) { return map->prot == prot; } /* Retourne : * - -1 si map1 est avant map2 ; * - 0 si map1 et map2 se chevauchent ; * - 1 si map1 est apres map2. */ int compare_maps(const struct map_struct *map1, const struct map_struct *map2) { return compare_segments(&map1->segment, &map2->segment); } /* map1 doit etre strictement inferieure a map2. * Si map1 est nulle, 0 est pris comme debut. * Si map2 est nulle, ULONG_MAX est pris comme fin. * Le trou est l'intervalle compris entre map1 et map2. * Retourne : * - -1 si le trou est avant [base, base + size[ ; * - 0 si le trou chevauche [base, base + size[ ; * - 1 si le trou est apres [base, base + size[. */ int hole_intersect(const struct map_struct *map1, const struct map_struct *map2, unsigned long base, unsigned long size, unsigned long *start, unsigned long *length) { struct segment_struct hole, seg, inter; int st; if (map1 && map1->segment.end == ULONG_MAX) return 1; if (map2 && map2->segment.start == 0) return -1; init_segment(&hole, map1 ? map1->segment.end + 1 : 0, map2 ? map2->segment.start - 1 : ULONG_MAX); init_segment(&seg, base, base + (size - 1)); st = intersect_segments(&hole, &seg, &inter); if (st) return st; *start = inter.start; *length = inter.end - inter.start + 1; /* valide car inferieur ou egal a size */ return 0; }