ยง39.5โ€“39.6The File Offset, and Sharing It: lseek, fork, dup

Part III OSTEP pp. 472โ€“476 ยท ~8 min read

  • file offset

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 , 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 exactly offset bytes.
  • SEEK_CUR โ€” set it to the current offset plus offset bytes.
  • SEEK_END โ€” set it to the file size plus offset bytes.

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 . Watch the offset move as a process reads a 300-byte file, then repositions with lseek:

The current offset lives in the open file table entry. Each read advances it; lseek() sets it directly. The bar is the 300-byte file; brown = already read, the marker is the offset.
fd = open("file", O_RDONLY)returns 3 ยท offset 00300offset 0

1open("file", O_RDONLY) โ†’ 3

A fresh open initializes the current offset to 0. The descriptor is 3.

step 1 / 7

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:

Opening the same file twice gives two descriptors AND two independent open file table entries โ€” so the offsets move separately.
returnOFT[10] offset (fd 3)OFT[11] offset (fd 4)
fd1 = open("file")30โ€“
fd2 = open("file")400
read(fd1, buf, 100)1001000
read(fd2, buf, 100)100100100
close(fd1)0โ€“100
close(fd2)0โ€“โ€“
Dotted-underlined cells have explanations โ€” click one.

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:

fork-seek.c: a forked child and its parent SHARE one open file table entry โ€” so the child's lseek is visible to the parent.
parent
child
OS (open file table)
Open, then fork
Child moves the shared offset
Parent sees the child's change
step 1 / 10 ยท time flows downward

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:

parent fds3 โ†’child fds3 โ†’shared open-file entryrefcnt: 2 ยท off: 10inode#1000

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?

6 questions