Statistics
| Branch: | Revision:

root / bsd-user / mmap.c @ baa8c602

History | View | Annotate | Download (16.1 kB)

1
/*
2
 *  mmap support for qemu
3
 *
4
 *  Copyright (c) 2003 - 2008 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., 51 Franklin Street - Fifth Floor, Boston,
19
 *  MA 02110-1301, USA.
20
 */
21
#include <stdlib.h>
22
#include <stdio.h>
23
#include <stdarg.h>
24
#include <string.h>
25
#include <unistd.h>
26
#include <errno.h>
27
#include <sys/mman.h>
28

    
29
#include "qemu.h"
30
#include "qemu-common.h"
31
#include "bsd-mman.h"
32

    
33
//#define DEBUG_MMAP
34

    
35
#if defined(USE_NPTL)
36
pthread_mutex_t mmap_mutex;
37
static int __thread mmap_lock_count;
38

    
39
void mmap_lock(void)
40
{
41
    if (mmap_lock_count++ == 0) {
42
        pthread_mutex_lock(&mmap_mutex);
43
    }
44
}
45

    
46
void mmap_unlock(void)
47
{
48
    if (--mmap_lock_count == 0) {
49
        pthread_mutex_unlock(&mmap_mutex);
50
    }
51
}
52

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

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

    
74
void mmap_unlock(void)
75
{
76
}
77
#endif
78

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

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

    
96
    mmap_unlock();
97
    return p;
98
}
99

    
100
void *qemu_malloc(size_t size)
101
{
102
    char * p;
103
    size += 16;
104
    p = qemu_vmalloc(size);
105
    *(size_t *)p = size;
106
    return p + 16;
107
}
108

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

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

    
125
void *qemu_realloc(void *ptr, size_t size)
126
{
127
    size_t old_size, copy;
128
    void *new_ptr;
129

    
130
    if (!ptr)
131
        return qemu_malloc(size);
132
    old_size = *(size_t *)((char *)ptr - 16);
133
    copy = old_size < size ? old_size : size;
134
    new_ptr = qemu_malloc(size);
135
    memcpy(new_ptr, ptr, copy);
136
    qemu_free(ptr);
137
    return new_ptr;
138
}
139

    
140
/* NOTE: all the constants are the HOST ones, but addresses are target. */
141
int target_mprotect(abi_ulong start, abi_ulong len, int prot)
142
{
143
    abi_ulong end, host_start, host_end, addr;
144
    int prot1, ret;
145

    
146
#ifdef DEBUG_MMAP
147
    printf("mprotect: start=0x" TARGET_FMT_lx
148
           " len=0x" TARGET_FMT_lx " prot=%c%c%c\n", start, len,
149
           prot & PROT_READ ? 'r' : '-',
150
           prot & PROT_WRITE ? 'w' : '-',
151
           prot & PROT_EXEC ? 'x' : '-');
152
#endif
153

    
154
    if ((start & ~TARGET_PAGE_MASK) != 0)
155
        return -EINVAL;
156
    len = TARGET_PAGE_ALIGN(len);
157
    end = start + len;
158
    if (end < start)
159
        return -EINVAL;
160
    prot &= PROT_READ | PROT_WRITE | PROT_EXEC;
161
    if (len == 0)
162
        return 0;
163

    
164
    mmap_lock();
165
    host_start = start & qemu_host_page_mask;
166
    host_end = HOST_PAGE_ALIGN(end);
167
    if (start > host_start) {
168
        /* handle host page containing start */
169
        prot1 = prot;
170
        for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) {
171
            prot1 |= page_get_flags(addr);
172
        }
173
        if (host_end == host_start + qemu_host_page_size) {
174
            for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
175
                prot1 |= page_get_flags(addr);
176
            }
177
            end = host_end;
178
        }
179
        ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS);
180
        if (ret != 0)
181
            goto error;
182
        host_start += qemu_host_page_size;
183
    }
184
    if (end < host_end) {
185
        prot1 = prot;
186
        for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
187
            prot1 |= page_get_flags(addr);
188
        }
189
        ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size,
190
                       prot1 & PAGE_BITS);
191
        if (ret != 0)
192
            goto error;
193
        host_end -= qemu_host_page_size;
194
    }
195

    
196
    /* handle the pages in the middle */
