File Descriptors
On this page
Focused on sockets
In my day to day I mostly deal with file descriptors that point to sockets. As a result, the examples below will be biased towards this use case.
Everything in linux is a file, and file descriptors are like pointers to a file. Therefore, file descriptors can point to all kinds of things.
They’re pretty opaque pointers though - they’re just integers. To even begin to make sense of them you need the FD table.
FD Mapping
Generally the relationship between FDs and the things they describe goes like this:

At a very high level, this is how FDs are mapped to the resources they describe:
- The FD table is per-process (meaning integers in one process probably don’t point to the same thing in another). Each integer value maps to a pointer (memory address) to a
file
struct - That
file
struct points eventually to aninode
struct - The
inode
describes the contents and metadata of the file
As it pertains to sockets, things get a little interesting. /proc
is where a bunch of pseudo-filesystems are located for getting information from the kernel. It is one of the places where the “everything is a file” becomes really apparent.
Two subdirectories are of particular interest as it pertains to file descriptors and how they apply to networking:
/proc/<pid>/fd
is te file descriptor table for a given process. Maps FD to inode numbers/proc/net
1 2 is part of a pseudo-filesystem containing all kinds of interesting details from the kernel.
Inspect a process’ open FDs. You’ll notice that this output is very helpful for identifying sockets.
1mierdin@t-bug:~ $ ls -lha /proc/$(pgrep -f "python3 sock_stream_server.py")/fd
2total 0
3dr-x------ 2 mierdin mierdin 5 Aug 4 09:52 .
4dr-xr-xr-x 9 mierdin mierdin 0 Aug 4 09:09 ..
5lrwx------ 1 mierdin mierdin 64 Aug 4 09:52 0 -> /dev/pts/7
6lrwx------ 1 mierdin mierdin 64 Aug 4 09:52 1 -> /dev/pts/7
7lrwx------ 1 mierdin mierdin 64 Aug 4 09:52 2 -> /dev/pts/7
8lrwx------ 1 mierdin mierdin 64 Aug 4 09:52 3 -> 'socket:[231891]'
9lrwx------ 1 mierdin mierdin 64 Aug 4 09:52 4 -> 'socket:[231892]'
10
11mierdin@t-bug:~ $ stat /proc/$(pgrep -f "python3 sock_stream_server.py")/fd/3
12 File: /proc/100464/fd/3 -> socket:[231891]
13 Size: 64 Blocks: 0 IO Block: 1024 symbolic link
14Device: 0,19 Inode: 284542 Links: 1
15Access: (0700/lrwx------) Uid: ( 1000/ mierdin) Gid: ( 1000/ mierdin)
16Access: 2025-08-04 09:53:05.421363362 -0400
17Modify: 2025-08-04 09:52:34.793398433 -0400
18Change: 2025-08-04 09:52:34.793398433 -0400
19 Birth: -
The inode in the previous example (231891
) can be used to look up the socket in /proc/net/tcp
:
Focused on sockets
The above examples are just emphasizing the file-like nature of these FDs even though they point to sockets.
A far better way of inspecting file descriptors is with lsof
Syscalls
Some syscalls create new FDs:
Others operate on an already-opened FD and therefore require it as a parameter: