2009年6月14日 星期日

[Linux] 基本的Timer介紹

Linux提供了兩種基本的Timer機制可以使用:
1.alarm
2.setitimer

● alarm
#include
unsigned int alarm(unsigned int seconds);
這是一個簡單的設定Timer介面。當呼叫了alarm( n )後,等待n秒後,就會觸發一次的SIGALRM的signal,所以必須要在呼叫alarm前,先設好SIGALRM的handler function才行。而當乎呼alarm(0)時,則表示停止當前的timer處理,不要發出SIGALRM的signal。

Return value : 返回上一次呼叫alarm的剩餘秒數。
若未設定alarm,則返回0。

● setitimer

#include
#define ITIMER_REAL 0
#define ITIMER_VIRTUAL 1
#define ITIMER_PROF 2
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value,struct itimerval *ovalue);

setitimer與getitimer提供了三種類別的Timer使用:

ITIMER_REAL : 以系統真實的時間來計算,觸發時會送出SIGALRM。

ITIMER_VIRTUAL : 只計算process真正在執行的時間(在User Mode的處理),觸發時會送出SIGVTALRM。

ITIMER_PROF : 計算該process在User Mode與Kernel Mode的處理時間,觸發時送出SIGPROF。

透過第一個參數which指定要使用哪一種Timer (ITIMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF )。setitimer是用來設定該種Timer的觸發時間為多少。getitimer則是取得上一次Timer設定的時間。設定的內容是一個系統內建的struct itimerval

setitimer由第二個參數value設定觸發的時間。第三個參數ovalue用來取得上一次 setitimer設定的itimerval值(此參數可以為NULL)。
值得注意的是,根據itimerval裡變數的意義,當it_interval設定為0時,Timer只會觸發一次。而it_value設定為0時,代表Timer結束。
Return value : 如果成功則return 0,失敗則return -1。

Example : 第一次等待1秒後觸發Timer,之後每隔2秒觸發一次


#include
#include using namespace std;
void my_alarm_handler(int a)
{
cerr<<"test "<}
int main()
{
struct itimerval t;
t.it_interval.tv_usec = 0;
t.it_interval.tv_sec = 2;
t.it_value.tv_usec = 0;
t.it_value.tv_sec = 1;
if( setitimer( ITIMER_REAL, &t, NULL) <> }
signal( SIGALRM, my_alarm_handler );
while(1)
{
sleep(2);
}
return 0;}

● 根據以上,可知Linux內建的Timer還是有點簡陋,而且setitimer同一時間只能處理3個Timer,如果應用程式需要多個Timer的話,這個Linux內建的Timer可能就不敷需求了!

deadlock

1. Deadlock: System中存在一些processes互相等待彼此所擁有的resources,即形成circular waiting,造成所有processes皆無法繼續執行(infinite blocking),使得CPU utilization與system throughput大幅降低。
2. Process使用resources的三個步驟
􀁺 Request: 可透過wait() semaphore實作
􀁺 Use
􀁺 Release: 可透過signal() semaphore實作
3. Deadlock v.s. Starvation
􀁺 Starvation只限於單一(或某些/少數) process因長期拿不到resource而形成自身停滯的現象,但其他processes仍可正常執行;且CPU utilization與system throughput未必會降低。
􀁺 兩者皆為不公平的resource allocation的結果
􀁺 兩者通常在preemptive環境下較顯著
4. 形成deadlock的四個必要條件(缺一不可) (deadlock ⇒ 4 conditions)
􀁺 Mutual Exclusion: 至少有一個resource是non-sharable的狀態
􀁺 Hold and Wait: 某個process必定hold住部份的resources、且等待其他processes所hold住的resources
􀁺 No Preemption: 某些resources是non-preemptible;processes不能搶奪其他processes的resources,必須等到其他processes自動release這些resources,才能使用這些resources。
􀁺 Circular Wait: 存在一組processes {P0, P1, ..., Pn},其中P0在等P1、P1在等P2、…、Pn在等P0。
􀁺 “Circular Wait” implies “Hold and Wait”.
5. Resource Allocation Graph, G = (V, E)
􀁺 Vertex set V = {process P, resource types R}
􀁺 Edge set E = {request edge (Pi → Rj), assignment edge (Ri → Pj)}
􀁺 Graph中,若沒有存在cycle,則不會產生deadlock。
􀁺 Graph中有cycle,未必造成deadlock
􀂕 每個resource type為multiple instances,則未必有deadlock
􀂕 每個reosource type皆是signle instance,則cycle ⇔ deadlock1

Process

工作程序-Process

什麼是 Process?簡單的說就是在系統中執行的一些程式。不論是因為系統運行過程所啟動的程式,或是使用者在登入後自行執行的程式、或是常駐執行、或是短暫執行,這些程式在系統中執行時,都會佔用系統的資源(CPU、記憶體等),此時系統就會替程式安排一個程序(Process),以便控管資源的使用!

