728x90
반응형

허용 함수를 다 안다는 전제로 글을 작성했습니다. 모르면 아래 글 보기.

https://jinho-study.tistory.com/1138

 

42 서울 pipex 정리 2 (access, dup2, execve, perror, strerror)

이어서 나머지 함수들을 알아보자. https://jinho-study.tistory.com/1137 pipex 정리 1 ( 프로세스, fork, pipe, wait, waitpid) 개요 pipex는 우리가 만들 pipex 프로그램을 위와 같은 방식으로 돌렸을 때, 아래 명령어

jinho-study.tistory.com

 

전체적으로 봤을 때 로직은 아래와 같이 생각보다 간단하다. 실제로 파일 3개면 구현 가능하다.

int	main(int ac, char **av, char **envp)
{
	int		i;

	if (ac != 5)
		에러 출력 후 종료
	i = -1;
	while (++i < 2)
	{
		if (i == 0)
       		명령어 파싱 -> 첫 번째 프로세스 실행(infile을 stdin으로 받고 명령어 실행)
		else if (i == 1)
			명령어 파싱 -> 두 번째 프로세스 실행(첫 번째 프로세스 실행 결과를 stdin으로 
            받고 명령어 실행 결과를 outfile에 저장)
	}
	포크된 프로세스 만큼 wait를 해준다. 멘덴토리의 경우에는 2번
	i = -1;
	while (++i < 2)
		wait(NULL);
}

다만 여기서 알고 넘어가야 될게 2가지가 있다. minishell 내용 같긴 한데 pipex 하면서 알고 간다면

minishell 할 때 아주 큰 도움이 된다.

1 ) 쉘에서 명령어들은 병렬로 돌아간다. 

예를 들어 sleep 3 | sleep 3은 6초를 기다릴 것 같지만, 실제로는 병렬로 돌기에 3초를 기다린다.

병렬이더라도 cat | cat 같은 경우에는 애초에 앞에서 입력이 들어와야 넘어가기 때문에 프로그램이 끝나지 않는다. 

2) 파이프 개수만큼 fork 되는 것이 아니라 파이프 개수 + 1 만큼 fork 된다.

실행되는 명령어가 2개(ls | cat)라서 pipe가 한 개라고 fork를 한번 하지 않는다.

실제로 bash에서 그냥 exit은 당연히 꺼지지만 exit | exit 은 bash가 꺼지지 않는다. 

bash 꺼짐
bash 안 꺼짐

그냥 exit은 fork가 안되고 바로 실행돼서 bash가 꺼지지만, exit | exit의 경우에는 fork가 2번 실행되어서

프로세스가 3개라 bash가 꺼지지 않는다. 이러한 구조를 가지는 것은 자식 프로세스에서 명령어를 수행하고

부모(main) 프로세스에서 모든 자식 프로세스를 기다리게 하기 위함이 아닐까?라고 작성자는 이해했다. 

 

각 프로세스 별로 구현해야 되는 것들을 순서대로 쭉 나열해 보면

1) 첫 번째 프로세스 

1.1) infile 읽고 pipe 세팅

자식 프로레스)

infile을 stdin으로 dup2, fd[1]을 stdout으로 dup2 해준다.

여기서 fd[0]만 사용 안 하니까 fd[0]만 close 하는 경우가 많은데, 둘 다 close 해줘야 된다.

fd[0]만 close 하면 yes | head -1 같은 테스트의 경우 프로그램이 끝나지 않는다.

부모 프로레스)

fd[0]을 stdin으로 dup2 해준다. 여기서 fd[0]은 자식 프로세스에서의 명령어 실행 결과가 된다.

결국 첫 번째 명령어의 실행 결과가 stdin으로 들어와 있는 상태라고 생각하면 된다.

여기서도 마찬가지로 fd[0], fd[1] 둘 다 close 해준다.

static void	dup_child_1(char **av, int *fd)
{
	int	infile;

	infile = open(av[1], O_RDONLY);
	if (infile == -1)
		perror_exit("infile error");
	if (dup2(infile, STDIN_FILENO) == -1)
		perror_exit("dup2 error");
	if (dup2(fd[1], STDOUT_FILENO) == -1)
		perror_exit("dup2 error");
	close(fd[0]);
	close(fd[1]);
}

