일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 부동소수점
- 스플릿키보드
- 스타트업
- 동료학습
- Cloud Spanner
- 엣지컴퓨팅
- adminbro
- raycasting
- 텍스트북
- GraphQL
- 레이캐스팅
- 자료구조
- psql extension
- 도커
- 파이썬
- 어셈블리
- 42서울
- 어셈블리어
- c++
- SFINAE
- uuid-ossp
- 쿠버네티스
- 정렬
- 42seoul
- 창업
- enable_if
- 이노베이션아카데미
- mistel키보드
- 프라이빗클라우드
- schema first
- Today
- Total
written by yechoi
[리눅스] 쉘 구현에 필요한 C 함수(feat. 42 미니쉘) 본문
C func for Minishell
시그널
sig_t signal(int sig, sig_t func);
sig 는 시그널 번호, func 는 해당 시그널을 처리할 핸들러.
- SIGINT: ctrl + c
- SIGQUIT: ctrl + \ (관련 링크)
❓왜 signal이 필요한가
ctrl + c를 예시로 들면, 이를 누르면 터미널이 꺼진다. 직접 만든 쉘을 실행하고 ctrl + c를 눌렀을 때 기대하는 건, 우리가 만든 쉘 프로그램만 꺼지는 것. 터미널 전체가 꺼지길 바라지 않는다. 그래서 ctrl + c(SIGINT)가 들어왔을 때 취해야 할 액션을 다른 함수로 바꿔줄 필요가 있다. 이러한 역할을 하는 게 signal 함수.
🔗 http://blog.naver.com/PostView.nhn?blogId=bitnang&logNo=70172674474 (시그널 종류)
파일 정보 읽기
int stat(const char *path, struct stat *buf)
첫번째 인자로 절대경로를 넘겨주고, 두번째 인자로 stat 구조체 주소를 넘겨준다. 성공하면 0, 실패하면 -1을 반환한다.
❓왜 stat이 필요할까
함수를 거치면 stat 구조체에 파일에 관한 정보가 들어오기도 하지만, 쉘을 만들 땐 성공 실패 여부가 더 중요한 정보로 쓰였다. 원하는 경로에 파일이 있는지 없는지를 확인하는 데 썼다는 것. 해당 경로에 파일이 있으면, 그 경로를 execve의 path에 인자로 넘겨줬다.
🔗 https://blog.naver.com/bitnang/70172673896
디렉토리 관련
char *getcwd(char *buf, size_t size)
현재 작업중인 디렉토리의 절대 경로를 복사해온다. size는 buf 배열의 크기이며, 반환값은 buf를 가리키는 포인터.
❗ pwd 구현
int chdir(const char *path)
현재 작업중인 디렉토리를 path로 변경. 성공하면 0이 반환되며, 에러가 발행하면 -1이 반환되고 errno가 설정된다.
❗cd 구현
프로세스
pid_t fork(void)
자식 프로세스일 경우 0을 반환하고, 부모 프로세스일 경우 자식 pid(프로세스 ID)를 반환함. -1이면 에러가 발생한 것.
❓ 왜 fork가 필요한가
exec 호출에 꼭 필요함. exec으로 호출된 프로그램이 현재 메모리에 올라와 있는 프로그램을 덮어서 로딩. 원 프로그램이 사라져버리는 것. 때문에 별도의 메모리 공간을 할당하고, 그 공간에서 exec을 실행해 원래 process는 보존해야 함. fork가 독립된 메모리 공간을 할당함.
🔗(추천) https://channelofchaos.tistory.com/55
pid_t wait(int *staloc)
부모 프로세스는 자식 프로세스가 끝날 때를 기다려 줘야 함. wait의 인자는 상태. 여러개 자식 프로세스 중에 어느 하나라도 종료하면 종료한 자식 프로세스 ID를 반환한다.
❓ 왜 wait가 필요할까
앞선 fork 함수에 따르면 process는 parent와 child로 나뉘게 된다. 원 프로그램이 실행되는 parent process는 child process가 끝날 때까지 기다려줘야 하는데, 이때 필요한 함수가 wait. 인자로 child process의 id인 0을 넣어 wait(0)하면 child process가 끝날 때까지 기다려줌.
pid_t waitpid(pit_t pit, int *staloc, int options)
해당 pid 값을 갖는 자식 프로세스를 기다림. 0일 때는 자신과 같은 그룹에 있는 프로세스의 자식 프로세스를 기다림. -1일 때는 모든 하위 프로세스를 기다림. -1보다 작은 값일 때는 절대값과 같은 자식 프로세스의 종료를 기다림
🔗 ehpub.co.kr/리눅스-시스템-프로그래밍-7-8-프로세스-종료-대기-및/
$?
쉘에서는 이전에 실행한 명령이 성공을 하였는지 실패를 하였는지를 ? 변수값에 저장을 한다. wait 류 함수에 staloc에 관련 정보가 담기는데, 이를 /256으로 나누면 $? 값.
int execve(const char *path, char *const argv[], char *const envp[])
첫번째 인자는 새 프로세스 파일의 경로. 두번째 인자는 프로그램 명. 더블 포인터인 이유는 int main(int argc, char **argv)
에서 argv[0]이 프로그램 이름이었던 것과 같은 원리.
🔗 (설명) https://www.it-note.kr/157
❓왜 execve가 필요할까
우리가 명령어로 생각하는 'ls', 'echo'등은 실은 $PATH 경로 안에 있는 실행파일이다. 즉 프로그램을 실행해 명령어를 사용한다는 얘기.
🔗 https://github.com/codingeverybody/codingyahac/issues/178
파일디스크립터 복사하기
int dup(int fildes)
파일디스크립터를 복제해 반환. fd2 = dup(fd1) 이면 fd2에 글을 쓰면 fd1과 같은 파일에 쓰임. 다만 서로 다른 파일디스크립터이기 때문에, fd1을 닫아도 fd2는 열려있음.
int dup2(int fildes, int fildes2)
두번째 인자를 첫번째 인자로 바꿔버림.
❓ 왜 dup2가 필요할까
redirection을 구현할 때 사용하는 함수. output redirection에서 앞선 명령어를 실행한 결과는 stdout으로 나오는데, 이를 > 뒤에 있는 파일에 덮어씌우기 위함. dup2(fd, STDOUT_FILENO);
input redirection도 같은 원리. dup2(fd, STDIN_FILENO);
🔗https://sosal.kr/186 (dup과 dup2에 대한 설명)
http://www.xevious7.com/linux/lpg_6_2_2.html (dup 활용 pipe 구현)
파이프
int pipe(int pipefd[2])
서로 독립된 프로세스가 데이터를 주고받을 수 있도록 하는 함수. 풀어서 말하자면 파이프로 만든 두개의 fd로 부모, 자식 프로세스가 서로 통신할 수 있다. fd[0]은 read, fd[1]은 write. 1에 쓰고 0에서 읽음.
int main()
{
int fd[2];
pid_t child;
char buff[BUFFERSIZE];
child = fork();
if (child == 0) // 자식프로세스가 출력하고
{
close(fd[0]); // 출력만 할 것이므로 입력을 닫음
write(fd[1], string, strlen(string));
exit(0);
}
else // 부모프로세스가 자식프로세스의 출력을 입력 받을 때
{
close(fd[1]); // 입력만 받으므로 출력은 닫아둠
read(fd[0], buff, BUFFERSIZE - 1);
}
🔗 https://sosal.kr/83 (pipe() 설명)
http://jullio.pe.kr/cs/lpg/lpg_6_2_2.html (pipe()를 통한 pipe 기능 구현)
https://12bme.tistory.com/226 (pipe()를 통한 pipe 기능 구현2)
종료하기
void exit(int status)
인자인 status는 종료하고 부모 프로세스에게 전달할 값이다
성공과 실패는 명령의 종료 상태값으로 알 수 있다. 보통 0은 성공을 나타내며, 0이 아닌 값은 실패를 나타낸다.
종료하기 전에 모든 열려진 파일을 자동으로 닫는다.