Unix系统中大多数I/O只需用到五个函数:open,read, write, lseek以及lose。然后说明不同缓冲长度对read和write函数的影响。
本章描述的函数被称为不带缓冲的*I/O(unbuffered I/O,与ch5中说明的函数对照)。术语不带缓冲*指的是每个read和write都调用内核中的一个系统调用。这些不带缓冲的I/O函数不是ISO C的组成部分,但是,它们是POSIX.1和Single Unix Specification的组成部分。
只要设计在多个进程间共享资源,原子操作的概念就变得非常重要,我们通过文件I/O和open函数的参数来讨论此概念。然后,本章将进一步讨论在多个进程间如何共享文件,以及所涉及的内核有关数据结构。在描述了这些特征之后,将说明dup, fcntl, sync, fsync和ioctl函数。
对于内核而言,所有打开的文件都是通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核像进程返回一个文件描述符。当读,写一个文件时,使用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。
在符合POSIX.1的应用程序中,幻数0,1,2虽然已被标准化,但应当把他们替换成符号常量STDIN_FILENO, STDOUT_FILENO和STDERR_FILENO以提高可读性。这些常量都在头文件<unistd.h>
中定义。
文件描述符的变化范围在0~OPEN_MAX-1
。
调用open或openat函数可以打开或创建一个文件。
#include <fcntl.h>
int open(const char *path, int oflag, .../* mode_t mode*/);
int openat(int fd, const char *path, int oflag, ... /*mode_t mode*/);
path
参数是要打开或创建文件的名字。oflag
参数可用来说明此函数的多个选项。用下列一个或多个常量进行“或”运算构成oflag参数(这些常量在头文件
What happens if NAME_MAX
is 14 and we try to create a new file in the current directory with a filename containing 15 characters? Traditionally, early release of System V, such as SVR2, allowed this to happen, silently truncating the filename beyond the 14th character. BSD-derived systems, in contrast returned an error status, with errno
set to ENAMETOOLONG
. Silently truncating the filename presents a problem that affects more than simply the creation of new files. if NAME_MAX
is 14 and a file exists whose name is exactly 14 characters, any function that accepts a pathname argument, such as open
or stat
, has no way to determine what the original name of the file was, as the original name might have been truncated.
With POSIX.1, the constant _POSIX_NO_TRUNC determines whether long filenames and long compoments of pathnames are truncated or an error is returned. As we saw in Chapter 2, this value can vary based on the type of the file system, and we can use fpathconf
or pathconf
to query a directory to see which behavior is supported.
If _POSIX_NO_TRUNC is in effect, errno
is set to ENAMETOOLONG
, and an error status is returned if any filename component of the pathname exceeds NAME_MAX
.
Most modern file system support a maximum of 255 characters for filenames. Because filenames are usually shorter than this limit, this constraint tends to not present problems for most applications.
create
FunctionA new file can also be created by calling the create
function
int create(const char *path, mode_t mode);
Returns:file descriptor opened for write-only if OK, -1 on error
Note that this function is equivalent to
open(path, O_WRONLY | O_CREATE | O_TRUNC, mode);
One deficiency with create
is that the file is opened only for writing. Before the new version of open
was provided, if we were creating a temporary file that we wanted to write and then read back, we had to call create
, close
, and then open
. A better way is to use the open
function, as in
open(path, O_RDWR | O_CREAT | O_TRUNC, mode);
close
FunctionAn open file is closed by calling the close
function.
#include <unistd.h>
int close(int fd);
Return: 0 if OK, -1 on error
Closing a file alos releases any record locks that the process may have on the file. We’ll discuss this point further in Section 14.3.
When a process terminates, all of its open files are closed automatically by the kernel. Many programs take advantage of this fact and don’t explicitly close open files. See the program in Figure 1.4, for example.
lseek
FunctionEvery open file has an associated “current file offset”, normally a non-negative integer that measures the number of bytes from the beginning of the file. (We describe some exceptions to the “non-negative” qualifier later in this section.) Read and write operations normally start at the current file offset and cause the offset to be incremented by the number of bytes read or written. By default, this offset is initialized to 0 when a file is opened, unless the O_APPEND opetion is specified.
An open file’s offset can be set explicitly by calling lseek.
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
Return: new file offset if OK, -1 on error
The interpretation of the offset
depends on the value of the whence
argument.
whence
is SEEK_SET, the file’s offset is set to offset
bytes from the beginning of the file.whence
is SEEK_CUR, the file’s offset is set to its current value plus the offset
. The offset
can be positive or negative.whence
is SEEK_END, the file’s offset is set to the size of the file plus the offset
. The offset
can be positive or negative.Because a successful call to lseek
returns the new file offset, we can seek zero bytes from the current position to determine the curretn offset:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
This technique can alos be used to determine if a file is capable of seeking. If the file descriptor refers to a pipe, FIFO, or socket, lseek
sets errno
to ESPIPE
and returns -1.
#include "apue.h"
int
main(void)
{
if(lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
printf("cannot seek\n");
else
printf("seek OK\n");
exit(0);
}
If we invoke this program interactively, we get
$ ./a.out < /etc/passwd
seek Ok
$ cat < /etc/passwd |./a.out
cannot seek
$ ./a.out < /var/spool/cron/FIFO
cannot seek
Normally, a file’s current offset must be a non-negative integer. It is possible, however, that certain devices could allow negative offsets. But for regular files, the offset must be non-negative. Beacuse negative offsets are possible, we should be careful to compare the return value from lseek
as being equal to or not equal to -1, rather than testing whether it is less than -.
The /dev/kmem device on FreeBSD for Intel x86 processor supports negative offsets.
Because the offset (off_t) is a singed data type (Figure 2.21), we lose a factor of 2 in the maximum file size. Ifoff_t
is 32-bit integer, the maximum file size is$2(31)−1$ bytes.
lseek
only records the current file offset within the kernel – it does not cause any I/O to take place. This offset is then used by the next read or write operation.
The file’s offset can be greater than the file’s current size, in which case the next write
to the file will exten the file. This is referred to as creating a hole in a file and is allowed. Any bytes in a file that have not been written are read back as 0.
A hole in a file isn’t rqquired to have storage backing it on disk. Depending on the file system implementation, when you write after seeking past the end of a file, new disk blocks might be allocated to store the data, but there is no need to allocate disk blocks for the data between the old end of file and the location where you starting writting.
#include "apue.h"
#include <fcntl.h>
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
int
main()
{
int fd;
if((fd = create("file.hole", FILE_MODE)) < 0) //>
err_sys("create error");
if(write(fd, buf1, 10) != 10)
error_sys("buf1 write error");
/* offset now = 10 */
if(lseek(fd, 16384, SEEK_SET) == -1)
error_sys("lseek error");
/*offset now = 16384 */
if(write(fd, buf2, 10) !=10)
error_sys("buf2 write error");
/*offset now = 16394*/
exit(0);
}
We use od(1) command to look at the contents of the file. The -c
flag tells it to print the contents as characters. We can see that the unwritten bytes in the miidle are read back by zero. The seve-digit number at the beginning of each line is the byte offset in octal.
To prove that there is really a hole in the file, let’s compare the file we just created with a file of the same size, but without holes:
Although both files are the same size, the file without holes consumes 20 disk blocks, whereas the file with holes consumes only 8 blocks.
In this example, we call the write
function (Section 3.8). We’ll have more to say about files with holes in Section 4.12.
Because the offset address that lseek
uses is represented by an off_t
, implementations are allowed to support whatever size is appropriate on their particular platform. Most platforms today provide two sets of interfaces to manipulate file offsets: one set that uses 32-bit file offsets and another set that uses 64-bit file offsets.
The Single UNIX Specification provides a way for applications to determine which environments are supported through the sysconf
function (Section 2.5.4). Figure 3.3 summarizes the sysconf
constants that are defined.
The c99
complier requires that we use the getconf(1)
command to map the desired data size model to the flags necessary to compile and link our programs. Different flags and libraries might be needed, depending on the environments supported by each platform.
Unfortunately, this is one are in which implementations haven’t caught up to the standards. If your system does not match the latest version of the standard, the system might support the option names from the previous version of the Single UNIX Specification:
_POSIX_V6_ILP32_OFF32
,_POSIX_V6_ILP32_OFFBIG
,_POSIX_V6_LP64_OFF64
, and_POSIX_V6_LP64_OFFBIG
.To get around this, application can set the
_FILE_OFFSET_BITS
constant to 64 to enable 64-bit offsets. Doing so changs the definition ofoff_t
to be a 64-bit signed integer. Setting_FILE_OFFSET_BITS
to 32 enables 32-bit file offset. Be aware, however ,that although all four platforms discussed in the text support both 32-bit and 64-bit file offsets, setting_FILE_OFFSET_BITS
is not guaranteed to be portable and might not have the desired effect.Figure 3.4 summarizes the size in bytes of the
off_t
data type for the platforms covered in this book when an application doesn’t define_FILE_OFFSET_BITS
, as wella s the size when an application defines_FILE_OFFSET_BITS
to have a value of either 32 or 64.
==Note that even though you might enable 64-bit file offset, your ability to create a file larger that 2GB( 2(32)−1 bytes) depends on the underlying file system type.==