View of xos/mm/map.c


XOS | Parent Directory | View | Download

/* 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 <http://www.gnu.org/licenses/>. */
 
/* 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 <node.h>
#include <stat_struct.h>
#include <map_info_struct.h>
#include <mapping.h>
#include <kmalloc.h>
#include <errno.h>
#include <consts.h>
#include <enums.h>
#include <i386.h>
#include <util.h>
#include <sprintf.h>
#include <string.h>
#include <limits.h>
 
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;
}