/dev/fd
file descriptor file들이 있는 디렉토리
0, 1, 2
/dev/fd/0: stdin/dev/fd/1: stdout/dev/fd/2: stderr
/dev/fd/0은 표준 입력이므로, 다음과 같이 cat을 실행해 놓고, 키보드로 입력을 해보면 입력한 내용이 그대로 출력되는 것을 볼 수 있다.
cat /dev/fd/0
/dev/fd/1은 표준 출력이므로, 다음과 같이 출력을 관찰할 수 있다.
echo "hello" > /dev/fd/1
한편, /dev/stdin, /dev/stdout, /dev/stderr는 각각 /dev/fd/0, /dev/fd/1, /dev/fd/2에 대한 심볼릭 링크이다.
$ ls -al /dev/std*
lr-xr-xr-x 1 root wheel 0 2023-08-17 Thu 22:59:57 /dev/stderr -> fd/2
lr-xr-xr-x 1 root wheel 0 2023-08-17 Thu 22:59:57 /dev/stdin -> fd/0
lr-xr-xr-x 1 root wheel 0 2023-08-17 Thu 22:59:57 /dev/stdout -> fd/1
man
/dev/fd는 special file에 해당하므로 [[/cmd/man]]로는 4를 지정해야 볼 수 있다.
man 4 fd
From: UNIX 고급 프로그래밍
최근 시스템들은
/dev/fd라는 이름의 디렉터리를 제공한다. 이 디렉터리에는 이름이0,1,2등인 파일들이 있다. 파일 서술자 n이 이미 열려 있다고 가정할 때,/dev/fd/n이라는 파일을 여는 것은 서술자 n을 복제하는 것과 동등한 일이다.
/dev/fd기능은 [[/duff-s-device]]{톰 더프Tom Duff}가 개발했으며 Research UNIX System 제8판에 등장했다. 이 책에서 설명하는 모든 시스템(FreeBSD 8.0, Linux 3.2.0, Max OS X 10.6.8, Solaris 10)이 이 기능을 지원한다. 이 기능이 POSIX.1의 일부는 아니다.(중략)
/dev/stdin이나/dev/stdout,/dev/stderr같은 경로이름들을 제공하는 시스템들도 있다. 이들은 각각/dev/fd/0,/dev/fd/1,/dev/fd/2와 동등하다.
/dev/fd파일들은 주로 셸에 쓰인다. 이들을 이용하면 경로이름 인수들을 사용하는 프로그램들이 표준 입력과 표준 출력을 다른 경로이름들과 동일한 방식으로 취급할 수 있다. 예를 들어cat(1) 프로그램은 일부러-이라는 입력 파일이름을 찾아보고, 만일 그런 이름이 있으면 그것을 표준 입력으로 간주한다. 다음이 그러한 예이다.filter file2 | cat file1 - file3 | lpr이 경우
cat은 먼저file1을 읽고, 그 다음 표준 입력(file2에 대한filter프로그램의 출력)을 읽고, 그런 다음file3을 읽는다./dev/fd가 지원되는 경우에는 cat에서-에 대한 특별한 처리를 제거할 수 있다. 대신 다음과 같은 명령을 사용하면 된다.filter file2 | cat filel /dev/fd/O files | lpr명령줄 인수에서
-가 표준 입력이나 표준 출력을 뜻하도록 특별하게 취급하는 방식을 따르는 프로그램들이 많이 생겼는데, 사실 그러한 처리는 바람직하지 않은 군더더기이다. 또한-를 첫 번째 파일로 지정하는 경우에는 그것이 다른 명령줄 옵션의 시작으로 보인다는 문제도 있다./dev/fd를 사용하는 것은 균일함과 깔끔함으로 나아가는 한 걸음이다.
cat 명령을 통해 살펴보는 파일 디스크립터 0, 1, 2, 3
다음의 예를 통해 strace를 사용하여 cat이 어떤 동작을 하는지 알아보자(가독성을 위해서 몇몇 호출들은 삭제함).
prompt> strace cat foo ... open("foo", O_RDONLY|O_LARGEFILE) = 3 read(3, "hello\n", 4096) = 6 write(1, "hello\n", 6) = 6 hello read(3, "", 4096) = 0 close(3) = 0 ... prompt>
cat이 가장 먼저 하는 것은 파일을 읽기 위해서 여는 것이다. 몇 가지 짚고 넘어갈 사항이 있다. 파일은O_RDONLY라는 플래그가 나타내는 것처럼 읽기만 가능한 상태로 열렸다(쓰기는 안됨). 두 번째는O_LARGEFILE플래그를 사용하여 64 bit 오프셋이 사용되도록 설정하였다. 세 번째는open()이 성공한 후에3이라는 값을 파일 디스크립터로 리턴하였다.어째서 첫 번째
open()임에도 불구하고 예상과 달리0또는1이 아닌3을 리턴하였을까? 프로세스가 이미 세 개의 파일을 열어 놓았기 때문이다. 이미 열려진 세 개의 파일은 표준 입력과 표준 출력, 그리고 오류 메시지를 기록할 수 있는 표준 에러이다. 각각의 파일 디스크립터는0,1그리고2로 표현된다. 다른 파일을 처음으로 열게 되면 (cat이 하는 듯이), 거의 확실하게 파일 디스크립터는3일 것이다.파일 열기가 성공하면
cat은read()시스템 콜을 사용하여 파일에서 몇 바이트씩 반복적으로 읽는다.read()의 첫 번째 인자는 파일 디스크립터이다. 파일 디스크립터는 읽고자 하는 파일을 파일 시스템에 알려준다. 프로세스는 동시에 여러 파일을 열 수 있다. 디스크립터는 운영체제가read명령이 읽어야 할 파일을 명시한다. 두 번째 인자는read()결과를 저장할 버퍼를 가리킨다. 위의 시스템 콜 추적 예제에서strace는 읽은 결과인 "hello"를 두 번째 인자 위치에 표시하였다. 세 번째 인자는 버퍼의 크기로서 여기서는 4 KB이다.read()가 성공적으로 리턴하며 읽은 바이트 수를 반환한다("hello" 의 5개의 문자와 줄의 끝을 표시하는 문자 하나가 있기 때문에 6을 반환함).이 시점에서
strace의 결과에 또 다른 흥미로운 점이 있다.write()시스템 콜이 결과를 쓰는 대상 파일로 파일 디스크립터 1번을 사용하는 것이다. 앞서 설명했듯이 이 디스크립터는 표준 출력 (STDOUT)으로서 "hello”라는 단어를 화면에 나타내기 위해 사용되고, 이는cat이 하기로 되어 있는 작업이다.cat프로그램이write()를 직접 호출하는 것일까? (만약 상당히 최적화가 되었다면) 그럴지도 모른다. 그렇지 않다면cat은 라이브러리 루틴인printf()를 호출했을 것이다. 내부적으로printf()는 전달 받은 문자열에 적절한 포멧을 적용한 후, 결과를 표준 출력에 써서 화면에 출력한다.출력한 이후
cat프로그램은 파일의 내용을 더 읽으려고 시도하고, 파일에 남은 바이트가 없기 때문에read()는0을 리턴한다. 프로그램은 리턴 값으로 파일을 끝까지 다 읽었음을 알게 된다. 그런 후 프로그램은 해당 파일 디스크립터를 인자로close()를 호출하여 “foo”라는 파일에서 할 일이 끝났음을 표시한다. 이제 파일은 닫혔으며 읽기 작업은 완료된다. 2
참고문헌
- UNIX 고급 프로그래밍 [제3판] / 리처드 스티븐스, 스티븐 레이고 공저 / 류광 역 / 퍼스트북 / 인쇄일: 2014년 08월 28일 / 원제: Advanced Programming in the UNIX Environment
- 운영체제 아주 쉬운 세 가지 이야기 [제2판] / Remzi H. Arpaci-Dusseau, Andrea C. Arpaci-dusseau 공저 / 원유집, 박민규, 이성진 공역 / 홍릉 / 제2판 발행: 2020년 09월 10일 / 원제: Operating Systems: Three Easy Pieces