CSC 357 Lecture Notes Week 4
Unbuffered File I/O UNIX Files and Directories
-
Relevant reading:
-
Stevens chapters 3 and 4.
-
Skim chapter 2.
-
Brief overview of C and UNIX standards (Stevens Chapter 2).
-
There are two levels of standards for the C language and UNIX library functions
that support the language.
-
The ISO C standard defines the language proper, and the C standard library.
-
Appendix A of K&R is the reference manual for the language proper.
-
Appendix B of K&R is a summary of the major library components.
-
The ISO (International Standards Organization) maintains the official standard,
called ISO/IEC 9899:1999, the most recent update to which is November 2004.
-
The IEEE POSIX defines the full library standard for UNIX, UNIX-like, and other
operating systems.
-
The standard is based on UNIX, but any operating system may meet the standard.
-
Systems that do are all POSIX compliant.
-
POSIX includes the ISO standard C library, but not the specification of the
language proper.
-
POSIX is a specification of library functions, not an implementation.
-
There are many implementations of UNIX, many of which are POSIX compliant, or
claim to be.
-
IEEE has an official POSIX certification program to which UNIX implementors can
apply and be certified for.
-
The four implementations of UNIX that Stevens refers to are:
-
Solaris from SUN Microsystems, used on falcon/hornet
-
Linux, the widely-distributed open-source version of UNIX
-
Mac OS X, from Apple
-
FreeBSD, another open source version of UNIX
-
UNIX unbuffered file I/O (Stevens Chapter 3).
-
Most UNIX file I/O can be performed using only five functions -- open,
read, write, lseek, and close.
-
These functions operate on file descriptors, at the UNIX kernel level.
-
They are lower-level functions than the "f" series, like fopen.
-
These lower-level functions are referred to as unbuffered, since the
operating system does not do any behind-the-scenes I/O data buffering.
-
The OS does perform buffering on FILE* streams, but not with files
accessed through lower level file descriptors.
-
Section 5.4 of Stevens talks about buffering in detail
-
File descriptors (Stevens Section 3.2).
-
At the kernel level, all files are referred to by a file descriptor,
which is a non-negative integer.
-
The open function returns a file descriptor.
-
Functions like read and write take file descriptors as
inputs.
-
open (Stevens Section 3.3).
-
Open a file, returning its file descriptor, or -1 if error.
-
Signature:
int open(const char *pathname, int
oflag, ... /* mode_t mode */);
-
pathname is the name of the file to open or create
-
oflag is used to specify options
-
the optional mode is only applicable when a new file is being created
-
Options values are constructed by a bitwise-inclusive-OR of flags from the
following lists, defined in <fcntl.h>.
-
Applications must specify exactly one of the following three values for the
file access mode:
O_RDONLY
| Open for reading only.
|
O_WRONLY
| Open for writing only.
|
O_RDWR
| Open for reading and writing.
|
-
Any combination of the following may be used:
O_APPEND
| Append to end of file on each write.
|
O_CREAT
| Create the file if it does not exist.
|
O_EXCL
| Fail O_CREAT if file exists.
|
O_TRUNC
| Truncate length to 0.
|
O_NOCTTY
| Do not have a terminal device become the controlling terminal.
|
O_NONBLOCK
| Do not block on open or for data to become available
|
-
POSIX synchronization options are:
O_DSYNC
| Wait for write to complete, but not for attribute updates.
|
O_RSYNC
| Have reads wait for pending writes.
|
O_SYNC
| Wait for write to complete, including for attribute updates.
|
-
There are other platform-specific options for such things as symbolic links,
locks, and 64-bit file offsets.
The following example opens a file named "data" for reading and appended
writing:
open("data", O_RDWR | O_APPEND)
-
creat (Stevens Section 3.4).
-
Create a file.
-
It's equivalent to the following call to open, which means that
creat is effectively obsolete:
open(pathname, O_WRONLY | O_CREAT | O_TRUNC,
mode)
-
close (Stevens Section 3.5).
-
Close an open file, returning 0 if OK, -1 if error.
-
Signature:
int close(int filedes);
-
When a process terminates, all open files are closed by the kernel.
-
lseek (Stevens Section 3.6).
-
The lseek function sets the read/write offset of an open file,
returning new offset if OK, -1 if error.
-
All open files have an offset position that defines from what byte a read
starts or to what byte a write starts.
-
The offset is initialized to 0 by open, unless O_APPEND is
specified.
-
Signature:
off_t lseek(int filedes, off_t offset,
int whence);
-
The interpretation of offset is based the value of whence,
which must be one of the following, defined in <unistd.h>:
-
If whence is SEEK_SET, the offset is set to offset bytes from the
beginning of the file.
-
If whence is SEEK_CUR, the offset is set to its current value plus
offset; the offset value can be positive or negative.
-
If whence is SEEK_END, the offset is set to the size of the file plus
offset.
-
The programmer can determine the value of the current offset without changing
it by supplying an offset value of 0, e.g.,
off_t curpos;
curpos = lseek(fd, 0, SEEK_CUR);
-
This can also be used to determine if a file is capable of seeking, which,
e.g., stdin from a terminal is not.
-
See the example on Page 64 of Stevens for further details on testing for the
seekability of a file.
-
When lseek is used to set a file's offset larger than its current
size, the file is said to have "a hole" in it.
-
Operating systems may take advantage of this by allocating fewer file blocks
for files with holes.
-
Unwritten bytes in file holes read back as 0s, whether or not there is actual
disk storage backing the hole.
-
See the example on pp. 65-66 of Stevens for further details.
-
The type off_t allows the implementation of an OS to provide different
size integers for file offsets, and hence the maximum size file that a program
can work with.
-
Most platforms these days support both 32-bit and 64-bit file offsets, the
latter allowing files larger than 2 GB (231-1 bytes).
-
For example, here are the alternative definitions of off_t on falcon
in /usr/include/stdio.h:
#if defined(_LP64) || _FILE_OFFSET_BITS == 32
typedef long off_t;
#else
typedef __longlong_t off_t;
#endif
-
read (Stevens Section 3.7).
-
Read from an open file, returning number of bytes read, 0 if eof, -1 if error
-
Signature:
ssize_t read(int fildes, void *buf,
size_t nbytes);
-
The ssize_t return value is the number of bytes read, 0 on eof.
-
The files is the file to read from, which must be open.
-
The buf is the buffer of at least nbytes into which data are
read.
-
There are several cases in which the number of bytes read is less than
requested, including:
-
If eof is reached during the read, the number of bytes read may be less than
the number requested.
-
When reading from a terminal device, normally only one line at a time is read.
-
When reading from a network, buffering may cause fewer bytes than requested to
be read.
-
When reading from a pipe, only the number of available bytes is read.
-
When reading from a record-oriented device, sometimes only a record at a time
is read.
-
When the read is interrupted by a signal, the read may only be partially
completed.
-
The read operation starts at the current file offset.
-
After a successful or partially successful read, the file offset is incremented
by the number of bytes actually read.
-
The typedefs ssize_t and size_t allow implementations
flexibility in the number of bytes readable and requestable by one call to
read.
-
write (Stevens Section 3.8).
-
Write data to an open file, returning number of bytes written if OK, -1 if
error.
-
Signature:
ssize_t write(int fildes, const void
*buf, size_t nbytes);
-
The write starts at the current file offset of the given filedes,
unless O_APPEND was set on open, in which case the file offset is set
to the end of file before writing starts.
-
After a successful write, the file offset is incremented by the number of bytes
actually written.
-
Typical causes for write failure are a full disk or exceeding the file size
limit for a process; -1 is returned for these or other types of failure.
-
I/O Efficiency (Stevens Section 3.9).
-
This section of Stevens has some interesting data on the effect of the
programmer-selected buffer size on execution time of read and
write functions.
-
We'll discuss this issue further when we compare timing for unbuffered versus
buffered I/O functions, in an upcoming lecture.
-
File sharing (Stevens Section 3.10).
-
Two or more processes 1 can share the same
file.
-
When they do, they have a common pointer to the same physical file data.
-
But the processes have independent copies of the following data:
-
the file descriptor and its flags
-
file status flags
-
current file offset
-
The pictures on pp. 72 and 73 illustrate things well.
-
If file-sharing processes only read the file, there are no problems.
-
However, if processes each try to write to a shared file, they can potentially
interfere with each other's work.
-
This is a classic "readers/writers" situation.
-
Atomic operations (Stevens Section 3.11).
-
The potential problem with multiple writers has to do with the fact that the
operation sequence of lseek followed immediately by write is
not guaranteed to be a single atomic operation.
-
Specifically, a process can seek to a file location, but then be suspended
before it gets a chance to do the write.
-
If during the suspension another process does a seek and write, then the
suspended process may end up writing to some unexpected place in the file.
-
For example, suppose processes A and B have a shared file.
-
Process A does a seek to the end of the file and is then suspended by the
kernel.
-
Process B then does a seek to the end of the file, and then writes 100 bytes.
-
Process A then gets reactivated to do its write, but the point it thinks is at
the end of the file is now 100 bytes in front of the end.
-
To address this problem, there are the functions pwrite and
pread, which perform an uninterruptible pair of operations -- seek
followed by the write or the read.
-
The signatures are:
ssize_t pwrite(int fildes, const void
*buf, size_t nbytes, off_t
offset);
ssize_t pread(int fildes, void *buf,
size_t nbytes, off_t offset);
-
There is also a potential problem with creating a file, when the intent is not
to create it if it already exists. E.g.,
-
Process A checks if a file exists, with the intent not to create it if it does.
-
Process A is suspended, and process B gets control.
-
Process B creates the file that A just checked, and it turns out the file did
not exist before B creates it.
-
Process A gets control back, thinks the file does not exist, and proceeds to
re-create it, potentially interfering with what B did.
-
E.g., there's a problem if B wrote to the file before A got control back, and
then A re-creates it with the truncation option on.
-
In general, the term atomic operation refers to an operation that may
be composed of multiple steps, but the steps are performed in an
uninterruptible sequence.
-
A subset of the steps cannot be performed.
-
Either all steps run to completion, or none of them runs at all.
-
dup and dup2 (Stevens Section 3.12).
-
File descriptors can be duplicated using these functions.
-
The only difference between dup'd descriptors is the value of their
file descriptor flags, of which there is only one defined by POSIX.
-
Duplicated descriptors share the same status flags, current offset,
and file data.
-
We'll discuss the relevance of duplicated descriptors later, when we talk about
the process exec function.
-
fsync
-
UNIX kernels typically use buffer caches to make read/write operations more
efficient.
-
This means that the contents of cache memory and the contents of a file may
differ until the cache is synchronized with the file.
-
For applications that care about this, e.g., database systems, the
fsync function forces the synchronization of the cache and the
associated file.
-
fcntl (Stevens Section 3.14).
-
The fcntl function provides for control of open files.
-
Signature:
int fcntl(int fildes, int cmd, ... /*
arg */ );
-
The cmd is a #defined command value from
<fcntl.h>.
-
The optional arg varies based on the value of cmd.
-
There are a myriad of different cmds and args to control file
properties.
-
Many of the properties that are settable with fcntl can be set when a
file is opened; the fcntl function is useful because
-
it allows file properties to be changed, without closing and reopening the
file;
-
in the case of stdio and pipes, fcntl is the only way to set file
properties, when an application did not itself open the file associated with
the stdio or pipe descriptor.
-
ioctl (Stevens Section 3.15).
-
The ioctl function provides control of file descriptors associated
with devices.
-
Signature:
int iocntl(int fildes, int request, ...
);
-
request and an optional third argument are interpreted by the device
driver
-
Generally the interpretation is performed in a very device-specific way.
-
/dev/fd
-
One of the nice things about UNIX is the uniform way that it treats files and
devices.
-
There is a standard directory named "/dev" that contains files
associated with the hardware devices connected to a machine.
-
We'll be seeing more about /dev files in upcoming lectures.
-
At the level of file descriptors, many UNIX systems provide a /dev/fd
subdirectory, with files numbered 0, 1, 2, and
possibly higher.
-
By convention, file descriptors 0, 1, and 2
correspond to stdin, stdout, and stderr
respectively.
-
Having corresponding /dev/fd files for these enforces the uniformity
of files and devices, and makes certain aspects of shell use cleaner.
-
The conventional association of stdio with the numeric file
descriptors 0, 1, and 2 is not a POSIX thing.
-
POSIX requires the definition of three symbolic constants
STDIO_FILENO, STDOUT_FILENO, and STDERR_FILENO.
-
Despite this POSIX generality, many UNIX applications, including shells, rely
on the hard numeric mapping of file descriptors 0, 1, and
2.
-
Files and directories (Stevens Chapter 4).
-
A fundamental part of any operating system is the structure of its files and
directories.
-
The UNIX structure treats files and directories pretty uniformly, in that a
directory is itself a file.
-
And as noted above, UNIX treats files and devices in a reasonably uniform
manner, though a device file is different in structural detail than a regular
data file.
-
UNIX also provides the symbolic link file type, which is a file that
points to another file.
-
At the level of system calls, there are stat functions that provide
the information maintained by the OS for all files.
-
There are also other useful system functions that operate on files and
directories.
-
stat, fstat, and lstat (Stevens Section 4.2).
-
These three functions return information about a file in a struct
stat, which is defined in <sys/stat.h>.
-
Signatures:
int stat(const char* restrict 2
pathname, struct stat* restrict buf );
int lstat(const char* restrict pathname, struct stat*
restrict buf );
int fstat(int fildes, struct stat* buf
);
-
The returned data is in the buf parameter, which must point to a
caller-declared structure.
-
For fstat, the filedes parameter is the file descriptor of an
open file.
-
For each function, the return value is 0 if OK, -1 if error.
-
The difference between stat and lstat is that the latter
returns the information about a symbolic link file, not the file referenced by
the link; i.e., stat follows the symbolic link pointer, lstat
does not.
-
Here's the definition of struct stat in use on falcon/hornet:
struct stat {
dev_t st_dev; /* device number */
ino_t st_ino; /* i-node number */
mode_t st_mode; /* file type and mode */
nlink_t st_nlink; /* number of links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device number for special files */
off_t st_size; /* size in bytes, for regular files */
timestruc_t st_atim; /* time of last access */
timestruc_t st_mtim; /* time of last modification */
timestruc_t st_ctim; /* time of last file status change */
blksize_t st_blksize; /* best I/O block size */
blkcnt_t st_blocks; /* number of disk blocks alloc'd */
char st_fstype[_ST_FSTYPSZ]; /* type of file system */
};
-
Note that all but the last struct field are declared as system-defined
datatypes, which are defined in <sys/types.h> and elsewhere.
-
Use of struct stat will figure prominently in your implementation of
programming assignment 3.
-
File types (Stevens Section 4.3).
-
The two most commonly used types of file are regular data files and
directories.
-
UNIX defines seven different files types:
-
Regular file, which holds data of some kind; the UNIX kernel does not
distinguish between text and binary data, leaving file data interpretation to
the application programs that process a file.
-
Directory file, which contains the names of other files and pointers to
the file information.
-
Block special file, which provides buffered I/O access to devices that
communicate in fixed-sized blocks, such as disk drives.
-
Character special file, which provides unbuffered I/O access to devices
that communicate in variable-sized units.
-
FIFO, which is a type of file for communication between processes, also
called a named pipe.
-
Socket, used for inter-process communication, including across a
network.
-
Symbolic link, which is a file that points to another file; symbolic
links are akin to short cuts in Windows.
-
Page 90 of Stevens has a useful code example.
-
It's a program that prints the file-type of each command-line argument.
-
It uses lstat to obtain the file information.
-
Later in the chapter, on pages 121-125, there is another code example that uses
lstat to traverse a directory hierarchy.
index
|
lectures
|
labs
|
programs
|
handouts
|
solutions
|
examples
|
documentation
|
bin
Footnotes:
1 As defined in Chapter 1 of Stevens, a
process is an independently executing program.
2 As described
on page 26 of Chapter 1 in Stevens, restrict is keyword
added to
the 1999 ISO standard of C, and hence not covered in K&R. This
keyword is
used to tell the compiler which function parameters can be optimized
within a function. Its use does not change the semantics of the function, only
potentially its performance. You need not use restrict in any of
your
357 programs.