티끌모아 태산

Chapter 1: Linux Processes 본문

CS 지식/시스템프로그래밍

Chapter 1: Linux Processes

goldpig 2023. 12. 22. 13:16
728x90

  이번 시간에는 리눅스 프로세스에 대해서 알아보겠습니다. 우선, 운영체제에 대해서 간략하게 알아보자면 운영체제(Operating System)는 사용자와 컴퓨터를 연결시켜주는 인터페이스 역할을 합니다. 즉 컴퓨터 하드웨어 바로 윗단에 올라와있는 소프트웨어로서 사용자에게는 더 나은 인터페이스를 제공하고 컴퓨터 하드웨어 자원을 효율적으로 관리하는 역할을 합니다. 그러면 리눅스 OS안의 핵심 코드인 커널에 대한 대략적인 Outlook을 보면 다음과 같습니다. 

Dual-Mode Operation(1)

현대의 CPU는 적어도 "User Mode"와 "Kernel Mode"라는 두 가지 운영 모드를 구분하는 하드웨어 지원을 제공합니다.

  • User Mode(사용자 모드)
    • 사용자를 대신하여 실행되며(Execution done on behalf of a user) 권한이 적은 모드입니다. 
    • User programs typically execute in this mode
  • Kernel Mode(aka, supervisor mode)
    • Execution done on behalf of operating system (more-privileged mode)
    • 운영 체제의 커널은 프로세서와 모든 명령, 레지스터 및 메모리완전히 제어(complete control)합니다.

Dual-Mode Operating(2)

Frequent mode changes. *KCP(Kernel control path)은 커널 요청(system call, interrupt, exception)을 처리하기 위해 커널 모드에서 수행되는 명령어 순서.

  1. Hardware interrupt
  2. Software intertupt(exception)
  3. System call

이렇게 커널 요청이 들어오면 사용자 모드에서 커널 모드로 그리고 커널 모드에서 다시 사용자 모드로의 모드 스위칭이 발생합니다.

Running OS (Kernel mode)(1)

커널 코드(kernel code)는 일반 컴퓨터 소프트웨어와 동일한 방식으로 작동하며, CPU(프로세서)에 의해 실행되는 프로그램입니다. 커널 코드가 프로세서에 의해 다른 프로그램처럼 실행된다면, 커널 코드는 프로세스인가요? 그렇다면, 어떻게 제어되는가요? 라는 의문을 갖게 됩니다.

  • Execution within User Process
    • Virtually all OS code (i.e., kernel code) get executed within the context of a user process. 커널은 사용자 프로세스의 일부인 것처럼 보입니다. 즉, 커널 코드는 프로그램 이미지와 사실상 연결되어 있습니다.
    • 인터럽트, 시스템 호출, 트랩 발생 시 CPU는 사용자 프로세스의 컨텍스트 내에서 OS 루틴을 실행하기 위해 커널 모드로 전환합니다. 전체 프로세스 전환(full process switch)이 아니라 모드 전환(mode switch)만 수행됩니다. 이는 두 프로세스 간 전환에 필요한 오버헤드를 절약합니다.(A full process switch is not performed, just a mode switch within the same process!)
    • 전체 프로세스 전환이 필요할 때만 발생합니다. 이것은 큰 장점입니다.

Process Virtual Address: Linux (x86)

⭐️위 사진처럼 Linux에서는 linear address를 3:1로 나누어 1/4를 kernel에 할당한다. 이렇게 하는 이유와 이 방식의 단점은 무엇이 있을까요? 

더보기

  일반적으로 linear address(logical address)에서 3을 유저가, 1을 커널이 차지한다. 이렇게 커널 코드를 따로 하나의 프로세스에 할당하지 않고 유저 프로세스 영역을 나눠서 1/4만 할당하는 이유는 전체 프로세스간 스위칭(유저프로세스 4GB + 커널 프로세스 4GB)으로 인해 발생하는 오버헤드를 줄이기 위해서이다. 즉, 커널 코드를 하나의 새로운 프로세스에 할당해 4GB를 모두 사용한다면 타이머 인터럽트 등으로 인해 전체 프로세스간 스위칭이 많이 발생하게 되는데 이에 따른 오버헤드가 발생하기 때문이다. 그래서 이 오버헤드를 줄이고자 유저 Process에 영역을 나누어서 할당하는 것이다. *오버헤드, 전체 프로세스간 스위칭 > 하나의 프로세스에서 모드 스위칭.

 

  물론 4GB를 모두 사용할 수 있다는 장점이 있지만 오버헤드로 인한 단점이 더 크기 때문에 영역을 나눈다. 결국 하나의 유저 프로세스에서 모드 스위칭이 두 개의 프로세스간 스위칭으로 인해 발생하는 오버헤드보다 적게 발생한다. 간단한 예를들어 설명하면, 하나의 아파트에서 방3개를 유저에게, 나머지 하나를 커널에게 할당하고(유저 process에서 방하나를 OS의 커널에 부여) 여기서 모드 스위칭을 할것이냐 아니면 아파트 하나를 더 구입해서 방4개 전부를 커널에게 할당한 후 아파트와 아파트간의 스위칭을 할 것이냐라고 생각한다면 이해하기 쉬울 것이다. 아파트 하나만 할 경우 집에서 모드 스위칭이 발생하기 때문에 밖에서는 보이지 않고 하나의 아파트 안에서 모드 스위칭 해결 가능. 그러나 모든 것을 해결할 수는 없다.

  하지만 이 방식에도 단점이 있는데 그것은 하나의 프로세스에서 영역을 나누다 보니 커널 공간의 한계가 있을 수 있다.

