유닉스
고친 사람 github-actions[bot]
유닉스는 컴퓨터 한 대를 여러 사람과 여러 프로그램이 나눠 쓰도록 관리하는 운영체제입니다. 벨 연구소에서 만들어 대학과 기업으로 퍼졌습니다. 뒤에 나온 운영체제들이 그 설계를 물려받았습니다. 서버에서 도는 리눅스와 macOS 도 같은 관례를 따르는 갈래라서 파일을 다루는 방법과 명령어 이름이 서로 닮았습니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 하드웨어를 여러 프로그램에게 나눠 주는 운영체제입니다. 지금은 그 한 제품보다, 그 제품이 세운 설계 관례를 함께 따르는 운영체제 무리를 가리키는 말로 더 많이 쓰입니다.
왜 이렇게 하나 — 관례가 같으면 한 곳에서 익힌 것이 다른 기계에서도 통합니다. 리눅스에서 짠 서버 코드가 맥북에서 거의 그대로 돕니다.
어떻게 도나
- 파일이든 장치든 네트워크 연결이든 「열고 · 읽고 · 쓰고 · 닫는」 같은 동작으로 다룹니다
- 작은 명령을 여럿 만들어 둡니다. 앞 명령의 출력을 뒷 명령의 입력으로 이어 씁니다
- 프로그램을 띄울 때는 돌고 있는 프로그램을 복제한 뒤 그 복제본을 다른 프로그램으로 갈아입힙니다
대가 — 파일을 뜻 없는 바이트 열로만 보기 때문에 데이터의 구조는 프로그램이 알아서 얹어야 합니다. 또 계보가 여럿으로 갈라져서 같은 이름의 명령이 시스템마다 옵션이 다릅니다.
상세
이 절은 유닉스를 두 가지로 갈라서 봅니다. 하나는 벨 연구소에서 시작한 운영체제 제품입니다. 다른 하나는 그 제품에서 퍼져 나간 설계 관례입니다. 백엔드 개발자가 매일 만나는 것은 뒤쪽입니다.
한 제품에서 한 계열로
처음의 유닉스는 벨 연구소에서 Ken Thompson 과 Dennis Ritchie 가 만든 하나의 운영체제였습니다. 곧 C 언어로 다시 짜였습니다. 그래서 기계가 바뀌어도 코드를 조금만 고쳐 옮길 수 있게 됐습니다. 그때까지 운영체제는 기계마다 따로 짜는 물건이었기 때문에 이것은 큰 차이였습니다.
소스 코드가 대학으로 퍼졌습니다. 여러 곳이 각자 고쳐 쓰기 시작했습니다. 갈라진 것들끼리 동작이 조금씩 달라지자, 공통으로 지킬 것을 문서로 묶어 표준으로 삼았습니다. 그것이 POSIX(Portable Operating System Interface, 이식 가능 운영체제 인터페이스)입니다.
여기서 낱말 둘이 갈립니다. 문서로 묶인 쪽은 표준(POSIX)입니다. 문서와 무관하게 굳은 쪽은 설계 관례입니다. 관례로 굳은 것 가운데 문서로 묶인 몫이 POSIX 입니다.
오늘날 「유닉스」라는 이름은 두 뜻으로 쓰입니다. 대문자로 적는 UNIX 는 상표라서, 심사를 받고 값을 치른 제품만 그 이름을 씁니다. 그 심사를 안 받았지만 같은 표준을 따르는 운영체제는 「유닉스 계열」이라고 부릅니다.
코드를 물려받은 것과 표준만 같이 지키는 것이 함께 「유닉스 계열」로 불립니다. FreeBSD 가 앞쪽이고 리눅스가 뒤쪽입니다.
모든 것을 파일로 다루는 관례
유닉스의 중심 설계 관례는 하나입니다. 입출력 대상을 되도록 같은 모양으로 다룬다는 것입니다. 디스크에 있는 파일이든 터미널이든 소켓이든, 프로그램은 「열고 · 읽고 · 쓰고 · 닫는」 네 가지 동작으로 씁니다.
프로그램이 무언가를 열면 운영체제는 작은 정수 하나를 돌려줍니다. 이 번호를 파일 디스크립터라고 합니다. 그 뒤로 프로그램은 대상의 정체를 몰라도 이 번호만 들고 읽고 쓸 수 있습니다.
장치도 마찬가지입니다. 유닉스 계열은 디스크나 사운드 카드 같은 하드웨어를, 파일 트리에서
장치들이 모이는 /dev 아래의 장치 파일로 드러냅니다.
flowchart TD
P["프로그램"] --> FD["파일 디스크립터 · 작은 정수 하나"]
subgraph T["번호 뒤에 붙는 대상"]
F["파일"]
TM["터미널"]
SK["소켓"]
DV["장치 파일 · /dev 아래"]
end
FD --> F
FD --> TM
FD --> SK
FD --> DV
성격이 다른 넷이 번호 하나로 모입니다. 파일을 읽도록 짠 코드가 네트워크 연결에도 그대로 붙습니다. 이 관례가 있어서 프로그램을 키보드 대신 파일에서 읽게 바꾸는 일도 명령 한 줄로 됩니다.
커널과 그 위에서 도는 프로그램
유닉스는 소프트웨어를 두 층으로 가릅니다. 아래층은 커널입니다. 커널은 메모리와 실행 순서와 장치를 직접 다루는 프로그램입니다. 컴퓨터가 켜져 있는 동안 계속 떠 있습니다.
위층은 그냥 프로그램들입니다. 여기서 중요한 것은 셸도 그중 하나라는 점입니다. 명령을 받아 프로그램을 띄워 주는 셸은 커널의 일부가 아니라 갈아 끼울 수 있는 부품이라서, 사람마다 다른 셸을 골라 씁니다.
위층 프로그램이 커널에게 일을 부탁하는 통로가 시스템 콜입니다. 파일을 열거나 프로세스를 띄우는 것처럼 하드웨어가 걸린 일은 전부 이 통로를 지납니다.
flowchart TD
subgraph U["사용자 쪽"]
S["셸"] --> C["명령 · 서버 프로그램"]
end
C -->|시스템 콜| K
S -->|시스템 콜| K
subgraph KS["커널 쪽"]
K["커널"] --> D["장치 드라이버"]
end
D --> H["하드웨어"]
셸이 명령을 띄우는 화살표와 시스템 콜 화살표는 따로입니다. 셸은 명령을 대신 실행해 줄 뿐입니다. 커널에게 부탁하는 일은 명령 자신이 합니다.
커널 아래에 붙은 장치 드라이버는 디스크나 랜카드처럼 한 종류의 장치를 다룰 줄 아는 코드입니다. 커널이 장치마다 다른 세부를 몰라도 되게 해 줍니다.
작은 명령과 파이프
유닉스의 명령들은 한 가지 일만 하도록 잘게 나뉘어 있습니다. 줄 수를 세는 명령, 글자를 찾는 명령, 정렬하는 명령이 따로 있습니다. 하나로 뭉치지 않은 것은 조합해서 쓰기 위해서입니다.
명령끼리 데이터를 주고받는 통로가 파이프입니다. 프로그램이 결과를 내보내는 기본 통로가 표준 출력입니다. 받아들이는 기본 통로는 표준 입력입니다. 평소에는 각각 화면과 키보드에 붙어 있습니다.
파이프는 앞 명령의 표준 출력을 뒷 명령의 표준 입력에 곧바로 이어 줍니다. 셸에서는 세로 막대
기호로 씁니다. 글자를 찍는 echo 와 글자·줄을 세는 wc 를 이어 봅니다.
echo hello | wc -c # 6: 줄바꿈까지 센다
echo hello | wc -l # 1: 줄 수
앞 명령이 찍은 글자가 화면 대신 뒷 명령으로 흘러 들어갔습니다.
이어 붙이는 데이터가 글자라는 점이 중요합니다. 명령마다 자기만의 데이터 형식을 쓰면 짝이 맞는 명령끼리만 이을 수 있습니다. 형식을 글자로 통일해 두면 아무 명령이나 이어집니다.
지금도 서버에서 로그를 뒤지거나 배포 스크립트를 짤 때 이 방식을 그대로 씁니다. 정해진 기능을 고르는 대신 필요한 조합을 그때그때 만들어 쓰는 것이 유닉스식 사용법입니다.
복제와 갈아타기로 만드는 프로세스
프로세스는 실행 중인 프로그램 하나를 운영체제가 관리하는 단위입니다. 유닉스는 새 프로세스를 만들 때 「새로 띄운다」가 아니라 「지금 것을 복제한다」로 시작합니다. 이 복제가 fork 입니다.
복제된 자식은 부모와 똑같은 상태로 섭니다. 여기서 자식이 exec 를 부르면 내용물만 다른 프로그램으로 바뀝니다. 프로세스 번호와 열어 둔 파일 디스크립터는 그대로 남습니다.
sequenceDiagram
participant 셸
participant 커널
participant 자식
셸->>커널: fork · 나를 복제해라
커널-->>셸: 자식 번호를 돌려준다
커널->>자식: 셸과 똑같은 복제본이 선다
셸->>자식: 입출력을 파일·파이프로 바꿔 놓는다
자식->>커널: exec · 이 프로그램으로 갈아입어라
Note over 자식: 번호는 그대로, 내용물만 바뀐다
자식-->>셸: 끝나면서 종료 코드를 남긴다
두 걸음으로 나누면 그 사이에 손을 댈 틈이 생깁니다. 셸은 복제 직후, 아직 프로그램을 갈아입기 전에 자식의 입출력을 파일이나 파이프로 바꿔 놓습니다. 그래서 명령 하나는 자기 출력이 어디로 가는지 몰라도 됩니다.
프로세스가 끝날 때는 숫자 하나를 남깁니다. 이 종료 코드가 0 이면 성공, 아니면 실패로 읽는 것이 관례입니다. 스크립트가 앞 명령의 성패를 보고 다음 줄을 정하는 근거가 이 값입니다.
종료 코드가 끝나면서 남기는 값이라면, 도는 중에 밖에서 넣는 짧은 통지가 시그널입니다. 「멈춰라」처럼 짧은 지시를 돌고 있는 프로세스에게 보낼 때 씁니다.
뿌리 하나에서 뻗는 파일 트리
유닉스에는 드라이브 문자가 없습니다. 파일 트리의 뿌리는 / 하나뿐입니다. 디스크를 더 붙이면
그 트리의 어느 디렉터리에 걸어 둡니다. 이 걸어 두는 일을 마운트라고 합니다.
그래서 프로그램은 데이터가 어느 디스크에 있는지 몰라도 됩니다. 경로만 알면 됩니다. 저장 장치를 바꿔 끼워도 경로가 같으면 코드를 안 고칩니다.
디렉터리 이름도 대체로 정해져 있습니다. 설정은 /etc, 로그와 그때그때 바뀌는 데이터는
/var, 사용자 데이터는 /home 아래에 둡니다. 이 관례가 있어서 처음 보는 서버에 들어가도
어디를 열어 볼지 짐작할 수 있습니다.
flowchart TD
R["/ · 뿌리"]
R --> E["/etc · 설정"]
R --> V["/var · 로그"]
R --> H["/home · 사용자 데이터"]
R --> D2["/dev · 장치 파일"]
subgraph M["나중에 붙인 둘째 디스크"]
MD["그 안의 파일들"]
end
MD -->|마운트| H
주인·그룹·나머지 세 칸으로 나눈 권한
유닉스는 여러 사람이 한 기계를 같이 쓰는 것을 전제로 만들어졌습니다. 그래서 파일마다 누가 무엇을 할 수 있는지를 붙여 둡니다. 이것이 권한입니다.
칸은 셋입니다. 파일 주인, 주인이 속한 그룹, 그 밖의 모두입니다. 칸마다 읽기·쓰기·실행을 따로 켜고 끕니다. 숫자로 적을 때는 한 칸이 읽기 4 · 쓰기 2 · 실행 1 의 합입니다. 6 은 4+2 라서 읽기와 쓰기를 뜻합니다.
아래는 권한을 바꾸고 확인하는 명령입니다.
chmod 640 secret.txt # 주인 읽기·쓰기, 그룹 읽기
ls -l secret.txt # -rw-r-----
640 의 세 숫자가 세 칸에 차례로 대응합니다. 글자 표기도 같은 순서입니다. 맨 앞 한 글자는
파일 종류입니다. 그 뒤 세 글자씩이 세 칸입니다. 셋째 칸이 비었으니 다른 사용자는 이 파일을
열 수 없습니다.
block-beta columns 3 a["6"] b["4"] c["0"] d["rw-"] e["r--"] f["---"] g["주인"] h["그룹"] i["나머지"]
이 검사에서 예외인 계정이 하나 있습니다. 관리자 계정인 루트는 권한 검사를 통과합니다. 서버 작업을 평소에 루트로 하지 않는 관례는 여기서 나옵니다. 실수 한 번의 범위가 다릅니다.
갈라진 두 계보와 리눅스
유닉스는 크게 두 갈래로 자랐습니다. 벨 연구소가 이어 간 계보와 버클리 대학이 고쳐 키운 계보입니다. 앞쪽에서 기업들이 파는 상용 유닉스가 나왔습니다. 뒤쪽에서는 FreeBSD 와 macOS 가 나왔습니다.
flowchart TD
U["유닉스 · 벨 연구소"] -->|코드를 물려받음| SV["벨 연구소 계보 · 상용 유닉스"]
U -->|코드를 물려받음| BS["버클리 계보 · FreeBSD · macOS"]
SV -->|표준만 따름| P["POSIX · 공통 표준"]
BS -->|표준만 따름| P
L["리눅스 · 코드는 새로 짰다"] -->|표준만 따름| P
리눅스는 이 그림에서 뻗어 나온 곳이 다릅니다. 유닉스 코드를 물려받은 것이 아니라, 같은 표준을 보고 커널을 처음부터 새로 짠 것입니다. 그래서 「유닉스에서 갈라져 나왔다」가 아니라 「유닉스 계열」이라고 부릅니다. 안드로이드도 리눅스 커널 위에 서 있습니다.
계보가 갈린 흔적은 지금도 남아 있습니다. 같은 이름의 명령이 리눅스와 macOS 에서 옵션을
다르게 받습니다. 파일을 직접 고치는 sed 의 -i 옵션이 그런 경우입니다. 리눅스에서 쓰던
스크립트가 맥북에서 걸리는 흔한 이유입니다.
유닉스가 안 하기로 한 것
무엇을 뺐는지를 보면 이 설계가 더 또렷해집니다. 첫째로 파일에 구조를 넣지 않았습니다. 유닉스에게 파일은 뜻 없는 바이트 열입니다. 레코드나 색인 같은 개념이 운영체제에 없습니다.
그 대가는 위층이 집니다. 데이터베이스는 자기 파일 형식과 색인을 스스로 얹어야 합니다. 얻은 것은 단순함입니다. 파일을 다루는 호출이 몇 개뿐이라 모든 프로그램이 같은 방법을 씁니다.
둘째로 화면과 창을 커널에 넣지 않았습니다. 그래픽은 위층 프로그램이 맡습니다. 서버에 리눅스를 깔면 화면 없이도 온전히 돌아갑니다.
셋째로 응답 시각을 약속하지 않습니다. 커널은 프로세스들에게 실행 시간을 나눠 주지만, 어떤 일이 몇 밀리초 안에 끝난다고 보장하지는 않습니다. 그런 보장이 필요한 기계에는 실시간 운영체제를 따로 씁니다.
관련 항목
유닉스가 속하는 상위 분류
운영체제 · 커널 · 시분할 시스템 · 시스템 프로그래밍 · 멀티유저 시스템
유닉스 설계를 물려받은 운영체제
Linux · macOS · FreeBSD · OpenBSD · NetBSD · Solaris · AIX · HP-UX · Android · BSD · System V · MINIX · Darwin
유닉스 인터페이스를 정의하는 표준
POSIX · Single UNIX Specification · C 언어 · C 표준 라이브러리 · glibc
유닉스가 파일처럼 다루는 대상
파일 · 파일 디스크립터 · 소켓 · 파이프 · 장치 파일 · 터미널 · 유닉스 도메인 소켓 · 아이노드 · 심볼릭 링크 · 표준 입력 · 표준 출력 · 표준 오류
유닉스가 프로세스를 다루는 호출
fork · exec · wait · 시스템 콜 · 시그널 · 종료 코드 · 프로세스 · 좀비 프로세스 · 데몬 · 프로세스 그룹
유닉스 파일 트리를 이루는 구조
파일 시스템 · 마운트 · 루트 디렉터리 · 디렉터리 · 경로 · chroot · 가상 파일 시스템
유닉스가 접근을 막는 규칙
권한 · chmod · 루트 계정 · sudo · setuid · 접근 제어 목록 · 사용자와 그룹
유닉스에서 명령을 엮어 쓰는 도구
셸 · bash · 리다이렉션 · grep · sed · awk · cron · make · vi · 파이프라인
유닉스 설계를 요약하는 원칙
유닉스 철학 · 모든 것은 파일이다 · 텍스트 스트림 · 이식성
유닉스와 맞세워지는 다른 계통
다른 이름: Unix · UNIX · unix · 유닉스 계열 · Unix-like