197
    if (host_start < host_end) {
198
        ret = mprotect(g2h(host_start), host_end - host_start, prot);
199
        if (ret != 0)
200
            goto error;
201
    }
202
    page_set_flags(start, start + len, prot | PAGE_VALID);
203
    mmap_unlock();
204
    return 0;
205
error:
206
    mmap_unlock();
207
    return ret;
208
}
209

    
210
/* map an incomplete host page */
211
static int mmap_frag(abi_ulong real_start,
212
                     abi_ulong start, abi_ulong end,
213
                     int prot, int flags, int fd, abi_ulong offset)
214
{
215
    abi_ulong real_end, addr;
216
    void *host_start;
217
    int prot1, prot_new;
218

    
219
    real_end = real_start + qemu_host_page_size;
220
    host_start = g2h(real_start);
221

    
222
    /* get the protection of the target pages outside the mapping */
223
    prot1 = 0;
224
    for(addr = real_start; addr < real_end; addr++) {
225
        if (addr < start || addr >= end)
226
            prot1 |= page_get_flags(addr);
227
    }
228

    
229
    if (prot1 == 0) {
230
        /* no page was there, so we allocate one */
231
        void *p = mmap(host_start, qemu_host_page_size, prot,
232
                       flags | MAP_ANON, -1, 0);
233
        if (p == MAP_FAILED)
234
            return -1;
235
        prot1 = prot;
236
    }
237
    prot1 &= PAGE_BITS;
238

    
239
    prot_new = prot | prot1;
240
    if (!(flags & MAP_ANON)) {
241
        /* msync() won't work here, so we return an error if write is
242
           possible while it is a shared mapping */
243
        if ((flags & TARGET_BSD_MAP_FLAGMASK) == MAP_SHARED &&
244
            (prot & PROT_WRITE))
245
            return -EINVAL;
246

    
247
        /* adjust protection to be able to read */
248
        if (!(prot1 & PROT_WRITE))
249
            mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE);
250

    
251
        /* read the corresponding file data */
252
        pread(fd, g2h(start), end - start, offset);
253

    
254
        /* put final protection */
255
        if (prot_new != (prot1 | PROT_WRITE))
256
            mprotect(host_start, qemu_host_page_size, prot_new);
257
    } else {
258
        /* just update the protection */
259
        if (prot_new != prot1) {
260
            mprotect(host_start, qemu_host_page_size, prot_new);
261
        }
262
    }
263
    return 0;
264
}
265

    
266
#if defined(__CYGWIN__)
267
/* Cygwin doesn't have a whole lot of address space.  */
268
static abi_ulong mmap_next_start = 0x18000000;
269
#else
270
static abi_ulong mmap_next_start = 0x40000000;
271
#endif
272

    
273
unsigned long last_brk;
274

    
275
/* find a free memory area of size 'size'. The search starts at
276
   'start'. If 'start' == 0, then a default start address is used.
277
   Return -1 if error.
278
*/
279
/* page_init() marks pages used by the host as reserved to be sure not
280
   to use them. */
281
static abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
282
{
283
    abi_ulong addr, addr1, addr_start;
284
    int prot;
285
    unsigned long new_brk;
286

    
287
    new_brk = (unsigned long)sbrk(0);
288
    if (last_brk && last_brk < new_brk && last_brk == (target_ulong)last_brk) {
289
        /* This is a hack to catch the host allocating memory with brk().
290
           If it uses mmap then we loose.
291
           FIXME: We really want to avoid the host allocating memory in
292
           the first place, and maybe leave some slack to avoid switching
293
           to mmap.  */
294
        page_set_flags(last_brk & TARGET_PAGE_MASK,
295
                       TARGET_PAGE_ALIGN(new_brk),
296
                       PAGE_RESERVED);
297
    }
298
    last_brk = new_brk;
299

    
300
    size = HOST_PAGE_ALIGN(size);
301
    start = start & qemu_host_page_mask;
302
    addr = start;
303
    if (addr == 0)
304
        addr = mmap_next_start;
305
    addr_start = addr;
306
    for(;;) {
307
        prot = 0;
308
        for(addr1 = addr; addr1 < (addr + size); addr1 += TARGET_PAGE_SIZE) {
309
            prot |= page_get_flags(addr1);
310
        }
311
        if (prot == 0)
312
            break;
313
        addr += qemu_host_page_size;
314
        /* we found nothing */
315
        if (addr == addr_start)
316
            return (abi_ulong)-1;
317
    }
318
    if (start == 0)
319
        mmap_next_start = addr + size;
320
    return addr;
321
}
322

    
323
/* NOTE: all the constants are the HOST ones */
324
abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
325
                     int flags, int fd, abi_ulong offset)
