[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

💻 CS/시스템프로그래밍

[시스템프로그래밍] Processes in UNIX

inu 2019. 10. 12. 16:59
반응형

이것은 한낱 대학생이 교수의 수업을 듣고 작성한 개인저장용 복습 문서입니다.

 

그렇지만, 물론 지적과 수정은 환영합니다.


Process

기본적 active entity. 실행중인 프로그램.

프로세스가 빠르게 context switch된다.

프로세스끼리 커뮤니케이션하려면 OS의 system call들을 사용해야 한다.

process는 언제든 interrupt 가능 (by device or system call)

PCB(process control block)안에서 관리

 

Context switch

발생시 현재 실행중인 프로세스 자신의 상태를 저장하고,

새로 선택된 프로세스 상태를 로드한다.

인터럽트, 디바이스, timer(tim quantum expired) 인터럽트 switch 가능.

-과정

1. cpu가 interrupt 인식 (cpu안의 하드웨어 플랙)

2. cpu(os)가 interrupt handler 시작. (in privileged mode, 특정모드)

3. (interrupt handler는 모든 interrupt를 안보이게 할 '수도' 있다. 이 경우 다른 인터럽트를 무시한다.)

4. 중단된 process 정보 저장

5. 발생 interrupt 처리 코드 처리 (by interrupt handler)

6. interrupt handler가 cpu 스케줄러에 의해 종료된다.

7. cpu 스케줄러가 선택한 프로세스의 상태를 불러옴 

8. (3번과정이 실행되었을 경우엔 interrupt handler가 다시 interrupt가 가능하도록 한다.)

9. cpu(os)가 유저모드로 다시 돌리고, 선택된 프로세스를 실행한다.

 

Process identification

pid_t getpid(void) : process ID를 가져온다.

pid_t getppid(void): parent process ID를 가져온다.

pid_t는 일종의 unsigned integer type (-1이 리턴될수도 있다.)

유저 ID와 그룹 ID도 있다. (gid_t getegid(void), uid_t geteuid(void)  gid_t getgid(void), uid_t getuid(void))

유저 ID는 real user(프로세스의 실제소유자, root) 그리고 effective user(프로세스를 호출한 사용자, csslab)으로 나뉜다.

 

Process State

Process context : 프로세스에 대한 정보들 보유

Quantm expired시에도 ready상태로 돌아가고, 인터럽트시에도 돌아갈 수 있다.

 

Process Hierachy

root process 제외하고 모두 부모를 가지고 있음 (계층 구조)

 

Shell ex

사용자가 명령어 입력시, shell을 거쳐서 새로운 프로세스를 확인하고 실행한다.


System Function for process

fork : 불러온 process의 클론을 생성한다.

exec family : 아예 부른 프로세스를 새로운 프로세스를 실행한다. 

exit : 부른 프로세스를 종료한다.

wait family :자식프로세스가 종료하기 전까지 stop한다.


fork()

카피본을 생성하고, 이를 자식 process화

-fork()의 리턴값은 parent는 자식의 PID, child는 0이다. (리턴 타입은 pid_t, signed interger)

-만약에 에러가 발생하면, fork()의 리턴값은 -1이다.

-부모 프로세스가 사용했던 리소스, 속성 등 상당 수를 물려받는다. (똑같다.)

-PID와 PPID만 다르다.

-fork() 이후엔 프로세스가 두개 진행되는 것이다.

-똑같은 코드를 진행한다.

 

fork()에서 자식프로세스에게 상속되지 않는 것

-Process ID / CPU 사용량 (자식은 0으로 리셋)

-rocks & alarm

(os가 지원하는 system call 함수 lock을 상속받지 않는다.

시간이 다되면 signal을 발생하는 timer인 alram을 상속받지 않는다.)

-pending signal

(특정 signal은 signal mask에 의하여 pending되는데, 이에 대한 정보는 상속받지 않는다.)

 

cf. 부모와 자식은 cpu를 두고 경쟁하는 관계라고 볼 수 있다.

다중 사용자 시스템에서 cpu를 많이 가지려면 더 많은 프로세스를 실행하면 된다.

 

fork()의 장단점

자식은 모든 정보를 받는데, 따라서 부모의 정보를 굳이 별도요청으로 얻을 필요가 없다.

하지만, 프로그램 코드가 부모-자식간에 똑같다. (즉, 별 의미가 없을 수 있다.)

 

ex) 

