Statistics
| Branch: | Revision:

root / linux-user / mmap.c @ 17e2377a

History | View | Annotate | Download (16.4 kB)

1
/*
2
 *  mmap support for qemu
3
 *
4
 *  Copyright (c) 2003 Fabrice Bellard
5
 *
6
 *  This program is free software; you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation; either version 2 of the License, or
9
 *  (at your option) any later version.
10
 *
11
 *  This program is distributed in the hope that it will be useful,
12
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 *  GNU General Public License for more details.
15
 *
16
 *  You should have received a copy of the GNU General Public License
17
 *  along with this program; if not, write to the Free Software
18
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
 */
20
#include <stdlib.h>
21
#include <stdio.h>
22
#include <stdarg.h>
23
#include <string.h>
24
#include <unistd.h>
25
#include <errno.h>
26
#include <sys/mman.h>
27

    
28
#include "qemu.h"
29

    
30
//#define DEBUG_MMAP
31

    
32
#if defined(USE_NPTL)
33
pthread_mutex_t mmap_mutex;
34
static int __thread mmap_lock_count;
35

    
36
void mmap_lock(void)
37
{
38
    if (mmap_lock_count++ == 0) {
39
        pthread_mutex_lock(&mmap_mutex);
40
    }
41
}
42

    
43
void mmap_unlock(void)
44
{
45
    if (--mmap_lock_count == 0) {
46
        pthread_mutex_unlock(&mmap_mutex);
47
    }
48
}
49

    
50
/* Grab lock to make sure things are in a consistent state after fork().  */
51
void mmap_fork_start(void)
52
{
53
    if (mmap_lock_count)
54
        abort();
55
    pthread_mutex_lock(&mmap_mutex);
56
}
57

    
58
void mmap_fork_end(int child)
59
{
60
    if (child)
61
        pthread_mutex_init(&mmap_mutex, NULL);
62
    else
63
        pthread_mutex_unlock(&mmap_mutex);
64
}
65
#else
66
/* We aren't threadsafe to start with, so no need to worry about locking.  */
67
void mmap_lock(void)
68
{
69
}
70

    
71
void mmap_unlock(void)
72
{
73
}
74
#endif
75

    
76
void *qemu_vmalloc(size_t size)
77
{
78
    void *p;
79
    unsigned long addr;
80
    mmap_lock();
81
    /* Use map and mark the pages as used.  */
82
    p = mmap(NULL, size, PROT_READ | PROT_WRITE,
83
             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
84

    
85
    addr = (unsigned long)p;
86
    if (addr == (target_ulong) addr) {
87
        /* Allocated region overlaps guest address space.
88
           This may recurse.  */
89
        page_set_flags(addr & TARGET_PAGE_MASK, TARGET_PAGE_ALIGN(addr + size),
90
                       PAGE_RESERVED);
91
    }
92

    
93
    mmap_unlock();
94
    return p;
95
}
96

    
97
void *qemu_malloc(size_t size)
98
{
99
    char * p;
100
    size += 16;
101
    p = qemu_vmalloc(size);
102
    *(size_t *)p = size;
103
    return p + 16;
104
}
105

    
106
/* We use map, which is always zero initialized.  */
107
void * qemu_mallocz(size_t size)
108
{
109
    return qemu_malloc(size);
110
}
111

    
112
void qemu_free(void *ptr)
113
{
114
    /* FIXME: We should unmark the reserved pages here.  However this gets
115
       complicated when one target page spans multiple host pages, so we
116
       don't bother.  */
117
    size_t *p;
118
    p = (size_t *)((char *)ptr - 16);
119
    munmap(p, *p);
120
}
121

    
122
/* NOTE: all the constants are the HOST ones, but addresses are target. */
123
int target_mprotect(abi_ulong start, abi_ulong len, int prot)
124
{
125
    abi_ulong end, host_start, host_end, addr;
126
    int prot1, ret;
127

    
128
#ifdef DEBUG_MMAP
129
    printf("mprotect: start=0x" TARGET_FMT_lx
130
           "len=0x" TARGET_FMT_lx " prot=%c%c%c\n", start, len,
131
           prot & PROT_READ ? 'r' : '-',
132
           prot & PROT_WRITE ? 'w' : '-',
133
           prot & PROT_EXEC ? 'x' : '-');
134
#endif
135

    
136
    if ((start & ~TARGET_PAGE_MASK) != 0)
137
        return -EINVAL;
138
    len = TARGET_PAGE_ALIGN(len);
139
    end = start + len;
140
    if (end < start)
141
        return -EINVAL;
142
    prot &= PROT_READ | PROT_WRITE | PROT_EXEC;
143
    if (len == 0)
144
        return 0;
145

    
146
    mmap_lock();
147
    host_start = start & qemu_host_page_mask;
148
    host_end = HOST_PAGE_ALIGN(end);
149
    if (start > host_start) {
150
        /* handle host page containing start */
151
        prot1 = prot;
152
        for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) {
153
            prot1 |= page_get_flags(addr);
154
        }
155
        if (host_end == host_start + qemu_host_page_size) {
156
            for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
157
                prot1 |= page_get_flags(addr);
158
            }
159
            end = host_end;
160
        }
161
        ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS);