因此,在系統中執行或運作的程式,我們可以稱之為 Process。誠如上述所言,當程式執行時會產生一個 Process,系統則在此時會配給每個 Process 一個辨識碼,這個辨識碼就是 Process ID 簡稱 PID。而分配的 Process ID 是不會重複的,當程式結束或終止後,PID 就會消失系統也會拋棄不再使用,等到下次重新啟動系統後再從頭計數。有些程序(Process)在執行時,會呼叫或啟用其它的程序,這些被呼叫的程序可稱為子程序,而最先被執行的程序就被當成父程序(Parent Process)。

子程序也有一個 PID,同時也會註記父程序的 PID,稱之為 PPID。整個系統最初的父程序是 init 這支程式,不論程序呼叫了幾層的子程序,往前推算最終還是到了 init 這個程式所產生的程序,而 init 程序的 PID 為 0。

內核診斷工具 SystemTap

內核診斷工具 SystemTap
在 SystemTap 出現之前,對于 Linux 程序員或者系統管理員而言,調試內核往往是一場噩夢。
例如,你懷疑傳遞給系統調用 read 的參數 fd 出了問題,想把它打印出來,你需要做的是︰
首先得到一份內核源碼,找到 sys_read() 的函數體中插入 printk() 語句,接下來重新編譯內核,然后用新的內核重新啟動系統。
謝天謝地,你總算看到了你想要看到的東西,不過你馬上會發現遇到了一個新的麻煩︰
除非重新啟動系 統到原來的內核,printk() 會無休止地打印下去。
SystemTap 的目的就是要把人們從這種泥潭中解救出來。
SystemTap 提供了一個簡單的命令行界面和強大的腳本語言,同時預定義了豐富的腳本庫。
基于內核中的 kprobe,SystemTap允許你自由地從營運中的內核無害地收集調試訊息和性能數據,來用于之后的分析和處理。
你可以隨時開始或者停止這種收集過程,而無需漫長的修改代碼、編譯內核和重啟系統的悲慘循環。
SystemTap 使得上面的問題變得簡單了,簡單得只需要一條命令就可以做到︰
stap -e 'probe syscall.read { printf("fd = %d\n",fd) }'
SystemTap的功能和Sun的DTrace和IBM的dprobe工具相似。
但是和它們不同的是, SystemTap 是遵循GPL的開源軟體項目。
它的出現使得Linux社群也擁有了功能強大而且易于使用的動態調試內核工具。
目前,SystemTap 的主要開發成員來自于RedHat、IBM、Intel 和 Hitachi。
安裝SystemTap
  在安裝SystemTap之前,需要確保系統中已經安裝了其它兩個套裝軟件︰
kernel-debuginfo RPM︰SystemTap需要透過內核調試訊息來定位內核函數和變量的位置。
對於通常的發行版,并沒有安裝 kernel-debuginfo RPM,我們可以到發行版的下載站下載。
對于 Fedora Core 6,這個位址是︰ http://download.fedora.redhat.com/pub/fedora/linux/core/6/i386/debug/
編譯 kernel
例如 linux-2.6.18.5
make menuconfig 需選 CONFIG_KPROBES, CONFIG_DEBUG_INFO, CONFIG_RELAY, CONFIG_DEBUG_FS
make; make modules_install; make install
cd /usr/lib/debug/lib/modules/
ln -s /lib/modules/2.6.18.5/

cd 2.6.18.5
ln -s /usr/src/kernels/linux-2.6.18.5/vmlinux
elfutils RPM︰SystemTap需要elfutils套裝軟件提供的庫函數來分析調試訊息。
目前的SystemTap要求安裝elfutils-0.123以上版本。
目前最新的版本是0.124-0.1。
如果需要,我們可以從SystemTap的站點下載RPM或者源碼來升級。
下載位址是︰ ftp://sources.redhat.com/pub/SystemTap/elfutils/i386/
  接下來就可以安裝SystemTap了,這有透過RPM或者源碼安裝兩種模式︰
  1. 透過RPM安裝 Fedora Core 6缺省情況下已經安裝了systemtap。
