Circles represent operating-system manager software; non circles represent resources, either hardware (such as the disk) or software (exited processes). Arrows between managers indicate interactions via subroutine calls; the caller is pointing and the called is being pointed at. The labels associated with each arrow gives the system event that triggers the subroutine call. For example the arrow labeled "Exit" from the Process Manager to the Disk Manager indicates that the Process Manager makes a subroutine call to the Disk Manager (via a routine provided by the Disk Manager) whenever a process exits the system.
The disk manager is responsible for loading programs. The interrupt handler is responsible for initializing the other managers on system boot-up; these interactions are not shown in the diagram.
interrupt_handler()
function, passing along the raised interrupt's code.
<interrupt handlers>= (U->) [D->] void interrupt_handler(interrupt_types i) { switch (i) { case disk_i: disk::ihandler(); break; case reboot_i: reboot_ih(); break; case system_call_i: syscall_ih(); break; default: panic("Unrecognized interrupt %d in interrupt_handler()", i); } if (process::done() and disk::idle()) process::halt(); }
Definesinterrupt_handler
(links are to index).
The system-call interrupt handler dispatches on the system-call code stored in register 0.
<interrupt handlers>+= (U->) [<-D->] static void syscall_ih(void) { word w; fetch_mem(0, w, syscall_ih); switch (w) { case system_call::exit: process::exit(); break; case system_call::fork: fetch_mem(1, w, syscall_ih); disk::load(w); break; case system_call::exec: fetch_mem(1, w, syscall_ih); process::exec(w); break; default: store_mem(0, status::bad_system_call, syscall_ih); } }
Definessyscall_ih
(links are to index).
On boot-up, initialize the process, storage, and disk resource managers. When initialized, the disk resource manager starts reading in the first program.
<interrupt handlers>+= (U->) [<-D] void reboot_ih(void) { process::init(); storage::init(usr_base, usr_size/words_per_disk_block); disk::init(); }
Definesreboot_ih
(links are to index).
<interrupts.cc
>=
#include "os.h"
#include "disk.h"
#include "storage.h"
#include "process.h"
// Deja vu c++ style.
static void reboot_ih(void);
static void syscall_ih(void);
unsigned read_clock(void) {
word c;
fetch_mem(clock_register, c, read_clock);
return c;
}
<interrupt handlers>
Definesinterrupts.cc
(links are to index).
<process interface declarations>= (U->) [D->] typedef unsigned id;
Definesprocess::id
(links are to index).
process::add()
creates a new process from the program loaded into User
Space at the given location and return the new process's id; the load request
came from the process with the given id. process::exit()
zombifies the
running process, removing it from the ready queue and freeing up its resources.
process::join()
blocks the current process until the process with the given
id exits, at which point the blocked process rejoins the ready queue.
<process interface declarations>+= (U->) [<-D->] id add(const storage::chunk &, id = 0); void exit(void); void exec(unsigned);
process::init()
initializes the process manager; process::done()
returns true if and only if the process manager has no more work to do.
process::halt()
halts the simulation.
<process interface declarations>+= (U->) [<-D->] void init(void); bool done(void); void halt(void);
process::block()
suspends the currently running process and schedules a
replacement, returning the newly blocked process's id. process::ready()
makes the blocked process identified by pid
ready to run.
<process interface declarations>+= (U->) [<-D->] id block(void); void ready(id pid);
Some of the other managers need to be able to store values into the saved
register context for a particular process; process::put_register()
makes
that possible.
<process interface declarations>+= (U->) [<-D] void put_register(id pid, unsigned r, word v);
A process may be blocked waiting for a non-cpu resource, ready to use the cpu, or executing in the cpu. It may also have exited but not yet been joined with.
<process data structures>= (U->) [D->] enum process_status { proc_ready = 0x01, proc_executing = 0x02, proc_blocked = 0x04, proc_exited = 0x08 };
Definesprocess_status
(links are to index).
A process-table entry contains all the information needed to manage a process, including the process's id, its status, its saved register context, and information about the User Space storage allocated to it. The process table entry also contains code to allocate process ids and save and restore process register contexts.
<process data structures>+= (U->) [<-D->] struct process_table_entry { static process::id next_id; process_table_entry() : id(next_id++), joiner_waiting(false) { } const process::id id; process_status status; word registers[next_register]; word exit_value; storage::chunk sc; bool joiner_waiting; process::id joiner_pid; void save_state(void) { for (address a = 0; a < next_register; a++) fetch_mem(a, registers[a], save_state); } void restore_state(void) const { for (address a = 0; a < next_register; a++) store_mem(a, registers[a], restore_state); } }; unsigned process_table_entry::next_id = 1000;
Definesprocess_table_entry
,restore_state
,save_state
(links are to index).
Process-table entries are stored in a list, with special iterators indicating the entries for the currently running process and the idle process (which may, on occasion, be the same process).
<process data structures>+= (U->) [<-D] typedef std::list<process_table_entry> process_table; typedef process_table::iterator ptab_iter; static process_table ptab; static ptab_iter running_process; static ptab_iter idle_process;
Definesidle_process
,process_table
,ptab
,ptab_iter
,running_process
(links are to index).
Loading a process into the cpu involves changing the process's state to executing and restoring the saved register state.
<process procedure definitions>= (U->) [D->] static void load_cpu(ptab_iter & pi) { pi->status = proc_executing; pi->restore_state(); running_process = pi; debugp(dbp_execute, dbpe_format "runs.\n", read_clock(), running_process->id); }
Definesload_cpu
(links are to index).
Return a count of the number of process-table entries with a status matching
any of those in the given status set. This should properly be done with
std::count_if()
, but that breaks CC (WorkShop 6 update 2 ver. 5.3
2001/05/15).
<process procedure definitions>+= (U->) [<-D->] static unsigned status_count(process_status status_flags) { unsigned i = 0; for (ptab_iter ptabi = ptab.begin(); ptabi != ptab.end(); ptabi++) if (ptabi->status & status_flags) i++; return i; }
Definesstatus_count
(links are to index).
Return an iterator to the process-table entry with id equal to pid
; die
with an error if there's no such entry.
<process procedure definitions>+= (U->) [<-D->] static bool matching_pid(const process_table_entry pte, process::id pid) { return pte.id == pid; } ptab_iter find_proc(process::id pid) { const ptab_iter & e = ptab.end(); ptab_iter i = find_if(ptab.begin(), e, bind2nd(ptr_fun(matching_pid), pid)); if (i == e) oops("Can't find the process with process-id %d", pid); return i; }
Definesfind_proc
(links are to index).
Return true if a process having the given id exists; false otherwise.
<process procedure definitions>+= (U->) [<-D->] bool exists_proc(process::id pid) { const ptab_iter & e = ptab.end(); return find_if(ptab.begin(), e, bind2nd(ptr_fun(matching_pid), pid)) != e; }
Definesexists_proc
(links are to index).
A couple of helper predicates to find process-table entries of ready or running processes.
<process procedure definitions>+= (U->) [<-D->] static bool readyp(process_table_entry & pte) { return pte.status == proc_ready; } static bool runningp(process_table_entry & pte) { return pte.status == proc_executing; }
Definesreadyp
,runningp
(links are to index).
End the executing process: release the storage held by the process and wake up the disk in case it's waiting for more storage to load a new process. Remove the executing process form the ready queue and schedule a new executing process.
<process procedure definitions>+= (U->) [<-D->] static void end_process(void) { storage::release(running_process->sc); disk::poke(); ptab.erase(running_process); reschedule(); }
Definesend_process
(links are to index).
If the idle process is running, check the process table for a ready process and run it if found. If no process is running (because the running process exited) find a ready process and run it (this should always be possible because the idle process is always around and always ready).
<process procedure definitions>+= (U->) [<-D->] static void reschedule(void) { ptab_iter end = ptab.end(); ptab_iter next_process; if (idle_process->status == proc_executing) { <re-schedule the idle process, if possible> } else { <schedule a running process, if necessary> } }
Definesreschedule
(links are to index).
The idle process is currently running. If there's some other process ready to run, find it and run it.
<re-schedule the idle process, if possible>= (<-U) next_process = find_if(ptab.begin(), end, readyp); if (next_process != end) { idle_process->status = proc_ready; load_cpu(next_process); }
If there is no running process (which there may not be if reschedule()
was
called after blocking the current process), look for and run a ready process
(if there is a running process, then everything's hunky dory). Because the
idle process is always ready and always available, something terrible happened
if there's no ready process to run.
<schedule a running process, if necessary>= (<-U) next_process = find_if(ptab.begin(), end, runningp); if (next_process == end) { next_process = find_if(ptab.begin(), end, readyp); if (next_process == end) panic("can't find a ready process in process::reschedule()"); load_cpu(next_process); }
When changing a process's status to ready, be sure to re-check the process-table to see if the new process can run immediately.
<process procedure definitions>+= (U->) [<-D] static void ready_proc(ptab_iter ptei) { ptei->status = proc_ready; reschedule(); }
Definesready_proc
(links are to index).
Create a process from the program loaded into allocated space given by sc
;
return the new process's id. If this process is being forked, copy the values
in parent's registers into the children's register set. The new process is
ready to run.
<process interface definitions>= (U->) [D->] id add(const storage::chunk & sc, id parent) { process_table_entry new_pte; const address a = storage::chunk_address(sc); if (parent) { ptab_iter old_ptei = find_proc(parent); for (unsigned i = 0; i < next_register; i++) new_pte.registers[i] = old_ptei->registers[i]; } new_pte.registers[base_register] = a; new_pte.registers[top_register] = a + storage::chunk_size(sc)*words_per_disk_block; new_pte.registers[pc_register] = a; new_pte.registers[ps_register] = 0; new_pte.sc = sc; ptab.push_front(new_pte); ready_proc(ptab.begin()); return new_pte.id; }
Definesprocess::add
(links are to index).
Block the currently running process and start another; return the ejected process's id.
<process interface definitions>+= (U->) [<-D->] id block(void) { running_process->save_state(); running_process->status = proc_blocked; process::id pid = running_process->id; reschedule(); return pid; }
Definesprocess::block
(links are to index).
The process manager's done when the idle process is the only process left to manage. However, that doesn't mean execution is done because the disk may still be at work loading programs.
<process interface definitions>+= (U->) [<-D->] bool done(void) { return status_count(static_cast<process_status>(proc_ready | proc_executing)) < 2; }
Definesprocess::done
(links are to index).
A process exits. The only resource held by an exiting process is its allocated storage, which is returned to the storage manager. If the disk is suspended because there's not enough space to load a program, freeing up this space may enable the disk to continue loading; poke the disk so it can check.
<process interface definitions>+= (U->) [<-D->] void exit(void) { debugp(dbp_execute, "At time %4d, process %2d exits.\n", read_clock(), running_process->id); end_process(); }
Definesprocess::exit
(links are to index).
If a process is waiting to join with the exiting process, wake up and ready the joiner and delete the exiter. Otherwise, zombify the exiting process and keep it around so it can be the target of a later join request.
<handle a join>= if (running_process->joiner_waiting) { const ptab_iter joiner = find_proc(running_process->joiner_pid); joiner->registers[0] = status::ok; joiner->registers[1] = eval; joiner->status = proc_ready; debugp(dbp_forkjoin, "At time %4d, process %2d joins with exited process %2d, passing value %d.\n", read_clock(), running_process->joiner_pid, running_process->id, eval); ptab.erase(running_process); } else { running_process->status = proc_exited; running_process->exit_value = eval; }
The running process wants to replace itself with the process formed from the
program pno
. If there's no such program the running process loses.
Otherwise, load the new process and end the current process.
<process interface definitions>+= (U->) [<-D->] void exec(unsigned pno) { if (!disk::load(pno)) { store_mem(0, status::bad_id, syscall_ih); return; } debugp(dbp_execute, "At time %4d, process %2d execs program %d.\n", read_clock(), running_process->id, pno); end_process(); }
Definesprocess::exec
(links are to index).
Halting the system has pie-like easiness.
<process interface definitions>+= (U->) [<-D->] void halt(void) { store_mem(halt_register, 0, halt); }
Definesprocess::halt
(links are to index).
Process manager initialization defines and runs the idle process, which always occupies the last slot of the process table.
<process interface definitions>+= (U->) [<-D->] void init(void) { process_table_entry idle_pte; idle_pte.status = proc_ready; idle_pte.registers[pc_register] = idle_start; idle_pte.registers[base_register] = idle_start; idle_pte.registers[top_register] = idle_end; idle_pte.registers[ps_register] = 0; idle_process = ptab.insert(ptab.begin(), idle_pte); load_cpu(idle_process); }
Definesprocess::init
(links are to index).
Store the given value in the given register of the given process's saved context.
<process interface definitions>+= (U->) [<-D->] void put_register(id pid, unsigned r, word v) { assert(r < next_register); ptab_iter pti = find_proc(pid); pti->registers[r] = v; }
Definesprocess::put_register
(links are to index).
When changing a process's status to ready, be sure to re-check the process-table to see if the new process can run immediately.
<process interface definitions>+= (U->) [<-D] void ready(id pid) { find_proc(pid)->status = proc_ready; reschedule(); }
Definesprocess::ready
(links are to index).
<process.h
>=
#ifndef _process_h_defined_
#define _process_h_defined_
#include "storage.h"
namespace process {
<process interface declarations>
};
#endif
<process.cc
>=
#include <algorithm>
#include <list>
#include <functional>
#include "process.h"
#include "disk.h"
#include "storage.h"
#include "os.h"
using std::ptr_fun;
using std::bind2nd;
#define dbpe_format "At time %4d, process %2d "
<process data structures>
<process procedure definitions>
namespace process {
<process interface definitions>
}
Definesprocess.cc
,process.h
(links are to index).
init()
initializes the disk manager.
ihandler()
handles disk interrupts.
load()
reads a program from disk and schedules it for execution.
poke()
wakes up the disk manager in case it's been suspended until
more resources (usually User Space) become available.
idle()
returns true if the disk manager has no programs to load.
<disk interface declarations>= (U->) void init(void); void ihandler(void); bool load(unsigned); void poke(void); bool idle(void);
On disk initialization, start a read of the batch-disk index block.
<disk interface definitions>= (U->) [D->] void init(void) { next_block_to_read = 0; next_program_address = usr_base; program_being_read = true; disk_read(disk::init); }
Definesdisk::init
(links are to index).
Once the disk index block lands in User Space, copy the information into
program_index
so the disk manager has easy access to program information
and start loading the first program (if it exists). Invalid program sizes
panic the system; it's hard to know what to do otherwise.
<disk procedure definitions>= (U->) [D->] static void handle_index_block(void) { check_disk_error(handle_index_block); unsigned start = 1; for (unsigned i = 0; i < words_per_disk_block; i++) { word w; fetch_mem(next_program_address + i, w, handle_index_block); if ((w < 0) or (static_cast<unsigned>(w) > usr_size/words_per_disk_block)) panic("invalid program size %d read from batch disk", w); if (w == 0) break; program_index.push_back(program_location(w, start)); start += w; } program_being_read = false; if (!program_index.empty()) queue_request(0, 0); }
Defineshandle_index_block
(links are to index).
Create a process using the i
th program on the batch disk. If the program
doesn't exist, return false; otherwise, queue the load request and return true.
<disk interface definitions>+= (U->) [<-D->] bool load(unsigned i) { if (i >= program_index.size()) return false; queue_request(i, process::block()); return true; }
Definesdisk::load
(links are to index).
If there's no programs to read or a read is in progress, there's no reason to start another read. Otherwise, there's more programs to read and reading isn't in progress, so start another read assuming there's enough User Space for the program.
<disk interface definitions>+= (U->) [<-D->] void poke(void) { if (load_requests.empty() or program_being_read) return; const program_location & pl = program_index[load_requests.front().pno]; program_chunk = storage::get_chunk(pl.size); if (storage::chunk_size(program_chunk) > 0) { program_being_read = true; blocks_to_read = pl.size - 1; next_block_to_read = pl.start + blocks_to_read; next_program_address = storage::chunk_address(program_chunk) + blocks_to_read*words_per_disk_block; disk_read(poke); } }
Definesdisk::poke
(links are to index).
If there's no program-load requests queued and there's no read in progress then the disk manager's idle.
<disk interface definitions>+= (U->) [<-D->] bool idle(void) { return load_requests.empty() and !program_being_read; }
Definesdisk::idle
(links are to index).
A disk interrupt can be caused either by the index block (only on the first read) or a program block (all other reads).
<disk interface definitions>+= (U->) [<-D] void ihandler(void) { static bool first_time = true; if (first_time) { handle_index_block(); first_time = false; } else handle_next_block(); }
Definesdisk::ihandler
(links are to index).
Queue up a request to load program i
make by process pid
. Poke the
disk manager just in case it was sittin' around, waitin' for something to do.
<disk procedure definitions>+= (U->) [<-D->] static void queue_request(unsigned i, process::id parent) { load_requests.push_back(load_request(i, parent)); disk::poke(); }
Definesqueue_request
(links are to index).
Handle a disk-interrupt that's the result of reading a program block. If all the program's blocks have been read in, then the program is ready to run; hand it off to the process manager and go read then next program, if any. Otherwise, schedule a disk-read for the next block in the program.
<disk procedure definitions>+= (U->) [<-D] static void handle_next_block(void) { check_disk_error(handle_next_block); if (blocks_to_read-- == 0) { <ready the new process> } else { next_block_to_read--; next_program_address -= words_per_disk_block; disk_read(handle_next_block); } }
Defineshandle_next_block
(links are to index).
Once the new program's been read into User Space, make it ready to run, unsuspend the requesting process, and pop the request from the queue. If the new process is being forked, store the new process's id in the parent's context and ready the parent.
<ready the new process>= (<-U) assert(!load_requests.empty()); const load_request & req = load_requests.front(); if (req.pid != 0) { process::put_register(req.pid, 0, status::ok); process::put_register(req.pid, 1, process::add(program_chunk, req.pid)); process::ready(req.pid); } else process::add(program_chunk); load_requests.pop_front(); program_being_read = false; disk::poke();
Check for a disk-io error on behalf of _caller
and die if found.
<disk macro definitions>= (U->) [D->] #define check_disk_error(_caller) \ do { \ word w; \ fetch_mem(disk_status_register, w, _caller); \ if (w != status::ok) \ panic("disk read error %d in " #_caller "()", w); \ } while (false)
Definescheck_disk_error
(links are to index).
Issue a disk read on behalf of _caller
.
<disk macro definitions>+= (U->) [<-D] #define disk_read(_caller) \ do { store_mem(disk_block_register, next_block_to_read, _caller); \ store_mem(disk_address_register, next_program_address, _caller); \ store_mem(disk_command_register, device::read, _caller); } while (false)
Definesdisk_read
(links are to index).
A program's location in the batch disk is represented by the disk-block number at which the program starts and the size of the program in disk blocks.
The location information on programs in the batch disk are stored in a vector indexed by the program's index-block index.
<disk manager data structures>= (U->) [D->] struct program_location { unsigned start, size; program_location(unsigned size, unsigned start) : start(start), size(size) { } }; static std::vector<program_location> program_index;
Definesprogram_index
,program_location
(links are to index).
A program-load request contains
pno
- the program-information index for the new process.
pid
- the forking process's (parent's) id. If there is no
parent, pid
is 0.
The program-load requests are queued in load_requests
.
<disk manager data structures>+= (U->) [<-D->] struct load_request { unsigned pno; process::id pid; load_request(unsigned pno, process::id pid) : pno(pno), pid(pid) { } }; static std::deque<load_request> load_requests;
Definesload_request
,load_requests
(links are to index).
If true, a program's being read in;
<disk manager data structures>+= (U->) [<-D->] static bool program_being_read;
Definesprogram_being_read
(links are to index).
The number of blocks to read from the disk.
<disk manager data structures>+= (U->) [<-D->] static unsigned blocks_to_read;
Definesblocks_to_read
(links are to index).
The disk index of the next block to read.
<disk manager data structures>+= (U->) [<-D->] static unsigned next_block_to_read;
Definesnext_block_to_read
(links are to index).
The next disk block read gets stored at the address given by
next_program_address
.
<disk manager data structures>+= (U->) [<-D->] static address next_program_address;
Definesnext_program_address
(links are to index).
The chunk of user-space storage into which the program is being read.
<disk manager data structures>+= (U->) [<-D] storage::chunk program_chunk;
Definesprogram_chunk
(links are to index).
<disk.h
>=
#ifndef _disk_h_defined_
#define _disk_h_defined_
namespace disk {
<disk interface declarations>
};
#endif
<disk.cc
>=
#include <vector>
#include <deque>
#include "os.h"
#include "disk.h"
#include "storage.h"
#include "process.h"
// Deja vu, c++ style
static void queue_request(unsigned, process::id);
<disk manager data structures>
<disk macro definitions>
<disk procedure definitions>
namespace disk {
<disk interface definitions>
};
Definesdisk.cc
,disk.h
(links are to index).
Correspondingly, the data representation for allocated storage is simple too. Each chunk of user space allocated to a program is represented by a pair of numbers giving the lowest user-space address and size of the chunk in disk blocks.
<storage-management interface definitions>= (U->) [D->] typedef std::pair<address, unsigned> chunk; inline address chunk_address(const chunk & sc) { return sc.first; } inline unsigned chunk_size(const chunk & sc) { return sc.second; }
Defineschunk
,chunk_address
,chunk_size
(links are to index).
The storage manage needs to be initialized with both the lowest address and size in disk blocks of the allocatable storage.
<storage-management interface definitions>+= (U->) [<-D->] void init(address base, unsigned size);
Allocate a chunk of User Space containing the given number of blocks.
<storage-management interface definitions>+= (U->) [<-D->] chunk get_chunk(unsigned);
And it should be possible to return an allocated chunk to the free-storage
<storage-management interface definitions>+= (U->) [<-D] void release(const chunk & sc);
Allocatable storage is organized into a sequence of blocks, each the size of a disk block. The whole of allocatable store is represented by a bit map, where bit i represents the availability of storage block i.
base_address
keeps track of the lowest address in allocatable storage.
<allocatable-storage data structures>= (U->) [D->] static std::vector<bool> free_blocks; static address base_address;
Definesbase_address
,free_blocks
(links are to index).
The internal representation of allocated storage is similar to the external representation. The only difference is using an index (into the bit map) instead of an address to indicate the start of the allocation. To avoid confusion between the representations, the internal representation is called an index range, and the external representation is called a storage chunk.
<allocatable-storage data structures>+= (U->) [<-D->] typedef std::pair<unsigned, unsigned> index_range; #define range_start(_r) (_r).first #define range_size(_r) (_r).second
Definesindex_range
,range_size
,range_start
(links are to index).
There needs to be a way of converting between the internal and external representations of allocated storage.
<allocatable-storage data structures>+= (U->) [<-D] static index_range sc2ir(const storage::chunk & sc) { return std::make_pair( (storage::chunk_address(sc) - base_address)/words_per_disk_block, storage::chunk_size(sc)); } static storage::chunk ir2sc(const index_range & ir) { return std::make_pair(base_address + range_start(ir)*words_per_disk_block, range_size(ir)); }
Definesir2sc
,sc2ir
(links are to index).
Return the next range of free blocks at or to the right of the block at start
.
Return a range of zero length if there's no free blocks.
<storage routine definitions>= (U->) [D->] static index_range find_free_range(unsigned size) { for (unsigned i = 0; i < free_blocks.size() - size + 1; i++) if (free_blocks[i]) { bool found = true; for (unsigned j = i + 1; (j < i + size) and found; j++) found = free_blocks[j]; if (found) return std::make_pair(i, size); } return std::make_pair(static_cast<unsigned>(0), static_cast<unsigned>(0)); }
Definesfind_free_range
(links are to index).
Set the free-map bits in the index range ir
to the value bv
. Die if
any of the bits in the range are already equal to bv
.
<storage routine definitions>+= (U->) [<-D] static void set_range(const index_range & ir, bool bv) { const unsigned e = range_start(ir) + range_size(ir); assert(e <= free_blocks.size()); for (unsigned i = range_start(ir); i < e; i++) { assert(free_blocks[i] != bv); free_blocks[i] = bv; } } #define release_range(_ir) set_range(_ir, true) #define allocate_range(_ir) set_range(_ir, false)
Definesallocate_range
,release_range
,set_range
(links are to index).
The storage manager is initialized with the start (lowest address) and size of the storage area to be managed. Initially, all storage is free.
<storage namespace definitions>= (U->) [D->] void init(address start, unsigned size) { assert(size > 0); base_address = start; while (size-- > 0) free_blocks.push_back(true); }
Definesstorage::init
(links are to index).
Allocate and return an s
-block chunk of User Space, or a chunk of 0 size if
there's no such chunk.
<storage namespace definitions>+= (U->) [<-D->] chunk get_chunk(unsigned s) { index_range free_range = find_free_range(s); allocate_range(free_range); return ir2sc(free_range); }
Definesstorage::get_chunk
(links are to index).
Return the allocated storage sc
to the free list.
<storage namespace definitions>+= (U->) [<-D] void release(const chunk & sc) { release_range(sc2ir(sc)); }
Definesstorage::release
(links are to index).
A quick check to make sure the storage-management code is working without problems.
<storage testing code>= (U->) #ifdef TESTING_STORAGE // g++ -DTESTING_STORAGE -g -o testing-storage -ansi -pedantic -Wall -I ../arch storage.cc && ./testing-storage int main() { storage::init(1024, 32); storage::chunk sc = storage::get_chunk(32); assert(storage::chunk_address(sc) == 1024); assert(storage::chunk_size(sc) == 32); storage::chunk sc2 = storage::get_chunk(1); assert(storage::chunk_size(sc2) == 0); storage::release(sc); sc2 = storage::get_chunk(1); assert(storage::chunk_size(sc2) == 1); sc = storage::get_chunk(32); assert(storage::chunk_size(sc) == 0); sc = storage::get_chunk(31); assert(storage::chunk_size(sc) == 31); assert(storage::chunk_address(sc) == 1024 + words_per_disk_block); } #endif
<storage.h
>=
#ifndef _storage_h_defined_
#define _storage_h_defined_
#include <algorithm>
#include "system.h"
namespace storage {
<storage-management interface definitions>
};
#endif
<storage.cc
>=
#include <vector>
#include "storage.h"
#include <cassert>
<allocatable-storage data structures>
<storage routine definitions>
namespace storage {
<storage namespace definitions>
};
<storage testing code>
Definesstorage.cc
,storage.h
(links are to index).
<os.h
>=
#ifndef _os_h_defined_
#define _os_h_defined_
#include <iostream>
#include "mass.h"
#include "utils.h"
#define fetch_mem(_a, _v, _w) \
do if (mem_fetch(_a, _v) != Memory::ok) \
panic(#_a " fetch failed in " #_w "()"); while (false)
#define store_mem(_a, _v, _w) \
do if (mem_store(_a, _v) != Memory::ok) \
panic(#_a " store failed in " #_w "()"); while (false)
#define dbp_execute dbp_f31
#define dbp_forkjoin dbp_f30
extern unsigned read_clock(void);
#endif
Definesdbp_execute
,fetch_mem
,os.h
,store_mem
(links are to index).
disk.cc
>: D1
disk.h
>: D1
interrupts.cc
>: D1
os.h
>: D1
process.cc
>: D1
process.h
>: D1
storage.cc
>: D1
storage.h
>: D1
This page last modified on 11 November 2002.