162
        if (ret != 0)
163
            goto error;
164
        host_start += qemu_host_page_size;
165
    }
166
    if (end < host_end) {
167
        prot1 = prot;
168
        for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
169
            prot1 |= page_get_flags(addr);
170
        }
171
        ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size,
172
                       prot1 & PAGE_BITS);
173
        if (ret != 0)
174
            goto error;
175
        host_end -= qemu_host_page_size;
176
    }
177

    
178
    /* handle the pages in the middle */
179
    if (host_start < host_end) {
180
        ret = mprotect(g2h(host_start), host_end - host_start, prot);
181
        if (ret != 0)
182
            goto error;
183
    }
184
    page_set_flags(start, start + len, prot | PAGE_VALID);
185
    mmap_unlock();
186
    return 0;
187
error:
188
    mmap_unlock();
189
    return ret;
190
}
191

    
192
/* map an incomplete host page */
193
static int mmap_frag(abi_ulong real_start,
194
                     abi_ulong start, abi_ulong end,
195
                     int prot, int flags, int fd, abi_ulong offset)
196
{
197
    abi_ulong real_end, addr;
198
    void *host_start;
199
    int prot1, prot_new;
200

    
201
    real_end = real_start + qemu_host_page_size;
202
    host_start = g2h(real_start);
203

    
204
    /* get the protection of the target pages outside the mapping */
205
    prot1 = 0;
206
    for(addr = real_start; addr < real_end; addr++) {
207
        if (addr < start || addr >= end)
208
            prot1 |= page_get_flags(addr);
209
    }
210

    
211
    if (prot1 == 0) {
212
        /* no page was there, so we allocate one */
213
        void *p = mmap(host_start, qemu_host_page_size, prot,
214
                       flags | MAP_ANONYMOUS, -1, 0);
215
        if (p == MAP_FAILED)
216
            return -1;
217
        prot1 = prot;
218
    }
219
    prot1 &= PAGE_BITS;
220

    
221
    prot_new = prot | prot1;
222
    if (!(flags & MAP_ANONYMOUS)) {
223
        /* msync() won't work here, so we return an error if write is
224
           possible while it is a shared mapping */
225
        if ((flags & MAP_TYPE) == MAP_SHARED &&
226
            (prot & PROT_WRITE))
227
            return -EINVAL;
228

    
229
        /* adjust protection to be able to read */
230
        if (!(prot1 & PROT_WRITE))
231
            mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE);
232

    
233
        /* read the corresponding file data */
234
        pread(fd, g2h(start), end - start, offset);
235

    
236
        /* put final protection */
237
        if (prot_new != (prot1 | PROT_WRITE))
238
            mprotect(host_start, qemu_host_page_size, prot_new);
239
    } else {
240
        /* just update the protection */
241
        if (prot_new != prot1) {
242
            mprotect(host_start, qemu_host_page_size, prot_new);
243
        }
244
    }
