, D- K; t7 t# w$ N & u a1 D4 X' ?6 j# a// 闹钟到时会执行此程序- L* H) r2 P V) t/ [4 m& u
6 i: H9 {5 b2 J$ X, R
void sig_alarm(int signo)1 V4 Z* w `2 M5 p3 m. \
% k/ K% v& a# ~' {* D: o{ $ g. s. ?. k$ r ; F( E: v, k/ `: \/ U; W# M //关煤气炉 3 g5 B: K1 U' L 6 P t1 \6 E W* K printf(“Close gas oven.\n”);9 v. I: d6 H3 w$ r6 ]
! |' a# A/ J' r, K5 z Z} 9 v$ x: n" f! S% Z q* A3 F& [: _3 _1 V' \6 [6 r 4 V; i! F- n6 z- x2 \ 3 c T; m) {5 @+ i* C; J$ j# z% T6 ^0 W5 ], E6 L/ K: d, a
void watching_tv() 1 b7 o; d3 F c3 z & y, W; L; m, W; x5 A( }{ 1 ]: y5 \+ H0 B: h2 g) P9 r3 Y9 f; Z6 T4 g: g) I; J1 B$ ?
while(1)8 I. s* f8 C% V7 Q
6 L* U" S3 Z6 p0 R& B) G7 |/ b
{ % C7 e3 B$ W U' k + e: |4 u5 ?2 ?2 i) Z // 呵呵,优哉游哉% S5 S* ]4 F: j. N" C$ @3 H
6 n/ c/ b7 v# x4 p: u6 U7 h
} $ f; b m9 b( J- Q( M' x$ U7 j & U8 Z3 |; n) G3 _4 t}( N& Q/ G" r) N0 l( C
* E! l( Y8 }3 G + ^$ `! r6 ~/ X7 O9 b+ G3 e " m# b/ r6 D7 d
! v0 J s0 K7 M+ X$ }+ E9 [3 { int main() 6 z6 {+ b$ m: Q% o4 O* o! |) m, L G! [2 F: ?
{1 p0 E$ l9 x+ y
0 m' @2 f, M. m' m, W! t4 ~
// 点火后设置定时中断/ _- X! {1 E- @9 a# h
* W, r+ n4 N" x printf(“Start to boil water, set Alarm”); / V& \) W: j2 F9 z7 @) S8 V ( L7 r5 T7 Q) c7 aif (signal( SIGALRM, sig_alrm ) == SIG_ERR)" U v" F- M5 t
) l+ E; g: r! \5 ?( F
{ - o+ S' T1 Z1 y9 E. l6 e3 [; g) m0 p2 T) Z- i* t, w
perror("signal(SIGALRM) error");7 d( g- C( ~1 h% M7 V$ x* U
! f* }( A! B t9 H7 x- A/ v" w$ d
return -1;! O$ {( z( q$ t- k7 O
' T6 {# f9 c$ g" Q* i. ~8 F; {5 B; Q } 2 t" h9 H* R4 I) ?! ^ 4 w2 I; o, i5 A3 d& w6 p 8 v, M; F5 j0 u $ V' J) a& v$ Y2 O- v' V2 U// 然后就可以欣赏电视节目了8 L+ ]" }- q0 u0 e
8 \! p+ l' Z$ q ; B+ ?5 @$ H' h* o# ?3 I7 O: y " X0 f0 {4 w1 |9 ]5 r' A实例一——为自己的操作系统中加入中断# T. p% T9 X" K% X0 ?6 T3 `
/ I1 P8 b$ L2 \7 ?2 r; b中断机制的实现% p5 H( @/ U v" G4 ~
/ G! Y. l- A& B) W
在这个部分,我将为大家详细介绍SagaLinux_irq中是如何处理中断的。为了更好的演示软硬件交互实现中断机制的过程,我将在前期实现的SagaLinux上加入对一个新中断-——定时中断——的支持。8 T, h. B; P) ?
0 S8 ?& }3 ^8 E3 S# y
首先,让我介绍一下SagaLinux_irq中涉及中断的各部分代码。这些代码主要包含在kernel目录下,包括idt.c,irq.c,i8259.s,boot目录下的setup.s也和中断相关,下面将对他们进行讨论。 1 |* B7 s$ n0 N( ` & d6 h& ]) H t, \# S G! |7 X1、boot/setup.s) E" d- D% t" V( C% }$ H6 a- @
3 a7 T4 ?% Q3 r9 `4 T
setup.s中相关于中断的部分主要集中在pic_init小结,该部分完成了对中断控制器的初始化。对8259A的编程是通过向其相应的端口发送一系列的ICW(初始化命令字)完成的。总共需要发送四个ICW,它们都分别有自己独特的格式,而且必须按次序发送,并且必须发送到相应的端口,具体细节请查阅相关资料。+ I/ _* W! Y0 c# L n# M1 k. w
8 G! E, M; k* bpic_init:/ Z- a; s8 p$ P0 p. H
- E) y% E0 f- D- C0 t) j7 { cli + {+ b. A! h% i 7 i/ O# i+ D9 a( o. f5 a mov al, 0x11 ; initialize PICs ) n# c' O5 ]& x& T1 L/ v. w. q9 n* O9 M- i1 ?3 T7 q E2 K. n. i
; 给中断寄存器编程 ! g3 s0 d) D" }& W/ p' q X. y. K" D0 }$ {% n$ {: v# }( C
; 发送ICW1:使用ICW4,级联工作) T& M. i, u* E0 Q
. ~, L6 r3 Z" E) a3 Z out 0x20, al ; 8259_MASTER5 | l4 T. P6 @+ V# f
2 _' b) B+ g% B4 Q
out 0xA0, al ; 8259_SLAVE" Z' m! r3 B! [" e: C1 J# N1 ]
* t; w4 J) D0 v6 M ; 发送 ICW2,中断起始号从 0x20 开始(第一片)及 0x28开始(第二片)9 S$ [& e v' C! L2 }" z8 A
. U5 m7 n& Y! z+ k mov al, 0x20 ; interrupt start 32 $ O2 ~- V4 n, {% f" }, S( A0 h: y) [+ t; P4 C% Z1 Y! T8 j+ n& `8 d1 u
out 0x21, al A* R& P; z5 V' b, H* H4 J" m 2 m. O e9 u: \' W: k mov al, 0x28 ; interrupt start 40 0 ~1 h: L) y; `4 z* T % k# v U5 w, s/ {# ~0 g P( H out 0xA1, al 1 g1 `! P b) v& v5 O/ Z: P1 M4 R" L/ D$ r* G
; 发送 ICW33 }8 u$ n/ F" V( R& a# }
! f" |" n1 Z* n mov al, 0x04 ; IRQ 2 of 8259_MASTER; c0 Y9 ]( K3 c7 S% o6 o
; t5 _ a; {' f8 K) m8 ^( L out 0x21, al & ~6 q* \( B- _& s/ X% m/ C$ L
) ?' K8 Z) H4 D" L1 B7 {7 D0 ]& n8 ^
; 发送 ICW45 D, U9 ?4 w1 b7 {' t8 O, l8 h
/ C8 e" r" t2 f1 s$ V# e
mov al, 0x02 ; to 8259_SLAVE% P) H% {6 F6 r+ w& X# X1 N' Z5 {
; ]; ^5 u6 M% H5 J/ L3 R: v
out 0xA1, al! h# s) ^! b2 x6 E$ e
/ B; b4 q4 _! O/ k
; 工作在80x86架构下 % Y+ B: }$ a, ~0 j1 q9 d' o. `7 V0 O3 C, Q& n
mov al, 0x01 ; 8086 Mode8 f/ Z5 }: m. Q2 X: W- |3 v
3 r |! N' z* `/ | out 0x21, al8 c: i' s$ s% g9 e$ P9 s
6 c/ X1 U5 k. ^! q
out 0xA1, al q6 `* t- t* w8 ^3 t( |- y) T) k! n
0 G0 l3 S( n! _6 S( U( G1 U; 设置中断屏蔽位 OCW1 ,屏蔽所有中断请求9 x2 D+ v! L' w+ d6 z7 _" Q# S
& q; N$ }9 f+ d1 h# `/ t. y4 \: R
mov al, 0xFF ; mask all8 d+ k: K' _0 `9 T" M2 y
! D3 V" q" U y G+ a; X# n& w
out 0x21, al8 j- ^* J) y4 [$ \: m' z, \, O% r
+ F' K! O& P& q8 r j; G out 0xA1, al: h5 E+ Y6 b' Y/ R. C g
; e7 k* [: R; ` Y6 ~% x
sti # ]' A% l" n% d3 B8 D 8 U, k% U2 d( u' v1 n( J7 U ( R* G4 _8 n6 L/ r# W8 \# w" k" N6 Z8 D' B, E9 ~0 f, L
2、kernel/irq.c: d7 J5 X ^0 w" B* B& @$ p; P
5 p6 }. L7 m ^0 G( r irq.c提供了三个函数enable_irq、disable_irq和request_irq,函数原型如下: $ G$ y9 c& c4 K+ ]: v4 K; e% u7 I1 p; t
8 L l7 c4 v+ ~0 ?* p) d( c7 f) G 4 j2 ]% G; w2 l5 A6 e , t. g# J# O1 gvoid enable_irq(int irq) - L# P, J3 d5 g2 J+ g" s. o+ A; i5 W _! o
void disable_irq(int irq) & _: r: Q2 `' r6 z! {: }* o6 E0 r0 U: E8 [: R3 G+ u) y8 B
void request_irq(int irq, void (*handler)())3 Y7 O2 _1 V- O6 m3 R2 T4 j
: x/ G$ F) D1 G5 \ f ) U2 U2 }3 j) P. _) a4 t7 A$ g
4 y9 Z0 L. L1 `& h3 W; V
enable_irq和disable_irq用来开启和关闭右参数irq指定的中断,这两个函数直接对8259的寄存器进行操作,因此irq对应的是实实在在的中断号,比如说X86下时钟中断一般为0号中断,那么启动时钟中断就需要调用enable_irq(1),而键盘一般占用2号中断,那么关闭键盘中断就需要调用disable_irq(2)。irq对应的不是中断向量。; i3 u) P* [2 Q s
5 W$ b/ {! h) `! J5 a
7 ?' X: v1 U: e; b$ q' Y
/ f d( V9 x" g1 }. U( p0 C
request_irq用来将中断号和中断服务程序绑定起来,绑定完成后,命令8259开始接受中断请求。下面是request_irq的实现代码: # e; ^6 }9 Q' k. R9 j; d2 u- M9 R( o/ Q. A6 [, ~
void request_irq(int irq, void (*handler)()) / Y5 z1 I0 e$ k- ], Q5 t+ M$ ~) I3 Z4 j8 Z; d
{ 4 a* S' Q0 p/ o 3 j8 s6 b, X( ~* G S irq_handler[irq] = handler; 5 a1 u, @. j0 D. h: Y- I 6 X/ ? q5 h+ K* @7 ~9 k enable_irq(irq);" |! w* i" h0 [1 {: e0 Q
3 ?( l; b, K4 O- f}- L4 u- Y) e3 |% e
) N R$ N* @) O7 T: _
其中irq_handler是一个拥有16个元素的数组,数组项是指向函数的指针,每个指针可以指向一个中断服务程序。irq_handler[irq] = handler 就是一个给数组项赋值的过程,其中隐藏了中断号向中断向量映射的过程,在初始化IDT表的部分,我会介绍相关内容。 6 o6 L. z/ \+ M* ]- }# A! e; a. p0 u
4 O' J7 U. ^' C0 E6 C 2 a* d9 a9 ~$ J0 Y3、kernel/i8259.s[2] ! z# ?2 ]4 U( }( m3 {4 C* ~6 l; ^3 \2 _" b+ G
i8259.c负责对外部中断的支持。我们已经讨论过了,8259芯片负责接收外部设备——如定时器、键盘、声卡等——的中断,两块8259共支持16个中断。1 H- C5 Y7 P) g; c2 @
2 |* S/ V7 ]% o% H
我们也曾讨论过,在编写操作系统的时候,我们不可能知道每个中断到底对应的是哪个中断服务程序。实际上,通常在这个时候,中断服务程序压根还没有被编写出来。可是,X86体系规定,在初始化中断向量表的时候,必须提供每个向量对应的服务程序的偏移地址,以便CPU在接收到中断时调用相应的服务程序,这该如何是好呢? ; p' q; h3 i% F8 H. T; Z: V* Y+ M M" h, F0 N
巧妇难为无米之炊,此时此刻,我们只有创造所有中断对应的服务程序,才能完成初始化IDT的工作,于是我们制造出16个函数——__irq0到__irq15,在注册中断服务程序的时候,我们就把它们填写到IDT的描述符中去。(在SagaLinux中当前的实现里,我并没有填写完整的IDT表,为了让读者看得较为清楚,我只加入了定时器和键盘对应的__irq和__irq1。但这样一来就带来一个恶果,读者会发现在加入新的中断支持时,需要改动idt.c中的trap_init函数,用set_int_gate对新中断进行支持。完全背离了我们强调的分隔变化的原则。实际上,只要我们在这里填写完整,并提供一个缺省的中断服务函数就可以解决这个问题。我再强调一遍,这不是设计问题,只是为了便于读者观察而做的简化。)- X! b7 z7 Z) R: E' M, y0 v
u: h N+ e. a& \: m- c
可是,这16个函数怎么能对未知的中断进行有针对性的个性化服务呢?当然不能,这16个函数只是一个接口,我们可以在其中留下后门,当新的中断需要被系统支持时,它实际的中断服务程序就能被这些函数调用。具体调用关系请参考图26 O G) M4 @6 [5 u8 p2 M: S l( A7 K$ y
7 A; ?7 d- {" L, `% b! s6 ^3 X - n3 L. {/ s- x图2:中断服务程序调用关系$ q- v+ P$ ^& @. s6 D! J
/ z8 ?' w9 ~$ ~# n! b$ p
如图2所示,__irq0到__irq15会被填充到IDT从32到47(之所以映射到这个区间是为了模仿Linux的做法,其实这部分的整个实现都是在模仿Linux)这16个条目的中断描述符中去,这样中断到来的时候就会调用相应的__irq函数。所有irq函数所作的工作基本相同,把中断号压入栈中,再调用do_irq函数;它们之间唯一区别的地方就在于不同的irq函数压入的中断号不同。 , \; q$ i5 c2 n: @( ?0 z0 }4 _/ y/ v6 E+ W/ C
do_irq首先会从栈中取出中断号,然后根据中断号计算该中断对应的中断服务程序在irq_handler数组中的位置,并跳到该位置上去执行相应的服务程序。 , ?; V* K- G- R# i( P$ h( g * M8 G. v' d" k% d- Y$ I V 还记得irq.c中介绍的request_irq函数吗,该函数绑定中断号和中断服务程序的实现,其实就是把指向中断服务程序的指针填写到中断号对应的irq_handler数组中去。现在,你应该明白我们是怎样把一个中断服务程序加入到SagaLinux中的了吧——通过一个中间层,我们可以做任何事情。) Q! H$ }0 G2 T# k0 Z9 P3 M
, z- e) P" F; R: w: C
在上图的实现中,IDT表格中墨绿色的部分——外部中断对应的部分——可以浮动,也就是说,我们可以任意选择映射的起始位置,比如说,我们让__irq0映射到IDT的第128项,只要后续的映射保持连续就可以了。5 Y5 S7 ~0 `5 b; u1 \4 i5 [9 u0 s, a
9 |; k- S" {% F( ]/ n$ w7 o- B' d6 o9 h, X0 r
% H7 L y4 t; ?. O: u7 T& f " O, \$ B$ u& c" _: Z# y4、kernel/idt.c ' l) z$ x9 `4 j- P: f9 |" V 6 u) h% E7 s+ ^4 I& i# H' Jidt.c当然是用来初始化IDT表的了。5 @# e ~1 P( J
2 L# H- z1 {- B9 U+ E% k在i8259.s中我们介绍了操作系统是如何支持中断服务程序的添加的,但是,有两个部分的内容没有涉及:一是如何把__irq函数填写到IDT表中,另外一个就是中断支持了,那异常怎么支持呢?idt.c负责解决这两方面的问题。 2 w0 Q; M; f2 ~: k0 e3 y% ]# f9 `, s& s$ H' w) |
idt.c提供了trap_init函数来填充IDT表。 2 ?! u) Q' w/ g+ \; V2 p. c0 o- C5 s) @) J$ t" C, E- F
void trap_init()4 J5 t( T y9 \) w
2 l8 A; s$ I0 T9 `% _
{ " a/ _) `+ N% H! M; G9 f9 B# K( \, I k9 F5 L) G( e int i;6 T V2 a( r6 f! T6 A7 I# n1 C/ P
; Z3 c9 X& k. @' ]& P idtr_t idtr;1 e- Q0 j4 f4 a" Y3 S
) m- o! {4 n; |7 y/ n7 m
// 填入系统默认的异常,共17个 - `2 A5 ?1 w6 o7 R" ~3 H u# G# C- i! p5 O: t& C8 [+ W: }
set_trap_gate(0, (unsigned int)÷_error); 2 K0 R5 @9 {: r3 Y8 S 9 ]& n% O4 n' W9 z set_trap_gate(1, (unsigned int)&debug); 8 I6 }* Z5 q& P0 b2 t% T, p& t. p# g" s! a6 o: o) k2 E1 S9 Z
set_trap_gate(2, (unsigned int)&nmi);! }* p9 p" j7 L) ^& s8 ~- A
* |) t' M3 Q5 |# y, Y
set_trap_gate(3, (unsigned int)&int3);# o4 S7 M! u' }1 f4 H
/ S3 N$ ]* I1 O* O" G* B' T set_trap_gate(4, (unsigned int)&overflow);9 |/ K. G1 H3 u9 ?# u% N% q
2 J1 R* Y |' E' O2 n/ e; m! w 0 W5 }% A8 z, l5 Q: P) `3 g# |
2 j0 o- T( _5 ?+ h
// 初始化硬件和技术器,启用中断 3 h. ?8 o+ R& t: j" h8 } z+ G& E0 q, P) T
void timer_init() & D$ f, o/ t. v. T1 S6 b: U! G5 w o" @4 x$ M6 c7 ?, T5 z [
{( t/ A g0 _- m2 k% k+ l9 \ O- n
3 Z4 M) v a! V# S ushort_t pit_counter = CLOCK_RATE * INTERVAL / SECOND;/ N0 M) B: @% M. z) S& _
% S' y& j$ Z5 p% p3 Z9 T
counter = 0; . y; s, _5 ^; n2 v5 F! G ; y- i# H0 X. T d. ` " q/ j7 w3 ?9 }7 k' G" K% a. x g5 F 0 k' E/ W! e& m3 ]$ E. P
2 F6 \5 ?4 F4 H9 ?/ K" o outb (SEL_CNTR0|RW_LSB_MSB|MODE2|BINARY_STYLE, CONTROL_REG); / D- H7 V c; c u. f : i$ P1 n: @- M% }* A outb (pit_counter & 0xFF, COUNTER0_REG); 4 N' I/ [7 e9 `& \0 d $ F; f1 ^: M' f7 F- b! N outb (pit_counter >> 8, COUNTER0_REG);6 ]. X$ [( z. ?& w2 d8 g6 d. c
$ v/ i f% g0 ~5 w // 申请0号中断,TIMER定义为0 6 D! m& z& W# p2 k - {: X" s9 _) i) n" ^9 \; n request_irq(TIMER, timer_handler);! J- _4 V/ {5 B
^. ]5 M2 s: D0 S" Y 1 ~" L# `' F- E& a+ ` 3 f/ a6 U' E M, z" o! Y // 设定时间的时候要避免溢出 h, V' l; U; [3 C, \" a ! u* P1 D$ `9 }4 i rtc_tm.tm_min += 10; : q2 H6 x, U" s. g8 i v% o, w( M8 {2 A. t+ ]6 z if (rtc_tm.tm_sec >= 60) { ' \: T* |8 P. Z9 r0 k4 Y! B: h6 W+ b2 v3 n7 @, N
rtc_tm.tm_sec %= 60; 7 |6 i5 O0 W( Z, W2 P. C7 M5 O7 {" @$ c7 O! d1 @5 G( r
rtc_tm.tm_min++; & ?) M$ l! G6 P, l3 w- [ 7 |9 R. X# g5 d }! M' \2 X: [, c% _
: x' _& K* r, v. d( w& A' L6 p! Q if (rtc_tm.tm_min == 60) { j. x0 K" d5 R2 R8 G( n' s5 B4 d# G
rtc_tm.tm_min = 0;7 c. S. ]% }2 w! S) G; D
2 f$ f# d# r' ^
rtc_tm.tm_hour++; ' D7 u+ ` \& |, T, f6 Q% ^ , k* ]8 L1 j# i }/ s5 M W0 u) W4 d. }
* J" s W$ B D! [ O( p if (rtc_tm.tm_hour == 24): R, f; D0 l ?" b
7 Q# l1 M4 ?! f" W! W rtc_tm.tm_hour = 0; & B9 S+ e$ y. {+ E 6 K$ F) X: t8 E- H9 h$ c / Y- P% M! `' r6 I: Y
9 r8 M$ [4 z$ `7 T0 t+ L // 实际的设定工作 ; I+ M7 g7 I: q0 o' h" i! e7 L1 L @- R @, \+ u
retval = ioctl(fd, RTC_ALM_SET, &rtc_tm);5 S( B% [! U( m& S8 Z# b
; w, a& R D( k( R# [1 l
if (retval == -1) { : U9 G- F& c, u& i$ ]& L% @4 D& ` D! V4 x& f
perror("ioctl"); 8 Y( ]7 R e; w' _3 n 7 t4 A5 C7 V' V3 w, R2 g* y- m' b exit(errno); 3 p+ v7 K/ ]# b/ f5 B # ~0 b) R4 q$ N+ l1 R1 W( n } / h& s+ F& |- }) W7 g, R2 E- K% [ c- e4 `
% e; U9 R" y4 n. k + y* V# i1 h- | // 检查一下,看看是否设定成功 ; b. p {# c8 a z; J- ]" `1 E, C) a+ I& C, g: N4 W5 K* r4 I# p5 m+ l1 b
/* Read the current alarm settings */ 5 H" C+ I* k! w- ?0 H: \+ n8 H5 e) }3 w# a6 l
retval = ioctl(fd, RTC_ALM_READ, &rtc_tm);# T/ S! Y3 ?) |8 P$ ^; t
" x. W' F! g5 S. E8 T
if (retval == -1) {5 O, R$ C% ?# y2 |
, r2 F. @* u) x3 [# ^% r perror("ioctl"); 5 H% r( d5 ]% F, j# Q5 `1 g; I& ~7 m
exit(errno);7 l) v8 x! I2 S p
7 L: P7 [; F8 W- a2 C }8 F5 M* p& G0 B
( k9 ]: Q+ {' [( x$ M 2 d8 I# l6 \9 B; `7 o5 n, Y
# I% D2 K3 t0 s2 z; H, z' K
fprintf(stderr, "Alarm time now set to %02d:%02d:%02d.\n", ) o6 D q4 k b3 b; P 1 N* G) U6 ^3 M9 ?! E rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);9 J* ^ @8 h4 Q9 U% a/ F+ S
3 D% Z- C6 ^) {
. X7 i' g \2 n% | . p% L- c$ W) w- c" m' D9 }& V // 光设定还不成,还要启用alarm类型的中断才行- n0 }4 @7 b0 r+ N" D, e
, }* E3 u7 A" A% X" h
/* Enable alarm interrupts */. [6 N6 y" K1 b$ e, _& K/ U
6 q4 [) w0 u; O
retval = ioctl(fd, RTC_AIE_ON, 0);; B6 G+ H. r( j# V. g* j
& Y. B. h( q4 d- u if (retval == -1) {! d! b: R! b. |! Q$ w
7 I% ?1 k/ Y" g% [7 I" M
perror("ioctl"); . }% j7 ^0 a5 {1 B 6 T. B# g* t# z: o3 i exit(errno);8 _6 c5 x4 [% h% R `$ w
) B; N8 o4 ?/ t2 Y }) U7 E4 R1 c4 A) {6 R/ h
- F2 p* t/ S/ @ ! I- i5 D2 }. R. w* y+ d! L6 T
( V, r* D0 j# I- M( h' ~+ s, [, {
// 现在程序可以耐心的休眠了,10分钟后中断到来的时候它就会被唤醒 ; d- G! A- ?& d# n7 k 6 ?& k8 F- M% P4 E- H5 c* M /* This blocks until the alarm ring causes an interrupt */& v9 u, o9 m( G4 {1 T+ n7 w
5 Y0 H& } A% Z8 [* j' q
retval = read(fd, &data, sizeof(unsigned long));2 K/ n) ~0 n0 a
; W# \4 \4 _5 o: T; ~ if (retval == -1) {+ s- n7 W: t. j ~( r& J' ~1 }" b* q# ~
: n* X5 r4 `5 }8 j0 [+ B
perror("read");- u' H$ Z$ B8 F: Q. v* M
) W1 p! j$ o5 w% u7 T
exit(errno); . ~& J' S4 |! [! Z/ F% W ! e' x6 v' g" K1 g }4 ~1 i# S4 P) J# W6 p
2 |2 B$ b/ C; Y: z9 X4 {
irqcount++;1 f0 m o4 f0 n8 _8 e
( Y8 v4 F2 ^, C& _6 f fprintf(stderr, " okay. Alarm rang.\n");4 G1 q/ D% |0 k8 V7 A
* ^4 Z3 Z3 e" p# _8 ]' e. n}' K8 G# b3 N) F8 H( h2 Y/ X! h
* B( [! H/ F8 H3 c2 T
这个例子稍微显得有点复杂,用到了open、ioctl、read等诸多系统调用,初看起来让人眼花缭乱。其实如果简化一下的话,过程还是“烧开水”:设定定时器、等待定时器超时、执行相应的操作(“关煤气灶”)。 ) s2 l7 @# S, G8 h # W2 G+ ^, o; Y读者可能不理解的是:这个例子完全没有表现出中断带来的好处啊,在等待10分钟的超时过程中,程序依然什么都不能做,只能休眠啊?! N1 \/ `+ Z; }0 A' r# b# l: u
1 m2 m8 c3 s2 v1 C4 }' c; b
读者需要注意自己的视角,我们所说的中断能够提升并发处理能力,提升的是CPU的并发处理能力。在这里,上面的程序可以被看作是烧开水,在烧开水前,闹铃已经被上好,10分钟后CPU会被中断(闹铃声)惊动,过来执行后续的关煤气工作。也就是说,CPU才是这里唯一具有处理能力的主体,我们在程序中主动利用中断机制来节省CPU的耗费,提高CPU的并发处理能力。这有什么好处呢?试想如果我们还需要CPU烤面包,CPU就有能力完成相应的工作,其它的工作也一样。这其实是在多任务操作系统环境下程序生存的道德基础——“我为人人,人人为我”。# h, B' ]7 H5 W. n
" m. l f3 ?8 s) [3 V5 a好了,这段程序其实是我们进入Linux中断机制的引子,现在我们就进入Linux中断世界。 : w$ r' Q8 l% F- w' A W9 {) w& z/ R2 c更详细的内容和其它一些注意事项请参考内核源代码包中Documentations/rtc.txt: j; e2 G; \) L+ F3 [7 ^, W W& q- H
' N4 g: z$ A9 r" k
% B' ^0 y$ R) S* T, P' G0 w* c1 q " u* t3 s2 o, b0 E1 {
% h) O4 R9 s P) l2 p- n z RTC中断服务程序" ^+ S U6 g) y
$ [, Z' x& @4 {( n1 v0 s$ B
RTC中断服务程序包含在内核源代码树根目录下的driver/char/rtc.c文件中,该文件正是RTC设备的驱动程序——我们曾经提到过,中断服务程序一般由设备驱动程序提供,实现设备中断特有的操作。/ Z/ p' R1 N' c9 U7 Y3 {
O# n; ]/ a+ [1 t
SagaLinux中注册中断的步骤在Linux中同样不能少,实际上,两者的原理区别不大,只是Linux由于要解决大量的实际问题(比如SMP的支持、中断的共享等)而采用了更复杂的实现方法。! @( o; b) W4 {/ c, b* J1 Y( O
]9 U* C1 R& |1 b6 P
RTC驱动程序装载时,rtc_init()函数会被调用,对这个驱动程序进行初始化。该函数的一个重要职责就是注册中断处理程序:2 _* C, z; o: d, ?0 t
8 X* y7 }# I+ P9 Z! ~2 a1 t2 N0 g% J if (request_irq(RTC_IRQ,rtc_interrupt,SA_INTERRUPT,”rtc”,NULL)){3 r9 S2 |" J' u$ b7 a) _# _% t
printk(KERN_ERR “rtc:cannot register IRQ %d\n”,rtc_irq); $ q8 e) g6 { H+ `, s; m2 M return –EIO; 1 m' V4 V h( g7 r/ V. w }; a5 [) D5 y( B) W/ O1 ?6 E' Q
这个request_irq函数显然要比SagaLinux中同名函数复杂很多,光看看参数的个数就知道了。不过头两个参数两者却没有区别,依稀可以推断出:它们的主要功能都是完成中断号与中断服务程序的绑定。 - J" Y! f; R% c" ~9 |- V8 u4 F& n+ Z& \( C
关于Linux提供给系统程序员的、与中断相关的函数,很多书籍都给出了详细描述,如“Linux Kernel Development”。我这里就不做重复劳动了,现在集中注意力在中断服务程序本身上。6 k) O6 l4 j6 ~- f+ H" ^