/////
Search
Duplicate
🤔

Philosophers

philosophers.ko.pdf
1862.0KB
궁금한점
왜 Trap을 동기적, Interrupt를 비동기적이라는 표현을 썼을까
User Mode의 프로세스는 대체적으로 사용자에 의해 구동된 어플리케이션이라고 했는데, 그 얘기는 어플리케이션 프로세스를 생성하는 거 자체는 Kernel Mode에서 이루어지고 그렇게 생선된 어플리케이션 상에서 일어나는 일들이 User mode로 처리된다는건가?
철학자 문제 해결 개선안
/* ** i 번째 철학자가 식사를 할 준비가 되었는지 확인한다. ** 양 옆의 철학자가 식사를 하고 있지 않아 젓가락을 모두 이용할 수 있다면, 철학자가 take_chopsticks에서 wait하지 않도록 signal을 보낸다. ** 주어진 조건을 만족하는 경우에는 philo[i]의 값이 1이므로 take_chopsticks에서 Block되지 않고 EATING 할 수 있다. */ check(int i) { if (state[i] == THINKING && state[LEFT] != EATING && state[RIGHT] != EATING) { state[i] = EATING; signal(philo[i]); } } /* ** mutex를 통해 i 번째 철학자의 상태를 변경한다. ** check를 통해 양쪽 철학자의 상태를 확인한다. ** i 번째 철학자는 자신이 식사를 할 수 있을 때까지 기다린다. */ take_chopsticks(int i) { wait(mutex); state[i] = THINKING; check(i); signal(mutex); wait(philo[i]); } /* ** mutex를 통해 i 번째 철학자의 상태를 변경한다. ** check를 통해 왼쪽 철학자와 오른쪽 철학자의 양 옆을 확인한다. ** 둘 중 식사가 가능한 철학자에게 check 내부에서 signal을 보낸다. */ put_chopsticks(int i) { wait(mutex); state[i] = SLEEPING; check(LEFT); check(RIGHT); signal(mutex); } /* ** Solution */ do { ... THINKING ... take_chopsticks(i) ... EATING ... put_chopsticks(i); ... SLEEPING ... } while (1);
C
복사
현재 상황
철학자1(sleeping) 철학자2(Eating) 철학자3 철학자4(sleeping) 철학자5(sleeping)
철학자3가 식사할 수 있는지에 대한 판단과정을 확인
과정
1.
철학자3가 take_chopsticks함수로 들어감
take_chopsticks(int i) { wait(mutex); state[철학자3] = THINKING; check(철학자3); signal(mutex); wait(philo[철학자3]); }
C
복사
wait(mutex)
철학자3 말고 다른 철학자에 대한 상태 접근 불가
state[철학자3] = THINKING;
밥을 먹기 전에 THINKING
check(철학자3)
check(int i) { if (state[철학자3] == THINKING && state[철학자2] != EATING && state[철학자4] != EATING) { state[i] = EATING; signal(philo[i]); } }
C
복사
철학자2가 Eating이기 때문에 조건문 안은 활성화되지 않고 넘어감
signal(mutex)
철학자3가 식사를 할 수 있기 위해서는 철학자2가 식사가 끝이 나야하기 때문에 다른 철학자들의 상태접근을 허용해줌
wait(philo[철학자3])
철학자2가 식사를 마칠때까지 기다린다
2.
철학자2가 put_chopsticks함수로 들어감
put_chopsticks(int i) { wait(mutex); state[철학자2] = SLEEPING; check(철학자1); check(철학자3); signal(mutex); }
C
복사
wait(mutex)
다른 철학자 상태에 접근하지 못하게 guard
state[철학자2] = SLEEPING;
철학자2의 상태를 sleeping
check(철학자1)
철학자1이 sleeping이기 때문에 그냥 넘어감
check(철학자3)
check(int i) { if (state[철학자3] == THINKING && state[철학자2] != EATING && state[철학자4] != EATING) { state[i] = EATING; signal(philo[i]); } }
C
복사
이렇게 다시 철학자3의 상태를 확인하게 되고 철학자2가 Eating에서 sleeping이 되었기 때문에 이제 철학자3는 식사를 할 수 있게 된다

context switch

