리눅스데브코스 [10주차 - 3]<CFS 스케줄러 실습>

심우열·2023년 6월 7일
0

1. 커널 코딩 스타일

1. 공식 문서

2. 정리

  • 8칸 탭을 사용한다.
  • 한 줄에 80열을 넘지 않는다.
  • statement는 해당 문이 시작하는 줄에서 중괄호를 연다.
  • 이름 있는 함수는 다음 줄에서 시작한다.
  • 한 문장으로 충분할 때 불필요하게 중괄호를 사용하지 않는다.
  • 조건문의 한 블록이 여러 줄일 경우는 모든 조건에 중괄호를 사용한다.
  • sizeof, typeof, alignof 및 __attribute__를 제외하고 대부분의 경우 키워드 뒤에 공백을 사용한다.
  • 포인터는 함수나 변수 이름에 인접하고 타입과 인접하지 않는다.
  • 함수는 짧고 간결해야 하며 한 가지 기능만 수행해야 한다.
    -지역 변수의 수가 5~10개를 넘지 않도록 한다.
  • EXPORT 매크로는 함수 바로 다음 줄에 작성한다.
  • 주석은 코드가 어떻게 작동하는지가 아닌 무엇을 하는지 알려주어야 한다.
  • 하나의 타입에 여러 변수를 선언하지 않도록 한다.
  • 매크로는 대문자로 지정하는 것이 좋지만, 함수와 유사한 경우 소문자로 지정할 수 있다.
  • 여러 문이 포함된 매크로는 do-while 블록으로 묶어야 한다.
  • 조건이 포함된 매크로는 사용하지 않는 것이 좋다. - 인라인 키워드를 많이 사용하면 커널이 커져 페이지 캐시에 사용할 수 있는 메모리가 줄기 때문에 시스템 전체 속도가 느려진다. 3줄 이상의 코드가 포함된 함수에 인라인을 넣지 않도록 한다.
  • 함수 이름이 액션 혹은 명령인 경우 오류 코드 정수를 반환하고 술어인 경우 성공 여부(bool)를 반환하도록 한다.
  • bool은 컴파일된 아키텍처에 따라 값의 크기와 정렬이 달라지므로 캐시 레이아웃이나 값의 크기가 중요한 경우 사용하지 않도록 한다.
  • 커널 헤더에는 적절한 매크로들이 선언되어 있으므로, 필요한 경우 가져다 사용하면 좋다.
  • 인라인 어셈블리를 사용할 수도 있으나, 가능하면 어셈블리 함수는 .S 파일에 넣고 C 프로토타입은 C 헤더 파일에 정의한다.
  • .c 파일에 전처리 조건문을 사용하면 코드를 읽기 어렵기 때문에 사용하지 않는 것이 좋다. 대신 헤더 파일에 사용하고 이를 .c 파일에서 호출하도록 한다.
  • 잠재적으로 사용되지 않을 수 있는 함수나 변수는 전처리기 조건부로 래핑하는 대신 __maybe_unused로 표시하는 것이 좋다.

2. include/linux/sched.h 자료구조분석

1. sched_entity

1. 코드

// 스레드의 스케줄링 정보 저장
struct sched_entity {
	struct load_weight load; // 스레드 cpu 사용량 추적
	struct rb_node run_node; // 스레드가 실행 대기 상태일 때 속한 런큐
	struct list_head group_node; // 스레드가 속한 스케줄링 그룹
	unsigned int on_rq; // 스레드 실행 대기 여부
	u64 exec_start; // 스레드 시작 시간(exec() 호출 이후)
	u64 sum_exec_runtime; // 스레드가 cpu를 소비한 총 시간
	u64	vruntime; // 타임 슬라이스 할당 기준. cpu 소비 시간과 시작 시간 등을 고려해 계산된다.
	u64 prev_sum_exec_runtime; // 이전 스케줄링 주기에서 스레드가 cpu를 소비한 총 시간
	u64 nr_migrations; // 스레드의 마이그레이션(스케줄러가 스레드를 다른 코어로 이동시키는 작업) 횟수
	int depth;
	struct sched_entity *parent;
	struct cfs_rq *cfs_rq;
	struct cfs_rq *my_q;
	unsigned long runnable_weight;
	struct sched_avg avg;
};

