SlideShare a Scribd company logo
プロセスとコンテキストスイッチ
Version:2.6.11
byk‑onishi
概要
コードローディングを通じてLinuxKernelが扱うプロセスのデータ構造と
そのプロセスに対するコンテキストスイッチの処理を見ていく。
環境:x86(32bit)
事前知識:セグメンテーション、ページング、x86レジスタ及びアセンブ
リ
目次
プロセス
プロセスディスクリプタ
thread_infoとカーネルスタック
TSS(TaskStateSegment)
thread
コンテキストスイッチ
プロセス
プログラムの実行状態のことを指し、CPUやメモリなどの資源を割り当
て実態として動作させたもの。タスクやスレッドとも呼ばれる。
この動作実態そのものをコンテキストと呼ぶ。
プロセスディスクリプタ
プロセスの情報を保持するデータ構造のこと。
プロセスの属性やどのCPUで実行されているか、どんな事象により停止
しているか、どのアドレス空間に割り当てられているか、そのファイル
に対してアクセス権限があるのかなどを保持する。
// include/linux/sched.h
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stop
struct thread_info *thread_info;
atomic_t usage;
unsigned long flags; /* per process flags, defined below *
int prio, static_prio;
struct list_head run_list;
prio_array_t *array;
struct list_head tasks;
struct mm_struct *mm, *active_mm;
/*
* pointers to (original) parent process, youngest child, yo
* older sibling, respectively. (p->father can be replaced
* p->parent->pid)
*/
struct task_struct *real_parent; /* real parent process (wh
struct task_struct *parent; /* parent process */
/* ipc stuff */
struct thread_struct thread;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
/* namespace */
struct signal_struct *signal;
struct sigpending pending;
};
スレッド情報とカーネルスタック
カーネルがプロセスに対して割り振る8KB(2Pages)の領域で、先頭にス
レッド情報である thread_info 構造体、末尾にはカーネル用のスタッ
クを配置する。
プロセスとコンテキストスイッチ
// include/linux/sched.h
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
// include/asm-i386/thread_info.h
struct thread_info {
struct task_struct *task; /* main task st
struct exec_domain *exec_domain; /* execution do
unsigned long flags; /* low level fl
unsigned long status; /* thread-synch
__u32 cpu; /* current CPU
__s32 preempt_count; /* 0 => preempta
mm_segment_t addr_limit; /* thread addre
0-0xBFFFFFFF
0-0xFFFFFFFF
*/
struct restart_block restart_block;
unsigned long previous_esp; /* ESP of the p
of nested (I
*/
__u8 supervisor_stack[0];
};
余談➀
カレントプロセスの取得方法。
#define current get_current()
static inline struct task_struct * get_current(void)
{
return current_thread_info()->task;
}
#define THREAD_SIZE (8192)
static inline struct thread_info
              *current_thread_info(void)
{
struct thread_info *ti;
__asm__("andl %%esp,%0; "
        :"=r" (ti)
        : "0" (~(THREAD_SIZE - 1))
);
return ti;
}
TSS(TaskStateSegment)
タスク(プロセス)の状態を保持するセグメント。Linuxでは主にI/Oポート
へのアクセス権限を保持するビットマップなどを保持する。各CPUに1
つずつ保持する。
// include/asm-i386/processor.h
struct tss_struct {
unsigned short back_link,__blh;
unsigned long esp0;
unsigned short ss0,__ss0h;
unsigned long esp1;
unsigned short ss1,__ss1h; /* ss1 is used to cache MSR_I
unsigned long esp2;
unsigned short ss2,__ss2h;
unsigned long __cr3;
unsigned long eip;
unsigned long eflags;
unsigned long eax,ecx,edx,ebx;
unsigned long esp;
unsigned long ebp;
unsigned long esi;
unsigned long edi;
unsigned short es, __esh;
unsigned short cs, __csh;
unsigned short ss, __ssh;
unsigned short ds, __dsh;
unsigned short fs, __fsh;
unsigned short gs, __gsh;
unsigned short ldt, __ldth;
unsigned short trace, io_bitmap_base;
/*
* The extra 1 is there because the CPU will access an
* additional byte beyond the end of the IO permission
* bitmap. The extra byte must be all 1 bits, and must
* be within the limit.
*/
unsigned long io_bitmap[IO_BITMAP_LONGS + 1];
/*
* Cache the current maximum and the last task that used the
*/
unsigned long io_bitmap_max;
struct thread_struct *io_bitmap_owner;
/*
* pads the TSS to be cacheline-aligned (size is 0x100)
*/
unsigned long __cacheline_filler[35];
/*
* .. and then another 0x100 bytes for emergency kernel stac
*/
unsigned long stack[64];
} __attribute__((packed));
余談➁
IntelのTSSという形式規定。
TSSは元々タスク毎に存在し、その状態を保持するセグメント(これが
本来のTSSの在り方)。
TSSを用いてタスクスイッチが行われるOSでは far jmp (セグメント間
のジャンプ命令)の1命令のみでハードウェア的にタスクスイッチに必要
な処理が走る。
e.g.
jmp 0048:00000000
余談➁~続き~
余談➁~続き~
な��Linuxはハードウェア機構を用いたコンテキストスイッチを採用しな
かったのか。
一部リッチなセグメンテーション及びコンテキストスイッチ機構を
持っていないRISCCPUにも対応するためメモリアーキテクチャに
フラットアドレスベースを採用しており実質セグメントをほぼ考慮
しない設計となっている。(考察)
命令を用いてレジスタを切り替えることで値の有効性を確認するこ
とができる。
コンテキストスイッチをハードウェアで行う場合とソフトウェアで
行う場合と速度の差異は存在しないため改善の余地を残すという意
味でソフトウェア側を採用した。
スレッド
プロセス切り替え時にはハードウェアコンテキストを全て退避させる必
要があるが、Intelの設計のようにプロセス毎にTSSを持っていないた
め、Linuxではプロセスディスクリプタ内にあるthread_struct型のthread
メンバにハードウェアコンテキストを退避させる。
// include/asm-i386/processor.h
struct thread_struct {
struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES];
unsigned long esp0;
unsigned long sysenter_cs;
unsigned long eip;
unsigned long esp;
unsigned long fs;
unsigned long gs;
unsigned long debugreg[8]; /* %%db0-7 debug registers */
unsigned long cr2, trap_no, error_code;
union i387_union i387;
struct vm86_struct __user * vm86_info;
unsigned long screen_bitmap;
unsigned long v86flags, v86mask, saved_esp0;
unsigned int saved_fs, saved_gs;
unsigned long *io_bitmap_ptr;
unsigned long io_bitmap_max;
};
コンテキストスイッチ
現在実行しているプロセスを休止しレジスタなどを退避させた後、次に
実行対象であるプロセスの情報をレジスタにストアし実行を開始するこ
と。
以下の処理が必要となる。
ページテーブルの切り替え
カーネルモードスタックとハードウェアコンテキスト(レジスタの情
報)の切り替え
// kernel/sched.c
/*
* schedule() is the main scheduler function.
*/
asmlinkage void __sched schedule(void)
{
long *switch_count;
task_t *prev, *next;
runqueue_t *rq;
prio_array_t *array;
struct list_head *queue;
unsigned long long now;
unsigned long run_time;
int cpu, idx;
:
static inline
task_t * context_switch(runqueue_t *rq,
task_t *prev, task_t *next)
{
struct mm_struct *mm = next->mm;
struct mm_struct *oldmm = prev->active_mm;
if (unlikely(!mm)) {
next->active_mm = oldmm;
atomic_inc(&oldmm->mm_count);
enter_lazy_tlb(oldmm, next);
} else
switch_mm(oldmm, mm, next);
if (unlikely(!prev->mm)) {
prev->active_mm = NULL;
WARN_ON(rq->prev_mm);
rq->prev_mm = oldmm;
}
switch_to(prev, next, prev);
return prev;
}
ページテーブルの切り替え
MMU(MemoryManagementUnit)が仮想アドレスを物理アドレスに変換
するために用いられるアドレス変換用のテーブル。
PT(PageTable)のアドレスはcr3が保持する。
// include/asm-i386/mmu_context.h
static inline void switch_mm(struct mm_struct *prev,
struct mm_struct *next,
struct task_struct *tsk)
{
:
/* Re-load page tables */
load_cr3(next->pgd);
:
}
// include/asm-i386/processor.h
#define load_cr3(pgdir) 
asm volatile("movl %0,%%cr3": :"r" (__pa(pgdir)))
カーネルモードスタックとハードウェアコン
テキストの切り替え
// include/asm-i386/system.h
#define switch_to(prev,next,last) do {
unsigned long esi,edi;
asm volatile("pushfl"
"pushl %%ebp"
"movl %%esp,%0" /* save ESP */
"movl %5,%%esp" /* restore ESP */
"movl $1f,%1" /* save EIP */
"pushl %6" /* restore EIP */
"jmp __switch_to"
"1:"
"popl %%ebp"
"popfl"
:"=m" (prev->thread.esp),
"=m" (prev->thread.eip),
"=a" (last),"=S" (esi),"=D" (edi)
:"m" (next->thread.esp),
"m" (next->thread.eip),
"2" (prev), "d" (next));
} while (0)
// archi386kernelprocess.c
struct task_struct fastcall * __switch_to(struct task_struct
{
struct thread_struct *prev = &prev_p->thread,
*next = &next_p->thread;
int cpu = smp_processor_id();
struct tss_struct *tss = &per_cpu(init_tss, cpu);
:
__unlazy_fpu(prev_p);
:
if (unlikely(prev->io_bitmap_ptr ||
next->io_bitmap_ptr))
handle_io_bitmap(next, tss);
return prev_p;
}
// includeasm-i386linkage.h
#define fastcall __attribute__((regparm(3)))
まとめ
CPUめちゃくちゃ有能
遅延切り替えしゅごい
インラインアセンブリ怖い

More Related Content

プロセスとコンテキストスイッチ