View of xos/mm/vm.c


XOS | Parent Directory | View | Download

/* Memoire virtuelle utilisateur */
/* 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/>. */
 
#include "memory.h"
#include "page_fault.h"
#include "maps.h"
#include "map.h"
#include "paging.h"
 
#include <vm_struct.h>
#include <map_info_struct.h>
#include <gdt.h>
#include <errno.h>
#include <consts.h>
#include <enums.h>
#include <config.h>
#include <util.h>
#include <string.h>
#include <limits.h>
 
#define USER_LIMIT (0xfffff - USER_BASE / PAGE_SIZE) /* en pages */
 
#define STACK_MARGIN (65536 + 32 * sizeof (unsigned long))
 
static inline int check_for_addition(unsigned long i1, unsigned long i2)
{
  return i1 <= ULONG_MAX - i2;
}
 
/* Verifie que la zone demarrant a l'adresse virtuelle start et de longueur
 * length est incluse dans le segment utilisateur.
 * Cela implique que toute adresse appartenant a cette zone peut etre convertie
 * en une adresse lineaire en lui ajoutant USER_BASE. */
static inline int in_user_segment(unsigned long start, unsigned long length)
{
  if (!check_for_addition(start, length - PAGE_SIZE))
    return 0;
  if (start + (length - PAGE_SIZE) > USER_LIMIT * PAGE_SIZE)
    return 0;
  return 1;
}
 
static inline int in_stack(unsigned long addr, unsigned long stack_top)
{
  return stack_top < STACK_MARGIN || addr >= stack_top - STACK_MARGIN;
}
 
void mm_init()
{
  init_zero_page();
  gdt_set_user_code_seg_descr(USER_BASE, USER_LIMIT); /* segment de code utilisateur */
  gdt_set_user_data_seg_descr(USER_BASE, USER_LIMIT); /* segment de donnees utilisateur */
}
 
/* Copie la memoire du processus courant. */
int vm_copy(const struct vm_struct *vm, struct vm_struct *dest_vm, unsigned long *dest_page_directory)
{
  int rv;
 
  init_page_directory(dest_page_directory);
 
  init_maps(&dest_vm->maps);
  if (!vm)
    return 0;
  else {
    if ((rv = copy_memory(&vm->maps, &dest_vm->maps, dest_page_directory, USER_BASE, (USER_LIMIT + 1) * PAGE_SIZE)) < 0)
      free_memory(&dest_vm->maps, USER_BASE, (USER_LIMIT + 1) * PAGE_SIZE);
    return rv;
  }
}
 
/* Libere la memoire du processus courant. */
void vm_free(struct vm_struct *vm)
{
  /* Puisqu'on libere *toute* la memoire, free_memory() ne devrait jamais
     echouer. On ignore sa valeur de retour. */
  free_memory(&vm->maps, USER_BASE, (USER_LIMIT + 1) * PAGE_SIZE);
}
 
/* Cree une projection de la memoire du noyau dans l'espace d'adressage virtuel
 * du processus courant.
 * start et end doivent etre alignes sur une frontiere de page.
 * end doit etre strictement superieur a start. */
int vm_map_mem(struct vm_struct *vm, unsigned long start, unsigned long end)
{
  unsigned long length;
 
  length = end - start;
  if (!in_user_segment(start, length))
    return -EINVAL;
  return map_mem(&vm->maps, USER_BASE + start, length, PROT_RDWR, (void *)start);
}
 
/* Cree une projection anonyme dans l'espace d'adressage virtuel du
 * processus courant.
 * start doit etre aligne sur une frontiere de page.
 * length doit etre non nul et multiple de PAGE_SIZE.
 * name peut etre nul. */
int vm_map_anon(struct vm_struct *vm, unsigned long start, unsigned long length, int prot, int grows_down, const char *name)
{
  if (!in_user_segment(start, length))
    return -EINVAL;
  return map_anon(&vm->maps, USER_BASE + start, length, prot, grows_down, name);
}
 
/* Cree une projection de fichier dans l'espace d'adressage virtuel du
 * processus courant.
 * start et offset doivent etre alignes sur une frontiere de page.
 * length doit etre non nul et multiple de PAGE_SIZE.
 * file doit etre un fichier regulier ou un fichier special. */
int vm_map_file(struct vm_struct *vm, unsigned long start, unsigned long length, int prot, struct file_struct *file, unsigned long offset)
{
  if (!in_user_segment(start, length))
    return -EINVAL;
  return map_file(&vm->maps, USER_BASE + start, length, prot, file, offset);
}
 
/* Detruit une projection dans l'espace d'adressage virtuel du processus
 * courant.
 * start doit etre aligne sur une frontiere de page.
 * length doit etre non nul et multiple de PAGE_SIZE. */