하나의 코어에는 하나의 작업만 존재
모든 프로세스를 처리하기 위해선 하나의 프로세스를 처리하고 그 다음 프로세스가 처리하는 식의 프로세스 간의 전환
Kernel의 Dispatcher라는 곳에서 일어나며 PCB라는 자료구조가 필요
Time Quantum이 모두 소진되거나, Interrupt에 의해 발생
Time Quantum은 프로세스가 한 번에 처리될 수 있는 시간 총량을 의미한다. 일반적으로 프로세스에게 할당되는 Time Quantum은 사용자가 체감하지 못할 정도로 작다. 덕분에 한 번에 하나의 프로세스 밖에 처리하지 못하는 상황임에도 사용자는 모든 프로세스가 동시에 처리되는 것처럼 느끼게 된다.
작업하던 내용을 PCB에 저장하고 작업하고 싶은 내용을 PCB로 부터 읽어들인다
Context Switch는 Interrupt에 의해 발생하지만 프로세스의 기록 자체는 시스템 콜에 기반하는 것을 알 수 있다.
Context Switch 자체는 위에서 언급된 것처럼 Dispatcher에 의해서 처리되는데, 이는 곧 Dispatcher의 호출로부터 시작된다. Dispatcher의 호출은 Interrupt에 의해 발생한다. Dispatcher를 호출하는 Interrupt는 Preemptive Scheduling과 Non-Preemptive Scheduling으로 나뉜다. Time Quantum을 모두 소진하여 운영체제 권한으로 프로세스의 권한을 뺏으면서 발생하는 Interrupt가 Preemptive Scheduling이고, I/O 호출과 같이 프로세스가 스스로 CPU 점유를 포기하면서 발생하는 Interrupt가 Non-Preemptive Scheduling이다.
Preemptive / Non-preemptive
비선점형 스케줄링
어떤 프로세스가 CPU를 할당 받으면 그 프로세스가 종료되거나 IO request가 발생하여 자발적으로 대기 상태로 들어갈 때까지 계속 실행
어떤 프로세스가 작업을 마치고 자발적으로 대기 상태로 들어가거나 종료되는 경우 다른 프로세스가 실행
선점형 스케줄링
어떤 프로세스가 실행되다 time slice를 모두 사용해 time-out되거나, IO가 발생하거나, event를 기다려야 하는 상황이라면 다른 프로세스에게 CPU 사용을 양보
프로세스를 쫒아 내고 CPU자원을 선점할 수 있다는 뜻
현재 OS는 대부분 시분할 선점형 스케쥴링을 사용. 비선점형은 해당 작업이 끝날 때 까지 계속 실행되기 때문에 멀티 프로세스 환경에서 응답성을 기대할 수 없다

User mode & kernel mode

프로세스에서 instruction을 수행할때 usermode 와 kernelmode에 따라서 cpu 실행이 달라짐
User Mode의 프로세스는 대체적으로 사용자에 의해 구동된 어플리케이션이며, Instruction에 대해 범용적인 권한을 갖고는 있지만 Kernel Mode 만큼의 권한을 갖지는 못한다.

trap & interrupt

1.
trap
동기적 이벤트 처리
현재 처리하고 있는 프로그램에 의해 발생
주로 시스템콜 처리
trap handler에 의해 처리
trap이 발생하게 되면 그 녀석이 다 처리되고 나야 다른 프로그램을 처리하기 때문에 context를 저장할 필요도 없고 sp와 pc만을 이용하여 기존에 수행하던 동작으로 복귀 가능
2.
interrupt
비동기적 이벤트 처리
네트워크 패킷 도착 혹은 I/O에 대한 이벤트 처리
하드웨어에 의해 발생. 하드웨어의 우선 순위에 따라 우선 순위 결정
하드웨어들이 동시에 interrupt를 걸어도 처리 가능
interrupt handler에 의해 처리
interrupt를 처리한 뒤 복귀해야 하는 프로세스가 현재 프로세스가 아닐 수 있기 때문에 context에 대한 기록이 요구됨
3.
특징
Trap의 경우에는 Trap Service Routine 내에서 처리 도중에 Interrupt를 받을 수도 있지만, Interrupt의 경우에는 Interrupt Service Routine 내에서 처리 도중에 Inetrrupt를 받을 수 없게 되어 있다. 이는 Interrupt의 Depth가 깊어짐에 따라 하나의 Interrupt가 끝나는데 긴 시간이 요구되는 구조가 될 수 있기 때문이다. 따라서 Interrupt를 처리할 때 다른 Interrupt들도 처리할 수 있도록 Interrupt Serivce Routine은 최대한 짧게 수행되도록 설계 되어 있다.
Trap은 프로세스의 Context를 저장하지 않아도 된다는 면에서 Interrupt보다 가볍지만 Trap에 대한 처리가 완료될 때까지 Block된다는 특징이 있다. Interrupt는 Trap의 반대의 특징을 갖고 있다고 보면 된다.
만일 Interrupt가 동시에 발생하면 우선 순위에 따라 처리한다고 했는데, 동일한 우선 순위를 가진 Interrupt를 받게 되면 운영체제와 하드웨어에 의해 하나의 Interrupt를 처리하고 나머지 Interrupt는 저장해두거나 무시하게 된다.

개발일지

입력값

./philo number_of_philosophers time_to_die time_to_eat time_to_sleep {number_of_times_each_philosopher_must_eat}
Shell
복사
number_of_philosophers: 철학자의 수와 포크의 수이다.
time_to_die: 밀리 초 단위이며, 만약 철학자가 마지막 식사를 시작하거나 시뮬레이션을 시작한 후 'time_to_die' miliseconds를 먹기 시작하지 않는다면 죽는다.
time_to_eat: 밀리 초 단위이며, 철학자가 식사하는 데 걸리는 시간이다. 그 시간 동안 그는 포크 두개를 유 지해야 할 것이다.
time_to_sleep: 밀리 초 단위이며, 철학자가 잠을 자는 데 쓰는 시간이다.
number_of_times_each_philosopher_must_eat: 논쟁은 선택사항이며, 만약 모든 철학자들이 적어도 'number_of_times_each_philosopher_must_eat'을 먹는다면, 시뮬레이션은 중단될 것이다. 구체적으로 명시하지 않으면, 시뮬레이션은 철학자가 사망 할 때만 중단될 것입니다.

