/* 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 . */ #include "memory.h" #include "page_fault.h" #include "maps.h" #include "map.h" #include "paging.h" #include #include #include #include #include #include #include #include #include #include #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); }