Search
Duplicate
🦀

minishell function(part 1)

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

fork

function

fork함수를 사용하면 새로운 프로세스를 하나 호출한다.
이때, 호출하는 자식 프로세스는 부모 프로세스의 메모리를 그대로 복사하여 가진다.
그리고 fork함수 호출 이후 각자의 메모리를 사용하여 실행된다.
#include <unistd.h> //header file pid_t fork(void);
C
복사

return value

성공 시 : 부모 프로세스에서는 자식 프로세스의 PID값, 자식 프로세스에서는 0
실패 시 : -1

example

#include <stdio.h> #include <unistd.h> int main(void) { int x = 0; fork(); x = 1; printf("PID : %d, x : %d\n", getpid(), x); return (0); }
C
복사
$ ./a.out PID : 8270, x : 1 PID : 8271, x : 1
Shell
복사
결국 x 에 값을 대입할 때, fork() 명령 이전에 대입하나 이후에 대입하나 상관이 없다! fork를 하면 자식 프로세스가 부모의 메모리를 그대로 복사해서 가지기 때문. 이후 같은 작업을 각 프로세스가 개별적으로 실행한다.
또한 자식 프로세스는 fork()함수를 실행하지 않기 때문에 이때 getpid()의 return value는 0이다. 따라서 getpid()의 return value를 기준으로 자식인지 부모인지 알 수 있다.
#include <stdio.h> #include <unistd.h> int main(void) { pid_t pid; pid = fork(); if(pid > 0) // 부모 코드 printf("부모 프로세스 입니다."); else // 자식 코드 printf("자식 프로세스 입니다."); return (0); }
C
복사

wait

function

1.
자식 프로세스가 동작 중이면 상태를 얻어올 때까지 대기
2.
wait() 함수 호출자가 시그널을 받을 때까지 대기
3.
자식 프로세스가 종료된 상태라면 즉시 호출이 반환되어 상태를 얻음, 이 때 wait() 함수는 자식 프로세스의 프로세스 ID를 반환
4.
자식 프로세스가 없다면 호출이 즉시 반환되며, 에러값을 반환
#include <sys/wait.h> pid_t wait(int *statloc);
C
복사
wait()의 인자 status 를 통하여 자식 프로세스의 상태를 받아올 수 있는데,
자식프로세스의 상태값은 자식프로세스의 종료값 * 256(FF) 이다.

return value

성공 시 : PID
실패 시 : -1

example

