?? 進程.txt
字號:
進程
進程
目 錄
進程
信號
sched.c
進程信號隊列
SMP
內核線程頁目錄的借用
代碼分析
線程
進程描述符
init進程從內核態切換到用戶態
SET_LINKS
REMOVE_LINKS
get_wchan()
sigframe的結構
rt_sigframe結構
信號隊列的結構
內核線程簡介
進程切換簡介
同步機制
進程
一 進程調度
進程的狀態([include/linux.h]):
TASK_RUNNING, it means that it is in the "Ready List"
TASK_INTERRUPTIBLE, task waiting for a signal or a resource (sleeping)
TASK_UNINTERRUPTIBLE, task waiting for a resource (sleeping), it is in same
"Wait Queue"
TASK_ZOMBIE, task child without father
TASK_STOPPED, task being debugged
______________ CPU Available ______________
| | ----------------> | |
| TASK_RUNNING | | Real Running |
|______________| <---------------- |______________|
CPU Busy
| /|\
Waiting for | | Resource
Resource | | Available
\|/ |
______________________
| |
| TASK_INTERRUPTIBLE / |
| TASK-UNINTERRUPTIBLE |
|______________________|
Main Multitasking Flow
從系統內核的角度看來,一個進程僅僅是進程控制表(process table)中的一項。進程控制表中的每一項都是一個task_struct
結構,而task_struct
結構本身是在include/linux/sched.h中定義的。在task_struct結構中存儲各種低級和高級的信息,包括從一些硬件設備的寄存器拷貝到進程的工作目錄的鏈接點。
進程控制表既是一個數組,又是一個雙向鏈表,同時又是一個樹。其物理實現是一個包括多個指針的靜態數組。此數組的長度保存在include/linux/tasks.h
定義的常量NR_TASKS中,其缺省值為128,數組中的結構則保存在系統預留的內存頁中。鏈表是由next_task
和prev_task兩個指針實現的,而樹的實現則比較復雜。
系統啟動后,內核通常作為某一個進程的代表。一個指向task_struct的全局指針變量current用來記錄正在運行的進程。變量current只能由kernel/sched.c中的進程調度改變。當系統需要查看所有的進程時,則調用for_each_task,這將比系統搜索數組的速度要快得多。
二、用戶進程和內核線程
某一個進程只能運行在用戶方式(user mode)或內核方式(kernel
mode)下。用戶程序運行在用戶方式下,而系統調用運行在內核方式下。在這兩種方式下所用的堆棧不一樣:用戶方式下用的是一般的堆棧,而內核方式下用的是固定大小的堆棧(一般為一個內存頁的大小)
盡管linux是一個宏內核系統,內核線程依然存在,以便并行地處理一些內核的“家務室”。這些任務不占用USER
memory(用戶空間),而僅僅使用KERNEL memory。和其他內核模塊一樣,它們也在高級權限(i386系統中的RING
0)下工作作。內核線程是被kernel_thread
[arch/i386/kernel/process]創建的,它又通過調用著名的clone系統調用[arch/i386/kernel/process.c]
(類似fork系統調用的所有功能都是由它最終實現):
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
long retval, d0;
__asm__ __volatile__(
"movl %%esp,%%esi\n\t"
"int $0x80\n\t" /* Linux/i386 system call */
"cmpl %%esp,%%esi\n\t" /* child or parent? */
"je 1f\n\t" /* parent - jump */
/* Load the argument into eax, and push it. That way, it does
* not matter whether the called function is compiled with
* -mregparm or not. */
"movl %4,%%eax\n\t"
"pushl %%eax\n\t"
"call *%5\n\t" /* call fn */
"movl %3,%0\n\t" /* exit */
"int $0x80\n"
"1:\t"
:"=&a" (retval), "=&S" (d0)
:"0" (__NR_clone), "i" (__NR_exit),
"r" (arg), "r" (fn),
"b" (flags | CLONE_VM)
: "memory");
return retval;
}
一旦調用,我們就有了一個新的任務(Task) (一般PID都很小, 例如2,3,等)
等待一個響應很慢的資源,例如swap或者usb事件,以便同步。下面是一些最常用的內核線程(你可以用ps x命令):
PID COMMAND
1 init
2 keventd
3 kswapd
4 kreclaimd
5 bdflush
6 kupdated
7 kacpid
67 khubd
init內核線程也是啟動以后最初的進程。 它會調用其它用戶模式的任務,(/etc/inittab)例如控制臺守護進程(daemons),
tty守護進程以及網絡守護進程(rc腳本)。
下面是一個典型的內核線程kswapd [mm/vmscan.c].
kswapd是被clone()建立的 [arch/i386/kernel/process.c]''
|do_initcalls
|kswapd_init
|kernel_thread
|syscall fork (in assembler)
·do_initcalls [init/main.c]
·kswapd_init [mm/vmscan.c]
·kernel_thread [arch/i386/kernel/process.c]
三 進程創建,運行和消失
Linux系統使用系統調用fork( )來創建一個進程,使用exit( )來結束進程。fork( )和exit(
)的源程序保存在kernel/fork.c and kernel/exit.c中。fork( )的主要任務是初始化要創建進程的數據結構,其主要的步驟有:
1)申請一個空閑的頁面來保存task_struct。
2)查找一個空的進程槽(find_empty_process( ))。
3)為kernel_stack_page申請另一個空閑的內存頁作為堆棧。
4)將父進程的LDT表拷貝給子進程。
5)復制父進程的內存映射信息。
6)管理文件描述符和鏈接點。
|sys_fork
|do_fork
|alloc_task_struct
|__get_free_pages
|p->state = TASK_UNINTERRUPTIBLE
|copy_flags
|p->pid = get_pid
|copy_files
|copy_fs
|copy_sighand
|copy_mm // should manage CopyOnWrite (I part)
|allocate_mm
|mm_init
|pgd_alloc -> get_pgd_fast
|get_pgd_slow
|dup_mmap
|copy_page_range
|ptep_set_wrprotect
|clear_bit // set page to read-only
|copy_segments // For LDT
|copy_thread
|childregs->eax = 0
|p->thread.esp = childregs // child fork returns 0
|p->thread.eip = ret_from_fork // child starts from fork exit
|retval = p->pid // parent fork returns child pid
|SET_LINKS // insertion of task into the list pointers
|nr_threads++ // Global variable
|wake_up_process(p) // Now we can wake up just created child
|return retval
·sys_fork [arch/i386/kernel/process.c]
·do_fork [kernel/fork.c]
·alloc_task_struct [include/asm/processor.c]
·__get_free_pages [mm/page_alloc.c]
·get_pid [kernel/fork.c]
·copy_files
·copy_fs
·copy_sighand
·copy_mm
·allocate_mm
·mm_init
·pgd_alloc -> get_pgd_fast [include/asm/pgalloc.h]
·get_pgd_slow
·dup_mmap [kernel/fork.c]
·copy_page_range [mm/memory.c]
·ptep_set_wrprotect [include/asm/pgtable.h]
·clear_bit [include/asm/bitops.h]
·copy_segments [arch/i386/kernel/process.c]
·copy_thread
·SET_LINKS [include/linux/sched.h]
·wake_up_process [kernel/sched.c]
撤消一個進程可能稍微復雜些,因為撤消子進程必須通知父進程。另外,使用kill( )也可以結束一個進程。sys_kill( )、sys_wait(
)和sys_exit( )都保存在文件exit.c中。
使用fork ( )創建一個進程后,程序的兩個拷貝都在運行。通常一個拷貝使用exec ( )調用另一個拷貝。系統調用exec (
)負責定位可執行文件的二進制代碼,并負責裝入和運行。Linux系統中的exec (
)通過使用linux_binfmt結構支持多種二進制格式。每種二進制格式都代表可執行代碼和鏈接庫。linux
_binfmt結構種包含兩個指針,一個指向裝入可執行代碼的函數,另一個指向裝入鏈接庫的函數。
Unix系統提供給程序員6種調用exec( ) 的方法。其中的5種是作為庫函數實現,而sys_execve(
)是由系統內核實現的。它執行一個十分簡單的任務:裝入可執行文件的文件頭,并試圖執行它。如果文件的頭兩個字節是#!
,那么它就調用在文件第一行中所指定的解釋器,否則,它將逐個嘗試注冊的二進制格式。
[目錄]
信號
struct semaphore {
atomic_t count; 進程抓取semaphore時減1
int sleepers; 抓取semaphore失敗時增1
wait_queue_head_t wait; semaphore的等待隊列
};
down(&sem) 編繹成:
movl $sem,% ecx 通過寄存器ecx向__down函數傳遞sem指針
decl sem
js 2f 如果為負值,表示semaphore已被占用,執行__down_failed過程
1:
由于出現semaphore競爭的可能性比較小,將分支代碼轉移到.text.lock段,以縮短正常的指令路徑.
.section .text.lock,"ax"
2: call __down_failed
jmp 1b
.previous
...
up(&sem) 編繹成:
movl $sem,% ecx
incl sem
jle 2f 如果小于或等于0,表示該semaphore有進程在等待,就去調用__up_wakeup
1:
.section .text.lock,"ax"
2: call __up_wakeup
jmp 1b
.previous
...
__down_failed:
pushl % eax
pushl % edx
pushl % ecx ; eax,edx,ecx是3個可用于函數參數的寄存器
call __down
popl % ecx
popl % edx
popl % eax
ret
__up_wakeup:
pushl % eax
pushl % edx
pushl % ecx
call __up
popl % ecx
popl % edx
popl % eax
ret
; semaphore.c
void __down(struct semaphore * sem)
{
struct task_struct *tsk = current;
DECLARE_WAITQUEUE(wait, tsk);
tsk->state = TASK_UNINTERRUPTIBLE;
add_wait_queue_exclusive(&sem->wait, &wait);
// 將當前進程加入到該semaphore的等待隊列中
spin_lock_irq(&semaphore_lock);
sem->sleepers++;
for (;;) {
int sleepers = sem->sleepers;
/*
* Add "everybody else" into it. They aren't
* playing, because we own the spinlock.
*/
// atomic_add_negative(int i,atomic_t *v)將i + v->counter相加,
// 結果為負返回1,否則返回0
if (!atomic_add_negative(sleepers - 1, &sem->count)) {
// 如果(sleepers - 1 + sem->count.counter)非負,則說明
// semaphore已經被釋放,可以返回
sem->sleepers = 0;
break;
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -