]> git.hungrycats.org Git - linux/commitdiff
[PATCH] Dynamic LDT sizing.
authorDave Jones <davej@suse.de>
Sun, 28 Apr 2002 11:57:35 +0000 (04:57 -0700)
committerJaroslav Kysela <perex@suse.cz>
Sun, 28 Apr 2002 11:57:35 +0000 (04:57 -0700)
Originally from Manfred Spraul.

* dynamically grow the LDT
Every app that's linked against libpthread right now allocates a full 64
kB LDT, without proper error handling, and always from the vmalloc area

arch/i386/kernel/ldt.c
arch/i386/kernel/process.c
arch/i386/kernel/setup.c
arch/i386/math-emu/fpu_system.h
include/asm-i386/desc.h
include/asm-i386/mmu.h
include/asm-i386/mmu_context.h
include/asm-i386/processor.h
kernel/fork.c

index a7ae70cb93e30599494e9a77084d195fe59a2592..1d6877dc2a91befcc3d1ccb545707b69cdce7edc 100644 (file)
 #include <linux/smp.h>
 #include <linux/smp_lock.h>
 #include <linux/vmalloc.h>
+#include <linux/slab.h>
 
 #include <asm/uaccess.h>
 #include <asm/system.h>
 #include <asm/ldt.h>
 #include <asm/desc.h>
 
+#ifdef CONFIG_SMP /* avoids "defined but not used" warnig */
+static void flush_ldt(void *mm)
+{
+       if (current->mm)
+               load_LDT(&current->mm->context);
+}
+#endif
+
+static int alloc_ldt(mm_context_t *pc, int mincount, int reload)
+{
+       void *oldldt;
+       void *newldt;
+       int oldsize;
+
+       if (mincount <= pc->size)
+               return 0;
+       oldsize = pc->size;
+       mincount = (mincount+511)&(~511);
+       if (mincount*LDT_ENTRY_SIZE > PAGE_SIZE)
+               newldt = vmalloc(mincount*LDT_ENTRY_SIZE);
+       else
+               newldt = kmalloc(mincount*LDT_ENTRY_SIZE, GFP_KERNEL);
+
+       if (!newldt)
+               return -ENOMEM;
+
+       if (oldsize)
+               memcpy(newldt, pc->ldt, oldsize*LDT_ENTRY_SIZE);
+       oldldt = pc->ldt;
+       memset(newldt+oldsize*LDT_ENTRY_SIZE, 0, (mincount-oldsize)*LDT_ENTRY_SIZE);
+       wmb();
+       pc->ldt = newldt;
+       pc->size = mincount;
+       if (reload) {
+               load_LDT(pc);
+#ifdef CONFIG_SMP
+               if (current->mm->cpu_vm_mask != (1<<smp_processor_id()))
+                       smp_call_function(flush_ldt, 0, 1, 1);
+#endif
+       }
+       wmb();
+       if (oldsize) {
+               if (oldsize*LDT_ENTRY_SIZE > PAGE_SIZE)
+                       vfree(oldldt);
+               else
+                       kfree(oldldt);
+       }
+       return 0;
+}
+
+static inline int copy_ldt(mm_context_t *new, mm_context_t *old)
+{
+       int err = alloc_ldt(new, old->size, 0);
+       if (err < 0) {
+               printk(KERN_WARNING "ldt allocation failed\n");
+               new->size = 0;
+               return err;
+       }
+       memcpy(new->ldt, old->ldt, old->size*LDT_ENTRY_SIZE);
+       return 0;
+}
+
 /*
- * read_ldt() is not really atomic - this is not a problem since
- * synchronization of reads and writes done to the LDT has to be
- * assured by user-space anyway. Writes are atomic, to protect
- * the security checks done on new descriptors.
+ * we do not have to muck with descriptors here, that is
+ * done in switch_mm() as needed.
  */