#include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t pidi; int status,i; pid = fork(); if (pid > 0) { pid_t waitpid; printf("부모 PID : %d\n", getpid()); waitpid = wait(&status); // 자식 프로세스의 상태를 받아올 때까지 wait! if (waitpid == -1) printf("error\n"); else { if (WIFEXITED(status)) printf("자식 프로세스 정상 종료\n"); else if (WIFSIGNALED(status)) printf("자식 프로세스 비정상 종료: %d\n", WTERMSIG(status)); } } else if(pid == 0){ // 자식 프로세스 sleep(6); printf("자식 PID : %ld \n",(long)getpid()); } else { // fork 실패 perror("fork Fail! \n"); return (-1); } return (0); }
C
복사
$ ./a.out 부모 PID : 10695 자식 PID : 10696 자식 프로세스 정상 종료
Shell
복사
wait 명령을 사용할 때 위치에 주의하여야 한다! 만약 위 코드에서 부모 pid를 출력하는 부분이 wait명령어 아래로 내려간다면, 부모 pid출력이 자식프로세스가 종료될때까지 기다렸다가 출력하게 된다.
wait()관련 함수에서 자식 프로세스의 종료 상태를 확인하기 위해서 인수로 전달했던 변수 status 값은 아래와 같이 구성되어 있다! 기존에는 255를 기준으로 비교 했었지만 아래 매크로를 활용해서 간단하게 확인할 수 있다.
Search
이름
태그
자식 프로세스가 시그널에 의해 종료되었다면 TRUE
자식 프로세스가 중단되었다면 TRUE
자식 프로세스가 정상 종료되었을 때 반환한 값
자식 프로세스를 종료하도록한 신호의 번호 반환 (WIFSGNALED가 non
자식을 정지하도록 야기한 신호의 숫자를 반환
COUNT6
WIF로 시작하는 macro는 상위 바이트의 상태를 판단하는 함수이며 나머지 macro함수는 상세 값을 읽을 수 있습니다.
이제 자식 프로세스를 오랜 시간동안 실행시키고 터미널에서 kill명령으로 자식 프로세스를 종료시켜보자!
$ kill -9 13439 부모 PID : 13438, 자식 PID : 13439 자식 프로세스 비정상 종료: 9
Shell
복사

waitpid

function

waitpid()는 wait()함수와 동일하게 자식 프로세스를 기다릴 때 사용한다. 하지만 자식 프로세스가 종료될 때까지 차단되는 것을 원하지 않을 경우, 옵션을 사용하여 차단을 방지할 수 있다.
#include <sys/wait.h> //header file pid_t waitpid(pid_t pid, int *status, int options);
C
복사
pid : wait()할 자식 프로세스의 유형
pid < -1 : 그룹ID가 절대값과 같은 자식 프로세스를 기다린다
pid == -1 : 아무 자식 프로세스 ID라도 기다린다
pid == 0 : 자신과 같은 프로세스 그룹 ID를 가진 자식 프로세스를 기다린다.
pid > 0 : 인자로 넘어온 pid를 가진 자식 프로세스만 기다린다.
status : wait()함수와 동일
options : 0 혹은 상수의 조합으로 설정된다
0 : 결과를 return 할 떄까지 block한다
WNOHANG : 현재 종료된 자식 프로세스가 없으면 block하지 않고 바로 0을 반환한다
WUNTRACED : 자식 프로세스가 STOP하면 반환한다
WCONTINUED : STOP되었던 자식 프로세스가 재 실행되면 반환한다

return value

성공 시 : PID → 상태가 변경된 자식 프로세스 ID (status가 상태값 저장)
성공 시 : 0 → option이 WNUHANG인 경우 종료된 자식 프로세스가 없으면 0을 return
실패 시 : -1 → wait 중 오류가 발생, 오류 내용은 errno 전역변수에 설정

example

#include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t pid; int status,i; pid = fork(); if (pid > 0) { pid_t waitpid; printf("부모 PID : %d, 자식 PID : %d\n", getpid(), pid); } else if(pid == 0){ // 자식 프로세스 for (int i = 0 ; i < 3 ; ++i) sleep(1); printf("자식 PID : %ld \n",(long)getpid()); printf("자식 종료\n"); } else { // fork 실패 perror("fork Fail! \n"); return -1; } pid = waitpid(pid, &status, 0); if (pid == -1 ) { printf("error\n"); } else { if (WIFEXITED(status)){ printf("프로그램에서 exited(%d) 또는 main에서 return ;하여 종료되었습니다.\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("siganl %d번이 발생하여 종료되었습니다.\n", WTERMSIG(status)); } else if (WIFSTOPPED(status)) { printf("signal %d번으로 인하여 stop되었습니다. \n", WSTOPSIG(status)); } else if (WIFCONTINUED(status)) { printf("stop된 프로세스를 진행합니다\n"); } } return 0; }
C
복사

wait3, wait4

function

wait3, wait4 함수는 사용자 시간 정보를 확인할 수 있다.
#include <sys/types.h> //header file #include <sys/wait.h> pid_t wait3(int *staloc, int options, struct rusage *rus); pid_t wait4(pid_t pid, int *staloc, int options, struct rusage *rus);
C
복사

parameters

staloc, options, pid : 위와 동일
rusage : 자식 프로세스의 리소스 사용량에 대한 정보가 담긴다. (struct rusage 형태로 담긴다)

return value

성공 시 : PID, 만약 함수가 WNOHANG 옵션으로 실행되었고, 자식 프로세스가 아직 종료되지 않았을 때, 0
실패 시 : -1

example

#include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/resource.h> void ViewRUsage(pid_t pid, struct rusage *pru); int main() { pid_t cpid = fork(); if(cpid == -1) { perror("error fork"); return 0; } if(cpid>0) { printf("<parent> fork child pid:%u\n",cpid); int rvalue = 0; struct rusage ru; pid_t wpid = wait3(&rvalue,0, &ru); ViewRUsage(wpid, &ru); } else { printf("<child> pid:%d \n",getpid()); int i=0,j=0; for(i=0;i<100000;i++) { fprintf(stderr,"."); } } return 0; } void ViewRUsage(pid_t pid, struct rusage *pru) { printf("\n=== pid rusage info ===\n"); struct timeval *tv = &(pru->ru_utime); printf("user CPU time used [%ld]sec [%d]usec\n", tv->tv_sec,tv->tv_usec ); tv = &(pru->ru_stime); printf("system CPU time used [%ld]sec [%d]usec\n", tv->tv_sec,tv->tv_usec ); }
C
복사

signal

프로세스가 시그널을 받게 되면
1. 시그널에 해당되는 기본 동작을 하거나
2. 그 시그널을 무시하거나
3. 사용자가 정의한 함수를 통해 동작 방식을 바꿀 수 있다.

function

위와 같은 시그널을 제어할 수 있는 함수. signal번호를 받아와 해당 signal에서의 행동을 지정할 수 있다.
#include <signal.h> //header file void (*signal(int signum, void (*handler)(int)))(int);
C
복사

parameters

signum : 시그널 번호
(*handler)(int) : 시그널을 처리할 핸들러

return value

성공 시 : void *()(int); 이전에 설정된 시그널 핸들러
실패 시 : null(?)
아래는 MacOS에 정의되어 있는 Signal들이다.
SIGKILL, SIGSTOP 등은 제어할 수 없다.

example

#include <stdio.h> #include <signal.h> #include <unistd.h> void (*old_fun)(int); void sigint_handler(int signo) { printf("signo : %d", signo); printf("Ctrl-C 키를 눌루셨죠!!\n"); printf("또 누르시면 종료됩니다.\n"); signal(SIGINT, old_fun); //signal( SIGINT, SIG_DFL); } int main( void) { old_fun = signal(SIGINT, sigint_handler); // printf("asdf : %d", SIGTSTP); // printf("zxcv : %d", SIGCHLD); while (1) { printf("forum.falinux.com\n"); sleep(1); } }
C
복사

kill

function

프로세스에 시그널을 전송해주는 함수. 시그널 SIGKILL을 보내면 쉘의 kill과 같은 역할을 한다.
#include <signal.h> //header file int kill(pid_t pid, int signo)
C
복사

parameters

pid, signo : 위와 동일

return value

성공 시 : 0
실패 시 : -1

getcwd

function

현재 작업 중인 디렉토리를 가져온다
#include <unistd.h> //header file char *getcwd(char *buf, size_t size);
C
복사

parameters

buf : 현재 작업 디렉토리를 저장할 buffer. 만약 buffer가 null이면 malloc을 통해서 할당된 메모리를 사용한다. 따라서 이 경우 무조건 free를 해주어야 한다.
size : buffer에 할당된 메모리의 크기

return value

성공 시 : 포인터
실패 시 : null (오류 내용은 errno에 저장된다)

example

#include <unistd.h> #include <stdio.h> #include <limits.h> int main() { char cwd[PATH_MAX]; if (getcwd(cwd, sizeof(cwd)) != NULL) { printf("Current working dir: %s\n", cwd); } else { perror("getcwd() error"); return 1; } return 0; }
C
복사

chdir

function

작업 디렉토리 폴더 변경하기 (cd in linux)
// man 2 chdir #include <unistd.h> //header file int chdir(const char *dirname);
C
복사

return value

성공 시 : 0
실패 시 : -1

example

#include <stdio.h> #include <limits.h> #include <unistd.h> // chdir header int main(void) { char buffer[PATH_MAX] = { 0, }; char changeDir[PATH_MAX] = { "/Users/42seoul/minishell/" }; int result = chdir(changeDir); if(result == 0) printf("Success\n"); else perror("Fail...\n"); return 0; }
C
복사

stat

function

파일의 크기, 권한, 생성일시, 최종 변경일 등 파일의 상태나 정보를 얻는 함수이다.
stat(2) 함수는 symbolic link인 파일을 path로 넘기면 그 원본 파일의 정보를 얻는다.
// man 2 stat #include <sys/stat.h> //header file int stat(const char *path, struct stat *buf);
C
복사

parameters

path : 파일 명 또는 파일에 대한 상대경로/절대경로
buf : 파일의 상태 및 정보를 저장할 구조체
struct stat { dev_t st_dev; ino_t st_ino; mode_t st_mode; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size; blksize_t st_blksize; blkcnt_t st_blocks; time_t st_atime; tiem_t st_mtime; time_t st_ctime; };
C
복사

return value

성공 시 : 0
실패 시 : -1

example

상세 예시는 아래 블로그를 가면 확인할 수 있다.

lstat, fstat

#include <unistd.h> int lstat(const char *path, struct stat *buf); int fstat(int fd, struct stat *buf);
C
복사
lstat : symbolic link인 파일 자체의 정보를 얻는다. 나머지는 같다.
fstat : 인자로 가져온 fd(열려있는 file)의 정보를 얻어온다. 나머지는 같다.

execve

function

실행가능한 파일인 filename의 실행 코드를 현재 프로세스에 적재하여 기존의 실행 코드와 교체하여 새로운 기능으로 실행한다.  즉, 현재 실행되는 프로그램의 기능은 없어지고 filename 프로그램을 메모리에 Loading하여 처음부터 실행한다.
#include <unistd.h> //header file int execve(const char *filename, char *const argv[], char *const envp[]);
C
복사

parameters

filename : 교체할 실행 파일 / 명령어. path와 명령어가 합쳐진 형태라고 생각하면 편함
ex) "usr/bin/ls"
argv : execve에는 argc가 없으므로 array의 마지막 원소 다음은 NULL이 있다
envp : key = value 형태의 환경변수 문자열 배열 리스트. 마지막은 NULL. 만약 이미 설정된 환경변수를 사용하려면 environ 전역변수를 그냥 사용한다.

