context_switch() 手続き

何故か分かりませんがここ起点にもくもくしてみることに。

mm メンバの話

Linux カーネル2.6 解読室の 13.6.4 空間の切り替えという節にこのあたりの話が出てます。基本的な部分は linaro な 2.6.35 でも変更は無い模様。
以下の処理に関する解説になってます。

	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 (likely(!mm)) {
		next->active_mm = oldmm;
		atomic_inc(&oldmm->mm_count);
		enter_lazy_tlb(oldmm, next);
	} else
		switch_mm(oldmm, mm, next);

	if (likely(!prev->mm)) {
		prev->active_mm = NULL;
		rq->prev_mm = oldmm;
	}

mm はこれから実行しようとしているプロセス (next) の mm メンバが格納されて、oldmm には今から切り替えられるプロセス (prev) の active_mm メンバが格納されてます。
mm および active_mm な属性の意味も記述されていて、一般プロセスの場合

  • mm は自空間
  • active_mm は常に mm と等しい

で、カーネルスレッドの場合

  • mm は常に NULL
    • カーネルスレッドはプロセス空間を持たないため
  • active_mm は借用している mm_struct (実行中のみ意味あり)
    • 直前に動いていたプロセスの空間を借用

とのこと。
なので上記に引用している手続きの記述だと

  • mm が NULL の場合 (カーネルスレッド)
    • 今から切り替えられるプロセスの active_mm を借用
    • atomic_inc 手続き呼び出しは TLB のフラッシュはしなくてよい、という印とのこと
  • mm が NULL でない場合 (一般プロセス)
    • switch_mm 呼び出し

witch_mm は確認必要。あと最後の条件分岐はカーネルスレッドの active_mm 属性を NULL に戻す処理なようです。

アーキテクチャ固有の処理

に手を付ける前に細かいソレ達の整理を。以下のソレ達については

static inline void
context_switch(struct rq *rq, struct task_struct *prev,
	       struct task_struct *next)
{
	struct mm_struct *mm, *oldmm;

	prepare_task_switch(rq, prev, next);
	trace_sched_switch(prev, next);
  • prepare_task_switch() 手続きは kernel/sched.c で定義されてます
    • context_switch のための準備と理解
    • finish_task_switch() 手続きとセットな模様
  • trace_sched_switch() 手続きは Tracepoints に関する手続きと理解
    • 何故か include/trace/events/sched.h で諸々の定義な記述がある

また、arch_start_context_switch() という手続きですが、find|xargs grep してみたところでは、ARM では何もしない、という理解で良いのだろうか。良いことにしときます。

$ find|xargs grep arch_start_context_switch
arch/x86/include/asm/paravirt.h:static inline void arch_start_context_switch(struct task_struct *prev)
kernel/sched.c:   arch_start_context_switch(prev);
include/asm-generic/pgtable.h:#define arch_start_context_switch(prev)     do {} while (0)
$

で、肝心の switch_mm および switch_to ですが、それぞれ

  • switch_mm はプロセス空間の切り替え (一般プロセスのみ)
  • switch_to は各種レジスタの切り替え

との記述があります。ちなみに switch_mm については proc-* 毎で実装が異なる模様。例えば以下。

./mm/proc-arm720.S:ENTRY(cpu_arm720_switch_mm)
./mm/proc-arm7tdmi.S:ENTRY(cpu_arm7tdmi_switch_mm)
./mm/proc-arm6_7.S:ENTRY(cpu_arm6_switch_mm)
./mm/proc-arm6_7.S:ENTRY(cpu_arm7_switch_mm)

ヘッダを grep した所によればこんなカンジの出力

./include/asm/system.h: * switch_mm() may do a full cache flush over the context switch,
./include/asm/cpu-multi32.h:    void (*switch_mm)(unsigned long pgd_phys, struct mm_struct *mm);
./include/asm/cpu-multi32.h:#define cpu_do_switch_mm(pgd,mm)    processor.switch_mm(pgd,mm)
./include/asm/cpu-single.h:#define cpu_do_switch_mm             __cpu_fn(CPU_NAME,_switch_mm)
./include/asm/cpu-single.h:extern void cpu_do_switch_mm(unsigned long pgd_phys, struct mm_struct *mm);
./include/asm/mmu_context.h:switch_mm(struct mm_struct *prev, struct mm_struct *next,
./include/asm/mmu_context.h:            cpu_switch_mm(next->pgd, next);
./include/asm/mmu_context.h:#define activate_mm(prev,next)      switch_mm(prev, next, NULL)
./include/asm/proc-fns.h:#define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm)

ええと、switch_mm という手続きは mmy_context.h というヘッダで定義されてて手続きの本体として cpu_switch_mm という手続きを呼び出している模様。この cpu_switch_mm は proc-fns.h という手続き、というかマクロで定義が以下。

#ifdef CONFIG_MMU

#define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm)

memory management unit な CONFIG が有効になってないと駄目なのかなぁ。とりあえず cpu_do_switch_mm の wrapper になってます。
で、spu_do_switch_mm というソレですが定義が二箇所。

./include/asm/cpu-multi32.h:#define cpu_do_switch_mm(pgd,mm)    processor.switch_mm(pgd,mm)
./include/asm/cpu-single.h:#define cpu_do_switch_mm             __cpu_fn(CPU_NAME,_switch_mm)

こいつら include しとるのは proc-fns.h らしい。

#ifndef MULTI_CPU
#include <asm/cpu-single.h>
#else
#include <asm/cpu-multi32.h>
#endif

single で掘ってみると __cpu_fn というソレは cpu-single.h で定義されているマクロで以下。

cpu-single.h:18:#define __cpu_fn(name,x)	__catify_fn(name,x)

おそらく名前を concat してるんだろうな。

./include/asm/cpu-single.h:#define __catify_fn(name,x)  name##x

なので名前がアレでも解決できる、というナニ。で、今回のコンパイル対象を確認したところ、mm/proc-arm926.S になる模様。

/*
 * cpu_arm926_switch_mm(pgd)
 *
 * Set the translation base pointer to be as described by pgd.
 *
 * pgd: new page tables
 */
	.align	5
ENTRY(cpu_arm926_switch_mm)
#ifdef CONFIG_MMU
	mov	ip, #0
#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
	mcr	p15, 0, ip, c7, c6, 0		@ invalidate D cache
#else
@ && 'Clean & Invalidate whole DCache'
1:	mrc	p15, 0, r15, c7, c14, 3 	@ test,clean,invalidate
	bne	1b
#endif
	mcr	p15, 0, ip, c7, c5, 0		@ invalidate I cache
	mcr	p15, 0, ip, c7, c10, 4		@ drain WB
	mcr	p15, 0, r0, c2, c0, 0		@ load page table pointer
	mcr	p15, 0, ip, c8, c7, 0		@ invalidate I & D TLBs
#endif
	mov	pc, lr

これ、ARM なマニュアル見ながら別途確認します。今日はもう限界ッス。

switch_to

ちなみに __switch_to な手続きは arch/arm/kernel/entry-armv.S にて定義されている模様。