關於核心執行緒(kernel_thread)(轉)

BSDLite發表於2007-08-12
關於核心執行緒(kernel_thread)(轉)[@more@]我們知道Linux核心使用核心執行緒來將核心分成幾個功能模組,
像kswapd,kflushd等,系統中的init程式也是由idle程式呼叫
kernel_thread()來實現產生的.

我們先來看看核心執行緒的實現,再來分析核心執行緒的性質.
int kernel_thread(int(*fn)(void*arg),void *arg,int flags)
{
long retval,d0; /* 至少是兩個區域性變數 */

__asm__ __volitate__(
"movl %%esp,%%esint"
"int $0x80nt"
"cmpl %%esp,%%esint"
"je 1f nt"
"movl %4,%%eaxnt"
"pushl %%eaxnt"
"call *%5nt"
"movl %3,%0nt"
"int $0x80nt"
"1:t"
:"=&a"(retval),"=&S"(d0)
:"0"(__NR_clone),"i"(__NR_exit),
"r"(arg),"r"(fn),
"b"(flags | CLONE_VM)
:"memory"
);

return retval;
}

這段程式碼翻譯成直觀的ASM碼:
{
movl __NR_clone,%0; /* 將clone的系統呼叫號載入reg */
movl __NR_exit,%3; /* 將exit的系統呼叫號載入reg */
movl arg,%4; /* 將函式fn的引數載入reg */
movl fn,%5; /* 將函式fn的指標載入reg */
movl flags|CLONE_VM,%ebx; /* 將flags移入%ebx中 */
mov %%esp,%%esi; /* 將暫存器%esp儲存在%esi中 */
int $0x80; /* 由於%eax中是clone的系統呼叫號,所以
sys_clone會被呼叫,同時又由於系統呼叫門
會把所有的暫存器壓棧,所以子程式會在相應
的暫存器中獲取flags,fn,__NR_exit等,系統呼叫
返回後,子程式幾乎繼承了父程式的一切,但是由
我們對do_fork的分析可知,子程式將獲取新的核心棧,
棧上就是各暫存器的內容,同時修改TSS使:
EIP=ret_from_fork,
ESP=新的核心棧底-sizeof(pt_regs),
SSO=__KERNEL_DS,
ESP0=新的核心棧頂,
(??)修改棧上的OLDESP=新的核心棧底

子程式恢復執行後,載入eip,esp,當RESTORE_ALL執行後
(pops,iret),暫存器被恢復了,同時我們知道當前的ESP
與ESI已是不同了.
*/

cmpl %%esp,%%esi;
je 1f; /* %esp,%esi相同,則是父程式 */
movl %4,%%eax; /* 將引數載入EAX,這樣不管fn是否使用-mregparam屬性
參看GCC manual for more information
*/
pushl %%eax /* 將引數壓棧 */
call *%5; /* 呼叫fn */
movl %3,%0;
int $0x80; /* 系統呼叫exit退出 */
1: movl %%eax,retval /* 將子程式的pid付給retval(系統呼叫的返回值在%eax中) */
movl %%esi,d0 /* ?? */
}

它的偽C碼為:
int kernel_thread()
{
pid=clone(flags);
if(child)
{
fn(arg);
exit(0);
}
return pid;
}

從上面的程式碼可以看出,核心執行緒有以下性質:
1.
核心執行緒是透過系統呼叫clone()來實現的,使用CLONE_VM標誌(使用者還可以
提供其他標誌,CLONE_PID,CLONE_FS,CLONE_FILES等),因此核心執行緒與呼叫
的程式(current)具有相同的程式空間.

2.
由於呼叫程式是在核心裡呼叫kernel_thread(),因此當系統呼叫返回時,子程式也處於
核心態中,而子程式隨後呼叫fn,當fn退出時,子程式呼叫exit()退出,所以子程式是在
核心態執行的.

3.
由於核心執行緒是在核心態執行的,因此核心執行緒可以訪問核心中資料,呼叫核心函式.
執行過程中不能被搶佔等等.

請注意在kernel_thread是如何呼叫系統呼叫的,我們知道kernel_thread是在核心中
呼叫,所以他是可以直接呼叫系統呼叫的,像sys_open()等,但是在這裡kernel_thread
透過系統呼叫門(int$80)來間接呼叫clone()函式,就提出以下問題:
1.為什麼這樣?
2.如果我們直接呼叫sys_clone()會有什麼樣的結果呢?

int kernel_thread()
{
int pid;
pid=sys_clone();
if(!pid)
{
/* child */
exit();
}
return pid;
}


這樣,當子程式獲取CPU資源時(執行時),從ret_from_fork恢復執行,棧佈局對於子程式而言
是不對的,問題在於當子程式執行到RESTORE_ALL的IRET,仔細想一想棧佈局的變化.

由sys_clone()的申明可知呼叫sys_clone需要pt_regs的棧結構,如果我們直接呼叫sys_clone
是沒用辦法做到的(如果可以我們也需要精心為它準備棧,//:-(,真是傷神)
同理,其他的類似系統呼叫,我們也必須透過int$80的系統呼叫門來實現.
而對於sys_execl,sys_open,sys_close,sys_exit,則可以直接呼叫.//xixi,我們可以
改動kernel_thread來測試sys_exit是否可以直接呼叫,同時也可以使用sys_clone的直接呼叫
來證明我們的分析是否正確.

而如果我們使用系統呼叫門(int$80)來解決問題,我們使用同樣的方法來分析:
A2)
ebx ecx
...
oldeip oldcs
eflags
d0 retval
fn arg
clone_flags
eip ..

由於kernel_thread在核心的程式碼段中,所以沒有發生棧切換,所有的壓棧/退棧都是在
核心棧中進行的.請注意這樣棧中便沒有(OLDSS,OLDESP),所以在kernel_thread宣告瞭
兩個區域性引數(retval,d0),對於retval的意義是明顯的,而d0大概是(dummy local
variable
0,...n)的意思吧,:)


B2)子程式執行前:
子程式的TSS,棧佈局

ebx ecx
...
oldeip
oldcs
eflags
d0 retval

執行到RESTORE_ALL時,將恢復CPU各暫存器,當執行到IRET時,
由於在相同特權等級的轉移,所以沒有發生特權級切換,所以ESP,SS沒有發生變化.

BTW,由上面的分析可知,kernel_thread建立的程式是不能轉到使用者態執行的。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617542/viewspace-949652/,如需轉載,請註明出處,否則將追究法律責任。

相關文章