245
    return 0;
246
}
247

    
248
#if defined(__CYGWIN__)
249
/* Cygwin doesn't have a whole lot of address space.  */
250
static abi_ulong mmap_next_start = 0x18000000;
251
#else
252
static abi_ulong mmap_next_start = 0x40000000;
253
#endif
254

    
255
unsigned long last_brk;
256

    
257
/* find a free memory area of size 'size'. The search starts at
258
   'start'. If 'start' == 0, then a default start address is used.
259
   Return -1 if error.
260
*/
261
/* page_init() marks pages used by the host as reserved to be sure not
262
   to use them. */
263
static abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
264
{
265
    abi_ulong addr, addr1, addr_start;
266
    int prot;
267
    unsigned long new_brk;
268

    
269
    new_brk = (unsigned long)sbrk(0);
270
    if (last_brk && last_brk < new_brk && last_brk == (target_ulong)last_brk) {
271
        /* This is a hack to catch the host allocating memory with brk().
272
           If it uses mmap then we loose.
273
           FIXME: We really want to avoid the host allocating memory in
274
           the first place, and maybe leave some slack to avoid switching
275
           to mmap.  */
276
        page_set_flags(last_brk & TARGET_PAGE_MASK,
277
                       TARGET_PAGE_ALIGN(new_brk),
278
                       PAGE_RESERVED); 
279
    }
280
    last_brk = new_brk;
281

    
282
    size = HOST_PAGE_ALIGN(size);
283
    start = start & qemu_host_page_mask;
284
    addr = start;
285
    if (addr == 0)
286
        addr = mmap_next_start;
287
    addr_start = addr;
288
    for(;;) {
289
        prot = 0;
290
        for(addr1 = addr; addr1 < (addr + size); addr1 += TARGET_PAGE_SIZE) {
291
            prot |= page_get_flags(addr1);
292
        }
293
        if (prot == 0)
294
            break;
295
        addr += qemu_host_page_size;
296
        /* we found nothing */
297
        if (addr == addr_start)
298
            return (abi_ulong)-1;
299
    }
300
    if (start == 0)
301
        mmap_next_start = addr + size;
302
    return addr;
303
}
304

    
305
/* NOTE: all the constants are the HOST ones */
306
abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
307
                     int flags, int fd, abi_ulong offset)
