23.11.28 최초 작성
TASK_RUNNING
(실행 대기) : ready
상태TASK_RUNNING
(실행) : running
상태TASK_INTERRUPTIBLE
: sleep
상태TASK_UNINTERRUPTIBLE
: TASK_DEAD
: terminate
상태// /source/include/linux/sched.h
#define TASK_RUNNING 0x0000 //실행 대기 상태
#define TASK_INTERRUPTIBLE 0x0001 //sleep 상태
#define TASK_UNINTERRUPTIBLE 0x0002 //sleep 상태
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
...
//https://elixir.bootlin.com/linux/v5.15.30/source/include/linux/sched.h
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
#endif
unsigned int __state; //프로세스 상태 저장
...
}
...
/source/kernel/sched/core.c
wake_up_new_task()
: TASK_RUNNING
(실행 대기)상태로 변경
yield()
: TASK_RUNNING
(실행 대기)상태로 변경
__schedule()
: current process
()상태로 변경
current process
: cpu를 점유하면서 실행되는 프로세스(런큐 구조체의 curr
필드에 저장된 태스크 디스크럽터)/source/include/linux/wait.h
wait_event_interruptible()
: TASK_INTERRUPTIBLE
상태로 변화/source/kernel/signal.c
__arm64_sys_pause()
: TASK_INTERRUPTIBLE
상태로 변화/source/kernel/signal.c
do_sigtimedwait()
: TASK_INTERRUPTIBLE
상태로 변화Race condition
의 주요 원인 중 하나CONFIG_PREEMPTION
el1h_64_irq()
을 통해 인터럽트 핸들러 실행
인터럽트 핸들러 완료하고 thread_info
의 preempt_count
값을 검사한 뒤
2.1 0이면 3 으로 Preemptive Scheduling
진행
2.2 0이 아니면 exit_el1_irq()
호출 해 익셉션이 발생한 위치로 복귀
arm64_preempt_schedule_irq()
실행
preempt_schedule_irq()
실행
schedule(SM_PREEMPT)
실행
preempt_schedule_irq()
: __schedule()
호출해 // /source/kernel/sched/core.c
asmlinkage __visible void __sched preempt_schedule_irq(void)
{
enum ctx_state prev_state;
/* Catch callers which need to be fixed */
BUG_ON(preempt_count() || !irqs_disabled());
prev_state = exception_enter();
do {
preempt_disable();
local_irq_enable();
__schedule(SM_PREEMPT);
local_irq_disable();
sched_preempt_enable_no_resched();
} while (need_resched());
exception_exit(prev_state);
}
el0t_64_irq()
을 통해 인터럽트 핸들러 실행
인터럽트 핸들러 완료한 후 exit_to_user_mode()
를 호출
flags
값을 _TIF_NEED_RESCHED
와 & 연산을 한 뒤
3.1 true면 4 로 Preemptive Scheduling
진행
3.2 false면 __exit_to_user_mode()
호출 해 유저모드 복귀
prepare_exit_to_user_mode()
실행
do_notify_resume()
실행
schedule()
실행
el0_sync()
, el0_sync_handler()
을 통해 인터럽트 핸들러 실행
인터럽트 핸들러 완료한 후 exit_to_user_mode()
를 호출
flags
값을 _TIF_NEED_RESCHED
와 & 연산을 한 뒤
3.1 true면 4 로 Preemptive Scheduling
진행
3.2 false면 __exit_to_user_mode()
호출 해 유저모드 복귀
do_notify_resume()
실행
schedule()
실행
schedule()
함수를 호출해 자발적으로 스케줄링 요청preempt_disable()
, preempt_enable()
: thead_info
의 preempt_count
값을 조작해 (+1/-1) preemption
을 비활성화/활성화 하는 함수
preempt_disable()
필요한 경우
preempt_disable()
피해야 하는 경우
schedule()
이전 preempt_disable()
함수 호출(에러 발생)preempt_disable()
함수 호출(spinlock에서 호출)vruntime
기준으로 관리하는 레드 블랙 트리에서 vruntime
이 가장 작은 프로세스를 next 프로세스
로 설정prev 프로세스
)를 cpu에서 비우고 next 프로세스
의 정보를 그 자리에 설정함source/kernel/sched/core.c
__schedule()
: context_switch()
호출context_switch()
: switch_to()
호출 /source/include/asm-generic/switch_to.h
switch_to()
: __switch_to()
호출/source/arch/arm64/kernel/process.c
__switch_to()
: cpu_switch_to()
호출/source/arch/arm64/kernel/entry.S
cpu_switch_to()
: 실제 cpu에서 prev 프로세스
와 next 프로세스
를 교체TASK_RUNNING
)와 cpu에서 실행중인 current 프로세스를 관리하는 자료구조vruntime
에 따른 우선순위로 관리percpu
타입의 변수 (runqueues
링크)RT
, CFS
, Deadline
서브 Runqueue 관리// /source/kernel/sched/sched.h
struct rq {
/* runqueue lock: */
raw_spinlock_t __lock;
/*
* nr_running and cpu_load should be in the same cacheline because
* remote CPUs use both these fields when doing load calculation.
*/
...
unsigned int nr_running; //런큐에 존재하는 프로세스 갯수
...
u64 nr_switches; //컨텍스트 스위칭 횟수
...
struct cfs_rq cfs; //CFS 런큐
struct rt_rq rt; //rt 런큐
struct dl_rq dl; //deadline 런큐
...
unsigned int nr_uninterruptible; //TASK_UNINTERRUPTABLE 프로세스 갯수
...
struct task_struct __rcu *curr; //해당 런큐에서 현재 cpu에서 실행중인 프로세스
...
};
/source/kernel/sched/sched.h
cpu_rq()
: runqueues
변수에서 cpu 오프셋을 적용한 주소에 접근this_rq()
: runqueues
변수에서 cpu 오프셋을 적용해 주소를 얻어 옴Runqueue
에서 실행 대기 상태인 프로세스를 공정하게 실행하도록 하는 스케줄러타임 슬라이스
단위로 관리 함타임 슬라이스
: 스케줄러가 프로세스에게 부여한 실행 시간vruntime
CFS
스케줄러가 프로세스를 선택할 수 있는 기준 제공CFS
스케줄러는 가장 작은 프로세스를 next 프로세스
로 선정- `sched_switch` : 프로세스가 스케줄링
- `sched_wakeup` : 프로세스를 깨움
- `sched_stat_runtime` : 프로세스의 실행 시각, `vruntime`출력
Non-Preemptive Scheduling vchiq-slot/0-74 [001] ..... 2375.466107: schedule+0x4/0x100 <-slot_handler_func+0xe9c/0x1730
// 프로세스가 schedule()을 통해 스케줄링 요청
vchiq-slot/0-74 [001] ..... 2375.466108: <stack trace>
=> schedule+0x8/0x100
=> slot_handler_func+0xe9c/0x1730
=> kthread+0xfc/0x110
=> ret_from_fork+0x10/0x20
vchiq-slot/0-74 [001] d..2. 2375.466111: sched_stat_runtime: comm=vchiq-slot/0 pid=74 runtime=13519 [ns]
vruntime=1265661013373 [ns]
// runtime : runtime=13519 ns
// vruntime=1265661013373 ns
vchiq-slot/0-74 [001] d..2. 2375.466112: sched_switch: prev_comm=vchiq-slot/0 prev_pid=74 prev_prio=101
prev_state=S ==> next_comm=kworker/1:1 next_pid=2525 next_prio=120
Preemptive Scheduling
Xorg-594 [001] dNh.. 2375.426845: irq_handler_exit: irq=2 ret=handled
// 인터럽트 서비스 루틴 종료 후 커널 코드로 복귀 중 Preemption 발생
Xorg-594 [001] dN... 2375.426846: preempt_schedule_irq+0x4/0xb8 <-el1_interrupt+0x50/0x70
Xorg-594 [001] dN... 2375.426848: <stack trace>
=> preempt_schedule_irq+0x8/0xb8
=> el1_interrupt+0x50/0x70
=> el1h_64_irq_handler+0x18/0x28
=> el1h_64_irq+0x64/0x68
// el1h_64_irq()을 통해 인터럽트 핸들러 실행
=> kfree+0x0/0x148
=> do_writev+0x114/0x130
=> __arm64_sys_writev+0x28/0x38
=> invoke_syscall+0x4c/0x110
=> el0_svc_common.constprop.3+0x98/0x120
=> do_el0_svc+0x34/0xd0
=> el0_svc+0x30/0x88
=> el0t_64_sync_handler+0x98/0xc0
=> el0t_64_sync+0x18c/0x190
Xorg-594 [001] dN.2. 2375.426850: sched_stat_runtime: comm=Xorg pid=594 runtime=103611 [ns]
vruntime=20138844005 [ns]
Xorg-594 [001] d..2. 2375.426853: sched_switch: prev_comm=Xorg prev_pid=594 prev_prio=120 prev_state=R+
==> next_comm=v3d_render next_pid=300 next_prio=98