?? 中斷.txt
字號:
while ((long)(jiffies - timer_jiffies) >= 0) {
struct timer_list *timer;
if (!tv1.index) {
int n = 1;
do {
cascade_timers(tvecs[n]);
} while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
}
while ((timer = tv1.vec[tv1.index])) {
void (*fn)(unsigned long) = timer->function;
unsigned long data = timer->data;
detach_timer(timer);
timer->next = timer->prev = NULL;
sti();
fn(data);
cli();
}
++timer_jiffies;
tv1.index = (tv1.index + 1) & TVR_MASK;
}
sti();
}
對run_timer_list函數的說明如下:
關中。
判斷jiffies是否大等于timer_jiffies,若不是,goto 8。
判斷tv1.index是否為0(即此時系統已經掃描過整個tv1的256個timer_list鏈表,又回到的第一個鏈表處,此時需重整TVECS結構),若是,置n為1;若不是,goto
6。
調用cascade_timers()函數把TVECS[n]中由其index指定的那條鏈表上的timer放到TVECS[n-1]中來。注意:調用cascade_timers()函數后,index已經加1。
判斷TVECS[n]->index是否為1,即原來為0。如果是(表明TVECS[n]上所有都已經掃描一遍,此時需對其后一級的TVECS[++n]調用cascade_timers()進行重整),把n加1,goto
4。
執行tv1.vec上由tv1->index指定的那條鏈表上的所有timer的服務函數,并把該timer從鏈表中移走。在執行服務函數的過程中,允許中斷。
timer_jiffies加1,tv1->index加1,若tv1->index等于256,則重新置為0,goto 2。
開中,返回。
Linux提供了兩種定時器服務。一種早期的由timer_struct等結構描述,由run_old_times函數處理。另一種“新”的服務由timer_list等結構描述,由add_timer、del_timer、cascade_time和run_timer_list等函數處理。
早期的定時器服務利用如下數據結構:
struct timer_struct {
unsigned long expires; /*本定時器被喚醒的時刻 */
void (*fn)(void); /* 定時器喚醒后的處理函數 */
}
struct timer_struct timer_table[32]; /*最多可同時啟用32個定時器 */
unsigned long timer_active; /* 每位對應一定時器,置1表示啟用 */
新的定時器服務依靠鏈表結構突破了32個的限制,利用如下的數據結構:
struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data; /* 用來存放當前進程的PCB塊的指針,可作為參數傳
void (*function)(unsigned long); 給function */
}
表示上述數據結構的圖示如下:
在這里,順便簡單介紹一下舊的timer機制的運作情況。
系統在每次調用函數do_bottom_half時,都會調用一次函數run_old_timers()。
函數run_old_timers()
該函數處理的很簡單,只不過依次掃描timer_table中的32個定時器,若掃描到的定時器已經到期,并且已經被激活,則執行該timer的服務函數。
間隔定時器itimer
系統為每個進程提供了三個間隔定時器。當其中任意一個定時器到期時,就會發出一個信號給進程,同時,定時器重新開始運作。三種定時器描述如下:
ITIMER_REAL 真實時鐘,到期時送出SIGALRM信號。
ITIMER_VIRTUAL 僅在進程運行時的計時,到期時送出SIGVTALRM信號。
ITIMER_PROF
不僅在進程運行時計時,在系統為進程運作而運行時它也計時,與ITIMER_VIRTUAL對比,該定時器通常為那些在用戶態和核心態空間運行的應用所花去的時間計時,到期時送出SIGPROF信號。
與itimer有關的數據結構定義如下:
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};
struct itimerspec {
struct timespec it_interval; /* timer period */
struct timespec it_value; /* timer expiration */
};
struct itimerval {
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value */
};
這三種定時器在task_struct中定義:
struct task_struct {
……
unsigned long timeout;
unsigned long it_real_value,it_prof_value,it_virt_value;
unsigned long it_real_incr,it_prof_incr,it_virt_incr;
struct timer_list real_timer;
……
}
在進程創建時,系統把it_real_fn函數的入口地址賦給real_timer.function。(見sched.h)
我們小組分析了三個系統調用:sys_getitimer,sys_setitimer,sys_alarm。
在這三個系統調用中,需用到以下一些函數:
函數static int _getitimer(int which, struct itimerval *value)
該函數的運行過程大致如下:
根據傳進的參數which按三種itimer分別處理:
若是ITIMER_REAL,則設置interval為current進程的it_real_incr,val設置為0;判斷current進程的real_timer有否設置并掛入TVECS結構中,若有,設置val為current進程real_timer的expires,并把real_timer重新掛到TVECS結構中,接著把val與當前jiffies作比較,若小等于當前jiffies,則說明該real_timer已經到期,于是重新設置val為當前jiffies的值加1。最后把val減去當前jiffies的值,goto
2。
若是ITIMER_VIRTUAL,則分別設置interval,val的值為current進程的it_virt_incr、it_virt_value,goto
2。
若是ITIMER_PROF,則分別設置interval,val的值為current進程的it_prof_incr、it_prof_value,goto 2。
(2)調用函數jiffiestotv把val,interval的jiffies值轉換為timeval,返回0。
函數 int _setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
該函數的運行過程大致如下:
調用函數tvtojiffies把value中的interval和value轉換為jiffies i 和 j。
判斷指針ovalue是否為空,若空,goto ;若不空,則把由which指定類型的itimer存入ovalue中,若存放不成功,goto 4;
根據which指定的itimer按三種類型分別處理:
若是ITIMER_REAL,則從TVECS結構中取出current進程的real_timer,并重新設置current進程的it_real_value和it_real_incr為j和i。若j等于0,goto
4;若不等于0,則把當前jiffies的值加上定時器剩余時間j,得到觸發時間。若i小于j,則表明I已經溢出,應該重新設為ULONG_MAX。最后把current進程的real_timer的expires設為i,把設置過的real_timer重新加入TVECS結構,goto
4。
若是ITIMER_VIRTUAL,則設置current進程的it-_virt_value和it_virt_incr為j和i。
若是ITIMER_PROF,則設置current進程的it-_prof_value和it_prof_incr為j和i。
(4)返回0。
函數verify_area(int type, const void *addr, unsigned long size)
該函數的主要功能是對以addr為始址的,長度為size的一塊存儲區是否有type類型的操作權利。
函數memcpy_tofs(to, from, n)
該函數的主要功能是從以from為始址的存儲區中取出長度為n的一塊數據放入以to為始址的存儲區。
函數memcpy_fromfs(from, to, n)
該函數的主要功能是從以from為始址的存儲區中取出長度為n的一塊數據放入以to為始址的存儲區。
函數memset((char*)&set_buffer, 0, sizeof(set_buffer))
該函數的主要功能是把set_buffer中的內容置為0,在這里,即把it_value和it_interval置為0。
現在,我簡單介紹一下這三個系統調用:
系統調用sys_getitimer(int which, struct itimerval *value)
首先,若value為NULL,則返回-EFAULT,說明這是一個bad address。
其次,把which類型的itimer取出放入get_buffer。
再次,若存放成功,再確認對value的寫權利。
最后,則把get_buffer中的itimer取出,拷入value。
系統調用sys_setitimer(int which, struct itimerval *value,struct itimerval *ovalue)
首先,判斷value是否為NULL,若不是,則確認對value是否有讀的權利,并把set_buffer中的數據拷入value;若value為NULL,則把set_buffer中的內容置為0,即把it_value和it_interval置為0。
其次,判斷ovalue是否為NULL,若不是,則確認對ovalue是否有寫的權利。
再次,調用函數_setitimer設置由which指定類型的itimer。
最后,調用函數memcpy_tofs把get_buffer中的數據拷入ovalue,返回。
系統調用sys_alarm(unsigned int seconds)
該系統調用重新設置進程的real_itimer,若seconds為0,則把原先的alarm定時器刪掉。并且設interval為0,故只觸發一次,并把舊的real_timer存入oldalarm,并返回oldalarm。
[目錄]
from aka
[目錄]
硬件中斷
硬件中斷
硬件中斷概述
中斷可以用下面的流程來表示:
中斷產生源 --> 中斷向量表 (idt) --> 中斷入口 ( 一般簡單處理后調用相應的函數) --->do_IRQ--> 后續處理(軟中斷等工作)
具體地說,處理過程如下:
中斷信號由外部設備發送到中斷芯片(模塊)的引腳
中斷芯片將引腳的信號轉換成數字信號傳給CPU,例如8259主芯片引腳0發送的是0x20
CPU接收中斷后,到中斷向量表IDT中找中斷向量
根據存在中斷向量中的數值找到向量入口
由向量入口跳轉到一個統一的處理函數do_IRQ
在do_IRQ中可能會標注一些軟中斷,在執行完do_IRQ后執行這些軟中斷。
下面一一介紹。
8259芯片
本文主要參考周明德《微型計算機系統原理及應用》和billpan的相關帖子
1.中斷產生過程
(1)如果IR引腳上有信號,會使中斷請求寄存器(Interrupt Request Register,IRR)相應的位置位,比如圖中, IR3, IR4,
IR5上有信號,那么IRR的3,4,5為1
(2)如果這些IRR中有一個是允許的,也就是沒有被屏蔽,那么就會通過INT向CPU發出中斷請求信號。屏蔽是由中斷屏蔽寄存器(Interrupt Mask
Register,IMR)來控制的,比如圖中位3被置1,也就是IRR位3的信號被屏蔽了。在圖中,還有4,5的信號沒有被屏蔽,所以,會向CPU發出請求信號。
(3)如果CPU處于開中斷狀態,那么在執行指令的最后一個周期,在INTA上做出回應,并且關中斷.
(4)8259A收到回應后,將中斷服務寄存器(In-Service Register)置位,而將相應的IRR復位:
8259芯片會比較IRR中的中斷的優先級,如上圖中,由于IMR中位3處于屏蔽狀態,所以實際上只是比較IR4,I5,缺省情況下,IR0最高,依次往下,IR7最低(這種優先級可以被設置),所以上圖中,ISR被設置為4.
(5)在CPU發出下一個INTA信號時,8259將中斷號送到數據線上,從而能被CPU接收到,這里有個問題:比如在上圖中,8259獲得的是數4,但是CPU需要的是中斷號(并不為4),從而可以到idt找相應的向量。所以有一個從ISR的信號到中斷號的轉換。在Linux的設置中,4對應的中斷號是0x24.
(6)如果8259處于自動結束中斷(Automatic End of Interrupt
AEOI)狀態,那么在剛才那個INTA信號結束前,8259的ISR復位(也就是清0),如果不處于這個狀態,那么直到CPU發出EOI指令,它才會使得ISR復位。
2.一些相關專題
(1)從8259
在x86單CPU的機器上采用兩個8259芯片,主芯片如上圖所示,x86模式規定,從8259將它的INT腳與主8259的IR2相連,這樣,如果從8259芯片的引腳IR8-IR15上有中斷,那么會在INT上產生信號,主8259在IR2上產生了一個硬件信號,當它如上面的步驟處理后將IR2的中斷傳送給CPU,收到應答后,會通過CAS通知從8259芯片,從8259芯片將IRQ中斷號送到數據線上,從而被CPU接收。
由此,我猜測它產生的所有中斷在主8259上優先級為2,不知道對不對。
(2)關于屏蔽
從上面可以看出,屏蔽有兩種方法,一種作用于CPU, 通過清除IF標記,使得CPU不去響應8259在INT上的請求。也就是所謂關中斷。
另一種方法是,作用于8259,通過給它指令設置IMR,使得相應的IRR不參與ISR(見上面的(4)),被稱為禁止(disable),反之,被稱為允許(enable).
每次設置IMR只需要對端口0x21(主)或0xA1(從)輸出一個字節即可,字節每位對應于IMR每位,例如:
outb(cached_21,0x21);
為了統一處理16個中斷,Linux用一個16位cached_irq_mask變量來記錄這16個中斷的屏蔽情況:
static unsigned int cached_irq_mask = 0xffff;
為了分別對應于主從芯片的8位IMR,將這16位cached_irq_mask分成兩個8位的變量:
#define __byte(x,y) (((unsigned char *)&(y))[x])
#define cached_21 (__byte(0,cached_irq_mask))
#define cached_A1 (__byte(1,cached_irq_mask))
在禁用某個irq的時候,調用下面的函數:
void disable_8259A_irq(unsigned int irq){
unsigned int mask = 1 << irq;
unsigned long flags;
spin_lock_irqsave(&i8259A_lock, flags);
cached_irq_mask |= mask; /*-- 對這16位變量設置 */
if (irq & 8) /*-- 看是對主8259設置還是對從芯片設置 */
outb(cached_A1,0xA1); /*-- 對從8259芯片設置 */
else
outb(cached_21,0x21); /*-- 對主8259芯片設置 */
spin_unlock_irqrestore(&i8259A_lock, flags);
}
(3)關于中斷號的輸出
8259在ISR里保存的只是irq的ID,但是它告訴CPU的是中斷向量ID,比如ISR保存時鐘中斷的ID
0,但是在通知CPU卻是中斷號0x20.因此需要建立一個映射。在8259芯片產生的IRQ號必須是連續的,也就是如果irq0對應的是中斷向量0x20,那么irq1對應的就是0x21,...
在i8259.c/init_8259A()中,進行設置:
outb_p(0x11, 0x20); /* ICW1: select 8259A-1 init */
outb_p(0x20 + 0, 0x21); /* ICW2: 8259A-1 IR0-7 mapped to 0x20-0x27 */
outb_p(0x04, 0x21); /* 8259A-1 (the master) has a slave on IR2 */
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -