+ All Categories
Home > Documents > Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf ·...

Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf ·...

Date post: 05-Apr-2020
Category:
Upload: others
View: 8 times
Download: 0 times
Share this document with a friend
12
Linux 核心程式設計 第三版 Linux Kernel ProgrammingThird Edition 原著 Michael BeckHarald BöhmeMirko Dziadzka Ulrich Kunitz Robert MagnusClaus SchröterDirk Verworker 譯者 江俊龍 台灣培生教育出版股份有限公司
Transcript
Page 1: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

Linux 核心程式設計 第三版 Linux Kernel Programming-Third Edition

原著 Michael Beck,Harald Böhme,Mirko Dziadzka

Ulrich Kunitz ,Robert Magnus,Claus Schröter,

Dirk Verworker

譯者 江俊龍

台灣培生教育出版股份有限公司

Page 2: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

Linux 核心程式設計第三版

3-28

計時器的中斷(請參考 3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

屆滿的計時器,並呼叫該 timer_list 結構中的函式。

static inline void run_timer_list(void);

3.2 主要演算法

本章將介紹處理程序管理的演算法。

3.2.1 信號(signals)

Unix 中,最古老的處理程序間的通訊方法是信號。核心使用信號,通知處理

程序一些事件的發生。

使用者通常使用信號來終止處理程序,或將交談模式的程式切換到另一個狀

態。處理程序也可以使用信號,來與其它處理程序達成同步。信號的傳遞通常

透過以下函式:

int send_sig_info(int sig,struct siginfo *info,

struct task_struct *t);

呼叫此函式,除了信號的編號 sig 之外,還必須提供一個指標指向將要接收信

號的任務,並且搭配 info 為其傳送的參數。核心可以傳送一個信號給每個處

理程序;而一個處理程序則必須在一些條件下,才可以如此作為。這條件就

是,這個處理程序必須擁有超級使用者的權限,或與接收處理程序有相同的使

用者識別碼 UID 和群組識別碼 GID。信號 SIGCONT 是一個特例,任何的處

理程序,都可以將它傳送給同一個 session 中的其它處理程序。

如果發送的處理程序有權利傳送某信號,而接收的處理程序又無意忽略這個信

號,那該信號就會被送到接收的處理程序。這就是藉由設定接收處理程序的

task 結構中的 sigpending 達成的。在這種情況下,要判斷是否有信號給一個

Page 3: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

ch.3 核心簡介

3-29

處理程序,只要判斷它的 task 結構中的 sigpending 變數有沒有被設定。如果

設定完成,信號也就已經傳送。接收處理程序不會立即處理信號。信號的處

理 , 只 有 發 生 在 在 排 程 程 式 啟 動 之 後 , 並 將 處 理 程 序 恢 復 成

TASK_RUNNING 的狀態下(請參閱 3.2.6)。

當一個處理程序被排程程式再次啟動,但在切換到使用者模式之前,會呼叫

ret_from_sys_call 常式。它會判斷,是否有任何信號,要送給該處理程序。

也就是查看,處理程序的 task 結構中的 sigpending 旗標,有沒有被設定。如

果有,do_signal()函式會被呼叫,它會負責真正的信號的處理。如果遇到一

個 信 號 , 又 有 使 用 者 定 義 的 函 式 該 被 呼 叫 , do_signal() 會 呼 叫

handle_signal()去處理。

至今似乎尚未解釋,handle_signal()如何呼叫處理程序定義的信號處理常

式。這需要一些技巧。handle_signal()函式,操縱處理程序的堆疊與暫存

器。首先,設定指令的指標到這個信號處理常式的第一個指令。然後,將要給

此信號處理常式的參數放進堆疊中。對於一個處理程序來說,信號處理常式就

好像執行一般的函式。

然而剛剛所言,只是一個大概的方法,實際的實作必須分成兩個步驟。首先,

Linux 宣稱與 POSIX 相容。所以,當一個信號處理常式在執行時,處理程序

應該指定哪些信號要被暫緩處理。核心的實作方法是,在呼叫使用者定義的信

號處理常式,就將信號加到 current blocked 信號遮罩之中。然而,有一個

問題:在信號處理常式結束之前,信號遮罩的值必須被回復到原來的狀態。為

了達成此事,核心在堆疊區中放入一個指令,那就是執行系統呼叫

sigreturn,來當作信號處理常式的返回位址。這樣才能使得使用者定義的信

號處理常式,能完整的結束。第二點,是關於最佳化。如果是數個信號處理常

式必須被呼叫,數個堆疊框架(stack frame)就須被設定。最後,信號處理

常式就會一個一個的被執行。

Page 4: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

Linux 核心程式設計第三版

3-30

3.2.2 硬體中斷

中斷,是硬體與作業系統的一種溝通方式。如何寫作中斷服務常式,將在

7.3.2 節中介紹。在此,我們只介紹執行中斷的原則。

當產生一個中斷處理常式,程式設計者就會面臨一些難題。一方面,真正的中

斷常式應該儘快的處理硬體中斷,然後釋放掉處理器的使用權,讓處理器可以

儘快處理其它任務。另一方面,這樣的中斷應該也要能處理大量的資料。

為了解決這些問題,Linux 中斷處理分為兩個階段。第一個階段,執行與硬體

的溝通,這些都是時間緊迫的(time-critical)工作,因為這段期間會擱置其

它的中斷。至於真正的資料處理,必須與中斷分開非同步的處理,軟體中斷就

是這方面的動作,它有分 tasklets 或是後段(bottom halves)處理的方法。

而這些函式都是稍後才被呼叫,也可以被其它中斷給打斷掉。

我們將主要的硬體中斷處理常式簡化如下:

unsigned int do_IRQ(struct pt_regs regs){

int irq;

struct irqaction *action;

/*從暫存器中取得中斷編號*/

irq=regs.orig_eax & 0xff;

/* 找尋該負責的處理函式 */

action = irq_desc〔irq〕.action;

/* 執行 action 函式 */

while(active)

action handler(irq,regs);

action = action next;

Page 5: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

ch.3 核心簡介

3-31

/* 真正的硬體中斷在此離開 */

if(softirq_active & softirq_mask)

do_softirq();

3.2.3 軟體中斷

Linux 2.4 版新引進了軟體中斷的觀念。一個軟體中斷就像一個硬體中斷,一

個事件之後就必須去處理中斷處理常式。但軟體中斷並不像硬體中斷立刻被處

理,只有在特定時間。事實上,那都發生在硬體中斷和系統呼叫之後。

如硬體中斷一般,軟體中斷有數目上的限制。

enum {

HI_SOFTIRQ = 0,

NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,

TASKLET_SOFTIRQ

};

static struct softirq_action softirq_vec〔32〕;

HI_SOFTIRQ 是 高 權 限 的 軟 體 中 斷 , NET_TX_SOFTIRQ 和

NET_RX_SOFTIRQ 是用在網路的程式,TASKLET_SOFTIRQ 是用來協助

處理 tasklets。

註冊一個軟體中斷處理常式,是透過 open_softirq()函式。呼叫 raise_softirq()

是為了確保,當 do_softirq()再次被呼叫時,註冊的處理常式確定被執行。

void open_softirq(int nr,

void (*action)(struct softirq_action *),

void *data);

raise_softirq(int nr);

Page 6: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

Linux 核心程式設計第三版

3-32

void do_softirq();

提醒大家,在多重處理器系統中,同樣的中斷的處理常式,在同時間內,可以

被許多的處理器處理。因此處理常式應該被設計成,可重新進入的

(reentered)或是只能循序的存取一般資源。

使用 tasklets 的工作會比軟體中斷簡單。因為,系統會確保一個特定的

tasklet,只能在特定的時間被執行一次。然而,不同的 tasklets 可以平行的

被處理。

註冊一個 tasklet,是透過 tasklet_init()來執行。呼叫 tasklet_schedule()後,

一個 tasklet 會被設定成待處理,然後軟體中斷 TASKLET_SOFTIRQ 就會啟

動,執行這個 tasklet。

struct tasklet_struct;

void tasklet_init(struct tasklet_struct *t,

void (*func)(unsigned long),

unsigned long data);

void tasklet_schedule(struct tasklet_struct *t);

軟體中斷和 tasklets 是 Linux 2.4 版新的觀念,然而後段(bottom halves)

已經存在很久的時間。但以前它像軟體中斷,如今卻像 tasklets。後段

(bottom halves)函式在新的發展趨勢應該不會再被使用,所以在此不詳

述。它與 tasklet 最大的不同是,多處理器系統中,在同時間內,只有一個後

段(bottom halves)處理函式被處理。

3.2.4 系統的啟動

Unix 系統的開機有些神奇(或其它的作業系統)。本章的目的,就是要讓開

機程序能透明化。

Page 7: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

ch.3 核心簡介

3-33

附錄 D 解釋了 LILO(Linux 的載入程式)如何找到 Linux 核心,並將其載入

記憶體。然後從 arch/i386/boot/setup.S 的進入點 start:開始執行。正如其

名,這組合語言碼負責初始硬體。一旦必要的硬體參數被建立,處理程序就設

定 machine status word 中的 protected mode 的位元,切換到保護模式,

jmpi 0x100000,__KERNEL_CS

以 上 的 組 語 指 令 就 是 跳 到 作 業 系 統 的 32 位 元 程 式 碼 , 且 從

arch/i386/kernel/head.S 的 startup_32:繼續執行。更多的硬體部分在此被初

始(特別是 MMU(分頁表)),還有輔助運算器和執行程式的環境(給核心

的 C 函式的堆疊、環境變數等等)。在初始完成之後,呼叫第一個 C 函式,

也就是 init/main.c 的 start_kernel()。

函式 setup_arch()取得並保留與硬體有關的資料,也初始了一些與硬體架構

有關的部分。最後,核心中與硬體無關的部分才被初始。

asmlinkage void __init start_kernel(void)

char *command_line;

printk(linux_banner);

setup_arch(&command_line);

parse_options(command_line);

trap_init();

init_IRQ();

sched_init();

time_init();

softirq_init();

console_init();

init_modules();

...

Page 8: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

Linux 核心程式設計第三版

3-34

系統現在啟動了處理程序 0。它產生一個核心執行緒,執行 init()函式。其

後,處理程序 0 只負責消耗沒有使用到的處理器空檔。

kernel_thread(init,NULL,...);

cpu_idle(NULL);

函式 init()執行剩下的所有初始工作。在此,do_basic_setup()初始所有設備

的驅動程式。

static int init()

do_basic_setup();

現在,核心嚐試建立一個主控台(console),並且開啟檔案描述值 0、1 與

2。

if(open(〝/dev/console”,O_RDWR,0)< 0)

printk(〝Warning: unable to open an initial console.”);

(void) dup(0);

(void) dup(0);

接著系統嚐試去執行-使用者指定的開機程式,或是 /sbin/init、 /etc/init 或

/bin/init 程式之一。Linux 都通常都是以背景執行以上程式,並且確認每個連

接終端都執行 getty 程式,而這程式就是提供使用者登入系統。

if(execute_command)

execve(execute_command,argv_init,envp_init);

execve(〝/sbin/init”,argv_init,envp_init);

execve(〝/etc/init”,argv_init,envp_init);

execve(〝/bin/init”,argv_init,envp_init);

如果以上程式都不存在,系統就試著開啟一個 shell,超級使用者可以藉此修

理系統。但是如果連這項都不成功,系統就會停止。

Page 9: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

ch.3 核心簡介

3-35

execve(〝/bin/sh”,argv_init,envp_init);

panic(〝No init found…”);

本書在此只說明系統啟動的概要,實際上的系統啟動是更複雜,尤其在硬體初

始(MMU、SMP)和例外狀況(exception)處理的設定,還有使用初始記憶

體磁碟(INITRD)等。

3.2.5 計時器的中斷

所有作業系統,都有一個計時的方法,維持系統時間。一般的系統時間,是由

硬體在一段時間後,觸發一個計時中斷所產生。這個中斷處理常式就是負責時

間的計數。在 Linux 中,系統時間的計數單位是 tick,它代表自從系統啟動後

的累積 tick 時間。每個 tick 代表 10 毫秒,所以計時器中斷每秒會觸發 100

次。而時間被紀錄在全域變數 jiffies 中,它就是由計時器中斷負責更新。

unsigned long volatile jiffies;

然而,這個方法只是當作核心內部時間的基準。因為,一般程式比較關心真正

的時間。時間紀錄在以下變數中,而這時間也是由計時器中斷負責更新。

volatile struct timeval xtime;

計時器中斷產生的頻率較高,而且都有時間緊迫性。所以實作成兩個部分。

真正的時間中斷處理常式是 do_timer(),負責更新變數 jiffies,然後設定時間

的後段(bottom-half)處理常式(參考 3.2.2 和 7.3.5)稍後啟動。於是系統

稍後才會執行時間的後段(bottom-half)處理常式,處理完時間中斷的剩餘

工作。

void do_timer(struct pt_regs *regs)

(*(unsigned long *)&jiffies)++;

Page 10: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

Linux 核心程式設計第三版

3-36

update_process_times(user_mode(regs));

mark_bh(TIMER_BH);

if(TQ_ACTIVE(tq_timer))

mark_bh(TQUEUE_BH);

update_process_times()在稍後會說明。在那之前,我們必須先了解時間中

斷的後半處理常式。

void timer_bh(void)

update_times();

run_timer_list();

}

run_timer_list()負責函式的處理(3.1.6 章會說明),更新全系統的計時器。

這也包含即時任務的計時器。update_times()負責更新時間。

static inline void update_times(void)

unsigned long ticks;

ticks = jiffies – wall_jiffies;

if(ticks){

wall_jiffies += ticks;

update_wall_time(ticks);

calc_load(ticks);

Page 11: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

ch.3 核心簡介

3-37

update_wall_time()負責更新真正的時間 xtime。update_process_time()收

集排程程式需求的資料,並選擇哪個處理程序是否該被呼叫。

static void update_process_times(int user_ticks)

struct task_struct *p = current;

int cpu = smp_processort_id();

unsigned long user = ticks – system;

首先,task 結構中的 counter 被更新。當 counter 變成 0 時,現在的處理常

式的可用時間便用盡,排程程式就可以重新啟動重新排程。

update_one_process(p,ticks,user,system,0);

if(p pid)

p counter -= 1;

if(p counter <= 0){

p counter = 0;

p need_resched =1;

在此之後,task 結構中的 per_cpu_user 也會被更新,統計一些數值。

p per_cpu_user[cpu]+= user_ticks;

於 Linux 中,限制處理程序的 CPU 計算資源是可能的。它是藉由系統呼叫

setrlimit 來做設定,同時這也可以用來限制處理程序的其他資源。至於處理程

序是否有超過時間資源的限制,計時器中斷會負責檢查,並藉由 SIGXCPU 信

號通知,或由 SIGKILL 信號終止之。隨後,現在的任務的內部計時器被更

新。當這些計時器都到期後,這任務也會收到相對應的信號。

void update_one_process(p,user,system,cpu)

Page 12: Linux 核心程式設計 第三版epaper.gotop.com.tw/pdf/AXP008300.pdf · Linux核心程式設計第三版 3-28 計時器的中斷(請參考3.2.5)規律性的呼叫以下的函式,它會搜尋時間已經

Linux 核心程式設計第三版

3-38

p per_cpu_utime〔cpu〕 += user;

p per_cpu_stime〔cpu〕 += system;

do_process_times(p,user,system);

do_it_virt(p,user);

do_it_prof(p);

void do_process_times(p,user,system)

psecs = (p times.tms_utime += user);

psecs += (p times.tms_stime += system);

if(psecs / HZ > p rlim〔RLIMIT_CPU).rlim_cur){

/* 每秒傳送 SIGXCPU */

if(!(psecs % HZ))

send_sig(SIGXCPU,p,1);

/*若超用資源則傳送 SIGKILL*/

if(psecs / HZ > p rlim〔RLIMIT_CPU〕.rlim_max)

send_sig(SIGKILL,p,1);

void do_it_virt(p,user){

unsigned long it_virt = p it_virt_value;

if(it_virt)

it_virt -= user;

if(it_virt <= 0)

it_virt = p it_virt_incr;


Recommended