static void	child_process_1(char **av, char **envp)
{
	int		fd[2];
	pid_t	pid;

	if (pipe(fd) == -1)
		perror_exit("pipe error");
	pid = fork();
	if (pid == -1)
		perror_exit("fork error");
	if (pid == 0)
	{
		dup_child_1(av, fd);
		execute(av[2], envp);
	}
	if (dup2(fd[0], STDIN_FILENO) == -1)
		perror_exit("dup2 error");
	close(fd[0]);
	close(fd[1]);
}

1.2) 명령어 파싱 후 실행

env 실행 결과

메인에서 인자로 받는 envp 안에는 이런 식으로 환경변수들이 저장되어 있다.

여기서 우리가 필요한 것은 PATH=/Users/jinhokim/.brew/bin:/Users/jinhokim/.brew/bin:/usr/local/bin:/usr/bin:/

bin:/usr/sbin:/sbin:/usr/local/munki이다. PATH= 뒤의 문자열을 : 기준으로 split 하게 되면 

["/bin", "/usr/bin", ...] 같은 결과가 되는데 총 6개의 경로가 나오게 된다. 이 경로들 안에 명령어 파일들이 들어있다.

예를 들어 ls는 /bin/ 안에 들어있는데 그렇기에 쉘에서 /bin/ls를 실행하면 ls가 실행된다.

/bin/ls

이제 저 6개의 경로와 첫 번째 명령어를 ft_strjoin한 결과를 access 함수를 통해 실행 가능한 파일인지 확인하고

없는 명령어의 경우에는 에러 출력, 있는 명령어의 경우에는 그냥 execve를 실행해주면 된다.

// path -> ["/bin", "/usr/bin", ...]
while (path[i])
	{
	// joined_cmd = "/ls"
	// ret_cmd = "/bin/ls"
        ret_cmd = ft_strjoin(path[i++], joined_cmd);
		if (access(ret_cmd, X_OK) != -1)
		{
			free(joined_cmd);
			return (ret_cmd);
		}
		free(ret_cmd);
	}
	free(joined_cmd);
	return (NULL);
}

 

2) 두 번째 프로세스 

2.1) outfile에 실행 결과 저장

자식 프로레스)

outfile을 생성하고 stdout으로 dup2 해준다. 그러고 위에 설명했던 명령어 파싱 후 실행을 반복하면 된다.

stdout이 outfile에 연결되어 있기 때문에 이 프로세스에서 표준 출력되는 내용들은 모두 outfile 안에 저장된다.

부모 프로레스)

아무것도 할 필요가 없다.

static void	child_process_2(char **av, char **envp)
{
	pid_t	pid;
	int		outfile;

	pid = fork();
	if (pid == -1)
		perror_exit("fork error");
	if (pid == 0)
	{
		outfile = open(av[4], O_RDWR | O_CREAT | O_TRUNC, 0644);
		if (outfile == -1)
			perror_exit("outfile error");
		if (dup2(outfile, STDOUT_FILENO) == -1)
			perror_exit("dup2 error");
		execute(av[3], envp);
	}
}

 

 

728x90
반응형
728x90
반응형

이어서 나머지 함수들을 알아보자. 

https://jinho-study.tistory.com/1137

 

pipex 정리 1 ( 프로세스, fork, pipe, wait, waitpid)

개요 pipex는 우리가 만들 pipex 프로그램을 위와 같은 방식으로 돌렸을 때, 아래 명령어와 똑같이 동작하도록 구현해야 되는 과제이다. 즉 infile 파일을 읽고 명령어 2개를 실행한 결과를 outfile에

jinho-study.tistory.com

 

함수 정리

1. access

#include <unistd.h>
int access(const char *pathname, int mode);

access 함수는 파일의 권한을 확인하고 성공하면 0 실패하면 -1을 반환한다.

mode에 따라 확인하는 내용이 달라지는데 mode는 아래 4가지가 있다.

R_OK(파일 존재, 읽기 권한), W_OK(파일 존재, 쓰기 권한), X_OK(파일 존재, 실행 권한), R_OK(파일 존재)

