파이프를 이용한 동기화
리눅스에서는 프로그램에서 표준 출력장치로 출력한 내용을 다른 프로그램의 입력으로 전송할 수 있는데 이를 파이프 라 한다.
popen()는 이 파이프기능을 이용해 다른 프로그램의 실행 결과를 읽어들이거나, 다른 프로그램의 표준 입력 장치로 출력할 수 있다.
1
2
3 #include <stdio.h>
FILE *popen (const char *command, const char *open_mode);
int pclose (FILE *stream_to_close);
command : 쉘에서 실행하는 명령어
open_mode : 출력방향
- 'r' : 파이프를 통해서 입력받는다.
- 'w' : 파이프를 통해서 출력한다.
<예제 1 - popen> 접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main () {
FILE *read_fp;
char buffer[BUFSIZ + 1 ];
int chars_read;
memset(buffer, '\0' , sizeof (buffer));
read_fp = popen("cat popen*.c | wc -l" , "r" );
if (read_fp != NULL ) {
chars_read = fread(buffer, sizeof (char ), BUFSIZ, read_fp);
while (chars_read > 0 ) {
buffer[chars_read - 1 ] = '\0' ;
printf("Reading:-\n %s\n" , buffer);
chars_read = fread(buffer, sizeof (char ), BUFSIZ, read_fp);
}
pclose(read_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
결과)
쉘에서 cat popen*.c | wc -l"을 실행시키고 결과를 출력한다.
이 때 wc -l은 파일의 라인 수를 뜻한다.
fread(buffer, sizeof(char), BUFSIZ, read_fp); 는 읽은 byte의 크기를 뜻한다.
접기 위 예제에서 알 수 있듯이 popen()은 쉘인 sh를 먼저 호출하고 command를 인수로 전달하여 실행한다. 따라서 popen()을 호출할 때마다 쉘도 함께 실행되므로 비효율적이다.
pipe()는 쉘을 호출하는 overhead 없이 두 프로그램간의 데이터를 전달하는 수단을 제공한다. 원형은 다음과 같다.
1
2 #include <unistd.h>
int pipe (int file_descriptor[2 ]);
file_descriptor[0] : 수신 파일의 파일 디스크립터
file_descriptor[1] : 발신 파일의 파일 디스크립터
파일 디스크립터는 운영체제가 만든 파일 또는 소켓을 지칭하기 위해 부여한 숫자이다. 파일을 관리하기 위해 필요한 정보들을 가지고 있다.
반환)
성공시 0을 반환한다.
실패시 -1을 반환한다.
<예제 1 - pipe> 접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 #include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main () {
int data_processed;
int file_pipes[2 ];
const char some_data[] = "123" ;
char buffer[BUFSIZ + 1 ];
memset(buffer, '\0' , sizeof (buffer));
if (pipe(file_pipes) == 0 ) {
data_processed = write(file_pipes[1 ], some_data, strlen(some_data));
printf("Wrote %d bytes\n" , data_processed);
data_processed = read(file_pipes[0 ], buffer, BUFSIZ);
printf("Read %d bytes: %s\n" , data_processed, buffer);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
결과)
11에서 pipe()를 이용해 연결을 한다.
12에서 wirte()를 이용해 some_data의 내용(=123)을 some_data의 길이만큼 file_pipes[1]에 저장하고, data_processed에는 wirte()의 리턴값인 3바이트가 저장된다.
14에서 read()를 통해 file_pipes[0]의 내용을 buffer에 저장한다. 이 때 file_pipes[0]에는 file_pipes[1]에서 수신한 내용이 들어가있다.(=123) 마찬가지로 data_processed에는 read()의 리턴값인 3바이트가 저장된다.
write(int filedes, void *buf, size_t nbyte)
nbyte바이트를 사용자버퍼 buf에서 파일디스크립터 filedes가 가리키는 곳으로 출력한다.
read(int filedes, void *buf, size_t nbyte)
filedes의 파일이나 장치에서 nbyte바이트를 읽어서 이를 변수 buf에 넣는다.
두 함수 다 성공시 읽어들인/출력한 바이트 수를 리턴한다.
접기
<예제 2 - pipe2(fork)> 접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main () {
int data_processed;
int file_pipes[2 ];
const char some_data[] = "123" ;
char buffer[BUFSIZ + 1 ];
pid_t fork_result;
memset(buffer, '\0' , sizeof (buffer));
if (pipe(file_pipes) == 0 ) {
fork_result = fork();
if (fork_result == -1 ) {
fprintf(stderr, "Fork failure" );
exit(EXIT_FAILURE);
}
if (fork_result == 0 ) {
data_processed = read(file_pipes[0 ], buffer, BUFSIZ);
printf("Read %d bytes: %s\n" , data_processed, buffer);
exit(EXIT_SUCCESS);
} else {
data_processed = write(file_pipes[1 ], some_data,
strlen(some_data));
printf("Wrote %d bytes\n" , data_processed);
}
}
exit(EXIT_SUCCESS);
}
결과)
코드의 내용 자체는 위 예제와 같다. 단 fork를 통해서 부모에서는 파이프에 데이터를 쓰고 자식은 파이프에서 데이터를 읽는다.
접기
파이프 호출의 다음 단계는 하위 프로세스가 상위 프로세스와 다른 프로그램이 되도록 허용하는 것이다. 이 때 어려운 점은 새로운 실행 프로세스가 액세스 할 파일 디스크립터를 알아야 한다는 점이다. 이를 위해서는 data producer와 data consumer를 필요로 한다.
exec()를 이용해 수행할 수 있다.
<예제 3 - pipe3(exec)> 접기
[#M_<소스파일1(pipe3.c)>|접기|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main () {
int data_processed;
int file_pipes[2 ];
const char some_data[] = "123" ;
char buffer[BUFSIZ + 1 ];
pid_t fork_result;
memset(buffer, '\0' , sizeof (buffer));
if (pipe(file_pipes) == 0 ) {
fork_result = fork();
if (fork_result == (pid_t )-1 ) {
fprintf(stderr, "Fork failure" );
exit(EXIT_FAILURE);
}
if (fork_result == 0 ) {
sprintf(buffer, "%d" , file_pipes[0 ]);
(void )execl("pipe4" , "pipe4" , buffer, (char *)0 );
exit(EXIT_FAILURE);
} else {
data_processed = write(file_pipes[1 ], some_data,
strlen(some_data));
printf("%d - wrote %d bytes\n" , getpid(), data_processed);
}
}
exit(EXIT_SUCCESS);
}
접기
<소스파일2(pipe4.c)> 접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14 #include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main (int argc, char *argv[]) {
int data_processed;
char buffer[BUFSIZ + 1 ];
int file_descriptor;
memset(buffer, '\0' , sizeof (buffer));
sscanf(argv[1 ], "%d" , &file_descriptor);
data_processed = read(file_descriptor, buffer, BUFSIZ);
printf("%d - read %d bytes: %s\n" , getpid(), data_processed, buffer);
exit(EXIT_SUCCESS);
}
접기
<결과화면> 접기
접기
pipe3.c의 19에서 sprintf()를 통해 file_pipes[0]의 내용을 buffer에 저장한다.
20을 통해서 buffer를 file_descriptor로 pipe4프로그램을 실행한다.
execl("전체 경로 이름","실행하고자 하는 프로그램 이름","실행하고자 하는 프로그램의 인수들")
pipe4.c의 10에서 sscanf()를 통해 argv[1](=buffer=file_descriptor)를 읽어서 실행한다.
_M#]