Process Virtual Address: multiprocessing

What is a Process?

사용자 프로그램이 실행되기 위해서는 메모리에 올라가야합니다. 그래서 디스파일에 있던 file.exe, 실행파일을 메모리에 올려놓고 CPU를 할당받아 생명력을 얻으면 비로서 Process가 됩니다. 

  • Program in execution: an instance of a running program
  • A process include:
    • images 
      • Code: machine instructions
      • Data: variables
      • Stack: states for function calls
      • Heap: dynamic memory
    • process context
      • Program context
        • data registers
        • program counter, stack pointer
      • Kernel context
        • pid, gid, sid, environment
        • VM structures (page table)
        • Open files
        • Signal handler, ...
     

Process Image Layout(1)

Memory segment of typical C

program

  • Stack: automatic variables, temporary variables, return address, caller's environment(registers)
  • Heap: memory dynamically allocated
  • Initialized data segment
  • Uninitialized data segment: inintialized as 0 by kernel before the program starts
  • Code segment: instruction code. Usually read only

Process Image Layout(2)

Process with Multiple Threads

Multiple Threads can be associated with a process

  • Each thread has its own logical control flow
  • Each thread shares the same code, data, and kernel context
  • Each thread has its own thread ID

Process vs Thread

프로세스에 대한 전통적인 접근은 어떠한 문제로 인해서 현대적인 접근이 나왔는가? Process vs Lightweight Process(또는 스레드) vs Kernel Thread에 대해서 생각해 보겠습니다.

더보기

  전통적인 접근은 자원을 공유하지 않는 것이다. (과거 OS는 combined between resources and execution.) 자원 공유 없으면 → 코드 공유 등도 하지 않아서 이에 따른 문제점들이 발생한다. 따라서 현대적인 접근방식의 핵심은 sharing 하는 것입니다. Multiple threads share resources within a single process.

 

  리눅스의 철학은 확장성과 portability! 즉,  코드가 적게 수정되고, 확장 가능한 자료구조를 만들어서 효율성 극대화 하려는 것. 전통적인 fork()는 프로세스를 전부 카피 해버림. 프로세스는 원래 공유하지 않는다 sharing nothing, but 자원을 공유하는 프로세스가 있는데 이것을 lightweight processes(Thread) 라한다. 그리고 스레드는 코드, 데이터, 변수 등의 자원을 공유한다. sharing! clone() 라이브러리 함수는 스레드를 구현하기위한 함수이다. flags는 어떤것을 공유하고 공유하지 않을 것인지 지정해주는 것! 결국 리눅스는 스레드를 우아한 방식으로 구현한다. 왜냐하면 스레드는 단지 똑같은 커널 문맥을 공유하는 프로세스이기 때문이다. 원래 프로세스는 자원공유를 하지 않는데 리눅스가 공유하게 만들어버렸다. 클론 함수를 사용해서! → 리눅스가 멀티스레딩을 구현해버림 다시 리마인드하면 멀티 스레드는 하나의 프로세스 안에서 여러개의 스레드를 나눠서 사용함. Process that share resources are lightweight → lightweight processes

 

커널 스레드(특정 스레드) ≠ 커널 레벨 스레드(방법론) 즉, 서로 다른 개념이다. 커널의 특정기능을 수행하는 스레드를 커널 스레드라고함. ex) disk caches, swapping 커널 스레드는 Lightweight Process에 의해서 구현이 된다. 이는 커널 모드에서만 오직 수행되고 즉, 커널 방에서만 놀다가 인생 종친다. Both user processes and kernel threads are schedulable.

Threads associated with a process form a pool of peers, unlike processes which form a tree hierarchy

