The VFS (Virtual File System) provides a uniform I/O interface through file descriptors, abstracting disk inodes, device files, and pipes behind a common struct file object.
The file descriptor layer gives Unix its uniform I/O interface: regular files, directories, devices, and pipes can all be accessed through small per-process integers. Each process has its own file descriptor table, but entries point to global struct file objects.
The struct file represents state of one open file description.
| Field | Meaning |
|---|---|
type | File kind: FD_NONE, FD_PIPE, FD_INODE, or FD_DEVICE. |
ref | Number of references to this open file object. |
readable | Says whether reads are allowed. |
writable | Says whether writes are allowed. |
pipe | Pipe pointer when type == FD_PIPE. |
ip | Inode pointer when type == FD_INODE or FD_DEVICE. |
off | Current file offset for inode files. |
major | Device major number for device files. |
A struct file wraps either an inode or a pipe:
typeidentifies the underlying object.refcounts references from process descriptor tables and duplicated descriptors.readableandwritablerecord the open mode.offstores the current I/O offset for inode-backed files; pipes do not use offsets.
Separate open calls create separate struct file objects with separate offsets. dup and fork share the same struct file, so they also share its offset.
The global file table manages open-file objects: allocating slots, tracking reference counts, and releasing the underlying pipe or inode when the last reference drops. Read and write dispatch to the appropriate backend (pipe, device, or inode) based on the file type, and advance the shared offset for inode-backed files.
Inode locking serializes offset updates so concurrent writes through the same open file do not overwrite the same offset accidentally, though their data may still be interleaved.
ftable is the global file-table state.
| Field | Meaning |
|---|---|
lock | Protects file allocation and reference counts. |
file[NFILE] | Fixed array of 100 open file objects. |
The file table is populated lazily: initial setup prepares the lock, and slots are claimed on demand as files are opened.
flowchart LR subgraph ProcessSide["process side: per-process file descriptor table"] direction TB Proc["proc<br><br>ofile[NOFILE]<br>cwd"] OFile["ofile[NOFILE]<br><br>fd is just an integer index<br>empty entry: 0<br>open entry: pointer to file"] end subgraph FileLayer["file layer"] direction TB FTable["ftable<br><br>lock<br>file[NFILE]<br><br>global pool of open-file objects<br>lock protects allocation and ref count changes"] StructFile["file<br><br>type:<br>FD_NONE | FD_PIPE | FD_INODE | FD_DEVICE<br><br>ref<br>readable<br>writable<br>pipe<br>ip<br>off<br>major<br><br>one file may be shared by multiple fd entries"] FileInit["fileinit()<br><br>initializes ftable.lock"] FileAlloc["filealloc()<br><br>scan ftable.file[]<br>find slot where ref == 0<br>set ref = 1<br>return file<br>return 0 if no free slot"] FileDup["filedup(f)<br><br>requires ref >= 1<br>increments ref<br>returns same file<br><br>after dup, two fd entries share:<br>same file object<br>same permissions<br>same offset for inode files"] FileClose["fileclose(f)<br><br>decrement ref<br>if ref remains > 0: done<br><br>if this was last reference:<br>copy file state locally<br>set ref = 0<br>set type = FD_NONE<br>then close pipe or iput inode"] FileRead["fileread(f, addr, n)<br><br>check readable<br>dispatch on type<br><br>FD_PIPE:<br>piperead(pipe, addr, n)<br><br>FD_DEVICE:<br>devsw[major].read(1, addr, n)<br><br>FD_INODE:<br>ilock(ip)<br>readi(ip, 1, addr, off, n)<br>advance off by bytes read<br>iunlock(ip)"] FileWrite["filewrite(f, addr, n)<br><br>check writable<br>dispatch on type<br><br>FD_PIPE:<br>pipewrite(pipe, addr, n)<br><br>FD_DEVICE:<br>devsw[major].write(1, addr, n)<br><br>FD_INODE:<br>write in log-sized chunks<br>begin_op()<br>ilock(ip)<br>writei(ip, 1, addr+i, off, n1)<br>advance off by bytes written<br>iunlock(ip)<br>end_op()"] FileStat["filestat(f, addr)<br><br>only FD_INODE and FD_DEVICE are valid<br><br>ilock(ip)<br>stati(ip, &st)<br>iunlock(ip)<br>copyout stat to user address<br><br>FD_PIPE returns -1"] end subgraph BackendBoundary["backend boundary only"] direction TB PipeBackend["pipe backend<br><br>piperead<br>pipewrite<br>pipeclose"] InodeBackend["inode / fs.c backend<br><br>readi<br>writei<br>stati<br>iput"] DeviceBackend["device backend<br><br>devsw[major].read<br>devsw[major].write"] end Proc -- contains --> OFile OFile -- entry n points to file when open --> StructFile FileInit -- initializes lock inside --> FTable FTable -- contains NFILE file slots --> StructFile FileAlloc -- "claims free slot: ref 0 -> 1" --> FTable FileAlloc -- returns pointer to allocated slot --> StructFile FileDup -- increments reference count on same object --> StructFile FileClose -- drops one reference from shared object --> StructFile FileClose -- "last close frees ftable slot by setting type = FD_NONE" --> FTable StructFile -- read uses readable and type --> FileRead StructFile -- write uses writable and type --> FileWrite StructFile -- stat uses type and ip --> FileStat FileRead -- FD_PIPE uses pipe --> PipeBackend FileWrite -- FD_PIPE uses pipe --> PipeBackend FileClose -- FD_PIPE last close calls pipeclose --> PipeBackend FileRead -- FD_INODE uses ip and shared off --> InodeBackend FileWrite -- FD_INODE uses ip and shared off --> InodeBackend FileStat -- FD_INODE / FD_DEVICE use ip --> InodeBackend FileClose -- FD_INODE / FD_DEVICE last close calls iput --> InodeBackend FileRead -- FD_DEVICE uses major --> DeviceBackend FileWrite -- FD_DEVICE uses major --> DeviceBackend Proc:::process OFile:::process FTable:::filelayer StructFile:::filelayer FileInit:::function FileAlloc:::function FileDup:::function FileClose:::function FileRead:::function FileWrite:::function FileStat:::function PipeBackend:::backend InodeBackend:::backend DeviceBackend:::backend classDef process fill:#F3EFE2,stroke:#111,stroke-width:2px,color:#111 classDef filelayer fill:#E9F1FF,stroke:#111,stroke-width:2px,color:#111 classDef function fill:#F3E8FF,stroke:#111,stroke-width:2px,color:#111 classDef backend fill:#F8F8F8,stroke:#111,stroke-width:2px,color:#111