return value

성공 시 : 없음 (이미 지정된 프로그램의 로직으로 실행되기 때문에 return을 받을 수 없다)
실패 시 : -1
명령어 위치를 찾아보자 which "명령어" → 만약 alias가 되어 있으면 해제하고 확인 해야 한다.

example

#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> extern char **environ; int main(int argc, char *argv[]) { char **new_argv; char command[] = "ls"; int idx; new_argv = malloc(sizeof(char *) * argc + 1); // 명령어를 ls로 변경 new_argv[0] = command; // command line으로 넘어온 parameter를 그대로 사용 for (idx = 1 ; idx < argc ; ++idx) new_argv[idx] = argv[idx]; // argc를 execve 파라미터에 전달할 수 없기 때문에 NULL을 끝으로 지정해우저야 함 new_argv[argc] = NULL; if (execve("/bin/ls", new_argv, environ) == -1){ fprintf(stderr, "프로그램 실행 error: %s\n", strerror(errno)); return 1; } printf("이후 로직은 실행되지 않습니다..\n"); return (0); }
C
복사

dup

function

파일 디스크립터를 복제하여 다른 파일 디스크립터 생성
#include <unistd.h> //header file int dup(int file_des);
C
복사

parameters

file_des : 파일 디스크립터

return value

성공 시 : 파일 디스크립터
실패 시 : -1

