<<

Low-Level I/O The low-level view in is that a file is just a sequence of bytes. Unix puts no other structure on a file. Even devices are viewed as files. We need functions to manipulate files that way.

To open a file: int open (char name[], int access, int mode); The return value is an index for that file in an array of information; this is usually called a “file descriptor”, often put in a variable “fd”. A negative value indicates an error. The name is the name. The access argument is a bitwise OR of the values:

O RDONLY only want to read the file O WRONLY only want to write the file O RDWR want to do both read and write O APPEND O CREAT O EXCL

To close a file: int close (int fd); The return value is either 0 for success or -1 for failure.

To get data from a file: int read (int fd, void *buf, int size); The return value is the actual number of bytes read. The argument buf is a pointer to a location to put the bytes; often, but not necessarily, an array of unsigned char. The argument size is the maximum number of bytes you can handle.

To put data into a file: int write (int fd, void *buf, int size); The return value is the actual number of bytes written. The argument buf is a pointer to a location to get the bytes; often, but not necessarily, an array of unsigned char. The argument size is the number of bytes you have to write.

It makes sense to be able to do random access on a file. This is common in database software; it is also common in code generation in a compiler. The system maintains a (internal) value for is the position which the next operation will occur.

To set the read/write position: int lseek (int fd, int offset, int whence); The argument ‘offset’ is the number of bytes to move (this can be negative). The argument ‘whence’ indicates where the offset is measured from; it takes the values:

SEEK SET measure the offset from the start of the file SEEK CUR measure the offset from the current position

SEEK END measure the offset from the current end of the file

The return value is the new offset from the start of the file. It is not legal to move before the start of the file; it IS legal (and useful) to move beyond the end of the file. Writing there can create file holes; reading (not previously written) byte reads byte of value 0. (This functionality also can be done with high-level IO; the functions are int ftell ( *f); and int fseek (FILE *f, int offset, int whence);) Interprocess Communication

We have seen the pipe symbol used on the command line to indicate that the output of one command is to be the input to another command. This is the idea for ‘Inter-Process Communication’: 2 programs communicating through what looks like a queue, a FirstIn- FirstOut [fifo] object. There are 2 ways to do this.

Simple Pipes This is a way to set up this communication between related processes. This is the shell’s pipe symbol; the 2 commands are related because they were both started by the shell (and the shell set this up). This pipe is set up in the common parent, and is passes to the children.

To set up a simple pipe: int pipe (int fd[2]); The return value is success/failure. The function returns 2 fd’s in the array (call by reference; the call uses the name of the array). It is always true that data written to fd[1] can be read by fd[0]. On many (but not necessarily all) systems it also happens that data written to fd[0] is available by reading fd[1]. Notice that we need low-level IO. Also notice that seeking makes no sense.

Named Pipes Here we set up a file system entry to be the pipe. The entry now has a name, so any of the usual I/O methods can access the pipe. This is a persistent object; it continues even after the creating process exits.

To set up a named pipe: int mkfifo (char *name, int mode); The return value is success/failure. The mode is the desired permission (thence modified by .