42Seoul - Cursus : pipex

eelijus·2022년 3월 3일
0

42Seoul - Cursus

목록 보기
1/11
post-thumbnail

프로세스간 통신(IPC) 방법 중 하나인 pipe를 이용한 프로그램을 완성하는 과제

원본 링크 : https://jewel-draw-b30.notion.site/pipex-5770547891954e81bb3d1b621921342f

헤더 파일

  • include
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>
#include <sys/wait.h>
#include <errno.h>
  • struct
typedef struct s_exec {
				char **cmd_split;
				char  *cmd_path;
				int    fd;
				int    status;
}              t_exec;

typedef struct s_pipe {
				int    write_fd;
				int    pipe_fd[2];
				pid_t  pid[2];
				int    status;
}              t_pipe;

사용되는 함수들

open

이미 존재하는 파일을 열거나 새로운 파일을 생성하는 system call 함수.

open으로 열린 파일은 read, write, lseek, close의 함수와 사용 가능하다.

#include <fcntl.h>

int open(const char *pathname, int flags);
  • pathname

    : open할 파일에 대한 절대경로의 파일명 혹은 상대경로 파일명

  • flag

    • O_RDONLY : 파일을 읽기 전용으로 open
    • O_WRONLY : 파일을 쓰기 전용으로 open
    • O_RDWR : 파일을 읽고 쓸 수 있게 open
  • 반환값

    • 0 이상 : 정상적으로 파일을 열었으며, 열려진 파일의 fd값을 반환

    • -1 : 오류 발생. 상세한 오류는 errno에 설정됨.

  • 사용예시
#include <fcntl.h>
#include <stdio.h>

int	main(void)
{
	int fd_hello;
	int fd_test;

	fd_hello = open("hello.txt", O_RDONLY);
	printf("fd_hello : %d\n", fd_hello);
	//OUTPUT : 3
	fd_test = open("test.txt", O_RDONLY);
	printf("fd_test : %d\n", fd_test);
	//OUTPUT : 4

	return 0;
}

따라서 0, 1, 2는 시스템적으로 건들면 안되는 플래그이므로 정상적으로 open 한 파일의 위치를 가리키는 fd는 3부터 부여된다.

추가적으로, 동일한 파일은 두 번 open하면 3, 4 차례로 fd값이 부여됨.

close

open()함수로 열기한 파일을 사용 중지함.

#include <unistd.h>

int close(int fd);
  • fd

    : 닫기할 파일의 fd 값

  • 반환값

    • 0 : 정상적으로 닫기
    • -1 : 실패 시

read

open()함수로 열기한 파일의 내용을 읽기함

#include <unistd.h>

ssize_t  read(int fd, void *buf, size_t nbytes);
  • fd

    : 파일 디스크립터

  • *buf

    : 파일을 읽어들일 버퍼. 어떤 자료형을 읽어들일지 몰라 void *로 설정돼있음.

    ex) int형 자료를 받고자하면 매개변수의 buf를 int형 배열로 선언해주면 됨.

  • nbytes

    : 버퍼의 크기. 읽어들일 글자 수

  • 반환값

    • 0 이상 : 정상적으로 실행됐을 시 읽어들일 바이트 수 리턴. 즉 nbytes 리턴
    • -1 : 실패
  • 사용 예시

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int	main(void)
{
	int fd_hello;
	int fd_test;
	int readbuf_hello;
	int readbuf_test;
	char buf[100];

	//정상적으로 open되었는지 확인
	fd_hello = open("hello.txt", O_RDONLY);
	fd_test = open("test.txt", O_RDONLY);
	printf("hello, test : %d, %d\n", fd_hello, fd_test);

	readbuf_hello = read(fd_hello, buf, 5);
	readbuf_test = read(fd_test, buf, 4);

	printf("hello : %d\n", readbuf_hello);
	//OUTPUT : 5
	printf("test : %d\n", readbuf_test);
	//OUTPUT : 4
	close(fd_hello);
	close(fd_test);

	return 0;
}

write

malloc

free

dup

fd를 복제하는 함수

#include <unistd.h>

