Search
Duplicate

minishell 이것저것

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
42cursus
42seoul
minishell
Scrap
태그
9 more properties

shell 구현하면서 알게된것과 여러가지 체크 포인트

두명이서 순수하게 100시간정도 썼다. 소요기간 약 4주 여기 내용가지고는 부족하니 직접 더 필요한 부분은 구글에 man 으로 검색해서 직접 읽어볼 것

환경변수

main의 경우 인자를 두개를 형으로 할 수 있는 줄알았는데 환경변수를 추가로 받아오는 방법이 있었다. int main(argc argv envp)를 통해서 를 받아 올 수 있다.

리다이렉션 fd 처리

쉘은 어떤 파일로부터 읽고, 어떤 파일로 출력을 할 수 있게하는 리다이렉션을 지원한다. 이걸 어떻게 처리하냐면 stdin, stdout의 fd 0, 1을 직접 파일에다가 연결하는 식으로 한다. dup2로 구현할 수 있다.

프로세스

일반적으로 프로세스를 실행하기 위해서는 기본적으로
1.
fork를 통해 현재 프로세스를 복제해서 새로만듬
2.
execve를 통해서 외부 프로그램 실행
fork 없이 execve를 하면 현재 프로세스에서 그대로 외부 프로그램을 실행하기에 다시 되돌아올 수 없다. fork를 할때 파일 스트림은 유지가 부모로부터 상속받는다. 자세한것은
재미있는 점은 OS에서도 같은 방식으로 프로세스를 새로 실행시킨다.

execve

비슷한 함수가 많은데 c,v,e에 대해서는 다음을 참고
우리가 사용하던 많은 명령어들은 명령어 그 자체가 하나의 실행 프로그램 이었다.
ls -al 은 알고보면 ./a.out arg1 과 같다.
main에서 받아온 환경변수 PATH에(여기에 추가가되었든 없어졌든):로 경로에 실행되는지 여부를 판단하면서 실행한다.
signal 세팅, 메모리 할당등 대부분을 것들은 싹 초기화한다. 자세한것은

여담 : copy on write

fork해서 malloc을 한것을 읽을 때 이게 그대로 남아있는가를 찾아보다 알게 된 정보 운영체제가 fork를 할때 모든 부분에 새로 메모리 할당을 하는 것이아닌 해당 메모리를 write하려고할때 할당을 하는 방식으로 최적화를 하는 방법이다.
C++의 얕은복사 깊은 복사 개념을 생각하면 이해하는데 도움이 될 것이다.

pipe

파이프에서는 이를 병렬로 실행을 한다. pipe 생성후 fork 한번으로 자식에서 write 부모에서 read를 하는 것이아닌 fork 두번으로 한쪽 자식은 write 다른쪽 자식은 read를 하는 식으로 해야한다. exit 88 | echo $? => 88이 출력되면 안됨. echo a | exit 시에 쉘이 종료되면 안됨 => 오른쪽 명령어를 처리할때 fork 된상태에서 처리
bash에서의 실제 파이프 처리. 프로세스로 직접 검색한경우 cat | cat | cat을 실행시에 입력대기를 받을때, 프로세스가 bash, cat 3개 이렇게 구성이 되어있음
내 경우엔 fork를 좀 많이해서 처리함…

세부구현사항

자식 프로세스의 리턴 값 처리

waitpid나 기타 wait함수로 받는 status값과 exit값은 다르다. status값에서 exit 값을 추가로 뽑아내는 방법은 status가 정상인지 WIFEXITED(status)로 체크를 한다음에 WEXITSTATUS(status)를 통해서 status에서 실제 exit값을 뽑아 올 수 있다.
참고로 42에서는 매크로 함수를 못쓰기때문에 원형을 보고 그대로 가져와서 사용

exit 구현

argument도 넣으면서 처리해볼것.
exit 범위는 0 ~ 255
exit값 인자 자체는 long long 범위까지 처리가 가능함 long long을 넘어갈시 numeric error을 출력하면서 255로 exit
255가 넘어가는 숫자는 %256함
숫자 대신 다른게 있을시 numeric error을 출력하면서 255로 exit
인자가 두개가 들어올시 too many argument error, exit은 안함

무한히 출력하는 파이프

1 cat /dev/random | head -n 100
cat /dev/random의 경우 일반적으로 무한히 출력하는데 head -n 100은 딱 100줄만 읽고 출력하고 끝나는게 정상적인 작동이다.

파이프 병렬처리

