minishell 프로젝트는 셸 스크립트 중 하나인 bash를 구현하는 프로젝트입니다.
그러나 이름에서 유추할 수 있듯 세부적인 동작까진 무리이고 manual을 참고하여 작성합니다.
명령어 처리기 (ex. 터미널)
쉘은 운영 체제 상에서 다양한 운영 체제 기능과 서비스를 구현하는 인터페이스를 제공하는 프로그램입니다. 즉, 사람이 컴퓨터에게 어떤 일을 시킬 때 쓰는 프로그램 정도로 이해하면 될 것 같습니다.
종류로는 bash, zbash, ksh, csh 등이 있습니다.
GNU 프로젝트의 일부로, Bourne shell을 대체하기 위해 만들어진 Unix 계열 운영체제용 POSIX shell.
거의 모든 리눅스 배포판에 기본 로그인 shell로 깔려있다. 그래서 보통 shell scripting이라고 하면 Bash를 의미합니다.
스크립팅 언어답게 인터프리터로 돌아가며, C로 개발되었습니다.
Bash Reference Manual
The Open Group Base Specifications Issue 7, 2018 edition
두 사이트의 매뉴얼을 참고하여 프로그램을 작성하였습니다.
minishell의 동작을 크게 4가지로 파트를 나눌 수 있습니다.
parsing / exec / built_in / signal
parsing : 문자열로 받은 입력을 가장 작은 단위인 토큰 단위로 먼저 나누고 원하는 형태에 맞게 가공해는 작업입니다.
'(싱글 쿼트)
, "(더블 쿼트)
, 공백
, '$'(환경 변수)
부터 해서, '|'(파이프라인)
, '<, >'(리다이렉션)
, '<<, >>' (heredoc)
등
exec : 실행 부분으로 parsing 단계에서 가공된 문자들을 해당 파트에서 동작시킵니다. 이때 맨 처음 들어온 명령어와 파이프의 유무에 따른 fork로 프로세스 생성 및 병렬처리 등을 진행합니다
built_in : PATH 디렉토리 안에 해당하는 명령어가 없을 경우 직접 구현하는 부분입니다. 예를 들어 export, unset, exit과 같은 명령어들은 해당 디렉토리에 없기 때문에 exec에서 실행이 불가능합니다.
signal : ctrl + c, ctrl + d, ctrl + \ 과 같은 시그널 처리들을 구현합니다. 특히나 erron 리턴 값을 유의하며 코드를 작성해야 합니다.
큰 동작은 위 Mermaid 플로우차트와 같습니다.
그 외 필요한 세팅
rm -rf $HOME/.brew && git clone --depth=1 https://github.com/Homebrew/brew $HOME/.brew && export PATH=$HOME/.brew/bin:$PATH && brew update && echo "export PATH=$HOME/.brew/bin:$PATH" >> ~/.zshrc
//설치
brew install readline
//업데이트
brew link --force readline
//설치
brew install bash
//업데이트
brew link --force bash
동기적 처리 : 순서대로 진행하는 것, 지금 진행하는 것이 종료되고 다음 작업으로 넘어감
비동기적 처리 : 순서대로가 아니라 한 번에 여러 개가 진행되는 것
프로세스 레벨에서 처리되고, pcb 블록에서 특정 변수에 비트 마스킹이 돼서 저장되는데 이는 프로세스 스케줄링을 할 때 확인을 해서 바로 실행된다.
예) kill 함수를 통해 특정 프로세스에게 시그널을 보낸다. Ctrl + c를 눌러서 프로세스를 끝내기
Application Programming Interface → 두 소프트웨어 구성 요소가 서로 통신할 수 있게 하는 메커니즘
예) 기상청 소프트웨어 시스템에는 기상 데이터가 있다. 휴대폰의 날씨 앱은 API를 통해 이 시스템과 대화하여
휴대폰에 매일 최신 날씨 정보를 표시한다.
POSIX가 지정하는 표준 인터페이스
값을 설정함으로써 인터페이스 제어
5가지 모드(입력, 출력, 제어, 로컬, 특수 제어문자)로 분류 가능
c_lflag의 속성
비트 마스킹으로 플래그를 지정한다.
ISIG : signal(ctrl + c(sigint), ctrl + z(sigstp)….)를 받아들인다.
ICANON : 정규모드로 입력을 받는다.
ECHO : 반향을 설정한다.
sa_flags는 아래와 같은 값이 사용되며 OR 연산자로 여러 개를 지정하여 사용할 수 있다.
옵션 | 의미 |
---|---|
SA_NOCLDSTOP | signum이 SIGCHLD일 경우, 자식 프로세스가 있을 경우에만 SIGCHLD를 보낸다. 자식 프로세스가 멈췄을 경우에는 보내지 않는다. |
SA_ONESHOT SA_RESETHAND | 시그널을 받으면 설정된 sa_handler를 실행하고, 시스템 기본 설정인 SIG_DFL로 설정된다. |
SA_RESTART | 시그널 처리에 의해 방해 받은 시스템 호출은 시그널 처리가 끝나면 재시작한다. |
SA_SIGINFO | 이 옵션이 사용되면 sa_handler 대신에 sa_sigaction이 동작되며, sa_handler 보다 더 다양한 인수를 받을 수 있다. sa_sigaction이 받는 인수에는 시그널 번호, 시그널이 만들어진 이유, 시그널을 받는 프로세스의 정보입니다. |
struct sigaction
{
void (*sa_handler)(int); //시그널을 처리하기 위한 핸들러
//SIG_DFS, SIG_IGN 또는 핸들러 함수
void (*sa_sigaction)(int, siginfo_t *, void *); //밑의 sa_flags가 SA_SIGINFO일 때
//sa_handler 대신에 동작하는 핸들러
sigset_t sa_mask; //시그널을 처리하는 동안 블록화할 시그널 집합의 마스크
int sa_flags; //아래 설명 참고
void (*sa_restorer)(void); //사용해서는 안 된다.
}
struct sigaction
{
union __sigaction_u __sigaction_u; /* signal handler */
sigset_t sa_mask; /* signal mask to apply */
int sa_flags; /* see signal options below */
}
union __sigaction_u
{
void (*__sa_handler)(int);
void (*__sa_sigaction)(int, siginfo_t *, void *);
};