308
{
309
    abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len;
310
    unsigned long host_start;
311

    
312
    mmap_lock();
313
#ifdef DEBUG_MMAP
314
    {
315
        printf("mmap: start=0x" TARGET_FMT_lx
316
               " len=0x" TARGET_FMT_lx " prot=%c%c%c flags=",
317
               start, len,
318
               prot & PROT_READ ? 'r' : '-',
319
               prot & PROT_WRITE ? 'w' : '-',
320
               prot & PROT_EXEC ? 'x' : '-');
321
        if (flags & MAP_FIXED)
322
            printf("MAP_FIXED ");
323
        if (flags & MAP_ANONYMOUS)
324
            printf("MAP_ANON ");
325
        switch(flags & MAP_TYPE) {
326
        case MAP_PRIVATE:
327
            printf("MAP_PRIVATE ");
328
            break;
329
        case MAP_SHARED:
330
            printf("MAP_SHARED ");
331
            break;
332
        default:
333
            printf("[MAP_TYPE=0x%x] ", flags & MAP_TYPE);
334
            break;
335
        }
336
        printf("fd=%d offset=" TARGET_FMT_lx "\n", fd, offset);
337
    }
338
#endif
339

    
340
    if (offset & ~TARGET_PAGE_MASK) {
341
        errno = EINVAL;
342
        goto fail;
343
    }
344

    
345
    len = TARGET_PAGE_ALIGN(len);
346
    if (len == 0)
347
        goto the_end;
348
    real_start = start & qemu_host_page_mask;
349

    
350
    if (!(flags & MAP_FIXED)) {
351
        abi_ulong mmap_start;
352
        void *p;
353
        host_offset = offset & qemu_host_page_mask;
354
        host_len = len + offset - host_offset;
355
        host_len = HOST_PAGE_ALIGN(host_len);
356
        mmap_start = mmap_find_vma(real_start, host_len);
357
        if (mmap_start == (abi_ulong)-1) {
358
            errno = ENOMEM;
359
            goto fail;
360
        }
361
        /* Note: we prefer to control the mapping address. It is
362
           especially important if qemu_host_page_size >
363
           qemu_real_host_page_size */
364
        p = mmap(g2h(mmap_start),
365
                 host_len, prot, flags | MAP_FIXED, fd, host_offset);
366
        if (p == MAP_FAILED)
367
            goto fail;
368
        /* update start so that it points to the file position at 'offset' */
369
        host_start = (unsigned long)p;
370
        if (!(flags & MAP_ANONYMOUS))
371
            host_start += offset - host_offset;
372
        start = h2g(host_start);
373
    } else {
374
        int flg;
375
        target_ulong addr;
376

    
377
        if (start & ~TARGET_PAGE_MASK) {
378
            errno = EINVAL;
379
            goto fail;
380
        }
381
        end = start + len;
382
        real_end = HOST_PAGE_ALIGN(end);
383

    
384
        for(addr = real_start; addr < real_end; addr += TARGET_PAGE_SIZE) {
385
            flg = page_get_flags(addr);
386
            if (flg & PAGE_RESERVED) {
387
                errno = ENXIO;
388
                goto fail;
389
            }
390
        }
391

    
392
        /* worst case: we cannot map the file because the offset is not
393
           aligned, so we read it */
394
        if (!(flags & MAP_ANONYMOUS) &&
395
            (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) {
396
            /* msync() won't work here, so we return an error if write is
397
               possible while it is a shared mapping */
398
            if ((flags & MAP_TYPE) == MAP_SHARED &&
399
                (prot & PROT_WRITE)) {
400
                errno = EINVAL;
401
                goto fail;
402
            }
403
            retaddr = target_mmap(start, len, prot | PROT_WRITE,
404
                                  MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
405
                                  -1, 0);
406
            if (retaddr == -1)
407
                goto fail;
408
            pread(fd, g2h(start), len, offset);
409
            if (!(prot & PROT_WRITE)) {
410
                ret = target_mprotect(start, len, prot);
411
                if (ret != 0) {
412
                    start = ret;
413
                    goto the_end;
414
                }
415
            }
416
            goto the_end;
417
        }
418
        
419
        /* handle the start of the mapping */
420
        if (start > real_start) {
421
            if (real_end == real_start + qemu_host_page_size) {
422
                /* one single host page */
423
                ret = mmap_frag(real_start, start, end,
424
                                prot, flags, fd, offset);
425
                if (ret == -1)
426
                    goto fail;
427
                goto the_end1;
428
            }
429
            ret = mmap_frag(real_start, start, real_start + qemu_host_page_size,
430
                            prot, flags, fd, offset);
431
            if (ret == -1)
432
                goto fail;
433
            real_start += qemu_host_page_size;
434
        }
435
        /* handle the end of the mapping */
436
        if (end < real_end) {
437
            ret = mmap_frag(real_end - qemu_host_page_size,
438
                            real_end - qemu_host_page_size, real_end,
439
                            prot, flags, fd,
440
                            offset + real_end - qemu_host_page_size - start);
441
            if (ret == -1)
442
                goto fail;
443
            real_end -= qemu_host_page_size;
444
        }
445

    
446
        /* map the middle (easier) */
447
        if (real_start < real_end) {
448
            void *p;
449
            unsigned long offset1;
450
            if (flags & MAP_ANONYMOUS)
451
                offset1 = 0;
452
            else
453
                offset1 = offset + real_start - start;
454
            p = mmap(g2h(real_start), real_end - real_start,
455
                     prot, flags, fd, offset1);
456
            if (p == MAP_FAILED)
457
                goto fail;
458
        }
459
    }
460
 the_end1:
461
    page_set_flags(start, start + len, prot | PAGE_VALID);
462
 the_end:
463
#ifdef DEBUG_MMAP
464
    printf("ret=0x" TARGET_FMT_lx "\n", start);
465
    page_dump(stdout);
466
    printf("\n");
467
#endif
468
    mmap_unlock();
469
    return start;
470
fail:
471
    mmap_unlock();
472
    return -1;
473
}
474

    
475
int target_munmap(abi_ulong start, abi_ulong len)
476
{
477
    abi_ulong end, real_start, real_end, addr;
478
    int prot, ret;
479

    
480
#ifdef DEBUG_MMAP
481
    printf("munmap: start=0x%lx len=0x%lx\n", start, len);
482
#endif
483
    if (start & ~TARGET_PAGE_MASK)
484
        return -EINVAL;
485
    len = TARGET_PAGE_ALIGN(len);
486
    if (len == 0)
487
        return -EINVAL;
488
    mmap_lock();
489
    end = start + len;
490
    real_start = start & qemu_host_page_mask;
491
    real_end = HOST_PAGE_ALIGN(end);
492

    
493
    if (start > real_start) {
494
        /* handle host page containing start */
495
        prot = 0;
496
        for(addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
497
            prot |= page_get_flags(addr);
498
        }
499
        if (real_end == real_start + qemu_host_page_size) {
500
            for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
501
                prot |= page_get_flags(addr);
502
            }
503
            end = real_end;
504
        }
505
        if (prot != 0)
506
            real_start += qemu_host_page_size;
507
    }
508
    if (end < real_end) {
509
        prot = 0;
510
        for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
511
            prot |= page_get_flags(addr);
512
        }
513
        if (prot != 0)
514
            real_end -= qemu_host_page_size;
515
    }