+int init_new_context(struct task_struct *tsk, struct mm_struct *mm)
+{
+       struct mm_struct * old_mm;
+       int retval = 0;
+
+       init_MUTEX(&mm->context.sem);
+       mm->context.size = 0;
+       old_mm = current->mm;
+       if (old_mm && old_mm->context.size > 0) {
+               down(&old_mm->context.sem);
+               retval = copy_ldt(&mm->context, &old_mm->context);
+               up(&old_mm->context.sem);
+       }
+       return retval;
+}
+
+/*
+ * No need to lock the MM as we are the last user
+ */
+void release_segments(struct mm_struct *mm)
+{
+       if (mm->context.size) {
+               clear_LDT();
+               if (mm->context.size*LDT_ENTRY_SIZE > PAGE_SIZE)
+                       vfree(mm->context.ldt);
+               else
+                       kfree(mm->context.ldt);
+               mm->context.size = 0;
+       }
+}
+
 static int read_ldt(void * ptr, unsigned long bytecount)
 {
        int err;
        unsigned long size;
        struct mm_struct * mm = current->mm;
 
-       err = 0;
-       if (!mm->context.segments)
-               goto out;
+       if (!mm->context.size)
+               return 0;
+       if (bytecount > LDT_ENTRY_SIZE*LDT_ENTRIES)
+               bytecount = LDT_ENTRY_SIZE*LDT_ENTRIES;
 
-       size = LDT_ENTRIES*LDT_ENTRY_SIZE;
+       down(&mm->context.sem);
+       size = mm->context.size*LDT_ENTRY_SIZE;
        if (size > bytecount)
                size = bytecount;
 
-       err = size;
-       if (copy_to_user(ptr, mm->context.segments, size))
+       err = 0;
+       if (copy_to_user(ptr, mm->context.ldt, size))
                err = -EFAULT;
-out:
-       return err;
+       up(&mm->context.sem);
+       if (err < 0)
+               return err;
+       if (size != bytecount) {
+               /* zero-fill the rest */
+               clear_user(ptr+size, bytecount-size);
+       }
+       return bytecount;
 }
 
 static int read_default_ldt(void * ptr, unsigned long bytecount)
@@ -53,7 +153,7 @@ static int read_default_ldt(void * ptr, unsigned long bytecount)
 
        err = 0;
        address = &default_ldt[0];
-       size = sizeof(struct desc_struct);
+       size = 5*sizeof(struct desc_struct);
        if (size > bytecount)
                size = bytecount;
 
@@ -88,24 +188,14 @@ static int write_ldt(void * ptr, unsigned long bytecount, int oldmode)
                        goto out;
        }
 
-       /*
-        * the GDT index of the LDT is allocated dynamically, and is
-        * limited by MAX_LDT_DESCRIPTORS.
-        */
-       down_write(&mm->mmap_sem);
-       if (!mm->context.segments) {
-               void * segments = vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE);
-               error = -ENOMEM;
-               if (!segments)
+       down(&mm->context.sem);
+       if (ldt_info.entry_number >= mm->context.size) {
+               error = alloc_ldt(&current->mm->context, ldt_info.entry_number+1, 1);
+               if (error < 0)
                        goto out_unlock;
-               memset(segments, 0, LDT_ENTRIES*LDT_ENTRY_SIZE);
-               wmb();
-               mm->context.segments = segments;
-               mm->context.cpuvalid = 1UL << smp_processor_id();
-               load_LDT(mm);
        }
 
-       lp = (__u32 *) ((ldt_info.entry_number << 3) + (char *) mm->context.segments);
+       lp = (__u32 *) ((ldt_info.entry_number << 3) + (char *) mm->context.ldt);
 
        /* Allow LDTs to be cleared by the user. */
        if (ldt_info.base_addr == 0 && ldt_info.limit == 0) {
@@ -143,7 +233,7 @@ install:
        error = 0;
 
 out_unlock:
-       up_write(&mm->mmap_sem);
+       up(&mm->context.sem);
 out:
        return error;
 }
