
이전 글에 이어 리눅스 기본 개념에 대해 작성한다.
사용자와 소유권, 프로세스 관리, 시그널 등에 관련된 내용이다.
처음 알게 된 내용도 많았지만, 배우다 보니 정말 기본적인 개념들이라고 느꼈다.
사용자의 종류
root 사용자: 모든 권한을 가진 관리자 계정으로 시스템 전체에 접근 가능
시스템 사용자: 리눅스가 자체적으로 생성하는 계정들로, 특정 서비스 실행용
일반 사용자: root 및 시스템 사용자 이외의 모든 사용자 계정
username@host:/$ cd root
-bash: cd: root: Permission denied # 일반 사용자는 root 디렉토리 접근 불가
root 사용자 권고사항
root 사용자 비밀번호를 반드시 안전하게 관리
시스템 외부에서 root 계정 직접 로그인을 금지하는 것이 보안에 도움이 됨
# root 사용자로 전환하기
username@host:/$ sudo -s
[sudo] password for username:
root@host:/# whoami
root
# root 세션 종료
root@host:~# exit
exit
username@host:/$ whoami
username
root 사용자 권한으로 명령 실행하는 방법
su
다른 사용자(대부분 root)로 전환
형식: su [옵션] [-] 사용자
sudo
현재 사용자 권한으로 특정 명령을 root 권한으로 실행
형식: sudo [옵션] [명령어]
sudo apt update # 패키지 목록 업데이트
sudo systemctl restart ssh # SSH 서비스 재시작
runuser
비대화형 스크립트에서 root 권한으로 명령 실행
/etc/passwd 파일 정보
계정명, UID, GID, 홈 디렉토리, 기본 셸 등의 정보 저장
비밀번호는 x로 표기되며, /etc/shadow에 별도 저장
username:x:1000:1000:설명:/home/username:/bin/bash
│ │ │ │ │ │ │
│ │ │ │ │ └─홈 디렉토리
│ │ │ │ └─────기본 셸
│ │ │ └─────────GID(그룹ID)
│ │ └─────────────UID(사용자ID)
│ └────────────────암호화 비밀번호(x=shadow 파일 참조)
└────────────────────계정명
username@host:/$ w
09:19:44 up 15 min, 2 users, load average: 0.12, 0.12, 0.09
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
username :0 :0 09:04 ?xdm? 55.87s 0.02s /usr/lib/gdm3/gdm-x-session
username pts/0 192.168.1.100 09:08 0.00s 0.09s 0.00s w
username@host:/$ tail /etc/passwd
fwupd-refresh:x:122:127:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
geoclue:x:123:128::/var/lib/geoclue:/usr/sbin/nologin
pulse:x:124:129:PulseAudio daemon,,,:/var/run/pulse:/usr/sbin/nologin
gdm:x:126:131:Gnome Display Manager:/var/lib/gdm3:/bin/false
sssd:x:127:132:SSSD system user,,,:/var/lib/sss:/usr/sbin/nologin
username:x:1000:1000:username,,,:/home/username:/bin/bash # 여기에 표기됨
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
vboxadd:x:998:1::/var/run/vboxadd:/bin/false
sshd:x:128:65534::/run/sshd:/usr/sbin/nologin
/etc/group 파일 정보
사용자 그룹 정보를 관리하는 시스템 파일
사용자 그룹 이름, 비밀번호(x로 표시), 사용자 그룹, 사용자 목록 등을 표기
그룹명:그룹비밀번호:GID:그룹멤버목록
username@host:/$ tail /etc/group
pulse:x:129:
pulse-access:x:130:
gdm:x:131:
sssd:x:132:
lxd:x:133:username
username:x:1000:
sambashare:x:134:username
systemd-coredump:x:999:
vboxsf:x:998:
rdma:x:135:
사용자 추가 명령어
| 명령어 | 특징 | 권장 상황 |
|---|---|---|
| adduser | 초보자 친화적, 비밀번호 등 자동 설정 | 일반 사용자 추가 |
| useradd | 수동 설정, 옵션 많음 (원본 명령어) | 고급 사용자, 스크립트 자동화 |
# super user 권한으로 전환
username@host:/$ sudo -s
[sudo] password for username:
# 사용자 추가 (-m: 홈 디렉토리 생성)
root@host:/# useradd -m newuser
# 확인
root@host:/# cat /etc/passwd | grep newuser
newuser:x:1001:1001::/home/newuser:/bin/sh
# bash 셸로 변경
chsh -s /bin/bash newuser
# 또는 생성 시 지정
useradd -m -s /bin/bash newuser
사용자 삭제 명령어
| 명령어 | 특징 |
|---|---|
| deluser | 홈 디렉토리/메일 등 함께 삭제 가능 |
| userdel | 기본 삭제,-r옵션으로 홈 디렉토리 동시 삭제 |
# 홈 디렉토리 포함 사용자 삭제
sudo deluser --remove-home newuser
# 또는
sudo userdel -r newuser
사용자 그룹 추가 및 삭제하기
# 그룹 생성
sudo addgroup newgroup
# 그룹 삭제 (그룹에 속한 사용자 없어야 함)
sudo delgroup newgroup
사용자를 그룹에서 추가 및 제거하기
# 사용자를 그룹에 추가
sudo usermod -aG newgroup newuser
# -a: append(추가), -G: supplementary group
# 그룹에서 사용자 제거
sudo gpasswd -d newuser newgroup
# 현재 사용자의 그룹 확인
groups newuser
id newuser
그룹 관련 옵션
a : 기존 그룹 유지하고 추가
G : 보조 그룹 지정 (기본 그룹은 변경되지 않음)
id username : UID, GID, 모든 그룹 정보 확인
su 명령어
su : 현재 디렉토리 유지
su - username : 홈 디렉토리로 이동 + 환경변수 로드
su -l username : 로그인 셸 완전 환경 (su - 와 동일함)
exit : 이전 사용자 복귀
su 명령어 사용 시 특이사항
root → 일반 사용자: 비밀번호 불필요
일반 사용자 → 다른 사용자: 대상 사용자 비밀번호 입력
su - username : 홈 디렉토리 + 환경변수까지 완전 전환
| 상황 | 명령어 | 비밀번호 | 환경변수 |
|---|---|---|---|
| root → 사용자 | su username | 불필요 | 유지 |
| 사용자 → root | su - | root 비번 | 로드 |
| 사용자A → 사용자B | su newuser | B의 비번 | 유지 |
| sudo 대안 | sudo -i | sudo 비번 | root 환경 |
# super user는 비밀번호 입력 없이 사용자 전환 가능
root@host:/home# su username
username@host:/home$ su newuser
Password: # 대상 사용자(newuser)의 비밀번호 필요
newuser@host:/home$ whoami
newuser
passwd 명령어
현재 사용자 자신의 비밀번호 변경: passwd 단독 실행
root가 타 사용자 비밀번호 변경: passwd 사용자명
passwd -l username # 비밀번호 잠금
passwd -u username # 비밀번호 잠금 해제
passwd -e username # 다음 로그인 시 비밀번호 변경 강제
passwd -S username # 비밀번호 상태 확인
# 사용자 비밀번호 변경 (root 권한 필요)
root@host:/home# passwd newuser
New password: # 새 비밀번호 입력
Retype new password: # 재입력
passwd: password updated successfully
파일 소유권
파일의 소유자(사용자)와 소유 그룹을 나타내는 속성
ls -l 명령어에서 파일명 앞에 표시됨
-rwxrwxr-x 1 username username 16696 11월 27 10:21 hello
↑소유자 ↑소유그룹
파일 소유권 변경하기
chown 을 사용하면 파일의 소유권을 변경할 수 있음
chown [옵션] 사용자[:그룹] 파일명
사용 예시
*# 소유자만 변경*
sudo chown newuser hello
*# 소유자와 그룹 모두 변경*
sudo chown newuser:developers hello
*# 하위 디렉토리까지 재귀 변경*
sudo chown -R newuser:developers /home/project
파일 권한
읽기(r), 쓰기(w), 실행(x) 권한이 소유자(u), 소유그룹(g), 일반사용자(o)에게 각각 부여됨
파일 권한 표기법
8진수 표기법
| 8진수 | 2진수 | 권한 | 의미 |
|---|---|---|---|
| 0 | 000 | --- | 아무 권한 없음 |
| 1 | 001 | --x | 실행 권한 있음 |
| 2 | 010 | -w- | 쓰기 권한 있음 |
| 3 | 011 | -wx | 쓰기 + 실행 권한 있음 |
| 4 | 100 | r-- | 읽기 권한 있음 |
| 5 | 101 | r-x | 읽기 + 실행 권한 있음 |
| 6 | 110 | rw- | 읽기 + 쓰기 권한 있음 |
| 7 | 111 | rwx | 읽기 + 쓰기 + 실행 권한 모두 있음 |
의미 표기법(기호 표기법)
누구에게 어떻게 무엇을 주는지 정보를 표기
| 구분 | 표기 | 의미 |
|---|---|---|
| 누구에게 | u | 소유자(user) |
| g | 소유 그룹(group) | |
| o | 일반 사용자(others) | |
| a | 모두(u+g+o) | |
| 어떻게 | + | 권한 추가 |
| - | 권한 제거 | |
| = | 권한 설정(교체) | |
| 무엇을 | r | 읽기 권한(Read) |
| w | 쓰기 권한(Write) | |
| x | 실행 권한(Execute) |
파일 권한 변경 예시
chmod u+x file.sh # 소유자에게 실행권한 추가
chmod g+rw file.txt # 그룹에 읽기+쓰기 추가
chmod 755 script.sh # 8진수로 직접 설정
chmod o-rwx secret # 일반사용자에게 모든 권한 제거
디렉토리 권한
읽기 : 디렉토리 내 파일 목록을 읽을 권한
쓰기 : 디렉토리 안에 파일을 생성할 권한
실행 : 디렉토리 내 파일에 접근 권한
| 권한 | 의미 | 파일에서의 의미 | 디렉토리에서의 의미 |
|---|---|---|---|
| r | 읽기(Read) | 파일 내용 읽기 | 디렉토리 내 파일 및 하위 디렉토리 목록 확인 |
| w | 쓰기(Write) | 파일 내용 변경 및 삭제 | 디렉토리 내 파일 및 하위 디렉토리 생성/삭제 |
| x | 실행(Execute) | 실행 가능한 파일 실행 | 디렉토리에 접근(c d명령어로 이동) 및 파일 접근 |
디렉토리 권한의 특이사항
r 권한만 있으면 디렉토리 내 파일 목록을 볼 수 있지만, x 권한이 없으면 접근 자체가 불가능
w 권한이 있어야 디렉토리 내 이름 변경, 생성, 삭제 작업 가능
디렉토리 권한 사용 예시
# 디렉토리 생성 및 권한 설정
mkdir project
chmod 755 project # 소유자:rwx, 그룹/기타:rx
chmod 700 secret_folder # 소유자만 접근 가능
# 확인
ls -ld project
drwxr-xr-x 2 username username 4096 11월 28 10:40 project
컴퓨터의 작동 원리
컴퓨터는 CPU가 명령어를 순차적으로 실행하는 방식으로 작동
멀티태스킹이란 여러 프로그램을 동시에 실행하기 위해 OS가 CPU 사용 시간을 프로세스에 분배하는 것
리눅스는 preemptive multitasking 방식을 사용
프로세스
프로세스는 실행 중인 프로그램을 의미하며, 리눅스에서 기본적인 스케줄링 단위
task_struct는 리눅스 커널에서 프로세스와 스레드를 관리하는 핵심 구조체 (Windows: instance)
PID, 상태, 메모리 정보, 파일 디스크립터 등을 포함
리눅스: Process Oriented
┌─────────────────┐
│ 스케줄링 엔티티 │ ← CPU가 스케줄링하는 최소 단위
├─────────────────┤
│ Process │ ← 사용자 프로세스
│ Thread │ ← 사용자 pthread, 커널 kthread
└─────────────────┘
↑ 관리 구조체: task_struct (리눅스에서 프로세스/스레드 정보 저장)
리눅스의 프로세스 계층 구조
모든 프로세스가 PID 1(systemd)에서 시작하여 트리 구조로 관리됨
부모-자식 관계를 통해 프로세스 생명주기를 제어
부모 프로세스와 자식 프로세스
부모 프로세스(Parent Process) : 다른 새 프로세스(자식 프로세스)를 생성하는 프로세스
자식 프로세스(Child Process) : 부모 프로세스가 생성한 프로세스
프로세스 생성 원리
리눅스에서 fork() 시스템 콜로 부모 프로세스는 자신과 거의 동일한 자식 프로세스를 생성
자식 프로세스는 부모의 메모리 공간을 복사받고 독립적으로 실행 가능
자식 프로세스가 exec() 시스템 콜을 통해 새로운 프로그램으로 대체될 수 있음
username@host:~$ ps -el | head
F S UID PID PPID C ... CMD
4 S 0 1 0 0 ... systemd # PID 1, 최상위 부모 프로세스
...
0 S 1000 1820 1819 ... bash # bash 프로세스, 부모는 PID 1819
0 S 1000 123551 1820 ... sleep # sleep 프로세스, 부모는 bash 1820
username@host:~$ pstree -p
systemd(1)─┬─bash(1820)─┬─sleep(123551)
│ └─ps(123552)
...
init 프로세스
init 프로세스는 리눅스 시스템에서 가장 먼저 실행되는 프로세스(PID 1)
시스템 부팅 후 모든 다른 프로세스의 부모 역할
systemd
```
과거: SysV init → /sbin/init
현재: systemd → /sbin/init (대체 구현)
```
systemd는 SysV init을 업그레이드한 현대적 init 시스템
병렬 부팅, 서비스 의존성 관리, 로그 통합 등을 제공
username@host:~$ ps -elf | head
F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD
4 S root 1 0 0 80 0 - 42390 - 09:04 ? 00:00:18 /sbin/init splash
1 S root 2 0 0 80 0 - 0 - 09:04 ? 00:00:00 [kthreadd]
username@host:~$ ps -el | grep system
4 S 0 1 0 0 80 0 - 42390 - ? 00:00:18 systemd ← PID 1
4 S 0 255 1 0 79 -1 - 19693 - ? 00:00:01 systemd-journal ← systemd 자식
4 S 0 298 1 0 80 0 - 6267 - ? 00:00:00 systemd-udevd ← systemd 자식
시그널
프로세스나 커널 간에 전달되는 비동기 신호
특정 이벤트 발생을 알리거나 프로세스를 제어하기 위해 사용
프로세스는 시그널을 받아 적절한 동작을 하거나 무시할 수 있음 (일부 시그널은 무시 불가)
Ctrl+C는 SIGINT(2) 시그널을 보내 프로그램을 정상 종료시킴
시그널 목록
username@host:~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
주요 시그널
| 번호 | 이름 | 의미 | 사용 예시 |
|---|---|---|---|
| 2 | SIGINT | Ctrl+C (중단 요청) | kill -2 PID |
| 9 | SIGKILL | 강제 종료 (거부 불가) | kill -9 PID |
| 15 | SIGTERM | 정상 종료 요청 | kill PID(기본값) |
프로세스 목록
ps 명령어로 프로세스 목록 확인
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 1820 1819 0 80 0 - 5202 do_wai pts/0 00:00:00 bash
ps 명령어 주요 필드 및 의미
| 필드 | 설명 |
|---|---|
| S | 프로세스 상태 (State) |
| PID | 프로세스 ID (Process ID) |
| PPID | 부모 프로세스 ID (Parent Process ID) |
| PRI | 우선순위 (Priority), 값이 낮을수록 우선순위 높음 |
| NI | Nice 값, -20~19 범위, 값이 높을수록 우선순위 낮음 |
프로세스 상태 (S 필드 주요 값)
| 상태 코드 | 의미 | 설명 |
|---|---|---|
| R | Running (실행 중) | CPU에서 실행 중이거나 실행 준비 상태 |
| S | Sleep (자고 있음) | 입출력 등 이벤트 대기 상태 |
| I | Idle (짧은 대기) | 커널에서 짧게 대기 중 |
| Z | Zombie (좀비) | 종료되었으나 부모가 아직 상태를 수거하지 않은 상태 |
| T | Stopped (중지됨) | SIGSTOP 등의 신호를 받아 중지된 상태 |
| D | Uninterruptible Sleep (잘못된 대기) | 디스크 I/O 등 중단 불가능한 대기 상태 |
백그라운드 작업 관리
jobs # 백그라운드 작업 목록
fg %1 # 1번 작업 전경 실행
bg %1 # 1번 작업 백그라운드 계속
kill %1 # 작업 번호로 종료
시그널 전송 예시
username@host:~$ sleep 100 &
[1] 123603 # 백그라운드 작업 시작
username@host:~$ ps
PID TTY TIME CMD
1820 pts/0 00:00:00 bash
123603 pts/0 00:00:00 sleep # 백그라운드 sleep 프로세스
123604 pts/0 00:00:00 ps
username@host:~$ kill -9 123603 # SIGKILL로 강제 종료
username@host:~$ ps
PID TTY TIME CMD
1820 pts/0 00:00:00 bash
123605 pts/0 00:00:00 ps
[1]+ Killed sleep 100 # 종료 완료
프로세스의 생애 주기
프로세스의 생성부터 종료까지의 과정
프로세스의 상태
| 상태 | 설명 |
|---|---|
| 생성 (New) | 프로그램 실행 명령이 입력되어 새 프로세스가 생성되는 단계 |
| 메모리 및 자원 할당, 프로세스 제어 블록 초기화 | |
| 준비 (Ready) | 실행을 위한 모든 준비가 완료되어 CPU 할당을 기다리는 상태 |
| 실행 (Running) | CPU를 할당받아 프로세스가 실제 작업을 수행하는 상태 |
| 대기(수면) (Waiting/Sleep) | 입출력이나 특정 이벤트가 발생하기를 기다리는 상태 |
| 다시 준비상태로 전환될 수 있음 | |
| 중단 (Stopped) | 외부 시그널(예: SIGSTOP)을 받아 일시 중단된 상태 |
| 실행이 일시 중지됨 | |
| 종료 (Terminated) | 프로세스가 작업을 완료하거나 종료됨 |
| 시스템 자원을 해제하는 단계 | |
| 좀비 (Zombie) | 종료 후 부모 프로세스가 자식의 종료 상태를 수거하지 않음 |
| 프로세스 테이블에 정보가 남아있는 상태 |
멀티태스킹을 위한 프로세스 상태 상세
실행/실행 대기 상태 : 프로세스가 CPU에 의해 실행 중이거나 실행 권한을 기다리는 상태
중단 상태 : 사용자가 수동으로 프로세스를 멈췄거나, 시스템에서 중단 신호를 받았을 때의 상태
종료 상태 & 좀비 상태: 좀비 상태는 이미 실행 종료됐지만, 부모 프로세스가 종료 상태를 수거하지 않음
수면 상태: 이벤트가 발생할 때까지 휴식하며 CPU 사용을 하지 않는 상태로, 깨어날 수 있는 상태(Interruptible Sleep)와 깨어날 수 없는 상태(Uninterruptible Sleep)로 나눔
프로세스 생성
fork() 시스템 콜을 통해 부모 프로세스가 자식 프로세스를 생성
자식 프로세스는 부모와 거의 동일한 메모리 공간과 정보를 복사하며 독립적으로 실행
fork()는 부모와 자식에게 각각 반환값을 다르게 주고 서로 다르게 동작 가능하게 함
프로세스 관리 정보: task_struct
스케줄러는 task_struct라는 구조체(프로세스 제어 블록) 단위로 리눅스 내 프로세스를 관리
프로세스 ID, 상태, 메모리 주소, 우선순위, 파일 디스크립터 등을 저장
운영체제는 이 정보를 바탕으로 프로세스를 스케줄링하고 자원을 분배
스레드 (Threads)
스레드는 프로세스와 메모리 공간을 공유하면서도 독립적인 실행 흐름(스택, 레지스터)을 가짐
한 프로세스 내 여러 스레드를 가질 수 있으며, 멀티태스킹을 세밀하게 수행하는 데 활용됨
리눅스에서는 사용자 스레드(pthread)와 커널 스레드(kthread)가 존재
파일 디스크립터 (File Descriptor)
파일을 관리하는 추상화된 정수값으로, 프로세스가 파일(또는 장치)에 접근할 때 사용
각 프로세스는 고유한 파일 디스크립터 테이블을 가짐
새 파일을 열면 가장 작은 사용 가능한 숫자가 할당됨
int fd = open("test.txt", O_RDWR); // fd = 3 (0,1,2는 표준 스트림 사용 중)
read(fd, buffer, size); // 파일 읽기
write(fd, data, size); // 파일 쓰기
close(fd); // 디스크립터 해제
파일 입출력의 두 가지 방식
| 방식 | 특징 | 사용 함수 | 예시 |
|---|---|---|---|
| 저수준 | 파일 디스크립터(int fd) 사용,버퍼를 사용하거나 미사용(미사용은 장치 접근 적합) | open(), read(), write(), close() | int fd = open("file.txt", O_RDONLY); |
| 고수준 | 파일 스트림(FILE* fp) 사용 | ||
| 앱처럼 기본 버퍼 포함된 프로그램에 적합 | fopen(), fread(), fwrite(), fclose() | FILE* fp = fopen("file.txt", "r"); |
표준 스트림
모든 프로세스는 시작 시 3개의 파일 디스크립터가 자동 할당됨
| FD | 이름 | 용도 | C stdio | 셸 리다이렉트 |
|---|---|---|---|---|
| 0 | stdin | 키보드 입력 | stdin | < |
| 1 | stdout | 일반 출력 | stdout | >,>> |
| 2 | stderr | 에러 출력 | stderr | 2>,2>> |
시스템 디바이스 파일 확인
username@host:~$ ls /dev/std*
/dev/stderr /dev/stdin /dev/stdout
stdin, stdout 사용 예시
username@host:~$ cat
hello world # 입력 → stdin (FD 0)
hello world # 반복 출력
^C # Ctrl+C로 종료
username@host:~$ echo hi # 출력 -> stdout (FD 1)
hi
username@host:~$ echo hi > a # 파일로 리다이렉트
username@host:~$ echo hello >> a # 파일에 추가
stderr 사용 예시
username@host:~$ cc # 컴파일러 오류
cc: fatal error: no input files
compilation terminated.
username@host:~$ cc > errmsg # stdout만 리다이렉트, stderr는 터미널 출력
cc: fatal error: no input files
compilation terminated.
username@host:~$ cc 2> errmsg # stderr (FD 2) 리다이렉트
username@host:~$ cat errmsg # stdout과 서로 다른 FD이므로 별도 제어 필요
cc: fatal error: no input files
compilation terminated
포어그라운드 프로세스 (Foreground Process)
사용자가 직접 입력과 출력을 제어할 수 있는 프로세스
터미널과 연결되어 있어 명령어 실행 시 결과를 바로 확인 가능
자식 프로세스가 부모로부터 생성되면 기본적으로 포어그라운드로 실행됨
실행 중에는 터미널을 잡고 있어 다른 명령어 입력 제한
백그라운드 프로세스 (Background Process)
터미널 입력(stdin)을 받지 않고, 터미널과 독립적으로 실행되는 프로세스
출력(stdout, stderr)은 가능하지만 사용자와 직접 상호작용하지 않음
명령어 끝에 &를 붙여 실행하면 백그라운드 프로세스로 동작
job의 개념
예전에는 여러 프로그램을 순서대로 실행하는 작업 단위를 의미
현대 리눅스 셸에서는 포어그라운드/백그라운드 실행 중인 작업 단위
jobs 를 입력하면 현재 실행 중인 job 목록 확인 가능
포어그라운드/백그라운드 프로세스 실행 예시
# 일반적으로 명령어를 실행하면 포어그라운드 프로세스로 동작
username@host:~$ ./a.out
# 명령어 끝에 & 붙이면 백그라운드 프로세스로 동작
username@host:~$ ./a.out &
[1] 12345 # job 번호와 PID 출력
# 백그라운드에서 실행되며 터미널을 바로 반환함
포어그라운드/백그라운드 프로세스 전환 예시
bg 명령어가 중지된 작업을 백그라운드로 실행
fg 명령어는 백그라운드 혹은 중지된 작업을 터미널이 직접 제어하는 포어그라운드로 전환
%1 은 job 번호
# 일시 중단 (Ctrl+Z) → 중지 상태 (T 상태)
^Z
[1]+ Stopped ./a.out
# 백그라운드로 전환
username@host:~$ bg %1
[1]+ ./a.out &
# 포어그라운드로 전환
username@host:~$ fg %1
./a.out
IPC
프로세스 간 통신 기법, 여러 프로세스가 데이터를 주고받거나 상태를 공유하기 위한 매커니즘
각 프로세스는 독립적인 메모리 공간을 가지므로, 커널이 제공하는 IPC 도구를 통해서만 서로 데이터를 교환할 수 있음
파이프(pipe)
한 프로세스의 출력 → 다른 프로세스의 입력으로 데이터를 전달하는 통로
셸에서 사용하는 | 기호는 대표적인 파이프의 예시
username@host:~$ ls
a.out hello.c
# ls의 stdout이 파이프로 grep의 stdin으로 전달
username@host:~$ ls | grep out
a.out
익명 파이프(명명되지 않은 파이프)
부모–자식처럼 연관 있는 프로세스 간에 사용
코드 안에서 변수/배열처럼 취급되는 커널 객체로, 파일 이름 없음
명명된 파이프(FIFO)
파일 시스템에 이름을 가진 특수 파일 형태로 존재
서로 관계없는 프로세스도 같은 경로의 파이프 파일을 열면 통신 가능
메시지 큐
정해진 형식(메시지 구조체)을 주고받는 큐 형태의 IPC
일반적으로 선입선출(FIFO)이지만, 메시지 타입을 이용해 우선순위나 선택적 수신도 가능
문자메시지나 메일처럼 메시지를 모아 두었다가 필요할 때 꺼내 읽는 모델에 가까움
소켓
네트워크 인터페이스를 이용한 IPC로, 주로 다른 컴퓨터에 있는 프로세스와 통신할 때 사용
TCP/UDP 같은 네트워크 프로토콜 위에서 동작하며, 클라이언트–서버 구조로 데이터 송수신
공유 메모리
여러 프로세스가 같은 물리 메모리 영역을 공유하는 IPC
각 프로세스는 자신의 가상 주소 공간 안에 공유 메모리 영역을 매핑(attach) 해서 사용
한 프로세스가 쓴 내용을 다른 프로세스가 곧바로 메모리에서 읽을 수 있기 때문에 가장 빠른 IPC
커널이 관리하는 공용 메모리 조각을 함께 보는 구조
동시에 접근하면 충돌이 나므로, 세마포어나 뮤텍스 같은 동기화 수단 필수
세마포어
공유 자원에 대한 동시 접근을 제어하는 동기화 객체
예: 화장실 문 잠금장치 - 누군가 사용 중(세마포어 값 0)이면 다른 사람은 들어갈 수 없음
카운팅 세마포어
자원을 여러 개 가진 경우, 세마포어 값이 사용 가능한 자원 개수
바이너리 세마포어
값이 0 또는 1인 세마포어로, 사실상 뮤텍스와 유사한 상호배제(Mutual Exclusion) 용도
공유 메모리, 파일, 장치 등에 여러 프로세스가 동시에 접근할 때 경쟁 상태를 방지하는 핵심 도구
경쟁 상태(Race condition) : 공용 자원을 병행적으로 사용할 때 그 순서에 따라 실행 결과가 달라지는 상태
시그널(Signal)
프로세스에 특정 이벤트가 발생했음을 알려주는 비동기 알림 매커니즘
커널, 다른 프로세스, 사용자의 키보드 입력을 통해 시그널을 전송할 수 있음
시그널 핸들러(signal handler)에는 시그널을 받았을 때 어떤 동작을 할지 함수 형태로 정의
핸들러가 없으면 커널이 미리 정해 둔 기본 동작이 수행됨
시그널의 종류
kill -l 명령어를 통해 지원하는 시그널의 종류를 확인 가능
username@host:~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
...
63) SIGRTMAX-1 64) SIGRTMAX
대표적인 시그널
| 번호 | 이름 | 언제 발생 / 쓰임새 | 기본 동작 |
|---|---|---|---|
| 2 | SIGINT | Ctrl+C 입력 (사용자 인터럽트) | 종료 |
| 3 | SIGQUIT | Ctrl+\ 입력, 종료 + 코어덤프 | 종료 + 코어 덤프 |
| 9 | SIGKILL | 강제 종료, 잡을 수 없고 무시 불가 | 즉시 종료 |
| 11 | SIGSEGV | 세그멘테이션 오류(잘못된 메모리 접근) | 코어 덤프 + 종료 |
| 15 | SIGTERM | 정상 종료에 대한 기본 종료 요청 | 종료 |
| 18 | SIGCONT | 중지된 프로세스를 다시 실행 | 실행 재개 |
| 19 | SIGSTOP | 강제 중지(무시/핸들러 불가) | 중지(Stopp ed) |
| 20 | SIGTSTP | Ctrl+Z 입력, 사용자 일시 정지 | 중지(Stopp ed) |
사용자 정의 시그널
SIGUSR1 , SIGUSR2 : 사용자가 애플리케이션에서 자유롭게 의미를 정해 사용할 수 있는 시그널
프로그램 내부의 사용자 정의 이벤트로 활용
키보드 입력으로 보내는 시그널
터미널에서 직접 키 조합으로 시그널을 보낼 수 있음
| 키 조합 | 시그널 | 기본 의미 |
|---|---|---|
| Ctrl+C | SIGINT | 인터럽트(프로세스 종료 요청) |
| Ctrl+Z | SIGTSTP | 프로세스 일시 중지(Stopped) |
| Ctrl+\ | SIGQUIT | 종료 + 코어 덤프 생성 |
기본 처리 방법(Default Action)
프로세스가 시그널을 받으면, 시그널마다 기본 처리 방법이 정해져 있음
일부 시그널에 대해 기본 처리 대신, 무시하거나(IGNORE) 사용자 정의 핸들러 등록 가능
(SIGKILL , SIGSTOP 은 예외적으로 무시나 핸들러 설정 불가)
기본 처리 방법 유형
| 기본 처리 방법 | 설명 | 대표 시그널 예시 |
|---|---|---|
| 프로세스 종료 | 즉시 또는 정상적으로 프로세스를 종료 | SIGINT, SIGTERM, SIGUSR1, SIGUSR2 등 |
| 프로세스 중단 | 실행을 멈추고 Stopped 상태로 전환 | SIGTSTP(Ctrl+Z), SIGSTOP, SIGTTIN, SIGTTOU |
| 프로세스 재시작/계속 | 중단된 프로세스를 다시 실행 상태로 전환 | SIGCONT |
| 무시 | 시그널을 받더라도 아무 동작도 하지 않음 | 기본값이 무시는 SIGCHLD 등, 그 외 다수는 무시로 재설정 가능 |
| 코어 덤프 | 메모리 상태를 코어 덤프로 저장 후 프로세스 종료 | SIGSEGV, SIGABRT, SIGILL, SIGFPE, SIGQUIT 등 |
프로세스 종료 : SIGTERM은 정상 종료 요청, SIGKILL은 강제 종료(잡을 수 없음)
중단 / 재시작 : SIGTSTP로 멈춘 뒤 SIGCONT로 다시 실행
코어 덤프 : 디버깅을 위해 당시 메모리 상태를 파일로 남기고 종료
프로세스 관점에서의 시그널 처리 선택지
프로세스는 각 시그널에 대해 보통 다음 중 하나로 동작
SIGKILL, SIGSTOP은 불가)signal(), sigaction() 등을 통해 함수 연결)사용자와 소유권에 관해서는 어느 정도 알고는 있었는데,
프로세스와 시그널에 관해서는 처음 배웠다.
잘 알아두고 나중에 여러 프로세스를 다룰 때 충분히 활용해 보면 좋겠다.