516

    
517
    ret = 0;
518
    /* unmap what we can */
519
    if (real_start < real_end) {
520
        ret = munmap(g2h(real_start), real_end - real_start);
521
    }
522

    
523
    if (ret == 0)
524
        page_set_flags(start, start + len, 0);
525
    mmap_unlock();
526
    return ret;
527
}
528

    
529
/* XXX: currently, we only handle MAP_ANONYMOUS and not MAP_FIXED
530
   blocks which have been allocated starting on a host page */
531
abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
532
                       abi_ulong new_size, unsigned long flags,
533
                       abi_ulong new_addr)
534
{
535
    int prot;
536
    unsigned long host_addr;
537

    
538
    mmap_lock();
539
    /* XXX: use 5 args syscall */
540
    host_addr = (long)mremap(g2h(old_addr), old_size, new_size, flags);
541
    if (host_addr == -1) {
542
        new_addr = -1;
543
    } else {
544
        new_addr = h2g(host_addr);
545
        prot = page_get_flags(old_addr);
546
        page_set_flags(old_addr, old_addr + old_size, 0);
547
        page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID);
548
    }
549
    mmap_unlock();
550
    return new_addr;
551
}
552

    
553
int target_msync(abi_ulong start, abi_ulong len, int flags)
554
{
555
    abi_ulong end;
556

    
557
    if (start & ~TARGET_PAGE_MASK)
558
        return -EINVAL;
559
    len = TARGET_PAGE_ALIGN(len);
560
    end = start + len;
561
    if (end < start)
562
        return -EINVAL;
563
    if (end == start)
564
        return 0;
565

    
566
    start &= qemu_host_page_mask;
567
    return msync(g2h(start), end - start, flags);
568
}