#include 
#include 
int main(void) {
    int x;
    x = 0;
    fork();
    x = 1;
    printf(“I am process %ld and my x is %d\n”, (long)getpid(), x);
}

 

이 코드의 output은

I am process 3394 and my x is 1

I am process 3395 and my x is 1

이다. 단, 순서는 바뀔 수 있다. 보통은 부모가 먼저 실행되긴 한다.

 

Run Different code

fork()함수의 리턴값이 다르다는 점을 활용한다. (조건문)

 

ex)

if (childpid == 0) /* child code */
    printf("I am child %ld\n", (long)getpid());
else /* parent code */
    printf("I am parent %ld\n", (long)getpid());

 

만약 fork()함수 위에 getpid를 변수로서 저장해버리면, 똑같이 출력된다.

왜냐하면 fork()함수는 해당 함수 밑의 코드부터 진행하기 때문이다.

 

Chain of processes

for (i = 1; i < n; i++)
    if (childpid = fork()) // '=='가 아니다. 즉, fork의 리턴값이 그대로 저장된다. 부모면 브레이크.
    break;

fprintf(stderr, "i:%d process ID:%ld parent ID:%ld child ID:%ld\n",i, (long)getpid(), (long)getppid(), (long)childpid);

 

반복문을 활용해 리턴값이 0이 아니면(부모프로세스면) break해서 print문을 출력한다.

아닐경우 다시 반복문으로 가서 fork를 생성한다.

즉, 자식이면 다시 반복문을 돌아서 하나의 자식을 또 만들고, 그 순간 부모의 입장이 된 자식은 반복문을 멈춘다.

단, 에러가 나도 -1이 되서 반복문을 멈춘다.

 

Fan of processes

위의 함수구조에서

if ((childpid = fork()) <= 0)

break 으로 할 경우

자식은 0을 리턴하여 break를 하게된다. 


wait()

부모는 자식 프로세스의 작업이 완료되었는지 확인해야한다.
– '자식의 상태가 가능해질 때까지' 또는 'caller가 signal을 수신할 때까지' caller가 실행을 일시 중단하게 된다.

– 자식이 종료될 때까지 기다리도록 해준다.
– 자식 프로세스로부터 상태 정보 수신하도록 해준다.
– exit()가 리턴하는 값을  가지도록 해준다.

 

#include <sys/wait.h>
• pid_t wait(int *stat_loc); 

exit에 의해 종료된 자식 프로세스의 PID가 wait()의 리턴값이다.

-또한 자식 프로세스의 exit를 stat_loc을 기반으로 리턴하기도 한다.

-자식이 signal을 받았을 때도 마찬가지이다.

-오류가 날경우 -1 혹은 지정된 errno를 리턴한다.


• pid_t waitpid(pid_t pid, int *stat_loc, int options);

좀 더 범용적으로 쓸 수 있다.

-특정 자식프로세스를 지정할수도 있고, 특정 그룹에 있는 자식프로세스 전부를 지정할 수도 있다.

-0이면 동일그룹의 프로세스, -1이면 임의의자식 프로세스, PID입력시 특정 프로세스

-non-blocking으로 제작가능하도록 설계되었다. (WNOHANG)
-오류가 날경우 -1 혹은 지정된 errno를 리턴한다.

cf. non-blocking? 함수가 종료되기 전에 return 처리.

 

stat_loc

함수 리턴 값 외에 알려줄 정보 : output 파라미터라고 함 (포인터로 넘긴다.)

stat_loc도 일종의 output 파라미터인데, 정상적 종료시에 wait하다가 수신받는다.