2. 분석

  • schded_entity 가 처음 생성 된 이후, depth 와 부모 sched_entity 포인터와, rq 그룹과 스스로의 cache value를 갖는 cfs_rq 구조체 생성.

2. task_struct

1. 코드

struct task_struct { // 아키텍처와 무관한 일반적인 프로세스 속성
#ifdef CONFIG_THREAD_INFO_IN_TASK
	struct thread_info thread_info; // 아키텍처에 의존적이며, 프로세스에 대한 추가 속성을 포함한다. 각종 레지스터 정보(context)를 저장하고 스택 시작 주소를 나타낸다.
#endif
	unsigned int __state;
	// ransomized_struct_fields_start
	void *stack;
	refcount_t usage;
	unsigned int flags; // PF_*
	unsigned int ptrace;
	...
	int on_rq;
	int prio;
	int static_prio;
	int normal_prio;
	unsigned int rt_priority;
	struct sched_entity se;
	struct sched_rt_entity rt; // realtime
	struct sched_dl_entity dl; // deadline(hard realtime)
	const struct sched_class *sched_class;
	...
#ifdef CONFIG_CGROUP_SCHED
	struct task_group *sched_task_group;
#endif
	...
	struct sched_info sched_info;
	struct list_head tasks; // 프로세스가 생성되면 init 프로세스의 tasks에 리스트로 연결된다.
	...
	unsigned long atomic_flags; // 아토믹 연산 접근 시 사용된다.
	struct restart_block restart_block;
	pid_t pid;
	pid_t tgid; // 스레드 그룹 id
	struct task_struct *real_parent; // 부모 프로세스가 자식 프로세스보다 먼저 소멸되는 경우 init을 부모로 설정한다.
	struct task_struct *parent;
	struct list_head children;
	struct list_head sibling;
	struct task_struct *group_leader;
	...
	struct fs_struct *fs;
	struct files_struct *files;
	...
	struct seccomp seccomp;
	struct syscall_user_dispatch syscall_dispatch;
	...
	// ransomized_struct_fields_end
	struct thread_struct thread; // 아키텍처에 의존적이며, 현재 실행 중인 스레드를 가리키는 task_struct에 대한 포인터를 지닌다. (자기 참조)
};

3. rq

1. 코드

struct rq {
	raw_spinlock_t lock;
	unsigned int nr_running; // 해당 런큐에 삽입된 모든 프로세스 개수
	...
	u64 nr_switches; // 컨텍스트 스위칭 진행 횟수
	...
	struct cfs_rq cfs;
	struct rt_rq rt;
	struct dl_rq dl;
	...
	unsigned long nr_uninterruptible; // 현재 런큐에 삽입된 프로세스 중 TASK_UNINTERRUPTIBLE 상태의 프로세스 개수. 수가 많으면 문제(race condition)가 발생하고 있을 확률이 높다.
	struct task_struct *curr; // 현재 실행 중인 프로세스
};

4. cfs_rq

1. 코드

struct cfs_rq { // 일종의 가상 런큐로, CFS 스케줄링 알고리즘에서 사용된다.
	struct load_weight load;
	unsigned int nr_running;
	unsigned int h_nr_running; // SCHED_{NORMAL,BATCH,IDLE}
	unsigned int idle_h_nr_running; // SCHED_IDLE
	u64 exec_clock;
	u64 min_vruntime;
	...
	struct rb_root_cached	tasks_timeline; // 레드-블랙 트리로 관리된다.
	struct sched_entity	*curr;
	struct sched_entity	*next;
	struct sched_entity	*last;
	struct sched_entity	*skip;
	...
};

3. 소스 디버깅

1. task_tick_fair()

  • gic_handle_irq -> timer_handelr -> scheduler_tick() 순으로 호출
  • 위의 호출 순서를 보았을때, timer interrupt 로 인해 발생한 것을 확인 할 수 있다.

2. dequeue_task_rt()

3. chech_preempt_curr_rt()

4. task_tick_rt()

profile
Dev Ops, "Git, Linux, Docker, Kubernetes, ansible, " .

0개의 댓글