index 60695f2b50a0d22b2db3864a98d58e7221b5b66f..3f3db75d96dfeecc3a230a1eccfeae130f02cdaa 100644 (file)
@@ -475,23 +475,6 @@ void show_regs(struct pt_regs * regs)
        show_trace(&regs->esp);
 }
 
-/*
- * No need to lock the MM as we are the last user
- */
-void release_segments(struct mm_struct *mm)
-{
-       void * ldt = mm->context.segments;
-
-       /*
-        * free the LDT
-        */
-       if (ldt) {
-               mm->context.segments = NULL;
-               clear_LDT();
-               vfree(ldt);
-       }
-}
-
 /*
  * Create a kernel thread
  */
@@ -544,42 +527,17 @@ void flush_thread(void)
 void release_thread(struct task_struct *dead_task)
 {
        if (dead_task->mm) {
-               void * ldt = dead_task->mm->context.segments;
-
                // temporary debugging check
-               if (ldt) {
-                       printk("WARNING: dead process %8s still has LDT? <%p>\n",
-                                       dead_task->comm, ldt);
+               if (dead_task->mm->context.size) {
+                       printk("WARNING: dead process %8s still has LDT? <%p/%d>\n",
+                                       dead_task->comm,
+                                       dead_task->mm->context.ldt,
+                                       dead_task->mm->context.size);
                        BUG();
                }
        }
 }
 
-/*
- * we do not have to muck with descriptors here, that is
- * done in switch_mm() as needed.
- */
-void copy_segments(struct task_struct *p, struct mm_struct *new_mm)
-{
-       struct mm_struct * old_mm;
-       void *old_ldt, *ldt;
-
-       ldt = NULL;
-       old_mm = current->mm;
-       if (old_mm && (old_ldt = old_mm->context.segments) != NULL) {
-               /*
-                * Completely new LDT, we initialize it from the parent:
-                */
-               ldt = vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE);
-               if (!ldt)
-                       printk(KERN_WARNING "ldt allocation failed\n");
-               else
-                       memcpy(ldt, old_ldt, LDT_ENTRIES*LDT_ENTRY_SIZE);
-       }
-       new_mm->context.segments = ldt;
-       new_mm->context.cpuvalid = ~0UL;        /* valid on all CPU's - they can't have stale data */
-}
-
 /*
  * Save a segment.
  */
index 859f213f1120a88f873cd4b600c00574716fdd5f..5cf32dc02fdddb828358582d7fe737125d38c4c7 100644 (file)
@@ -2875,7 +2875,7 @@ void __init cpu_init (void)
        set_tss_desc(nr,t);
        gdt_table[__TSS(nr)].b &= 0xfffffdff;
        load_TR(nr);
-       load_LDT(&init_mm);
+       load_LDT(&init_mm.context);
 
        /* Clear %fs and %gs. */
        asm volatile ("xorl %eax, %eax; movl %eax, %fs; movl %eax, %gs");
index 1da2e1ff9c26746d02ea28928df4fd19c9bda833..ea4d1fdb51fc0f32f8b83b4e9aa654fb7b6ef39d 100644 (file)
@@ -20,7 +20,7 @@
    of the stack frame of math_emulate() */
 #define SETUP_DATA_AREA(arg)   FPU_info = (struct info *) &arg
 
-#define LDT_DESCRIPTOR(s)      (((struct desc_struct *)current->mm->context.segments)[(s) >> 3])
+#define LDT_DESCRIPTOR(s)      (((struct desc_struct *)current->mm->context.ldt)[(s) >> 3])
 #define SEG_D_SIZE(x)          ((x).b & (3 << 21))
 #define SEG_G_BIT(x)           ((x).b & (1 << 23))
 #define SEG_GRANULARITY(x)     (((x).b & (1 << 23)) ? 4096 : 1)
index 2bb68fc40dbcae4196bb02a21e719b041e3f3480..1a6d24e903c69a327cf787c6287f884dbd36ffdf 100644 (file)
@@ -87,13 +87,13 @@ static inline void clear_LDT(void)
 /*
  * load one particular LDT into the current CPU
  */