int dup(int fd);
  • fd

    : 전달받아 복제해 반환할 d값. 사용하지 않는 디스크립터 번호 하나가 자동으로 지정됨

  • 반환값

    • 0 이상 : 성공 시 새 파일 서술자 반환 → 가장 낮은 서술자 반환 ?
    • -1 : 실패 시 오류 반환
  • 사용 예시

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int	main(void)
{
	int fd_hello;
	int fd_test;
	int fd_duphello;
	int fd_duptest;

	fd_hello = open("hello.txt", O_RDONLY);
	fd_test = open("test.txt", O_RDONLY);

	if (fd_hello == -1 || fd_test == -1)
		printf("fail to open file\n");

	fd_duphello = dup(fd_hello);
	fd_duptest = dup(fd_test);

	printf("duphello : %d, duptest : %d\n", fd_duphello, fd_duptest);
	//OUTPUT : 5. 6

	write(fd_duphello, "duphello", 8);
	write(fd_duptest, "duptest", 7);

	return 0;
}

dup2

fd를 새 fd값으로 복제하는 함수

#include <unistd.h>

int dup2(int fd, int fd2);
💡 [https://www.it-note.kr/5](https://www.it-note.kr/5) [https://nomad-programmer.tistory.com/109](https://nomad-programmer.tistory.com/109)

execve

filename이 가리키는 파일을 실행하는 함수.

execve를 불러낸 프로세스가 execve의 프로세스로 교체됨.

pipex에서는 메인 프로세스가 교체되고 종료되는 것을 방지하기 위해 fork로 자식 프로세스를 생성, 자식프로세스에서 execve를 실행

#include <unistd.h>

int execve(const char *filename, const *char argv[], char *const envp[]);
  • file

    : 파일 이름. path. 실행가능한 binary(바이너리 실행파일)거나 shell(스크립트 파일)이어야 함.

  • argv[]

    : 파일인자의 포인터

  • envp[]

    : 환경변수의 포인터. 마지막은 NULL이어야함

  • 실행예시

argv와 envp는 포인터 배열로 filename의 인자로 들어감. 마지막에 NULL 문자열 저장.

아래 코드는 /bin/sh 를 실행시키는 코드

#include <unistd.h>

int   main(void)
{
				char *str[2];
				str[0] = "/bin/sh";
				str[1] = NULL;

				execve(str[0], str, NULL);				
}

fork

현재 실행되는 프로세스에 대해 복사본 프로세스를 생성하는 함수

#include <unistd.h>

pid_t  fork(void);
  • 반환값
    • -1 : 실패
    • 0 : 자식 프로세스. 반환한 본인이 새로 생성된 프로세스란 뚯임
    • 0 이상 : 생성된 자식 프로세스의 pid
  • 실행 예시
#include <unistd.h>
#include <stdio.h>

int	main(void)
{
	int counter = 0;
	pid_t pid;

	printf("create child process\n");
	pid = fork();

	switch(pid)
	{
	case -1 :
		printf("fail to create child process\n");
		return (-1);
	case 0 :
		printf("countdown with child process\n");
		while (1)
		{
			printf("child : %d\n", counter--);
			sleep(1);
		}
	default :
		printf("countdown with parent process\n");
		printf("child's pid is %d\n", pid);
		while (1)
		{
			printf("parent : %d\n", counter++);
			sleep(1);
		}
	}
  return (0);
}
  • 결과
create child process
countdown with parent process
child's pid is 59932
parent : 0
countdown with child process
child : 0
child : -1
parent : 1
child : -2
parent : 2
child : -3
parent : 3
child : -4
parent : 4
child : -5
parent : 5
child : -6
parent : 6
child : -7
parent : 7
child : -8
parent : 8
^C

perror

오류 메세지를 출력하는 함수. 전역변수 errono의 값을 해석해 이에 해당하는 시스템 오류 메세지를 stderr에 출력.

#include <stdio.h>

void  perror(const char *str);
  • str

    : 시스템 오류 메시지 다음에 이어 출력할 사용자 정의 문자열 메세지. 관습적으로 프로그램의 이름이 인자로 사용됨.

  • 사용 예시

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int	main(void)
{
	int fd_hello;

	fd_hello = open("no.txt", O_RDONLY);
	if (fd_hello < 0)
		perror("There is no file named no\n");
	else
		close(fd_hello);
	return (0);
}
  • 결과
There is no file named no
: No such file or directory

strerror

오류 메세지 문자열을 가리키는 포인터를 반환하는 함수. errnum의 값을 통해 발생했던 오류에 알맞은 오류 메세지를 가리키는 포인터를 리턴.

#include <string.h>

char *strerror(int errnum);
  • errnum

    : 오류 번호

  • 반환값

    : 오류 번호에 해당하는 오류 문자열을 가리키는 포인터

  • 사용 예시

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int	main(void)
{
	int fd_hello;

	fd_hello = open("no.txt", O_RDONLY);
	if (fd_hello < 0)
		printf("no file named no : %s\n", strerror(errno));
	return (0);
}
  • 결과
no file named no : No such file or directory

exit

정상적으로 프로세스를 종료시키는 함수

프로그램 종료 → 열려있는 파일 스트림을 닫고 atexit함수로 등록한 함수들을 수행

프로세스 내 파일입출력 중인 것을 저장함과 동시에 프로세스를 종료하며 운영체제에 권한을 넘김

#include <stdlib.h>

void  exit(int status);
  • status

    : 호스트 환경에게 알려줄 종료 값. main의 리턴값과 똑같음

    • 0 : 정상 종료
    • 1 : 에러메시지 종료
  • return과 의 차이

    : exit()함수는 바로 프로세스 종료. return ()은 뒤 문장을 실행하며 종료.

pipe

#include <unistd.h>

int  pipe(int fd[2]);
  • fd : 크기가 2인 int형 배열
    • fd[0] - 파이프 출구 : 함수 호출 후 fd[0]dp 데이터를 입력 받을 수 있는 파일 디스크립터가 담김
    • fd[1] - 파이프 입구 : 함수 호출 후 데이터를 출력할 수 있는 파일 디스크립터가 담김
  • 반환값
    • 0 : 성공 시
    • -1 : 실패 시

access

파일 또는 디렉토리의 사용자 권한을 체크하는 함수

#include <unistd.h>

int  access(const char *pathname, int mode);
  • pathname

    : 체크하고자할 디렉토리 또는 파일명

  • mode

    • R_OK : 파일 존재 여부, 읽기 권한 여부
    • W_OK : 파일 존재 여부, 쓰기 권한 여부
    • X_OK : 파일 존재 여부, 실행 권한 여부
    • F_OK : 파일 존재 여부
  • 반환값

    • 0 : 성공 시
    • -1 : 실패 시 → 오류에 대해서는 errno에 세팅됨
  • 사용예시

int	main(void)
{
	int mode = R_OK;

	if (access("hello.txt", mode) == 0)
		printf(" can read and write\n");
	else
		printf("you don't have any right");
	return 0;
}
  • 결과
sujilee@MacBook-Pro:~/Desktop(master⚡) » ./a.out
 can read and write

파일을 삭제하는 system call 함수

#include <unistd.h>

int unlink(const char *pathname);
  • pathname

    : 삭제할 파일명

  • 반환값

    • 0 : 정상적으로 파일 link 삭제
    • -1 : 오류 발생. 상세오류 내용은 errno에 저장됨
  • 사용 예시

#include <stdio.h>
#include <unistd.h>

int	main(void)
{
	if (unlink("hello.txt") == 0)
		printf("successfuly unlinked\n");
	else
		printf("unlink faliled\n");
	return 0;
}

wait

자식 프로세스 작업이 끝날때까지 대기하며, 자식 프로세스가 종료한 상태를 구하는 함수

#include <sys/wait.h>

pid_t  wait(int *status);
  • status

    : 자식 프로세스의 종료 상태. 굳이 알 필요 없으면 NULL전달.

  • 반환값

    • 성공 : 자식 프로세스 id 반환(pid_t)
    • 실패 : -1 반환. 자식 프로세스가 없을 경우.
  • 실행 예시

#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>

int	main(void)
{
	int pid = fork();
	//pid == 0 : 본인이 자식 프로세스란 뜻
	if (pid == 0)
	{
		printf("parent = %d, child = %d\n", getpid(), getppid());
		while (1)
			return (0);
	}
	printf("parent = %d\n", getpid());
	wait(NULL);

	return (0);
}
  • 실행 결과
sujilee@MacBook-Pro:~/Desktop(master⚡) » ./a.out
parent = 69199
parent = 69200, child = 69199

부모는 자식 프로세스가 종료될 때까지 계속 블록에 걸려있다.

waitpid

좀 더 세부적인 wait 함수라고 생각하면 됨

#include <sys/wait.h>

pid_t  waitpid(pid_t pid, int *status, int option);
  • pid

    감시할 자식 프로세스 id

  • status

    자식 프로세스의 종료 상태 정보

  • option

    대기를 위한 옵션

  • 반환값

    • -1 : 오류. 지정한 pid의 프로세스 또는 프로세스 그룹이 없을 경우 발생 & pid가 자식프로세스가 아닐 경우 발생

    • 0 : WHOHANG을 사용했고 자식 프로세스가 종료되지 않았을 경우

    • 0 이상 : 정상 종료된 자식 프로세스 id

      💡 [https://kirkim.github.io/42seoul/2021/08/04/pipex_func.html](https://kirkim.github.io/42seoul/2021/08/04/pipex_func.html)
</aside>

함수 분석

pipex 구현

환경변수

execve()함수의 세번째 인자로 받을 환경변수를 어떻게 처리할 것이냐...

프로그램에서 환경변수를 불러오는 방법 중 main()의 매개변수를 추가하는 방법을 택했다. 왜냐? 젤 간단해보임

#include <stdio.h>

int	main(int argc, char **envp)
{
	int i;

	i = 0;
	while (i < 80)
	{
		printf("%d >> %s\n", i, envp[i]);
		i++;
	}
	return 0;
}

OUTPUT

sujilee@MacBook-Pro:~/Desktop(master⚡) » ./a.out
0 >> ./a.out
1 >> (null)
2 >> TERM_SESSION_ID=w1t0p0:4DB52014-E1B9-4FDE-AE82-5D45EC2B6A02
3 >> SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.6eedkvzIF7/Listeners
4 >> LC_TERMINAL_VERSION=3.4.15
5 >> COLORFGBG=7;0
6 >> ITERM_PROFILE=Default
7 >> XPC_FLAGS=0x0
8 >> LANG=ko_KR.UTF-8
9 >> PWD=/Users/sujilee/Desktop
10 >> SHELL=/bin/zsh
11 >> __CFBundleIdentifier=com.googlecode.iterm2
12 >> TERM_PROGRAM_VERSION=3.4.15
13 >> TERM_PROGRAM=iTerm.app
14 >> PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/bin:/Library/Apple/usr/bin
15 >> LC_TERMINAL=iTerm2
16 >> COLORTERM=truecolor
17 >> COMMAND_MODE=unix2003
18 >> TERM=xterm-256color
19 >> HOME=/Users/sujilee
20 >> TMPDIR=/var/folders/3h/zdj9rkg1549csmzrgzrw6dk40000gn/T/
21 >> USER=sujilee
22 >> XPC_SERVICE_NAME=0
23 >> LOGNAME=sujilee
24 >> ITERM_SESSION_ID=w1t0p0:4DB52014-E1B9-4FDE-AE82-5D45EC2B6A02
25 >> __CF_USER_TEXT_ENCODING=0x0:3:51
26 >> SHLVL=1
27 >> OLDPWD=/Users/sujilee
28 >> ZSH=/Users/sujilee/.oh-my-zsh
29 >> PAGER=less
30 >> LESS=-R
31 >> LSCOLORS=Gxfxcxdxbxegedabagacad
32 >> _=/Users/sujilee/Desktop/./a.out
33 >> (null)
34 >> executable_path=./a.out
35 >>
36 >>
37 >>
38 >> ptr_munge=
39 >> main_stack=
40 >> executable_file=0x1a01000004,0x2cb154
41 >> dyld_file=0x1a01000004,0xfffffff000d8a2d
42 >> th_port=
43 >> (null)
[1]    72371 segmentation fault  ./a.out
profile
sujileelea

0개의 댓글