https://pages.cs.wisc.edu/~remzi/OSTEP/cpu-api.pdf
해당 교재에 있는 Homework(code) 풀어보고 정답 정리 및 모르는 부분 체크
Write a program that calls fork(). Before calling fork(), have the main process access a variable (e.g., x) and set its value to something (e.g., 100). What value is the variable in the child process?
What happens to the variable when both the child and parent change the value of x?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int x = 100;
printf("x from main process: %d\n", x);
int child = fork();
if (child < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (child == 0) {
printf("x from child process(%d): %d\n", (int)getpid(), x);
x = 200;
printf("now x is %d\n", x);
}
else {
printf("x from parent process(%d): %d\n", (int)getpid(), x);
x = 300;
printf("now x is %d\n", x);
}
return 0;
}
(해당 코드를 통해 확인한 내용)
parent 프로세스를 복제하여 child 프로세스를 생성하므로 처음 x는 같지만 child 프로세스에서 변경한 값이 parent 프로세스로 전달되지는 않음 (pipe와 같은 IPC를 통해 값을 전달 할 수 있다.)
(코드 작성 중 100% 알 지 못한 내용들, 애매한 것들도 포함)
getpid() 함수는 unistd.h 헤더에 정의 되어있고 pid_t 타입의 값을 반환해준다.
대부분의 시스템에서는 32비트 또는 64비트 정수(일반적으로 int 또는 long)를 사용한다.
코드 이식성을 높이고 싶다면 getpid()에 형 변환을 해주는 것이 좋다.
(int)getpid()
fprintf는 C 표준 라이브러리(stdlib.h)의 함수이고 지정된 스트림(파일이나 표준 입출력)에 형식화된 출력을 쓰는 데 사용한다.
int fprintf(FILE *stream, const char *format, ...);
C 프로그램에서는 기본적으로 세 가지 표준 스트림이 존재한다.
... 부분은 printf와 마찬가지로 %d나 이런게 들어가면 적어주면됨
fprintf(stdout, "Character: %c\n", 'A');
Write a program that opens a file (with the open() system call) and then calls fork() to create a new process. Can both the child and parent access the file descriptor returned by open()? What happens when they are writing to the file concurrently, i.e., at the same time?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
int fd = open("./test.txt", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
if (fd < 0) {
fprintf(stderr, "open failed\n");
exit(1);
}
int child = fork();
if (child < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (child == 0) {
char *child_msg = "Hello I'm Child!!\n";
write(fd, child_msg, strlen(child_msg));
}
else {
char *parent_msg = "Hello I'm Parent!!\n";
write(fd, parent_msg, strlen(parent_msg));
}
close(fd);
return 0;
}
open()을 통해서 test.txt를 오픈 혹은 없는 경우 파일을 생성하고,
child process와 parent process가 write를 하게되면 test.txt에 프로세스가 접근하여 text를 작성할 수 있다.
(코드 작성 중 100% 알 지 못한 내용들, 애매한 것들도 포함)
open() 함수는 파일을 열거나 생성하는 데 사용되는 시스템 콜
(fcntl.h 헤더 파일에 정의)
int open(const char *pathname, int flags, mode_t mode);
pathname은 열거나 생성할 파일의 경로
flags는 파일을 어떤 방식으로 open할 것인지 설정
mode는 파일을 생성할 때 설정할 권한 (O_CREAT 플래그가 있을 때만 사용)
반환값은 성공시 파일 디스크립터(양의 정수), 실패시 -1을 반환한다.
flags는 여러 옵션이 있는데 | 를 통해 여러개를 적용할 수 있다.
mode도 여러 옵션이 존재한다.
write() 함수는 파일 디스크립터에 데이터를 쓰는 데 사용되는 시스템 콜
(unistd.h에 들어있다)
ssize_t write(int fd, const void *buf, size_t count);
성공시 실제로 쓴 바이트 수 (일반적으로 count와 같음),
실패 시 -1 (오류 코드는 errno에 설정됨) 를 반환해준다.
파일 디스크립터는 운영체제가 관리하는 테이블의 인덱스로, 열려 있는 파일이나 기타 입출력 리소스를 참조하는 데 사용되는 정수값이다.
(일반적으로 0부터 시작하는 음이 아닌 정수)
모든 프로세스는 기본적으로 세 개의 프로세스를 가지고 있다.
0 (STDIN_FILENO): 표준 입력 (일반적으로 키보드)
1 (STDOUT_FILENO): 표준 출력 (일반적으로 콘솔/터미널)
2 (STDERR_FILENO): 표준 오류 (일반적으로 콘솔/터미널)
open(), socket(), pipe() 등의 시스템 콜을 통해 생성되고,
할당 시 사용 가능한 가장 낮은 번호가 할당된다.
사용이 끝나면 close()를 통해 해제한다.
fork() 호출 시, 자식 프로세스는 부모 프로세스의 모든 파일 디스크립터를 상속되므로 부모와 자식이 동일한 파일에 동시에 쓸 수 있다.
Write another program using fork(). The child process should print “hello”; the parent process should print “goodbye”. You should try to ensure that the child process always prints first; can you do this without calling wait() in the parent?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int rc = fork();
if (rc < 0) {
// fork failed
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child process
printf("hello\n");
} else {
// parent process
// 자식이 먼저 출력하도록 잠시 대기
usleep(10000); // 10ms 대기
printf("goodbye\n");
}
return 0;
}
항상 부모 프로세스가 먼저 실행되므로 wait(0x0) 또는 sleep을 통해 부모 프로세스가 기다리게 한 후 자식 프로세스를 실행 시켜야
자식 -> 부모로 실행된다.
X
Write a program that calls fork() and then calls some form of exec() to run the program /bin/ls. See if you can try all of the variants of exec(), including (on Linux) execl(), execle(), execlp(), execv(), execvp(), and execvpe(). Why do you think there are so many variants of the same basic call?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[]) {
printf("메인 프로그램 시작 (PID: %d)\n", (int)getpid());
int rc = fork();
if (rc < 0) {
fprintf(stderr, "fork 실패\n");
exit(1);
} else if (rc == 0) {
// 자식 프로세스
printf("자식 프로세스 (PID: %d) exec 실행 예정\n", (int)getpid());
// 테스트할 exec 변형을 선택하세요:
// execl - 인자를 개별적으로 나열
execl("/bin/ls", "ls", "-la", NULL);
// execle - execl과 비슷하지만 환경 변수 포함
// char *envp[] = {"PATH=/bin", NULL};
// execle("/bin/ls", "ls", "-la", NULL, envp);
// execlp - PATH를 사용하여 실행 파일 검색
// execlp("ls", "ls", "-la", NULL);
// execv - 인자를 벡터(배열)로 전달
// char *args[] = {"ls", "-la", NULL};
// execv("/bin/ls", args);
// execvp - PATH를 사용하고 인자를 벡터로 전달
// char *args[] = {"ls", "-la", NULL};
// execvp("ls", args);
// execvpe - PATH를 사용하고 인자와 환경 변수를 벡터로 전달
// char *args[] = {"ls", "-la", NULL};
// char *envp[] = {"PATH=/bin", NULL};
// execvpe("ls", args, envp);
printf("exec 실패했다면 이 메시지가 보임\n");
exit(1);
} else {
// 부모 프로세스
printf("부모 프로세스 (PID: %d)\n", (int)getpid());
}
return 0;
}
Nowwrite a programthat uses wait() towait for the child process to finish in the parent. What does wait() return? What happens if you use wait() in the child?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int rc = fork();
if (rc < 0) {
// fork failed
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child process
printf("Hello from child (pid:%d)\n", (int)getpid());
// 자식에서 wait() 호출
int wc = wait(NULL);
printf("Child: wait() returned %d\n", wc);
} else {
// parent process
printf("Hello from parent of %d (pid:%d)\n", rc, (int)getpid());
// 부모에서 wait() 호출
int wc = wait(NULL);
printf("Parent: wait() returned %d\n", wc);
}
return 0;
}
부모 프로세스에서 wait의 value를 체크해보면 자식 프로세스의 pid값인 것을 알 수 있다.
하지만 자식 프로세스에서 wait을 호출하고 value를 체크해보면 -1로
위 코드에서 자식 프로세스는 기다릴 자식 프로세스가 없기 때문에
wait이 에러 코드인 -1을 출력해준 것이다.
X
Write a slight modification of the previous program, this time using waitpid() instead of wait(). When would waitpid() be useful?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int rc = fork();
if (rc < 0) {
// fork failed
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child process
printf("Hello from child (pid:%d)\n", (int)getpid());
} else {
// parent process
printf("Hello from parent of %d (pid:%d)\n", rc, (int)getpid());
// waitpid()를 사용하여 특정 자식 대기
int wc = waitpid(rc, NULL, 0);
printf("Parent: waitpid() returned %d\n", wc);
}
return 0;
}
waitpid()를 사용하면 특정 자식 프로세스를 기다릴 수 있다.
fork()를 두 번하는 상황이 되어서 전체 프로세스가 4개라고 가정하면
wait()을 하게되면 랜덤 프로세스가 종료되기르 기다리지만
waitpid()를 하게되면 특정 프로세스가 종료되는 것만 기다린다.
waitpid의 syntax
Write a program that creates a child process, and then in the child closes standard output (STDOUT FILENO).What happens if the child calls printf() to print some output after closing the descriptor?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int rc = fork();
if (rc < 0) {
// fork failed
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child process
printf("Child: before closing stdout\n");
// 표준 출력 닫기
close(STDOUT_FILENO);
// 표준 출력 닫은 후 출력 시도
printf("Child: after closing stdout (you shouldn't see this)\n");
} else {
// parent process
wait(NULL);
printf("Parent: child has finished\n");
}
return 0;
}
표준 출력을 닫으면 더 이상 출력이 불가능하다.
Write a program that creates two children, and connects the standard output of one to the standard input of the other, using the pipe() system call.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int pipefd[2]; // 파이프를 위한 파일 디스크립터
pid_t child1, child2;
// 파이프 생성
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// 첫 번째 자식 생성 (출력을 생성)
child1 = fork();
if (child1 < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (child1 == 0) { // 첫 번째 자식 프로세스
// 읽기 종단은 필요 없으므로 닫음
close(pipefd[0]);
// 표준 출력을 파이프의 쓰기 종단으로 리다이렉션
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[1]);
// 명령 실행 (예: ls 명령)
execlp("ls", "ls", "-l", NULL);
// exec가 실패한 경우
perror("execlp in first child");
exit(EXIT_FAILURE);
}
// 두 번째 자식 생성 (입력을 받음)
child2 = fork();
if (child2 < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (child2 == 0) { // 두 번째 자식 프로세스
// 쓰기 종단은 필요 없으므로 닫음
close(pipefd[1]);
// 표준 입력을 파이프의 읽기 종단으로 리다이렉션
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
// 명령 실행 (예: wc 명령)
execlp("wc", "wc", "-l", NULL);
// exec가 실패한 경우
perror("execlp in second child");
exit(EXIT_FAILURE);
}
// 부모 프로세스는 파이프의 양쪽 종단을 닫음
close(pipefd[0]);
close(pipefd[1]);
// 두 자식 프로세스가 종료될 때까지 대기
waitpid(child1, NULL, 0);
waitpid(child2, NULL, 0);
printf("부모: 두 자식 프로세스 모두 종료됨\n");
return 0;
}