326
{
327
    abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len;
328
    unsigned long host_start;
329

    
330
    mmap_lock();
331
#ifdef DEBUG_MMAP
332
    {
333
        printf("mmap: start=0x" TARGET_FMT_lx
334
               " len=0x" TARGET_FMT_lx " prot=%c%c%c flags=",
335
               start, len,
336
               prot & PROT_READ ? 'r' : '-',
337
               prot & PROT_WRITE ? 'w' : '-',
338
               prot & PROT_EXEC ? 'x' : '-');
339
        if (flags & MAP_FIXED)
340
            printf("MAP_FIXED ");
341
        if (flags & MAP_ANON)
342
            printf("MAP_ANON ");
343
        switch(flags & TARGET_BSD_MAP_FLAGMASK) {
344
        case MAP_PRIVATE:
345
            printf("MAP_PRIVATE ");
346
            break;
347
        case MAP_SHARED:
348
            printf("MAP_SHARED ");
349
            break;
350
        default:
351
            printf("[MAP_FLAGMASK=0x%x] ", flags & TARGET_BSD_MAP_FLAGMASK);
352
            break;
353
        }
354
        printf("fd=%d offset=" TARGET_FMT_lx "\n", fd, offset);
355
    }
356
#endif
357

    
358
    if (offset & ~TARGET_PAGE_MASK) {
359
        errno = EINVAL;
360
        goto fail;
361
    }
362

    
363
    len = TARGET_PAGE_ALIGN(len);
364
    if (len == 0)
365
        goto the_end;
366
    real_start = start & qemu_host_page_mask;
367

    
368
    if (!(flags & MAP_FIXED)) {
369
        abi_ulong mmap_start;
370
        void *p;
371
        host_offset = offset & qemu_host_page_mask;
372
        host_len = len + offset - host_offset;
373
        host_len = HOST_PAGE_ALIGN(host_len);
374
        mmap_start = mmap_find_vma(real_start, host_len);
375
        if (mmap_start == (abi_ulong)-1) {
376
            errno = ENOMEM;
377
            goto fail;
378
        }
379
        /* Note: we prefer to control the mapping address. It is
380
           especially important if qemu_host_page_size >
381
           qemu_real_host_page_size */
382
        p = mmap(g2h(mmap_start),
383
                 host_len, prot, flags | MAP_FIXED, fd, host_offset);
384
        if (p == MAP_FAILED)
385
            goto fail;
386
        /* update start so that it points to the file position at 'offset' */
387
        host_start = (unsigned long)p;
388
        if (!(flags & MAP_ANON))
389
            host_start += offset - host_offset;
390
        start = h2g(host_start);
391
    } else {
392
        int flg;
393
        target_ulong addr;
394

    
395
        if (start & ~TARGET_PAGE_MASK) {
396
            errno = EINVAL;
397
            goto fail;
398
        }
399
        end = start + len;
400
        real_end = HOST_PAGE_ALIGN(end);
401

    
402
        for(addr = real_start; addr < real_end; addr += TARGET_PAGE_SIZE) {
403
            flg = page_get_flags(addr);
404
            if (flg & PAGE_RESERVED) {
405
                errno = ENXIO;
406
                goto fail;
407
            }
408
        }
409

    
410
        /* worst case: we cannot map the file because the offset is not
411
           aligned, so we read it */
412
        if (!(flags & MAP_ANON) &&
413
            (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) {
414
            /* msync() won't work here, so we return an error if write is
415
               possible while it is a shared mapping */
416
            if ((flags & TARGET_BSD_MAP_FLAGMASK) == MAP_SHARED &&
417
                (prot & PROT_WRITE)) {
418
                errno = EINVAL;
419
                goto fail;
420
            }
421
            retaddr = target_mmap(start, len, prot | PROT_WRITE,
422
                                  MAP_FIXED | MAP_PRIVATE | MAP_ANON,
423
                                  -1, 0);
424
            if (retaddr == -1)
425
                goto fail;
426
            pread(fd, g2h(start), len, offset);
427
            if (!(prot & PROT_WRITE)) {
428
                ret = target_mprotect(start, len, prot);
429
                if (ret != 0) {
430
                    start = ret;
431
                    goto the_end;
432
                }
433
            }
434
            goto the_end;
435
        }
436

    
437
        /* handle the start of the mapping */
438
        if (start > real_start) {
439
            if (real_end == real_start + qemu_host_page_size) {
440
                /* one single host page */
441
                ret = mmap_frag(real_start, start, end,
442
                                prot, flags, fd, offset);
443
                if (ret == -1)
444
                    goto fail;
445
                goto the_end1;
446
            }
447
            ret = mmap_frag(real_start, start, real_start + qemu_host_page_size,
448
                            prot, flags, fd, offset);
449
            if (ret == -1)
450
                goto fail;
451
            real_start += qemu_host_page_size;
452
        }
453
        /* handle the end of the mapping */
454
        if (end < real_end) {
455
            ret = mmap_frag(real_end - qemu_host_page_size,
456
                            real_end - qemu_host_page_size, real_end,
457
                            prot, flags, fd,
458
                            offset + real_end - qemu_host_page_size - start);
459
            if (ret == -1)
460
                goto fail;
461
            real_end -= qemu_host_page_size;
462
        }
463

    
464
        /* map the middle (easier) */
465
        if (real_start < real_end) {
466
            void *p;
467
            unsigned long offset1;
468
            if (flags & MAP_ANON)
469
                offset1 = 0;
470
            else
471
                offset1 = offset + real_start - start;
472
            p = mmap(g2h(real_start), real_end - real_start,
473
                     prot, flags, fd, offset1);
474
            if (p == MAP_FAILED)
475
                goto fail;
476
        }
477
    }
478
 the_end1:
479
    page_set_flags(start, start + len, prot | PAGE_VALID);
480
 the_end:
481
#ifdef DEBUG_MMAP
482
    printf("ret=0x" TARGET_FMT_lx "\n", start);
483
    page_dump(stdout);
484
    printf("\n");
485
#endif
486
    mmap_unlock();
487
    return start;
488
fail:
489
    mmap_unlock();
490
    return -1;
491
}
492

    
493
int target_munmap(abi_ulong start, abi_ulong len)
494
{
495
    abi_ulong end, real_start, real_end, addr;
496
    int prot, ret;
497

    
498
#ifdef DEBUG_MMAP
499
    printf("munmap: start=0x%lx len=0x%lx\n", start, len);
500
#endif
501
    if (start & ~TARGET_PAGE_MASK)
502
        return -EINVAL;
503
    len = TARGET_PAGE_ALIGN(len);
504
    if (len == 0)
505
        return -EINVAL;
506
    mmap_lock();
507
    end = start + len;
508
    real_start = start & qemu_host_page_mask;
509
    real_end = HOST_PAGE_ALIGN(end);
510

    
511
    if (start > real_start) {
512
        /* handle host page containing start */
513
        prot = 0;
514
        for(addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
515
            prot |= page_get_flags(addr);
516
        }
517
        if (real_end == real_start + qemu_host_page_size) {
518
            for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
519
                prot |= page_get_flags(addr);
520
            }
521
            end = real_end;
522
        }
523
        if (prot != 0)
524
            real_start += qemu_host_page_size;
525
    }
526
    if (end < real_end) {
527
        prot = 0;
528
        for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
529
            prot |= page_get_flags(addr);
530
        }
531
        if (prot != 0)
532
            real_end -= qemu_host_page_size;
533
    }
534

    
535
    ret = 0;
536
    /* unmap what we can */
537
    if (real_start < real_end) {
538
        ret = munmap(g2h(real_start), real_end - real_start);
539
    }
540

    
541
    if (ret == 0)
542
        page_set_flags(start, start + len, 0);
543
    mmap_unlock();
544
    return ret;
545
}
546

    
547
int target_msync(abi_ulong start, abi_ulong len, int flags)
548
{
549
    abi_ulong end;
550

    
551
    if (start & ~TARGET_PAGE_MASK)
552
        return -EINVAL;
553
    len = TARGET_PAGE_ALIGN(len);
554
    end = start + len;
555
    if (end < start)
556
        return -EINVAL;
557
    if (end == start)
558
        return 0;
559

    
560
    start &= qemu_host_page_mask;
561
    return msync(g2h(start), end - start, flags);
562
}