노무현 대통령 배너


2006. 12. 18. 17:38

리눅스 프로그래머를 위한 가이드

FrontPageDocbookSgml/Ext2fs-Undeletion-Dir-StructLFS/BuildingTitleIndex › 리눅스프로그래머를위한가이드

Contents

[-]
1 리눅스 운영체제 (The Linux operating system)
2 리눅스 커널 (The Linux Kernel)
3 리눅스 libc 패키지 (The Linux libc Package)
4 시스템 호출 (System Calls)
5 "스위스 군용 칼"같은 ioctl (The "swiss army knife" ioctl)
6 리눅스 프로세스간의 통신 (Linux Interprocess Communications)
6.1 소개 (Introduction)
6.2 반이중 유닉스 파이프 (Half-duplex UNIX Pipes)
6.2.1 기본 개념 (Basic Concepts)
6.2.2 C로 파이프 만들기 (Creating Pipes in C)
6.2.3 파이프 쉬운 방법! (pipes the Easy Way!)
6.2.4 파이프의 Atomic 동작 (Atomic Operations with Pipes)
6.2.5 반이중 파이프의 정리 (Note on half-duplex pipes)
6.3 이름을 가진 파이프(Named pipes:FIFOs - First In First Out)
6.3.1 기본 개념 (Basic Concepts)
6.3.2 FIFO 만들기 (Creating a FIFO)
6.3.3 FIFO 동작 (FIFO Operations)
6.3.4 FIFO의 동작 차단 (Blocking Actions on a FIFO)
6.3.5 잘 알려지지 않은 SIGPIPE 신호 (The Infamous SIGPIPE Signal)
6.4 시스템 V IPC (System V IPC)
6.4.1 기본적인 개념 (Fundamental Concepts)
6.4.1.1 IPC 확인자 (IPC Identifiers)
6.4.1.2 IPC 키 (IPC Keys)
6.4.1.3 ipcs 명령어 (The ipcs Command)
6.4.1.4 ipcrm 명령어 (The ipcrm Command)
6.4.2 메세지 큐 (Message Queues)
6.4.2.1 기본 개념 (Basic Concepts)
6.4.2.2 내부와 사용자 자료 구조 (Internal and User Data Structures)
6.4.2.3 시스템 호출:msgget() (SYSTEM CALL:msgget())
6.4.2.4 시스템 호출:msgsnd() (SYSTEM CALL:msgsnd())
6.4.2.5 시스템 호출:msgctl() (SYSTEM CALL:msgctl())
6.4.2.6 msgtool:상호작용 메세지 큐 조종자 (An interactive message queue manipulator)

1 리눅스 운영체제 (The Linux operating system)

1991년 3월 Linus Benedict Torvalds는 자신의 AT 386 컴퓨터용으로 멀티태스킹 시스템인 Minix를 구입했다. 그는 그것을 활용하여 리눅스라 부르는 자신만의 멀티태스킹 시스템을 개발하였다. 1991년 9월 그는 인터넷 상의 Minix 사용자들에게 E-메일을 통해 첫번째 프로토타입(Prototype)을 발표했다. 이것이 리눅스 프로젝트의 기원이다. 그때부터 많은 프로그래머들이 리눅스를 지원하기 시작했다. 그들은 디바이스 드라이버를 추가하고, 응용프로그램을 개발하고 POSIX를 따르는 것을 목표로 했다. 오늘날 리눅스는 매우 강력해졌지만 가장 큰 장점은 공짜(FREE)라는 점이다. 다른 플랫폼으로 리눅스를 이식하는 작업이 진행중이다.

2 리눅스 커널 (The Linux Kernel)

