minix/servers/pm/alloc.c

446 lines
15 KiB
C
Raw Normal View History

/* This file is concerned with allocating and freeing arbitrary-size blocks of
* physical memory on behalf of the FORK and EXEC system calls. The key data
* structure used is the hole table, which maintains a list of holes in memory.
* It is kept sorted in order of increasing memory address. The addresses
* it contains refers to physical memory, starting at absolute address 0
* (i.e., they are not relative to the start of PM). During system
* initialization, that part of memory containing the interrupt vectors,
* kernel, and PM are "allocated" to mark them as not available and to
* remove them from the hole list.
*
* The entry points into this file are:
* alloc_mem: allocate a given sized chunk of memory
* free_mem: release a previously allocated chunk of memory
* mem_init: initialize the tables when PM start up
* max_hole: returns the largest hole currently available
* mem_holes_copy: for outsiders who want a copy of the hole-list
*/
#include "pm.h"
#include <minix/com.h>
#include <minix/callnr.h>
#include <minix/type.h>
#include <minix/config.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include "mproc.h"
#include "../../kernel/const.h"
#include "../../kernel/config.h"
#include "../../kernel/type.h"
#define NIL_HOLE (struct hole *) 0
PRIVATE struct hole hole[_NR_HOLES];
2005-10-18 19:21:11 +02:00
PRIVATE u32_t high_watermark = 0;
PRIVATE struct hole *hole_head; /* pointer to first hole */
PRIVATE struct hole *free_slots;/* ptr to list of unused table slots */
#if ENABLE_SWAP
PRIVATE int swap_fd = -1; /* file descriptor of open swap file/device */
PRIVATE u32_t swap_offset; /* offset to start of swap area on swap file */
PRIVATE phys_clicks swap_base; /* memory offset chosen as swap base */
PRIVATE phys_clicks swap_maxsize;/* maximum amount of swap "memory" possible */
PRIVATE struct mproc *in_queue; /* queue of processes wanting to swap in */
PRIVATE struct mproc *outswap = &mproc[0]; /* outswap candidate? */
#else /* ! ENABLE_SWAP */
#define swap_base ((phys_clicks) -1)
#endif /* ENABLE_SWAP */
FORWARD _PROTOTYPE( void del_slot, (struct hole *prev_ptr, struct hole *hp) );
FORWARD _PROTOTYPE( void merge, (struct hole *hp) );
#if ENABLE_SWAP
FORWARD _PROTOTYPE( int swap_out, (void) );
#else
#define swap_out() (0)
#endif
/*===========================================================================*
* alloc_mem *
*===========================================================================*/
PUBLIC phys_clicks alloc_mem(clicks)
phys_clicks clicks; /* amount of memory requested */
{
/* Allocate a block of memory from the free list using first fit. The block
* consists of a sequence of contiguous bytes, whose length in clicks is
* given by 'clicks'. A pointer to the block is returned. The block is
* always on a click boundary. This procedure is called when memory is
* needed for FORK or EXEC. Swap other processes out if needed.
*/
register struct hole *hp, *prev_ptr;
phys_clicks old_base;
do {
prev_ptr = NIL_HOLE;
hp = hole_head;
while (hp != NIL_HOLE && hp->h_base < swap_base) {
if (hp->h_len >= clicks) {
/* We found a hole that is big enough. Use it. */
old_base = hp->h_base; /* remember where it started */
hp->h_base += clicks; /* bite a piece off */
hp->h_len -= clicks; /* ditto */
2005-10-18 19:21:11 +02:00
/* Remember new high watermark of used memory. */
if(hp->h_base > high_watermark)
high_watermark = hp->h_base;
/* Delete the hole if used up completely. */
if (hp->h_len == 0) del_slot(prev_ptr, hp);
/* Return the start address of the acquired block. */
return(old_base);
}
prev_ptr = hp;
hp = hp->h_next;
}
} while (swap_out()); /* try to swap some other process out */
return(NO_MEM);
}
/*===========================================================================*
* free_mem *
*===========================================================================*/
PUBLIC void free_mem(base, clicks)
phys_clicks base; /* base address of block to free */
phys_clicks clicks; /* number of clicks to free */
{
/* Return a block of free memory to the hole list. The parameters tell where
* the block starts in physical memory and how big it is. The block is added
* to the hole list. If it is contiguous with an existing hole on either end,
* it is merged with the hole or holes.
*/
register struct hole *hp, *new_ptr, *prev_ptr;
if (clicks == 0) return;
if ( (new_ptr = free_slots) == NIL_HOLE)
panic(__FILE__,"hole table full", NO_NUM);
new_ptr->h_base = base;
new_ptr->h_len = clicks;
free_slots = new_ptr->h_next;
hp = hole_head;
/* If this block's address is numerically less than the lowest hole currently
* available, or if no holes are currently available, put this hole on the
* front of the hole list.
*/
if (hp == NIL_HOLE || base <= hp->h_base) {
/* Block to be freed goes on front of the hole list. */
new_ptr->h_next = hp;
hole_head = new_ptr;
merge(new_ptr);
return;
}
/* Block to be returned does not go on front of hole list. */
prev_ptr = NIL_HOLE;
while (hp != NIL_HOLE && base > hp->h_base) {
prev_ptr = hp;
hp = hp->h_next;
}
/* We found where it goes. Insert block after 'prev_ptr'. */
new_ptr->h_next = prev_ptr->h_next;
prev_ptr->h_next = new_ptr;
merge(prev_ptr); /* sequence is 'prev_ptr', 'new_ptr', 'hp' */
}
/*===========================================================================*
* del_slot *
*===========================================================================*/
PRIVATE void del_slot(prev_ptr, hp)
2005-08-29 18:47:18 +02:00
/* pointer to hole entry just ahead of 'hp' */
register struct hole *prev_ptr;
/* pointer to hole entry to be removed */
register struct hole *hp;
{
/* Remove an entry from the hole list. This procedure is called when a
* request to allocate memory removes a hole in its entirety, thus reducing
* the numbers of holes in memory, and requiring the elimination of one
* entry in the hole list.
*/
if (hp == hole_head)
hole_head = hp->h_next;
else
prev_ptr->h_next = hp->h_next;
hp->h_next = free_slots;
hp->h_base = hp->h_len = 0;
free_slots = hp;
}
/*===========================================================================*
* merge *
*===========================================================================*/
PRIVATE void merge(hp)
register struct hole *hp; /* ptr to hole to merge with its successors */
{
/* Check for contiguous holes and merge any found. Contiguous holes can occur
* when a block of memory is freed, and it happens to abut another hole on
* either or both ends. The pointer 'hp' points to the first of a series of
* three holes that can potentially all be merged together.
*/
register struct hole *next_ptr;
/* If 'hp' points to the last hole, no merging is possible. If it does not,
* try to absorb its successor into it and free the successor's table entry.
*/
if ( (next_ptr = hp->h_next) == NIL_HOLE) return;
if (hp->h_base + hp->h_len == next_ptr->h_base) {
hp->h_len += next_ptr->h_len; /* first one gets second one's mem */
del_slot(hp, next_ptr);
} else {
hp = next_ptr;
}
/* If 'hp' now points to the last hole, return; otherwise, try to absorb its
* successor into it.
*/
if ( (next_ptr = hp->h_next) == NIL_HOLE) return;
if (hp->h_base + hp->h_len == next_ptr->h_base) {
hp->h_len += next_ptr->h_len;
del_slot(hp, next_ptr);
}
}
/*===========================================================================*
* mem_init *
*===========================================================================*/
PUBLIC void mem_init(chunks, free)
struct memory *chunks; /* list of free memory chunks */
phys_clicks *free; /* memory size summaries */
{
/* Initialize hole lists. There are two lists: 'hole_head' points to a linked
* list of all the holes (unused memory) in the system; 'free_slots' points to
* a linked list of table entries that are not in use. Initially, the former
* list has one entry for each chunk of physical memory, and the second
* list links together the remaining table slots. As memory becomes more
* fragmented in the course of time (i.e., the initial big holes break up into
* smaller holes), new table slots are needed to represent them. These slots
* are taken from the list headed by 'free_slots'.
*/
int i;
register struct hole *hp;
/* Put all holes on the free list. */
for (hp = &hole[0]; hp < &hole[_NR_HOLES]; hp++) {
hp->h_next = hp + 1;
hp->h_base = hp->h_len = 0;
}
hole[_NR_HOLES-1].h_next = NIL_HOLE;
hole_head = NIL_HOLE;
free_slots = &hole[0];
/* Use the chunks of physical memory to allocate holes. */
*free = 0;
for (i=NR_MEMS-1; i>=0; i--) {
if (chunks[i].size > 0) {
free_mem(chunks[i].base, chunks[i].size);
*free += chunks[i].size;
#if ENABLE_SWAP
if (swap_base < chunks[i].base + chunks[i].size)
swap_base = chunks[i].base + chunks[i].size;
#endif
}
}
#if ENABLE_SWAP
/* The swap area is represented as a hole above and separate of regular
* memory. A hole at the size of the swap file is allocated on "swapon".
*/
swap_base++; /* make separate */
swap_maxsize = 0 - swap_base; /* maximum we can possibly use */
#endif
}
/*===========================================================================*
* mem_holes_copy *
*===========================================================================*/
2005-10-18 19:21:11 +02:00
PUBLIC int mem_holes_copy(struct hole *holecopies, size_t *bytes, u32_t *hi)
{
if(*bytes < sizeof(hole)) return ENOSPC;
memcpy(holecopies, hole, sizeof(hole));
*bytes = sizeof(hole);
2005-10-18 19:21:11 +02:00
*hi = high_watermark;
return OK;
}
#if ENABLE_SWAP
/*===========================================================================*
* swap_on *
*===========================================================================*/
PUBLIC int swap_on(file, offset, size)
char *file; /* file to swap on */
u32_t offset, size; /* area on swap file to use */
{
/* Turn swapping on. */
if (swap_fd != -1) return(EBUSY); /* already have swap? */
endpoint-aware conversion of servers. 'who', indicating caller number in pm and fs and some other servers, has been removed in favour of 'who_e' (endpoint) and 'who_p' (proc nr.). In both PM and FS, isokendpt() convert endpoints to process slot numbers, returning OK if it was a valid and consistent endpoint number. okendpt() does the same but panic()s if it doesn't succeed. (In PM, this is pm_isok..) pm and fs keep their own records of process endpoints in their proc tables, which are needed to make kernel calls about those processes. message field names have changed. fs drivers are endpoints. fs now doesn't try to get out of driver deadlock, as the protocol isn't supposed to let that happen any more. (A warning is printed if ELOCKED is detected though.) fproc[].fp_task (indicating which driver the process is suspended on) became an int. PM and FS now get endpoint numbers of initial boot processes from the kernel. These happen to be the same as the old proc numbers, to let user processes reach them with the old numbers, but FS and PM don't know that. All new processes after INIT, even after the generation number wraps around, get endpoint numbers with generation 1 and higher, so the first instances of the boot processes are the only processes ever to have endpoint numbers in the old proc number range. More return code checks of sys_* functions have been added. IS has become endpoint-aware. Ditched the 'text' and 'data' fields in the kernel dump (which show locations, not sizes, so aren't terribly useful) in favour of the endpoint number. Proc number is still visible. Some other dumps (e.g. dmap, rs) show endpoint numbers now too which got the formatting changed. PM reading segments using rw_seg() has changed - it uses other fields in the message now instead of encoding the segment and process number and fd in the fd field. For that it uses _read_pm() and _write_pm() which to _taskcall()s directly in pm/misc.c. PM now sys_exit()s itself on panic(), instead of sys_abort(). RS also talks in endpoints instead of process numbers.
2006-03-03 11:20:58 +01:00
tell_fs(CHDIR, who_e, FALSE, 0); /* be like the caller for open() */
if ((swap_fd = open(file, O_RDWR)) < 0) return(-errno);
swap_offset = offset;
size >>= CLICK_SHIFT;
if (size > swap_maxsize) size = swap_maxsize;
if (size > 0) free_mem(swap_base, (phys_clicks) size);
return(OK);
}
/*===========================================================================*
* swap_off *
*===========================================================================*/
PUBLIC int swap_off()
{
/* Turn swapping off. */
struct mproc *rmp;
struct hole *hp, *prev_ptr;
if (swap_fd == -1) return(OK); /* can't turn off what isn't on */
/* Put all swapped out processes on the inswap queue and swap in. */
for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
if (rmp->mp_flags & ONSWAP) swap_inqueue(rmp);
}
swap_in();
/* All in memory? */
for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
if (rmp->mp_flags & ONSWAP) return(ENOMEM);
}
/* Yes. Remove the swap hole and close the swap file descriptor. */
for (hp = hole_head; hp != NIL_HOLE; prev_ptr = hp, hp = hp->h_next) {
if (hp->h_base >= swap_base) {
del_slot(prev_ptr, hp);
hp = hole_head;
}
}
close(swap_fd);
swap_fd = -1;
return(OK);
}
/*===========================================================================*
* swap_inqueue *
*===========================================================================*/
PUBLIC void swap_inqueue(rmp)
register struct mproc *rmp; /* process to add to the queue */
{
/* Put a swapped out process on the queue of processes to be swapped in. This
* happens when such a process gets a signal, or if a reply message must be
* sent, like when a process doing a wait() has a child that exits.
*/
struct mproc **pmp;
if (rmp->mp_flags & SWAPIN) return; /* already queued */
for (pmp = &in_queue; *pmp != NULL; pmp = &(*pmp)->mp_swapq) {}
*pmp = rmp;
rmp->mp_swapq = NULL;
rmp->mp_flags |= SWAPIN;
}
/*===========================================================================*
* swap_in *
*===========================================================================*/
PUBLIC void swap_in()
{
/* Try to swap in a process on the inswap queue. We want to send it a message,
* interrupt it, or something.
*/
struct mproc **pmp, *rmp;
phys_clicks old_base, new_base, size;
off_t off;
int proc_nr;
pmp = &in_queue;
while ((rmp = *pmp) != NULL) {
proc_nr = (rmp - mproc);
size = rmp->mp_seg[S].mem_vir + rmp->mp_seg[S].mem_len
- rmp->mp_seg[D].mem_vir;
if (!(rmp->mp_flags & SWAPIN)) {
/* Guess it got killed. (Queue is cleaned here.) */
*pmp = rmp->mp_swapq;
continue;
} else
if ((new_base = alloc_mem(size)) == NO_MEM) {
/* No memory for this one, try the next. */
pmp = &rmp->mp_swapq;
} else {
/* We've found memory. Update map and swap in. */
old_base = rmp->mp_seg[D].mem_phys;
rmp->mp_seg[D].mem_phys = new_base;
rmp->mp_seg[S].mem_phys = rmp->mp_seg[D].mem_phys +
(rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_vir);
endpoint-aware conversion of servers. 'who', indicating caller number in pm and fs and some other servers, has been removed in favour of 'who_e' (endpoint) and 'who_p' (proc nr.). In both PM and FS, isokendpt() convert endpoints to process slot numbers, returning OK if it was a valid and consistent endpoint number. okendpt() does the same but panic()s if it doesn't succeed. (In PM, this is pm_isok..) pm and fs keep their own records of process endpoints in their proc tables, which are needed to make kernel calls about those processes. message field names have changed. fs drivers are endpoints. fs now doesn't try to get out of driver deadlock, as the protocol isn't supposed to let that happen any more. (A warning is printed if ELOCKED is detected though.) fproc[].fp_task (indicating which driver the process is suspended on) became an int. PM and FS now get endpoint numbers of initial boot processes from the kernel. These happen to be the same as the old proc numbers, to let user processes reach them with the old numbers, but FS and PM don't know that. All new processes after INIT, even after the generation number wraps around, get endpoint numbers with generation 1 and higher, so the first instances of the boot processes are the only processes ever to have endpoint numbers in the old proc number range. More return code checks of sys_* functions have been added. IS has become endpoint-aware. Ditched the 'text' and 'data' fields in the kernel dump (which show locations, not sizes, so aren't terribly useful) in favour of the endpoint number. Proc number is still visible. Some other dumps (e.g. dmap, rs) show endpoint numbers now too which got the formatting changed. PM reading segments using rw_seg() has changed - it uses other fields in the message now instead of encoding the segment and process number and fd in the fd field. For that it uses _read_pm() and _write_pm() which to _taskcall()s directly in pm/misc.c. PM now sys_exit()s itself on panic(), instead of sys_abort(). RS also talks in endpoints instead of process numbers.
2006-03-03 11:20:58 +01:00
sys_newmap(rmp->mp_endpoint, rmp->mp_seg);
off = swap_offset + ((off_t) (old_base-swap_base)<<CLICK_SHIFT);
lseek(swap_fd, off, SEEK_SET);
endpoint-aware conversion of servers. 'who', indicating caller number in pm and fs and some other servers, has been removed in favour of 'who_e' (endpoint) and 'who_p' (proc nr.). In both PM and FS, isokendpt() convert endpoints to process slot numbers, returning OK if it was a valid and consistent endpoint number. okendpt() does the same but panic()s if it doesn't succeed. (In PM, this is pm_isok..) pm and fs keep their own records of process endpoints in their proc tables, which are needed to make kernel calls about those processes. message field names have changed. fs drivers are endpoints. fs now doesn't try to get out of driver deadlock, as the protocol isn't supposed to let that happen any more. (A warning is printed if ELOCKED is detected though.) fproc[].fp_task (indicating which driver the process is suspended on) became an int. PM and FS now get endpoint numbers of initial boot processes from the kernel. These happen to be the same as the old proc numbers, to let user processes reach them with the old numbers, but FS and PM don't know that. All new processes after INIT, even after the generation number wraps around, get endpoint numbers with generation 1 and higher, so the first instances of the boot processes are the only processes ever to have endpoint numbers in the old proc number range. More return code checks of sys_* functions have been added. IS has become endpoint-aware. Ditched the 'text' and 'data' fields in the kernel dump (which show locations, not sizes, so aren't terribly useful) in favour of the endpoint number. Proc number is still visible. Some other dumps (e.g. dmap, rs) show endpoint numbers now too which got the formatting changed. PM reading segments using rw_seg() has changed - it uses other fields in the message now instead of encoding the segment and process number and fd in the fd field. For that it uses _read_pm() and _write_pm() which to _taskcall()s directly in pm/misc.c. PM now sys_exit()s itself on panic(), instead of sys_abort(). RS also talks in endpoints instead of process numbers.
2006-03-03 11:20:58 +01:00
rw_seg(0, swap_fd, rmp->mp_endpoint, D, (phys_bytes)size << CLICK_SHIFT);
free_mem(old_base, size);
rmp->mp_flags &= ~(ONSWAP|SWAPIN);
*pmp = rmp->mp_swapq;
check_pending(rmp); /* a signal may have waked this one */
}
}
}
/*===========================================================================*
* swap_out *
*===========================================================================*/
PRIVATE int swap_out()
{
/* Try to find a process that can be swapped out. Candidates are those blocked
* on a system call that PM handles, like wait(), pause() or sigsuspend().
*/
struct mproc *rmp;
struct hole *hp, *prev_ptr;
phys_clicks old_base, new_base, size;
off_t off;
int proc_nr;
rmp = outswap;
do {
if (++rmp == &mproc[NR_PROCS]) rmp = &mproc[0];
/* A candidate? */
if (!(rmp->mp_flags & (PAUSED | WAITING | SIGSUSPENDED))) continue;
/* Already on swap or otherwise to be avoided? */
if (rmp->mp_flags & (DONT_SWAP | TRACED | REPLY | ONSWAP)) continue;
/* Got one, find a swap hole and swap it out. */
proc_nr = (rmp - mproc);
size = rmp->mp_seg[S].mem_vir + rmp->mp_seg[S].mem_len
- rmp->mp_seg[D].mem_vir;
prev_ptr = NIL_HOLE;
for (hp = hole_head; hp != NIL_HOLE; prev_ptr = hp, hp = hp->h_next) {
if (hp->h_base >= swap_base && hp->h_len >= size) break;
}
if (hp == NIL_HOLE) continue; /* oops, not enough swapspace */
new_base = hp->h_base;
hp->h_base += size;
hp->h_len -= size;
if (hp->h_len == 0) del_slot(prev_ptr, hp);
off = swap_offset + ((off_t) (new_base - swap_base) << CLICK_SHIFT);
lseek(swap_fd, off, SEEK_SET);
endpoint-aware conversion of servers. 'who', indicating caller number in pm and fs and some other servers, has been removed in favour of 'who_e' (endpoint) and 'who_p' (proc nr.). In both PM and FS, isokendpt() convert endpoints to process slot numbers, returning OK if it was a valid and consistent endpoint number. okendpt() does the same but panic()s if it doesn't succeed. (In PM, this is pm_isok..) pm and fs keep their own records of process endpoints in their proc tables, which are needed to make kernel calls about those processes. message field names have changed. fs drivers are endpoints. fs now doesn't try to get out of driver deadlock, as the protocol isn't supposed to let that happen any more. (A warning is printed if ELOCKED is detected though.) fproc[].fp_task (indicating which driver the process is suspended on) became an int. PM and FS now get endpoint numbers of initial boot processes from the kernel. These happen to be the same as the old proc numbers, to let user processes reach them with the old numbers, but FS and PM don't know that. All new processes after INIT, even after the generation number wraps around, get endpoint numbers with generation 1 and higher, so the first instances of the boot processes are the only processes ever to have endpoint numbers in the old proc number range. More return code checks of sys_* functions have been added. IS has become endpoint-aware. Ditched the 'text' and 'data' fields in the kernel dump (which show locations, not sizes, so aren't terribly useful) in favour of the endpoint number. Proc number is still visible. Some other dumps (e.g. dmap, rs) show endpoint numbers now too which got the formatting changed. PM reading segments using rw_seg() has changed - it uses other fields in the message now instead of encoding the segment and process number and fd in the fd field. For that it uses _read_pm() and _write_pm() which to _taskcall()s directly in pm/misc.c. PM now sys_exit()s itself on panic(), instead of sys_abort(). RS also talks in endpoints instead of process numbers.
2006-03-03 11:20:58 +01:00
rw_seg(1, swap_fd, rmp->mp_endpoint, D, (phys_bytes)size << CLICK_SHIFT);
old_base = rmp->mp_seg[D].mem_phys;
rmp->mp_seg[D].mem_phys = new_base;
rmp->mp_seg[S].mem_phys = rmp->mp_seg[D].mem_phys +
(rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_vir);
endpoint-aware conversion of servers. 'who', indicating caller number in pm and fs and some other servers, has been removed in favour of 'who_e' (endpoint) and 'who_p' (proc nr.). In both PM and FS, isokendpt() convert endpoints to process slot numbers, returning OK if it was a valid and consistent endpoint number. okendpt() does the same but panic()s if it doesn't succeed. (In PM, this is pm_isok..) pm and fs keep their own records of process endpoints in their proc tables, which are needed to make kernel calls about those processes. message field names have changed. fs drivers are endpoints. fs now doesn't try to get out of driver deadlock, as the protocol isn't supposed to let that happen any more. (A warning is printed if ELOCKED is detected though.) fproc[].fp_task (indicating which driver the process is suspended on) became an int. PM and FS now get endpoint numbers of initial boot processes from the kernel. These happen to be the same as the old proc numbers, to let user processes reach them with the old numbers, but FS and PM don't know that. All new processes after INIT, even after the generation number wraps around, get endpoint numbers with generation 1 and higher, so the first instances of the boot processes are the only processes ever to have endpoint numbers in the old proc number range. More return code checks of sys_* functions have been added. IS has become endpoint-aware. Ditched the 'text' and 'data' fields in the kernel dump (which show locations, not sizes, so aren't terribly useful) in favour of the endpoint number. Proc number is still visible. Some other dumps (e.g. dmap, rs) show endpoint numbers now too which got the formatting changed. PM reading segments using rw_seg() has changed - it uses other fields in the message now instead of encoding the segment and process number and fd in the fd field. For that it uses _read_pm() and _write_pm() which to _taskcall()s directly in pm/misc.c. PM now sys_exit()s itself on panic(), instead of sys_abort(). RS also talks in endpoints instead of process numbers.
2006-03-03 11:20:58 +01:00
sys_newmap(rmp->mp_endpoint, rmp->mp_seg);
free_mem(old_base, size);
rmp->mp_flags |= ONSWAP;
outswap = rmp; /* next time start here */
return(TRUE);
} while (rmp != outswap);
return(FALSE); /* no candidate found */
}
#endif /* SWAP */