Two file descriptor numbers can move the same file offset. On Linux, this occurs when both descriptors refer to one open file description, as happens after dup() and across inherited descriptors after fork().
The distinction matters because a file descriptor is a process-visible integer, while the open file description is the kernel object that carries state for an open instance of a file. Treating those layers as interchangeable can produce offset interference, status-flag changes that cross descriptor boundaries, and surprising behavior after process creation.
A descriptor points at an open file description
A successful open() creates a new open file description and returns a file descriptor that refers to it. The open file description stores state including the current file offset and file status flags.
Opening the same pathname twice normally creates two separate open file descriptions:
int a = open("events.log", O_RDONLY);
int b = open("events.log", O_RDONLY);Both descriptors can refer to the same underlying file while retaining independent offsets. A read through a advances the offset associated with a’s open file description; it does not advance the independently created state behind b.
dup() has different semantics:
int a = open("events.log", O_RDONLY);
int b = dup(a);Here, a and b are distinct descriptor numbers referring to the same open file description. The shared object, rather than either integer, owns the current offset.
Offset changes cross duplicated descriptors
Suppose a and b share one open file description. A seek through either descriptor changes the offset observed through both:
lseek(a, 4096, SEEK_SET);
read(b, buf, sizeof buf);The read() starts at offset 4096, subject to the normal semantics of the underlying file and call. A successful read then advances the shared offset.
This is not aliasing at the pathname layer. Renaming or unlinking the pathname does not split the open file description. The descriptors continue to refer to the already opened file object while that reference remains valid.
The same shared-offset property affects sequential reads. Two execution contexts consuming one shared open file description do not have private cursors merely because they hold different descriptor numbers.
fork preserves the shared kernel object
After fork(), the child receives copies of the parent’s file descriptors. Those inherited descriptors refer to the same open file descriptions as their parent-side counterparts.
If a parent opens a regular file and then forks, a read in the child can advance the offset later observed by the parent. The process boundary creates separate descriptor tables, but it does not create fresh open file descriptions for inherited entries.
A process that requires an independent file position needs a separate open file description. Reopening the file is one common source of that separation. Duplicating an existing descriptor is not.
File status flags also belong to the shared description
The offset is only part of the shared state. File status flags such as O_APPEND and O_NONBLOCK are associated with the open file description.
Changing such a flag with fcntl() through one duplicated descriptor therefore affects operations through other descriptors attached to the same open file description. This differs from file descriptor flags such as FD_CLOEXEC, which are attached to individual descriptor-table entries.
That split creates an important boundary:
- file status flags describe the shared open file description;
- file descriptor flags describe one descriptor entry.
Code that duplicates a descriptor to isolate status-flag changes does not create that isolation.
close removes a reference, not necessarily the open state
Closing one duplicated descriptor removes that descriptor’s reference. The open file description remains alive while another descriptor still refers to it.
This lifetime rule appears in several Linux interfaces. An object registered with an event facility or associated with open-file-description state can outlive the closure of one descriptor when another duplicate still keeps the underlying open file description referenced.
The descriptor number itself can also be reused after close(). A later descriptor with the same integer value is not evidence that it refers to the same open file description.
Descriptor identity and open-state identity are separate
Systems code often tracks descriptors as integers because system calls accept those integers. That representation is insufficient when correctness depends on shared open state.
Two different integers may share one offset and one set of status flags. The same integer value at different times may refer to unrelated open file descriptions. Two descriptors for the same inode may still have independent open file descriptions.
The durable boundary is therefore not descriptor equality or pathname equality. It is the kernel open file description referenced by each descriptor. Operations that duplicate or inherit a descriptor preserve that shared object; a fresh open() creates a new one.