example

#include <unistd.h> // dup header file #include <fcntl.h> #include <stdio.h> #include <stdlib.h> int main(void) { int fd1, fd2; if ((fd1 = open("file_1.txt", O_RDONLY)) < 0) { printf("file open error\n"); exit(0); } fd2 = dup(fd1); printf("fd1 : %d, fd2 : %d\n", fd1, fd2); close(fd1); close(fd2); return (0); }
C
복사
항상 낮은 서술자를 반환함

dup2

function

fd2 에 기존의 파일 디스크립터를 복제.
만일 fd2가 이미 열려있으면 fd2를 닫은 후 복제.
#include <unistd.h> //header file int dup2(int file_des1, int file_des2);
C
복사

parameters

file_des : 기존의 파일 디스크립터
file_des2 : 새로운 파일 디스크립터

return value

성공 시 : -1 이외의 값
실패 시 : -1

example

int main(void) { int fd; if ((fd = open("file_1.txt", O_RDONLY)) < 0){ printf("file open error\n"); exit(0); } /* 표준 출력을 file_1.txt 파일로 redirection 함 */ dup2(fd, 1); /* 표준 오류를 file_1.txt 파일로 redirection 함 */ dup2(fd, 2); close(fd); return (1); }
C
복사

pipe

function

서로 독립된 프로세스들이 데이터를 주고 받을 수 있게 도와준다.
#include <unistd.h> //header file int pipe(int fd[2]);
C
복사