-static inline void load_LDT (struct mm_struct *mm)
+static inline void load_LDT (mm_context_t *pc)
 {
        int cpu = smp_processor_id();
-       void *segments = mm->context.segments;
-       int count = LDT_ENTRIES;
+       void *segments = pc->ldt;
+       int count = pc->size;
 
-       if (!segments) {
+       if (!count) {
                segments = &default_ldt[0];
                count = 5;
        }
index de014031cda676cb8dd7996a06c1a4a959635049..8ca14eb8c5a26e8cedd59efa387b537bce28e051 100644 (file)
@@ -4,10 +4,13 @@
 /*
  * The i386 doesn't have a mmu context, but
  * we put the segment information here.
+ *
+ * cpu_vm_mask is used to optimize ldt flushing.
  */
 typedef struct { 
-       void *segments;
-       unsigned long cpuvalid;
+       int size;
+       struct semaphore sem;
+       void *  ldt;
 } mm_context_t;
 
 #endif
index c4aafc0dc0097a79bfdabfe44e9d42688266a25e..4b9c0b312206a29a191e97f523c161d7340c7ef8 100644 (file)
@@ -11,7 +11,7 @@
  * possibly do the LDT unload here?
  */
 #define destroy_context(mm)            do { } while(0)
-#define init_new_context(tsk,mm)       0
+int init_new_context(struct task_struct *tsk, struct mm_struct *mm);
 
 #ifdef CONFIG_SMP
 
@@ -31,19 +31,20 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, str
        if (likely(prev != next)) {
                /* stop flush ipis for the previous mm */
                clear_bit(cpu, &prev->cpu_vm_mask);
-               /*
-                * Re-load LDT if necessary
-                */
-               if (unlikely(prev->context.segments != next->context.segments))
-                       load_LDT(next);
 #ifdef CONFIG_SMP
                cpu_tlbstate[cpu].state = TLBSTATE_OK;
                cpu_tlbstate[cpu].active_mm = next;
 #endif
                set_bit(cpu, &next->cpu_vm_mask);
-               set_bit(cpu, &next->context.cpuvalid);
+
                /* Re-load page tables */
                asm volatile("movl %0,%%cr3": :"r" (__pa(next->pgd)));
+
+               /* load_LDT, if either the previous or next thread
+                * has a non-default LDT.
+                */
+               if (next->context.size+prev->context.size)
+                       load_LDT(&next->context);
        }
 #ifdef CONFIG_SMP
        else {
@@ -55,9 +56,8 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, str
                         * tlb flush IPI delivery. We must flush our tlb.
                         */
                        local_flush_tlb();
+                       load_LDT(&next->context);
                }
-               if (!test_and_set_bit(cpu, &next->context.cpuvalid))
-                       load_LDT(next);
        }
 #endif
 }
index 838c7d3ef7ea912890c16ed9b11719f49865d7a0..21fab1d41018d34e529388b91da5b9c234dca925 100644 (file)
@@ -429,8 +429,7 @@ extern void release_thread(struct task_struct *);
  */
 extern int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags);
 
-/* Copy and release all segment info associated with a VM */
-extern void copy_segments(struct task_struct *p, struct mm_struct * mm);
+/* Release all segment info associated with a VM */
 extern void release_segments(struct mm_struct * mm);
 
 extern unsigned long thread_saved_pc(struct task_struct *tsk);
index 1d2b8551bd39d941580893851fe9994e5b925dce..9c02ba5c4635e74465483407d05f93373fa52a33 100644 (file)
@@ -394,11 +394,6 @@ static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
        if (retval)
                goto free_pt;
 
-       /*
-        * child gets a private LDT (if there was an LDT in the parent)
-        */
-       copy_segments(tsk, mm);
-
        if (init_new_context(tsk,mm))
                goto free_pt;