...

[리눅스/ 시스템 프로그래밍] Shell 구현하기 본문

CS/시스템 프로그래밍

[리눅스/ 시스템 프로그래밍] Shell 구현하기

gi2 2022. 1. 5. 18:30

[ MyShell 구현 ]

리눅스 터미널에서 명령어를 받아 처리한 후 결과를 출력하는 Shell 프로그램의 구현

 

[ 프로그램 구현 흐름 ]

 

1. 현재 디렉토리 name 출력하기 

2. 사용자로부터 명령어 입력 받아오기 

3. 명령어 토큰화 하기 (단어별로 나누기)

4. 프로세스 fork하여 자식 생성하기 

5. 부모 프로세스가 wait 하는 동안 자식 프로세스 동작(명령어 실행)

6. exit 입력시 Shell 중단

 

[ 구현 전 알아야 하는 부분

 

shell 프로그램을 구현할 때 반드시 알고 있어야 할 부분은 

리눅스의 명령어는 내부 명령어와 외부 명령어로 나누어져 있다는 점이다. 

외부 명령어는 자식 프로세스의 exec 시스템 콜에 의하여 모두 처리 가능하다. 

보통 대부분의 명령어들이 외부 명령어이다. 

하지만 내부 명령어는 따로 shell 프로그램 내부에 구현해 주어야 한다. 

대표적으로 cd, redirection, help 등이 있다. 

사실 몇 개 안되어서... 대부분이 외부 명령어라고 생각하면 된다. 

 

[ 코드 구현

 

* 나는 귀찮아서,,, 내부 명령어는 리다이렉션만 구현했다.

 

// 전처리 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

#define DELIMS " \t\r\n" //token화 할 때 이용
int status;

// main 

int main(void)
{
	char line[1024];

	while (1)
	{
		printf("%s $ ", get_current_dir_name());
		fgets(line, sizeof(line) - 1, stdin);
		if (run(line) == false)
			break;
	}

	return 0;
}

명령어를 입력 받은 후, shell 프로그램이 중단될 때까지 (run 함수의 반환이 false가 될 때까지) 명령어 입출력을 반복.

 

//run [1] 

bool run(char* line)
{
	char* tokens[300];
	int tokensize ;
	int i;
	pid_t pid = -1;
	bool back; 

        for (i = 0; i < strlen(line); i++) {
		if (line[i] == '&') {
			back = true;
			line[i] = '\0';
			break;
		}
	}

	tokensize = tokenization(line, tokens);

	if (tokensize == 0)
	{
		printf("Tokenization error\n");
		return true;
	}

	if (strcmp(tokens[0],"exit")==0)
		return false;

back 변수는 부모 프로세스와 자식 프로세스를 동시에 실행할 것인지, 자식 프로세스가 실행되는 동안 부모 프로세스가 대기할 것인지를 결정 

명령어를 탐색하여 '&' 가 있는 경우 back을 true로 변경함. ( 동시 실행을 의미 )

명령어를 토큰화 하고, 명령어가 exit인 경우 run 함수가 false를 반환하며 shell에서 빠져나오게 됨.

tokensize 가 0인 경우 오류 메세지를 반환하고 run 함수가 true를 반환하여 새로운 명령어를 입력받음.

 

 

//run [2]

pid = fork();

	if (pid == 0)
	{
		if(redirection(tokens)==0){
			execvp(tokens[0], tokens);
			printf(" No such file\n ");
			exit(1);
		}
		else {
			printf("Redirection Error\n");
			exit(1);
		}
	}
	else if (pid < 0)
	{
		printf("Fork error\n");
		exit(1);
	}
	else if(back == false)
		waitpid(pid, &status, WUNTRACED);

	return true; 
}

자식 프로세스를 생성함. (fork)

자식 프로세스일 경우 (pid = 0) 리다이렉션 여부를 확인하고 '>' 가 있는 경우 리다이렉션 진행.

리다이렉션이 실패할 경우 오류메세지 출력, 

성공하거나 리다이렉션이 필요하지 않을 경우 exec 시스템 콜 실행 후 명령어 실행 

pid가 음수인 경우는 fork 실패를 의미, fork error 알림

pid가 0 이상일 경우 부모 프로세스를 의미, 이때 back이 false라면 부모 프로세스 대기, 

true일 경우에는 대기하지 않고 자식 프로세스와 동시 실행 

 

//tokenization

int tokenization(char* line, char* tokens[])
{
	int tokensize=0;
	char* token = strtok(line, DELIMS);
	
	while (token != NULL)
	{
		tokens[tokensize++] = token;
		token = strtok(NULL, DELIMS);
	}
	tokens[tokensize] = NULL;
	return tokensize;
}

DELIMS 기준으로 토큰화 

 

//redirection

int redirection(char* tokens[])
{
	int i;
	int fd;

	for (i = 0; tokens[i] != NULL; i++) {
		if (!strcmp(tokens[i], ">"))
		{
			break;
		}
	}

	if (tokens[i]) {
		if (!tokens[i + 1]) {
			return -1;
		}
		else {
			if ((fd = open(tokens[i + 1], O_RDWR | O_CREAT | S_IROTH, 0644)) == -1) {
				perror(tokens[i + 1]);
				return -1;
			}
		}
		dup2(fd, STDOUT_FILENO);
		close(fd);
		tokens[i] = NULL;
		tokens[i + 1] = NULL;

		for (i = i; tokens[i] != NULL; i++) {
			tokens[i] = tokens[i + 2];
		}
		tokens[i] = NULL;
	}
	return 0;
}

'>'가 있는지 검사한 후 '>' 다음 토큰을 file descriptor로 이동해줌. 

그리고 dup2 함수를 이용하여 STDOUT으로 갈 출력을 fd로 변환해줌. 

예를 들어 cat file1.c > file2.c일 경우에는 

file1.c의 내용을 STDOUT해 터미널에서 보여주는 것이 아니라 file2.c로 옮기는 것을 의미함.

dup2 후에는 >(tokens[i]) 와 file2.c(tokens[i+1]) 를 NULL로 바꿔줌.

 

 

//전체 코드 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

#define DELIMS " \t\r\n"
int status;

int tokenization(char* line, char* tokens[])
{
	int tokensize=0;
	char* token = strtok(line, DELIMS);
	
	while (token != NULL)
	{
		tokens[tokensize++] = token;
		token = strtok(NULL, DELIMS);
	}
	tokens[tokensize] = NULL;
	return tokensize;
}
int redirection(char* tokens[])
{
	int i;
	int fd;

	for (i = 0; tokens[i] != NULL; i++) {
		if (!strcmp(tokens[i], ">"))
		{
			break;
		}
	}

	if (tokens[i]) {
		if (!tokens[i + 1]) {
			return -1;
		}
		else {
			if ((fd = open(tokens[i + 1], O_RDWR | O_CREAT | S_IROTH, 0644)) == -1) {
				perror(tokens[i + 1]);
				return -1;
			}
		}
		dup2(fd, STDOUT_FILENO);
		close(fd);
		tokens[i] = NULL;
		tokens[i + 1] = NULL;

		for (i = i; tokens[i] != NULL; i++) {
			tokens[i] = tokens[i + 2];
		}
		tokens[i] = NULL;
	}
	return 0;
}
bool run(char* line)
{
	char* tokens[300];
	int tokensize ;
	int i;
	pid_t pid = -1;
	bool back; 

        for (i = 0; i < strlen(line); i++) {
		if (line[i] == '&') {
			back = true;
			line[i] = '\0';
			break;
		}
	}

	tokensize = tokenization(line, tokens);

	if (tokensize == 0)
	{
		printf("Tokenization error\n");
		return true;
	}

	if (strcmp(tokens[0],"exit")==0)
		return false;

	pid = fork();

	if (pid == 0)
	{
		if(redirection(tokens)==0){
			execvp(tokens[0], tokens);
			printf(" No such file\n ");
			exit(1);
		}
		else {
			printf("Redirection Error\n");
			exit(1);
		}
	}
	else if (pid < 0)
	{
		printf("Fork error\n");
		exit(1);
	}
	else if(back == false)
		waitpid(pid, &status, WUNTRACED);

	return true; 
}

int main(void)
{
	char line[1024];

	while (1)
	{
		printf("%s $ ", get_current_dir_name());
		fgets(line, sizeof(line) - 1, stdin);
		if (run(line) == false)
			break;
	}

	return 0;
}

 

 

Comments