many-to-many와 two-level models에서 communication between kernel and the thread library는 어떻게 이루어지는가?
many-to-many와 two-level models에서는 user와 kernel thread 사이에 intermediate data structure인 lightweight process 혹은 LWP를 사용한다.
user-thread library의 경우 LWP는 virtual processor이어서 user thread가 실행되는 것을 schedule할 수 있다.
각 LWP는 kernel thread에 붙어있으며, 운영체제가 physical processor에 스케줄링 하는 것은 커널 스레드이다.
만약 커널 스레드가 block되면, LWP 또한 block되며, LWP에 연결된 user-level thread 또한 block된다.
즉, LWP는 kernel thread에 연결되는 user thread를 관리 및 스케줄링 해주는 중간 다리 역할을 한다고 볼 수 있다. (정확히 LWP가 어떻게 구현되는지는 운영체제마다 다르다. -> 별개의 thread로 볼건지, kernel thread에 붙어있는 형태로 볼 건지 등)
Scheduler activation
- 커널은 application에 여러 LWP를 제공하고, application은 user threads를 available virtual processor에 스케줄링할 수 있다.
- 커널은 application에게 특정한 event에 대해서 알려줘야 하고, 그 과정을 upcall이라고 부른다.
- upcall : thread library는 upcall handler를 이용해서 upcall을 핸들하고, 이는 virtual processor에서 실행되어야 한다.
- 예시 : application thread가 block되는 경우 : kernel은 application에 특정한 thread가 block된다고 upcall하고, 새로운 virtual processor를 application에 할당 -> application은 이 새로운 virtual processor에서 upcall handler를 실행 -> blocking thread의 상태를 저장하고, blocking thread가 실행되고 있던 virtual processor를 내놓음 -> upcall handler는 이 내놓은 virtual processor에 실행가능한 다른 스레드를 스케줄함. -> blocking thread가 기다리던 event 발생시 -> kernel이 thread library에게 upcall을 보냄 -> upcall handler는 virtual processor에서 동작해야 하므로 필요시 커널이 virtual processor를 주거나 다른 thread가 사용중이던 것을 뺏아서 준다. -> unblocked thread가 실행가능하다고 표시하고, 그 스레드를 다시 스케줄링한다.
(자세한 동작은 나중에)
Linux는 process를 복제할 때 fork() system call을 사용함.
thread를 복제할 때 clone() system call을 사용함.
Linux는 process와 thread를 구분을 하지 않는다 -> 프로그램의 flow of control을 말할 때에 process나 thread 대신에 task라는 표현을 사용한다.
clone()이 불려질 때, parent와 child task 사이에 얼마나 많은 sharing이 이루어질 것인지에 대한 set of flags가 전달된다.
이 4가지 flag를 모두 전달하면 parent task와 child task가 대부분의 자원을 공유하므로 지금까지 우리가 이야기했던 thread를 만드는 경우와 동일하게 된다.
모두 unset하게 되면 fork() system call과 비슷하게 기능한다.
Linux에서는 task별로 struct task_struct가 존재한다. 이 task_struct는 직접 데이터를 저장하는 것 대신에, 저장된 데이터(e.g. open files, signal-handling information, virtual memory들의 list)를 가리키는 포인터가 저장된다.
fork()가 호출되었을 때, parent process의 모든 data structures가 copy 되어 새로운 task가 만들어진다.
반면, clone()이 호출되었을 때, 똑같이 새로운 task가 만들어지지만, 이 경우에는 clone()을 통해 넘겨받은 flag에 따라 parent task의 data structures를 point 하게 된다.
container의 경우에도 clone() system call을 사용할 수 있다. -> 나중에 다룬다네요