如果沒有,也可以從如下的位址下載︰ http://download.fedora.redhat.com/pub/fedora/linux/core/updates/testing/6/i386/SystemTap-0.5.10-1.fc6.i386.rpm
  2.透過源碼安裝︰
  從SystemTap的FTP站點下載最新的源碼
  然后安裝如下︰
  /root > tar -jxf SystemTap-20061104.tar.bz2
  /root > cd src
  /root/src> ./configure
  /root/src> make
  /root/src> make install

  運行SystemTap
  營運SystemTap首先需要root權限。
  營運SystemTap有三種形式︰
  1. 從文件(通常以.stp作為文件名后綴)中讀入並營運腳本︰stap [選項] 文件名
  2. 從標準輸入中讀入並營運腳本︰ stap [選項] -
  3. 營運命令行中的腳本︰stap [選項] -e 腳本
  4. 直接營運腳本文件(需要可執行屬性並且第一行加上#!/usr/bin/stap)︰./腳本文件名使用"Ctrl+C"中止SystemTap的營運。
systemtap的選項還在不斷的擴展和更新中,其中最常用的選項包括︰
  -v -- 打印中間訊息
  -p NUM -- 營運完Pass Num后停止(缺省是營運到Pass 5)
  -k -- 營運結束后保留臨時文件不刪除
  -b -- 使用RelayFS文件系統來將數據從內核空間傳輸到用戶空間
  -M -- 僅當使用-b選項時有效,營運結束時不合併每個CPU的單獨數據文件
  -o FILE -- 輸出到文件,而不是輸出到標準輸出
  -c CMD -- 啟動探測后,營運CMD命令,直到命令結束后退出
  -g -- 採用guru模式,允許腳本中嵌入C語句
  其它更多選項請參看stap的手冊。
SystemTap的語法
我們利用一個簡單的systemtap腳本來介紹一下SystemTap的語法︰
#!/usr/local/bin/stap
global count
function report(stat) {
printf("stat=%d\n", stat)
}
probe kernel.function("sys_read") {
++count
}
probe end {
report()
}
探測點(probe)︰每 個systemtap腳本中至少需要定義一個探測點,也就是指定了在內核的什么位置進行探測。
探測點名稱后面緊跟的一組大括號內定義了每次內核營運到該探測點時需要營運的操作,這些操作完成后再返回探測點,繼續下面的指令。
這裡給出了systemtap目前支持的所有探測點類型。
全局變量(global)︰用來定義全局變量。
單個探測點函數體中使用的局部變量不需要預先定義,但是如果一個變量需要在多個探測點函數體中使用,則需要定義為全局變量。
函數(function)︰用來定義探測點函數體中需要用到的函數。
除了可以用腳本語言定義函數以外,還可以用C語言來定義函數,只是這時函數名后面的大括號對需要換成%{ %}。
例如,前面的report()函數可以寫成︰
  function report(stat) %{
  _stp_printf("stat=%d\n", THIS->stat);
 %}

SystemTap的例子
  了解了SystemTap的基本用法,下面讓我們來看幾個有趣的例子。
  統計當前系統中調用最多的前10個系統調用
  在進行性能分析的時候,我們常常需要知道那些函數調用次數最多,才能有的放矢地展開分析。
下面這個簡單的例子可以打印出在過去的5秒鐘裡調用次數最多的那些系統調用。
#!/usr/bin/env stap
#
# display the top 10 syscalls called in last 5 seconds
#
global syscalls
function print_top () {
cnt=0
log ("SYSCALL\t\t\t\tCOUNT")
foreach ([name] in syscalls-) {
printf("%-20s\t\t%5d\n",name, syscalls[name])
if (cnt++ == 10)
break
}
printf("--------------------------------------\n")
delete syscalls
}
probe syscall.* {
syscalls[probefunc()]++
}
probe timer.ms(5000) {
print_top ()
}
看誰在動我的文件
有時候,我們如果中了惡意的病毒軟體,會發現某些文件莫名其妙的被修改,下面這個例子可以幫你監視誰在修改你的文件。
 #!/usr/bin/env stap
 #
 # monitor who is messing my file of secrets
 #
 probe generic.fop.open {
if(filename == "secrets")
printf("%s is opening my file: %s\n", execname(), filename)
 }
SystemTap的基本原理
現下,大家已經熟悉了SystemTap的基本用法。
在結束之前,讓我們再來了解一下SystemTap的基本原理和工作流程以加深理解。
  可以看出,SystemTap營運的過程依次分為五個階段,通常稱為Pass 1 - Pass 5。
就像前面介紹用法的時候提到的,在命令行中加上-p NUM選項可以使得SystemTap在營運完Pass NUM之后停止,而不是營運到Pass 5。
這允許你分析SystemTap在每一個階段的輸出,對于調試腳本尤其有用。
下面來介紹每一個階段的主要功能︰
Pass 1 - parse︰這個階段主要是檢查輸入腳本是否存在語法錯誤,例如大括號是否匹配,變量定義是否規範等
Pass 2 - elaborate︰這個階段主要是對輸入腳本中定義的探測點或者用到的函數展開,不但需要綜合SystemTap的預定義腳本庫,還需要分析內核或者內核模塊的調試訊息
Pass 3 - translate: 在這個階段,將展開后的腳本轉換成C文件。前三個階段的功能類似于編譯器,將.stp文件編譯成為完整的.c文件,因此又被合起來稱為轉換器(translator)
Pass 4 - build︰在這個階段,將C源文件編譯成內核模塊,在這過程中還會用到SystemTap的營運時庫函數。
Pass 5 - run︰這個階段,將編譯好的內核模塊插入內核,開始進行數據收集和傳輸。

今日主要的OS

蘋果Mac OS

Mac OS是一套執行于蘋果Macintosh系列電腦上的作業系統。Mac OS是首個在商用領域成功的圖形使用者介面。Macintosh組包括比爾·阿特金森(Bill Atkinson)、傑夫·拉斯金(Jef Raskin)和安迪·赫茨菲爾德(Andy Hertzfeld)。現行的最新的系統版本是Mac OS X v10.5版。









Mac OS X 10.3
-----------------------------------------------------------------------
微軟Windows

Microsoft Windows 系列作業系統是在微軟給IBM機器設計的MS-DOS的基礎上設計的圖形作業系統。現在的Windows系統,如Windows 2000、Windows XP皆是建立於現代的Windows NT核心。NT核心是由OS/2OpenVMS等系統上借用來的。Windows 可以在32位元和64位元的IntelAMD的處理機上執行,但是早期的版本也可以在DEC AlphaMIPSPowerPC架構上執行。 雖然由於人們對於開放原始碼作業系統興趣的提升,Windows的市場佔有率有所下降,但是到2004年為止,Windows作業系統在世界範圍內佔據了桌面作業系統90%的市場。[4] Windows系統也被用在低階和中階伺服器上,並且支援網頁服務的資料庫服務等一些功能。最近微軟花費了很大研究與開發的經費用於使Windows擁有能執行企業的大型程式的能力。 Windows XP在2001年10月25日發布,2004年8月24日發布服務包 2,2008年4月21日發布最新的服務包 3。 微軟最新的作業系統 Windows Vista(開發代碼為Longhorn)於2007年1月30日發售[5]。Windows Vista增加了許多功能,尤其是系統的安全性和網路管理功能。Windows Vista擁有介面華麗的Aero Glass









Windows XP桌面
------------------------------------------------------------------------------------------
類Unix系統

一個在Linux底下執行的客製化KDE桌面系統 所謂的類Unix家族指的是一族種類繁多的OS,此族包含了System VBSDLinux。由於Unix是The Open Group的註冊商標,特指遵守此公司定義的行為的作業系統。而類Unix通常指的是比原先的Unix包含更多特徵的OS。 Unix系統可在非常多的處理器架構下執行,在伺服器系統上有很高的使用率,例如大專院校或專案應用的工作站自由軟體Unix變種,例如Linux與BSD近來越來越受歡迎,它們也在個人桌面電腦市場上大有斬獲,例如Ubuntu系統,但大部分都是電腦高手在使用。 某些Unix變種,例如HPHP-UX以及IBM的AIX僅設計用於自家的硬體產品上,而SUNSolaris可安裝於自家的硬體或x86電腦上。蘋果電腦的Mac OS X是一個從NeXTSTEPMach以及FreeBSD共同衍生出來的微核心BSD系統,此OS取代了蘋果電腦早期非Unix家族的Mac OS。經歷數年的披荊斬棘,自由開源的Unix系統逐漸蠶食鯨吞以往專利軟體的專業領域,例如以往電腦動畫運算巨擘──SGIIRIX系統已被Linux家族及Plan 9[3]叢集所取代。









個在Linux底下執行的客製化KDE桌面系統

thread

Thread 就是 Lightweight process

一個Thread 就是一個小型的 Process ,若我們把 Process 分為兩個部份----- Threads 和 Resources,Threads 就是這個 Process 的動態執行者(Dynamic Object),而每一個 Thread 的開頭就是這個程式的一個 Control Point. 因為同屬一個Process,所以 Thread 的 context switch 不同於Process 的 context switch,因為後者必須做 Address space 的置換(很費時). 傳統的 UNIX Process 就是一個 單一 Thread 的 Process. 而另外也有 Multithread in uniproceeor and on multiprocessors. 而因為一個 Thread 是一個小型的 Process, 所以它必須有自己的 PC (Program Counter), Stack 和 Register Set, 以儲存被 context switch 時, 相關 registers 的內容.

2009年6月13日 星期六

關於GDB

基本gdb
gdb是個命令列模式的交談(interactive)除錯器, 跟telnet或其它的unix交談式程式一樣有個提示符號,然後要下命令
(gdb)COMMAND
不要忘了gcc編譯時要加 -g 參數, 基本gdb命令
檔案處理
========
file a.out 載入可執行檔a.out
path 告訴gdb obj code在那
directory 告訴gdb source code在那裡
SHELL
=====
shell ls 就會執行ls了
cd xxx 不過用shell的方法跟Makefile一樣喚起sub shell而已
要真的cd到目錄要用cd
中斷點(Break point and watch point)處理
=======================================
break 設定中斷點
clear 清除中斷點
delete 清除中斷點
disable 暫時使中斷無作用
enable 使中斷再作用
condition 進一步設中斷點的條件 如果條件為true則中斷
commands 如果中斷了則執行commands與end中的一連串gdb命令
.....
end
其中 中斷點可以用source code的行數來代表(這些資訊藏在ELF格式 裡的.line這個section裡),也可以用中斷點的流水號來表示
br 在目前位置設中斷點
br 100 在100行中斷
br func1 在func1中斷
br +100 目前位置+100行中斷
br *0x08048123 在這位址中斷
br file.c:100 因為如果是多個c檔案時指定file.c
tbreak 同break的寫法 不過中斷一次後 此中斷點就失效
br 100 if (var == 5) 條件中斷 後面跟著c語法的條件判斷式
br 100 在第100行中斷並且執行command...end中的gdb命令
commands
silent
printf "x is %d\n",x
end
break String::func1 C++ Function Overloading的中斷 String是class
clear 100 清除中斷點 後面跟著行號或函數名
clear func1
delete 5 清除5號中斷點 後面是中斷點流水編號
disable 3 暫時使3號中斷點沒作用 後面是中斷點流水編號
enable 2 使2號中斷點作用 後面是中斷點流水編號
condition 3 (var > 3) 設3號中斷點的條件 如果條件為true則中斷
condition 3 清除3號中斷點的條件
程式執行
========
set args xxx 給執行程式參數xxx,就是main裡的**argv
run 開始跑程式
continue 中斷後繼續跑
next 往下跳一步c程式 如果有副程式 執行完整個副程式
step 往下跳一步c程式 如果有副程式 追進副程式
until 跳離一個while for迴圈
nexti 往下一步CPU組語的指令(Instruction)執行完整個副程式
stepi 往下一步CPU組語的指令(Instruction)追進副程式
until 執行到source code的行數比目前的大
如果目前所在行是loop的最後一行就會跳離loop
程式變數值(data)處理
====================
print var 看var的值
print &var 印出var的位址(其時這就是C 啦)
print *var 印出*var值 var是pointer
display var display會每次step, next時都會印出值來,print只印一次
print (var=value) 設var的值為value
其實print 可以只用p代替 很多指令都可以簡寫代替
p/x /x表示印hex值
/u表示unsigned digit
/d signed digit
/t 二進位值

/是列印的選項 在Solaris上的adb也有相似形式
x/3uh 0x8048012 印出記憶體
其中
3表示看3個
u unsigned digit(跟上面p命令一樣意義)
h halfword就是2bytes(bhwg分別是1248bytes)
GDB內定變數(跟程式變數不一樣喔)
===============================
一些gdb方便的變數(convenience variable)
$_ 用x命令所得到的最後一個位址
$__ 用x命令所得到的最後一個位址的值
$_exitcode 程式離開的code就是用exit時的code
CPU暫存器(registers)
$pc program counter就是目前cpu指到的執行位置啦
$sp stack pointer
訊息觀看與設定
==============
info 得到一些program debug資訊
info break
info frame
info display
info program
info share
info registers

show 得到一些系統(OS, CPU Arch), GDB資訊
show args (系統傳進來的argv[0],argv[1]...)
show os (OS是什麼)
show endian
show prompt (gdb的提示符號)
list 看原始碼
list x 從第x行的source code印出,x不寫從目前行印出
list *addr 秀出addr所在source code的行
可以先用info program找出目前PC的值
再用list *addr
search REGEXP 在目前source code做RE搜尋
disas 想看machine code用這個
whatis var 告訴我var的資料型別是啥 int, char or double
ptype var 告訴我var的資料型別是啥 這用來看struct用的
set 設定gdb, 系統的控制變數值(這些變數不是program內的)
set listsize xx 設定要看xx行source code
set $pc xx 把PC設到 xx
set convenience可以自己設變數
help 可以得到命令HELP
程序與副程式(process and sub-function)
======================================
backtrace(bt)2 程式執行到這裡前的兩個副程式,2不寫則列出全部
frame 2 選擇2號frame跳過去 2不寫就列出現在執行到那裡
up 2 往上走2個副程式
down 3 往下走2個副程式
return expression 不要玩了,回到上一層呼叫的routine去並return一個值
finish 繼續玩完一個選擇的stack frame(副程式)
kill 砍掉child process
signal procss-id 送signal給process
attach procss-id debug一個已經在記憶體跑的process
detach procss-id 釋放attach的process脫離gdb的控制
其中每次程式呼叫副程式時, 原本的執行的世界的東西(變數值啊等等)必需先保存起來, 然後再跳到新世界(將要執行的副程式)這就是stack, 每叫一個sub routine就等於進到一個stack frame
(gdb)frame 2
就是選擇2號frame,而0號frame就是目前在執行的副程式, 1號是呼叫0號的副程式,以此類推, finish搭配frame這個命令來用
所以bt這個命令很重要,可以追回之前叫了那些function來到目前的地方。 通常在命令列也有類似的追蹤system call的程式,因為system call很重要, 在Solaris上我們可以用
$ truss prog1
在Linux上
$ strace prog1
來看現在程式到底叫了甚麼system call導致他毀掉。
attach, detach必需在有支援process 的環境, 因為有的沒記憶體保護OS,或embadded system沒有支援, 另外也要有能力送signal給process的環境才行, 這主要可以來debug deamon或做multiprocess的除錯

2009年6月12日 星期五

Context Switch

Context Switch
在 multi-tasking 的作業系統中,kernel 不斷的切換 CPU 於各個工作間。FreeBSD kernel 以 thread 為工作的單位,透過不斷的切換,達成多工的目的。切換 thread 的過程,可以切割成三個步驟:
選擇下一個 thread
保存目前 thread 的狀態
切換到新的 thread 這裡不討論如何選擇下一個 thread,只討論保存和切換 thread 的方法。
下面是 FreeBSD kernel 實作 context switch 的三個重要 function:
mi_switch(int flags, struct thread *newtd) /* in kern_synch.c */
void sched_switch(struct thread *td, struct thread *newtd, int flags) /* in sched_*.c */
void cpu_switch(struct thread *old, struct thread *new) /* in sys/proc.h , i386/i386/swtch.s */
cpu_switch()
cpu_switch() 將 CPU 狀態儲存在 thread object 的 PCB 裡,並從 new thread 的 PCB 裡,將 CPU 狀態回復,以切換成 new thread 。由於 CPU 的不同,CPU 的狀態內容也不同,下面是 i386 定義的 PCB ,儲存 CPU 的 register 內容和 gs 和 fs 的 segment descriptor 。PCB 內容,於 kernel 其它部分而言,是一個黑盒子,不知其內容,只是將之視為一塊存放 CPU 狀態的記憶體。
struct pcb {
int pcb_cr3;
int pcb_edi;
int pcb_esi;
int pcb_ebp;
int pcb_esp;
int pcb_ebx;
int pcb_eip;

int pcb_dr0;
int pcb_dr1;
int pcb_dr2;
int pcb_dr3;
int pcb_dr6;
int pcb_dr7;

union savefpu pcb_save;
u_int pcb_flags;
#define FP_SOFTFP 0x01 /* process using software fltng pnt emulator */
#define PCB_DBREGS 0x02 /* process using debug registers */
#define PCB_NPXTRAP 0x04 /* npx trap pending */
#define PCB_NPXINITDONE 0x08 /* fpu state is initialized */
#define PCB_VM86CALL 0x10 /* in vm86 call */

caddr_t pcb_onfault; /* copyin/out fault recovery */
int pcb_gs;
struct segment_descriptor pcb_fsd;
struct segment_descriptor pcb_gsd;
struct pcb_ext *pcb_ext; /* optional pcb extension */
int pcb_psl; /* process status long */
u_long pcb_vm86[2]; /* vm86bios scratch space */
};
下面程式碼,是取自 i386/i386/swtch.s ,儲存 CPU 狀態,包括所有 register 的內容。 ENTRY(cpu_switch)

/* Switch to new thread. First, save context. */
movl 4(%esp),%ecx

#ifdef INVARIANTS
testl %ecx,%ecx /* no thread? */
jz badsw2 /* no, panic */
#endif

movl TD_PCB(%ecx),%edx

movl (%esp),%eax /* Hardware registers */
movl %eax,PCB_EIP(%edx)
movl %ebx,PCB_EBX(%edx)
movl %esp,PCB_ESP(%edx)
movl %ebp,PCB_EBP(%edx)
movl %esi,PCB_ESI(%edx)
movl %edi,PCB_EDI(%edx)
movl %gs,PCB_GS(%edx)
pushfl /* PSL */
popl PCB_PSL(%edx)
/* Test if debug registers should be saved. */
testl $PCB_DBREGS,PCB_FLAGS(%edx)
jz 1f /* no, skip over */
movl %dr7,%eax /* yes, do the save */
movl %eax,PCB_DR7(%edx)
andl $0x0000fc00, %eax /* disable all watchpoints */
movl %eax,%dr7
movl %dr6,%eax
movl %eax,PCB_DR6(%edx)
movl %dr3,%eax
movl %eax,PCB_DR3(%edx)
movl %dr2,%eax
movl %eax,PCB_DR2(%edx)
movl %dr1,%eax
movl %eax,PCB_DR1(%edx)
movl %dr0,%eax
movl %eax,PCB_DR0(%edx)
1:
#ifdef DEV_NPX
/* have we used fp, and need a save? */
cmpl %ecx,PCPU(FPCURTHREAD)
jne 1f
addl $PCB_SAVEFPU,%edx /* h/w bugs make saving complic$
pushl %edx
call npxsave /* do it in a big C function */
popl %eax
1:
#endif
將 cr3 切換到新 thread 的 page table (page directory table) 。 /* Save is done. Now fire up new thread. Leave old vmspace. */
movl 8(%esp),%ecx /* New thread */
#ifdef INVARIANTS
testl %ecx,%ecx /* no thread? */
jz badsw3 /* no, panic */
#endif
movl TD_PCB(%ecx),%edx
movl PCPU(CPUID), %esi

/* switch address space */
movl PCB_CR3(%edx),%eax
#ifdef PAE
cmpl %eax,IdlePDPT /* Kernel address space? */
#else
cmpl %eax,IdlePTD /* Kernel address space? */
#endif
je sw1
movl %cr3,%ebx /* The same address space? */
cmpl %ebx,%eax
je sw1
movl %eax,%cr3 /* new address space */
修改新舊 pmap 的 active CPU,舊的清成空 (沒有 active CPU),新的設定成目前 CPU 。 /* Release bit from old pmap->pm_active */
movl PCPU(CURPMAP), %ebx
#ifdef SMP
lock
#endif
btrl %esi, PM_ACTIVE(%ebx) /* clear old */

/* Set bit in new pmap->pm_active */
movl TD_PROC(%ecx),%eax /* newproc */
movl P_VMSPACE(%eax), %ebx
addl $VM_PMAP, %ebx
movl %ebx, PCPU(CURPMAP)
#ifdef SMP
lock
#endif
btsl %esi, PM_ACTIVE(%ebx) /* set new */
將 CPU 狀況恢復成新 thread 前一次執行的最後狀態 sw1:
/*
* At this point, we've switched address spaces and are ready
* to load up the rest of the next context.
*/
cmpl $0, PCB_EXT(%edx) /* has pcb extension? */
je 1f /* If not, use the default */
movl $1, PCPU(PRIVATE_TSS) /* mark use of private tss */
movl PCB_EXT(%edx), %edi /* new tss descriptor */
jmp 2f /* Load it up */

1: /*
* Use the common default TSS instead of our own.
* Set our stack pointer into the TSS, it's set to just
* below the PCB. In C, common_tss.tss_esp0 = &pcb - 16;
*/
leal -16(%edx), %ebx /* leave space for vm86 */
movl %ebx, PCPU(COMMON_TSS) + TSS_ESP0

/*
* Test this CPU's bit in the bitmap to see if this
* CPU was using a private TSS.
*/
cmpl $0, PCPU(PRIVATE_TSS) /* Already using the common? */
je 3f /* if so, skip reloading */
movl $0, PCPU(PRIVATE_TSS)
PCPU_ADDR(COMMON_TSSD, %edi)
2:
/* Move correct tss descriptor into GDT slot, then reload tr. */
movl PCPU(TSS_GDT), %ebx /* entry in GDT */
movl 0(%edi), %eax
movl 4(%edi), %esi
movl %eax, 0(%ebx)
movl %esi, 4(%ebx)
movl $GPROC0_SEL*8, %esi /* GSEL(GPROC0_SEL, SEL_KPL) */
ltr %si
3:

/* Copy the %fs and %gs selectors into this pcpu gdt */
leal PCB_FSD(%edx), %esi
movl PCPU(FSGS_GDT), %edi
movl 0(%esi), %eax /* %fs selector */
movl 4(%esi), %ebx
movl %eax, 0(%edi)
movl %ebx, 4(%edi)
movl 8(%esi), %eax /* %gs selector, comes straight after *$
movl 12(%esi), %ebx
movl %eax, 8(%edi)
movl %ebx, 12(%edi)
/* Restore context. */
movl PCB_EBX(%edx),%ebx
movl PCB_ESP(%edx),%esp
movl PCB_EBP(%edx),%ebp
movl PCB_ESI(%edx),%esi
movl PCB_EDI(%edx),%edi
movl PCB_EIP(%edx),%eax
movl %eax,(%esp)
pushl PCB_PSL(%edx)
popfl

movl %edx, PCPU(CURPCB)
movl %ecx, PCPU(CURTHREAD) /* into next thread */

/*
* Determine the LDT to use and load it if is the default one and
* that is not the current one.
*/
movl TD_PROC(%ecx),%eax
cmpl $0,P_MD+MD_LDT(%eax)
jnz 1f
movl _default_ldt,%eax
cmpl PCPU(CURRENTLDT),%eax
je 2f
lldt _default_ldt
movl %eax,PCPU(CURRENTLDT)
jmp 2f
1:
/* Load the LDT when it is not the default one. */
pushl %edx /* Preserve pointer to pcb. */
addl $P_MD,%eax /* Pointer to mdproc is arg. */
pushl %eax
call set_user_ldt
addl $4,%esp
popl %edx
2:

/* This must be done after loading the user LDT. */
.globl cpu_switch_load_gs
cpu_switch_load_gs:
movl PCB_GS(%edx),%gs

/* Test if debug registers should be restored. */
testl $PCB_DBREGS,PCB_FLAGS(%edx)
jz 1f

/*
* Restore debug registers. The special code for dr7 is to
* preserve the current values of its reserved bits.
*/
movl PCB_DR6(%edx),%eax
movl %eax,%dr6
movl PCB_DR3(%edx),%eax
movl %eax,%dr3
movl PCB_DR2(%edx),%eax
movl %eax,%dr2
movl PCB_DR1(%edx),%eax
movl %eax,%dr1
movl PCB_DR0(%edx),%eax
movl %eax,%dr0
movl %dr7,%eax
andl $0x0000fc00,%eax
movl PCB_DR7(%edx),%ecx
andl $~0x0000fc00,%ecx
orl %ecx,%eax
movl %eax,%dr7
1:
ret
FreeBSD 以 cpu_switch() 為基礎,提供誇 CPU 平臺的 context switch 能力。
sched_switch()
sched_switch() 是由 scheduler 提供的 function ,從 run queue 挑選下一個被執行的 thread ,並呼叫 cpu_switch() 切換 CPU 狀態,執行新的 thread 。目前 FreeBSD 提供三種 scheduler ,分別於 kern/sched_core.c 、 kern/sched_ule.c 和 kern/sched_4bsd.c ,各自有自己的 schedule 方式,應該有各自的 sched_switch() ,以挑選下一個 thread 。
sched_switch() 的主要功能是決定執行順序,真正進行 context switch ,還是呼叫 cpu_switch() 進形。因此,如果你想設計新的 scheduler ,最重要的就是 implement sched_switch() ,決定 thread 的執行次序,並呼叫 cpu_switch() 進行切換。
mi_switch()
mi_switch() 是屬於和 schedule 和 CPU 平臺無關的部分。主要工作是進行資料統計,和環境的檢查。統計資料包括 CPU 使用量,執行時間長短,切換的時間點。做完這些統計之後,就呼叫 sched_switch() ,以切換到下一個合適的 thread。

這學期的OS(下)

各位看官們,歡樂的時光總是過得特別快 又到了時間講掰掰!!
沒有啦= ="
我是說很快就到了期中考
結果在讀完上課自己抄的筆記跟投影片後
再去翻考古題
才發現怎麼我讀的東西很多都不在考古題上
難道是我方向一整個錯誤或是重點抓錯嗎= ="
就一整個很沮喪的去背好考古題...想當然爾成績不是很高ˊˋ
感覺這堂課吸收效率不是很好ˊˋ 不知道是不是我不習慣的關係QQ
然後作業部分到後面其實有在慢慢習慣了 至少不像第一次作業時如此無助
不過一直重灌砍掉重灌砍掉其實還蠻浪費時間的 囧>
常常為了一個作業因為灌OS弄了很久
但是真正寫起來又沒花多少時間 很嘔
不知道有沒有同學跟我試一樣的感覺ˊˋ

這學期的OS(上)

這學期開始時,選了五哥的OS課程,其實心裡是有點緊張的
因為我上學期是柯仁松教授教的
而柯仁松教授上學期的課程並沒有作業,而考試也是開卷考
所以我對於大體的OS(上)是可以理解的 但在背誦的名詞這方面以及細微的內容就略顯薄弱
而且也沒有灌新的OS做作業這種經驗

果不其然,上課後其實有很多英文名詞是我不太清楚的,上起來不是那麼輕鬆
而且上課的教學跟上學期果然有不同的差異 讓我有點難適應
這樣的感覺就好像我專題教授講的一句話
"請勿用專業名詞來解釋專業名詞",而對於我這個名詞不清楚的人來說
五哥的上課有蠻多機會是拿專業名詞跟專業名詞作比較
而偏偏這是我上學期沒學好的QQ
而第一次的作業也難倒了我,必須讓我四處去問別人 連VM我那時都不懂XD
有時候上起來會有一種無力感
不過我也知道是自己上學期沒有努力好
雖然說老師不一樣一定會有一定問題
但我想這不能當作藉口吧
所以那時希望能好好努力學好LINUX~!