[Linux_System_programming] 06 프로세스 관리

Linux System Programming (Robert Love) chapter 06 Advanced process management

유닉스는 바이너리 이미지를 메모리에 적재하는 과정에서 새로운 프로세스를 생성하는 부분을 분리함 이는 fork(), exec()라는 두 개의 시스템 콜을 필요

프로세스 스케줄링

  • 프로세서 스케줄러는 커널의 서브시스템으로 다음에 실행할 프로세서를 선택(시간 배분)
  • 멀티 태스킹 운영체제는 선점형, 비선점형으로 나눔
  • 현재(?) 리눅스 스케줄러는 CFS로 이전의 스케줄러(O(1) 등)과는 많이 다르다

타임 슬라이스

  • 스케줄러가 선점하기 전까지 프로세스에 허락된 실행 시간 (스케줄러가 프로세스에 할당)

입출력 위주 프로세스와 CPU 위주 프로세스

  • CPU위주 프로세스 : 무한 루프와 같이 사용가능한 타임 슬라이스를 끊임없이 계속 사용하는 프로세스(수학연산, 이미지 처리)
  • 입출력 위주 프로세스 : 실행시간 보다 리소스를 사용하기 위해 기다리는 시간이 더 많은 프로세스(파일 네트워크 입출력)
  • CPU 위주 프로세스는 최대한 작업을 빨리 마칠 수 있도록 큰 타임 슬라이스가 적합하며 입출력 위즈 프로세스는 반응속도를 빠르게 하기 위해 작은 타임 슬라이스가 적합

선점형 스케줄링

  • 전통적인 유닉스 프로세스 스케줄링에서 모든 실행 가능한(블록되지 않은) 프로세스는 타임 슬라이스를 할당 받음
  • 타임 슬라이스 모두 소진시 커널은 그 프로세스를 잠시 멈추고 다른 프로세스 실행

CFS 스케줄러

  • CFS전의 유닉스는 스케줄링에 우선순위와 타임슬라이스라는 변수를 사용, 최신 데스크탑/모바일 장치에서는 조금 부족한 면이 있음
  • CFS(Completely Fair Scheduler)는 타임슬라이스를 사용하지 않는 공정한 스케줄링 알고리즘 사용
    • CFS는 N개 프로세스에 각각 1/N 만큼 CPU시간을 할당(한정된 시간, 즉 타겟 레이턴시를 N으로 나눔)하고 nice값에 따라 가중치를 둠
    • 가중치가 높을 수록 더 많은 CPU시간을 할당(기본값은 0 = 가중치 1)
    • 프로세스가 많아서 할당받은 CPU시간이 매우 적을 때를 대비해 핵심 변수인 최소단위를 사용
    • 최소 단위가 효과를 발휘하면 공정성이 무너진다는 뜻이므로 평범한 상황에서는 최소단위가 적용되지 않고 타겟레이턴시 만으로 공정성을 유지
  • CFS는 CPU위주 프로세스와 입출력 위주 프로세스를 함케 스케줄링하는데서 발생하는 많은 문제를 해결

프로세서 양보하기

1
2
3
#include <sched.h>

int sched_yield(void);
  • sched_yield()를 호출하면 현재 실행 중인 프로세스를 잠시 멈춘 다음 스케줄러가 다음에 실행할 새로운 프로세스를 선택하도록 함
  • 다른 실행 가능한 프로세스가 없으면 sched_yield()를 호출한 프로세스의 실행이 즉시 재개
  • 호출 성공시 0반환, 실패 시 -1 반환 후 errno를 적절한 에러 코드로 설정

적당한 사용법

  • 선점형 멀티태스킹에서 커널은 어플리케이션보다 더 효율적으로 스케줄링 할 수 있으므로 sched_yield()는 잘 사용하지 않음
  • 외부 인터럽트를 기다려야 하는 프로그램에서 사용할 수 있지만 유닉스 프로그램은 블록가능한 파일 디스크립터에 의존하는 이벤트 드리븐 해법을 목표로 함
  • 최근까지 사용자 영역에서 스레드 락이라는 상황에서 sched_yield()가 필요했으나 최신 커널에서는 퓨텍스(Fast user-space mutex)를 사용

프로세스 우선순위

  • 프로세스에 할당되는 CPU시간은 nice값(프로세스 우선순위)에 의해 우선 순위가 할당
  • nice값은 -20~19 까지 가능(default 0), nice값이 적을 수록 우선순위가 높다(타임슬라이스가 커짐)

