I/O

We close this chapter with a discussion of I/O in Limbo. Although I/O is not strictly part of Limbo, it is an important aspect of programming in any language. Moreover, the mechanisms by which Inferno's I/O system connects to Limbo is characteristic of the system and a convenient framework on which to hang some informative examples.

Every Limbo program begins execution with three file descriptors available, conventionally called standard input, standard output, and standard error output. Many simple programs just read standard input, print results on standard output, and report errors to standard error. For instance, the Sys function print writes directly to standard output, a property we have exploited silently up to now.

At the lower levels of the system, file descriptors are identified by numbers: 0 for standard input, 1 for standard output, and 2 for standard error. This will all sound familiar to readers with Unix experience. However, the similarity to Unix is not complete, because the I/O primitives in Limbo do not use these integer file descriptors. Instead, they use a reference to an ADT from the Sys module, called FD. For example, to print formatted text other than to standard output, one must acquire an FD reference to the appropriate file descriptor, and pass that to Sys->fprint.

The Sys module contains a function, fildes, that takes an integer file descriptor and creates a reference to a Sys->FD. Programs that wish to report errors on standard error therefore often contain code like this:

	init(nil: ref Draw->Context, argl: list of string)
	{
		sys = load Sys Sys->PATH;
	
		stderr := sys->fildes(2);
		...
		if(problem)
			sys->fprint(stderr, "error: %s\n", msg);
	}

Sys->FDs are used by all the I/O routines. Sys->open prepares a named file for I/O; Sys->create creates a file or truncates it if it already exists; both return a ref Sys->FD. Sys->read copies bytes from an open file into memory, while Sys->write copies bytes from memory to a file. Here, for example, is the complete source to the Inferno cat command, analogous to the Unix cat command, which copies the contents of files named as arguments to the standard output. If no files are named, it copies its standard input:

	implement Cat;
	
	include "sys.m";
	include "draw.m";
	
	Cat: module
	{
		init: fn(ctxt: ref Draw->Context, argl: list of string);
	};
	
	sys: Sys;
	stderr, stdout: ref Sys->FD;
	
	init(nil: ref Draw->Context, argl: list of string)
	{
		sys = load Sys Sys->PATH;
	
		stdout = sys->fildes(1);
		stderr = sys->fildes(2);
	
		argl = tl argl;
		if(argl == nil)
			argl = "-" :: nil;
		while(argl != nil){
			cat(hd argl);
			argl = tl argl;
		}
	}
	
	cat(file: string)
	{
		fd: ref Sys->FD;
		buf := array[8192] of byte;
	
		if(file == "-")
			fd = sys->fildes(0);
		else{
			fd = sys->open(file, sys->OREAD);
			if(fd == nil){
				sys->fprint(stderr, "cat: %s: %r\n", file);
				return;
			}
		}
		n: int;
		for(;;){
			n = sys->read(fd, buf, len buf);
			if(n <= 0)
				break;
			if(sys->write(stdout, buf, n) < n){
				sys->fprint(stderr, "cat: write error: %r\n");
				return;
			}
		}
		if(n < 0){
			sys->fprint(stderr, "cat: read error: %r\n");
			return;
		}
	}

The program begins with the usual invocations, after which it declares globals stderr and stdout, which are initialized early in init. The argument processing loop uses the convention that the file name "-" represents the otherwise unnamable standard input. The function cat then opens the named file to acquire fd, a ref Sys->FD suitable for reading.

If the file cannot be opened, an error is reported. The print format %r takes no arguments; it represents the diagnostic string returned by the operating system to describe the most recently occurring error. It can often help the user understand what went wrong. For example, if the file did not exist, the call to fprint will produce a message something like

	cat: filename: no such file

because the failing open call would have set the `error string' to "no such file". The %r format should always be used when reporting errors from the system.

The loop in cat reads data from the file and writes it to standard output. At the beginning of the function, the array buf is created with 8192 bytes, a conventional but arbitrary size. The call to sys->read takes a ref Sys->FD (fd), an array to fill (buf), and the maximum number of bytes to read (len buf). The return value is the number of bytes transferred to the array. If it is positive, that many bytes can then be written out; if it is zero or negative, the loop terminates. After the loop, we check the terminating byte count. If it is zero, the loop encountered a normal `end of file' condition; if it is negative, some error occurred and we use %r to report what.

The cat routine demonstrates one very important property of I/O in Limbo: the file descriptor fd is never explicitly `closed' by the program as it would be in most systems. Instead, when the function returns, the variable is no longer referenced and the system collects it as `garbage'. When this happens, special processing occurs: the type Sys->FD is known to the system, and when a variable of that type is collected, the associated file descriptor is closed automatically. In other words, file descriptors are garbage collected. What if we wanted to close one explicitly? All we need to do is to remove the last reference to the FD, which we can do like this:

	fd = nil;

Assigning nil to a reference variable removes the reference previously stored there. If that is the last reference to the data, the garbage collector will scavenge the no-longer-referenced data and perform any associated cleanup. Moreover, this cleanup happens the instant the last reference is removed. Unlike some garbage collection algorithms that look for unreferenced variables after the fact, the Limbo collector maintains a count of how many references are outstanding for each variable and cleans up as soon as the count goes to zero.

This automatic collection is central to Inferno and a great convenience; Limbo programmers depend on it implicitly. Other system types that are collected automatically include windows, character fonts, network connections, and so on.

previous next

© Rob Pike and Howard Trickey 1997. All rights reserved.