Man, I’ve always had this nerdy obsession with Linux. From day one, I was diving headfirst into the kernel, writing tiny tools using raw syscalls,basically skipping libc entirely and talking to the kernel like a badass. I made my own headers, wrote my own print routines, opened directories using getdents64, parsed /proc for processes… basically living dangerously in the kernel’s backyard.
At the time, I thought I was the shit doing everything “the real way.” And yeah, it worked. But what I didn’t realize was that I was skipping a whole layer of sanity that exists in libc. I had no idea about all the stuff it actually does. All the buffering, human-readable formatting, and things that make life not completely painful.
That WTF Moment: /etc/nsswitch.conf
Everything hit me when I stumbled upon /etc/nsswitch.conf while poking around network and user stuff. I opened it and literally went:
“What the actual hell is this? Who the fuck uses this? Isn’t the kernel supposed to know this?”
It was super confusing. I was in full raw syscall mode, so the idea that some config file could tell libc how to handle getpwnam() or getaddrinfo() blew my mind. I had no clue why this “signboard” was even necessary.
Okay, Now NSS Makes Sense
Turns out, NSS = Name Service Switch. And yeah, it’s basically a signboard inside libc telling it where the hell to look for information.
When a program calls something like getpwuid() or getpwnam(), it’s asking:
“Who is this user? What’s their username?”
Here’s the thing: the kernel doesn’t know usernames, it only knows numeric IDs, like UID = 1000. That’s where libc + NSS swoop in like superheroes.
How It Actually Works
Say you call getpwnam("Deadpool"). libc does this:
1.Read /etc/nsswitch.conf
-
Finds the line for users:
passwd: files systemd -
This basically says:
- First, check local files (
/etc/passwd) - Then, ask systemd’s dynamic user database
- First, check local files (
2.Call each NSS module in order
-
First:
libnss_files.so.2→ looks in/etc/passwd- Found? Cool → return result
- Not found? Keep going
-
Second:
libnss_systemd.so.2→ ask systemd- Found? Sweet → return result
- Not? Continue to next source if any
3.Return a struct passwd to your program
- Full info: username, UID, GID, home dir, shell, all the juicy stuff
Important: NSS itself is not a running program, just a config file + shared libraries. libc calls them dynamically, no extra processes involved.
The Secret Sauce: libc Beyond Raw Syscalls
Here’s something I didn’t realize at first: libc is more than just a syscall wrapper. Sure, it wraps syscalls like write, read, open, getpid, etc., but it also adds a ton of extra sauce:
-
Buffering and formatting:
printf,fopenbuffers,getline, etc. -
Memory management:
malloc,free -
Threading support:
pthread_*functions - Policy and lookup logic: NSS modules, locale, hostname resolution
Without NSS, this extra sauce works partially:
- Syscalls themselves still happen correctly (you can
openfiles,readdirectories,writeto stdout) - Buffering, formatting, memory, threading still works
- But any function that needs human-readable names (users, groups, hosts, services) would fail or return NULL, because libc has no map to tell it where to look
NSS Modules Still Use Syscalls Under the Hood
Here’s the other thing that clicked the perfect image on my mind: even NSS modules themselves ultimately use syscalls.
- For example,
libnss_files.so.2reads/etc/passwdusingopen,read,close(syscalls). -
libnss_systemd.so.2talks to the systemd service using IPC syscalls. - So everything still hits the kernel eventually the NSS just adds a layer of logic so libc knows where and how to get the data.
Basically, libc + NSS = fancy middleware. Raw syscalls = truth. NSS just gives meaning to that truth and ensures humans don’t see useless numbers.
How This Hit Me
Before this epiphany, my tools were like:
- Open directories with
getdents64 - Read
/proc/<pid>/status→ get numeric UIDs - Display numbers only
Which is perfectly fine if you just want raw truth. But it’s boring for humans. Humans want:
PID: 742 User: Deadpool Process: bash
Not:
PID: 742 UID: 1000 Process: bash
NSS basically translates truth into meaning, and it’s libc that does all the heavy lifting.
Layers of the System (Mental Picture)
Here’s how it finally clicked for me:
Some key nuggets I learned:
- libc does way more than syscalls its buffering, formatting, threading, memory, and yes, NSS
- NSS is optional if you only need numbers, but humans will hate your output
- Systemd has a dynamic user database for service and container users, which NSS can query
- Raw syscalls = truth, NSS = meaning
My Reflection
Real talk: this was a big “oh shit” moment. I realized that all my badass low level tools were cool, but they weren’t really “user-friendly” or system aware. I had been skipping an entire ecosystem of userspace logic.
Now, I see the value in combining both:
- Raw syscalls → to understand the kernel and ABIs like a boss
- libc + NSS → to make tools that are readable, robust, and actually useful
The One-Liner to Finalize It
“Syscalls give you the raw truth; NSS gives libc the damn map to turn that truth into something humans can actually understand.”
This journey taught me not just how Linux works, but also how to think in layers: kernel, ABI, libc, NSS, and human-readable output.
And now, whenever I build a tool, I don’t just make it work, I know why it works, what layer is doing what, and how the pieces actually fit together.

Top comments (0)