nice()

1
2
3
#include <unistd.h>

int nice(int inc);
  • nice() 호출이 성공하면 inc만틈 프로세스의 nice 값을 증가 시킴
  • CAP_SYS_NICE 기능이 있는 프로세스(사실상 root소유 프로세스)만 inc에 음수를 넘겨 nice값을 감소시킬수 있다
  • 에러가 발생하면 -1, 성공시 nice 값 반환(-1 반환이 가능하므로 errno를 0으로 초기화 후 검사가 필요)(에러코드 EPRM, EINVAL)
  • inc에 0을 넘겨 현재 nice 값도 알 수 있다

getpriority()와 setpriority()

1
2
3
4
5
#include <sys/time.h>
#include <sys/resource.h>

int getpriority(int which, int who);
int setpriority(int which, int who, int prio);
  • 우선순위 설정에 선호되는 방법은 좀 더 많은 제어가 가능한 getpriority(), setpriority() 시스템콜 사용
  • which는 PRIO_PROCESS, PRIO_PGRP, PRIO_USER 중 하나 일것
  • who는 프로세스 ID, 프로세스 그룹ID, 사용자ID(0이라면 현재 프로세스/프로세스그룹/사용자 ID로 해석)
  • getpriority() 호출 시 지정한 프로세스 중 가장 높은 우선순위 반환
    • 에러가 발생하면 -1, 성공시 nice 값 반환(-1 반환이 가능하므로 errno를 0으로 초기화 후 검사가 필요)
  • setpriority() 호출 시 모든 프로세스의 우선순위를 prio로 변경
    • CAP_SYS_NICE 기능이 있는 프로세스만 nice값을 감소시킬수 있다
    • 호출 성공 시 0, 실패 시 -1 반환

입출력 우선순위

  • 기본적으로 입출력 스케줄러는 입출력 우선순위를 결정하기 위해 프로세스의 nice값을 사용
  • nice값 설정 시 자동적으로 입출력 우선순위도 변경
  • 리눅스 커널은 nice값에 독립적으로 입출력 우선 순위를 명시적으로 설정하고 가져올 수 있는 추가적 시스템 콜을 제공
    • ioprio_get, ioprio_set
    • 그러나 사용자 영역 인터페이스는 제공하지 않음

프로세스 친화

  • 리눅스는 멀티 프로세서를 지원하며 스케줄러는 프로세스가 어떤 CPU에서 수행할지 결정해야함
  • 프로세스가 다른 CPU로 이전하는 것은 부대 비용이 발생해 스케줄러는 최대한 프로세스를 특정 CPUd에 유지하려 함
    • 부대비용 : CPU 이동시 이동한 CPU캐시에 접근불가, 이전 CPU 캐시의 내용을 무효로 해야 함
  • CPU 사용률 불균형 해소를 위해 언제 프로세스를 옮길지 결정하는 작업을 로드 밸렁싱이라 함
  • 프로세서 친화도는 프로세스를 꾸준히 같은 CPU에 스케줄링할 가능성을 말하며 느슨한 친화도, 엄격한 친화도가 있다.

sched_getaffinity()와 sched_setaffinity()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#define _GNU_SOURCE
#include <sched.h>

typedef struct cpu_set_t;
size_t CPU_SETSIZE;

void CPU_SET(unsigned long cpu, cpu_set_t* set);
void CPU_CLR(unsigned long cpu, cpu_set_t* set);
int CPU_ISSET(unsigned long cpu, cpu_set_t* set);
void CPU_ZERO(cpu_set_t* set);
int sched_setaffinity(pid_t pid, size_t setsize, const cpu_set_t* set);
int sched_getaffinity(pid_t pid, size_t setsize, cpu_set_t* set);
  • sched_getaffinity() 호출 시 프로세스의 pid의 CPU 친화도를 조회하여 cpu_set_t 타입 변수에 저장
    • pid가 0이면 현재 프로세스의 친화도를 조회
    • setsize 인자는 cpu_set_t타입의 크기(sizeof(cpu_set_t))를 나타내며 타입의 크기가 바뀔 시 호환성 유지 목적
    • 호출이 성공하면 0을 반환, 실패시 -1을 반환하고 errno를 설정(EFAULT, EINVAL, EPERM, ESRCH)
  • CPU_SETSIZE는 set인자의 크기가 아니라 set으로 표현할 수 있는 프로세스의 개수이며 현재 구현은 각 프로세스를 단일 비트로 표현
  • 책에 예제 있으므로 읽어보자

