Linux Processes
2014년 2학기 시스템 프로그래밍 시험 공부

시스템 프로그래밍 시험 공부하면서 정리한 내용이다. 내용 갱신은 없을 예정이다.

Linux Processes

Processor Execution Modes

  • Dual Mode Operation

User Mode

  • Level 3
    • 권한 제한되어있음
    • 일반적인 프로그램

Kernel Mode

  • Level 0
    • 커널의 모든 영역 접근 가능
  • 진입방법
    • system call
    • interrupt
    • exeception

Execution Within User Processes

  • 커널은 유저 프로세스에 붙어서 작동한다
  • Mode Switch = user mode <-> kernel mode
  • 0~3GB : User Address Space
  • 3~4GB : Kernel Address Space
  • User Address Space/Kernel Address Space 이동이 Mode switch
  • 실행 소유권은 유저한테 있다. 커널은 유저측을 빌려서 사용하는 형태
  • Mode switch overhead는 낮다

Process(프로세스)

  • Process
    • an instance of a running program

프로세스의 구조

  • Images
    • Code : 기계어(.text)
    • Data : 변수(.data, .bss)
    • Execution Stack : Stack + Heap
  • Process Context
    • Program Context
      • data registers
      • program counter(PC)
      • stack pointer(SP)
    • Kernel Context
      • pid, gid, fs, etc…

Process 메모리 구조

  • .text : program text, code segment, read-only
  • .data : initialized data,
  • .bss : uninitialized data, 프로그래 실행 전에 커널이 0으로 초기화
  • stack : 위에서 밑으로 내려감
  • heap : 밑에서 위로 올라감
  • library : stack과 heap 사이에 적절히 위치

Process / Thread 개요

  • Process의 구성 요소
    • Process Context
    • code, data, stack
  • Thread의 구성 요소
    • Thead Context(쓰레드 별로)
    • Kernel Context(공유)
    • code, data, stack

Process Context = Program Context + Kernel Context 라는걸 기억하자. 이를 쪼개서 program context(data register, SP, PC)의 이름을 Thread Context로 바꿨을 뿐이다.

Process State

General Model

  • New
    • -> Ready
  • Ready
    • -> Dispatch -> Running : Scheduler
  • Running
    • -> Timeout -> Ready
    • -> Event Wait -> Blocked
    • -> Release -> Exit
  • Blocked(Waiting)
    • 예를 들면 I/O 대기
    • -> Event Occurs -> Ready
  • Exit

Linux

  • New State가 존재하지 않는다
  • Ready, Running은 둘다 TASK_RUNNING.
  • Blocked의 종류가 세분화
    • TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE
    • TASK_STOPPED
  • 종료는 EXIT_ZOMBIE 를 거쳐서 EXIT_DEAD로

Process Descriptor

  • PCB(Process Control Block)
  • task_struct
    • thread_struct : Program Context
    • mm_struct : code, data, stack

Process 구분

  • 각각의 프로세스는 고유한 process description를 가짐
  • PID = Process ID, 프로세스의 고유한 값
  • 0~32767, 근데 커널 컴파일에 따라 다를 수 있음

User Stack / Kernel Stack

  • Dual mode operation => Stack도 2개
    • user stack, user mode stack
    • kernel stack, kernel model stack
  • Mode Switch 발생할때 Stack도 바뀐다
  • 스택을 2개로 분리하는 이유
    • user stack만 존재하면 꽉차서 커널 함수 실행 못할수도 있다
    • 커널 보안

User Stack

  • 프로세스마다 가진다
  • 높은 주소에서 낮은 주소로
  • 내용물
    • 프로그램 종료후의 리턴 주소
    • 프로그램 실행 인자
    • 환경 변수
    • call stack(함수 인자 + 리턴 주소)
    • 임시 변수

Kernel Stack

  • 커널 모드 진입하기 전에 커널 스택에 실행 상태를 저장한다
    • 실행상태 : uesr mode stack(SP)같은거
  • 내용물
    • 함수 인자 + 리턴 주소
    • 임시 변수
    • 존재할수 있는것은 user stack와 거의 동일

