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.

FieldMeaning
typeFile kind: FD_NONE, FD_PIPE, FD_INODE, or FD_DEVICE.
refNumber of references to this open file object.
readableSays whether reads are allowed.
writableSays whether writes are allowed.
pipePipe pointer when type == FD_PIPE.
ipInode pointer when type == FD_INODE or FD_DEVICE.
offCurrent file offset for inode files.
majorDevice major number for device files.

A struct file wraps either an inode or a pipe:

  • type identifies the underlying object.
  • ref counts references from process descriptor tables and duplicated descriptors.
  • readable and writable record the open mode.
  • off stores 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.

FieldMeaning
lockProtects 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 &gt;= 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 &gt; 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, &amp;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