Fundamentals 16 min read

What Exactly Is a File Descriptor (fd) in Linux? A Deep Dive into Kernel Structures

This article explains the true nature of a file descriptor (fd) in Linux, tracing how the kernel represents open files through task_struct, files_struct, fdtable, and inode structures, and shows how these layers interact with system calls like open, read, write, dup, and fork.

Liangxu Linux
Liangxu Linux
Liangxu Linux
What Exactly Is a File Descriptor (fd) in Linux? A Deep Dive into Kernel Structures

In Linux, a file descriptor ( fd) is simply a non‑negative integer that serves as an index into a per‑process table of open files. When a program calls open or creat, the kernel returns an fd, which is then used for all subsequent I/O operations such as read, write, dup, and seek.

Key Kernel Data Structures

The process is represented by struct task_struct. One of its fields, files, points to a struct files_struct, which manages all open files for that process.

struct task_struct {
    /* ... */
    struct files_struct *files; // pointer to file table manager
    /* ... */
};
struct files_struct

contains both a static array ( fd_array) and a dynamic array ( fdtable) that store pointers to struct file objects. The fd you receive is the index into this array.

struct files_struct {
    atomic_t count;
    bool resize_in_progress;
    wait_queue_head_t resize_wait;
    struct fdtable *__rcu fdt;   // dynamic array
    struct fdtable fdtab;       // static array wrapper
    unsigned int next_fd;
    unsigned long close_on_exec_init[1];
    unsigned long open_fds_init[1];
    unsigned long full_fds_bits_init[1];
    struct file *fd_array[NR_OPEN_DEFAULT]; // static array
};

The dynamic array is described by struct fdtable:

struct fdtable {
    unsigned int max_fds;               // array bound
    struct file __rcu **fd;            // pointer to array of file pointers
};

Each entry in the array points to a struct file, which represents an opened file and holds important fields such as the current file offset ( f_pos), a pointer to the VFS inode ( f_inode), and file operation callbacks.

struct file {
    struct path f_path;
    struct inode *f_inode;
    const struct file_operations *f_op;
    atomic_long_t f_count;
    unsigned int f_flags;
    fmode_t f_mode;
    struct mutex f_pos_lock;
    loff_t f_pos;          // current offset
    struct fown_struct f_owner;
    /* ... */
};

The struct inode is the VFS abstraction that hides the specifics of underlying file systems (e.g., ext4, ext2). It contains metadata such as mode, UID/GID, size, timestamps, and pointers to file‑system‑specific operations.

struct inode {
    umode_t i_mode;
    unsigned short i_opflags;
    kuid_t i_uid;
    kgid_t i_gid;
    unsigned int i_flags;
    const struct inode_operations *i_op;
    struct super_block *i_sb;
    struct address_space *i_mapping;
    loff_t i_size;
    struct timespec64 i_atime, i_mtime, i_ctime;
    const struct file_operations *i_fop;
    struct address_space i_data;
    void *i_private; // FS‑specific data
};

File‑system‑specific inodes (e.g., struct ext4_inode_info) embed the VFS inode and add their own fields. The kernel obtains the concrete inode by casting the VFS inode pointer back to the container structure using the container_of macro.

#define container_of(ptr, type, member) \
    (type *)((char *)(ptr) - (char *)&((type *)0)->member)

Practical Implications

When write advances the file offset beyond the current length, the inode size is updated, effectively extending the file.

Opening a file with O_APPEND forces each write to first set the offset to the inode's length, guaranteeing atomic appends. seek changes only the offset in the file structure; it does not perform I/O.

Multiple processes can share the same struct file (e.g., after fork), while the underlying inode is a system‑wide resource.

Duplicating an fd with dup or dup2 creates another entry in the same fd_array that points to the same struct file.

Summary

The fd you see in user space is merely an index into a kernel‑managed array of struct file objects. Those objects link to VFS inodes, which abstract away the details of concrete file‑system implementations. Understanding this chain—from task_struct to files_struct to file to inode —gives you full insight into how Linux I/O works under the hood.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Linux kernelinodevfsfile descriptortask_structfdfiles_struct
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.