Pseudo code

인자값 구조체에 초기화() { 인자값 atoi() 철학자 수만큼 mutex생성() } 인자값구조체를 철학자 구조체에 초기화() { int main() { 인자개수 유효성체크() 인자값 구조체에 초기화() 인자값구조체를 철학자 구조체에 초기화()
C
복사

mutex 걸어야 하는 이유

#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> pthread_mutex_t m; void *t_function(void *data) { pid_t pid; // process id pthread_t tid; // thread id pid = getpid(); tid = pthread_self(); char* thread_name = (char*)data; int i = 0; pthread_mutex_lock(&pm); while (i < 5) // 0,1,2 까지만 loop 돌립니다. { // 넘겨받은 쓰레드 이름과 // 현재 process id 와 thread id 를 함께 출력 printf("[%s] pid:%u, tid:%x --- %d\n", thread_name, (unsigned int)pid, (unsigned int)tid, i); i++; sleep(1); // 1초간 대기 } pthread_mutex_unlock(&m); } int main() { pthread_t p_thread[2]; int thr_id; int status; char p1[] = "thread_1"; char p2[] = "thread_2"; char pM[] = "thread_m"; sleep(1); pthread_mutex_init(&m, NULL); thr_id = pthread_create(&p_thread[0], NULL, t_function, (void *)p1); if (thr_id < 0) { perror("thread create error : "); exit(0); } thr_id = pthread_create(&p_thread[1], NULL, t_function, (void *)p2); if (thr_id < 0) { perror("thread create error : "); exit(0); } while(1) ; pthread_join(p_thread[0], (void **)&status); pthread_join(p_thread[1], (void **)&status); printf("언제 종료 될까요?\n"); return (0); }
C
복사
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> pthread_mutex_t m; void *t_function(void *data) { pid_t pid; // process id pthread_t tid; // thread id pid = getpid(); tid = pthread_self(); char* thread_name = (char*)data; int i = 0; // pthread_mutex_lock(&m); while (i < 5) // 0,1,2 까지만 loop 돌립니다. { // 넘겨받은 쓰레드 이름과 // 현재 process id 와 thread id 를 함께 출력 printf("[%s] pid:%u, tid:%x --- %d\n", thread_name, (unsigned int)pid, (unsigned int)tid, i); i++; sleep(1); // 1초간 대기 } // pthread_mutex_unlock(&m); } int main() { pthread_t p_thread[2]; int thr_id; int status; char p1[] = "thread_1"; char p2[] = "thread_2"; char pM[] = "thread_m"; sleep(1); pthread_mutex_init(&m, NULL); thr_id = pthread_create(&p_thread[0], NULL, t_function, (void *)p1); //pthread_detach(p_thread[0]); if (thr_id < 0) { perror("thread create error : "); exit(0); } thr_id = pthread_create(&p_thread[1], NULL, t_function, (void *)p2); //pthread_detach(p_thread[1]); if (thr_id < 0) { perror("thread create error : "); exit(0); } while(1) ; pthread_join(p_thread[0], (void **)&status); pthread_join(p_thread[1], (void **)&status); printf("언제 종료 될까요?\n"); return (0); }
C
복사
mutex를 걸어주면 thread1이 먼저 다 돌고 thread2가 도는 것을 알 수 있다

쓰레드란?

pthread_detach와 pthread_join의 차이

둘다 함수가 끝이나면 자원해제를 해줌. thread시작을 했으면 자원해제를 무조건 해줘야함
join
join은 그 해당 쓰레드가 끝날때까지 대기. 리턴값을 받아옴
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> // 쓰레드 함수 // 1초를 기다린후 (매개변수*매개변수)를 리턴합니다 void *t_function(void *data) { int num = *((int *)data); printf("num %d\n", num); sleep(1); num *= num; printf("쓰레드 함수 종료 합니다\n"); return (void *)(num); // warning 발생 } int main() { pthread_t p_thread; int thr_id; int result; int a = 200; thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a); if (thr_id < 0) { perror("thread create error : "); exit(0); } // 쓰레드 식별자 p_thread 가 종료되길 기다렸다가 // 종료후에 리턴값을 받아옵니다. //pthread_join(p_thread, (void *)&result); printf("thread join : %d\n", result); printf("main() 종료\n"); return 0; }
C
복사
pthread_join x
pthread_join o
detach는 메인 쓰레드에서 분리되어 메인 쓰레드가 끝나면 종료. 이것을 막기 위해 적절한 mutex lock 필요. join과 다르게 바로 다음 명령이 실행