从原始系统调用到弄清 NSS 与 libc 的疯狂之旅

发布: (2026年2月4日 GMT+8 15:27)
6 min read
原文: Dev.to

Source: Dev.to

我一直对 Linux 抱有一种书呆子式的执着。从第一天起,我就全身心投入到内核中,使用 raw syscalls 编写小工具——完全跳过 libc,直接与内核对话。我自己写头文件,自己实现 printf‑风格的函数,用 getdents64 打开目录,解析 /proc 获取进程信息……基本上就是在内核的后院里冒险。

当时我觉得自己 牛逼,把一切都做到“真正的方式”。它能跑,但我跳过了 libc整整一层理智。我根本不知道它实际上做了多少事:缓冲、可读的格式化以及其他让生活不那么痛苦的便利功能。

The 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:

“这到底是怎么回事?谁他妈会用这个?难道内核不应该自己知道吗?”

I was in full raw‑syscall mode, so the idea that 某个配置文件 could tell libc how to handle getpwnam() or getaddrinfo() blew my mind. I had no clue why this “sign‑board” was even necessary.

好了,现在 NSS 有意义了

原来,NSS = Name Service Switch。它基本上是 libc 内的一个招牌,告诉它到底去哪里查找信息。

当程序调用类似 getpwuid()getpwnam() 时,它在询问:

“这个用户是谁?他的用户名是什么?”

内核 并不知道用户名;它只知道数字 ID(例如,UID = 1000)。这时 libc + NSS 就像超级英雄一样出现。

实际工作原理

假设你调用 getpwnam("Deadpool")libc 大致执行以下步骤:

  1. 读取 /etc/nsswitch.conf
    找到用户对应的行:

    passwd: files systemd

    这表示:

    • 首先,检查本地文件(/etc/passwd
    • 然后,查询 systemd 的动态用户数据库
  2. 按顺序调用每个 NSS 模块

    • libnss_files.so.2 → 在 /etc/passwd 中查找

      • 找到?→ 返回结果
      • 未找到?→ 继续
    • libnss_systemd.so.2 → 向 systemd 询问

      • 找到?→ 返回结果
      • 未找到?→ 继续(如果还有其他来源)
  3. 返回一个 struct passwd 给你的程序,里面包含用户名、UID、GID、主目录、Shell 等信息。

重要提示: NSS 本身 不是一个运行中的程序;它只是一个配置文件加上一组共享库。libc 动态加载这些库,因此不会涉及额外的进程。

秘密酱汁:libc 超越原始系统调用

libc 不仅仅是系统调用的薄包装。的确,它包装了 writereadopengetpid 等调用,但它还加入了大量额外功能:

类别示例
缓冲与格式化printf, fprintf, fopen, getline
内存管理malloc, free, realloc
线程支持pthread_* 函数
策略与查找逻辑NSS 模块、区域设置处理、主机名解析

如果没有 NSS,这些额外的酱汁只能部分工作:

  • 系统调用仍然会正确执行(openreadwrite,……)
  • 缓冲、格式化、内存、线程仍然可用
  • 任何需要人类可读名称的函数(用户、组、主机、服务)将会 失败或返回 NULL,因为 libc 没有映射告诉它去哪里查找。

NSS Modules Still Use Syscalls Under the Hood

Even NSS modules ultimately rely on syscalls:

  • libnss_files.so.2 reads /etc/passwd using open, read, close.
  • libnss_systemd.so.2 talks to the systemd service via IPC syscalls.

So everything still hits the kernel; NSS just adds a layer of logic so libc knows where and how to get the data.

Bottom line: libc + NSS = fancy middleware. Raw syscalls = truth. NSS gives meaning to that truth and ensures humans don’t see useless numbers.

这让我产生的感悟

在顿悟之前,我的工具会做以下事情:

  • 使用 getdents64 打开目录
  • 读取 /proc/<pid>/status → 获取数值 UID
  • 仅显示数字

如果你只想要原始事实,这样做没问题,但对人类来说很无聊。人类想要的是:

PID: 742   User: Deadpool   Process: bash

而不是:

PID: 742   UID: 1000      Process: bash

NSS 基本上把事实翻译成意义,而 libc 完成所有繁重的工作。

系统层次(心理图)

Linux 名称服务切换图

我学到的关键要点

  • libc 不仅仅是包装系统调用:缓冲、格式化、线程、内存,以及 NSS
  • 如果只需要数字,NSS 是可选的,但人类会讨厌你的输出。
  • Systemd 为服务和容器提供动态用户数据库,NSS 可以查询它。
  • 原始系统调用 = 真相;NSS = 含义。

我的反思

实话实说:这是一次巨大的“哎呀”时刻。我的低层工具虽然很酷,但并不真正“用户友好”或“系统感知”。我一直在跳过整个用户空间逻辑的生态系统。

现在我看到将两者结合的价值:

  • 原始系统调用 → 像高手一样理解内核和 ABI。
  • libc + NSS → 制作可读、稳健且真正有用的工具。

One‑Liner to Summarize

“系统调用给你原始真相;NSS 给 libc 那该死的映射。”

以将该真相转化为人类真正能理解的东西。

Back to Blog

相关文章

阅读更多 »