parameters

fd[0] : 함수 호출 후 fd[0]에 데이터를 입력 받을 수 있는 fd가 담김(파이프 출구)
fd[1] : 함수 호출 후 fd[1]에 데이터를 출력할 수 있는 fd가 담김(파이프 입구)

return value

성공 시 : 0
실패 시 : -1

example

#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> int pipes[2] ; void parent_proc() { char * buf = 0x0; ssize_t s; size_t len = 0; // 읽기용 파이프를 닫는다 close(pipes[0]); while ((s = getline(&buf, &len, stdin)) != -1) { buf[s - 1] = 0x0; ssize_t sent = 0; char * data = buf; while (sent < s) { sent += write(pipes[1], buf + sent, s - sent); } free(buf); buf = 0x0; len = 0; } close(pipes[1]); } void child_proc() { char buf[32]; ssize_t s; // 쓰기용 파이프를 닫는다 close(pipes[1]); while ((s = read(pipes[0], buf, 31)) > 0) { buf[s + 1] = 0x0; printf(">%s\n", buf); } exit(0); } int main() { pid_t child_pid; int exit_code; if (pipe(pipes) != 0) { perror("Error"); exit(1); } printf("%d %d\n", pipes[0], pipes[1]); child_pid = fork(); if (child_pid == 0) child_proc(); else parent_proc(); return (0); }
C
복사

opendir

function

디렉토리 열기
#include <dirent.h> // header file DIR * opendir(const char * name);
C
복사

parameters

name : open 할 디렉토리의 이름

return value

성공 시 : DIR 포인터 디렉토리 정보 구조체 포인터
실패 시 : NULL

readdir

function

디렉토리 내에 있는 모든 파일과 디렉토리 정보 구하기
#include <dirent.h> // header file struct dirent *readdir(DIR *dirp);
C
복사

parameters

dirp : open 할때 반환되는 DIR 포인터

return value

성공 시 : 디렉토리내의 파일/디렉토리에 대한 정보를 가진 구조체 포인터
실패 시 : NULL
readdir()을 처음 호출하면 opendir()를 통해 연 디렉토리 내의 첫번째 파일에 대한 정보를 가져오고, 다시 호출시 두번째 파일에 대한 정보, 다시 호출시 세번째 파일을 호출,,, 하다가 더이상 파일이나 디렉토리가 없으면 NULL 반환

closedir

function

디렉토리 닫기
#include <dirent.h> // header file int closedir(DIR *dirp);
C
복사

parameters

dirp : open 할때 반환되는 DIR 포인터

return value

성공 시 : 0
실패 시 : -1

example (opendir/readdir/closedir)

#include <dirent.h> #include <stdio.h> #include <unistd.h> #include <sys/stat.h> int main() { DIR *dir_info; struct dirent *dir_entry; mkdir("test_A", 0755); // 실행파일이 있는 곳에 dir 생성 mkdir("test_B", 0755); dir_info = opendir("."); // 현재 디렉토리 열기 if (dir_info) { while((dir_entry = readdir(dir_info))) // 디렉토리 안에 있는 모든 파일과 디렉토리 출력 printf("%s\n", dir_entry->d_name); // d_name : 읽힌 디렉토리/파일의 이름 저장 closedir(dir_info); } return (0); }
C
복사

strerror

function

시스템 오류 번호에 대한 오류 메세지를 문자열로 반환하는 함수이다.
#include <string.h> char *strerror(int errnum);
C
복사

parameters

errnum : 오류 번호

return value

성공 시 : 오류 번호와 일치하는 오류 메세지 문자열
실패 시 : NULL or "Unknown error nnn"

example

#include <errno.h> #include <string.h> #include <stdio.h> int main(int argc, char **argv) { FILE *fp; if ((fp = fopen(argv[1], "r")) == NULL) { fprintf(stderr, "errno[%d] : %s\n", errno, strerror(errno)); return (1); } return (0); }
C
복사

errno

마지막으로 발생한 에러 번호
#include <errno.h> extern int errno;
C
복사