diff options
Diffstat (limited to 'sys/fio/doc/fio.hlp')
-rw-r--r-- | sys/fio/doc/fio.hlp | 1912 |
1 files changed, 1912 insertions, 0 deletions
diff --git a/sys/fio/doc/fio.hlp b/sys/fio/doc/fio.hlp new file mode 100644 index 00000000..1f87049f --- /dev/null +++ b/sys/fio/doc/fio.hlp @@ -0,0 +1,1912 @@ + +.help fio Jan83 "File i/o Design Rev.5" +.tp 30 +.sh +STRUCTURE OF THE BASIC FILE I/O PROCEDURES + + The high level FIO input procedures are GETC, GETLINE, and READ. +These procedures read directly out of the "current buffer". When the +buffer is exhausted, FILBUF is called to refill the buffer. The action +taken by FILBUF depends on whether the file contains text or binary data, +but does not depend on the characteristics of the device on which the +file is resident. The output procedures are similar to the input +procedures, except that FLSBUF is called to flush the buffer when it fills. + + + + +.ks +.nf + getc getline read + + + + filbuf + + + text files binary files + + zgettx fmkbfs ffault + + + + Structure of the Input Procedures +.fi +.ke + + + + + +.ks +.nf + putc putline write + + + + + flsbuf + + + text files binary files + + + zputtx fmkbfs ffault + + + + Structure of the Output Procedures +.fi +.ke + + +The "file fault" procedure (FFAULT) is called by both FILBUF and FLSBUF +for binary files, when the referenced data lies outside the range of +the current buffer. + + + +.ks +.nf + ffault + + + + + ffilbf frelnk fflsbf + + + + + fwatio fbseek + + + + aread await aseek/anote awrite + + + + zaread zawait zseek/znote zawrite + + + + FIO Structure for Accessing Binary Files +.fi +.ke + + +In the above structure chart, the "z" routines at the lowest level +are system and device dependent, and are actually part of the system +interface, rather than FIO. A separate set of z-routines is required +for each device serviced by FIO (regular binary files, the CL interface, +pipes, magtapes, memory, etc.). + +All of the system and device dependence of FIO is concentrated +into the z-routines. Only the routines AREAD, AWRITE, and AWAIT know +that more than one type of binary device is serviced by FIO. Furthermore, +FIO maintains a device table containing the entry point addresses of +the z-routines for each device. This provides a clean interface to the +device dependent routines, and makes it possible to add new devices +without editing the source for FIO. In fact, it is possible to interface +new devices to FIO dynamically, at run time. + + +.tp 10 +.sh +SEMICODE FOR THE BASIC FILE I/O PROCEDURES + + The procedures GETC and PUTC read and write character data, a single +character at a time. Since these procedures may be called once for each +character in a file, they must be as efficient (ergo, simple) as feasible. +These machine code for these routines should be hand optimized if much +text processing (i.e. compilations) is anticipated. + + + +.nf +.tp 5 +int procedure getc (fd, ch) # get character + +begin + if (iop < bufptr || iop >= itop) # buffer exhausted? + switch (filbuf(fd)) { + case EOF: + return (EOF) + case ERR: + take error action + } + + ch = Mem[iop] + iop = iop + 1 + + return (ch) +end + + + +.tp 5 +procedure putc (fd, ch) # put character + +begin + if (iop < bufptr || iop >= otop) { # buffer full? + if (flsbuf (fd) == ERR) + take error action + } + + Mem[iop] = ch + iop = iop + 1 + + if (ch == newline) { # end of line? + if (flush on newline is enabled for this file) + if (flsbuf (fd) == ERR) + take error action + } +end +.fi + + +Characters and strings (and even binary data) may be "pushed back" into +the input stream. UNGETC pushes a single character. Subsequent calls +to GETC, GETLINE, READ, etc. will read out the characters in the order +in which they were pushed (first in, first out). When all of the +pushback data has been read, reading resumes at the preceeding file +position, which may either be in one of the primary buffers, or an +earlier state in the pushback buffer. + +UNGETS differs from UNGETC in that it pushes back whole strings, +in a last in, first out fashion. UNGETS is used to implement recursive +macro expansions. The amount of recursion permitted may be specified +after the file is opened, and before any data is pushed back. Recursion +is limited by the size of the input pointer stack, and pushback capacity +by the size of the pushback buffer. + + +.tp 5 +.nf +procedure ungetc (fd, ch) # push back a character + +begin + if (iop < bufptr || iop >= otop) { + if (no pushback buffer) + create pushback buffer + else + error: "pushback buffer overflow" + + stack old iop, itop + + set iop to point at beginning of the pushback buffer, + set itop to iop, otop to top of pushback buffer. + } + + Mem[iop] = ch + iop = iop + 1 + itop = itop + 1 +end + + + +.tp 5 +procedure ungets (fd, str) # recursively push back a string + +begin + if (iop < bufptr || iop >= otop) { + if (no pushback buffer) { + create pushback buffer + setup iop, buftop for pushback buffer + } else + error: "pushback buffer overflow" + } + + stack old iop, itop + copy string to Mem[iop], advance iop + itop = iop +end +.fi + + + +Calls to GETLINE may be intermixed with calls to GETC, READ, and so on. +If, however, only GETLINE is used to access a file, and the associated +file is a text file, a file buffer will never need to be created (the +data will be placed directly in the user buffer instead). +If a buffer has been created and is not yet empty, GETLINE will read the +remainder of the current line from that buffer, before again calling FILBUF. + +The newline character is returned as part of the line. The maximum size +of a line (size of a line buffer) is set at compile time by the system +wide constant SZ_LINE. The constant SZ_LINE includes space for the newline +character, but not for the EOS marker (character array dimensions never +include space for the EOS, because the preprocessor automatically allows an +extra character for the EOS when dimensioning the array for Fortran). +.nf + + +.tp 5 +int procedure getline (fd, linebuf) # get a line from file + +begin + op = 1 + if (buffer is empty and file type is TEXT_FILE) { + # call ZGETTX to copy line directly into user linebuf + zgettx (channel(fd), linebuf, status) + + } else { + while (op <= SZ_LINE) { + if (iop < bufptr || iop >= itop) { + status = filbuf (fd) + if (status == ERR || status == EOF) + break + } + + linebuf[op] = Mem[iop] + iop = iop + 1 + op = op + 1 + + if (the character was newline) + break + } + linebuf[op] = EOS + } + + if (status == ERR) + take error action + else if (op == 1) + return (EOF) + else + return (op - 1) # number of chars +end + + + + +.tp 5 +procedure putline (fd, linebuf) # put a line to file + +begin + for (i=1; linebuf[i] != EOS; i=i+1) { + if (iop < bufptr || iop >= otop) + if (flsbuf (fd) == ERR) + take error action + } + + Mem[iop] = linebuf[i] + iop = iop + 1 + + if (the character is newline) { + if (flush on newline is enabled) + if (flsbuf (fd) == ERR) + take error action + } + } +end + + + +.fi +The READ procedure reads a maximum of MAXCHARS characters from the file +FD into the user supplied buffer BUFFER. In the case of block structured +devices, READ will continue to read blocks from the file until the output +buffer has filled. In the case of record structured devices (i.e., terminals, +text files, pipes) READ will read at most one record, after exhausting the +contents of the file buffer. + + + +.tp 5 +.nf +int procedure read (fd, buffer, maxchars) + +begin + check that fd is a valid file opened for reading + nchars = 0 + + while (nchars <= maxchars) { + if (iop < bufptr || iop >= itop) { + switch (filbuf(fd)) { + case EOF: + break + case ERR: + take error action + default: + # don't loop if record structured device or EOF + if (nchars read != buffer size) + maxchars = min (maxchars, nchars + nchars read) + } + } + chunk = min (maxchars - nchar, itop - iop) + if (chunk <= 0) + break + else { + amovc (Memc[iop], buffer[nchars+1], chunk) + iop = iop + chunk + nchars = nchars + chunk + } + } + + if (nchars == 0) + return (EOF) + else + return (nchars) +end + + + + +.tp 5 +procedure write (fd, buffer, maxchars) + +begin + check that fd is a valid file opened for writing + nchars = 0 + + while (nchars <= maxchars) { + if (iop < bufptr || iop >= otop) { + if (flsbuf (fd) == ERR) + take error action + } + chunk = min (maxchars - nchar, otop - iop) + if (chunk <= 0) + break + else { + amovc (buffer[nchars+1], Mem[iop], chunk) + iop = iop + chunk + nchars = nchars + chunk + } + } +end + + + + +.tp 5 +int procedure filbuf (fd) + +begin + verify fd: file open with read permission + + if (iop points into pushback buffer) { + pop state off pushback stack + return (itop - bufptr) + # eventually end up back in a real file buffer + } else if (no buffers) { + call fmkbfs to allocate buffer space for the file + # fmkbfs must adjust iop to reflect current file position + } + + if (TEXT_FILE) + zgettx (fd, file_buffer, nchars) + else + nchars = ffault (fd, logical_offset_in_file) + + iop = bufptr + itop = max (bufptr, bufptr + nchars) + otop = bufptr + + return (nchars) +end + + + + +.tp 5 +int procedure flsbuf (fd) + +begin + verify fd: file open with write permission + if (no buffers) + call fmkbfs to allocate buffer space + + if (otop = bufptr) { + set otop to top of buffer + status = OK + } else if (TEXT_FILE) { + zputtx (channel[fd], file_buffer, status) + reset iop to start of buffer + } else { + status = ffault (fd, logical_offset) + } + + return (status) +end +.fi + + +.sh +Buffer Management for Binary Files + + FIO maintains a "current buffer" for each file. A "file pointer" +is also maintained for each file. The file pointer is the character offset +within the file at which the next i/o transfer will occur. When the file +pointer no longer points into the current buffer, a "file fault" occurs. +The file pointer is modified when, and only when, an i/o transfer or seek +occurs. + +All i/o to binary files is routed through FFAULT. FILBUF and FLSBUF handle +i/o to text files directly. + +FFAULT makes a binary file appear to be a contiguous array (stream) of +characters, regardless of the device on which the file is resident, and +regardless of the block size. Image i/o and structure i/o depend on the +buffer management capabilities of FFAULT for efficient i/o. + +FFAULT must be able to deal with variable block size devices. The block +size is a run time variable, which is device dependent. +Magtapes and Mem files, for example, have a block size of one char, +whereas most disks have 256 char blocks (assuming two machine bytes per char). + +Image i/o requires that the number and size of the buffers for a file +be variable, and that asynchronous i/o be possible. The size of a +buffer, and the size of the data segment to be read in (normally one +row in the case of two dimensional imagefiles) need not be the same. + +Structure or virtual i/o is based on a global pool of buffers, shared +amongst all the files currently mapped for virtual i/o. Each buffer +in the pool is always linked into the list for the global pool, and is +also linked into the local list for a file, when containing data from +that file. New buffers are allocated from the tail of the global list. + +The virtual i/o primitives interface to file i/o via READ and WRITE +requests on a mapped file. FFAULT is required to manage the global pool +properly when faulting on a mapped file. The number and size of the +buffers in the global pool are run time variables. + +FFAULT calculates the file offset of the new buffer implied by the offset +argument (note that offset may be BOF or EOF as well as a real offset). +No actual i/o takes place if the data is already buffered. + + + +.tp 5 +.nf +int procedure ffault (fd, char_offset) + +fd: file descriptor number +char_offset: desired char offset in file + +begin + calculate buffer_offset (modulus block size) + if (i/o in progress on file fd) + wait for completion (awatio) + + if (buffer is already in local pool) + relink buffer at head of list (frelnk) + else { + if (buffer has been written into) + flush to file (fflsbf) + relink next buffer at head of lists (frelnk) + set buffer offset for new buffer + fill buffer from file (ffilbf) + } + + if (file is being accessed sequentially) + initiate write behind or read ahead + + set iop corresponding to desired char_offset + return (status: OK, ERR, or EOF) +end +.fi + + +.sh +Verification of the File Fault Procedure + + The database managed by FFAULT consists of the local and global +buffer lists, and the file descriptor structure. The major types of +file access are (1) sequential read, (2) write at EOF, (3) random +access, and (4) sequential write not at EOF. A mode change may occur +at any time. In what follows, we follow the logic of FFAULT through +for these four modes of access, to verify that FFAULT works properly +in each case. + +.tp 4 +.ls 4 Case 1: Sequential Read + +FFAULT will detect the sequential nature of the read requests, and will +begin reading ahead asychronously. No writing occurs, since the buffer +is never written into. If a buffer were to be written into, the subsequent +write i/o operation would cause read ahead to be interrupted for a time +(random mode would be asserted temporarily). + +.ks +.nf + normally, read ahead will be in progress + wait for i/o + buffer is now in pool + relink buffer at head of lists + initiate i/o on next available buffer + + when EOF is detected, buffer is zeroed, EOF is returned +.fi +.ke +.le + +.tp 4 +.ls Case 2: Sequential Write at EOF + +When writing at EOF, FFAULT will detect the fact that the writes are +occurring sequentially, and will start flushing the newly filled buffers +asynchronously. Read ahead does not occur, since the file is positioned +at EOF. + +.ks +.nf + normally, write behind will be in progress + wait for i/o + get next buffer (will not need to be flushed, due to + automatic write behind) + relink buffer at head of lists + fill buffer (no actual file access when at EOF) + initiate write behind of most recent buffer +.fi +.ke +.le + +.tp 4 +.ls Case 3: Random Access + +Old buffer is left in pool. No i/o is done on the old buffer, regardless +of whether the old buffer has been written into or not (unless there is only +one buffer in the pool). The buffer pool serves as a cache, with the buffers +linked in order of most recent access. Read ahead and write behind do not +occur as long as the pattern of access remains random. + +.ks +.nf + no i/o in progress + buffer not in pool + take buffer from tail of list + relink buffer at head of lists + if (buffer needs to be flushed) + flush it, wait for completion + fill buffer +.fi +.ke +.le + +.tp 4 +.ls Case 4: Sequential Write not at EOF + +This mode differs from write at EOF in that read and write i/o operations +are interspersed. Since only one i/o operation can be in effect on a +given file at one time, we cannot both read ahead and write behind. +Write behind will occur, but reading will not be asynchrounous. + +.ks +.nf + wait for i/o + buffer not in pool + take buffer from tail of list + relink buffer at head of lists + buffer will not need to be flushed, due to write behind + fill buffer, wait for completion + initiate write behind of most recent buffer +.fi +.ke +.le + + + + +.fi +In certain circumstances, such as when IMIO overwrites a line of an +image, where each line is known to be aligned on a block boundary, +the "fill buffer" operation can be omitted (since it is guaranteed +that the entire contents of the buffer will be overwritten before the +buffer is flushed). The fill buffer operation is disabled via an FSET +option. Both access modes 3 and 4 are affected, yielding a factor +of two reduction in the number of i/o transfers. + + + + +.tp 5 +.nf +procedure ffilbf (fd, bufdes) + +fd: file descriptor number +bufdes: buffer descriptor + +begin + if (at EOF) + return + else { + if (io in progress on file fd) + call fwatio to wait for completion of transfer + fbseek (fd, bufdes) + aread (fd, Memc[bufptr], buffer_size) + + set i/o mode word in buffer descriptor + set pointer to active buffer in file descriptor + } +end + + + +.fi +The FFLSBF routine is called by FFAULT to actually flush a buffer to +the file. Note that if the buffer is at the end of the file, and the +buffer is only partially full, a partially full block will be written. +If partial file blocks are not permitted by the underlying system, +the z-routine must compensate. + + + +.tp 6 +.nf +procedure fflsbf (fd, bufdes) + +fd: file descriptor number +bufdes: buffer descriptor + +begin + if (no write permission on file) + take error action + if (io in progress on file fd) + call fwatio to wait for completion of transfer + + nchars = max (iop, itop) - bufptr + fbseek (fd, bufdes) + awrite (fd, Memc[bufptr], nchars) + + set i/o mode word in buffer descriptor + set pointer to active buffer in file descriptor +end + + + + +.tp 5 +procedure fwatio (fd) + +begin + if (i/o mode == NULL) + return + nchars = await (fd) + + if (nchars == ERR) + set ERROR bit in status word + else { + # set i/o pointers in buffer descriptor + if (i/o mode == READ_IN_PROGRESS) + itop = bufptr + nchars + else + # don't change itop, data still valid + otop = bufptr + clear i/o mode word in buffer descriptor + clear pointer to active buffer in file descriptor + } +end + + + + +.tp 5 +procedure fbseek (fd, bufdes) + +begin + if (current_offset != buffer_offset) + aseek (fd, buffer_offset) +end + + + + +.fi +SEEK is used to move the file pointer (offset in a file at which the +next data transfer will occur). With text files, one can only seek +to the start of a line, the position of which must have been determined +by a prior call to NOTE. For binary files, SEEK merely sets the logical +offset within the file. This will usually cause a file fault when the +next i/o transfer occurs. An actual physical seek does not occur until +the fault occurs. + +The logical offset is the character offset in the file at which the next +i/o transfer will occur. In general, there is no simple relationship +between the logical offset and the actual physical offset in the file. +The physical offset is the file offset at which the next AREAD or AWRITE +transfer will occur, and is maintained by those routines and by the system. +The logical offset may be set to any character in a file. The physical +offset is always a multiple of the device block size. + +The logical offset is defined at all times by the offset of the current +buffer (buf_offset), and by the offset within the buffer (iop-bufptr). +The logical offset may take on the special values BOF and EOF. +Since the offset of the first character in a file is one (1), +and BOF and EOF are zero or negative, the special offsets are unambiguous. + +.rj (logical offset) + new iop = offset - buf_offset + bufptr + +A logical seek on a binary file is effected merely by setting the in-buffer +pointer IOP according to the relation shown above. A macro LSEEK (fd, offset) +is defined to perform a logical seek with inline code. +.nf + + + +.tp 5 +procedure seek (fd, offset) + +begin + verify that fd is a legal file descriptor of an open file + clear any pushback + + # make newly written data readable + itop = max (itop, iop) + + if (TEXT_FILE) { + if (buffer has been written into) + call zputtx to flush buffer to file + reset iop to beginning of buffer + if (offset is not equal to offset of buffer) + call zsektx routine to seek on text file + } else + lseek (fd, offset) +end + + + + +.tp 5 +long procedure note (fd) # note file position for later seek + +begin + verify that fd is a legal file descriptor of an open file + + if (TEXT_FILE) { + call znottx to get offset into text file + if (a buffer is in use) + save offset of buffer in buffer descriptor + return (offset) + } else + return (logical offset) +end + + + + +.tp 5 +procedure flush (fd) + +begin + verify fd: file open with write permission + + if (TEXT_FILE) + if (buffer has been written into) { + call zputtx to write out buffer + reset buffer pointers + } + else + for (each buffer in local pool) + if (buffer has been written into) + call fflsbf to flush buffer +end + + + + +.fi +The asynchronous i/o primitives ZAREAD and ZAWRIT must enforce device block +boundaries. Thus, if maxchars is not an integral multiple of the block size, +the file pointer will nonetheless be advanced to the next block boundary. +Some files (such as Mem files and magtapes) may have a block size of one char. + +Note that memory may be accessed as a "file". This facility is most often +used by the formatted i/o routines, to decode and encode character data in +strings. On a virtual memory machine, an entire binary file could be mapped +into memory, then opened with MEMOPEN as a memory resident file (this would +in effect replaces the FFAULT file faults by hardware page faults). + +The calling program is required to call AWAIT after an AREAD or AWRITE call to +a file, before issuing the next i/o request to that file. Failure to do so +causes an error action to be taken. This is done to ensure that the success +or failure of the i/o transfer (the status returned by AWAIT) is checked by +the calling program. + +The z-routines ZCALL2 and ZCALL3 are machine dependent routines which +call the procedure whose entry point address is given as the first argument. +The numeric suffix N means that the procedure given as the first argument is +to be called with N arguments, the values of which make up the remaining +arguments to ZCALL. The additional machine dependence of this routine +is thought to be more than justified by the clean, flexible interface +which it provides between FIO and the various supported devices. +.nf + + + +.tp 5 +procedure aread (fd, buffer, maxchars) + +begin + check that fd is a valid file opened for reading + if (i/o is already in progress on file fd) + error: "i/o already in progress" + set read_in_progress word in file descriptor + + zcall3 (zaread[fd], channel[fd], buffer, maxchars) +end + + + +.fi +Note that FIO, when it seeks to the end of a file for a buffered binary +write, actually seeks to the nearest block boundary preceeding the physical +EOF which is an integral multiple of the file buffer size. When the file +buffer fills, it is flushed out, OVERWRITING THE EOF. This may pose problems +for the implementor of the ZAWRITE routine on some systems. + + + +.tp 5 +.nf +procedure awrite (fd, buffer, maxchars) + +begin + check that fd is a valid file opened for writing + if (i/o is already in progress on file fd) + error: "i/o already in progress" + set write_in_progress in i/o mode word in file descriptor + + zcall3 (zawrite[fd], channel[fd], buffer, maxchars) +end + + + + +.tp 5 +int procedure await (fd) + +begin + verify thaf fd is a legal file descriptor of an open file + + if (bad error code in file descriptor) + set status to ERR + else if (no io in progress on file fd) + return (0) + else + zcall2 (zawait[fd], channel[fd], status) + + switch (status) { + case ERR: + set error code in file descriptor + case EOF: + set EOF flag + default: + increment file position counter by N file blocks + set nchars_last_transfer in file descriptor + } + + clear io_in_progress word in file descriptor + return (status) +end + + + + +.tp 5 +procedure aseek (fd, offset) + +begin + switch (offset) { + case BOF: + char_offset = 1 + clear at EOF flag + case EOF: + if (already at EOF) + return + else { + zcall2 (zaseek[fd], channel[fd], EOF) + current_offset = anote (fd) + char_offset = current_offset + set at EOF flag + } + default: + char_offset = offset + clear at EOF flag + } + + # can seek only to the beginning of a device block + block_offset = char_offset - mod (char_offset-1, block_size) + + zcall2 (zaseek[fd], channel[fd], block_offset) + if (anote(fd) != block_offset) + take error action +end + + + +.tp 5 +long procedure anote (fd) + +begin + zcall2 (zanote[fd], channel[fd], current_offset) + return (current_offset) +end +.fi + + + +.sh +Z-ROUTINES REQUIRED TO INTERFACE TO A BINARY DEVICE + + The interface between FIO and a binary device is defined by a set of +six so called z-routines. These routines may be as device and system +dependent as necessary, provided the standard calling sequences and semantics +are implemented. + +The following z-routines are required for each device serviced by FIO. +Since only the entry point addresses are given to FIO, the actual names +are arbitrary, but must be distinct to avoid collisions. The names shown +are reserved. + +.ks +.nf + zaread (channel, buffer, maxchars) + zawrit (channel, buffer, maxchars) + zawait (channel, nchars/EOF/ERR) + zaseek (channel, char_offset/BOF/EOF) + zanote (channel, char_offset) + zblksz (channel, device_block_size_in_chars) +.fi +.ke + +The exact specifications of these routines will be detailed in the system +interface documentation. + + +The following binary devices are fully supported by the program interface: + + +.ks +.nf + device type initialization + + regular random access binary files OPEN + the CL interface (STDIN,STDOUT,...) task startup + pipes CONNECT + memory MEMOPEN + magnetic tapes MTOPEN + graphics devices GOPEN +.fi +.ke + + +A new device may be interfaced to FIO at run time with the procedure FIODEV. +Repetitive calls to FIODEV for the same device are harmless and are +ignored. The maximum number of devices that may be interfaced to FIO is set +when FIO is compiled. An error action will occur if this number is exceedd. + + fiodev (zaread, zawrit, zawait, zaseek, zanote, zblksz) + +The purpose of FIODEV is to make the entry points of the z-routines for the +new device known to FIO. The device table is indexed by the entry point +address of the ZAREAD procedure, which must therefore be distinct for each +device. + +A default device is associated with a file when the file is opened. +To specify a device other than the default device requires a call to FSET, +passing the entry point address of the ZAREAD procedure for the device. +The device must have been installed with the FIODEV call by the time FSET +is called to associate the device with a particular file, or an error action +will result. + + +.sh +SEMICODE FOR THE FIO INITIALIZATION AND CONTROL PROCEDURES + + Before any i/o can be done on a file, the file must be opened. The +standard OPEN procedure may be used to access ordinary files containing either +text or binary data. To access a file on one of the special devices, a special +open procedure must be used (MEMOPEN, MTOPEN, ..). + +All file open procedures are alike in that they call the FIO routine +FGETFD to allocate and initialize (with defaults) a file descriptor. +Assorted calls to FSET and possibly FIODEV may optionally follow, +if the default file parameters are not applicable to the device in question. + + + + +.ks +.nf + open close + + + + + fgetfd frtnfd flush + + + + + zmapfn zopen malloc mfree zclose + + + + Structure of the Initialization Procedures +.fi +.ke + + + + +.tp 5 +.nf +int procedure open (file, mode, type) + +file: file name (EOS terminated character string) +mode: type of access permission desired +type: file type (text or binary) + +begin + # allocate and initialize file descriptor + fd = fgetfd (file, mode, type) + if (fd == ERR) { + set error code in file descriptor + return (ERR) + } + + # map virtual file name to OS file name + zmapfn (file, osfname, SZ_OSFNAME) + + switch (type) { # open file + case TEXT_FILE: + zopntx (osfname, mode, channel[fd]) + case BINARY_FILE: + zopenb (osfname, mode, channel[fd]) + default: + set error code in file descriptor + channel[fd] = ERR + } + + if (channel[fd] == ERR) { + frtnfd (fd) # return file descriptor + return (ERR) + } else + return (fd) +end + + + +.fi +To conserve resources (file descriptors, buffer space) a file should be +closed when no longer needed. Any file buffers that may have been +created and written into will be flushed before being deallocated. + +CLOSE ignores any attempts to close STDIN or CLIN. Attempts to close +STDOUT, STDERR, or CLOUT cause the respective output byte stream to be +flushed, but are otherwise ignored. An error action results if one +attempts to close a file which is not open, or if one attempts to close +a file which was not opened with OPEN. +.nf + + + +.tp 5 +procedure close (fd) # close an opened file + +begin + if (fd == STDIN || fd = CLIN) { + return + } else if (fd == STDOUT || fd == STDERR || fd == CLOUT) { + flush (fd) + return + } else if (fd is not a valid file descriptor of an open file) { + take error action + } else if (file device is not a standard one) + take error action + + flush (fd) + zclose (channel[fd]) + frtnfd (fd) +end + + + + + +.tp 5 +int procedure fgetfd (file, mode, type) # get file descriptor + +file: file name (EOS terminated character string) +mode: type of access permission desired +type: file type (text or binary) + +begin + # find an unused file descriptor slot + for (fd=FIRST_FD; fd <= LAST_FD; fd=fd+1) + if (fdes[fd] == NULL) + break + if (fd > LAST_FD) + return (ERR) + + # allocate memory for file descriptor proper + fdes[fd] = malloc (sizeof_struct_fiodes, TY_CHAR) + if (fdes[fd] == NULL) + return (ERR) + + initialize fields of file descriptor to default values + return (fd) +end + + + + +.tp 5 +procedure frtnfd (fd) # return file descriptor and buffers + +begin + if (fdes[fd] == NULL) + return + + # deallocate file buffers, if any + + if (file takes its buffers from the global pool) { + if (any buffers were actually ever allocated) + decrement reference count of files using global pool + for (each buffer in the local list) { + unlink buffer from the local list + if (global pool reference count is zero) { + unlink buffer from the global list + return buffer space to the system + } else + link at tail of the global list + } + } else + for (each buffer in the local list) { + unlink buffer from the local list + return buffer space to the system + } + + if (push back buffer exists) + return push-back buffer + + mfree (fdes[fd], TY_CHAR) + fdes[fd] = NULL +end +.fi + + +.sh +SETTING AND INSPECTING THE FIO CONTROL PARAMETERS + + Any file may be accessed after specifying only the file name, access +mode, and file type parameters in the OPEN call. +Occasionally, however, it is desirable to change the default file control +parameters, to optimize i/o to the file. The IMIO and VSIO interfaces, +for example, control the size, number, and ownership of the FIO file buffers. + + +.ks +.nf + fset (fd, parameter, value) + value = fget (fd, parameter) +.fi +.ke + + +The FSET procedure is used to set the FIO parameters for a particular file, +while FGET is used to inspect the values of these parameters. The special +value DEFAULT will restore the default value of the indicated parameter. +The following parameters are defined: + +.ls 4 +.ls 15 ADVICE +This parameter is used to advise FIO on the type of access expected for +the file. The legal values are SEQUENTIAL and RANDOM. Given such advice, +FIO will set up the buffers for the file using system dependent defaults +for the buffer types, sizes, and numbers. ADVICE is more system independent +than explicit calls to NBUFFERS, BUF_SIZE, and so on. +.le +.ls ASYNC_IO +If enabled (value = YES), and there are two or more buffers in the pool, +FIO will employ read ahead and early write behind when a sequential pattern +of i/o is detected. Specifying NO for this parameter guarantees that +buffered data will be retained until reuse of a buffer is forced by a fault. +Note that even if ASYNC_IO is enabled, read ahead and early write behind +are ONLY used while the pattern of i/o remains sequential. +.le +.ls BUF_SIZE +The size of a file buffer, in chars. The actual size of the buffer +created and used by FIO depends on the device block size and may be larger +than BUF_SIZE, but will not be any smaller. +.le +.ls BUF_TYPE +This parameter may have one of two values, LOCAL or GLOBAL, specifying whether +a local pool of buffers is to be created, or whether buffers are to be drawn +from the global pool. +.le +.ls FIO_DEVICE +The value given must be the entry point address of the ZAREAD procedure +for the desired device. The device must have been installed in the FIO +device table by a prior call to FIODEV. +.le +.ls FLUSH_NL +If enabled, the output buffer will be flushed after every line of output text, +rather than when the buffer fills or when a flush is otherwise forced. +Useful when the output file is an interactive terminal. +.le +.ls GBUF_SIZE +The size of a buffer in the global pool, in chars. +The FD parameter is ignored. +.le +.ls GNBUFFERS +The number of file buffers in the global pool. +The FD parameter is ignored. +.le +.ls NBUFFERS +The number of file buffers in the local pool. +.le +.ls PBB_SIZE +The size of the combined push back buffer and push back control stack area, +in chars. +.le +.le + + +The parameters controlling the size and number of the various buffers +(ADVICE, NBUFFERS, BUF_SIZE, BUF_TYPE, PBB_SIZE, GNBUFFERS, GBUF_SIZE) must +be set before i/o causes the affected buffers to be created using the default +number and size parameters. Thereafter, FSET calls to change these parameters +will be ignored. The values of the other parameters may be changed at any +time, with the new values taking effect immediately. + +.sh +Example 1: File access is expected to be highly random. + + The most system independent approach is to call FSET to set the +ADVICE parameter to RANDOM. + + +.nf + include <fio.h> + ... + + fd = open (file, READ_WRITE, BINARY_FILE) + if (fd == ERR) + ... + + call fset (fd, ADVICE, RANDOM) +.fi + +.sh +Example 2: High speed sequential access is desired + + In this case, the best approach would again be to call FSET to set ADVICE +to SEQUENTIAL. To demonstrate use of some of the other parameters, we have +taken a different approach here. + + +.nf + fd = open (file, READ_ONLY, BINARY_FILE) + if (fd == ERR) + ... + + call fset (fd, NBUFFERS, 2) + call fset (fd, BUF_SIZE, SZ_BLOCK * 16) + call fset (fd, ASYNC_IO, YES) +.fi + + +In practice it will rarely be necessary for the user to call FSET, because +the facilities provided by VSIO and IMIO (which do call FSET in the manner +shown) will probably provide the desired i/o capability, without need to +resort to the comparatively low level facilities provided by FIO. +Another reason for NOT calling FSET is that the system provided defaults +may indeed be best for the system on which the software is being run. + +The default values selected for the FIO parameters may be tuned to the +particular system. At one extreme, for example, we might provide a global +pool containing only two buffers, each the size of a single disk block. +By default, all files would share these buffers, and asynchronous i/o +would be disabled. This would be the minimum memory configuration. +At the other extreme, we might allocate two large buffers to each file, +with asynchronous i/o enabled. + + +.sh +DETAILS OF THE FIO DATA STRUCTURES + + By this point we have sufficiently detailed information about the +functioning of FIO to be able to fill in the details of the data +structures. The FIO database consists of the MAXFD file descriptors, +the global buffer pool, the descriptor for the global pool, and the +device table. Each file descriptor controls a local list of buffers, +and possibly a buffer for pushed back data. A buffer descriptor +structure is associated with each file buffer. + + + +.ks +.nf +# Static part of file descriptor structures + +common fiocom { + int gnbufs # size of global pool + int gbufsize # size of global buffer + int gnref # number of files using gpool + struct bufdes *ghead # head of the global list + struct bufdes *gtail # tail of the local list + int ndev # number of devices + int zdev[SZ_DEVTBL] # device table + char *iop[MAXFD] # i/o pointer + char *itop[MAXFD] # itop for current buffer + char *otop[MAXFD] # otop for current buffer + char *bufptr[MAXFD] # pointer to current buffer + long offset[MAXFD] # offset of the current buffer + struct fiodes *fdes[MAXFD] # pointer to rest of fd + char osfname[SZ_OSFNAME] # buffer for OS file names +} +.fi +.ke + + +.ks +.nf +# Template for dynamically allocated part of file descriptor + +struct fiodes { + char fname[SZ_FNAME] # file name string + int fmode # mode of access + int ftype # type of file + int fchan # OS file number (channel) + int fdev # index into device table + int bufsize # size of a file buffer + int pbbsize # size of pushback buffer + int nbufs # number of local buffers + int fflags # flag bits + int nchars # size of last transfer + int iomode # set if i/o in progress + int errcode # error code + long fpos # actual file position + char *pbbp # pointer to pushback buffer + char *pbsp # pushback stack pointer + char *pbsp0 # pointer to stack elem 0 + struct bufdes *iobuf # buffer i/o being done on + struct bufdes *lhead # head of local list + struct bufdes *ltail # tail of local list +} +.fi +.ke + + +.nf +# flags (saved in fdes[fd].fflags) + + F_ASYNC # enable async_io + F_EOF # true if at EOF + F_ERR # set when error occurs + F_FLUSHNL # flush after newline + F_GLOBAL # local or global buffers + F_RANDOM # optimize for rand. access + F_READ # read perm on file + F_SEQUENTIAL # optimize for seq. access + F_WRITE # write perm on file +.fi + + + +.ks +.nf +# Buffer descriptor structure. + +struct bufdes { + int b_fd # fd to which buffer belongs + int b_iomode # set when i/o in progress + int b_bufsize # size of buffer, chars + long b_offset # offset of buffer in file + char *b_itop # saved itop + char *b_otop # saved otop + char *b_bufptr # pointer to start of buffer + struct bufdes *luplnk # next buffer up, local list + struct bufdes *ldnlnk # next buffer down, local list + struct bufdes *guplnk # next buffer up, global list + struct bufdes *gdnlnk # next buffer down, global list +} +.fi +.ke + + +.sh +SEMICODE FOR THE FIO DATABASE ACCESS PROCEDURES + + Routines are required to allocate and deallocate buffers, +and to link and unlink buffers from the buffer lists. Now that the +data structures have been more clearly defined, we shall go into a +little more detail in the semicode. + + +.ks +.nf + fmkbfs + + + + fmklst + + + + flnkhd fmkbuf flnktl + + + + malloc + + + + Structure of the Buffer Allocation Procedures +.fi +.ke + + + +The main buffer creation procedure, FMKBFS, is called by either +FILBUF or FLSBUF when i/o is first done on a file. FMKLST allocates +a set of buffers and links them into a doubly linked list. FLNKHD +links a buffer at the head of a list, while FLNKTL links a buffer at +the tail of a list. FMKBUF calls MALLOC to allocate memory for a file +buffer, and initializes the descriptor for the buffer. + + + + +.tp 5 +.nf +procedure fmkbfs (fd) + +fd: file descriptor number +fp: pointer to file descriptor +bp: pointer to buffer descriptor + +begin + if (use global pool) { + if (no buffers in global pool yet) { + gnbufs = fmklst (NULL, gnbufs, gbufsize, GLOBAL) + if (gnbufs <= 0) # can't make buffers + take error action + } + gnref = gnref + 1 + + } else { # create local buffers + adjust bufsize to be an integral number of device blocks + fp = fdes[fd] + fp.nbufs = fmklst (fd, fp.nbufs, bufsize, LOCAL) + + if (fp.nbufs == 0) # must be at least one + take error action + } +end + + + +.fi +Unlink a buffer from whatever lists it is on, relink it at head of the +local list, and also at head of global list if a mapped file. Called +by FFAULT. +.nf + + +.tp 5 +procedure frelnk (fd, bp) + +fd: file descriptor number +bp: pointer to buffer descriptor + +begin + # relink buffer at head of the local list for file fd + call funlnk (bp, LOCAL) + call flnkhd (fd, bp, LOCAL) + + # relink at head of global list, if buffer in global pool + if (buffer is linked into the global pool) { + call funlnk (bp, GLOBAL) + call flnkhd (fd, bp, GLOBAL) + } +end + + + + +.tp 5 +int procedure fmklst (fd, nbufs, bufsize, list) # make list + +list: either global or local +bufdes: pointer to buffer descriptor + +begin + for (nb=0; nb <= nbufs; nb=nb+1) { + bufdes = fmkbuf (fd, bufsize) + if (bufdes == NULL) + break + else if (nb == 1) + flnkhd (fd, bufdes, list) + flnktl (fd, bufdes, list) + } + return (nb) +end + + + + +.tp 5 +int procedure fmkbuf (fd, bufsize) # make a buffer + +begin + assert (bufsize > 0 && mod (bufsize, block_size) == 0) + + sizeof_buffer = sizeof (struct bufdes) + bufsize + bufdes_pointer = malloc (sizeof_buffer, TY_CHAR) + if (bufdes_pointer == NULL) + return (NULL) + else { + initialize buffer descriptor + return (bufdes_pointer) + } +end + + + + +.tp 5 +procedure flnkhd (fd, bp, list) # link buf at head of list + +fd: file descriptor number +bp: pointer to buffer descriptor +list: global or local +fp: pointer to file descriptor + +begin + assert (bp != NULL) + assert (list == LOCAL || list == GLOBAL) + + switch (list) { + case GLOBAL: + if (buffer not already linked at head of list) { + bp.gdnlnk = ghead + ghead.guplnk = bp + ghead = bp + } + case LOCAL: + fp = fdes[fd] + if (buffer not already linked at head of list) { + bp.fd = fd + bp.ldnlnk = fp.lhead + if (fp.lhead != NULL) + fp.lhead.luplnk = bp + fp.lhead = bp + } + } +end + + + + +.tp 5 +procedure flnktl (fd, bp, list) # link buf at tail of list + +fd: file descriptor number +bp: pointer to buffer descriptor +list: global or local +fp: pointer to file descriptor + +begin + assert (bp != NULL) + assert (list == LOCAL || list == GLOBAL) + + switch (list) { + case GLOBAL: + if (buffer not already linked at tail of list) { + bp.guplnk = gtail + gtail.gdnlnk = bp + gtail = bp + } + case LOCAL: + fp = fdes[fd] + if (buffer not already linked at tail of list) { + bp.fd = fd + bp.luplnk = fp.ltail + if (fp.ltail != NULL) + fp.ltail.ldnlnk = bp + fp.ltail = bp + } + } +end + + + + +.tp 5 +procedure flnkto (fd, bp, to) # link buf bp after to + +bp: pointer to descriptor of buffer to be linked +to: pointer to descriptor of buffer to be linked to + +begin + bp.ldnlnk = to.ldnlnk + bp.luplnk = to + to.ldnlnk = bp + if (bp.ldnlnk == NULL) + fdes[fd].ltail = bp # new tail of list + else + bp.ldnlnk.luplnk = bp +end + + + + +.tp 5 +procedure funlnk (bp, list) # unlink from list + +bp: pointer to buffer descriptor +list: global or local +fp: pointer to file descriptor + +begin + switch (list) { + + case GLOBAL: + if (buffer is at head of the global list) + ghead = bp.gdnlnk + if (buffer is at tail of the global list) + gtail = bp.guplnk + if (bp.guplnk != NULL) + bp.guplnk.gdnlnk = bp.gdnlnk + if (bp.gdnlnk != NULL) + bp.gdnlnk.guplnk = bp.guplnk + + case LOCAL: + fp = fdes[bp.fd] + if (buffer is at head of the local list) + fp.lhead = bp.ldnlnk + if (buffer is at tail of the local list) + fp.ltail = bp.luplnk + if (bp.luplnk != NULL) + bp.luplnk.ldnlnk = bp.ldnlnk + if (bp.ldnlnk != NULL) + bp.ldnlnk.luplnk = bp.luplnk + } +end +.fi + + +.sh +SEMICODE FOR FFAULT, AGAIN + + The file fault procedure lies at the heart of FIO. Now that the +data structures, initialization procedures, and linked list operators are +clearer, it is time to go back and fill in some of the details in FFAULT. + + + +.tp 5 +.nf +int procedure ffault (fd, char_offset) + +fd: file descriptor number +char_offset: desired char offset in file +bp: pointer to a buffer descriptor +fp: pointer to the file descriptor + +begin + # calculate buffer_offset (modulus file buffer size) + buffer_offset = char_offset - mod(char_offset, buffer_size) + 1 + + # compute pointers to fd structure, current buffer + fp = fdes[fd] + bp = fp.lhead + + # update i/o pointers in the buffer descriptor + # note writes may have pushed iop beyond original itop + itop[fd] = max(itop[fd], iop[fd]) + if (bp != NULL) { + bp.b_itop = itop[fd] + bp.b_otop = otop[fd] + } + + # if buffer is found in local pool, relink at head of list. + if (ffndbf (fd, buffer_offset, bp) == YES) { + frelnk (fd, bp) + itop[fd] = bp.b_itop + otop[fd] = bp.b_otop + + # this next section of code is invoked whenever a fault + # occurs which requires an actual i/o transfer. + + } else { + if (bp.otop != bp.b_bufptr) # buffer dirty? + fflsbf (fd, bp) # flush buffer + + frelnk (fd, bp) # relink at head + bp.b_offset = buffer_offset + + if (F_READ flag is set) { + ffilbf (fd, bp) # fill buffer + fwatio (fd) + } else { + bp.b_itop = bp.b_bufptr + bp.b_otop = bp.b_bufptr + } + + # if asynchronous i/o is enabled (only if two or more + # buffers) initiate write behind or read ahead, if + # fwatio has detected a sequential pattern of i/o. + + if (ASYNC_IO enabled) + switch (io_pattern) { + case WSEQ: # write behind + bufp = bp.ldnlnk + if (bufp != NULL) + if (bufp.b_otop != bufp.b_bufptr) + fflsbf (fd, bufp) + case RSEQ: # read ahead + new_buffer_offset = buffer_offset + buffer_size + if (ffndbf (fd, new_buffer_offset, bufp) == YES) + # skip read ahead, buffer already in pool + else if (bufp.b_otop == bufp.b_bufptr) { + if (bufp.luplnk != fp.lhead) { + funlnk (bufp, LOCAL) + flnkto (bp, bufp, fp.lhead) + } + if (buffer in global pool) { + funlnk (bufp, GLOBAL) + flnkhd (bufp, GLOBAL) + } + bufp.b_offset = new_buffer_offset + ffilbf (fd, bufp) + } + } + } + + bufptr[fd] = bp.b_bufptr # set i/o pointers + offset[fd] = buffer_offset + lseek (fd, char_offset) + + if (fp.status == ERR) # check for ERR,EOF + return (ERR) + else if (iop[fd] == itop[fd]) + return (EOF) + else + return (itop[fd] - iop[fd]) # return nchars +end + + + + +# Search for a file buffer. If found, return buffer pointer in BP, +# otherwise allocate a buffer from the tail of either the global or +# local list. + + +.tp 5 +int procedure ffndbf (fd, buffer_offset, bp) + +begin + # desired buffer may be on the way; wait and see + if (read in progress on file fd) + fwatio (fd) + + # search local pool for the buffer + for (bp = fp.lhead; bp != NULL; bp = bp.ldnlnk) + if (bp.b_offset == buffer_offset) + break + + # if buffer already in pool, return buffer pointer, + # otherwise use oldest buffer in appropriate list. + + if (bp != NULL) # buffer found in pool + return (YES) + else { # use buffer at tail of list + if (this file uses global pool) { + bp = gtail + if (io in progress on this buffer) + fwatio (bp.fd) + } else + bp = fp.ltail + return (NO) + } + +end +.fi + + +.sh +SUMMARY OF THE FIO/OS INTERFACE (MACHINE DEPENDENT PRIMITIVES) + + FIO depends on a number of machine dependent primitives. Many of these +have been introduced in the semicode. Other primitives are not involved in +i/o, and hence have not appeared thus far in the discussion. Primitives are +required to map virtual file names into OS file names. + +The goal in designing the FIO/OS interface was to make the primitives as +"primitive" as feasible, rather than to minimize the number of primitives. +These primitives should be easy to implement on almost any modern minicomputer. +The ideal target OS will provide asynchronous, random access i/o, +logical name facilities, multiple directories per task, multitasking and +intertask communication facilities, and dynamic memory allocation/deallocation +facilities. + + +.nf +Text Files + + zopntx (osfn, access_mode; chan) + + zgettx (chan, line_buf, maxchars; nchars) + zputtx (chan, line_buf, nchars; nchars) + zflstx (chan) + zfsttx (chan, what; status_info) + zclstx (chan) + zsektx (chan, znotln_offset; status) + znottx (chan; file_offset) + + +Binary File Initialization (one set per device) + + zopnbf (osfn, access_mode; chan) + zfaloc (osfn, nchars; chan) + + +Binary File I/O primitives (one set per device) + + zaread (chan, buffer, maxchars, file_offset) + zawrit (chan, buffer, maxchars, file_offset) + zawait (chan; status) + zfsttb (chan, what; status_info) + zclsbf (chan; status) + + standard devices: regular files, inter-task pipes (CL,GIO), + memory, magnetic tapes. + + +Virtual File Name Mapping + + zmapfn (vfn, osfn, maxch) + zabsfn (vfn, osfn, maxch) + + +File Manipulation, Status, File Protection, Temporary Files + + zacces (osfn, mode, type; status) + zfdele (osfn; status) + zrenam (from_osfn, to_osfn; status) + zfprot (osfn) + zmktmp (root, temp_file_osfn) + + +Other Dependencies (also used outside of FIO) + + zcallN (entry_point, arg1, ..., argN) + pntr = malloc (nelements, data_type) + pntr = calloc (nelements, data_type) + mfree (pntr, data_type) + int = and (int, int) + int = or (int, int) + int = loc (reference) +.fi + + +The STATUS returned by the Z-routines may be ERR or a meaningful number, +such as the channel number or number of characters read or written. +EOF is signified at this level by a return value of zero for the number +of characters read (only ZGETTX and ZAREAD read from a file). There is +no provision for special error codes or messages at the Z-routine level. |