시스템 프로그래밍 시험 공부하면서 정리한 내용이다. 내용 갱신은 없을 예정이다.
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…
- Program Context
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
- 상세 절차
- 이전 프로세스의 실행상태(EIP, ESP, …)를 이전 프로세스의 커널 스택에 저장한다
- 주소 공간을 다음 프로세스로 변경
- kernel mode stack를 다음 프로세스의 것으로 변경
- hardware context를 다음 프로세스의 것으로 변경
- 이후의 내용에서는 3과 4 집중적으로 다움
Hardware Context
- 프로세스를 다시 실행하기전에 CPU 레지스터에 올려야 하는 데이터
- thread_struct thread와 커널 스택이 저장
- eip, esp와 같은것은 thread_struct에 저장
- thread_struct에 없는 수많은 레지스터는 커널 스택에 저장
Process Switch
- 관련 코드
schedule()
functionswitch_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 : 잡탕, 패스
- User-level threads
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
- 부모가