실시간 시스템

  • 실시간 시스템은 운영 시 지켜야 할 최소한의 응답속도가 보장되어야 함
  • 리눅스 및 최신의 운영체제는 실시간성을 몇단계로 나눠 지원

실시간 시스템의 종류

  • 실시간 시스템은 하드 실시간, 소프트 실시간으로 나뉨
  • 실시간 시스템이 반드시 빠를 필요는 없다, 사실 같은 하드웨어에서 비교 시 실시간 시스템은 실시간 프로세스를 지원하는데 따른 부하로 비 실시간 시스템 보다 느릴 수 있다.

레이턴시, 지터, 최소 응답시간

  • 레이턴시 : 명령이 내려졌을 때 실행을 시작하기까지의 시간(명령이 언제 내려졌는지 정확하게 알아야 해서 측정이 힘듬)
  • 지터 : 연속적인 이벤트 간의 시간 편차
  • 하드 실시간 시스템에서 지터가 0이고 레이턴시가 최소 응답시간과 같도록 설계
  • 소프트 실시간 시스템은 지터에 더 민감

리눅스의 실시간 지원

  • 리눅스는 IEEE표준에 정의된 시스템 콜 함수군을 통해 소프트 실시간을 지원
  • 리눅스 커널의 실시간 지원은 더 짧은 레이턴시와 일관적인 지터를 제공할 수 있도록 계속 개선됨
  • 리눅스 커널에 적용된 임베디드 시스템과 실시간 시스템을 위한 변경은 공식 메인스트림이 아님, 그러나 실시간을 위한 변경은 POSIX 인터페이스를 활용 (다음 설명할 내용은 변경된 시스템에서도 유효)

리눅스 스케줄링 정책과 우선순위

  • 프로세스와 관련된 리눅스 스케줄러의 동작 방식은 스케줄링 클래스라고 불리는 프로세스의 스케줄링 정책에 의존
  • 리눅스는 기본정책(SCHED_OTHER) 외 두가지 실시간 스케줄링 정책(SCHED_FIFO, SCHED_RR)을 제공
  • 모든 프로세스는 nice 값과 무관한 고유의 우선순위를 가지며 리눅스는 항상 가장 높은 우선순위의 프로세스를 실행

FIFO 정책

  • FIFO 스케줄링 정책은 타임 슬라이스를 필요로 하지 않는 매우 단순한 실시간 스케줄 정책
  • FIFO 스케줄링을 따르는 프로세스는 더 높은 우선순위의 프로세스가 실행가능한 상태가 되지 않는 한 계속 실행
  • FIFO 스케줄링 정책은 SCHED_FIFO로 지정
  • 기본적으로 시스템 내에서 가장 높은 우선순위를 가진다면 원하는 만큼 계속 실행이 가능
  • 스케줄링 상세 내역은 책을 참조

라운드 로빈 스케줄링 정책

  • 라운드 로빈 스케줄링 정책은 SCHED_RR 매크로로 지정
  • 스케줄러는 각 라운드 로빈 프로세스에 타임슬라이스를 배분
  • 라운드로빈 프로세스가 타임슬라이스를 다 소진하면 스케줄러는 같은 우선순위 프로세스 목록에서 다음 프로세스를 실행
  • 상기 내용을 제외하고는 FIFO 정책과 동일

표준 스케줄링 정책

  • SCHED_OTHER 매크로는 비실시간 프로세스의 기본 스케줄링 정책인 표준 스케줄링 정책
  • 모든 일반 프로세스는 고유의 우선순위로 0을 가지므로 FIFO나 RR프로세스는 실행중인 일반 프로세스를 선점 가능

배치 스케줄링 정책

  • SCHED_BATCH는 일괄 또는 유휴 스케줄링 정책(실시간 정책과 약간 반되되는 동작)
  • 이 정책을 따르는 프로세스는 다른 프로세스가 타임 슬라이스를 모두 소진했더라도 시스템에 실행 가능한 프로세스가 없을 때만 실행
  • 우선 순위가 더 높은 프로세스가 타임슬라이스를 다 소진하면 결국에 우선 순위가 가장 낮은 프로세스도 실행 가능