Nowdays, most mulithreaded applications are written using the pthread libary

Process State: General Model

As a process executes, it changes state:

  • new: The process is being created
  • ready: CPU를 얻기위해 대기중인 상태(The process is waiting to be assigned to a process)
  • running: 프로세스가 CPU를 얻어 명령어를 실행하는 상태(Instructions are being executed)
  • blocked (or waiting): The process is waiting for some event (e.g., I/O completion) to ouccr
  • exit (or terminated): The process has finishied execution.

Linux Process Descriptor

PCB(Process Control Block): Data structure for process information. 

  • 프로세스가 갖고있는 특정 자원을 가리킨다.(Refer to specific resources owned by the process), (1) Task information (2) Task Image (3) Program context
  • task_struct:"include/linux/sched.h" (ver.2.6.11.12)

Identifying a Process

  • 각각의 프로세스는 자신만의 process descriptor(i.e., task_struct)를 소유한다.
    • Even lightweight process(Thread)도 자신만의 task_struct structure를 갖는다.
    • Process descriptor pointer is useful means for the kernel to identify processes. 즉, 커널은 이러한 process descriptor pointer를 통해 프로세스를 참조합니다. 현재 실행 중인 프로세스의 descriptor pointer는 'current'라고 하며, get_current() 함수를 사용하여 얻습니다.
  • Process ID (PID)
    • 각 프로세스에게 부여되는 고유한 숫자 식별자입니다. 범위는 0부터 32767까지이며, 이는 16비트 하드웨어 플랫폼과의 호환성을 위한 것입니다.

User Stack and Kernel Stack

Dual Mode Operation

  • 사용자 모드와 커널 모드, 두 가지로 분리되어 있기때문에 각각 모드에 대한 스택도 user mode stack and kernel mode stack으로 분리되어 있습니다. 
    • Entering kernel invloves a stack-switching
    • Necessary for robustness: e.g., user-mode stack might be exhausted
    • 보안에 바람직함: 예를 들어, 불법적인 매개변수가 제공될 수 있음.(Desirable for security: e.g., Illegal parameters might be supplied)
     

  • User (Mode) Stack
    • Upon entering 'main()' - A program's exit-address is on user stack, Command-line argument on user stack, Environment variables are on user stack
  • Kernel (Mode) Stack
    • Upon entering kernel mode - Task's register are saved on kernel stack (e.g., address of task's user mode stack: SP, Stack Pointer)

Process Descriptor Handling

  • Every user process/thread has a process desciptor
  • Every user process must have two stacks: one to execute in user mode and one for kernel mode.
  • The kernel stack for a user process is stored in the kernel data segment, and stack switching (from user stack to kernel stack) takes place at mode switching

Wait Queuees

  프로세스는 종종 특정 이벤트가 발생하기를 기다려야 합니다(예: 디스크 작업의 종료, 시스템 자원의 해제, 정해진 시간 간격의 경과 등). 그리고 특정 이벤트를 기다리고 싶은 프로세스는 해당 대기 큐에 자리를 잡고 제어를 포기합니다. 대기 큐는 잠자고 있는 프로세스들의 집합을 나타내며, 특정 조건이 참이 되었을 때 커널에 의해 깨어납니다.(Wait queues represent a set of sleeping processes, which are woken up by the kernel when a certain condition becomes true) 또한 커널은 대기 큐에 있는 프로세스를 TASK_RUNNING 상태로 넣음으로써 깨움니다. 이 기능은 wake_up() macro의 familty에 의해 실현됩니다.

Processes Switching 

커널은 프로세스 switching을 허락한다(Kernel permits a process switch). 그리고 프로세스 스위칭을 리눅스에서는 task switching or context switching 이라고도 부릅니다. CPU에서 실행 중인 프로세스의 실행을 중단(Suspend)하고, 다른 일시 중단된 프로세스의 실행을 재개(Resume)합니다.

 

한 프로세스(prev)에서 다른 프로세스(next)로의 전환:

  1. prev의 사용자 모드 레지스터 값(EIP, ESP 등)을 모두 저장합니다.
  2. prev의 주소 공간에서 next의 주소 공간으로 전환합니다.
  3. prev의 커널 모드 스택에서 next의 커널 모드 스택으로 전환합니다.
  4. prev의 '하드웨어 컨텍스트'에서 next의 '하드웨어 컨텍스트'로 전환합니다.

