티스토리 뷰
6.2.4 스케줄링 요청하기, 요청 체크해서 스케줄링 시도하기
6-4 kernel/sched/core./c resched_curr
- 스케줄링이 필요한 경우 TIF_NEED_RESCHED 플래그를 설정
- _TIF_POLLING_NRFLAG : CPU가 스케줄러 인터럽트를 기다리지 않고, polling 방식으로 상태를 확인. (IDLE task 에서 주로 사용?, ARM64 는 사용하지 않음) -> 따라서 이 플래그가 켜있었으면 다른 cpu 가 알아치리도록 별도로 알림 줄 필요 없음.
6-5 arch/arm64/kernel/entry.S work_pending
- tbnz : Test bit and Branch if Nonzero.
- cbnz : Compare and branch if nonzero
// arm64 의 WORK_MASK
#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \
_TIF_NOTIFY_RESUME | _TIF_FOREIGN_FPSTATE)
work_pending:
tbnz x1, #TIF_NEED_RESCHED, work_resched // NEED_RESCHED flag 켜져있는 경우 - 스케줄 호출
// 그 외 WORK_MASK 켜져있는 경우
// 별도 처리 과정..
work_resched:
bl schedule
ret_to_user:
and x2, x1, #_TIF_WORK_MASK
cbnz x2, work_pending // WORK_MASK 걸려있으면 다시 work_pending
6-6 include/linux/sched.h need_resched
- NEED_RESCHED 비트 설정됐는지 확인 task_struct -> thread_info -> flags & TIF_NEED_RESCHED
6.2.5 스케줄링의 핵심
6-7 kernel/sched/core.c __schedule
// 부가 설명
4) Per-task and per-thread statistics
__u64 nvcsw; /* Context voluntary switch counter */
__u64 nivcsw; /* Context involuntary switch counter */
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
// @param preempt 커널이 선점됐는지 여부
// preempt = true → 강제 선점(타이머 인터럽트, 인터럽트 핸들러, 높은 우선순위 태스크 깨어남 등).
// preempt = false → 자발적 스케줄링(태스크가 직접 schedule() 호출, 락 등으로 인해 선점 방지됨).
static void __sched notrace __schedule(bool preempt)
{
if (unlikely(prev->state == TASK_DEAD)) // do_exit 에서 선점 카운트를 1증가시켜뒀어서, 예외처리로 1 감소가 필요
preempt_enable_no_resched_notrace();
switch_count = &prev->nivcsw; // 자발적이지 않은
// !preempt 커널을 선점하고 있지 않았고
// prev->state 커널이 RUNNING 상태가 아닐때
if (!preempt && prev->state) {
// pending 상태 : 스위칭 되기전 처리해야 할 시그널이 있는 경우
// prev가 원래 잠자려고 했던 상태였는데, 잠들기 전에 시그널이 도착해서 다시 깨어나도록 처리.
if (unlikely(signal_pending_state(prev->state, prev))) {
prev->state = TASK_RUNNING; // deque 하지 않음
} else {
deactivate_task(rq, prev, DEQUEUE_SLEEP); // 처리할 시그널이 없는 경우 signal 로 만들고 deque
prev->on_rq = 0;
/*
* If a worker went to sleep, notify and ask workqueue
* whether it wants to wake up a task to maintain
* concurrency.
*/
if (prev->flags & PF_WQ_WORKER) { // 다른 task 를 같은 WORKER_THREAD 라서 깨워야 하는 경우
struct task_struct *to_wakeup;
to_wakeup = wq_worker_sleeping(prev);
if (to_wakeup)
try_to_wake_up_local(to_wakeup);
}
}
switch_count = &prev->nvcsw;
}
// task_on_rq_queued { return p->on_rq == TASK_ON_RQ_QUEUED; }
if (task_on_rq_queued(prev))
update_rq_clock(rq); // queue 에서 빠지지 않은 경우 clock update
next = pick_next_task(rq, prev); // 다음 task 선택
clear_tsk_need_resched(prev); // 스케줄링 됐으니 플래그 제거
// 이후 next 와 prev 가 달랐다면 switching, 그렇지 않다면 끝
if (likely(prev != next)) {
rq = context_switch(rq, prev, next); /* unlocks the rq */
} else {
...
}
}
6-8 kernel/sched/core.c context_switch
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next)
{
struct mm_struct *mm, *oldmm;
prepare_task_switch(rq, prev, next); // arm64 next 의 on_cpu 를 1로 변경
mm = next->mm;
oldmm = prev->active_mm;
/*
* For paravirt, this is coupled with an exit in switch_to to
* combine the page table reload and the switch backend into
* one hypercall.
*/
arch_start_context_switch(prev);
if (!mm) { // mm 이 없는 경우 커널 쓰레드. 유저 공간이 없기때문에 빌려서 사용
next->active_mm = oldmm;
atomic_inc(&oldmm->mm_count);
enter_lazy_tlb(oldmm, next);
} else // mm 이 있기때문에 내껄로 변경
switch_mm(oldmm, mm, next); // 유저 공간인 mm 을 ttbr0 에 등록
if (!prev->mm) { // 이전 task 가 커널 테스크였던 경우 정리
prev->active_mm = NULL;
rq->prev_mm = oldmm; // 이후 이 필드를 사용하여 정리됨
}
/*
* Since the runqueue lock will be released by the next
* task (which is an invalid locking op but in the case
* of the scheduler it's an obvious special-case), so we
* do an early lockdep release here:
*/
lockdep_unpin_lock(&rq->lock);
spin_release(&rq->lock.dep_map, 1, _THIS_IP_);
/* Here we just switch the register state and the stack. */
// cpu 레지스터와 커널 스택을 스위칭 6-10
// 세번째 인자인 last 는 __switch_to 가 리턴한 값을 저장
// 다시 받는 이유는 여기에서 커널 스택이 변경되기 때문에 prev 의 주소를 찾기 위함
switch_to(prev, next, prev);
barrier();
return finish_task_switch(prev); // 태스크 스위칭이 끝난 후 마무리 작업 6-12
}
6-8 arch/arm64/include/asm/mmu_context.h switch_mm
- TTBR0 레지스터에 next 태스크의 유저 주소 공간 페이지 테이블 주소를 설정
- asid : Address Space ID
asid_generation이 증가하는 경우?
- ASID 개수 초과(예: 8비트면 256개 이상)
- TLB가 가득 차서 ASID 재사용이 필요할 때
- 이 경우 이전 ASID 를 사용중이었으면 잘못된 곳이므로 플러시 필요
void check_and_switch_context(struct mm_struct *mm, unsigned int cpu)
{
unsigned long flags;
u64 asid;
asid = atomic64_read(&mm->context.id);
/*
* The memory ordering here is subtle. We rely on the control
* dependency between the generation read and the update of
* active_asids to ensure that we are synchronised with a
* parallel rollover (i.e. this pairs with the smp_wmb() in
* flush_context).
*/
if (!((asid ^ atomic64_read(&asid_generation)) >> asid_bits) // asid 가 유효하고, xor 하는 이유는 하나라도 달라졌는지 확인
&& atomic64_xchg_relaxed(&per_cpu(active_asids, cpu), asid)) // 현재 cpu 의 asid 를 교체 성공한 경우
goto switch_mm_fastpath;
raw_spin_lock_irqsave(&cpu_asid_lock, flags);
/* Check that our ASID belongs to the current generation. */
asid = atomic64_read(&mm->context.id);
if ((asid ^ atomic64_read(&asid_generation)) >> asid_bits) { // 유효하지 않은 경우 (하나라도 다른 경우)
asid = new_context(mm, cpu); // 새로 지정이 필요함
atomic64_set(&mm->context.id, asid);
}
if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending)) // tlb_flush_pending 플래그가 켜져있다면 flush
local_flush_tlb_all();
atomic64_set(&per_cpu(active_asids, cpu), asid);
raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
switch_mm_fastpath:
cpu_switch_mm(mm->pgd, mm); // cpu 의 pgd 를 변경
}
switch_to, __switch_to : 태스크 스위칭 하기
include/asm-generic/switch_to.h
#define switch_to(prev, next, last) \
do { \
((last) = __switch_to((prev), (next))); \
} while (0)
next task 의 x0 에 이전 커널 스택에서의 task_struct 를 보관해서, next task 로 간 이후에도 접근이 가능하고 이후 정리할때 사용한다.
6-10 __switch_to
struct task_struct *__switch_to(struct task_struct *prev,
struct task_struct *next)
{
// 유저 테스크가 변경되더라도, 내 task 가 어디를 실행되어야 했는지 등은 커널 스택에서 관리하기 때문에, 커널스택의 레지스터들이 변경된다.
last = cpu_switch_to(prev, next);
return last;
}
6-11 arch/arm64/kernel/entry.S cpu_switch_to
DEFINE(THREAD_CPU_CONTEXT, offsetof(struct task_struct, thread.cpu_context));
ENTRY(cpu_switch_to)
mov x10, #THREAD_CPU_CONTEXT // x10 = task_struct 의 cpu_context 주소만큼
add x8, x0, x10 // x8 = prev task.cpu_context
mov x9, sp // x9 = sp
stp x19, x20, [x8], #16 // store callee-saved registers (이전 task 의 cpu_context 에 레지스터 저장
stp x21, x22, [x8], #16
stp x23, x24, [x8], #16
stp x25, x26, [x8], #16
stp x27, x28, [x8], #16
stp x29, x9, [x8], #16
str lr, [x8]
add x8, x1, x10 // x8 = current task.cpu_context
ldp x19, x20, [x8], #16 // restore callee-saved registers
ldp x21, x22, [x8], #16
ldp x23, x24, [x8], #16
ldp x25, x26, [x8], #16
ldp x27, x28, [x8], #16
ldp x29, x9, [x8], #16
ldr lr, [x8]
// sp는 현재 실행 중인 레벨의 스택 포인터
// sp_el0는 유저 모드에서의 스택 포인터
mov sp, x9 // 스택 포인터 지정
and x9, x9, #~(THREAD_SIZE - 1) // 페이지 단위로 정렬
// sp_el0 는 커널 모드에서는 실제로 유저 스택을 가리키고있지 않고, 유저모드로 돌아갈때 스택포인터가됨
// ARM 64 에서는 유저모드의 스택포인터로 설계됐으나
// 리눅스에서는 sp_el0 를 current 관리용으로 사용함
// Ref: https://www.inflearn.com/community/questions/1476439/current-%EB%A7%A4%ED%81%AC%EB%A1%9C%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%A0%EB%95%8C-sp-el0-%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0?srsltid=AfmBOoqE9l36JvnlPRnpV9iCsD_ONNMTnaEdX9gwpB22kRSRrhHZxB1B
msr sp_el0, x9
ret // 리턴값은 x0 에 저장된 prev task_struct. 이전 pc(lr) 인 x30 으로 돌아감
ENDPROC(cpu_switch_to)
6-12 kernel/sched/core.c finish_task_switch
static struct rq *finish_task_switch(struct task_struct *prev) // 이전 task 가 들어옴
__releases(rq->lock)
{
struct rq *rq = this_rq();
struct mm_struct *mm = rq->prev_mm;
long prev_state;
/*
* The previous task will have left us with a preempt_count of 2
* because it left us after:
*
* schedule()
* preempt_disable(); // 1
* __schedule()
* raw_spin_lock_irq(&rq->lock) // 2
*
* Also, see FORK_PREEMPT_COUNT.
*/
if (WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET,
"corrupted preempt_count: %s/%d/0x%x\n",
current->comm, current->pid, preempt_count()))
preempt_count_set(FORK_PREEMPT_COUNT);
rq->prev_mm = NULL;
/*
* A task struct has one reference for the use as "current".
* If a task dies, then it sets TASK_DEAD in tsk->state and calls
* schedule one last time. The schedule call will never return, and
* the scheduled task must drop that reference.
*
* We must observe prev->state before clearing prev->on_cpu (in
* finish_lock_switch), otherwise a concurrent wakeup can get prev
* running on another CPU and we could rave with its RUNNING -> DEAD
* transition, resulting in a double drop.
*/
prev_state = prev->state;
vtime_task_switch(prev); // 내 task 의 vtime 을 타이머의 이전값에서 가져와서 넣고, 내 새 task 의 vtime 은 타이머에 등록
perf_event_task_sched_in(prev, current);
finish_lock_switch(rq, prev); // prev 가 on_cpu 가 아닐때까지 기다리고, current task 가 lock 의 오너가 되도록 설정
finish_arch_post_lock_switch();
fire_sched_in_preempt_notifiers(current);
if (mm)
mmdrop(mm); // mm ref 카운트 감소, 0이라면 자료구조 해제
if (unlikely(prev_state == TASK_DEAD)) {
if (prev->sched_class->task_dead)
prev->sched_class->task_dead(prev); // 스케줄러에 콜백이 붙어있었다면 처리
/*
* Remove function-return probe instances associated with this
* task and put them back on the free list.
*/
kprobe_flush_task(prev);
put_task_struct(prev); // task 의 usage 가 없다면, task 제거 처리
}
tick_nohz_task_switch(); // Re-evaluate the need for the tick as we switch the current task. tick 이 멈춰있던 경우 다시 실행할지
return rq;
}
6.2.6 태스크 깨우기: try_to_wake_up (ttwu)
잠들어있던 task 가 깨우기에 적절한 실행 상태인지 체크하고 실행할 cpu 를 결정
6-13 kernel/sched/core.c try_to_wake_up
- 태스크가 이전 실행되던 cpu 에서 wakeup
- 새로운 cpu 에서 wakeup
static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
if (!(p->state & state)) // 깨우는 조건을 확인
goto out;
success = 1; /* we're going to change ->state */
cpu = task_cpu(p);
if (p->on_rq && ttwu_remote(p, wake_flags)) // 이미 rq 에 들어있거나 마이그레이션 진행중이라면
goto stat; // light wakeup 을 수행
#ifdef CONFIG_SMP
/*
* Ensure we load p->on_cpu _after_ p->on_rq, otherwise it would be
* possible to, falsely, observe p->on_cpu == 0.
*
* One must be running (->on_cpu == 1) in order to remove oneself
* from the runqueue.
*
* [S] ->on_cpu = 1; [L] ->on_rq
* UNLOCK rq->lock
* RMB
* LOCK rq->lock
* [S] ->on_rq = 0; [L] ->on_cpu
*
* Pairs with the full barrier implied in the UNLOCK+LOCK on rq->lock
* from the consecutive calls to schedule(); the first switching to our
* task, the second putting it to sleep.
*/
smp_rmb();
/*
* If the owning (remote) cpu is still in the middle of schedule() with
* this task as prev, wait until its done referencing the task.
*
* Pairs with the smp_store_release() in finish_lock_switch().
*
* This ensures that tasks getting woken will be fully ordered against
* their previous state and preserve Program Order.
*/
smp_cond_acquire(!p->on_cpu);
// 태스크가 runque 의 로드에 기여하는지
// = 스케줄러가 시스템 로드를 계산할 때 해당 태스크를 포함할지 여부를 나타내는 플래그
p->sched_contributes_to_load = !!task_contributes_to_load(p);
p->state = TASK_WAKING;
if (p->sched_class->task_waking)
p->sched_class->task_waking(p); // fair 스케줄링이라면, CFS 런큐의 min_vruntime 을 빼준다
cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags); // 태스크가 실행될 cpu 를 선택
if (task_cpu(p) != cpu) { // 새로운 cpu 에서 실행되어야 한다면, MIGRATION 되어야 하기때문에 플래그 설정
wake_flags |= WF_MIGRATED;
set_task_cpu(p, cpu);
}
#endif /* CONFIG_SMP */
ttwu_queue(p, cpu); // 6-15 에서 자세히, remote wakeup (?) 을 실행
stat:
if (schedstat_enabled())
ttwu_stat(p, cpu, wake_flags);
out:
raw_spin_unlock_irqrestore(&p->pi_lock, flags);
return success;
}
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250201 5.7 IDLE 쓰레드 초기화 ~ 6.2 스케줄링 (1) | 2025.02.08 |
---|---|
20250111 5.5 테스크 생성하기 ~ 5.7 idle 쓰레드 (swapper) (0) | 2025.01.18 |
20250104 5.2.2 PID 할당 ~ 5.5 테스크 생성하기 (0) | 2025.01.11 |
20241221 4.9.5 per-cpu 동적 할당 (1) | 2025.01.04 |
20241214 4.9.3 per-cpu first_chunk 구성 & 초기화 & 동적할당 (1) | 2024.12.21 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- Golang
- vrpit
- red underline
- 잘못된 빨간줄
- hole-punching
- chrome-extension
- Quest2
- 에러 위치 찾기
- ad skip
- 면접
- cockroach db
- 봄날에 스케치
- 카카오
- set value
- it's called a vrpit
- 코어 남기기
- Reciprocal n-body Collision Avoidance
- boost
- print shared_ptr class member variable
- vr핏
- shared_from_this
- 클래스 맴버 변수 출력하기
- Obstacle Avoidance
- Visual Studio
- mysql
- 우리는 vr핏이라고 부릅니다
- SuffixArray
- RVO
- 영상 픽셀화 하기
- C++
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
글 보관함