struct thread_info

  • size = 8Kb(페이지 2개 크기)
  • 포로세스마다 하나씩 할당
  • union을 이용해서 thread_info와 kernel stack을 동시에 사용

Process List

All Process List

  • task_struct에 존재하는 tasks로 모든 process description은 연결된다. 이중 링크드 리스트
  • init_task == process 0, swapper

pidhash Table

  • 0~2047의 해시 테이블
  • 모든 process description가 연결된 링크드 리스트에서 원하는 pid의 process description을 얻는 것은 느리기 때문에 해시테이블 사용한다.
  • hash collision => chaining

Process 관계도

  • 서로 밀접한 관계가 있는 프로세스도 따로 연결시킨다.
  • parent
  • children
  • sibling
  • group_leader, thread group leader, …

State

  • TASK_RUNNING
    • 실행 상태의 프로세스는 runqueue를 이용해서 연결된다.
    • 스케줄링할때 접근속도를 빠르게 하는것이 목적
    • 자세한 것은 scheduling에서 다룬다
    • task_struct의 run_list를 이용해서도 연결
  • TASK_STOPPED, TASK_ZOMBIE
    • 특별한 리스트로 연결하지 않는다
  • TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE
    • 여러 자료구조에 연결됨
    • waiting queue에 연결

Waiting Queue

  • sleeping processes로 구성됨
  • 특정 조건이 갖춰지면 작업을 깨운다
    • 배타적 프로세스, 적절히 하나씩 커널이 깨운다
    • 비배타적 프로세스, 이벤트 들어오면 동시에 깨우기

Process Switching

  • 발생 조건
    • 프로세스가 스스로 잠들었다
    • 프로세스가 종료됨
    • 프로세스가 시스템콜을 끝내고 유저모드로 돌아오는데 프로세스를 돌릴수 있는 상태가 적절한 아니다.
    • 프로세스가 인터럽트 처리를 끝내고 유저모드로 돌아왔는데 우선순위 문제로 실행자격이 없을때
      • time quantum을 전부 썼다
  • 간단한 과정
    • context switch를 할지, 현재 허용되어있는지를 확인
    • 옛날 프로세스의 실행상태를 저장
    • 스케줄링 알고리즘에 따라 스케줄러가 적절히 다음 프로세스를 선택
    • 선택된 프로세스의 실행상태 복구
  • 용어. 전부 같은 뜻
    • task switching
    • context switching
    • process switching
  • 상세 절차
    1. 이전 프로세스의 실행상태(EIP, ESP, …)를 이전 프로세스의 커널 스택에 저장한다
    2. 주소 공간을 다음 프로세스로 변경
    3. kernel mode stack를 다음 프로세스의 것으로 변경
    4. hardware context를 다음 프로세스의 것으로 변경
  • 이후의 내용에서는 3과 4 집중적으로 다움

Hardware Context

  • 프로세스를 다시 실행하기전에 CPU 레지스터에 올려야 하는 데이터
  • thread_struct thread와 커널 스택이 저장
    • eip, esp와 같은것은 thread_struct에 저장
    • thread_struct에 없는 수많은 레지스터는 커널 스택에 저장

Process Switch

  • 관련 코드
    • schedule() function
    • switch_to() macro
    • __switch_to() macro
  • switch_to macro 개요
    • CPU 상태를 이전 커널 스택에 저장
    • esp를 새로운 프로세스의 것으로 교체
    • eip를 새로운 프로세스의 것으로 교체
    • 새로운 프로레스 준비 완료
  • switch_to macro 상세
    • flags, ebp와 같은 CPU 상태를 prev 커널 스택에 저장
    • esp를 prev 프로세스 thread_struct의 esp에 저장
    • next 프로세스 thread_struct에 있는 esp를 esp로
    • L1을 prev 프로세스 thread_struct의 eip에 저장
    • next 프로세스 thread_struct에 있는 eip를 스택으로
    • __switch_to macro. 나머지 hardware context를 저장/교체
    • L1 : 스택에서 ebp, flags를 빼서 CPU로 저장

