Weโve read and written files only sequentially so far โ start to finish. Sometimes, though, you need to jump to a specific spot (an index into a document, say). That requires understanding the offset that every open file carries.
39.5 Reading And Writing, But Not Sequentially
For each open file the OS tracks a current offset file offset The 'current' position stored in an open file table entry, deciding where the next read or write begins. Each read/write of N bytes advances it by N; lseek() sets it explicitly (enabling random access). lseek() only changes this in-memory variable โ it does NOT itself cause a disk seek.
defined in ch. 39 โ open in glossary
,
which decides where the next read or write begins. It updates two ways.
Implicitly: a read or write of N bytes adds N to the offset. Explicitly:
lseek(int fd, off_t offset, int whence) sets it. The whence argument picks
the reference point:
SEEK_SETโ set the offset to exactlyoffsetbytes.SEEK_CURโ set it to the current offset plusoffsetbytes.SEEK_ENDโ set it to the file size plusoffsetbytes.
The offset lives in the kernelโs per-open-file structure (simplified from xv6):
struct file {
int ref; // reference count
char readable;
char writable;
struct inode *ip; // which underlying file
uint off; // current offset
};
These structures โ all currently-open files in the system โ together form the
open file table open file table A system-wide table of entries, each describing one open file: which inode it refers to, whether it is readable/writable, a reference count, and the current file offset. A process's file descriptors point into this table; usually one-to-one, but entries can be shared (via fork() or dup()).
defined in ch. 39 โ open in glossary
. Watch the offset move as a
process reads a 300-byte file, then repositions with lseek:
1open("file", O_RDONLY) โ 3
A fresh open initializes the current offset to 0. The descriptor is 3.
Aside: Calling lseek() Does NOT Perform A Disk Seek
The name is a trap.lseek() only changes a variable in OS memory
โ the offset for your next read or write. A disk seek is a physical
head movement, and happens only when a read/write actually lands on a
different track (see the disk chapter). Using
lseek() to then read scattered offsets will cause disk
seeks in those reads โ but the lseek() call itself does no I/O.Two opens, two offsets
If a process opens the same file twice, it gets two descriptors and two separate open file table entries โ so each has its own offset, moving independently:
| return | OFT[10] offset (fd 3) | OFT[11] offset (fd 4) | |
|---|---|---|---|
| fd1 = open("file") | 3 | 0 | โ |
| fd2 = open("file") | 4 | 0 | 0 |
| read(fd1, buf, 100) | 100 | 100 | 0 |
| read(fd2, buf, 100) | 100 | 100 | 100 |
| close(fd1) | 0 | โ | 100 |
| close(fd2) | 0 | โ | โ |
39.6 Shared File Table Entries: fork() and dup()
Usually the descriptor โ open-file-table mapping is one-to-one; even two different processes reading the same file each get their own entry (and offset). But two important cases share a single entry.
The first is fork(). When a parent forks, the child inherits copies of the
parentโs descriptors โ and those copies point at the same open file table
entry, so parent and child share one offset. The entryโs reference count
records this; itโs freed only when both close it. Here is fork-seek.c in
motion:
parent: fd = open("file.txt", O_RDONLY)
The relationships look like this โ two private descriptor arrays converging on one shared open-file entry, which points at the inode:
The second case is dup(). It creates a new descriptor in the same process
that refers to the same underlying open file as an existing one:
int fd = open("README", O_RDONLY);
int fd2 = dup(fd); // fd and fd2 now share one open-file entry
dup() (and especially dup2()) is the mechanism a shell uses for output
redirection โ point a commandโs descriptor 1 at a fileโs open-file entry, and
its writes land in the file instead of on the screen.
Check yourself: offsets, fork, and dup
1.What is the current offset of an open file, and how is it updated?
2.lseek(fd, 200, SEEK_SET) is called, then read(fd, buf, 50). What is the offset afterward?
3.Does calling lseek() cause a disk seek?
4.A process calls open("file") twice, getting fd 3 and fd 4. What about their offsets?
5.In fork-seek.c, the child does lseek(fd, 10, SEEK_SET) and exits; the parent then checks the offset and sees 10. Why?
6.What does dup() do, and what is it commonly used for?