exit 88 | echo $? => 출력 0 echo a | exit 88 => 여기서 쉘 종료되면안됨 echo $? => 88 cat | cat | cat =>입력대기 받을 때, bash에서 bash, cat 3개 이렇게 화면에 떠있어야한다
C
복사

명령어 옵션처리

사실 실제 명령어 처리하는 방식이 다르겠지만 내가 유추하고있는거..
인자가 -이 아닐때까지 계속 args를 탐색한다 -이니 여기서 인자 전체를 탐색 명령어 마다 정해진 옵션을 switch로 돌리면서 case에 각각맞는 option을 세팅 만약 default에 걸리면 이거는 인자가 아니었으니 option처리하던걸 던지고 에러처리
echo -nnnnnnnnnnnnnnnnnnnnnn -n -n -n -n a a% => 유효한 옵션 echo -nnna a -nnna a => 유효하지않은 옵션
C
복사
heredoc에서 시그널 처리는 따로 해주었는가?
끝에 fd가 close되지 않고 열려있는게 있는지도 체크해볼것
세부사항 : env export 정렬

중첩 shell 처리

부모와 자식의 signal 처리
readline 사용 : static으로 폴더 자체를 뗴와서 사용…했는데 잘 안동작한다 wsl2에서 아직 컴파일 안되다.
자식 부모기능 구분

설계

따옴표 처리 => 리스트
추상트리 : printTree 함수를 만들어서 사용
=> 사실 한번에 처리할 수 있는 부분이긴한데 각각 기능구현이 너무 복잡해서 이를 분리하여 처리
리다이렉션의 재귀적처리
stdout/in 복원 : 실제 파일을 fd 0, 1에 직접 dup하는 경우가 많아서 각 처리 부분마다 원래 stdout/in을 보장해 줄 수있는 back up 저장소를 따로 만들었다.
exit code 저장 : 자식이나 builtin 등 exit code를 따로 저장할 저장소가 필요해 따로 마련.
pipe 왼쪽 오른쪽 자식을 fork 하고 다음 node를 가리켜 재귀적이게 execute을 처리함.
heredoc 꼼수 : 원 bash에서는 fork를 따로 하지않고 처리하나 부모 프로세스에서 heredoc을 입력받는 상태에서 시그널 처리를 어떻게 해야할지 도저히 몰라 fork를 받고 프로세스자체를 강종시키는 식으로 해결함.

구현 기법

주요 구현 기법이나, 새롭게 알게 된 것을 기술.
여기 코딩스탠다드를 많이 사용했다.
_or_null : null을 반환할 수도 있는 함수/변수 근데 이거 정작 내가 or_null 붙어있는데도 null처리 안해서 에러난적이 있음..
_recursive : 중간에 f1 -> f2 -> f1 재귀적으로 처리되는 부분이있어서 붙임
pointer는 접두사 p

try 접두사

함수가 정상적으로 수행되었는지 bool으로 반환 그리고 실제 output 값은 포인터 인자로 받아서 처리한다
ex
1 bool try_atoi(const char *str, int *out_num)

allocator는 {}로 표시 하고 중괄호 끝날때 무조건 free

ex
p_str = (~)malloc(); { } free(str)`
C
복사

파일 구조 : Singlton

사실 singlton이라고 생각하고 쓴게아니었는데, 끝내고 얘기해보니 그냥 싱글턴이었다.
파일단위로 static 전역변수를 두고 이걸 관리하는 함수 api들을 헤더에 두고, 사용자는 그냥 가져다가 자유롭게 쓰는 방식이었다.
std stream 관리, env 관리, exit code 등 많은걸 이걸로 처리해버렸다.
42에서 정적변수를 못쓰게해서.. 꼼수로서 함수내부에 스태틱 변수를 만들고 이를 반환하는 getter static함수를 만들어서 그걸로 받아왔다..

+ jaham

export a=”dfjkksjadfb kjsadfnaksjdf “
execute 중 : signal -> exit code setting 128 + signum
prompt 인터럽트 => exit code 1
echo $ => $
exit은 파이프가 사용될때 exit 출력을 하지않는다. <- 그럼 어떻게 자식프로세스인지를 확인하냐? => isatty
그가 잠깐 짠 실제 bash작동을 생각한 pipe처리 의사코드
pipe { int in = 0; pid arr[100]; while (i < len + 1) { int pipeline[2]; pipe(pipeline); arr[i ] = fork(); if (!pid) { dup2(in, 0); dup2(pipeline[1], 1); builtin + execve(); exit(errno + 125); } close(); if (in != 0) close(in); in = pipeline[0]; i++; } wait_all(arr); }
C
복사