int vm_unmap(struct vm_struct *vm, unsigned long start, unsigned long length)
{
  if (!in_user_segment(start, length))
    return -EINVAL;
  return free_memory(&vm->maps, USER_BASE + start, length);
}
 
/* Change la protection d'une projection dans l'espace d'adressage virtuel du
 * processus courant.
 * start doit etre aligne sur une frontiere de page.
 * length doit etre non nul et multiple de PAGE_SIZE. */
int vm_protect(struct vm_struct *vm, unsigned long start, unsigned long length, int prot)
{
  if (!in_user_segment(start, length))
    return -EINVAL;
  return protect_memory(&vm->maps, USER_BASE + start, length, prot);
}
 
/* Retourne l'adresse de demarrage d'une zone libre pour une projection de
 * longueur length dans le segment [base, base + size[ de l'espace
 * d'adressage virtuel du processus courant.
 * base doit etre aligne sur une frontiere de page.
 * size doit etre non nul et multiple de PAGE_SIZE.
 * length doit etre non nul et multiple de PAGE_SIZE.
 * Si side est negatif, la zone libre est calee vers les basses addresses,
 * sinon la zone libre est calee vers les hautes addresses.
 * Zero est retourne en cas d'echec. */
unsigned long vm_get_free_area(const struct vm_struct *vm, unsigned long base, unsigned long size, unsigned long length, int side)
{
  unsigned long start;
 
  if (!in_user_segment(base, size))
    return 0;
  if (!(start = get_free_area(&vm->maps, USER_BASE + base, size, length, side)))
    return 0;
  return start - USER_BASE;
}
 
/* Verifie que [start, start + length[ peut etre accede, et remplit les pages
 * si necessaire.
 * length doit etre non nul.
 * start et length ne sont soumis a aucune autre contrainte.
 * 0 est retourne si l'acces memoire est invalide. */
int vm_verify_area(const struct vm_struct *vm, unsigned long start, unsigned long length, int access)
{
  unsigned long poff;
 
  poff = PAGE_OFFSET(start);
  start -= poff;
  if (!check_for_addition(length, poff))
    return 0;
  length += poff;
  if (!check_for_addition(length, PAGE_SIZE - 1))
    return 0;
  length = PAGE_ALIGN(length);
  if (!in_user_segment(start, length))
    return 0;
  return verify_memory(&vm->maps, USER_BASE + start, length, access);
}
 
/* Recherche le caractere nul '\0' dans une page. */
static int scan_page(unsigned long start, unsigned int n, unsigned int *len)
{
  const void *p;
 
  n = min(n, PAGE_SIZE - PAGE_OFFSET(start));
  if ((p = memchr((void *)(USER_BASE + start), 0, n))) {
    if (len)
      *len += p - (void *)(USER_BASE + start);
    return 1;
  }
  else {
    if (len)
      *len += n;
    return 0;
  }
}
 
/* Verifie que la chaine de caractere demarrant a l'adresse virtuelle start
 * peut etre accedee en lecture, et remplit les pages si necessaire.
 * start n'est soumise a aucune contrainte.
 * 0 est retourne si l'acces memoire est invalide. */
int vm_verify_str(const struct vm_struct *vm, unsigned long start, unsigned int n, unsigned int *len)
{
  unsigned long poff;
 
  poff = PAGE_OFFSET(start);
  start -= poff;
  if (!in_user_segment(start, PAGE_SIZE))
    return 0;
  if (!verify_memory(&vm->maps, USER_BASE + start, PAGE_SIZE, PF_READ))
    return 0;
  if (len)
    *len = 0;
  if (!scan_page(start + poff, n, len) && n >= PAGE_SIZE)
    do {
      n -= PAGE_SIZE;
      if (!check_for_addition(start, PAGE_SIZE))
        return 0;
      start += PAGE_SIZE;
      if (start > USER_LIMIT * PAGE_SIZE)
        return 0;
      if (!verify_memory(&vm->maps, USER_BASE + start, PAGE_SIZE, PF_READ))
        return 0;
    }
    while (!scan_page(start, n, len) && n >= PAGE_SIZE);
  return 1;
}
 
/* Retourne la liste des projections. */
int vm_get_maps(const struct vm_struct *vm, struct map_info_struct **map_info_array, unsigned int *count)
{
  int rv;
  unsigned int i;
 
  if ((rv = get_maps(&vm->maps, map_info_array, count)) < 0)
    return rv;
  for (i = 0; i < *count; i++) {
    (*map_info_array)[i].start_addr -= USER_BASE;
    (*map_info_array)[i].end_addr -= USER_BASE;
  }
  return 0;
}
 
int vm_handle_page_fault(const struct vm_struct *vm, int cause, int access, unsigned long stack_top, unsigned long laddr)
{
  return _handle_page_fault(laddr, cause, access, in_stack(laddr - USER_BASE, stack_top), &vm->maps);
}