/proc

이 디렉토리는 현재 시스템에서 실행되는 프로세스들에 대한 정보를 제공하기 위해 OS 에서 제공하는 가상의 파일시스템 입니다. 그러니까 실제 파일이 존재하는 것이 아닙니다. 커널이 가지고 있는 정보는 사용자 프로그램에서 직접 접근할 수가 없으므로 system call 을 통해 제공하는 방법도 생각해 볼 수 있겠으나 프로세스가 가지는 자료구조를 생각할때 사용자가 쉽게 사용할 수 있도록 가상의 파일 시스템을 만들어 구조적으로 제공하는 것입니다. 각각의 파일은 커널 내부에서 특정 함수와 연결되어 있어서 read 하면 실시간으로 해당 정보가 표시됩니다.

이와 같은 정보제공 방법이 유용한 점이 많기 때문에 지금은 프로세스 정보뿐만 아니라 커널이 가지고 있는 여러가지 시스템 관련 정보도 /proc 를 통해 제공하고 있고 커널 파라미터 값을 write 을 통해 변경할 수도 있습니다.

# `ps` 명령은 모든 정보를 /proc 를 이용해 표시합니다.

$ strace -e %file ps |& grep '/proc/' 
openat(AT_FDCWD, "/proc/sys/kernel/osrelease", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/self/stat", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/uptime", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/sys/kernel/pid_max", O_RDONLY) = 4
...
...
openat(AT_FDCWD, "/proc/6/stat", O_RDONLY) = 6
openat(AT_FDCWD, "/proc/6/status", O_RDONLY) = 6
stat("/proc/7", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/7/stat", O_RDONLY) = 6
openat(AT_FDCWD, "/proc/7/status", O_RDONLY) = 6
...
...
openat(AT_FDCWD, "/proc/19270/stat", O_RDONLY) = 6
openat(AT_FDCWD, "/proc/19270/status", O_RDONLY) = 6
stat("/proc/30628", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/30628/stat", O_RDONLY) = 6
openat(AT_FDCWD, "/proc/30628/status", O_RDONLY) = 6
----------------------------------------------------

# vmstat 명령
$ strace -e %file vmstat |& grep '/proc/' 
openat(AT_FDCWD, "/proc/sys/kernel/osrelease", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/sys/kernel/osrelease", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/meminfo", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/stat", O_RDONLY) = 4
openat(AT_FDCWD, "/proc/vmstat", O_RDONLY) = 5

# iostat 명령
strace -e %file iostat  |& grep '/proc/'
openat(AT_FDCWD, "/proc/diskstats", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/uptime", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/stat", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/diskstats", O_RDONLY) = 3

/proc 디렉토리를 ls 했을때 보이는 숫자로된 디렉토리가 현재 시스템에서 실행되고 있는 프로세스들입니다. 예를 들어 현재 bash shell 프로세스와 관련된 정보를 보려면 다음과 같이 하면 됩니다.

# '$$' 는 현재 shell pid 를 나타내는 특수변수 입니다.
$ ls -l /proc/$$/
total 0
dr-xr-xr-x 2 mug896 mug896 0 2017-02-18 04:48 attr/
-rw-r--r-- 1 mug896 mug896 0 2017-02-18 04:48 autogroup
-r-------- 1 mug896 mug896 0 2017-02-18 04:48 auxv
-r--r--r-- 1 mug896 mug896 0 2017-02-18 04:48 cgroup
--w------- 1 mug896 mug896 0 2017-02-18 04:48 clear_refs
-r--r--r-- 1 mug896 mug896 0 2017-02-18 04:48 cmdline
...
...

# 현재 shell 프로세스가 가지고 있는 file descriptors
$ ls -l /proc/$$/fd/
total 0
lrwx------ 1 mug896 mug896 64 2017-02-18 05:06 0 -> /dev/pts/2
lrwx------ 1 mug896 mug896 64 2017-02-18 05:06 1 -> /dev/pts/2
lrwx------ 1 mug896 mug896 64 2017-02-18 05:06 2 -> /dev/pts/2
lrwx------ 1 mug896 mug896 64 2017-02-18 05:06 255 -> /dev/pts/2

/proc/self

/proc/self 는 심볼릭링크 인데 항상 프로세스 자신의 pid 번호에 연결되어 있습니다. 그러므로 프로세스 자신의 정보를 구하려고 할경우 따로 pid 를 알아낼 필요없이 /proc/self 를 이용하면 됩니다. /dev 디렉토리에 존재하는 fd, stdin, stdout, stderr 도 모두 /proc/self 와 연결된 링크로 shell script 를 작성할 때 유용하게 사용할 수 있습니다.

/dev/fd/    -> /proc/self/fd/
/dev/stdin  -> /proc/self/fd/0
/dev/stdout -> /proc/self/fd/1
/dev/stderr -> /proc/self/fd/2

/proc 가 가지는 또 한가지 기능은 파일 시스템을 통해 직접 커널의 옵션 설정을 변경할수 있다는 것입니다. 하지만 설정이 저장되는 것은 아니므로 시스템을 다시 시작하면 기본값으로 설정이 됩니다. 다음은 icmp echo 와 관련된 옵션 설정을 /proc 를 통해서 하는 예입니다.

# icmp_echo_ignore_all 은 ping 명령에 대한 응답 여부를 설정하는 옵션입니다.
# 값이 0 일 경우는 ON 을, 1 은 OFF 를 나타냅니다.
# www.naver.com, www.daum.net 같은 경우 ping 을 했을때 응답을 볼수가 없으므로
# icmp_echo_ignore_all 값이 1 로 설정되어 있다고 할 수 있습니다.

$ cat /proc/sys/net/ipv4/icmp_echo_ignore_all 
0

$ sudo sh -c 'echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all'

$ cat  /proc/sys/net/ipv4/icmp_echo_ignore_all 
1

-------------------------------------------------------------
# 다음은 sysctl 명령을 이용한 설정 방법입니다.
# /proc/sys/ 를 제외하고 '/' 를 '.' 로 변경하여 사용하면 됩니다.
$ sysctl net.ipv4.icmp_echo_ignore_all 
net.ipv4.icmp_echo_ignore_all = 0

$ sudo sysctl -w net.ipv4.icmp_echo_ignore_all=1
net.ipv4.icmp_echo_ignore_all = 1

$ sysctl net.ipv4.icmp_echo_ignore_all 
net.ipv4.icmp_echo_ignore_all = 1

# 재부팅 후에도 적용되게 하려면 다음과 같이 파일로 저장해야 합니다.
# 이때 /etc/sysctl.d/ 디렉토리에 저장되는 파일이름은 임의로 사용할수 있고
# 확장자만 .conf 으로 맞으면 됩니다.
$ sudo sh -c 'echo "net.ipv4.icmp_echo_ignore_all = 1" > /etc/sysctl.d/icmp-ignore.conf'

/proc 디렉토리 구조에 대한 자세한 설명은 man 5 proc 로 볼 수 있습니다.
시스템 전체 디렉토리 구조에 대한 설명은 man 7 hier 입니다.

/sys

/sys 디렉토리는 /proc 이후에 생긴 가상 파일 시스템입니다. 원래 /proc 는 프로세스 정보와 ps, top, free 같은 몇몇 시스템 유틸리티가 사용하는 시스템 정보를 제공하는 게 목적으였으나 사용상의 이점으로 인해 무작위로 시스템 관련 정보를 제공하다 보니 일종의 dumping ground 가 되어서 나중에 device, system 관련 정보를 좀 더 구조적으로 제공하기 위해 만든 것이 /sys 디렉토리 입니다.

기존 legacy 시스템 정보는 아직 /proc 를 통해서 접근할 수 있으나 새로 추가되는 device, system 관련 정보는 /sys 를 통해서 제공됩니다.

/dev

/proc 가 커널이 가지고 있는 프로세스와 시스템 관련 정보를 사용자에게 제공하는게 목적이라면 어떻게 하면 커널이 관리하는 장치들을 사용자에게 제공할수 있을까에 대한 해결책이 /dev 디렉토리 입니다. 이 디렉토리는 /proc 와 달리 가상의 파일 시스템이 아니고 mknod 명령으로 직접 장치파일을 만듭니다. 생성된 장치파일은 꼭 /dev 디렉토리에 위치할 필요는 없고, 일반 파일들과 똑같이 mv, rm, cp -R, rename 을 할 수 있습니다. 또한 사용자 프로그램에서 장치파일을 사용하는 방법도 일반 파일과 같습니다.

/dev 디렉토리를 ls 해 보면 파일 사이즈 대신에 두개의 숫자가 , 로 구분되어 표시되는 것을 볼 수 있는데요 각각 major number, minor number 라고 합니다. 파일을 오픈 했을때 이 번호로 커널은 어떤 장치 드라이버를 사용해야 될지 알 수 있습니다. 그러니까 장치파일의 이름은 실질적으로 중요하지 않고 임의로 만들수 있습니다. 터미널 프로그램을 몇개 오픈한뒤 ls -l /dev/pts/ 해보면 장치 파일들이 새로 생성된걸 볼 수 있는데 minor number 가 모두 다른 것을 알 수 있습니다. major number 가 장치 드라이버를 나타낸다면 minor number 는 이렇게 같은 장치 드라이버를 사용하지만 구분하기 위한 용도로 사용됩니다.

컴퓨터에서 사용되는 장치들을 두가지로 분류해 볼 수 있는데요. 데이터에 접근할때 random access 를 할 수 있는 장치와 그렇지 않은 장치입니다. 첫번째의 경우를 block device 라고 하고 디렉토리 퍼미션의 첫번째 문자가 b 로 표시됩니다. 두번째는 character device 라고 하며 c 로 표시됩니다.

# block device 만 출력
$ find /dev -type b -exec ls -l {} \;

# character device 만 출력
$ find /dev -type c -exec ls -l {} \;

Character device

character device 는 데이터가 1 byte 크기의 character by character 로 전달되고 random access 를 할 수 없는 장치입니다. 데이터가 전달될 때 커널이 사용하는 버퍼를 거치지 않고 바로 장치에 전달됩니다. 키보드키를 하나 누르면 바로 문자가 표시되고 마우스를 움직이면 바로 포인터가 움직이는데 모두 character device 입니다. 사실 디스크와 관련된 장치를 제외하고 대부분의 장치가 character device 입니다.

  • 마우스 : /dev/input/
  • 그래픽 : /dev/dri/
  • 사운드 : /dev/snd/

/dev/tty

/dev/tty 는 프로세스의 controlling terminal 과 동일합니다. 다시말해 현재 ctty 가 /dev/pts/1 이라면 /dev/tty 도 /dev/pts/1 라고 할 수 있습니다. 어떤 프로세스가 /dev/tty 를 open 하는데 실패하였다면 ctty 를 갖고있지 않다고 할 수 있습니다. standard stream 이 모두 redirect 되어있다고 하더라도 /dev/tty 로 출력하면 터미널로 출력할 수 있고 또한 /dev/tty 를 이용해 터미널로부터 입력을 받을 수 있습니다.

Block device

하드디스크, CD-ROM, USB 메모리 같이 random access 를 할 수 있는 장치가 block device 에 해당합니다. block device 는 커널이 관리하는 버퍼를 이용하고 block 단위로 데이터가 전달됩니다. character device 와 block device 는 서로 내부적으로 처리하는 방식이 다르기 때문에 하나의 물리 장치에 두 종류의 장치 파일을 제공하기도 합니다. 또한 버퍼 캐시를 이용하는 것이 실제 데이터가 장치에 쓰여지는 순서나 가시성에 영향을 줄 수 있다고 하여 아예 character device 만 제공하는 OS 도 있습니다.

/dev/sda[번호]     # 하드디스크

/dev/ram[번호]     # ramdisk 를 만들때 사용

/dev/loop[번호]    # iso 이미지 파일을 마운트 할때 사용

Pseudo devices

Pseudo devices 는 실제 물리 장치에 연결되어 있는것은 아니고 커널에서 제공하는 하나의 기능으로 보면 되겠습니다. /dev/null 은 shell script 에서도 많이 사용됩니다.

  • /dev/null : 이 장치에 쓰기를 할경우 입력되는 데이터는 모두 사라지고 쓰기 성공이 반환됩니다. 블랙홀과 같은 존재입니다. 보통 명령 실행시 원하는지 않는 data stream 을 여기로 보냅니다. 읽을경우 end-of-file 상태를 리턴합니다.

  • /dev/zero : 읽을경우 연속된 NUL 문자가 제공되므로 보통 데이터 스토리지를 초기화 할때 사용합니다. 쓰기를 할경우 /dev/null 과같이 블랙홀 역할을 합니다.

# 다음과 같이 하면 해당 파티션 데이터가 실질적으로 모두 삭제되어 복구할수 없게 됩니다.

$ dd if=/dev/zero of=/dev/<destination partition>
  • /dev/urandom : 연속된 pseudo random numbers 를 제공합니다.

  • /dev/full : 이 장치에 쓰기를 할경우 항상 ENOSPC (No space left on device) error code 를 반환합니다. 그러므로 보통 프로그램에서 disk full 상태를 테스트 할때 사용됩니다. 읽을경우 연속된 NUL 문자가 제공됩니다.

Architectural view of the Linux file system components