-
시스템 프로그래밍 실습 8주차 : IPCSystem Programming/Ubuntu Linux 2021. 10. 17. 14:04728x90
시스템 프로그래밍 실습 8주차 : IPC
[목차]
- IPC
- Open Files in Kernel
- I/O Redirection
- Pipes
- Anonymous Pipe
- Named Pipe (FIFOs)
IPC란?
IPC = Inter Process Communication!
- 프로세스들끼리 데이터를 주고 받는 기능
- IPC의 방법들
1. signals (7주차)
2. I/O Directions (3주차, 8주차)
3. Anonymous Pipe (3주차, 8주차)
4. Named Pipe (FIFO) (3주차, 8주차)
5. Shared Memory, Message Queue, etc.
https://doitnow-man.tistory.com/110
Open Files in Kernel
* 사실 모든 것들은 file descriptor로 표현할 수 있다!
유닉스 커널에서는 파일 여는 것을 다음과 같은 3단계로 표현한다
1) Descriptor table
- 프로세스 당 하나의 테이블
- open file table에 있는 entry를 가리킨다
2) Open File table
- 모든 프로세스들에서 공유된다
- v-node table에 있는 entry를 가리키며, 현재 파일 위치와 mode, reference count 등의 정보를 가지고 있다
3) v-node table
- 모든 프로세스들에서 공유된다
- 그 파일 자체에 대한 정보(size, permission, type, ....)를 가지고 있다
예시 1) Calling open( ) twice with the same filename / 하나의 프로세스에서 같은 파일 이름을 open()로 두번 불렀을 때
- 같은 파일을 두 번 열면 서로 다른 파일 디스크립터를 부여하고 서로 다른 파일 오프셋을 유지. 따라서 프로세스에서 파일 입출력은 open 함수로 연 작업을 구분하는 것이며 실제 물리적인 파일이 같은지는 구분하지 않는다
- 이 때 프로세스의 파일 디스크립터 테이블에는 각각의 파일 디스크립터를 부여하고 커널에서도 각각의 파일의 상태와 현재 작업 위치를 별도로 갖는다. 다만 이 둘은 같은 vnode를 참조!예시 2) Calling fork() / fork() 함수를 불렀을 때
- fork() 함수에 의해 프로세스가 부모 프로세스, 자식 프로세스 두 개가 된다. 이때 부모 프로세스와 자식 프로세스는 fork() 호출 시에 File Descriptor 를 공유하게 되며, 즉 자식 프로세스와 부모 프로세스가 같은 파이프를 가리킴
- 따라서 이 경우에는 결과값은 y가 출력될 것이다
I/O Redirection
3주차에서도 한번 언급되었다! (https://asidefine.tistory.com/70)
간단하게 정리하자면, 표준 입력과 출력의 방향을 재지정한다. 즉, 모니터로 나와야하는 걸 파일로 지정할 수 있다!
( = standard I/O를 파일들로 리다이렉트한다 )
ls > foo.txt grep include < ex.c
- > : redirect stdout for overwrite (create if not exist) : stdout 결과를 파일에 덮어쓴다
- >> : redirect stdout for append : stdout 결과를 파일에 추가하여 쓴다
- < : redirect stdin
- 2> : redirect stderr
* dup2() 함수 = file descriptor 복제 함수 (이걸로 I/O direction을 구현할 수 있다)
#include <unistd.h> int dup2(int fd, int fd2);
첫 번째 인자로 열려진 파일 디스크립터를 전달하고 두 번째 인자로 파일 디스크립터를 전달하면,
첫 번째 인자로 열려진 파일 디스크립터가 참조하는 파일 테이블 엔트리를 두 번째 전달한 파일 디스크립터도 참조.
만약 두 번째 인자로 전달한 파일 디스크립터가 열려진 파일 디스크립터일 때는 먼저 닫고 난 후에 복제.
예시 1)
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <string.h> #include <fcntl.h> int main(int argc,char **argv) { int fd = 0; if(argc != 2) { fprintf(stderr,"usage: %s [file name]\n",argv[0]); return 1; } fd= open(argv[1],O_WRONLY|O_CREAT|O_TRUNC); if(fd == -1) { perror("failed open "); return 1; } if(dup2(fd,STDOUT_FILENO) == -1) { perror("failed dup2"); return 1; } printf("Hello World\n"); close(fd); return 0; }
- dup2(fd, STDOUT_FILENO);를 호출한 후에 printf 함수를 호출하면 fd로 연 파일에 쓰여진다.
예시 2)
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <string.h> #include <fcntl.h> int main(void){ int fd1, ret; char message[32]={"STDERR from fd1\n"}; //그림 1번 fd1=open("made_by_fd1",O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); if(fd1<0){ printf("file open error\n"); exit(0); } //표준 입출력으로 print됨 printf("file open\n"); //fd1의 파일 디스크립터가 명시한 STDOUT_FILENO의 파일 디스크립터로 //복제됨, //그림 2번 ret=dup2(fd1,STDOUT_FILENO); //fd1으로 출력됨 printf("fd1 :%d, ret:%d\n",fd1,ret); //STDERR_FILENO 디스크립터가 명시된 fd1으로 복제됨 //그림 3번 ret=dup2(STDERR_FILENO,fd1); //fd1은 에러로 출력됨 write(fd1,message,strlen(message)); //stdout이 file로 써짐 printf("printf를 썼지만 파일에 기록됨 \n"); close(fd1); }
- 우선 파일을 여는데 위의 예제와 같은 이름으로 파일을 엽니다. 그렇다면 fd1은 3이 되겠네요.
- dup2로 STDOUT_FILENO라는 파일 서술자를 명시된 fd1로 바꿔버립니다. dup2를 조금 더 쉽게 이해하려면 두 번째인자가 첫 번째 인자로 가리키는 화살표 방향이 바뀐다라고 이해하시면 됩니다. 그리고 dup2는 성공적으로 호출이 되면 두 번째 인자의 값을 반환합니다. 실패시 -1을 반환하므로 에러 처리는 필수인데 저는 귀찮아서 하지 않았습니다.
printf는 표준 출력인데 printf로 문자열을 출력한다면 fd1으로 출력하는 것과 같습니다. 그러니까 우리가 만든 파일로 출력이 되겠네요.
- 이제 fd1을 표준 에러로 redirect합니다. 방향을 바꿔버린다는 것이죠. 그렇게 되어 fd1으로 메시지를 출력하게 되면 표준 에러로 메시지를 출력하는 것과 같습니다.
$ gcc dup2_test.c $ ./a.out file open STDERR from fd1 $ cat made_by_fd1 fd1 :3, ret:1 # printf를 썼지만 파일에 기록됨
예시 3)
- dup2 호출 전
- dup2 호출 후
https://architectophile.tistory.com/10
https://reakwon.tistory.com/104
Pipe
* Pipe도 3주차에서도 한번 언급되었다! (https://asidefine.tistory.com/70)
한 프로세스의 output을 다른 프로세스의 input으로 넣을 수 있다!
$ cat test.txt | grep "a"
-> test.txt의 내용을 화면에 보여주고, test.txt로부터 문자열 a가 있는 곳을 다 검색한다
- 여러 개의 프로세스가 공통으로 사용하는 임시공간
- 임시 공간은 실제로 파일 시스템에 생성되는 임시 파일
- 하나의 프로세스가 파이프에 쓰게 되면 다른 프로세스는 그 파이프에서 읽는 방식으로 쓰게 됨
- 파이프는 시스템 내부에서 관리하는 임시 파일을 이용하므로, 다른 IPC 기법 중 하나인 SIGNAL 과는 다르게 대용량의 메시지도 전송이 가능
Pipe에는 두 가지 종류가 있는데 Anonymous pipe와 Named pipe(=FIFO)가 그것이다!
Pipes - Anonymous Pipe
Anonymous pipe는 일시적이고, 부모 프로세스와 자식 프로세스 간의 IPC이라 할 수 있다
① 파이프는 입구와 출구의 개념이 없음
- 방향성이 없기 때문에 자신이 쓴 메시지를 자신이 읽을 수 있음
- 두 개의 프로세스가 통신할 때는 읽기전용 파이프와 쓰기 전용 파이프의 두 개의 파이프를 사용, 두 개의 파이프를 생성하고, 한쪽 파이프를 일부러 닫아 버리는 것
- 파이프를 두 개 생성하여 하나의 방향으로만 갈 수 있도록 함 -> "pipe() 함수를 통해! "
* pipe() 함수
- pipe를 두 개 생성하여 하나는 읽기용 파이프 fd[0], 다른 하나는 쓰기용 파이프 fd[1]로 사용하기 위해 pipe() 함수를 사용한다!
- 쓰기용 파이프 fd[1]의 output은 읽기용 파이프 fd[0]의 input이 된다!② 파이프는 fork() 함수에 의해 복사 되지 않음
- fork() 함수에 의해서 프로세스가 생성되면, 자식 프로세스는 부모가 사용하던 변수를 복사하게 됨
- 파이프의 경우 복사되는 것이 아니라 File Descriptor 를 공유하게 되며, 즉 자식 프로세스와 부모 프로세스가 같은 파이프를 가리킴
③ 부모 자식프로세스 사이에서만 파이프를 사용 가능 (단점)
- 파이프는 운영체제에서 임시로 생성되는 파일이고, 접근 가능한 방법은 File Descriptor(파일 디스크립터) 를 공유하는 방법만이 존재함
- 익명의 파이프는 부모와 자식 프로세스만이 파일 디스크립터를 공유하므로 다른 프로세스는 파이프를 사용하여 통신이 불가능
※ Named PIPE 의 경우, 다른 프로세스도 지정한 이름으로 파일 디스크립터를 열수 있기 때문에, 부모 자식 프로세스 간이 아니더라도 사용할 수 있음
- 부모 프로세스에서 자식 프로세스로 보낼 때
위의 pipe 함수를 사용하여 생성한 fd[0]과 fd[1] 중,
부모는 자식에게 써서 줘야 하기 때문에 쓰기용 파이프 fd[1]를 이용하고, 읽기용 파이프 fd[0]을 닫는다
자식은 부모가 준 것을 읽어야 하기 때문에 읽기용 파이프 fd[0]를 이용하고, 쓰기용 파이프 fd[1]을 닫는다
- 자식 프로세스에서 부모 프로세스로 보낼 때
위의 pipe 함수를 사용하여 생성한 fd[0]과 fd[1] 중,
부모는 자식이 준 것을 읽어야 하기 때문에 읽기용 파이프 fd[0]를 이용하고, 쓰기용 파이프 fd[1]을 닫는다
자식은 부모에게 써서 줘야 하기 때문에 쓰기용 파이프 fd[1]를 이용하고, 읽기용 파이프 fd[0]을 닫는다
예시 1)
- 자식 프로세스에서 쓰기용 파이프 fd[1]를 닫고 부모 프로세스에선 읽기용 파이프 fd[0]을 닫았기 때문에, "부모가 쓴 것을 자식 프로세스에서 읽는" 코드라고 할 수 있다.
예시 2)
#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> int main(void) { int fd[2], nbytes, rc = 0; pid_t childpid; char string[] = "Hello, world!\n"; char readbuffer[80]; if ((rc = pipe(fd)) < 0) { printf("Creating Pipe is Error [%d]\n", rc); } if((childpid = fork()) == -1) { perror("fork"); return 0; } if (childpid == 0) { /* 자식 프로세스는 Write할것이기에 Read FD는 닫아준다 */ close(fd[0]); /* Pipe에 메시지 보내기 */ write(fd[1], string, (strlen(string)+1)); return 0; } else { /* 부모 프로세스는 Read할것이기에 Write FD는 닫아준다 */ close(fd[1]); /* Pipe에서 메시지 읽기 */ nbytes = read(fd[0], readbuffer, sizeof(readbuffer)); printf("Received Parent string: %s [%d]", readbuffer, nbytes); } return 0; }
예시 3)
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #define MAX_BUF 1024 #define READ 0 #define WRITE 1 int main(){ int fdA[2],fdB[2]; pid_t pid; char buf[MAX_BUF]; int count=0; if(pipe(fdA) < 0){ printf("pipe error\n"); exit(1); } if(pipe(fdB) < 0){ printf("pipe error\n"); exit(1); } if((pid=fork())<0){ printf("fork error\n"); exit(1); } printf("\n"); if(pid>0){ //parent process close(fdA[READ]); close(fdB[WRITE]); while(1){ sprintf(buf,"parent %d",count++); write(fdA[WRITE],buf,MAX_BUF); memset(buf,0,sizeof(buf)); read(fdB[READ],buf,MAX_BUF); printf("parent got message : %s\n",buf); sleep(1); } }else{ //child process close(fdA[WRITE]); close(fdB[READ]); count=100000; while(1){ sprintf(buf,"child %d",count++); write(fdB[WRITE],buf,MAX_BUF); memset(buf,0,sizeof(buf)); read(fdA[READ],buf,MAX_BUF); printf("\tchild got message : %s\n",buf); sleep(1); } } exit(0); }
https://munsonghabnida.tistory.com/86
https://doitnow-man.tistory.com/121
https://reakwon.tistory.com/80
https://velog.io/@hidaehyunlee/minishell-5.-%ED%8C%8C%EC%9D%B4%ED%94%84Pipe-%EC%B2%98%EB%A6%AC
https://jihooyim1.gitbooks.io/unixbasic/content/contents/09.html
Pipes - Named Pipe (FIFOs)
- 리눅스에서는 Named PIPE 기법을 위해 FIFO 라는 특수 파일을 제공
- 부모-자식간이 아닌 독립적인 프로세스 간에 통신하기 위해서는 이름 있는 파이프 사용
- FIFO로 사용할 특수파일을 명령이나 함수로 먼저 생성해야함
* 리눅스 파일 종류
- 일반 파일 : 텍스트 바이너리 형태의 데이터를 저장하는 파일 - 특수 파일 : 데이터 전송, 장치 접근에 사용하는 파일(device file) - 디렉터리 : 파일 저장 위치, 공간- 특수 파일로 생성되는 FIFO는 생성 방법에 차이는 있지만 PIPE와 거의 유사
- 익명의 파이프는 따로 이름이 없는 통신 채널인데 비해서 FIFO 는 mkfifo() 함수에 의해 실제로 생성되는 특수 파일
* mknod나 mkfifo 명령으로 FIFO 파일 생성
1) mknod 명령
#include <sys/stat.h>
int mknod(const char *path, mode_t mode, dev_t dev);
- path : 특수 파일을 생성할 경로
- mode : 특수 파일의 종류와 접근 권한 지정
- dev : 블록/문자 장치 설정값
- 개념 : FIFO 파일 / 특수 파일 생성
- 형식
mknod filename p
- 성공 시 : 0 리턴
- 실패 시 : -1 리턴
※ mode : 생성할 특수파일의 종류 지정
- S_IFIFO : FIFO 특수 파일
- S_IFCHAR : 문자장치 특수 파일
- S_IFDIR : 디렉토리
- S_IFDIR : 블록장치 특수파일
- S_IFREG : 일반파일
2) mkfifo명령
#include <sys/tpyes.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
- const char *pathname : fifo파일이 생성될 경로를 지정한다. 일반 파일의 경로를 지정하는 것과 동일
- mode_t mode : 생성 될fifo파일의 접근 권한을 지정한다.
- 형식 및 예
/usr/bin/mkfifi [-m mode] path....
- path로 지정한 경로에 접근 권한을 지정해 FIFO 파일 생성
- 성공 시 : 0 반환
- 실패 시 : -1 반환
① mkfifo() 함수가 정상적으로 실행되면 인자로 넘긴 경로에 특수 파일로 FIFO 가 등록
② 다른 프로세스는 일반 파일에서 사용하는 것처럼 FIFO를 사용 가능하지만 특정 프로세스가 FIFO를 쓰기용으로 열기 전까지는 다른 읽기용 개방은 블럭(Block) 됨- 어떤 프로세스라도 생성된 경로로 파일에 접근하여 다른 파일을 사용하듯 읽거나 쓰기가능
- FIFO로 pipe와 마찬가지로 단방향 통신, 반드시 읽기전용(O_RDONLY), 쓰기 전용(O_WRONLY) 으로만 열어야 함 (읽기쓰기로는 열 수 X -> 그래도 최초 개방 시에는 읽기쓰기로 열어야 한다! )
FIFOs 사용 방법!
예시 1)
- client (writer) 코드
#include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #define MSG_SIZE 80 #define PIPENAME "./named_pipe_file" int main(void) { char msg[MSG_SIZE]; int fd; int nread, i; /* named pipe 열기, Write 전용으로 열기 */ if ((fd = open(PIPENAME, O_WRONLY)) < 0) { printf("fail to open named pipe\n"); return 0; } /* Data를 보낸다. */ for (i = 0; i < 3; i++) { snprintf(msg, sizeof(msg), "Send Message[%i]", i); if ((nread = write(fd, msg, sizeof(msg))) < 0 ) { printf("fail to call write()\n"); return 0; } } return 0; }
- server (reader) 코드
#include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #define MSG_SIZE 80 #define PIPENAME "./named_pipe_file" int main(void) { char msg[MSG_SIZE]; int fd; int nread, rc; /* 기존에 named pipe가 있으면 삭제 */ if (access(PIPENAME,F_OK) == 0) { unlink(PIPENAME); } /* named pipe 생성하기 */ if ((rc = mkfifo(PIPENAME,0666)) < 0) { printf("fail to make named pipe\n"); return 0; } /* named pipe 열기, Read Write가능 해야 한다 */ if ((fd = open(PIPENAME, O_RDWR)) < 0) { printf("fail to open named pipe\n"); return 0; } while (1) { if ((nread = read(fd, msg, sizeof(msg))) < 0 ) { printf("fail to call read()\n"); return 0; } printf("recv: %s\n", msg); } return 0; }
https://www.softprayog.in/programming/interprocess-communication-using-pipes-in-linux
https://doitnow-man.tistory.com/120
https://tuxthink.blogspot.com/2012/02/inter-process-communication-using-named.html
https://www.softprayog.in/programming/interprocess-communication-using-fifos-in-linux
http://cs.sookmyung.ac.kr/~chang/lecture/sp/chap13.pdf
728x90'System Programming > Ubuntu Linux' 카테고리의 다른 글
Shell 만들기 참고할 것 (0) 2021.10.28 시스템 프로그래밍 실습 9주차 : System V IPC (0) 2021.10.22 시스템 프로그래밍 실습 7주차 : Signals (0) 2021.10.10 시스템 프로그래밍 실습 6주차 : Daemon (0) 2021.10.03 시스템 프로그래밍 실습 5주차 : Processes (0) 2021.09.27