Application Interface

Galley provides users with a relatively simple interface.

Metadata Operations

File operations

int gfs_create_file(char *name, int *IOP_list, int num_iops)
Create a new file named name. The application has the option of specifying the number of IOPs it wishes the file to be created on, and even which IOPs. This call implicitly creates a subfile on each IOP.

int gfs_open_file(char *name)
Open a file and return either an open file ID or an error code.

int gfs_close_file(int file_id)
Close a file.

int gfs_delete_file(char *name)
Delete the file name. This implicitly deletes all of the files subfiles and their forks.

Fork operations

int gfs_create_fork(int file_id, int subfile, char *name)
Create a new fork named name in subfile subfile of the previously opened file file.

int gfs_all_create(int file_id, char *name)
Create a fork named name in each subfile of the open file file_id.

int gfs_open_fork(int file_id, int subfile, char *name)
Open the fork name in subfile subfile of the previously opened file file. Return either an open fork ID or an error code.

int gfs_all_open(int file_id, int *fork_ids, char *name)
Open the fork name in each subfile of the previously opened file file. The array, fork_ids, is filled with fork IDs. This call returns the number of forks opened or an error code.

int gfs_extend_fork(int fork_id, ulong_t size)
Extend fork fork_id to be size bytes long. Explictly extending a file increases the likelihood that a file will have good disk locality.

int gfs_close_fork(int fork_id)
Close a fork.

int gfs_all_close(int file_id, int *fork_ids)
Close all the forks in a file. There is an assumption that the array of fork IDs was returned by a gfs_all_open() call, but it is not required.

int gfs_delete_fork(int file_id, int subfile, char *name)
Delete the fork name in subfile subfile of the open file file_id.

int gfs_all_delete(int file_id, char *name)
Delete the fork name in each subfile of the file file_id.

Data Operations

All data operations take a fork ID and a local buffer, and return the amount of data actually transferred. Only the read operations are shown.

Simple operations

int gfs_read(int fork_id, char *buf, ulong_t offset, ulong_t size)
This call is similar to the standard, Unix-style read(), reading a contiguous range of size bytes, and storing them in memory at buf. Unlike Unix, however, Galley does not maintain an implicit file pointer, marking the location of the previous access. As a result, the application must explicitly indicate the starting offset of the request in the fork.

int gfs_read_block(int fork_id, char *buf, int block)
Read a single block of data from a fork. This call was provided to simplify the implementation of 'I/O optimal' algorithms.

Batch Operations

These operations allow applications to issue requests for many pieces of data at a time, giving Galley enough information to deliver higher performance than is possible when each piece is requested individually (eg, with a gfs_read()). Specifically, Galley is able to schedule disk accesses intelligently, reducing the amount of time that is wasted seeking from one portion of the disk to another. Galley is also able to reduce the number of messages that are exchanged between the CP and IOP. For example, rather than sending 512 messages, each with 64 bytes of data, Galley can pack those pieces into a single 32768 byte message.
int gfs_read_strided(int fork_id, char *buf, ulong_t offset, ulong_t size, int file_stride, int mem_stride, int quant)
Beginning at offset, Galley will read quant records of size bytes. The offset of each record is file_stride bytes greater than the previous record's offset. The records will be stored in memory at buf, with mem_stride bytes between each record.

If file_stride is equal to size, then data will be read contiguously from disk (as with the gfs_read() call), but will be scattered in memory. On the other hand, if mem_stride is equal to size, then data will be gathered from disk, but stored contiguously in memory. Of course, the more adventurous programmer may scatter and gather in the same operation. Note also that either file_stride or mem_stride may be negative.

int gfs_read_nested(int fork_id, char *buf, ulong_t offset, ulong_t size, struct gfs_stride *vector, int levels)
This interface allows applications to make nested-strided requests, such as those seen here. The vector is a pointer to an array of (file_stride, mem_stride, quantity) triples listed from the innermost level of nesting to the outermost. The number of levels of nesting is indicated by levels. As with the gfs_strided() call, appropriate choices of file_stride and mem_stride allow the application a wide array of scatter/gather possibilities.

int gfs_read_listio(int fork_id, char *buf, struct gfs_list *list, int quant)
This interface allows the application to combine a number of simple read requests into a single request. list points to a vector of (file_offset, memory_offset, size) triples, and quant indicates how many triples are in the vector. Unlike the previous interfaces, the listio interface does not require that the requests exhibit any sort of regularity. Although this interface does not reduce the amount of work performed by the application (it must still calculate every offset explicitly), it does allow Galley to perform the same optimizations as other batched requests.

Non-blocking operations

Every data transfer operation has a non-blocking counterpart. The syntax is nearly identical, with the addition of a gfs_handle as the first parameter of each call. For example, a gfs_read(fork_id, buf, offset, size) becomes gfs_nb_read(handle, fork_id, buf, offset, size). A non-blocking call cannot return the amount of data transferred, since that information is not available at the time the call is made. So, non-blocking calls simply return an error code if one or more parameters to the call are invalid.

There are a number of extra calls required to support non-blocking I/O.

gfs_handle gfs_new_handle()
Generate a new handle, used in subsequent non-blocking calls. Handles may only be associated with a single outstanding call at a time, but they may be reused for multiple noncontemporaneous requests.

int gfs_test(gfs_handle handle)
Test to see if the non-blocking operation associated with handle has completed.

int gfs_wait(gfs_handle h)
Wait for the non-blocking operation associated with handle to complete. Return the result of the data transfer operation (ie, the amount of data transferred). The calling process must wait() for a non-blocking operation to complete before the handle may be reused.

int gfs_free_handle(gfs_handle h)
Release the handle and its internal resources.

Nils A. Nieuwejaar
nils@cs.dartmouth.edu
Last modified Friday, May 30, 1996