WIFEXITED(int stat_val)
• 자식 프로세스가 정상종료시 0이 아닌값을 리턴한다.
– WEXITSTATUS(int stat_val)
• 자식 프로세스가 정상종료 되었다면(WIFEXITED가 0이 아닌값 리턴), 해당 프로세스 리턴값을 8bit로 리턴한다. 
– WIFSIGNALED(int stat_val)
• 자식프로세스가 종료된 것이 uncaught signal 때문이라면, 0이 아닌값을 리턴한다. 
– WTERMSIG(int stat_val)
• uncaught signal이 발견되었다면(WIFSIGNALED가 0이 아닌값 리턴), 해당 signal의 넘버를 리턴한다. 
– WIFSTOPPED(int stat_val)
• 자식프로세스가 stop되었다면, 0이 아닌값을 리턴한다.
– WSTOPSIG(int stat_val)
• 자식프로세스가 stop되었다면(WIFSTOPPED가 0이 아닌값 리턴), 해당 stop을 야기한 signal의 넘버를 리턴한다.

 

Waiting for all children

모든 자식프로세스가 종료될때 까지 기다리려면

while(r_wait(NULL) > 0);

마지막 자식이 종료되면 wait가 할 것이 없는데 또 하면 -1을 리턴하면서 반복문이 종료된다.

r_wait? 만약 시그널 때문에 함수가 인터럽트되면 wait를 restart시키는 wait의 응용함수이다.

즉, 시그널을 피하는 wait라고 보면 된다. 이게 있어야 오류없이 작동한다.

 


exec()

fork()의 단점 : 부모와 코드 진행이 동일하다.

fork() 후에 exec()를 호출하면 기존과는 완전히 다른 new code를 load한다.

부모-자식 관계이지만 전혀 다른 진행을 하게 되는 것이다.

$cat file1 도 비슷하다고 볼 수 있다.

shell이 fork로 또다른 shell을 만들고, 거기서 exec를 실행한다음 cat을 load하는 것이다.

 

exec()에도 여러가지 함수들이 존재하는데, 여기서 각 함수들의 차이점은 arguments뿐이다.

결국엔 모든 함수는 execve가 하는 일과 똑같다.

 

exec()를 하면 현재 프로세스가 완전히 새로운 프로세스로 대체된다.

(fork() 처럼 부모 프로세스를 copy하는 것이 아니다.)

리턴값은 존재하지않는다.

text, variables, stack and heap 등 프로그램 이미지를 새로운 이미지로 덮어 씌운다.

execle나 execve를 사용하지 않는 이상, 환경변수 정보는 상속받아서 그대로 사용한다.

 

execl(const char *path(실행할 프로세스 경로), const char *arg0(실행할구문), …, const char *argn(실행할구문), (char*) 0 (종료표시));

보통 arg0는 명령어, arg1~n은 코멘트

 

int execv(const char *path(실행할 프로세스 경로), char *const argv[](실행할구문에 대한 배열));

 

각 execl혹은 execv 뒤에

p를 붙이면 : 경로 대신 이름을 주면 알아서 경로를 찾아서 실행한다.

e를 붙이면 : 환경변수 char *const envp[]를 파라미터에 추가로 받는다. 환경변수를 조절할 수 있다. (이것도 (char*) 0으로 끝내야 한다.)

 

ex)

main() {

char * const av[]={“ls”, “-l”, (char*)0}; 

execv (“/bin/ls”, av);

perror(“execl failed to run ls”);

exit(1)

}

 

execcmd : exec함수들을 활용해 넘겨주는 리눅스 명령어를 그대로 실행하도록 짠 함수


exit() : 프로세스를 종료시켜줌. 오류를 발견했는데 오류메세지 출력 후 종료시켜줄 수 있다.

atexit : 정상 종료전에 사용할 공통 함수를 등록할 수 있다.

atexit(clean_up);

if(found_error()) exit(1);

식으로 작성시, 종료시 clean_up 호출.


Interrupt character :

대부분의 시스템에서 Ctrl C 로 설정되어 있는 일종의 종료 커맨드

현재 실행중인 프로세스를 종료한다.

단, 백그라운드 프로세스에 있는 프로세스는 종료되지 않는다. (계속진행)

 

& 을 뒤에 붙히면 백드라운드로 보낼 수 있다. (ls -l &)

이를 종료하고 싶을 경우, kill -9 PID로 종료가능

 

daemon : 항상 켜져있는 백그라운드 프로세스. 일반적 task 처리를 위해 사용.

 

cf. 커맨드 인터럽터 발생 단계

명령프롬프트가 표준 입력에서 명령을 읽고 자녀에게 명령을 실행하도록 하고, 자식이 끝날때까지 기다린다.

반응형