우리는 이 함수를 써서 우리가 입력한 명령어가 실제로 있는 명령어인지 확인할 수 있다.

예시 1) 파일이 있으면 Success, 없으면 Fail이 출력된다.

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

int main(void)
{
	char *pathname = "./test.txt";
	if ( access(pathname, R_OK | W_OK) == 0)
		printf("읽고 쓰기 가능\n");
	else
		printf("권한이 없거나 파일이 없음");
}

 

2. dup2

#include <unistd.h>
int dup2(int fd1, int fd2)

dup2 함수는 디스크립터를 변경해주는 함수이다. fd2가 fd1을 가리키게 하고 기존 fd2는 close 된다.

아래 함수는 pipex 과제 중 만든 함수인데, 정상적으로 동작할 시 std_in을 표준 입력으로 std_out을 표준 출력으로 바꿔준다.

static void	pipe_control(int close_fd, int std_in, int std_out, t_info *info)
{
	close(close_fd);
	if (dup2(std_in, STDIN_FILENO) == -1)
		invalid_exit_opt(info, "STDIN dup2 error", 1);
	if (dup2(std_out, STDOUT_FILENO) == -1)
		invalid_exit_opt(info, "STDOUT dup2 error", 1);
	close(std_in);
	close(std_out);
}

pipex 하면서 보기는 쉽지 않은 경우이긴 하지만 stdin에 dup2를 쓰고 close를 한 경우 stdin이 죽어서 계속 입력으로 

EOF를 받게 된다. minishell 정리할 때도 작성하겠지만 아래와 같은 식으로 stdin, stdout을 dup 함수를 통해 기억해두고

마지막에 dup2를 사용해 stdin, stdout을 되돌려 줄 수 있다. 작성자는 이 에러 때문에 minsihell 할 때 10시간을 버렸다.

int	main(int ac, char **av, char **envp)
{
	int		stdin_dup;
	int		stdout_dup;

	stdin_dup = dup(0);
	stdout_dup = dup(1);
	//~~~
	//process
	//~~~
	dup2(stdin_dup, 0);
	dup2(stdout_dup, 1);
	close(stdin_dup);
	close(stdout_dup);
}

 

3. execve

#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[])

execve 함수는 exec 계열 함수 중 하나로 프로세스를 하나 생성해서 명령어를 실행시키고 자신을 종료시킨다.

성공 시에는 종료가 되기 때문에 실패 시에만 -1을 반환한다.

예시 1) Running ls with execve 출력 후 ls 명령어가 실행된다.

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

int	main(int argc, char **argv, char **envp)
{
	char *arg[2] = {"ls"};
	printf("Running ls with execve\n");
	execve("/bin/ls", arg, envp);
	printf("execve failed to run ls\n");
}

 

아래는 에러를 출력해주는 함수들인데 우리 프로그램에서 에러를 출력할 때 쉘과 거의 똑같이 출력하고 싶다면 사용하면 된다.

마음대로 출력할 거라면 그냥 fd_putstr 같은 함수를 사용하면 된다.

4. perror

#include <stdio.h>
void perror(const char* str);

perror 함수는 전역 변수 errno에 해당하는 에러 메시지를 출력해준다. str이 NULL이 아닐 시 str도 출력해준다.

예시 1) myfile이 없을 시 Could not open data file: No such file or directory가 출력된다.

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
   FILE *fh;
 
   if ((fh = fopen("myfile", "r")) == NULL)
      perror("Could not open data file");
}

 

5. strerror

#include <string.h>
char* strerror(int errnum);

strerror 함수는 errnum의 값을 통해 발생했던 에러에 맞는 에러 메시지를 반환해준다.

예시 1) myfile이 없을 시 No such file or directory, 2가 출력된다.

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

int main()
{
	FILE* fh;

	if ((fh = fopen("myfile", "r")) == NULL)
		printf("%s, %d\n", strerror(errno), errno);
	return 0;
}
728x90
반응형

'42 SEOUL > pipex' 카테고리의 다른 글

42 서울 pipex Mandatory  (0) 2022.12.19
42 서울 pipex 정리 1 ( 프로세스, fork, pipe, wait, waitpid)  (0) 2022.09.06

+ Recent posts