Process/Thread

  • 접근법
    • {one|multiple} Process + {one|multiple} Thread
    • 4가지 조합이 가능
  • 전통적인 접근법 : multiple process, one thread
    • 각각의 프로세스는 공유되는 것이 없다.
    • 그래서 중복되는게 많다. 비효율적
  • Thread 구현방법
    • User-level threads
      • 한 thread에서 block걸리면 모든 thread가 blocked
    • Kernel-level threads
    • combined thread : 잡탕, 패스

Lightweight Process

  • 리눅스에서 Kernel-level Thread를 구현한 방법
  • 자원을 공유하는 process = Lightweight Process
  • Thread Group, 첫번째 프로세스가 thread group leader
  • 모든 쓰레드는 고유 pid를 갖는다. 모두 독립된 프로세스처럼 취급
  • TGID(Thread Group ID)가 같으면 한 프로세스에 딸린 쓰레드처럼 취급

clone()

  • clone()를 이용해서 fork(), vfork() 구현
  • flags : parent/chlid간의 공유 리소스 결정. 아무것도 공유하지 않으면 fork
  • COW(copy-on-write)
    • 쓰기할때 진짜로 복사한다. 읽기일때는 page 복사는 없다
    • 복사 overhead 제거
    • fork했다고 프로세스 전체를 복사할 필요 없다
    • Page단위로 이루어짐
    • fork후에는 child 먼저 실행됨. child는 자신의 user stack에 쓰기를 할테니 fork직후에 필요한 페이지가 복사가 이루어질 것이다. 이것은 scheduling에서 다시 나옴
  • vfork(), fork() -> clone() -> do_fork()

do_fork()

  • 자식용 PID 생성
  • copy_process()를 이용해서 process descriptor 복사
    • thread_info, kernel stack 생성
    • 최대 process 갯수 확인(생성 가능한 상태인지 확인)
    • descriptor, page table등등 복사
    • state = TASK_RUNNING
    • 부모 관계 설정
    • thread group 관계 설정
  • runqueue에 자식을 추가
  • (CLONE_VFORK일 경우) 부로를 wait queue에 넣음(vfork가 fork와 다른점 : block)
  • 자식 PID를 반환하고 함수 종료

Kernel Thread

  • kernel-level threads와는 다른다, kernel-level threads와는!
  • 커널의 일부 함수(디스크 캐시 flush, swapping,…)를 kernel thread로 취급
  • 커널 쓰레드를 lightweight process의 일종으로 구현
    • process descriptor, pid를 가진다
    • 스케줄링 가능하다. 프로세스처럼 취급
    • kernel mode에서만 작동한다
      • user mode address space가 없다
      • 3~4GB 영역의 메모리만 존재
    • 다른 커널 쓰레드와 kernel address space 공유
    • kernel data struct(ex: open file descriptor)도 공유

Process 0 == Swapper

  • start_kernel()로 생성됨
  • 모든 커널 자료 구조 초기화
  • 인터럽트 활성화
  • process 1 실행(이것도 커널 쓰레드)
  • process 1 생성후 cpu_idle() 호출하고 대기
  • TASK_RUNNING 상태의 프로세스가 없으면 스케줄러가 process 0 선택
  • 각각의 CPU마다 process 0 존재

Process 1 == init

  • 커널 완전히 초기화
  • 다른 커널 쓰레드 생성(ex: ksoftirqd)
  • 절대 죽지 않는다. 모든 프로세스 감시

프로세스 끝내기

  • 프로세스 끝내는 방법
    • 일반적인 방법 : exit()
    • 일반적인 방법 아님 : 시그널 보냄, SIGKILL
  • 프로세스 종료 : do_exit()
    • 커널 자료구조 제거. __exit_mm(), __exit_fs(), …
    • 부모관계 갱신
    • state = TASK_ZOMBIE
    • schedule() 호출, 새로운 프로세스를 선택해서 실행
  • 프로세스 제거
    • 부모가 wait()을 호출해서 종료값 읽기 전까지 지우지 않는다. 부모가 exit code를 읽으면 제거 가능
    • release_task() : 좀비 프로세스의 PCB 해제
    • 8KB 메모리 회수 : thread_info + kernel stack

comments powered by Disqus