리눅스 스케줄링 정책 설정하기

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <sched.h>

struct sched_param {
  /* ... */
  int sched_priority;
  /* ... */
};

int sched_getscheduler(pid_t pid);
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *sp);
  • sched_getscheduler()함수는 호출이 성공하면 pid로 지정한 프로세스의 스케줄링 정책 반환(SCHED_FIFO 등)
    • pid가 0일시 호출한 프로세스 스케줄링 정책 반환
    • 호출이 실패 시 -1를 반환하고 errno를 적절한 값으로 설정
  • sched_setscheduler()를 호출하면 pid로 지정한 프로세스의 스케줄링 정책을 설정
    • 스케줄링 정책은 sp인자를 통해 설정, pid가 0이면 호출한 프로세스 스케줄링 정책을 설정
    • 호출 성공 시 0반혼, 실패시 -1 반환 및 errno를 적절한 값으로 설정(EFAULT, EINVAL, EPERM, ESRCH)
    • sched_param에서 유효한 필드는 스케줄링 정책에 따라 다르며 RR/FIFO는 sched_priority만 유효
    • SCHED_OTHER외 다른 스케줄 정책을 적용하려면 CAP-SYS_NICE가 필요(대부분 root사용자가 실행)

스케줄링 인자 설정하기

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <sched.h>

struct sched_param {
  /* ... */
  int sched_priority;
  /* ... */
};

int sched_getparam(pid_t pid, struct sched_param *sp);
int sched_setparam(pid_t pid, const struct sched_param *sp);
  • sched_getparam() 호출 시 sp인자를 통해 pid로 지정한 프로세스의 스케줄링 설정값 반환
    • pid가 0일시 호출한 프로세스 스케줄링 정책 반환
    • 호출이 실패 시 -1를 반환하고 errno를 적절한 값으로 설정
  • sched_setparam() 호출 성공시 pid로 지정한 프로세스의 스케줄링 인자는 sp에 지정한 내용으로 설정
    • 호출 성공 시 0 반환, 실패시 -1 반환 및 errno설정(EFAULT, EINVAL, EPERM, ESRCH)

유효한 우선순위 범위 확인

  • POSIX는 시스템에 어떤 우선순위 값이 존재하는지 보장하지 않음(min/max 값 사이에 적어도 32개의 우선순위가 존재해야 한다는 것만 명시)
  • 프로그램은 보통 자체 우선순위 값을 두고 이를 운영체제에 제공하는 우선순위에 맵핑하는 방법을 사용
  • 따라서 우선순위 min/max값을 확인할 수 있는 2가지 시스템 콜을 제공
1
2
3
4
#include <sched.h>

int sched_get_priority_min(int policy);
int sched_get_priority_max(int policy);
  • sched_get_priority_min/max() 시스템콜은 policy 인자에서 명시한 스케줄링 정책과 관련된 최소/최대 우선순위 반환
    • 호출 실패시 -1 반환, errno는 EINVAL

sched_rr_get_interval()

1
2
3
4
5
6
7
8
#include <sched.h>

struct timespec {
  time_t tv_sec;/* 나노 초 */
  long tv_nsec; /* 나노 초 */
};

int sched_rr_get_interval(pid_t pid, struct timespec *tp);
  • sched_rr_get_interval()은 호출이 성공하면 pid 프로세스에 할당된 타임 슬라이스의 길이를 tp 포인터가 가리키고 있는 timespec 구조체에 저장
  • 호출 성공 시 0, 실패 시 -1 반환 및 errno 설정(EFAULT, EINVAL, ESRCH)
  • POSIX 표준에 따르면 이 함수는 SCHED_RR 프로세스에 대해서만 사용해야 하지만 리눅스에서는 어떤 프로세스의 타임 슬라이스 길이도 반환

실시간 프로세스의 주의점

  • CPU를 계속 사용하는 루프가 인터럽트나 더 높은 우선순위 실시간 프로세스가 없어도 무한히 실행되지 않도록(시스템 hang 방지)
  • 실시간 프로세스는 시스템에서 가장 비싼 비용으로 실행, 시스템의 다른 부분이 CPU시간을 얻지 못하는 일이 없도록
  • busy waiting 사용 시 주의(ex. 우선순위 낮은 프로세스의 리소스를 실시간 프로세스가 대기 시)
  • 실시간 프로세스를 개발할 때 그 실시간 프로세스보다 우선순위가 높은 터미널을 하나 열어놓을 것(비상 탈출)
  • util-linux 패키지의 chrt 유틸리티는 쉽게 다른 프로세스의 실시간 속성을 가져오거나 설정할 수 있다.

