/* * modifications a apporter aux sources pour que le mhshm soit effectif : * ajouter mhshm.o comme cible a compiler dans mm (ou ailleurs si mhshm.c est déplacé) * ajouter sys_mhshm_diverter comme appel système dans arch/.../kernel/entry.S * ajouter l'appel à mhshm_init() dans init/main.c * ajouter l'appel à mhshm_handle_write_fault() dans arch/.../mm/fault.c:do_page_fault() (avec prototype) (sortie par return) * ajouter l'appel à mhshm_handle_singlestep () dans traps.c:do_debug() (sortie par clear_dr7) * * il faut également mettre à jour le numéro de l'appel système dans mhshm.c (hors noyau). * */ /* TODO pour le moment si un process essaye de mapper deux fois la meme zone (meme id), le comportement est indéfini */ #include #include #include #include /* file* */ #include /* vm_area_struct */ #include /* flags PROT_READ ... */ #include /* proc */ #include /* ptrace */ #include /* kernel_lock() */ #include /* copy_to_user */ #include /* flush_tlb */ /* mémoire partagée multi-machines * projet lan GUILLOT LADRON IIE2003 */ #define DEBUG 0 #ifdef DEBUG #define dbgprintk(x, A...) printk(KERN_DEBUG "%d %s: " x "\n", current->pid, __FUNCTION__, ## A) #else #define dbgprintk(x...) do {} while (0) #endif /* 8+5*203=1023 */ #define MHSHM_NBMODS_KERN 200 struct mhshm_mod { struct mhshm_mod *next; unsigned long nb_users; unsigned long nb_mod; unsigned long offset[MHSHM_NBMODS_KERN]; }; struct pid_list { struct pid_list *next; pid_t pid; unsigned long address; /* addresse de mappage de la zone (addresse de début) */ }; struct mhshm { long id; struct mhshm *next; pid_t daemon_pid; unsigned long nb_users; struct file *filp; /* pointeur sur la zone partagée */ struct pid_list *pid_list; /* le premier pid de la liste est le démon */ struct mhshm_mod *mods; }; struct semaphore mhshm_sem; struct mhshm *mhshm_head; struct mhshm_mod mhshm_nomods; /* variable recopiée par getmod si nomod */ /* variables utilisées pour la gestion de l'écriture */ struct mhshm *mhshm_writtenmhshm; unsigned long mhshm_writtenoffset; pid_t mhshm_writerpid; unsigned char mhshm_lastdata[4]; /* prédéclarations */ static void mhshm_open (struct vm_area_struct *vma); static void mhshm_close (struct vm_area_struct *vma); static int mhshm_mmap (struct file *filp, struct vm_area_struct *vma); static struct page *mhshm_nopage (struct vm_area_struct *, unsigned long, int); static struct vm_operations_struct mhshm_vm_ops = { open: mhshm_open, close: mhshm_close, nopage: mhshm_nopage }; static struct file_operations mhshm_file_ops = { mmap: mhshm_mmap }; /* fonction de lecture du fichier /proc/mhshm */ /* * buffer est un buffer d'une page, offset et length sont les parametres du read () * length est inférieur a la taille du buffer * start est un pointeur dans le buffer et eof doit etre mis a 1 si la lecture est finie * data ne sert a rien (c'est le dernier parametre de create_proc_read_entry) */ #ifdef CONFIG_PROC_FS int mhshm_proc_status (char *buffer, char **start, const off_t offset, const int length, int *eof, void *data) { int pos, dec; /* pos est la position dans la chaine ("id daemon...") dec est le nombre de caractères écrits depuis le dernier reset du buffer */ struct mhshm *mhshmp = mhshm_head; struct pid_list *pl; /* le snprintf du noyau renvoie le nombre d'octets ecrits (!=libc qui renvoie le nombre d'octets qu'il aurait fallu) */ /* affiche l'entete */ dec = 0; pos = snprintf (buffer, length, " id daemon pid_list\n"); down (&mhshm_sem); while (mhshmp) { /* affiche les informations sur chaque page */ pos += snprintf (buffer + pos - dec, length - pos, "%10ld %8ld ", mhshmp->id, (long)mhshmp->daemon_pid); pl = mhshmp->pid_list; while (pl) { pos += snprintf (buffer + pos - dec, length - pos, " %ld [%lX]%c", (long)pl->pid, pl->address, (pl->next ? ',' : ' ')); pl = pl->next; } if (pos < offset + length) pos += sprintf (buffer + pos - dec, "\n"); mhshmp = mhshmp->next; /* si on atteint la fin demandée */ if (pos > offset + length) goto done; /* si on n'a pas atteint le début demandé, on continue les sprintf a partir du début du buffer */ if (pos < offset) dec = pos; } /* si on arrive ici c'est qu'on a dit tout ce que l'on avait a dire */ *eof = 1; done: up (&mhshm_sem); *start = buffer + offset - dec; pos -= offset; if (pos > length) /* ce test est toujours faux (snprintf) */ pos = length; return pos; } #endif /* initialise le systeme mhshm (variables globales et semaphore) */ void __init mhshm_init (void) { mhshm_head = NULL; sema_init (&mhshm_sem, 1); mhshm_writerpid = -1; /* initialisation de mhshm_nomods */ memset (&mhshm_nomods, 0, sizeof (mhshm_nomods)); mhshm_nomods.next = NULL; #ifdef CONFIG_PROC_FS create_proc_read_entry ("mhshm", 0, NULL, mhshm_proc_status, NULL); #endif /* flashouille */ printk (KERN_NOTICE "\n\007 XXX Multi-host shared memory system initialized XXX \n\n"); } /* renvoie un pointeur sur la structure ayant l'identifiant id */ inline struct mhshm **mhshm_findmhshmp (const long id) { struct mhshm **mhshmp = &mhshm_head; while (*mhshmp) { if ((*mhshmp)->id == id) break; mhshmp = &(*mhshmp)->next; } return mhshmp; } /* renvoie un pointeur sur le pid_list ou sur le next du dernier si le pid n'existe pas */ inline struct pid_list **mhshm_findpid (struct mhshm *mhshm, const pid_t pid) { struct pid_list **ppl = &mhshm->pid_list; while (*ppl) { if ((*ppl)->pid == pid) break; ppl = &(*ppl)->next; } return ppl; } /* ajoute un pid à la liste des utilisateurs de la page mhshmp */ long mhshm_addpid (struct mhshm *mhshm, const pid_t pid, const unsigned long address) { struct pid_list **ppl = mhshm_findpid (mhshm, pid); if (!*ppl) { struct pid_list *pl = (struct pid_list *)kmalloc (sizeof (*pl), GFP_KERNEL); if (!pl) return -ENOMEM; pl->next = NULL; pl->pid = pid; pl->address = address; *ppl = pl; mhshm->nb_users++; } return 0; } /* enleve un pid de la liste des utilisateurs de la page */ void mhshm_delpid (struct mhshm *mhshm, const pid_t pid) { struct pid_list **ppl = mhshm_findpid (mhshm, pid); if (*ppl) { struct pid_list *pl = *ppl; *ppl = pl->next; kfree (pl); if (!--mhshm->nb_users) send_sig_info (SIGHUP, (struct siginfo *)1UL, find_task_by_pid (mhshm->daemon_pid)); } } /* destruction de la page partagée (on previent les utilisateurs par un signal ?) */ void mhshm_destroy (struct mhshm **mhshmp) { struct pid_list *pl; struct mhshm *mhshm = *mhshmp; dbgprintk ("destroying id %ld", mhshm->id); /* gestion des processus utilisateurs */ while ((pl = mhshm->pid_list)) { struct task_struct *tsk; /* on efface la tete de liste en lui envoyant un signal */ mhshm->pid_list = pl->next; tsk = find_task_by_pid (pl->pid); if (tsk) { up (&mhshm_sem); send_sig_info (SIGHUP, (struct siginfo *)1UL, tsk); down (&mhshm_sem); } kfree (pl); } mhshm->pid_list = NULL; /* destruction du segment partagé proprement dit */ fput (mhshm->filp); /* maj de la liste chainée */ *mhshmp = mhshm->next; kfree (mhshm); dbgprintk ("removed mhshm from list"); } /* ajoute la modification a l'addresse offset dans le segment en question */ long mhshm_addmod (struct mhshm *mhshm, const unsigned long offset) { struct mhshm_mod **modp = &mhshm->mods; int i = 0; dbgprintk ("offset : %lu\n", offset); /* on parcourt la liste des modifications que l'on a déjà en mémoire pour voir si il n'y a pas deux fois la meme addresse */ while (*modp) { for (i=0 ; i<(*modp)->nb_mod ; i++) { /* si c'est le cas on se contente de mettre a jour la valeur */ if ((*modp)->offset[i] == offset) return 0; } /* si on a parcouru une structure qui n'est pas pleine, c'est la derniere et il faut ajouter la modification a la fin de celle-ci */ if (i < MHSHM_NBMODS_KERN) goto setmod; /* sinon on regarde si la structure est la derniere de la liste */ modp = &(*modp)->next; } /* ici toutes les structures mod sont pleines et modp est l'addresse du champs suivant de la derniere */ *modp = kmalloc (sizeof (struct mhshm_mod), GFP_KERNEL); if (!*modp) /* on n'a plus de mémoire */ return -ENOMEM; /* on initialise la structure a 0 */ memcpy (*modp, &mhshm_nomods, sizeof (mhshm_nomods)); setmod: (*modp)->offset[(*modp)->nb_mod] = offset; (*modp)->nb_mod++; return 0; } /* met current en single-step (repris de ptrace.c) */ #define EFL_OFFSET (12*4-sizeof(struct pt_regs)) #define EIP_OFFSET (10*4-sizeof(struct pt_regs)) #define TRAP_FLAG 0x100 inline void *mhshm_geteip (void) { void *ret; unsigned char *stack; lock_kernel (); stack = (unsigned char*)current->thread.esp0; stack += EIP_OFFSET; ret = (*((void**)stack)); unlock_kernel (); return ret; } inline void mhshm_setTRAP (void) { long tmp; unsigned char *stack; lock_kernel (); stack = (unsigned char*)current->thread.esp0; stack += EFL_OFFSET; tmp = (*((int*)stack)); tmp |= TRAP_FLAG; *(unsigned long*)stack = tmp; unlock_kernel (); } inline void mhshm_unsetTRAP (void) { long tmp; unsigned char *stack; lock_kernel (); stack = (unsigned char*)current->thread.esp0; stack += EFL_OFFSET; tmp = (*((int*)stack)); tmp &= ~TRAP_FLAG; *(unsigned long*)stack = tmp; unlock_kernel (); } /* renvoie le descripteur de page a partir de l'addresse dans l'espace d'addressage courant */ inline pte_t *mhshm_getpte (unsigned long addr) { pgd_t *pgd; pmd_t *pmd; pte_t *pte; pgd = pgd_offset (current->mm, addr); if (pgd_none (*pgd) || pgd_bad (*pgd)) return NULL; pmd = pmd_offset (pgd, addr); if (pmd_none (*pmd) || pmd_bad (*pmd)) return NULL; pte = pte_offset (pmd, addr); if (!pte_present (*pte)) return NULL; return pte; } /* appelé a chaque faute d'écriture dans une page mémoire */ /* renvoyer 1 si on gere la faute et 0 sinon * si on gere, il faut que la reprise de l'instruction qui a déclenché la faute soit autorisée * * on entre dans cette fonction avec current->mm->mmap_sem down_read, si on gere il faut le up() ici */ long mhshm_handle_write_fault (struct vm_area_struct *vma, unsigned long address, unsigned long err) { struct mhshm *mhshm; struct pid_list *pl; unsigned long offset; pte_t *pte; unsigned long opcode; if (!vma->vm_file || vma->vm_ops != &mhshm_vm_ops) return 0; down (&mhshm_sem); mhshm = *mhshm_findmhshmp (vma->vm_file->f_dentry->d_inode->i_ino); /* on verifie que la faute est bien celle qui nous interresse (un client qui essaye d'écrire dans son segment) */ if (!mhshm || !(pl = *mhshm_findpid (mhshm, current->pid)) || (pl->address != vma->vm_start)) goto out_unlock; /* transforme l'addresse en offset par rapport au début du segment */ offset = address - pl->address; dbgprintk ("Found process %d trying to modify segment id %ld at offset %lX", current->pid, mhshm->id, offset); if (!(err & 1)) { int fault; dbgprintk ("la page n'est pas présente, appel de handle_mm_fault pour l'amener en mémoire"); /* la page n'est pas présente en mémoire */ if ((fault = handle_mm_fault (current->mm, vma, address, 0)) <= 0) { printk (KERN_ERR "mhshm: trying to make page present failed (handle_mm_fault returned %d)\n", fault); goto out_unlock; } } /* on met a jour les variables internes du noyau, il faut aussi mettre a jour la mmu (il semble que ca ne soit pas fait, l'écriture rappelle do_page_fault() qui appelle do_wp_page()) */ pte = mhshm_getpte (address); if (!pte) { printk (KERN_ERR "mhshm: getpte: impossible de trouver le pointeur de page pour l'addresse %lX\n", address); goto out_unlock; } if (pte_write (*pte)) { printk (KERN_ERR "mhshm: la page a déjà l'autorisation d'écriture, et une faute a quand meme eu lieu\n"); } up_read (¤t->mm->mmap_sem); /* on arrive ici avec le sémaphore mm en lecture */ set_pte (pte, pte_mkdirty (pte_mkwrite (*pte))); flush_tlb_page (vma, address); update_mmu_cache (vma, address, *pte); /* on met le processus ecrivain en single-step */ mhshm_setTRAP(); copy_from_user (mhshm_lastdata, (void *)address, 4); copy_from_user (&opcode, mhshm_geteip(), 4); dbgprintk ("Addresse d'exécution courante : %p, opcode : %lX", mhshm_geteip(), opcode); dbgprintk ("page en écriture, processus en single-step, exiting."); /* met a jour les informations pour retrouver la modification */ mhshm_writtenoffset = offset; mhshm_writtenmhshm = mhshm; mhshm_writerpid = current->pid; /* on renvoie 1 en gardant le sémaphore mhshm_sem qui sera débloqué dans handle_singlestep() */ return 1; out_unlock: up (&mhshm_sem); return 0; } /* appelé chaque fois qu'un processus vient d'exécuter une instruction en étant tracé */ long mhshm_handle_singlestep (void) { struct task_struct *daemon; struct pid_list *pl; unsigned long address; pte_t *pte; unsigned char newdata[4]; int i; struct vm_area_struct *vma; /* on regarde si le processus qui nous appele est celui qui nous interesse */ if (current->pid != mhshm_writerpid) return 0; dbgprintk ("resetting run mode and memory protection"); pl = *mhshm_findpid(mhshm_writtenmhshm, mhshm_writerpid); if (!pl) { printk (KERN_ERR "mhshm: le pid %d a disparu pendant l'écriture sur la page\n", mhshm_writerpid); goto out; } address = pl->address + mhshm_writtenoffset; vma = find_vma (current->mm, address); /* on reinterdit l'écriture sur la page */ pte = mhshm_getpte (address); if (!pte) { printk (KERN_ERR "mhshm: handle_singlestep::getpte: impossible de trouver le pointeur de page pour l'addresse %lX\n", address); goto out; } set_pte (pte, pte_mkdirty (pte_wrprotect (*pte))); flush_tlb_page (vma, address); update_mmu_cache (vma, address, *pte); copy_from_user (newdata, (void *)address, 4); /* on met a jour le champs mod du segment */ for (i=0 ; i<4 ; i++) if (newdata[i] != mhshm_lastdata[i]) mhshm_addmod (mhshm_writtenmhshm, mhshm_writtenoffset+i); /* puis on prévient le démon controlant le segment */ daemon = find_task_by_pid (mhshm_writtenmhshm->daemon_pid); if (daemon) send_sig_info (SIGHUP, (struct siginfo *)1UL, daemon); else printk (KERN_ERR "mhshm: le démon du segment %lu est mort ?\n", mhshm_writtenmhshm->id); out: mhshm_unsetTRAP(); mhshm_writerpid = -1; up (&mhshm_sem); return 1; } /* appelé a chaque fois qu'un processus se fork(), current est le fils */ static void mhshm_open (struct vm_area_struct *vma) { struct mhshm *mhshm; down (&mhshm_sem); mhshm = *mhshm_findmhshmp (vma->vm_file->f_dentry->d_inode->i_ino); if (mhshm) { dbgprintk ("add pid %d to page id %ld", current->pid, mhshm->id); mhshm_addpid (mhshm, current->pid, vma->vm_start); } up (&mhshm_sem); } /* appelé lors du démappage d'un processus (soit volontaire soit au exit()) */ static void mhshm_close (struct vm_area_struct *vma) { struct mhshm **mhshmp; down (&mhshm_sem); mhshmp = mhshm_findmhshmp (vma->vm_file->f_dentry->d_inode->i_ino); if (*mhshmp) { dbgprintk ("rm pid %d from page id %ld", current->pid, (*mhshmp)->id); if ((*mhshmp)->daemon_pid == current->pid) mhshm_destroy (mhshmp); else mhshm_delpid (*mhshmp, current->pid); } up (&mhshm_sem); } static struct page *mhshm_nopage (struct vm_area_struct *vma, unsigned long address, int dontknow) { struct page *shm_nopage(struct vm_area_struct *, unsigned long, int); return shmem_nopage (vma, address, dontknow); } static int mhshm_mmap (struct file *filp, struct vm_area_struct *vma) { vma->vm_ops = &mhshm_vm_ops; return 0; } /* cree la page partagée en utilisant current comme démon a prévenir à l'écriture */ long do_mhshm_create (struct mhshm ***mhshmpp, const long id, const long size) { struct mhshm *tmp; char name[14]; long err; if (*((*mhshmpp) = mhshm_findmhshmp (id))) /* une page avec cet identifiant existe déjà */ return -EEXIST; tmp = (struct mhshm *)kmalloc (sizeof (*tmp), GFP_KERNEL); if (!tmp) return -ENOMEM; /* initialisation du descripteur */ tmp->id = id; tmp->next = NULL; tmp->daemon_pid = current->pid; tmp->nb_users = 0; tmp->pid_list = NULL; tmp->mods = NULL; /* le filp est repris de ipc shm */ sprintf (name, "MHSHM%8lx", id); tmp->filp = shmem_file_setup (name, size); err = PTR_ERR(tmp->filp); if (IS_ERR(tmp->filp)) { kfree (tmp); return err; } tmp->filp->f_dentry->d_inode->i_ino = id; /* ceci permet de retrouver le mhshm depuis le vma (vma->vm_file..->i_ino) */ tmp->filp->f_op = &mhshm_file_ops; /* insertion en fin de liste */ **mhshmpp = tmp; return 0; } /* mappe la page partagée dans l'espace d'addressage du processus current en renvoyant l'adresse dans kaddr (QUI DOIT POINTER DANS L'ESPACE DU NOYAU) */ long do_mhshm_attach (const struct mhshm *mhshm, unsigned long *kaddr, const long size, const unsigned long prot, const unsigned long flags) { void * user_addr; unsigned long ssize; ssize = mhshm->filp->f_dentry->d_inode->i_size; if (size > ssize) return -ENOSPC; down_write (¤t->mm->mmap_sem); user_addr = (void *)do_mmap (mhshm->filp, 0, size, prot, flags, 0); up_write (¤t->mm->mmap_sem); *kaddr = (unsigned long)user_addr; if (IS_ERR (user_addr)) /* user_addr doit etre signé */ return PTR_ERR(user_addr); return 0; } /* appel système de mappage d'une page existant déjà */ long sxs_mhshm_attach (const long id, unsigned long *uaddr) { long err; unsigned long kaddr; unsigned long prot, flags; struct mhshm *mhshm; down (&mhshm_sem); mhshm = *mhshm_findmhshmp (id); err = -EINVAL; if (!mhshm) goto out; prot = PROT_READ; /* le client peut juste lire par défaut */ flags = MAP_SHARED; err = do_mhshm_attach (mhshm, &kaddr, PAGE_SIZE, prot, flags); if (err) goto out; dbgprintk ("do_attach done, paddr = %p", (void*)kaddr); err = -EFAULT; if (copy_to_user (uaddr, &kaddr, sizeof (unsigned long))) goto out; err = mhshm_addpid (mhshm, current->pid, kaddr); out: up (&mhshm_sem); return err; } /* appel système de création et mappage d'une nouvelle page dont current sera le démon */ long sxs_mhshm_create (const long id, unsigned long *uaddr) { long err; unsigned long kaddr; struct mhshm **mhshmp; unsigned long prot, flags; down (&mhshm_sem); /* on créée la page */ err = do_mhshm_create (&mhshmp, id, PAGE_SIZE); if (err) goto out; dbgprintk ("create ok, attaching..."); /* et on en autorise l'acces par le créateur */ prot = PROT_READ | PROT_WRITE; /* le démon a acces à toute la page */ flags = MAP_SHARED; err = do_mhshm_attach (*mhshmp, &kaddr, PAGE_SIZE, prot, flags); if (err) goto out_destroy; err = -EFAULT; if (copy_to_user (uaddr, &kaddr, sizeof (unsigned long))) goto out_destroy; up (&mhshm_sem); dbgprintk ("done"); return 0; out_destroy: mhshm_destroy (mhshmp); out: up (&mhshm_sem); return err; } /* appel systeme de demappage d'une page pour un processus (si ce processus est le démon, destruction de la page) */ long sxs_mhshm_detach (const unsigned long addr) { struct vm_area_struct *vma; struct mm_struct *mm = current->mm; long err = -EINVAL; dbgprintk ("addr = %p", (void*)addr); down_write (&mm->mmap_sem); /* on cherche le vma qui a été créé pour le mappage du mhshm->filp */ for (vma=mm->mmap ; vma ; vma=vma->vm_next) { if (vma->vm_ops == &mhshm_vm_ops && (vma->vm_start - (vma->vm_pgoff<vm_start, vma->vm_end - vma->vm_start); err = 0; dbgprintk ("done"); break; } } up_write (&mm->mmap_sem); if (err) dbgprintk ("failed"); return err; } /* appel systeme de recuperation de la liste des modifications survenues sur la page depuis le dernier appel */ long sxs_mhshm_getmod (const long id, struct mhshm_mod *buff) { struct mhshm *mhshm; long err; static unsigned long again; down (&mhshm_sem); mhshm = *mhshm_findmhshmp (id); err = -EINVAL; if (!mhshm) goto out; /* seul le démon peut récuperer les modifications */ err = -EACCES; if (mhshm->daemon_pid != current->pid) goto out; err = -EFAULT; if (mhshm->mods) { struct mhshm_mod *tmp; /* on copie le buffer de modifications tel quel */ if (copy_to_user (buff, mhshm->mods, sizeof (*buff))) goto out; again = (mhshm->mods->next ? 1 : 0); /* puis on remplace le premier champs, qui est un pointeur pour le noyau et un flag pour le demon */ copy_to_user (buff, &again, sizeof (unsigned long)); copy_to_user (buff+4, &mhshm->nb_users, sizeof (unsigned long)); /* et on efface les modifications */ tmp = mhshm->mods; mhshm->mods = mhshm->mods->next; kfree (tmp); /* rq : si le démon essaye de recopier les modifications dans un buffer qui est cette page mappée en client, c'est la fete du slip */ } else { /* aucune modification n'est survenue, donc mhshm->mods == NULL * on recopie alors mhshm_nomods dans le buffer user */ if (copy_to_user (buff, &mhshm_nomods, sizeof (*buff))) goto out; /* mais on met la valeur réelle pour le nb d'utilisateurs */ copy_to_user (buff+4, &mhshm->nb_users, sizeof (unsigned long)); } err = 0; dbgprintk ("done"); out: up (&mhshm_sem); return err; } asmlinkage long sys_mhshm_diverter (const int funnum, long arglong, void *argptr) { switch (funnum) { case 0: return sxs_mhshm_attach (arglong, (unsigned long*)argptr); case 1: return sxs_mhshm_create (arglong, (unsigned long*)argptr); case 2: return sxs_mhshm_detach ((unsigned long)argptr); case 3: return sxs_mhshm_getmod (arglong, (struct mhshm_mod *)argptr); default: return -ENOSYS; } } #undef dbgprintk