Raw Syscalls에서 NSS와 libc 파악까지의 스릴 넘치는 여정
Source: Dev.to
저는 언제나 Linux에 대한 nerdy한 집착이 있었습니다. 첫날부터 커널에 머리를 박고, raw syscalls을 사용해 작은 도구들을 만들었습니다—libc를 완전히 건너뛰고 커널과 직접 통신했습니다. 저는 직접 헤더를 만들고, printf‑style 루틴을 작성했으며, getdents64로 디렉터리를 열고, 프로세스를 위해 /proc을 파싱했습니다… 기본적으로 커널 뒷마당에서 위험하게 살아갔습니다.
그때 저는 제가 the shit이라 생각했고, 모든 것을 “진짜 방식”으로 하고 있다고 믿었습니다. 작동은 했지만, libc에 존재하는 전체적인 sanity 레이어를 건너뛰고 있었습니다. 버퍼링, 인간이 읽기 쉬운 포맷팅, 그리고 삶을 덜 고통스럽게 만드는 기타 편리함 등 libc가 실제로 하는 모든 일을 전혀 몰랐습니다.
WTF 순간: /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 some config file 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는 대략 다음과 같이 동작합니다:
-
/etc/nsswitch.conf읽기
사용자에 대한 라인을 찾습니다:passwd: files systemd이것은 다음을 의미합니다:
- 먼저 로컬 파일(
/etc/passwd)을 확인하고 - 그 다음 systemd의 동적 사용자 데이터베이스에 질의합니다
- 먼저 로컬 파일(
-
각 NSS 모듈을 순서대로 호출
-
libnss_files.so.2→/etc/passwd에서 검색- 찾았나요? → 결과 반환
- 못 찾았나요? → 계속 진행
-
libnss_systemd.so.2→ systemd에 질의- 찾았나요? → 결과 반환
- 못 찾았나요? → 계속 진행(다른 소스가 있다면)
-
-
struct passwd반환
프로그램에 사용자 이름, UID, GID, 홈 디렉터리, 쉘 등 정보를 담은struct passwd를 전달합니다.
중요: NSS 자체는 실행 중인 프로그램이 아닙니다; 단지 설정 파일과 여러 공유 라이브러리 집합일 뿐입니다.
libc가 이를 동적으로 로드하므로 추가 프로세스가 관여하지 않습니다.
비밀 소스: libc는 순수 시스템 콜을 넘어
libc는 시스템 콜을 감싸는 얇은 래퍼 그 이상이다. 물론 write, read, open, getpid 같은 것을 감싸지만, 훨씬 많은 부가 기능을 제공한다:
| Category | Examples |
|---|---|
| 버퍼링 및 포맷팅 | printf, fprintf, fopen, getline |
| 메모리 관리 | malloc, free, realloc |
| 스레드 지원 | pthread_* functions |
| 정책 및 조회 로직 | NSS 모듈, 로케일 처리, 호스트네임 해석 |
NSS 없이 이 추가 소스는 부분적으로만 동작한다:
- 시스템 콜은 여전히 정상적으로 수행된다 (
open,read,write, …) - 버퍼링, 포맷팅, 메모리, 스레드 기능은 여전히 동작한다
- 인간이 읽을 수 있는 이름이 필요한 모든 함수(사용자, 그룹, 호스트, 서비스)는 실패하거나
NULL을 반환한다. 이는libc에 어디를 찾아야 할지 알려주는 매핑이 없기 때문이다.
NSS 모듈은 여전히 내부에서 시스템 콜을 사용합니다
NSS 모듈도 결국 시스템 콜에 의존합니다:
libnss_files.so.2는open,read,close를 사용해/etc/passwd를 읽습니다.libnss_systemd.so.2는 IPC 시스템 콜을 통해 systemd 서비스와 통신합니다.
따라서 모든 작업이 여전히 커널에 도달합니다; NSS는 단지 libc가 데이터를 어디서 그리고 어떻게 가져올지 알 수 있도록 논리 레이어를 추가합니다.
핵심:
libc+ NSS = 고급 미들웨어. 순수 시스템 콜 = 진실. NSS는 그 진실에 의미를 부여하고 인간이 쓸모 없는 숫자를 보지 않도록 합니다.
이것이 나에게 충격을 준 방식
에피파니가 오기 전, 내 도구들은 다음과 같은 작업을 수행했습니다:
getdents64로 디렉터리 열기/proc/<pid>/status를 읽어 숫자 UID 얻기- 숫자만 표시
원시 데이터를 그대로 보고 싶다면 괜찮지만, 인간에게는 지루합니다. 인간은 다음과 같이 보고 싶어합니다:
PID: 742 User: Deadpool Process: bash
대신에:
PID: 742 UID: 1000 Process: bash
NSS는 기본적으로 진실을 의미로 번역하며, libc가 모든 무거운 작업을 수행합니다.
시스템 레이어 (정신적 그림)

배운 핵심 포인트
libc는 시스템 콜을 단순히 래핑하는 것보다 훨씬 많은 일을 합니다: 버퍼링, 포맷팅, 스레딩, 메모리, 그리고 NSS.- NSS는 숫자만 필요할 경우 선택 사항이지만, 사람들은 당신의 출력물을 싫어할 것입니다.
- Systemd는 서비스와 컨테이너를 위한 동적 사용자 데이터베이스를 가지고 있으며, NSS가 이를 조회할 수 있습니다.
- Raw syscalls = 진실; NSS = 의미.
나의 반성
솔직히 말해서, 이건 큰 “오‑시트” 순간이었어요. 내 저수준 도구들은 멋졌지만, 실제로는 “사용자 친화적”도 아니고 시스템을 인식하는 것도 아니었어요. 나는 전체 사용자 공간 로직 생태계를 건너뛰고 있었죠.
이제 두 가지를 결합하는 가치가 보입니다:
- Raw syscalls → 커널과 ABI를 마스터처럼 이해한다.
libc+ NSS → 읽기 쉽고, 견고하며 실제로 유용한 도구를 만든다.
One‑Liner to Summarize
“시스템 콜은 원시적인 진실을 제공하고; NSS는
libc에 그 망을 제공합니다.”그 진실을 인간이 실제로 이해할 수 있는 형태로 바꾸기 위해.