리눅스의 기본은 커널이다. 모든 라이브러리를 바꿔버리더라도, 리눅스 커널이 남아있는 한 리눅스이다. 커널은 디바이스 드라이버, 메모리 관리, 프로세스 관리와 통신 관리를 포함한다. 커널 해커들은 때때로 프로그래밍을 쉽게도 어렵게도 만드는 POSIX 가이드라인을 따른다. 당신의 프로그램이 새로운 리눅스 커널 위에서 다르게 동작한다면, 변화는 곧 새로운 POSIX 가이드라인이 구현되었을 확률이 크다. 리눅스 커널에 대한 프로그래밍 정보를 원한다면, 리눅스 커널 해커를 위한 가이드(The Linux Kernel Hacker's Guide)를 읽어라.

3 리눅스 libc 패키지 (The Linux libc Package)

libc: ISO 8859.1, , YP 함수들, crypt 함수들, 기본적인 Shadow 루틴들 (기본적으로는 포함되지 않음), ... libcompat에 있는 호환성을 위한 기존의 루틴들 (기본적으로 수행되지 않음),영어,불어 또는 독어 에러 메세지들, libcourses에 있는 bsd 4.4 lite와 호환성 있는 화면 핸들링 루틴들, libbsd에 있는 bsd와 호환되는 루틴들, libtermcap에 있는 화면 핸들링 루틴들, libdbm에 있는 데이타베이스 관리 루틴들, libm에 있는 수학계산 루틴들, crt0.o???에 있는 프로그램 실행을 위한 항목, libieee???에 있는 바이트 sex 정보 (우스운 ???를 대신할 정보를 보내주세요.), libgmon에 있는 사용자 공간 프로파일링(Profiling). 리눅스 libc 개발자들 중 누군가가 이 장을 써 주기를 바랍니다. 지금 내가 말할 수 있는 것은 실행할 수 있는 형태인 a.out이 공유 라이브러리 만들기의 변화를 의미하는 elf(executable and linkable format)으로 바뀔 것이라는 것뿐이다. 현재는 두 형태(a.out과 elf)가 모두 지원된다.

crt0.o같은 몇몇은 특별한 예외적인 저작권의 영향을 받기도 하지만, 리눅스 libc 패키지의 대부분은 라이브러리 GNU Public 라이센스(License)의 영향을 받는다. 상업적인 이진 배포본은 정적 링크에 의한 실행에 제약이 있다. 동적 링크에 의한 실행은 특별히 예외이고 FSF의 Richard Stallman은 아래와 같이 말했다.

Section 5에 따라 실행시키는데 아무런 제약이 없이 만들어진 오브젝트 파일이 제공되는 라이브러리를 첨부하지 않는 동적 링크 실행파일의 배포는 명백히 허용되어야 한다고 본다. 그래서 나는 지금 그것을 허용하도록 결정할 것이다. 실제적인 LGPL의 보완은 내가 새버전을 만들고 확인할 때까지 기다려야만 할 것이다.

4 시스템 호출 (System Calls)

시스템 호출은 일반적으로 운영체제(커널)가 하드웨어/시스템에 지정된 또는 특권이 있어야 하는 동작들을 수행토록 요청하는 것이다. 리눅스 1.2 에서는 140개의 시스템 호출들이 정의되어 있다. close()와 같은 시스템 호출은 리눅스 libc에 구현되어 있다. 이 구현은 종종 결국에는 syscall()를 호출하는 매크로의 호출을 포함한다. syscall()에 넘겨지는 파라미터는 필요한 아규먼트에 의해 추적되는 시스템 호출 번호이다. 실제의 시스템 호출 번호들은 이 새로운 libc에 의해 업데이트되는 동안 에서 찾을 수 있다. libc를 근간을 두지않는 새로운 호출이 나타나지 않는 한 syscall()를 사용할 수 있다. 예를 들면, 아래와 같이 syscall()를 사용하여 파일을 닫을 수 있다.(권장하지 않음) :

#include extern int syscall(int, ...);int my_close(int filedescriptor){   return syscall(SYS_close, filedescriptor);}


i386 구조에서는 하드웨어 레지스터의 갯수때문에 시스템 호출 번호이외에 5개의 아규먼트로 시스템 호출이 제한된다. 또 다른 구조위에서 리눅스를 사용한다면 _syscall 매크로를 위해 하드웨어가 얼마나 많은 아규먼트를 지원하는지 또는 얼마나 많은 개발자의 선택이 지원되는지를 알아 보기위해 를 체크할 수 있다. 이러한 _syscall 매크로들은 syscall() 대신에 사용될 수 있지만, 이러한 매크로는 라이브러리에 이미 존재할런지 모르는 Full Function으로 확장되므로 추천할만하지 못하다.

#include _syscall1(int, close, int, filedescriptor);


_syscall1 매크로는 close() 함수와 같은 모습으로 확장된다. 그러므로 libc 안에 close()를 한,두번 그리고 프로그램 안에 한번 가진다. 시스템 호출이 실패하면 syscall() 이나 _syscall 매크로의 반환값은 -1이고 성공하면 0 이나 0보다 큰값을 갖는다. 시스템 호출이 실패했다면 무슨 일이 일어났는지 알기위해 전역변수인 errno를 살펴봐라.

BSD와 SYS V에서 사용할 수 있는 다음의 시스템 호출들은 리눅스에서 사용할 수 없다. : audit(),auditon(),auditsvc(),fchroot(),getauid(),getdents(),getmsg(),mincore(), poll(),putmsg(),setaudit(),setauid().

5 "스위스 군용 칼"같은 ioctl (The "swiss army knife" ioctl)

ioctl은 input/output control을 의미하며 파일디스크립터(filedescriptor)를 가지고 캐릭터 디바이스(character device)를 조종하는데 사용된다. ioctl의 형태는 ioctl(unsigned int fd, unsigned intrequest, unsigned long argument)이다. 에러를 만나면 반환값은 -1이고 다른 시스템 호출과 같이 요청이 성공하면 0보다 크거나 같은 값을 갖는다. 커널은 특수 파일(special file)이나 일반 파일들(regular files)과 구분된다. 특수 파일(special file)은 주로 /dev나 /proc 디렉토리에서 찾을 수 있다. 이 파일들은 드라이버와의 인터페이스를 숨기고 있고 텍스트나 이진 데이타를 포함하는 실제(일반) 파일이 아니라는 점에서 일반 파일(regular file)과 다르다. 이러한 점은 유닉스의 철학이고 모든 파일에 대해 정상적으로 읽기/쓰기 동작의 사용을 허락한다. 그러나 특수 파일이나 일반 파일을 가지고 그 이상의 일을 하고자 한다면 ioctl를 가지고 할 수 있다. 일반 파일보다는 특수 파일에 대해 ioctl이 종종 더 많이 필요할테지만 일반 파일에 대해서도 ioctl의 사용이 가능하다.

6 리눅스 프로세스간의 통신 (Linux Interprocess Communications)

추상적 개념:

IPC(interprocess communication facilities)에 대한 자세한 내용은 리눅스 운영체제에 기술 되어 있다.

6.1 소개 (Introduction)

리눅스 IPC(Inter-process communication)은 여러 프로세스들이 다른 프로세스와 통신할 수 있는 방법을 제공한다. 리눅스 C 프로그래머들이 이용할 수 있는 IPC 방법에는 아래와 같은 몇가지가 있다.

  • 반이중 유닉스 파이프 (Half-duplex UNIX pipes)
  • FIFOs (named pipes)
  • SYS V 스타일의 메세지 큐 (SYS V style message queues)
  • SYS V 스타일의 세마퍼 집합 (SYS V style semaphore sets)
  • SYS V 스타일의 공유 메모리 세그멘트 (SYS V style shared memory segments)
  • 네트워킹 소켓 (버클리 스타일) (Networking sockets (Berkeley style)) : 여기서는 다루지 않음
  • 전이중 파이프 (Full-duplex pipes (STREAMS pipes) : 여기서는 다루지 않음

이러한 방법들이 효과적으로 사용될 때, 리눅스를 포함한 모든 유닉스 시스템에서의 클라이언트/서버 개발을 위한 견고한 프레임웍크(framework)를 제공한다.

6.2 반이중 유닉스 파이프 (Half-duplex UNIX Pipes)

6.2.1 기본 개념 (Basic Concepts)

간단히 말해서, 파이프(Pipe)는 한 프로세스의 표준 출력(Standard output)을 다른 프로세스의 표준 입력(Standard input)으로 연결하는 방법이다. 파이프는 IPC툴 중 가장 오래된 것으로 유닉스 운영체제의 초기 단계부터 사용되어 왔다. 파이프는 프로세스간의 단방향(반이중,half-duplex) 통신의 한 방법을 제공한다.

이러한 특징은 유닉스 명령어 라인(유닉스 쉘)에서 조차 폭넓게 사용된다.

ls | sort | lp

위의 예는 ls의 출력을 sort의 입력으로 하고 sort의 출력을 lp의 입력으로 사용하기 위해 파이프(pipe)를 이용하였다. 자료는 파이프라인을 통해 왼쪽에서 오른쪽으로 이동하듯이, 반이중 파이프를 통해 움직인다.

우리들이 쉘 스크립트 프로그래밍에서 파이프를 아주 세심하게 사용한다해도, 커널 레벨에서 일어나는 일에 대해서는 생각하지 않고 사용한다.

프로세스가 파이프를 만들 때, 커널은 파이프에 의해 사용될 두개의 파일 식별자(descriptor)를 준비한다. 한개의 식별자(descriptor)는 파이프(Write)의 입력 통로로 사용되고 다른 하나는 파이프(Read)로 부터 자료를 얻는데 사용된다. 이런 점에서 생성하는 프로세스는 자기자신과의 통신에만 파이프를 사용함으로 파이프는 거의 실질적으로 사용되지 않는다. 파이프가 만들어진 이후의 프로세스와 커널의 개념작용에 주의하라.

- 애석하게도 원문에서 다이어그램 그림이 빠져있음

위의 다이어그램으로 부터 식별자들이 서로 어떻게 연결되는지 쉽게 알 수 있다. 프로세스가 파이프(fd0)를 통해 자료를 보낸다면, fd1로 부터 자료를 얻을 수 (읽을 수) 있는 능력을 가지고 있다. 그렇지만 위의 극히 간단한 그림의 실재물은 매우 크다. 파이프가 처음에 프로세스와 자신을 연결하는 동안, 파이프를 통해 여행 중인 자료는 커널을 통해 움직인다. 특히 리눅스에서 파이프는 실제로 내부적으로 유효한 inode를 가지고 있음을 의미한다. 물론, 이 inode는 커널안에 존재하고 물리적인 파일 시스템에는 아무런 영향을 끼치지 않는다. 이러한 특징은 우리가 수동 I/O 문(handy I/O doors)을 열 수 있게 한다.

이러한 점에서 파이프는 명백히 쓸모없다. 결국, 우리가 오직 우리 자신에게 이야기하고자 한다면 왜 파이프 생성의 어려움을 가지고 가야만 하는가? 생성하는(creating) 프로세스는 전통적으로 자식 프로세스(child process)를 생성(fork)한다. 자식 프로세스는 부모로 부터 열려진 파일 식별자도 상속받았으므로 우리는 부모와 자식간의 멀티프로세스 통신을 위한 기초를 가지고 있다. 우리의 간단한 그림 수정본에 주의하라.


위와 같이 우리는 두 프로세스가 파이프 라인을 구성하는 파일 식별자에 접근할 수 있음을 볼 수 있다. 이번에는 중요한 결정을 내려야 한다. 어떤 방향으로 자료가 이동하기를 원하는가? 자식 프로세스가 부모에게 정보를 보낼 것인가 아니면 그 반대인가? 두 프로세스는 이러한 문제에 대해 상호 동의하고 있고 관계되어 있지 않은 파이프의 끝은 닫는다. 토론의 목적을 위해, 자식 프로세스가 어떤 작업을 수행하고 파이프를 통해 부모에게 정보를 전하는지를 살펴보자. 우리의 새롭게 고안된 그림은 다음과 같이 나타난다.:


현재 파이프라인의 구성은 완벽하다! 남아있는 일은 파이프의 사용뿐이다. 파이프에 직접적으로 접근하기 위해, 하위 레벨의 파일 I/O를 위해 사용되는 같은 기능의 시스템 호출을 사용할 수 있다. (파이프가 실제 내부적으로 유효한 inode로 표현됨을 기억하라)

파이프로 자료를 보내기 위해, 우리는 write() 시스템 호출을 사용하고 파이프로 부터 자료를 조회하기 위해 read()라는 시스템 호출을 사용한다. 하위 레벨의 파일 I/O 시스템 호출은 파일 식별자를 가지고 작업함을 기억하라! 어쨌든, 파이프에서 식별자를 가지고 작업하지 않는 lseek()와 같은 시스템 호출들을 기억하라.

6.2.2 C로 파이프 만들기 (Creating Pipes in C)

C 프로그래밍 언어로 파이프라인을 만드는 것은 간단한 쉘 예제보다는 많은 것을 포함한다. C로 간단한 파이프를 만들기 위해 pipe() 시스템 호출을 사용한다. 이것은 두 정수의 배열인 한개의 아규먼트(argument)를 가지며, 성공하면 배열은 파이프 라인을 위해 사용되는 새로운 두개의 파일 식별자를 갖는다. 파이프를 만들고 난 후, 프로세스는 전통적으로 새로운 프로세스를 낳는다. (자식은 열려진 파일 식별자를 상속받는다는 것을 기억하라)

SYSTEM CALL: pipe();PROTOTYPE: int pipe(int fd[2]);  RETURNS: 성공시 0           -1 on error: errno = EMFILE (자유로운 식별자가 없다)                                EMFILE (시스템 파일 테이블이 다 찼다)                                EFAULT (fd 배열이 유효하지 않다)NOTES: fd[0]는 읽기를 위해 준비되고, fd[1]은 쓰기를 위해 준비된다.


배열의 첫번째 정수(element 0)는 읽기를 위해 준비되고 열려지는 반면, 두번째 정수(element 1)는 쓰기를 위해 준비되고 열려진다. 시각적으로 말하면, fd1의 출력은 fd0를 위한 입력이 된다. 파이프를 통해 이동하는 모든 자료는 커널을 통해 이동함을 다시 말해둔다.

#include #include #include main(){ int fd[2]; pipe(fd); . .}


C에서 배열의 이름은 첫번째 멤버의 포인터임을 기억하라. 위에서, fd는 &fd[0]와 같다. 파이프 라인을 만들고 난 후, 새로운 자식 프로세스를 생성(fork)한다.:

#include #include #include main(){ int fd[2]; pipe(fd); if((childpid = fork()) == -1) {  perror("fork");  exit(1); } . .}


부모가 자식으로 부터 자료를 조회하기를 원한다면, fd1를 닫아야 하고 자식은 fd0를 닫아야 한다. 부모가 자식에게 자료를 보내고자 한다면, fd0를 닫아야 하고 자식은 fd1을 닫아야 한다. 부모와 자식간에 식별자를 공유하고 있으므로, 관계하고 있지 않는 파이프의 끝은 항상 닫혀져야만 한다. 기술적인 주의사항은 불필요한 파이프의 끝을 명백하게 닫지 않으면 EOF는 영원히 돌아오지 않는다는 것이다.

#include #include #include main(){ int^Ifd[2]; pid_t^Ichildpid; pipe(fd); if((childpid = fork()) == -1) {  perror("fork");  exit(1); } if(childpid == 0) {  /*자식 프로세스는 파이프의 입력 쪽을 닫는다*/  close(fd[0]); } else {  /*부모 프로세스는 파이프의 출력 쪽을 닫는다*/  close(fd[1]); } . .}


앞에서 말했던 것처럼, 파이프라인을 만든 후에 파일 식별자는 일반 파일의 식별자처럼 취급된다.

/***************************************************************************** 리눅스 프로그래머를 위한 가이드 - 6장 에서 발췌 (C)opyright 1994-1995, Scott Burkett *****************************************************************************  MODULE: pipe.c *****************************************************************************/#include #include #include int main(void){        int     fd[2], nbytes;        pid_t   childpid;        char    string[] = "Hello, world!n";        char    readbuffer[80];        pipe(fd);        if((childpid = fork()) == -1)        {                perror("fork");                exit(1);        }        if(childpid == 0)        {^I^I/*자식 프로세스는 파이프의 입력 쪽을 닫는다*/                close(fd[0]);^I^I/*파이프의 출력 쪽으로 "string"을 보낸다*/                write(fd[1], string, strlen(string));                exit(0);        }        else        {^I^I/*부모 프로세스는 파이프의 출력 쪽을 닫는다*/                close(fd[1]);^I^I/*파이프로 부터 문자열을 읽는다*/                nbytes = read(fd[0], readbuffer, sizeof(readbuffer));                printf("Received string: %s", readbuffer);        }        return(0);}


종종 자식의 식별자는 표준 입력 또는 출력과 중첩된다. 자식은 표준 stream을 상속하는 다른 프로그램을 exec() 할 수 있다. dup() 시스템 호출을 살펴보자.:

  SYSTEM CALL: dup();                                                             PROTOTYPE: int dup( int oldfd );                                                  RETURNS: 성공시 새로운 식별자              -1 on error: errno = EBADF (oldfd가 유효한 식별자가 아니다)                                         EBADF (newfd가 범위를 벗어났다)                                                   EMFILE (프로세스에 대해 식별자가 너무 많다)   NOTES: 과거의 식별자는 닫혀지지 않는다. 두개가 상호 교환되어질 수 있다.


과거의 식별자와 새로 만들어진 식별자가 상호 교환되어질 수 있더라도, 전통적으로 표준 stream의 한쪽을 닫는다. dup() 시스템 호출은 새로운 식별자에게 아직 사용되지 않은 가장 작은 번호를 부여한다.

주의해서 보자:

        .        .        childpid = fork();        if(childpid == 0)        {^I^I/*자식의 표준 입력을 닫는다*/                close(0);^I^I/*파이프의 입력 쪽을 표준입력으로 한다*/                dup(fd[0]);                execlp("sort", "sort", NULL);                .        }


파일 식별자 0이 닫힌 후, dup()를 호출하여 파이프의 입력 식별자(fd0)를 표준 입력으로 복사한다. 정렬(sort) 프로그램과 자식의 텍스트 세그먼트(text segment)를 중첩시키기 위해 execlp()의 호출을 사용한다. 새롭게 exec된 프로그램들은 그들의 부모로 부터 표준 stream을 상속받으므로, 실제로 파이프의 입력 끝을 표준 입력처럼 상속받는다. 원래의 부모 프로세스가 파이프로 보내려는 것은 sort로 가게 된다.

dup2()라는 또 다른 시스템 호출을 사용할 수 있다. 이러한 특별한 호출은 유닉스 버전 7로 부터 시작되었고, BSD 버전에서도 수행되며 POSIX 표준에서도 필요하다.

  SYSTEM CALL: dup2();                                                            PROTOTYPE: int dup2( int oldfd, int newfd );                                      RETURNS: 성공시 새 식별자             -1 on error: errno = EBADF (oldfd가 유효한 식별자가 아니다)                                         EBADF (newfd가 범위를 벗어났다)                                                   EMFILE (프로세스에 대해 식별자가 너무 많다)   NOTES: 과거의 식별자는 dup2()에 의해 닫힌다!


이런 특별한 호출을 사용하여 한개의 시스템 호출로 포장되어 있는 close 작업과 실제 식별자 복사 작업을 한다. 게다가, 작업의 원자화(atomic)가 허용되는데 이는 기본적으로 신호의 도착에 의해 작업이 중단되지 않음을 의미한다. 전체 동작은 신호의 발송을 위해 커널에게 통제권을 돌려주기 전에 일어난다. 프로그래머는 호출하기 전에 원래의 dup() 시스템 호출을 가지고 close() 작업을 수행해야만 한다. 그것은 그들 사이에서 경과되는 작은 시간의 양에 대한 다소의 약점을 가진 두개의 시스템 호출로 끝난다. 그 짧은 사이에 신호가 도착한다면, 시별자의 복사는 실패한다. 물론, dup2()는 이러한 문제를 해결했다.

살펴보자:

        .        .        childpid = fork();        if(childpid == 0)        {                /* Close stdin, duplicate the input side of pipe to stdin */                dup2(0, fd[0]);                execlp("sort", "sort", NULL);                .                .        }


6.2.3 파이프 쉬운 방법! (pipes the Easy Way!)

앞의 두서없는 글들이 파이프를 만들고 사용하는 매우 무난한 방법처럼 보였다면, 또 다른 것이 있다.

  LIBRARY FUNCTION: popen();                                                      PROTOTYPE: FILE *popen ( char *command, char *type);                              RETURNS: 성공시 새로운 파일 스트림(stream)                                                    fork(),pipe()호출이 실패했을 때 널(NULL)  NOTES: 명령어("command")를 사용하여 파이프를 만들고 fork/exec를 수행한다.


이 표준 라이브러리 함수는 내부적으로 pipe()를 호출하여 반이중 파이프라인을 만든다. 쉘에서 자식 프로세스를 생성(fork)하고, 본 쉘(Bourne ahell)을 exec하고 "command" 아규먼트를 실행한다. 자료 흐름의 방향은 두번째 아뮤먼트인 "type"에 의해 결정된다. "read"이나 "write"에 대해 "r"이나 "w"가 될 수 있다. 두가지가 모두 될 수는 없다. 리눅스에서, 파이프는 "type"의 첫번째 글자에 의해 지정된 모드로 열려진다. 따라서, "rw"를 넘긴다면 읽기("read") 모드로 열려진다.

이 라이브러리 함수가 당신을 위해 까다로운 일을 수행하는 동안, 중요한 흥정(tradeoff)이 일어난다. pipe() 시스템 호출을 사용하고 fork/exec를 취급하는 것에 의해 당신은 잠시 통제권을 잃어버린다. 본 쉘이 직접 사용되므로, "command" 아규먼트 내에서 와일드 카드(wildcard)를 포함한 쉘 메타문자 확장(shell metacharacter expansion)이 가능하다.

popen()에 의해 만들어진 파이프는 pclose()로 닫아야만 한다. popen/pclose가 표준 파일 스트림 I/O 함수인 fopen(),fclose()와 매우 비슷하다는 것을 알았을 것이다.

 LIBRARY FUNCTION: pclose();                                                     PROTOTYPE: int pclose( FILE *stream );                                            RETURNS: wait4() 호출의 탈출 상태(exit status)             -1 스트림("stream")이 유효하지 않거나 wait4()가 실패했으면   NOTES: 파이프 프로세스가 종료되기를 기다렸다가 스트림을 닫는다.


pclose() 함수는 popen()에 의해 생성(fork)된 프로세스에 대해 wait4()를 수행한다. wait4()로부터 반환될 때, 파이프와 파일 스트림을 파괴한다. 일반적인 스트림에 기초한 파일 I/O에 대한 fclose() 함수와 동의어라 할 수 있다.

sort명령어로 파이프를 열어 문자열의 배열을 정렬처리하는 예제를 살펴보자:

/***************************************************************************** 리눅스 프로그램머를 위한 가이드 - 6장 에서 발췌 (C)opyright 1994-1995, Scott Burkett *****************************************************************************  MODULE: popen1.c *****************************************************************************/#include #define MAXSTRS 5int main(void){        int  cntr;        FILE *pipe_fp;        char *strings[MAXSTRS] = { "echo", "bravo", "alpha",                                  "charlie", "delta"};^I/*popen() 호출을 사용하여 단방향 파이프를 만든다*/        if (( pipe_fp = popen("sort", "w")) == NULL)        {                perror("popen");                exit(1);        }^I/*반복 처리*/        for(cntr=0; cntr                fputs(strings[cntr], pipe_fp);                fputc('n', pipe_fp);        }^I/*파이프를 닫는다*/        pclose(pipe_fp);        return(0);}


popen()는 자신의 명령을 수행하는데 쉘을 사용함으로, 모든 쉘 확장 문자들과 메타문자의 사용이 가능하다. 더군다나 redirection과 같은 보다 진보된 기술과 파이프의 출력조차 popen()에서 사용될 수 있다. 다음의 간단한 호출을 살펴보자:

        popen("ls ~scottb", "r");        popen("sort > /tmp/foo", "w");        popen("sort | uniq | more", "w");


popen()의 또 다른 예인, 두개의 파이프(하나는 ls, 다른 하나는 sort)를 여는 작은 프로그램을 살펴보자:

/***************************************************************************** 리눅스 프로그램머를 위한 가이드 - 6장 에서 발췌 (C)opyright 1994-1995, Scott Burkett *****************************************************************************  MODULE: popen2.c *****************************************************************************/#include int main(void){        FILE *pipein_fp, *pipeout_fp;        char readbuf[80];^I/*popen() 호출을 사용하여 단방향 파이프를 만든다*/        if (( pipein_fp = popen("ls", "r")) == NULL)        {                perror("popen");                exit(1);        }^I/*popen() 호출을 사용하여 단방향 파이프를 만든다*/        if (( pipeout_fp = popen("sort", "w")) == NULL)        {                perror("popen");                exit(1);        }^I/*반복 처리*/        while(fgets(readbuf, 80, pipein_fp))                fputs(readbuf, pipeout_fp);^I/*파이프를 닫는다*/        pclose(pipein_fp);        pclose(pipeout_fp);        return(0);}


popen()의 마지막 예제를 위해, 넘겨받은 명령어와 파일명간의 파이프라인을 여는 일반적인 프로그램을 작성해 보자:

/***************************************************************************** 리눅스 프로그래머를 위한 가이드 - 6장 에서 발췌 (C)opyright 1994-1995, Scott Burkett *****************************************************************************  MODULE: popen3.c *****************************************************************************/#include int main(int argc, char *argv[]){        FILE *pipe_fp, *infile;        char readbuf[80];        if( argc != 3) {                fprintf(stderr, "USAGE:  popen3 [command] [filename]n");                exit(1);        }^I/*입력 파일을 연다*/        if (( infile = fopen(argv[2], "rt")) == NULL)        {                perror("fopen");                exit(1);        }^I/*popen() 호출을 사용하여 단방향 파이프를 만든다*/        if ((