결정론

  • 실시간 컴퓨팅 환경에서 결정론적 : 주어진 입력이 같다면 항상 같은 결과를 같은 시간안에 도출
  • 최신의 컴퓨터는 여러 계층에 걸친 캐시, 멀티 프로세서, 페이징 스와핑, 멀티태스킹으로 인해 결정론적이지 않음
  • 실시간 어플리케이션은 예측할 수 없는 부분과 최악의 지연을 제한하기 위해 선행폴트 데이터와 메모리락, CPU친화도를 이용

선행폴트 데이터와 메모리 락

  • 페이징과 스와핑은 실시간 프로세스를 망가트릴 수 있는 비결정적인 동작 야기
  • 선행폴트를 일으켜 스와핑된 데이터를 메모리에 올린 다음, 주소공간 내 모든 페이지를 실제 물리 메모리에 락을 걸거나 고정 배선 해버림
  • 페이지가 담겨 있는 모든 메모리를 락 걸고 나면 커널은 절대 이 페이지를 디스크로 스왑하지 않음
  • 4장에서는 데이터를 메모리에 선행 폴트하는 인터페이스(readahead)를, 9장에서는 물리 메모리에 있는 데이터를 락거는 방법 설명

CPU 친화도와 실시간 프로세스

  • 멀티태스킹에서 (비록 선점형이라도) 스케줄러는 항상 특정 프로세스를 위해 다른 프로세스를 즉시 스케줄링 할 수 없다. (다른 프로세스가 크리티컬 섹션에서 동작할 경우 등)
  • 멀티태스킹은 페이징에 관련된 예측 불가능성에 따른 비결정성을 유발
  • 만약 멀트 프로세스 시스템이라면 그 중 하나를 실시간 프로세스에만 할당하면 됨
    • 이를 간단하게 구현하는 것은 init프로그램 수정(책에 코드 설명 있음)

리소스 제한

  • 리눅스 커널은 프로세스에 대해 파일개수, 메모리 페이지, 대기중 시그널 같은 커널 리소스 제한을 도입
  • 커널은 이 제한을 초과하는 리소스 소비를 허용하지 않음(허용 수치를 초과하면 해당 호출 실패)
  • 리소스 제한을 설정할 수 있는 두가지 시스템콜을 제공(getrlimit, setrlimit)
  • 소프트제한은 프로세스 생성 시 기본으로 적용되는 한도, 하드제한은 소프트제한이 최대한 늘릴 수 있는 제한
  • 프로세스는 소프트제한값을 0~하드제한값까지 자유롭게 변경가능, CAP_SYS_RESOURCE 기능이 제한된 프로세스(non-root)는 하드제한보다 낮은 값만을 설정 가능
  • 리소스 제한의 특수 값 0: 리소스 사용 금지, -1 : 무한값

제한

  • 리눅스에는 16가지 리소스 제한이 존재 (책에 설명 있음)

기본제한

  • 프로세스에 적용 가능한 기본 제한은 최초의 소프트제한, 최초의 하드제한, 시스템 관리자라는 세가지 변수에 의존(책에 기본값 표 있음)
  • 실제제한은 일반적으로 시스템 관리자가 여러가지 제한을 설정해둔 사용자의 쉘에 의해 결정
  • 관리자는 이값을 낮출 필요가 없음.

제한 설정과 조회

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <sys/time.h>
#include <sys/resource.h>

struct rlimit {
  rlim_t rlim_curr; /* 소프트 제한 */
  rlim_t rlim_max; /* 하드 제한 */
}

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
  • getrlimit()는 resource인자가 나타내는 리소스의 하드제한과 소프트제한을 rlim포인터가 기리키고 있는 구조체에 저장
  • setrlimit()는 resource인자로 지정한 리소스의 제한을 rlim 포인터가 가리키는 값으로 설정
  • 호출이 성공시 0반환, 실패시 -1반환 및 errno를 설정 (EFAULT, EINVAL, EPERM)
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy