명령어 정리
echo
- 출력 관리
cd, pwd
- 디렉터리 관리
export, unset, env
- 환경변수 관리
- env와 export차이가 하나씩 존재했음
-> _=/usr/bin/env(env) , OLDPWD(export)
exit
- 프로세스 종료
세미콜론(;)
- 명령어 구분, 차례대로 실행 (실패해도 그대로 실행함, &&와 다름)
따옴표 quote(', ")
- SingleQuote : 모든 것을 메타문자가 아닌 문자 그대로 사용하게 함
- DoubleQuote : $, \, `을 메타문자로 인식, 나머지는 문자 그대로
- ex) echo " '$a' "
리디렉션 (<, >, >>)
< : 파일의 내용을 표준 입력으로 보내기
> : 표준 출력을 파일로 보내기
>> : 표준 출력을 파일로 보내는데, 추가모드
파이프 (|)
- 왼쪽의 표준 출력을 오른쪽의 표준 입력으로 처리
$char
- 변수
- $? : 마지막으로 실행된 명령의 종료 상태 (리턴 값)
-> ex) 없는 명령 후, echo $? (127)
스크립트 제어 (Ctrl + C, D, \)
Ctrl-C : 프로세스 종료, ( SIGINT )
Ctrl-D : EOF를 표준입력으로 보냄 (라인 첫부분에서만 작동), 입력이 아닌경우 exit함
Ctrl-\ : 프로세스 종료, ( SIGQUIT )
superuser.com/questions/169051/whats-the-difference-between-c-and-d-for-unix-mac-os-x-terminal
모르는 외부함수 정리
42norm에서 허용되지 않은 매크로 함수는 사용할 수 없다함
char *getcwd(char *buf, size_t size);
현재 작업 디렉터리 이름 얻기 (절대 경로)
파라미터
- buf : 현재 경로가 담길 공간
- size : buf의 사이즈
반환
- 성공 시 buf의 포인터
- 실패 시 NULL
int chdir(const char *path);
현재 작업 디렉터리 변경
파라미터
- path : 변경할 디렉터리 경로
반환
- 성공 시 0
- 실패 시 -1
DIR *opendir(const char *name);
디렉터리 열기
파라미터
- name : 파일 경로
반환값
- 성공 시 열린 디렉터리 스트림 포인터
- 실패 시 NULL
struct dirent *readdir(DIR *dirp);
디렉터리 읽기
파라미터
- dirp : 디렉터리 스트림 포인터
반환값
- 성공 시 "디렉터리 엔트리 포인터"
- 실패 시 NULL
cf) dirent 구조체에서 d_type과 d_name을 가지고 놀 것
-> dirent와 d_type같은 경우는 man dirent에서 확인할 것
- 순서대로 하나씩 dirent를 뱉으므로 NULL일 때까지 읽어주면 됨
int closedir(Dir *dirp);
디렉터리 닫기
파라미터
- dirp : 디렉터리 스트림 포인터
반환값
- 성공 시 0
- 실패 시 -1
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <unistd.h>
# define DIRTYPE_TO_STRING(X) \
((X) == DT_DIR ? "directory" : \
(X) == DT_REG ? "regular file" : \
"unknown file")
int main(void)
{
char path[256];
memset(path, 0, 256);
if (getcwd(path, 256) == NULL)
{
printf("%s", strerror(errno));
return (-1);
}
printf("Before chdir, current path : %s\n", path);
if (chdir("..") == -1)
{
printf("%s", strerror(errno));
return (-1);
}
if (getcwd(path, 256) == NULL)
{
printf("%s", strerror(errno));
return (-1);
}
printf("after chdir, current path : %s\n", path);
DIR *p_dir;
if ((p_dir = opendir(path)) == NULL)
{
printf("fail opendir()\n");
return (-1);
}
struct dirent *dir_ent;
while ((dir_ent = readdir(p_dir)) != NULL)
{
printf("*****************************************\n");
printf("d_name : %s\n", dir_ent->d_name);
printf("d_type : %s\n", DIRTYPE_TO_STRING(dir_ent->d_type));
printf("*****************************************\n");
}
if (closedir(p_dir) == -1)
{
printf("fail closedir()\n");
return (-1);
}
return (0);
}
|
cs |
char *strerror(int errno);
errno를 정수 값이 아닌 message형태로 뽑아줌
파라미터
- errno : errno.h의 errno 넣어주면 됨
반환 값
- 정상적인 errno : message
- 비정상적인 errno : unknown error message
extern int errno
syscall 에러의 형태를 define된 정수값으로 들고 있음
errno : 0은 에러가 없는 것
int dup(int fd);
fd를 복제함, 현재 fd가 가르키는 파일인 파일디스크립터를 복제해줌 (같은 정수가 아님)
파라미터
- fd : 복제하고 싶은 파일디스크립터
반환 값
- 성공 시 : 새 파일 디스크립터
- 실패 시 : -1
int dup2(int fd, int fd2);
fd를 복제함, fd2가 기존의 fd쪽을 가르키게 함
즉, 새로 복제된 fd의 값을 fd2로 지정하는 것
만약 fd2가 이미 열려있다면(가르키고 있다면) 그것을 닫고 복제함
파라미터
- fd : 복제하고 싶은 파일디스크립터
- fd2 : 복제될 파일디스크립터
반환 값
- 성공 시 : 복제 된 fd2값
- 실패 시 : -1
cf) 0은 stdin, 1은 stdout, 2는 stderr
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
32
33
34
35
36
37
|
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(void) // 짧은 코드를 위해 예외처리를 하지 않음
{
int fd1 = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
int fd2 = dup(fd1);
int fd3 = 5;
printf("fd1: %d, fd2: %d\n", fd1, fd2); // fd1 : 3, fd2 : 4
write(fd1, "Hello\n", 6);
write(fd2, "World\n", 6); // write on the same file
int ret1 = dup2(fd2, fd3);
printf("fd2: %d, fd3: %d, ret1: %d\n", fd2, fd3, ret1); // fd2 : 4, fd3: 5, ret1: 5
write(fd3, "!!!!!\n", 6); // write on the same file
write(ret1, "?????\n", 6);// write on the same file
int ret2 = dup2(1, fd3); //열려있는 fd3를 1이 가르키는 stdout를 가르키게 함
printf("fd3: %d, ret2: %d\n", fd3, ret2); // fd3: 5, ret2: 5
write(fd3, "zzzzz\n", 6); // write to stdout
write(ret2, "hhhhh\n", 6); //wrtie to stdout
if (close(fd1) == -1)
{
printf("close(fd1) error\n");
return (-1);
}
if (close(fd2) == -1) // no error occurred
{
printf("close(fd2) error\n");
return (-1);
}
return (0);
}
|
cs |
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
파일의 속성을 조회함
파라미터
- pathname : 속성을 조회하고 싶은 파일경로
- fd : 속성을 조회하고 싶은 열린 파일 디스크립터
- statbuf : 파일 속성을 저장할 버퍼
반환 값
- 성공 시 0
- 실패 시 -1
cf) stat함수의 인자로 pathname이 심볼릭링크라면 가르키는 원본파일의 속성을 담고
+ lstat함수의 인자로 pathname이 심볼릭링크라면 그 심볼릭링크파일의 속성을 담음 (즉, 그 파일자체의 속성)
cf) struct stat은 man 2 stat으로 조회
cf) struct stat중의 st_mode는 type과 mode에 대한 정보를 담고 있음 -> S_ISXXX 매크로 함수와 사용될 것 -> 참(1), 거짓(0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#include <stdio.h>
#include <sys/stat.h>
int main(void)
{
struct stat stat_buf;
stat("test.txt", &stat_buf);
printf("file size of test.txt: %lld\n", stat_buf.st_size);
if (S_ISDIR(stat_buf.st_mode))
printf("text.txt is directory\n");
else if (S_ISREG(stat_buf.st_mode))
printf("test.xt is regular file\n");
else
printf("else\n");
return (0);
}
|
cs |
cf) S_ISXXX(st_mode)참고 (www.skrenta.com/rt/man/stat.2.html)
cf) st_mode 비트연산도 가능
pid_t fork(void);
자식프로세스를 생성함, 프로세스 복제 (메모리, 코드 정보도 그대로 복제함, 복제 후 독립적으로 메모리 존재)
자식프로세스의 종료처리를 해줘야 함 (wait 계열 함수)
파라미터
- 없음
반환 값
- 성공 시
-> 부모 프로세스 : 자식 프로세스의 PID를 리턴
-> 자식 프로세스 : 0
- 실패 시
-> 부모 프로세스 : -1
-> 자식 프로세스는 생성되지 않음
cf) pid_t getpid(void) : 나의 pid를 얻음
cf) pid_t getppid(void) : 부모의 pid를 얻음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid == 0)
printf("child process: %d\n", getpid());
else if (pid > 0)
printf("parent process: %d, child pid: %d\n", getpid(), pid);
else
{
printf("fork error\n");
return (-1);
}
printf("end pid: %d\n", getpid());
return (0);
}
|
cs |
int execve(const char *path, char *const argv[], char *const envp[]);
해당 프로세스는 종료하고 새로운 프로세스 생성 및 실행
종료되기 때문에 자식프로세스를 fork()로 생성하고 자식프로세스에서 exec를 하는게 일반적
매개변수
- path : 실행파일 경로
- argv : main과 흡사, 마지막에 NULL 필요
- envp : 환경변수 문자열 배열리스트("key=value"형태로 저장), 마지막에 NULL 필요
반환 값
- 성공 시 : 반환하지 않음 (새로운 프로세스를 실행하기 때문)
- 실패 시 : non-zero, -1
cf) argv와 envp를 main에서 받고 그대로 넘겨주는 것도 나쁘지 않음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(void)
{
char *argv[] = { "ls", "-al", NULL };
char *envp[] = { "MY_PATH=/Users/malbong", NULL };
printf("!!! execve !!!\n");
if (execve("/bin/ls", argv, envp))
{
printf("execve error : %s\n", strerror(errno));
return (-1);
}
/* 정상적으로 실행 되었을 때 여기를 실행하는지 확인 */
printf("!!! check !!!\n");
/* 위의 printf가 실행되지 않음 */
return (0);
}
|
cs |
void exit(int status);
프로세스 종료
나의 프로세스를 생성한 부모프로세스에게 상태를 알림
파라미터
- status : 종료 상태
-> 0 : 정상 종료 성공 상태
-> non-zero : 정상 종료 실패 상태
잠깐 보는 Signal
- 프로세스나, 커널이 프로세스에게 보내주는 신호 (signum : 숫자)
자식 프로세스 종료 시그널 - SIGCHLD
- 자식 프로세스가 종료되었을 때, 부모 프로세스에게 SIGCHLD시그널을 전송함
- 부모 프로세스는 SIGCHLD에 대해 수신을 대기하고
- SIGCHLD를 수신을 하면, 자식 프로세스 상태를 확인 한 후, 종료된 자식 프로세스로 처리함 (순서중요)
- 이와 비슷하게 자식프로세스를 처리하는 방법이 있음 -> wait
pid_t wait(int *status);
자식 프로세스 종료 대기
자식 프로세스가 종료가 될 때까지 블록킹함
파리미터
- wstatus : child process의 종료 상태
반환 값
- 성공 시 : termminated된 자식 프로세스의 pid
- 실패 시 : -1
WIFEXITED : 정상 종료 되었는지 (참, 거짓으로 반환)
WEXITSTATUS : 자식프로세스가 넘긴 종료코드를 확인
WIFSIGNALED : 특정 시그널을 받아서 종료 되었는지, 다른 이유로 종료 되었는지 (참, 거짓)
WTERMSIG : 그 특정 시그널 번호를 알려줌
WCOREDUMP : 자식 프로세스가 코어덤프파일을 생성했는지
WIFSTOPPED : 자식 프로세스가 stop 되었는지
WSTOPSIG : 멈춘 이유에 대한 시그널 번호를 알려줌
WIFCONTINUED : 자식 프로세스가 resume 되었는지
pid_t waitpid(pid_t pid, int *status, int options);
pid_t wait3(int *status, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
파라미터
- pid : 종료 대기할 프로세스의 pid
- status : 자식 프로세스의 종료코드
- options
-> WNOHANG: 논블럭킹처럼 작동, 살아있는지 바로 알려줌, 그래서 while문이랑 사용가능
-> WUNTRACED: 자식프로세스가 SIGSTOP 받아도 반환 받음
-> WCONTINUED : 자식프로세스가 SIGCONT 받아도 반환 받음
- rusage : 자식 프로세스의 리소스 사용량을 반환 받음 (cpu, memory size... etc), man getrusage로 확인
반환 값
- 양수 : 상태가 바뀐 chlid process의 pid
- 0 : WNOHANG 지정 시 멀쩡하게 자식이 살아있다면 0을 리턴함
- 실패 : -1
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
32
33
34
35
36
37
38
39
40
|
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
int main(void)
{
pid_t pid;
int status;
char *argv[] = { "ls", "-al", NULL };
char *envp[] = { "A=a", NULL };
pid = fork();
if (pid > 0) /* parent */
{
printf("parent pid: %d, child pid: %d\n", getpid(), pid);
while (!waitpid(pid, &status, WNOHANG));
if (WIFEXITED(status))
printf("child procees exited number: %d\n", WEXITSTATUS(status));
else
printf("is not exited\n");
}
else if (pid == 0) /* child */
{
printf("child pid: %d, execve ls\n", getpid());
if (execve("/bin/ls", argv, envp) == -1)
{
printf("execve error: %s\n", strerror(errno));
return (-1);
}
}
else /* error */
{
printf("error... %s", strerror(errno));
return (-1);
}
printf("### end pid: %d ###\n", getpid());
return (0);
}
|
cs |
Signal 정의 (좀 더 알아보기 + 알아만 놓기)
정의
- 비동기 이벤트를 처리하기 위한 메커니즘 (언제 일어날지 모르는 이벤트 처리)
- 소프트웨어 인터럽트
쓰임
- Ctrl + C (SIGINT)
- Child process termination
- Alarm
- divide by zero
- inter-process communication
담는 정보
- 시그널 번호 + 추가정보 + 사용자 정의 데이터(작은 크기)
Signal 처리
무시
- 아무런 동작도 하지 않음
- SIGKILL, SIGSTOP은 무시 불가능
붙잡아 처리
- 시그널 별 처리 함수를 수행 (사용자 정의 함수 수행)
기본 동작 (정의 되어 있음)
- 시그널 종류 별 기본 동작 수행
-> 프로세스 종료
-> 코어덤프 생성 후 종료
-> 무시 혹은 정지
주요 Signal 번호 (man 3 signal)
시그널 번호 | 기본 동작 | 의미 |
SIGHUB | 종료 | 프로세스의 제어 터미널이 닫힐 때 (로그아웃) 설정 리로드 |
SIGINT | 종료 | 사용자가 Ctrl + C |
SIGQUIT | 코어 덤프 파일 생성 | 사용자가 Ctrl + \ |
SIGKILL | 종료 | 붙잡을 수 없는 프로세스 종료 |
SIGSEGV | 코어 덤프 파일 생성 | 메모리 접근 위반 |
SIGALARM | 종료 | 알람 발생 |
SIGTERM | 종료 | 붙잡을 수 있는 프로세스 종료 |
SIGUSR1/2 | 종료 | 사용자 정의 시그널 (USR1, USR2임) |
SIGCHLD | 종료 | 자식 프로세스 종료 |
SIGCONT | 진행 | 프로세스를 정지했다가 다시 수행함 |
SIGSTOP | 정지 | 프로세스 정지 |
Signal의 실행과 상속
fork() - 자식 프로세스는 부모 프로세스의 시그널 동작을 상속 받음
exec() - 부모 프로세스가 붙잡아 처리하는 시그널은 기본동작으로 변경
시그널 동작 | fork() 수행 후 | exec() 수행 후 |
무시 | 상속됨 | 상속됨 |
기본 동작 | 상속됨 | 상속됨 |
붙잡아 처리 | 상속됨 | 상속되지 않음 (기본동작으로 처리함) |
대기 중인 시그널 | 상속되지 않음 | 상속됨 |
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
Signal 처리 설정, 붙잡아 처리
파라미터
- signum : 처리 대상 시그널 번호
- handler : 시그널 핸들러
-> SIG_IGN : 해당 시그널을 무시하게 함
-> SIG_DFL : 해당 시그널을 기본 동작 처리함
-> 그외 사용자 정의 시그널 핸들러
반환 값
- 성공 시 : 이전 시그널 핸들러
- 실패 시 : SIG_ERR
cf) 시그널 핸들러는 재진입이 가능한 함수로 작성해야 함 (비동기적이기 때문)
-> 글로벌 데이터를 수정하는 시그널 핸들러는 조심해야 함
-> 그런 것이 필요한 경우, 시그널블록 -> 데이터처리 -> 시그널언블록 하여 안전하게 사용
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void my_signal_handler(int signum)
{
printf("signal: %d\n", signum);
exit(123);
}
int main(void)
{
pid_t pid;
int status;
pid = fork();
if (pid == 0) /* child */
{
signal(SIGTERM, my_signal_handler);
printf("child : %d, infinity loop\n", getpid());
while (1)
sleep(1);
}
else if (pid > 0) /* parent */
{
while (1)
{
int n;
scanf("%d", &n);
if (n == 0) break ;
}
kill(pid, SIGTERM);
pid = wait(&status);
printf("terminated pid: %d\n", pid);
if (WIFSIGNALED(status))
printf("terminate signal number: %d\n", WTERMSIG(status));
if (WIFEXITED(status))
printf("terminate code: %d\n", WEXITSTATUS(status));
}
else
printf("error\n");
return (0);
}
|
cs |
ps -ef | grep으로 좀비는 나오지 않음
wait로 블록으로 자식 프로세스가 종료되는 것을 기다림
WEXITSTATU(status)의 결과는 임의로 만든 handler의 exit(123) 값임
int kill(pid_t pid, int sig)
시그널 보내기
자식프로세스를 중지시킬 수 있음
파라미터
- pid : 시그널 송신 대상 지정
-> 1 이상 : 해당 프로세스 ID
-> 0 : 프로세스 그룹 전체에
-> -1 : 권한 내의 모든 프로세스
-> -1미만 : 프로세스 그룹 아이디가 (-pid)인 프로세스 그룹 전체 (-10이면 프로세스그룹아이디가 10인 곳에)
- sig : 보낼 시그널 번호
-> 단, sig가 0인 경우 시그널을 보내지는 않으나, process 유뮤 및 권한 판단을 할 수 있음
반환 값
- 하나 이상의 프로세스에게 송신 시 0 리턴
- 실패 시 : -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(void)
{
pid_t pid[10];
for (int i = 0; i < 10; ++i)
{
pid[i] = fork();
if (pid[i] == 0) /* child */
{
while (1)
printf("I'm child process: %d\n", getpid());
}
}
for (int i = 0; i < 10; ++i)
{
printf("kill(pid:%d)\n", pid[i]);
kill(pid[i], SIGTERM);
}
printf("Parent process is terminated\n");
return (0);
}
|
cs |
int pipe(int fildes[2]);
파이프를 생성하고 fd쌍을 만들어줌
fd쌍으로 프로세스간에 데이터 통신을 가능하게 함
파라미터
- fildes[2] : 파이프에 이용할 fd쌍
반환 값
- 성공 시 : 파이프 생성 후 0 반환
- 실패 시 : -1
fd[0]은 읽기를, fd[1]는 쓰기를 담당함
프로세스가 비어있는 파이프에서 읽을려고 한다면 read(fd[0])는 읽을 수 있는 상태일 때까지 블락
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
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
int main(void)
{
int prcw[2]; /* parent read, child write */
int pwcr[2]; /* parent write, child read */
pid_t pid;
int status;
char res[10];
if (pipe(prcw) || pipe(pwcr))
{
printf("pipe error: %s\n", strerror(errno));
return (-1);
}
pid = fork();
if (pid > 0) /* parent */
{
write(pwcr[1], "Parent", 6);
memset(res, 0, 10);
read(prcw[0], res, 5); res[5] = 0;
printf("Parent Process: %s\n", res);
while (!waitpid(pid, &status, WNOHANG));
}
else if (pid == 0) /* chlid */
{
write(prcw[1], "Child", 5);
memset(res, 0, 10);
read(pwcr[0], res, 6); res[6] = 0;
printf("Chlid Process: %s\n", res);
}
else
{
printf("fork error\n");
return (-1);
}
printf("end\n");
return (0);
}
|
cs |
pipe를 두개 생성한 이유
pipe를 하나를 쓰면 부모나 자식이 혼자서 write하고 그것을 혼자 read할 수 있기 때문
아래 예제 참고사이트
%%% 프로세스, 파이프, 시그널에 대한 공부는 더 필요함 %%%
'42cursus' 카테고리의 다른 글
philosophers[철학자] - 외부함수 정리하기 (시간, 쓰레드, 뮤텍스, 세마포어) (0) | 2021.02.25 |
---|---|
minishell - 간단한 정리 (0) | 2021.02.18 |
ft_services - 가이드 및 참고자료 정리하기 (42seoul) (0) | 2021.01.07 |
Kubernetes(쿠버네티스) 개념 정리하기 (0) | 2021.01.07 |
libasm - 참고 자료 위주로 정리하기 (42seoul) (1) | 2020.11.28 |