diff options
Diffstat (limited to 'src/mm/memory.c')
-rw-r--r-- | src/mm/memory.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/src/mm/memory.c b/src/mm/memory.c new file mode 100644 index 0000000..340a7fc --- /dev/null +++ b/src/mm/memory.c | |||
@@ -0,0 +1,431 @@ | |||
1 | /* | ||
2 | * linux/mm/memory.c | ||
3 | * | ||
4 | * (C) 1991 Linus Torvalds | ||
5 | */ | ||
6 | |||
7 | /* | ||
8 | * demand-loading started 01.12.91 - seems it is high on the list of | ||
9 | * things wanted, and it should be easy to implement. - Linus | ||
10 | */ | ||
11 | |||
12 | /* | ||
13 | * Ok, demand-loading was easy, shared pages a little bit tricker. Shared | ||
14 | * pages started 02.12.91, seems to work. - Linus. | ||
15 | * | ||
16 | * Tested sharing by executing about 30 /bin/sh: under the old kernel it | ||
17 | * would have taken more than the 6M I have free, but it worked well as | ||
18 | * far as I could see. | ||
19 | * | ||
20 | * Also corrected some "invalidate()"s - I wasn't doing enough of them. | ||
21 | */ | ||
22 | |||
23 | #include <signal.h> | ||
24 | |||
25 | #include <asm/system.h> | ||
26 | |||
27 | #include <linux/sched.h> | ||
28 | #include <linux/head.h> | ||
29 | #include <linux/kernel.h> | ||
30 | |||
31 | void do_exit(long code); | ||
32 | |||
33 | static inline void oom(void) | ||
34 | { | ||
35 | printk("out of memory\n\r"); | ||
36 | do_exit(SIGSEGV); | ||
37 | } | ||
38 | |||
39 | #define invalidate() \ | ||
40 | __asm__("movl %%eax,%%cr3"::"a" (0)) | ||
41 | |||
42 | /* these are not to be changed without changing head.s etc */ | ||
43 | #define LOW_MEM 0x100000 | ||
44 | #define PAGING_MEMORY (15*1024*1024) | ||
45 | #define PAGING_PAGES (PAGING_MEMORY>>12) | ||
46 | #define MAP_NR(addr) (((addr)-LOW_MEM)>>12) | ||
47 | #define USED 100 | ||
48 | |||
49 | #define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \ | ||
50 | current->start_code + current->end_code) | ||
51 | |||
52 | static long HIGH_MEMORY = 0; | ||
53 | |||
54 | #define copy_page(from,to) \ | ||
55 | __asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024)) | ||
56 | |||
57 | static unsigned char mem_map [ PAGING_PAGES ] = {0,}; | ||
58 | |||
59 | /* | ||
60 | * Get physical address of first (actually last :-) free page, and mark it | ||
61 | * used. If no free pages left, return 0. | ||
62 | */ | ||
63 | unsigned long get_free_page(void) | ||
64 | { | ||
65 | register unsigned long __res asm("ax"); | ||
66 | |||
67 | __asm__("std ; repne ; scasb\n\t" | ||
68 | "jne 1f\n\t" | ||
69 | "movb $1,1(%%edi)\n\t" | ||
70 | "sall $12,%%ecx\n\t" | ||
71 | "addl %2,%%ecx\n\t" | ||
72 | "movl %%ecx,%%edx\n\t" | ||
73 | "movl $1024,%%ecx\n\t" | ||
74 | "leal 4092(%%edx),%%edi\n\t" | ||
75 | "rep ; stosl\n\t" | ||
76 | " movl %%edx,%%eax\n" | ||
77 | "1: cld" | ||
78 | :"=a" (__res) | ||
79 | :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES), | ||
80 | "D" (mem_map+PAGING_PAGES-1) | ||
81 | ); | ||
82 | return __res; | ||
83 | } | ||
84 | |||
85 | /* | ||
86 | * Free a page of memory at physical address 'addr'. Used by | ||
87 | * 'free_page_tables()' | ||
88 | */ | ||
89 | void free_page(unsigned long addr) | ||
90 | { | ||
91 | if (addr < LOW_MEM) return; | ||
92 | if (addr >= HIGH_MEMORY) | ||
93 | panic("trying to free nonexistent page"); | ||
94 | addr -= LOW_MEM; | ||
95 | addr >>= 12; | ||
96 | if (mem_map[addr]--) return; | ||
97 | mem_map[addr]=0; | ||
98 | panic("trying to free free page"); | ||
99 | } | ||
100 | |||
101 | /* | ||
102 | * This function frees a continuos block of page tables, as needed | ||
103 | * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks. | ||
104 | */ | ||
105 | int free_page_tables(unsigned long from,unsigned long size) | ||
106 | { | ||
107 | unsigned long *pg_table; | ||
108 | unsigned long * dir, nr; | ||
109 | |||
110 | if (from & 0x3fffff) | ||
111 | panic("free_page_tables called with wrong alignment"); | ||
112 | if (!from) | ||
113 | panic("Trying to free up swapper memory space"); | ||
114 | size = (size + 0x3fffff) >> 22; | ||
115 | dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */ | ||
116 | for ( ; size-->0 ; dir++) { | ||
117 | if (!(1 & *dir)) | ||
118 | continue; | ||
119 | pg_table = (unsigned long *) (0xfffff000 & *dir); | ||
120 | for (nr=0 ; nr<1024 ; nr++) { | ||
121 | if (1 & *pg_table) | ||
122 | free_page(0xfffff000 & *pg_table); | ||
123 | *pg_table = 0; | ||
124 | pg_table++; | ||
125 | } | ||
126 | free_page(0xfffff000 & *dir); | ||
127 | *dir = 0; | ||
128 | } | ||
129 | invalidate(); | ||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | /* | ||
134 | * Well, here is one of the most complicated functions in mm. It | ||
135 | * copies a range of linerar addresses by copying only the pages. | ||
136 | * Let's hope this is bug-free, 'cause this one I don't want to debug :-) | ||
137 | * | ||
138 | * Note! We don't copy just any chunks of memory - addresses have to | ||
139 | * be divisible by 4Mb (one page-directory entry), as this makes the | ||
140 | * function easier. It's used only by fork anyway. | ||
141 | * | ||
142 | * NOTE 2!! When from==0 we are copying kernel space for the first | ||
143 | * fork(). Then we DONT want to copy a full page-directory entry, as | ||
144 | * that would lead to some serious memory waste - we just copy the | ||
145 | * first 160 pages - 640kB. Even that is more than we need, but it | ||
146 | * doesn't take any more memory - we don't copy-on-write in the low | ||
147 | * 1 Mb-range, so the pages can be shared with the kernel. Thus the | ||
148 | * special case for nr=xxxx. | ||
149 | */ | ||
150 | int copy_page_tables(unsigned long from,unsigned long to,long size) | ||
151 | { | ||
152 | unsigned long * from_page_table; | ||
153 | unsigned long * to_page_table; | ||
154 | unsigned long this_page; | ||
155 | unsigned long * from_dir, * to_dir; | ||
156 | unsigned long nr; | ||
157 | |||
158 | if ((from&0x3fffff) || (to&0x3fffff)) | ||
159 | panic("copy_page_tables called with wrong alignment"); | ||
160 | from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */ | ||
161 | to_dir = (unsigned long *) ((to>>20) & 0xffc); | ||
162 | size = ((unsigned) (size+0x3fffff)) >> 22; | ||
163 | for( ; size-->0 ; from_dir++,to_dir++) { | ||
164 | if (1 & *to_dir) | ||
165 | panic("copy_page_tables: already exist"); | ||
166 | if (!(1 & *from_dir)) | ||
167 | continue; | ||
168 | from_page_table = (unsigned long *) (0xfffff000 & *from_dir); | ||
169 | if (!(to_page_table = (unsigned long *) get_free_page())) | ||
170 | return -1; /* Out of memory, see freeing */ | ||
171 | *to_dir = ((unsigned long) to_page_table) | 7; | ||
172 | nr = (from==0)?0xA0:1024; | ||
173 | for ( ; nr-- > 0 ; from_page_table++,to_page_table++) { | ||
174 | this_page = *from_page_table; | ||
175 | if (!(1 & this_page)) | ||
176 | continue; | ||
177 | this_page &= ~2; | ||
178 | *to_page_table = this_page; | ||
179 | if (this_page > LOW_MEM) { | ||
180 | *from_page_table = this_page; | ||
181 | this_page -= LOW_MEM; | ||
182 | this_page >>= 12; | ||
183 | mem_map[this_page]++; | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | invalidate(); | ||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | /* | ||
192 | * This function puts a page in memory at the wanted address. | ||
193 | * It returns the physical address of the page gotten, 0 if | ||
194 | * out of memory (either when trying to access page-table or | ||
195 | * page.) | ||
196 | */ | ||
197 | unsigned long put_page(unsigned long page,unsigned long address) | ||
198 | { | ||
199 | unsigned long tmp, *page_table; | ||
200 | |||
201 | /* NOTE !!! This uses the fact that _pg_dir=0 */ | ||
202 | |||
203 | if (page < LOW_MEM || page >= HIGH_MEMORY) | ||
204 | printk("Trying to put page %p at %p\n",page,address); | ||
205 | if (mem_map[(page-LOW_MEM)>>12] != 1) | ||
206 | printk("mem_map disagrees with %p at %p\n",page,address); | ||
207 | page_table = (unsigned long *) ((address>>20) & 0xffc); | ||
208 | if ((*page_table)&1) | ||
209 | page_table = (unsigned long *) (0xfffff000 & *page_table); | ||
210 | else { | ||
211 | if (!(tmp=get_free_page())) | ||
212 | return 0; | ||
213 | *page_table = tmp|7; | ||
214 | page_table = (unsigned long *) tmp; | ||
215 | } | ||
216 | page_table[(address>>12) & 0x3ff] = page | 7; | ||
217 | /* no need for invalidate */ | ||
218 | return page; | ||
219 | } | ||
220 | |||
221 | void un_wp_page(unsigned long * table_entry) | ||
222 | { | ||
223 | unsigned long old_page,new_page; | ||
224 | |||
225 | old_page = 0xfffff000 & *table_entry; | ||
226 | if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) { | ||
227 | *table_entry |= 2; | ||
228 | invalidate(); | ||
229 | return; | ||
230 | } | ||
231 | if (!(new_page=get_free_page())) | ||
232 | oom(); | ||
233 | if (old_page >= LOW_MEM) | ||
234 | mem_map[MAP_NR(old_page)]--; | ||
235 | *table_entry = new_page | 7; | ||
236 | invalidate(); | ||
237 | copy_page(old_page,new_page); | ||
238 | } | ||
239 | |||
240 | /* | ||
241 | * This routine handles present pages, when users try to write | ||
242 | * to a shared page. It is done by copying the page to a new address | ||
243 | * and decrementing the shared-page counter for the old page. | ||
244 | * | ||
245 | * If it's in code space we exit with a segment error. | ||
246 | */ | ||
247 | void do_wp_page(unsigned long error_code,unsigned long address) | ||
248 | { | ||
249 | #if 0 | ||
250 | /* we cannot do this yet: the estdio library writes to code space */ | ||
251 | /* stupid, stupid. I really want the libc.a from GNU */ | ||
252 | if (CODE_SPACE(address)) | ||
253 | do_exit(SIGSEGV); | ||
254 | #endif | ||
255 | un_wp_page((unsigned long *) | ||
256 | (((address>>10) & 0xffc) + (0xfffff000 & | ||
257 | *((unsigned long *) ((address>>20) &0xffc))))); | ||
258 | |||
259 | } | ||
260 | |||
261 | void write_verify(unsigned long address) | ||
262 | { | ||
263 | unsigned long page; | ||
264 | |||
265 | if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1)) | ||
266 | return; | ||
267 | page &= 0xfffff000; | ||
268 | page += ((address>>10) & 0xffc); | ||
269 | if ((3 & *(unsigned long *) page) == 1) /* non-writeable, present */ | ||
270 | un_wp_page((unsigned long *) page); | ||
271 | return; | ||
272 | } | ||
273 | |||
274 | void get_empty_page(unsigned long address) | ||
275 | { | ||
276 | unsigned long tmp; | ||
277 | |||
278 | if (!(tmp=get_free_page()) || !put_page(tmp,address)) { | ||
279 | free_page(tmp); /* 0 is ok - ignored */ | ||
280 | oom(); | ||
281 | } | ||
282 | } | ||
283 | |||
284 | /* | ||
285 | * try_to_share() checks the page at address "address" in the task "p", | ||
286 | * to see if it exists, and if it is clean. If so, share it with the current | ||
287 | * task. | ||
288 | * | ||
289 | * NOTE! This assumes we have checked that p != current, and that they | ||
290 | * share the same executable. | ||
291 | */ | ||
292 | static int try_to_share(unsigned long address, struct task_struct * p) | ||
293 | { | ||
294 | unsigned long from; | ||
295 | unsigned long to; | ||
296 | unsigned long from_page; | ||
297 | unsigned long to_page; | ||
298 | unsigned long phys_addr; | ||
299 | |||
300 | from_page = to_page = ((address>>20) & 0xffc); | ||
301 | from_page += ((p->start_code>>20) & 0xffc); | ||
302 | to_page += ((current->start_code>>20) & 0xffc); | ||
303 | /* is there a page-directory at from? */ | ||
304 | from = *(unsigned long *) from_page; | ||
305 | if (!(from & 1)) | ||
306 | return 0; | ||
307 | from &= 0xfffff000; | ||
308 | from_page = from + ((address>>10) & 0xffc); | ||
309 | phys_addr = *(unsigned long *) from_page; | ||
310 | /* is the page clean and present? */ | ||
311 | if ((phys_addr & 0x41) != 0x01) | ||
312 | return 0; | ||
313 | phys_addr &= 0xfffff000; | ||
314 | if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM) | ||
315 | return 0; | ||
316 | to = *(unsigned long *) to_page; | ||
317 | if (!(to & 1)) { | ||
318 | if ((to = get_free_page())) | ||
319 | *(unsigned long *) to_page = to | 7; | ||
320 | else | ||
321 | oom(); | ||
322 | } | ||
323 | to &= 0xfffff000; | ||
324 | to_page = to + ((address>>10) & 0xffc); | ||
325 | if (1 & *(unsigned long *) to_page) | ||
326 | panic("try_to_share: to_page already exists"); | ||
327 | /* share them: write-protect */ | ||
328 | *(unsigned long *) from_page &= ~2; | ||
329 | *(unsigned long *) to_page = *(unsigned long *) from_page; | ||
330 | invalidate(); | ||
331 | phys_addr -= LOW_MEM; | ||
332 | phys_addr >>= 12; | ||
333 | mem_map[phys_addr]++; | ||
334 | return 1; | ||
335 | } | ||
336 | |||
337 | /* | ||
338 | * share_page() tries to find a process that could share a page with | ||
339 | * the current one. Address is the address of the wanted page relative | ||
340 | * to the current data space. | ||
341 | * | ||
342 | * We first check if it is at all feasible by checking executable->i_count. | ||
343 | * It should be >1 if there are other tasks sharing this inode. | ||
344 | */ | ||
345 | static int share_page(unsigned long address) | ||
346 | { | ||
347 | struct task_struct ** p; | ||
348 | |||
349 | if (!current->executable) | ||
350 | return 0; | ||
351 | if (current->executable->i_count < 2) | ||
352 | return 0; | ||
353 | for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) { | ||
354 | if (!*p) | ||
355 | continue; | ||
356 | if (current == *p) | ||
357 | continue; | ||
358 | if ((*p)->executable != current->executable) | ||
359 | continue; | ||
360 | if (try_to_share(address,*p)) | ||
361 | return 1; | ||
362 | } | ||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | void do_no_page(unsigned long error_code,unsigned long address) | ||
367 | { | ||
368 | int nr[4]; | ||
369 | unsigned long tmp; | ||
370 | unsigned long page; | ||
371 | int block,i; | ||
372 | |||
373 | address &= 0xfffff000; | ||
374 | tmp = address - current->start_code; | ||
375 | if (!current->executable || tmp >= current->end_data) { | ||
376 | get_empty_page(address); | ||
377 | return; | ||
378 | } | ||
379 | if (share_page(tmp)) | ||
380 | return; | ||
381 | if (!(page = get_free_page())) | ||
382 | oom(); | ||
383 | /* remember that 1 block is used for header */ | ||
384 | block = 1 + tmp/BLOCK_SIZE; | ||
385 | for (i=0 ; i<4 ; block++,i++) | ||
386 | nr[i] = bmap(current->executable,block); | ||
387 | bread_page(page,current->executable->i_dev,nr); | ||
388 | i = tmp + 4096 - current->end_data; | ||
389 | tmp = page + 4096; | ||
390 | while (i-- > 0) { | ||
391 | tmp--; | ||
392 | *(char *)tmp = 0; | ||
393 | } | ||
394 | if (put_page(page,address)) | ||
395 | return; | ||
396 | free_page(page); | ||
397 | oom(); | ||
398 | } | ||
399 | |||
400 | void mem_init(long start_mem, long end_mem) | ||
401 | { | ||
402 | int i; | ||
403 | |||
404 | HIGH_MEMORY = end_mem; | ||
405 | for (i=0 ; i<PAGING_PAGES ; i++) | ||
406 | mem_map[i] = USED; | ||
407 | i = MAP_NR(start_mem); | ||
408 | end_mem -= start_mem; | ||
409 | end_mem >>= 12; | ||
410 | while (end_mem-->0) | ||
411 | mem_map[i++]=0; | ||
412 | } | ||
413 | |||
414 | void calc_mem(void) | ||
415 | { | ||
416 | int i,j,k,free=0; | ||
417 | long * pg_tbl; | ||
418 | |||
419 | for(i=0 ; i<PAGING_PAGES ; i++) | ||
420 | if (!mem_map[i]) free++; | ||
421 | printk("%d pages free (of %d)\n\r",free,PAGING_PAGES); | ||
422 | for(i=2 ; i<1024 ; i++) { | ||
423 | if (1&pg_dir[i]) { | ||
424 | pg_tbl=(long *) (0xfffff000 & pg_dir[i]); | ||
425 | for(j=k=0 ; j<1024 ; j++) | ||
426 | if (pg_tbl[j]&1) | ||
427 | k++; | ||
428 | printk("Pg-dir[%d] uses %d pages\n",i,k); | ||
429 | } | ||
430 | } | ||
431 | } | ||