Hardware context

  • 필요성:
    • 각 프로세스가 자신만의 주소 공간을 가질 수 있지만, 모든 프로세스(All processes)는 CPU 레지스터를 공유해야 합니다.
    • 커널은 프로세스가 일시 중단되었을 때 각 레지스터에 저장되었던 값을 다시 로드해야 합니다.
  • 하드웨어 컨텍스트:
    • 프로세스가 실행을 재개하기 전에 CPU 레지스터에 로드되어야 하는 데이터입니다.
    • 프로세스 실행에 필요한 모든 정보를 포함합니다.
    • 프로세스의 하드웨어 컨텍스트 일부는 프로세스 디스크립터(struct thread_struct thread)에 저장되며, 나머지 부분은 커널 모드 스택에 저장됩니다.
  • Hardware context for Linux
    • Each process descriptor(task_struct) includes a field called thread of type struct thread_struct, in which the kernel saves the hardware context whenever the process 'is removed' from the CPU

Performing the Process Switch

프로세스 전환은 두 가지 스탭으로 구성되어 있습니다. 1. 새로운 주소 공간을 설치하기 위해 "The Page Global Directory"를 전환합니다. 2. (a) 커널 모드 스택의 전환 (b) 하드웨어 컨택스트(struct tread_struct thread) 전환합니다.

Creating Processes and Threads (1)

  • Traditional approach: 자원와 실행을 결합했었다.
  • Modern approach: 자원과 실행을 분리시켰다. (Separate between resources and execution), Multiple theads share resources within a single process

Creating Processes and Threads (2)

  • Support multiple threads of execution within a single process. Treads share resources such as address space (code, data), file handles,... and Threads are scheduled individually
  • Processes that share reources are lightweight processes

Lightweight Processes

  • The main use of clone() is to implement threads: multiple threads of control in a program that run concurrently in a shared memory.

⭐️(regular) Process vs Lightweight Process(또는 스레드) vs Kernel Thread에 대해 다시 생각해 보겠습니다. 

더보기

  Modern OS에서 Resource 와 execution을 분리시키고 하나의 프로세스는 여러개의 쓰레드를 갖는다. 즉, 한 프로세스 내에서 여러 쓰레드가 자원을 공유 → muli-threading, *멀티스레드와 멀티 프로세스는 동시에 여러작업을 한다는 점에서 유사하지만 멀티프로세스가 더 많은 메모리와 CPU를 사용한다는 단점이 있습니다.

 

  regular 프로세스는 프로그램이 메모리에 적재되어 CPU를 할당 받아 실행되는 상태를 말합니다. 이 프로세스는 독립적인 하나의 실행 단위로 자체적인 메모리 공간과 자원을 갖습니다. 그리고 프로세스간의 통신은 비용이 높고 오버헤드가 큽니다. 경량 프로세스(lightweight process)는 프로세스 내에서 실행되는 작은 실행 단위입니다. 같은 프로세스 내의 경량 프로세스는 메모리 공간을 공유합니다. 경량 프로세스간 통신은 비용이 비교적 낮고 오버헤드가 작습니다.

 

  커널 스레드(kernel thread)는 운영 체제 커널 내에서 실행되는 스레드입니다. 각 커널 스레드는 thread에 의해 구현되며 독립적인 스케줄링 단위를 가지며, 운영 체제가 직접 관리합니다. 또한 커널 스레드는 메모리 공간을 공유하지 않으며, 각각 독립된 컨텍스트를 가집니다. 그리고 regular prcesss는 사용자 모드와 유저 모드를 상황에 따라 왔다갔다 하며 실행하지만 kernel thread는 오직 커널 모드에서만 실행합니다. 

 

요약하면, 프로세스는 독립적인 실행 환경을 가지며 무겁고 오버헤드가 크며, 경량 프로세스는 같은 프로세스 내에서 메모리를 공유하며 경량화되어 있습니다. 하지만 커널 스레드는 운영 체제 내부에서 동작하는 스레드로 프로세스와는 별개로 운영 체제가 직접 관리합니다.

Process 0

  • Process 0 (also called swpper process) is the ancestor of all processes, created during the initialization phase of Linux by the start_kernel() function. And Initialzes all the data structures needed by the kernel, enables interrupt, and creates another kernl thread (process 1: init process)

Process 1

  • Process 1(also, called the init processis executing the init() function which completes the initialization of the kernel 
  • init process never terminates, since it creates and monitors the activity of all the processes

  지금까지 리눅스의 프로세스에 대해서 알아보았습니다. 다음 시간부터는 프로세스 스케줄링에 대해서 알아보겠습니다. 

728x90

'CS 지식 > 시스템프로그래밍' 카테고리의 다른 글

Chapter 4 : Timing Measurements  (0) 2023.12.25
Chapter 3: Interrupt and Exception  (0) 2023.12.23
소개  (0) 2023.12.21