aboutsummaryrefslogtreecommitdiff
path: root/linux/mm/memory.c
diff options
context:
space:
mode:
Diffstat (limited to 'linux/mm/memory.c')
-rw-r--r--linux/mm/memory.c264
1 files changed, 264 insertions, 0 deletions
diff --git a/linux/mm/memory.c b/linux/mm/memory.c
new file mode 100644
index 0000000..7cdcfb6
--- /dev/null
+++ b/linux/mm/memory.c
@@ -0,0 +1,264 @@
+#include <signal.h>
+
+#include <linux/config.h>
+#include <linux/head.h>
+#include <linux/kernel.h>
+#include <asm/system.h>
+
+int do_exit(long code);
+
+#define invalidate() \
+__asm__("movl %%eax,%%cr3"::"a" (0))
+
+#if (BUFFER_END < 0x100000)
+#define LOW_MEM 0x100000
+#else
+#define LOW_MEM BUFFER_END
+#endif
+
+/* these are not to be changed - thay are calculated from the above */
+#define PAGING_MEMORY (HIGH_MEMORY - LOW_MEM)
+#define PAGING_PAGES (PAGING_MEMORY/4096)
+#define MAP_NR(addr) (((addr)-LOW_MEM)>>12)
+
+#if (PAGING_PAGES < 10)
+#error "Won't work"
+#endif
+
+#define copy_page(from,to) \
+__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024):"cx","di","si")
+
+static unsigned short mem_map [ PAGING_PAGES ] = {0,};
+
+/*
+ * Get physical address of first (actually last :-) free page, and mark it
+ * used. If no free pages left, return 0.
+ */
+unsigned long get_free_page(void)
+{
+register unsigned long __res asm("ax");
+
+__asm__("std ; repne ; scasw\n\t"
+ "jne 1f\n\t"
+ "movw $1,2(%%edi)\n\t"
+ "sall $12,%%ecx\n\t"
+ "movl %%ecx,%%edx\n\t"
+ "addl %2,%%edx\n\t"
+ "movl $1024,%%ecx\n\t"
+ "leal 4092(%%edx),%%edi\n\t"
+ "rep ; stosl\n\t"
+ "movl %%edx,%%eax\n"
+ "1:"
+ :"=a" (__res)
+ :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
+ "D" (mem_map+PAGING_PAGES-1)
+ :"di","cx","dx");
+return __res;
+}
+
+/*
+ * Free a page of memory at physical address 'addr'. Used by
+ * 'free_page_tables()'
+ */
+void free_page(unsigned long addr)
+{
+ if (addr<LOW_MEM) return;
+ if (addr>HIGH_MEMORY)
+ panic("trying to free nonexistent page");
+ addr -= LOW_MEM;
+ addr >>= 12;
+ if (mem_map[addr]--) return;
+ mem_map[addr]=0;
+ panic("trying to free free page");
+}
+
+/*
+ * This function frees a continuos block of page tables, as needed
+ * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
+ */
+int free_page_tables(unsigned long from,unsigned long size)
+{
+ unsigned long *pg_table;
+ unsigned long * dir, nr;
+
+ if (from & 0x3fffff)
+ panic("free_page_tables called with wrong alignment");
+ if (!from)
+ panic("Trying to free up swapper memory space");
+ size = (size + 0x3fffff) >> 22;
+ dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
+ for ( ; size-->0 ; dir++) {
+ if (!(1 & *dir))
+ continue;
+ pg_table = (unsigned long *) (0xfffff000 & *dir);
+ for (nr=0 ; nr<1024 ; nr++) {
+ if (1 & *pg_table)
+ free_page(0xfffff000 & *pg_table);
+ *pg_table = 0;
+ pg_table++;
+ }
+ free_page(0xfffff000 & *dir);
+ *dir = 0;
+ }
+ invalidate();
+ return 0;
+}
+
+/*
+ * Well, here is one of the most complicated functions in mm. It
+ * copies a range of linerar addresses by copying only the pages.
+ * Let's hope this is bug-free, 'cause this one I don't want to debug :-)
+ *
+ * Note! We don't copy just any chunks of memory - addresses have to
+ * be divisible by 4Mb (one page-directory entry), as this makes the
+ * function easier. It's used only by fork anyway.
+ *
+ * NOTE 2!! When from==0 we are copying kernel space for the first
+ * fork(). Then we DONT want to copy a full page-directory entry, as
+ * that would lead to some serious memory waste - we just copy the
+ * first 160 pages - 640kB. Even that is more than we need, but it
+ * doesn't take any more memory - we don't copy-on-write in the low
+ * 1 Mb-range, so the pages can be shared with the kernel. Thus the
+ * special case for nr=xxxx.
+ */
+int copy_page_tables(unsigned long from,unsigned long to,long size)
+{
+ unsigned long * from_page_table;
+ unsigned long * to_page_table;
+ unsigned long this_page;
+ unsigned long * from_dir, * to_dir;
+ unsigned long nr;
+
+ if ((from&0x3fffff) || (to&0x3fffff))
+ panic("copy_page_tables called with wrong alignment");
+ from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
+ to_dir = (unsigned long *) ((to>>20) & 0xffc);
+ size = ((unsigned) (size+0x3fffff)) >> 22;
+ for( ; size-->0 ; from_dir++,to_dir++) {
+ if (1 & *to_dir)
+ panic("copy_page_tables: already exist");
+ if (!(1 & *from_dir))
+ continue;
+ from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
+ if (!(to_page_table = (unsigned long *) get_free_page()))
+ return -1; /* Out of memory, see freeing */
+ *to_dir = ((unsigned long) to_page_table) | 7;
+ nr = (from==0)?0xA0:1024;
+ for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
+ this_page = *from_page_table;
+ if (!(1 & this_page))
+ continue;
+ this_page &= ~2;
+ *to_page_table = this_page;
+ if (this_page > LOW_MEM) {
+ *from_page_table = this_page;
+ this_page -= LOW_MEM;
+ this_page >>= 12;
+ mem_map[this_page]++;
+ }
+ }
+ }
+ invalidate();
+ return 0;
+}
+
+/*
+ * This function puts a page in memory at the wanted address.
+ * It returns the physical address of the page gotten, 0 if
+ * out of memory (either when trying to access page-table or
+ * page.)
+ */
+unsigned long put_page(unsigned long page,unsigned long address)
+{
+ unsigned long tmp, *page_table;
+
+/* NOTE !!! This uses the fact that _pg_dir=0 */
+
+ if (page < LOW_MEM || page > HIGH_MEMORY)
+ printk("Trying to put page %p at %p\n",page,address);
+ if (mem_map[(page-LOW_MEM)>>12] != 1)
+ printk("mem_map disagrees with %p at %p\n",page,address);
+ page_table = (unsigned long *) ((address>>20) & 0xffc);
+ if ((*page_table)&1)
+ page_table = (unsigned long *) (0xfffff000 & *page_table);
+ else {
+ if (!(tmp=get_free_page()))
+ return 0;
+ *page_table = tmp|7;
+ page_table = (unsigned long *) tmp;
+ }
+ page_table[(address>>12) & 0x3ff] = page | 7;
+ return page;
+}
+
+void un_wp_page(unsigned long * table_entry)
+{
+ unsigned long old_page,new_page;
+
+ old_page = 0xfffff000 & *table_entry;
+ if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
+ *table_entry |= 2;
+ return;
+ }
+ if (!(new_page=get_free_page()))
+ do_exit(SIGSEGV);
+ if (old_page >= LOW_MEM)
+ mem_map[MAP_NR(old_page)]--;
+ *table_entry = new_page | 7;
+ copy_page(old_page,new_page);
+}
+
+/*
+ * This routine handles present pages, when users try to write
+ * to a shared page. It is done by copying the page to a new address
+ * and decrementing the shared-page counter for the old page.
+ */
+void do_wp_page(unsigned long error_code,unsigned long address)
+{
+ un_wp_page((unsigned long *)
+ (((address>>10) & 0xffc) + (0xfffff000 &
+ *((unsigned long *) ((address>>20) &0xffc)))));
+
+}
+
+void write_verify(unsigned long address)
+{
+ unsigned long page;
+
+ if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
+ return;
+ page &= 0xfffff000;
+ page += ((address>>10) & 0xffc);
+ if ((3 & *(unsigned long *) page) == 1) /* non-writeable, present */
+ un_wp_page((unsigned long *) page);
+ return;
+}
+
+void do_no_page(unsigned long error_code,unsigned long address)
+{
+ unsigned long tmp;
+
+ if (tmp=get_free_page())
+ if (put_page(tmp,address))
+ return;
+ do_exit(SIGSEGV);
+}
+
+void calc_mem(void)
+{
+ int i,j,k,free=0;
+ long * pg_tbl;
+
+ for(i=0 ; i<PAGING_PAGES ; i++)
+ if (!mem_map[i]) free++;
+ printk("%d pages free (of %d)\n\r",free,PAGING_PAGES);
+ for(i=2 ; i<1024 ; i++) {
+ if (1&pg_dir[i]) {
+ pg_tbl=(long *) (0xfffff000 & pg_dir[i]);
+ for(j=k=0 ; j<1024 ; j++)
+ if (pg_tbl[j]&1)
+ k++;
+ printk("Pg-dir[%d] uses %d pages\n",i,k);
+ }
+ }
+}