找回密码
 注册
关于网站域名变更的通知
查看: 329|回复: 1
打印 上一主题 下一主题

Linux内核设计与实现之中断下半部的处理

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-11-30 17:35 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
5 s$ z0 j  C' V& T* r
在前一章(Linux内核设计与实现之中断处理https://www.eda365.com/thread-464742-1-1.html)也提到过,之所以中断会分成上下两部分,是由于中断对时限的要求非常高,需要尽快的响应硬件。
8 g" Q+ N  q. Z1 z" r5 m5 N' H; e  A
$ I; W  w8 P! G, z主要内容:
) ?( y9 i1 ]0 g$ d1 R9 C  ~0 ?& a5 r  r$ R5 u/ @
中断下半部处理
& _' f6 x! A: S6 b' ^实现中断下半部的机制
5 I( M( N( c8 N( s总结中断下半部的实现# S: R/ z8 {8 L! O0 ]/ c5 I5 l& |
中断实现示例5 ]/ l( t- [4 U

( X! a2 g. \) I8 h' h
( h" j# l, C0 E0 q# a4 r1. 中断下半部处理
9 _0 ~' l$ u! d& l那么对于一个中断,如何划分上下两部分呢?哪些处理放在上半部,哪些处理放在下半部?6 [  r0 q. T! E2 ?' g  {
9 u/ O6 ^( t5 @$ d8 ?1 H6 i
这里有一些经验可供借鉴:
, X/ n8 _) @: n# @
/ P& Y! g' F/ X如果一个任务对时间十分敏感,将其放在上半部, d- z& o) Z4 ~! u. a
如果一个任务和硬件有关,将其放在上半部+ v6 P8 U0 _4 K' l! g
如果一个任务要保证不被其他中断打断,将其放在上半部
( g7 ^# U9 U( S4 m其他所有任务,考虑放在下半部
/ I5 g: F" i' N$ p" X6 o
! w# P  K3 K2 v. u2 C+ e0 T2 r* Q9 K! f" i" E8 l# q
2. 实现中断下半部的机制; ~2 ~5 y; L6 E' }
实现下半部的方法很多,随着内核的发展,产生了一些新的方法,也淘汰了一些旧方法。
- |' B# ?: K1 _8 {2 h* ?
* V: K3 K. g( [. r5 f8 u* J0 w目前使用最多的是以下3中方法
! p3 P$ {- I9 g  y
4 r% Z7 u3 y/ Y- ?3 P2 _: L/ G2.1 软中断9 ~: e* E8 f! [9 a
2.2 tasklet( W  j2 v3 N, ], Z; R! I
2.3 工作队列( I( m% v" G# c6 Q$ V" N) d5 C
2.1 软中断
( h$ w' G) e5 V: O" L& j' W软中断的代码在:kernel/softirq.c
# p9 U$ ?) K4 ?) o% }+ }
, D4 F& z' }$ V3 I* ]3 i % A3 H2 W# y! E& M$ f4 t

/ L/ o* T  p/ T+ i9 Y0 I! l软中断的流程如下:
$ f6 V5 u1 K' t" H+ u7 r
0 ~6 c' q" `: C4 b# S* U0 d  M+ B8 Jsoftirq
- R. T7 S9 G; u! y: g) U1 C! z2 X
( l& x" B/ z! \: _  B2 e流程图中几个步骤的说明:' X8 R# i, p' c6 \

' T$ S2 X# i1 y* ]/ i" F5 U- t① 注册软中断的函数 open_softirq参见 kernel/softirq.c文件)4 M4 k) t& u  Z( X' t  h+ s
5 K4 ~# U. Q7 B" S5 L$ q$ K# L1 ?
复制代码
8 q" w1 v& J; y2 x/* $ V+ P  U# v7 M' q% ]+ W
* 将软中断类型和软中断处理函数加入到软中断序列中! ?! R  s7 ~$ u3 J
* @nr                                 - 软中断类型" \% S# h7 Q, n, f" E
* @(*action)(struct softirq_action *) - 软中断处理的函数指针
, d% h" L+ L" Z# y! F9 t */
  m9 C4 B8 \4 F) [void open_softirq(int nr, void (*action)(struct softirq_action *))$ o0 t! D( C4 y* }/ G  K
{' Z9 \9 s1 T: E0 f3 ?7 j/ g
    /* softirq_vec是个struct softirq_action类型的数组 */5 P2 ~4 O9 h9 h& I: F( V
    softirq_vec[nr].action = action;
+ X/ B( N6 V0 b; }}: x  ~- o% E% j/ P4 p
复制代码
5 Y( V; v$ s: p+ E7 y) c软中断类型目前有10个,其定义在 include/linux/interrupt.h 文件中:2 g4 j. e- [. v( y0 E

, W) N+ h6 [- w6 s9 c- g复制代码
+ B4 ~' B, d- w  j+ r1 B9 Lenum( [) x5 p+ {7 D4 v8 c$ ?
{, Y# g8 [" z- J. ^
    HI_SOFTIRQ=0,
% j2 y9 T! W" o- x* l7 i! B    TIMER_SOFTIRQ,
4 Z# Q! b: t7 L# z    NET_TX_SOFTIRQ,
% h* E& f6 z/ r$ Q( V. P6 g    NET_RX_SOFTIRQ,
, Q' G2 ?9 j# n6 c    BLOCK_SOFTIRQ,
+ m  h4 A. |3 N    BLOCK_IOPOLL_SOFTIRQ,
0 g* c3 r3 R. x2 U    TASKLET_SOFTIRQ,4 A$ c/ G( v/ F+ {
    SCHED_SOFTIRQ,
; G3 ?* i7 M# h1 |$ R& `/ }    HRTIMER_SOFTIRQ,
, D" {  g" Z  G: u0 j    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */* H( n0 B# c  W

$ h+ E& O: |2 I. Z& s6 Z6 F    NR_SOFTIRQS: B+ h4 ?' G, O# s: y; C7 U) I
};+ |8 l: I3 b5 u8 ^4 R) i+ c- B
复制代码
- U. f( J+ z6 M1 Y) w* gstruct softirq_action 的定义也在 include/linux/interrupt.h 文件中
& Z3 T, S3 g9 a8 a* s2 r0 V
5 }& ^: O( Y& o" i9 F& v复制代码! K# m5 [3 W/ Q7 h2 ?) O  H
/*. H" x( O& U8 ?  g4 d+ I) u
* 这个结构体的字段是个函数指针,字段名称是action' }3 p- j% ~. [. _" n, z$ [& G+ V
* 函数指针的返回指是void型
- D" d$ \7 s- Q' a * 函数指针的参数是 struct softirq_action 的地址,其实就是指向 softirq_vec 中的某一项( a0 P, e$ M3 Z! c9 ?
*     如果 open_softirq 是这样调用的: open_softirq(NET_TX_SOFTIRQ, my_tx_action);
* P# b& S, J3 N6 H" z) O! ~ *     那么 my_tx_action 的参数就是 softirq_vec[NET_TX_SOFTIRQ]的地址* v6 v/ e$ L0 N& l
*/. G" N' R3 c% v3 q- O7 Y
struct softirq_action8 d$ }0 m& K1 V4 D0 u  L; ~
{, }* Q' E# I4 d& J& M
    void    (*action)(struct softirq_action *);( u$ P+ {. K5 b" o' D: g2 s
};# b/ w' Q) r; F2 N" P5 y% R6 v
复制代码
3 E- v9 b/ m8 A4 u: e9 _ 8 K; s5 o( e6 @9 m
- A  ?1 K! i+ I9 a' Q0 h
② 触发软中断的函数 raise_softirq 参见 kernel/softirq.c文件
5 b6 O, D+ \3 {3 B! \4 n9 z& f& n4 ]7 n) A, L0 s8 P1 D: V
复制代码  K5 C1 U9 `" _; O; A" i! X1 c
/*
! L  P7 r$ C& c( C$ E * 触发某个中断类型的软中断# P3 e: P/ f, F9 K# Y5 P
* @nr - 被触发的中断类型
. f( v8 {9 Z. z# T * 从函数中可以看出,在处理软中断前后有保存和恢复寄存器的操作) r4 m' Z; `* ?7 |% R
*/2 f0 [2 ^2 Z4 A) E
void raise_softirq(unsigned int nr)
8 d3 c6 Q5 l' Y3 t: c7 V3 G{
" O, R( G) r: A% D  Z6 K/ [3 c6 y! ^    unsigned long flags;0 n5 p$ i  {2 d, u: B9 K
  t" ~$ r" M% S8 z$ Z1 Z7 ^
    local_irq_save(flags);' o, X0 A  X8 e1 ]
    raise_softirq_irqoff(nr);
* P/ @( G" m* O: {0 T6 L& B+ V7 E  d    local_irq_restore(flags);! |* Z( n' z0 W9 T) p4 w8 b& V
}2 |4 C" b. S  r6 S' [0 O* b9 U
复制代码9 H4 c+ P: D7 e+ J1 L
" h" O" F! K/ C' f( p% [3 i$ Q
* C/ l+ Z6 H# T+ @- r- g  N0 O
③ 执行软中断 do_softirq 参见 kernel/softirq.c文件# t; b5 X; x$ D- k
. O& W$ G3 E- s* I
复制代码0 E! S$ a3 c9 b& ^$ m8 W8 t0 e7 L
asmlinkage void do_softirq(void)
; Z/ y7 V) s9 j9 V& D. ~1 x{
. \- Z6 m4 a: E6 @" `    __u32 pending;9 u5 W8 I; H6 B  U& u  \
    unsigned long flags;% W; ?" s8 J: m, e. Z
4 {5 {  a& N; C* O. F
    /* 判断是否在中断处理中,如果正在中断处理,就直接返回 */
4 c5 I9 W# i( p4 k% d5 t    if (in_interrupt())
$ i/ t$ g+ Z" J# z% @        return;
3 ?8 q) `2 c1 p* T; Z$ c7 p4 b$ |, z! a  W! G- w; D2 s
    /* 保存当前寄存器的值 */) X, P6 N7 s2 p" x
    local_irq_save(flags);
# F# l$ A+ R$ [: I( P
+ r! O7 M$ J/ \, U% |& K7 R    /* 取得当前已注册软中断的位图 */
" t, E, O! c, T    pending = local_softirq_pending();# }( Y5 ~8 F$ `2 r( _
$ J$ c5 l. m% D5 r. u
    /* 循环处理所有已注册的软中断 */
' E$ h" L+ b# @) Z' R    if (pending)
% I, B$ y, y% i8 b, `! p        __do_softirq();3 G; c! e8 B$ N6 J3 |5 A/ \
& W& Q9 P$ f  ^0 \- U! c
    /* 恢复寄存器的值到中断处理前 */
! a# U; A) E, ?- d7 T    local_irq_restore(flags);
+ _& C2 v; @) Q" ~5 {% K4 C6 O}5 C8 C: _$ g& t/ `; o6 f
复制代码4 M# M: A1 U1 A- ^' ^* }. [* x

2 \( H2 S/ }: v& c% E; J' z9 C1 X$ z* a9 p
④ 执行相应的软中断 - 执行自己写的中断处理
7 q6 o' I" U0 [+ M, X  ?+ u) Y5 ^% R
linux中,执行软中断有专门的内核线程,每个处理器对应一个线程,名称ksoftirqd/n (n对应处理器号)
( G: T% b( K# k- X7 E9 v% v/ b3 N" D6 S4 e! K2 B
通过top命令查看我的单核虚拟机,CentOS系统中的ksoftirqd线程如下:
9 g) Q3 f4 I: _; q. |8 g6 ~8 [- b% F7 e( K% Y
[root@vbox ~]# top | grep ksoftirq1 B1 N) D/ A) u& p- Z8 N3 C3 K  [1 Q
    4 root      20   0     0    0    0 S  0.0  0.0   0:00.02 ksoftirqd/0
& E- B# {& m( q$ c# L1 G' f / L/ T9 V9 R, R6 W! I

; Q# I2 p8 m3 b5 V+ f- \2.2 tasklet
  p) \1 M& r9 ^: N- E4 ~tasklet也是利用软中断来实现的,但是它提供了比软中断更好用的接口(其实就是基于软中断又封装了一下),3 q- h$ K' J3 W4 Q1 P0 e" e0 K

9 q1 H- P* e  k0 K! z- v7 K3 O所以除了对性能要求特别高的情况,一般建议使用tasklet来实现自己的中断。* I( P' Q: L2 S5 j# L
1 p2 O( z: l! U7 A8 v

  T6 K% s8 X2 D; z8 N- V# X* ?. G4 A
7 K. Z8 J* D! n( c: Q6 x3 }  h, Ftasklet对应的结构体在 <linux/interrupt.h> 中
, L! W1 j) U* B
, G% w. n  l& `( {3 L: }7 P/ d复制代码. a1 W8 q& z9 M/ r: s
struct tasklet_struct$ i/ T1 h# q: m4 x4 C: s3 R/ \
{
- W, D+ i2 e5 f" r. t" e2 U) W& c    struct tasklet_struct *next; /* 链表中的下一个tasklet */
/ j9 v7 v* j% b$ T    unsigned long state;         /* tasklet状态 */+ r, ?0 B4 e0 s9 _
    atomic_t count;              /* 引用计数器 */
, h+ o9 \; w7 B3 D8 X    void (*func)(unsigned long); /* tasklet处理函数 */8 X7 R- j( g: T. L3 `
    unsigned long data;          /* tasklet处理函数的参数 *// ^! o. J$ U$ A- x
};
$ w4 ~  ~' @$ d1 F  x* [复制代码% o" ]. g7 j' _* R' _" g
tasklet状态只有3种值:
# t4 g9 ^" Z$ E& R* j' k# \2 r# d  h" l8 s: w  A/ N
值 0 表示该tasklet没有被调度, t: n$ M5 Y7 k
值 TASKLET_STATE_SCHED 表示该tasklet已经被调度
+ v/ O: _2 N4 W2 \值 TASKLET_STATE_RUN 表示该tasklet已经运行
6 j# e$ `+ h# H$ e9 y" }引用计数器count 的值不为0,表示该tasklet被禁止。
$ q/ U" R" v$ N4 W
* B' X3 U. b  V. S; j) J( E) F9 x
$ v. @# j- z+ Ktasklet使用流程如下:
9 g$ t8 M  V( X) i" o
0 C4 ^9 {9 M0 A6 H) O/ D7 [1. 声明tasklet (参见<linux/interrupt.h>)" A7 C8 S# |; p3 d4 X& j
3 U# N. I# o9 i+ |8 V) A+ E( [0 D
复制代码9 z1 c3 b9 [( Z. w+ d& L
/* 静态声明一个tasklet */
$ V% L* B  \/ i; k, r#define DECLARE_TASKLET(name, func, data) \
# c: R! {. `3 V" }+ e9 ystruct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
, O' t0 J6 t3 [7 ?3 b+ b6 p3 M5 y9 n' m8 B
#define DECLARE_TASKLET_DISABLED(name, func, data) \
! O! f8 i! v) p% n$ [* W/ astruct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }$ G1 b; h- U" f& J4 _. I8 B3 M

$ I' d/ B3 i+ Y' w! m( V3 w/* 动态声明一个tasklet 传递一个tasklet_struct指针给初始化函数 */
+ f3 d% w6 m$ L$ A0 Jextern void tasklet_init(struct tasklet_struct *t,* o- P9 F; ?3 O  V' {( R
             void (*func)(unsigned long), unsigned long data);
. ~, ?1 d' W5 A% t5 p$ D& q  c复制代码
1 m  T+ P/ m. c2. 编写处理程序  X: J( W/ L- }- Q

* e- C+ m- e4 k( \参照tasklet处理函数的原型来写自己的处理逻辑' f/ }2 C2 G" l

; O$ p+ V3 K2 ?2 evoid tasklet_handler(unsigned long date)
9 o, d; O( |; H7 V3. 调度tasklet
& U/ k2 A: B) i. @0 R
+ d% r5 R% i* t* ]中断的上半部处理完后调度tasklet,在适当时候进行下半部的处理5 [) d# i# Z2 W; n  \) p
# t. g+ A, z3 u
tasklet_schedule(&my_tasklet)  /* my_tasklet就是之前声明的tasklet_struct */
% [7 N: e6 |4 I % |8 w; [# b% i. Q* c

2 e( d/ }- t, t* \" d# z5 G! F4 s2.3 工作队列1 Y( F3 Q4 f* @& S6 ^
工作队列子系统是一个用于创建内核线程的接口,通过它可以创建一个工作者线程来专门处理中断的下半部工作。
! W( h, x8 r9 D. O' K" a% A/ g, Y
4 Z# g8 K5 k! u, X$ _工作队列和tasklet不一样,不是基于软中断来实现的。
$ \% I( \) l6 A2 i7 K- A3 e
/ O5 V2 r" y& N
: d. e6 n  J) x+ D% C& \  Q" {2 H0 H. o; ?
缺省的工作者线程名称是 events/n (n对应处理器号)。
/ f$ T* B2 [  l2 q8 v# ~% ~- V# z) F
$ h- D4 n& M; k通过top命令查看我的单核虚拟机,CentOS系统中的events线程如下:& n& d6 u# v& _- c) N4 o5 D3 }

& A: H5 N( T. p( a- O" s  z[root@vbox ~]# top | grep event7 X. t# j' i7 X0 O' P4 m- v
    7 root      20   0     0    0    0 S  0.0  0.0   0:03.71 events/03 f3 }: |' p* e

! X$ w1 l& ]- W! ?7 R  w. ?0 e' \3 z  T# A1 `$ M- _( b% I
工作队列主要用到下面3个结构体,弄懂了这3个结构体的关系,也就知道工作队列的处理流程了。
- H6 }9 l% N, E' I1 b5 n) f5 m0 W6 x- {) i, X& y% _5 h( d
复制代码* W* T% I8 s0 q4 y; e( t
/* 在 include/linux/workqueue.h 文件中定义 */
" O+ j& n) u0 ~  {7 Q6 @struct work_struct {# v8 l! D  c# o) y' e0 |
    atomic_long_t data;             /* 这个并不是处理函数的参数,而是表示此work是否pending等状态的flag */
2 Q# |. ?4 x5 ~; z#define WORK_STRUCT_PENDING 0        /* T if work item pending execution */6 V2 ^% H* i. l1 m" w; h
#define WORK_STRUCT_FLAG_MASK (3UL)4 U9 n4 w' O; d! u
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)+ @* |8 r" r2 E" _% Z
    struct list_head entry;         /* 中断下半部处理函数的链表 */: R6 D9 d; j# m2 @  d* c
    work_func_t func;               /* 处理中断下半部工作的函数 */
+ a& w3 ^3 o2 O- P( y$ x$ B#ifdef CONFIG_LOCKDEP% c, P  u! ^& X* B' U$ B* Y/ r
    struct lockdep_map lockdep_map;0 i. a3 Q5 \- a. B1 p2 j
#endif% H$ l5 H9 Q, `! A$ ?# I
};
7 t7 F: S! @) `& i* D% o* X  J2 |' N0 {) U
/* 在 kernel/workqueue.c文件中定义9 X$ Q3 |2 r' Z% a4 w
* 每个工作者线程对应一个 cpu_workqueue_struct ,其中包含要处理的工作的链表& p4 ^: e! Z3 R& x! S" z, r
* (即 work_struct 的链表,当此链表不空时,唤醒工作者线程来进行处理)) Z7 {1 n* A% ~- O  O4 X) h
*/
: D9 |2 R- X0 R) A" Y' ?/*. b  r0 E5 g& P4 l$ [7 U4 o
* The per-CPU workqueue (if single thread, we always use the first
# z/ }% ^8 \( [- p- {/ L- ^- ] * possible cpu).0 S" n# g8 `9 u: Y( H+ p9 u
*/
+ F8 W, a. N3 |7 H" F" q, qstruct cpu_workqueue_struct {
6 z' d3 }" j) C% Q$ v
+ v6 ~, I1 K0 k9 U    spinlock_t lock;                   /* 锁保护这种结构 */
0 r! ?2 a; d/ P4 T7 i; u/ j! w
. k7 l" E! }& X( ?8 p8 v2 U    struct list_head worklist;         /* 工作队列头节点 */
' Z. q, p% X" p4 k. S# A* z    wait_queue_head_t more_work;! o  l) W2 `+ {8 [: ]" d
    struct work_struct *current_work;4 e) n& q" Y1 H* K/ r  Y6 @3 R
  C1 \, Y) z9 i% M; }
    struct workqueue_struct *wq;       /* 关联工作队列结构 */( C5 |; A( w. G
    struct task_struct *thread;        /* 关联线程 */
- A; c6 ]. C  z( M} ____cacheline_aligned;
$ }" T8 q6 b/ N; Z3 l5 B( X0 A6 v
  q! N) X2 \6 l8 B/* 也是在 kernel/workqueue.c 文件中定义的
3 z6 I% r6 F, d4 x# t2 W, E1 E2 | * 每个 workqueue_struct 表示一种工作者类型,系统默认的就是 events 工作者类型
( f4 u/ {" k. N: a# j! {6 q7 W * 每个工作者类型一般对应n个工作者线程,n就是处理器的个数
4 D1 }  ~& Q9 v  N6 O9 d- k% z0 I */
$ S5 ]1 |* o# m  Y' F9 U4 O/*, g- Q5 t" D" o) J7 c* F
* The externally visible workqueue abstraction is an array of" I  Y+ A' E2 c5 V0 a. I
* per-CPU workqueues:6 s. q7 b) H8 o5 l1 [. T
*/" c/ M4 ~. n' t4 L. W8 v
struct workqueue_struct {
/ b" N- c2 m4 l* l  m! [: u0 ~    struct cpu_workqueue_struct *cpu_wq;  /* 工作者线程 */
5 L% c) W) D- e& {; S/ Y4 [    struct list_head list;. x  c, a# k& d' i! J( U1 m, ^
    const char *name;
4 K& A4 g7 F1 }    int singlethread;
0 x4 F4 V/ W2 u8 E$ b    int freezeable;        /* Freeze threads during suspend */5 Y7 I& ^9 B1 F% h8 i' q$ @
    int rt;. d- i. q& D) W. C% T# p/ f
#ifdef CONFIG_LOCKDEP
3 j0 P0 k) P5 ?9 [$ H- b3 ~    struct lockdep_map lockdep_map;3 ^$ j* x$ ]. }/ b$ I8 M% H
#endif; N. G. O! |. o) N
};# b6 C( g- h2 Y1 J
$ W3 n3 z+ h1 |. e

" Q3 _6 ]$ U  x. l  ~7 Y& O使用工作者队列的方法见下图:
0 E: R5 U! H$ o0 z
5 S% y* d* \) r+ h
4 D% O8 m6 O1 a  Y9 k$ F 0 P! ?, u1 z% _0 y

' R/ V3 v" f- |0 W- k4 K6 f2 G① 创建推后执行的工作 - 有静态创建和动态创建2种方法( `- a6 D% ~- M! ], D2 w

' I: }) ]! b+ n! U复制代码5 V# F( k# ], J1 ]4 j, R9 j, c
/* 静态创建一个work_struct
' v7 X7 y% G" g5 @( U * @n - work_struct结构体,不用事先定义4 F# Q' Z, B* m4 d
* @F - 下半部处理函数- a* b( l' K9 T6 J& k% G3 ~% j- L7 b
*/) \# q8 e6 F& ~% a" Z
#define DECLARE_WORK(n, f)                    \% ^! N& a. X! W
    struct work_struct n = __WORK_INITIALIZER(n, f); K; ^+ G3 Y1 @: B
% p1 q6 x: `5 Z+ ?; Y$ L9 P7 ?
/* 动态创建一个 work_struct* g# I! `% R6 [  _, z* Z8 ?' ]6 ]
* @_work - 已经定义好的一个 work_struct
, M0 _+ q/ a3 j- Z) R. q. `( @ * @_func - 下半部处理函数
8 S0 n* E4 C& f3 K */
3 t8 S; J+ P: y4 [" _#ifdef CONFIG_LOCKDEP
' E6 l  ?  H  g$ K" I- o9 o' Q" E$ z#define INIT_WORK(_work, _func)                        \
. _  H0 S% l% @7 @: `    do {                                \
2 o2 S1 S0 P/ C; U4 J- b* G# a        static struct lock_class_key __key;            \
, w3 g* \( `3 E! A+ p: V9 _+ X* ^                                    \9 k) w% X  T+ u$ i: A6 b5 ?# R# n& P- _
        (_work)->data = (atomic_long_t) WORK_DATA_INIT();    \
, ]6 d. L3 ]& E6 F' w$ e        lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0);\
& `3 o9 P3 @5 K( h2 v0 H, E% {9 e        INIT_LIST_HEAD(&(_work)->entry);            \
5 S* m  |6 W4 ~3 j        PREPARE_WORK((_work), (_func));                \  Y& D. ?% k- i+ Z5 G
    } while (0)
  O6 R: l; V' a: Y6 F9 ^#else
* l1 a* W9 z' ?4 H$ u  Q) D! H#define INIT_WORK(_work, _func)                        \4 V% P# J& `5 o& _8 u3 |' p. M% ?
    do {                                \5 _! r& `% h0 S& D0 J
        (_work)->data = (atomic_long_t) WORK_DATA_INIT();    \
4 a& d1 i( b  t        INIT_LIST_HEAD(&(_work)->entry);            \
' o$ _4 d# ~7 y        PREPARE_WORK((_work), (_func));                \/ H) [! G9 y3 g6 Z& L. {" e
    } while (0)
0 ]" ~; w9 f! @* v#endif
1 I9 u0 ?$ Z  ^, f+ o+ o& Q& S复制代码% U0 t' f4 K) F4 H) l! \
工作队列处理函数的原型:
. i8 ?7 t2 o; W6 Q0 v. W" z' J8 u3 x7 Q
typedef void (*work_func_t)(struct work_struct *work);' i% {" j  a- Q; g, K6 M

  P) R5 ~$ K% H( }' e3 v6 I) \0 v+ {% P2 P6 r6 V8 \3 D0 ]
② 刷新现有的工作,这个步骤不是必须的,可以直接从第①步直接进入第③步4 {+ N( `, t3 x9 X2 M$ }$ c& B  Z
; R# ~! C+ a6 [: j* E0 F
   刷新现有工作的意思就是在追加新的工作之前,保证队列中的已有工作已经执行完了。1 @- e% v4 Y. ]; s
, _1 y# ~7 ~9 p, E
复制代码
. k; B$ i" I2 k3 D4 o/* 刷新系统默认的队列,即 events 队列 */
; H" I4 w, q( u" @/ `* svoid flush_scheduled_work(void);
; Z" G+ x) |- j3 M# s0 R6 g  `; Y! W8 S0 q9 L/ h. O5 g
/* 刷新用户自定义的队列; v: v9 G( ~) O9 j2 Z
* @wq - 用户自定义的队列; `; V- W. h+ p5 e9 R! M
*/7 ^2 {# Z9 E% S* F8 n) i
void flush_workqueue(struct workqueue_struct *wq);  M+ \7 r8 E# `! c0 G  [& g% F
复制代码& Y. ^, S3 Y3 Q& p2 ?8 \! _

5 y% [2 ?( v! n9 \/ t+ `4 C
+ K; r/ K" ]; Z: u+ ~0 G9 [③ 调度工作 - 调度新定义的工作,使之处于等待处理器执行的状态
/ u: e& L- j7 ^/ a
0 X) d  U( ?! B2 q  }复制代码
7 D8 J; S5 w7 p/* 调度第一步中新定义的工作,在系统默认的工作者线程中执行此工作6 d9 q$ ]1 w* a; \- W# D1 j
* @work - 第一步中定义的工作
! L# [( U) v  A, e */
3 g2 o: g& e% I$ y1 Zschedule_work(struct work_struct *work);* t' `3 \6 `/ N1 p) G% K, s

& U. x0 @* v& R" w/* 调度第一步中新定义的工作,在系统默认的工作者线程中执行此工作6 v' x$ c  s& o8 H4 ~. y- f
* @work  - 第一步中定义的工作; B  C! D5 B: e+ k& H
* @delay - 延迟的时钟节拍/ T' c  S. u8 Y" q1 B$ X
*/
( j7 q$ s1 z4 Zint schedule_delayed_work(struct delayed_work *work, unsigned long delay);
* W, V7 ~$ V2 p: m' ~5 p3 M+ i! p  ]. l/ ]% B' i0 y; @
/* 调度第一步中新定义的工作,在用户自定义的工作者线程中执行此工作( R( Y* Z3 |  J1 s% _4 Q
* @wq   - 用户自定义的工作队列类型
& G; U8 X+ c+ j6 w7 ?, m  g+ Y * @work - 第一步中定义的工作
4 e; Q( b) _$ `4 v1 S: I */7 W( Y# o; [7 ?6 X+ C0 X5 X4 n
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
# q$ @) d6 Z7 X0 e2 p
  z" W0 }9 H* k( g& q$ v& E' Q6 }# i/* 调度第一步中新定义的工作,在用户自定义的工作者线程中执行此工作' i6 N! F, M/ u! ]: P7 ~' ^
* @wq    - 用户自定义的工作队列类型
  O! q# o$ Y, \8 ~ * @work  - 第一步中定义的工作( D2 ]! q+ f$ K+ E; Y# F
* @delay - 延迟的时钟节拍3 Z' P6 Q! l" V
*/
9 K% T3 N- v! |9 z4 c. oint queue_delayed_work(struct workqueue_struct *wq,0 j/ a9 Y8 O7 j
            struct delayed_work *work, unsigned long delay);
8 t: F1 q" N: B4 M1 C- ~8 g. c" D/ M复制代码0 Z6 E) Z7 j" ]9 y: C9 b$ u

+ p6 ?/ E, |: b" Q
9 T" k; j  k, i6 }7 D3. 总结中断下半部的实现
, \3 _2 p5 H. R; }- P* x9 _+ n下面对实现中断下半部工作的3种机制进行总结,便于在实际使用中决定使用哪种机制$ H. e0 Q; G+ h/ X% p
2 }' T8 e! P# C7 A
下半部机制
上下文
复杂度
执行性能
顺序执行保障
软中断中断
9 ~: ^. R0 U5 x* q0 c$ L2 }(需要自己确保软中断的执行顺序及锁机制)

7 f% r7 }; ]( H- G(全部自己实现,便于调优)
没有
tasklet中断
' f1 i, G& S3 |( F; d9 j8 o(提供了简单的接口来使用软中断)
同类型不能同时执行
工作队列进程3 E# o0 ]+ c8 |
(在进程上下文中运行,与写用户程序差不多)
没有
, \) f2 m4 ~" U% Z( C, b  g(和进程上下文一样被调度)
) ]; e! O$ e6 o: J0 o8 W5 d4 P

) l5 N+ R" u- _, y0 Z$ O4. 中断实现示例8 I& o7 v7 t+ M
4.1 软中断的实现
9 n' ]0 `) g* J本来想用内核模块的方法来测试一下软中断的流程,但是编译时发现软中断注册函数(open_softirq)和触发函数(raise_softirq)
! {# B7 y+ b- D& s3 d; T  u$ k% ?1 T- v( p1 Q* W, B# v
并没有用EXPORT_SYMBOL导出,所以自定义的内核模块中无法使用。6 A; H( f( e( r3 L# i8 ?

- u2 E. W$ k4 u+ K' P测试的代码如下:: M! @& Z/ c8 i: C: l6 U
% c, V+ k+ F' p. P
复制代码
0 z" u* u$ u7 \& U/ d  y- Q" \#include <linux/interrupt.h>
/ D" ]- _/ S0 I3 o+ X#include "kn_common.h"
. {; {0 d0 y: K" q3 P: q/ Z2 [3 D  k0 S. e* J* d
MODULE_LICENSE("Dual BSD/GPL");' @8 h, I# V* @+ L9 e/ H
. N1 x6 A% B9 a
static void my_softirq_func(struct softirq_action*);
& S0 d6 r% y# X, R/ t4 o% O$ D& U0 N1 v6 h
static int testsoftirq_init(void)
" x8 _, M* v2 g) {1 F& a! A4 a{& W/ W0 Z( N7 m, h2 J$ D
    // 注册softirq,这里注册的是定时器的下半部. u4 P8 F" K4 x
    open_softirq(TIMER_SOFTIRQ, my_softirq_func);1 z3 Y* o/ K; ?4 g2 v
   
; F, h& G- t. d6 o# }    // 触发softirq
0 _$ c8 S, G/ y. N. `2 A* ]3 e    raise_softirq(TIMER_SOFTIRQ);
: e( |* L. m% u1 k  z6 x
( P/ `3 S- C3 I8 o. J- z    return 0;
# @( L2 p0 g7 {0 O   
- s$ O: I. i3 Q3 |( [, ~}$ a: X6 @) y* v

9 e1 J4 Q$ w: i' i8 R/ Gstatic void testsoftirq_exit(void)& n1 ]2 L/ v6 F5 Y
{
$ l* e2 R' F$ o3 k8 o    printk(KERN_ALERT "*************************\n");7 D& j/ u+ V2 a, F: D7 Z, K1 u9 ]; {, d
    print_current_time(0);
# s; g8 @, {3 T! E6 k& Y# Z3 i    printk(KERN_ALERT "testrbtree is exited!\n");
, d9 H; X* M4 G. I1 M3 U$ |& \    printk(KERN_ALERT "*************************\n");
0 M7 P4 Q' n6 i# `        
4 G5 }3 V6 n4 B% T1 N}
* @3 ^# k3 d% p& g( Q: t& m9 [/ d" A# ?# L- I
static void my_softirq_func(struct softirq_action* act)/ I% L5 e' @5 ?2 ~) A
{
4 x+ Q# M; U. q# V/ r    printk(KERN_ALERT "=========================\n");
6 s1 h* k7 y4 n6 R/ `) l    print_current_time(0);
" K5 A' p, H% a3 o4 C7 r9 o( _3 b    printk(KERN_ALERT "my softirq function is been called!....\n");
7 F- w2 E3 b: r3 q3 D    printk(KERN_ALERT "=========================\n");. u5 e# P0 L) d/ V1 R
}. }& ]- x$ A* T
0 D$ Y) c% M% f) L/ ^! l. |. l8 U

4 W. |+ V9 @3 L6 wmodule_init(testsoftirq_init);
$ n4 H$ C+ I; D2 Y1 ?, Mmodule_exit(testsoftirq_exit);+ C, t3 S6 F; R
& M/ ^4 n' T  M5 i* d
由于内核没有用EXPORT_SYMBOL导出open_softirq和raise_softirq函数,所以编译时有如下警告:' @8 z' h$ ~  ?" g/ l- ?6 D' }
) \9 r  _$ C1 V1 _6 B, B" K
WARNING: "open_softirq" [/root/chap08/mysoftirq.ko] undefined!
: h0 Z# H  i1 h1 e6 V$ tWARNING: "raise_softirq" [/root/chap08/mysoftirq.ko] undefined!0 r9 b2 P1 G& u1 k" M$ f
注:编译用的系统时centos6.3 (uname -r结果 - 2.6.32-279.el6.x86_64)0 w" p- Z- u) {! P% m

  @3 m7 _0 S, C) B' F 7 ^& e3 \- p( g2 L6 P$ S. v

: r! C2 Y, L- e0 E没办法,只能尝试修改内核代码(将open_softirq和raise_softirq用EXPORT_SYMBOL导出),再重新编译内核,然后再尝试能否测试软中断。
2 K) f0 ~% m& K- c! B) P3 y; [. V( }- k! ^  i. u9 T6 t. C
主要修改2个文件,(既然要修改代码,干脆加了一种软中断类型):
4 G! u1 N  _5 v: {1 o/ y8 D  [2 |$ [8 }. ~! Z# p1 v
复制代码
4 I$ y$ V& D0 A) X# G1 ?/* 修改 kernel/softirq.c */
; ?- M$ @/ D. x# L! }2 v// ... 略 ...
! \- F" k0 _* h7 D, `$ ~char *softirq_to_name[NR_SOFTIRQS] = {. @1 c' I7 _, \+ f
    "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL",
: o2 b; `1 \2 D8 G! m$ p# O' h# l    "TASKLET", "SCHED", "HRTIMER",  "RCU", "WYB"4 _) ?, h5 E4 b  L# F2 `) M
};  /* 追加了一种新的softirq,即 "WYB",我名字的缩写 ^_^ */
5 i- L8 |: E0 _4 a0 t
1 ?% y/ d  Z; [7 h5 y// ... 略 ...
5 E  c# Q% X6 ]5 i% p8 h+ T" `5 d8 {: k4 @- ?7 e8 C$ K
void raise_softirq(unsigned int nr)
% V: V. Q" G' B0 @& G. V{
4 P' E, e& ~. n) Y# e  b" ~( b8 E    unsigned long flags;
* n6 H. m4 A, g. _9 s, P8 S! g# b1 Z& \7 O0 S
    local_irq_save(flags);4 |% E) S6 F0 d3 S
    raise_softirq_irqoff(nr);5 K+ ~7 R; O9 w( _% O
    local_irq_restore(flags);
7 N7 X- H, h# x7 M) H}. R# o# N2 [) J
EXPORT_SYMBOL(raise_softirq);   /* 追加的代码 */* V4 v% R# K% U7 B: f
/ _+ T: N' b# @% n8 l3 b+ R
void open_softirq(int nr, void (*action)(struct softirq_action *)). A1 \1 x1 F" H! Z  G
{' Y$ N/ ~( I* {3 _; s" j6 z
    softirq_vec[nr].action = action;5 w: A+ ?6 |5 I% f" S: r) k
}9 E3 \+ X0 b: W1 S( O/ [; x
EXPORT_SYMBOL(open_softirq);    /* 追加的代码 */
* I$ W! W4 J3 x0 }, R+ q' {" r2 s/ M; m: u
// ... 略 ...
6 k- M4 a) O. ^) V" P
9 Z  u) M; C6 }* ?- W6 W7 F, m$ m9 t1 d: _0 J6 v
/* 还修改了 include/linux/interrupt.h */
+ f* e, b5 c0 ^- t) henum
: t, b+ E2 \; _- }! B& |{5 a& t! v3 H, j& R7 _7 b
    HI_SOFTIRQ=0,
& M  q& Y2 {8 d3 e    TIMER_SOFTIRQ,; c/ e- R. L  y4 B$ F5 u
    NET_TX_SOFTIRQ,
: f# K' w5 G, _1 Z# o    NET_RX_SOFTIRQ,* h% Q: w' x$ W/ _  F
    BLOCK_SOFTIRQ,, F. P/ a' E' [* S3 }
    BLOCK_IOPOLL_SOFTIRQ,
; N( U& G1 l) k' N' ]9 I, L    TASKLET_SOFTIRQ,$ B( h3 q# J' P4 Z' b
    SCHED_SOFTIRQ,% K2 H$ g, B  E, I) @
    HRTIMER_SOFTIRQ,) e8 v; i3 v3 v4 ^& @5 b
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */$ d& U" k0 j* s: K# A
! h5 n/ k' ^0 W
    WYB_SOFTIRQS,   /* 追加的一种中断类型 */2 p, E4 Q" ?0 G
    NR_SOFTIRQS3 A; V" v$ f8 F, J  [2 n
};
. X( K: n. u6 h; v( U' z) e0 w# x% j复制代码
$ L  M9 J, s9 a- N% F$ X重新编译内核后,在新的内核上再次实验软中断代码:
9 t0 \( R/ ~9 K( Y% t9 A4 S
3 V0 x7 f0 a9 R& z测试软中断的代码:testsoftirq.c) W  x( W" T+ i! X8 ^5 p/ r
7 C0 [# o9 ^' M) B
复制代码
. w; B! ~/ s6 |  E" A#include <linux/interrupt.h>
5 E) B( `+ @: u, ]#include "kn_common.h"+ N4 \! }6 D9 Q  f( F. u
$ `( M( Q" s& ^! a& L2 w5 Q7 n9 M
MODULE_LICENSE("Dual BSD/GPL");0 `) N' T& g# u* a# {
) o* x' |- n, F2 }, Y! z3 m
static void my_softirq_func(struct softirq_action*);. H0 J7 t1 B! I) o

9 s( y3 W3 M8 O- k) Sstatic int testsoftirq_init(void)# b9 j/ @) W1 Q, z9 c
{
, C  g' E6 A8 T. k/ f3 d    printk(KERN_ALERT "interrupt's top half!\n");
3 r& I1 ?: `% B    2 U  M3 u6 w6 w: L5 h6 |, W* A) D8 @* v
    // 注册softirq,这里注册的是自定义的软中断类型
9 H, e/ h. C- |$ W6 Z/ g6 J    open_softirq(WYB_SOFTIRQS, my_softirq_func);
& m% w% n+ A, R3 b   
. v% D6 z4 V3 r. R$ d  _    // 触发softirq
$ O; Q4 i- T, U! H& x1 q, \    raise_softirq(WYB_SOFTIRQS);
0 Y, [4 s0 I( `0 F/ o; x+ c
* h2 P9 _+ n2 E+ t* f    return 0;' W( o9 t& E% B* A
   
' J3 u* ?& {9 I9 W+ _& j}
) a; a1 f9 J& ~1 h% T! k: B6 Z7 c- @, R0 R7 V1 r
static void testsoftirq_exit(void)
9 I% z5 v6 ~* o{
: w* i3 y: D/ W" v) @9 x; G    printk(KERN_ALERT "*************************\n");
+ M" m/ T% P1 V5 A& O* Z: ]1 e    print_current_time(0);
4 d, S: V4 ?; f7 ]# }9 O- Q    printk(KERN_ALERT "testsoftirq is exited!\n");
# b- n. N* ^. x  I1 U9 ^    printk(KERN_ALERT "*************************\n");
- g5 ]( M/ A+ j/ p  m) n0 i% r        
: m+ M0 U) l  L}- h% _$ g( F" A8 }1 E
; O* v6 \( s; \8 w7 I4 |
static void my_softirq_func(struct softirq_action* act)4 u+ \8 F) A# i* S0 {  s( |
{
& D, t8 a& @% k: W2 u0 M% z    printk(KERN_ALERT "=========================\n");
! O" E3 L2 ^1 g9 q1 R! x; F* U( e+ U    print_current_time(0);: j; T1 j+ W3 X3 ^
    printk(KERN_ALERT "my softirq function is been called!....\n");
8 K7 _  I: z* Y& ^6 l$ {4 G    printk(KERN_ALERT "=========================\n");6 V# p- u5 i6 O2 ]& @1 O. D; S
}" z3 z7 T3 i' ]$ q7 ~
" A. u' W0 c6 B' A/ k
8 s; c$ s' z# q) O& V
module_init(testsoftirq_init);6 `3 Z& I; L' A) R2 J* V! L
module_exit(testsoftirq_exit);
3 n- x3 D+ f/ L4 E/ [; w5 g复制代码
  g4 X3 k( h  U6 F- ]/ X" {( I 6 D' O1 F  w; A0 J' a

; @+ U7 |- B' O% C- |  ^) W5 OMakefile:" K  h+ I* a/ ]. A3 B+ O0 g
( H4 f7 Z% F$ @( x5 h3 q& b2 E+ u
复制代码& c1 I# v) X0 j$ `6 h" d
obj-m += mysoftirq.o9 v5 H$ ^5 W( z2 H, C
mysoftirq-objs := testsoftirq.o kn_common.o
8 u; }2 n1 `. g) I. p: n2 k- e* E- N4 K5 p- a# d( r
#generate the path% U  v. ^* y: [0 S6 c9 c
CURRENT_PATH:=$(shell pwd)
; N' \) e# R1 z- O2 W9 w#the current kernel version number
4 I7 e) R4 C: LLINUX_KERNEL:=$(shell uname -r)9 {2 |8 l& o0 q
#the absolute path) Y1 a9 N! f- T( j
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)( @! s. u8 U& Q
#complie object
% O" O$ M, C( ], P0 C* \( {all:
- L' O9 B0 ?  r0 Z& _+ t. O& i    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules( E: @3 R5 C( D" u0 G$ x4 _+ B
    rm -RF modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned
2 z7 H% K/ O' U/ q6 N+ W( }+ k* _/ u#clean
. q4 w1 U: m& C2 _) hclean:
# F) K4 Y# w9 J5 Q    rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned) [* G1 H0 h1 w/ G
复制代码5 L' d& A; t2 W0 [

( T) S$ N: E. e% c0 H, m- G, x  k( a1 ?8 [' J1 S
测试软中断的方法如下:" t8 i9 m; W, Q/ @/ N
1 ]6 ~3 O) N' d2 P/ c7 D1 I
复制代码
7 v1 ^) N6 d* t9 w' }: Jmake8 Z6 u4 ^  @+ d0 }2 K9 d$ c
insmod mysoftirq.ko: v/ {4 L& a& Z
rmmod mysoftirq% I* y& i( e% H" v# @1 ]
dmesg | tail -9
/ f1 t' I; B" m
5 I$ h8 A, k* Q( C7 h# 运行结果
& _/ Y3 j$ X# Y2 U& o# G/ Hinterrupt's top half!5 {1 u1 E# W) k( _) X! v
=========================
" \+ C7 `: c8 e3 t/ r2013-4-22 14:4:57
* W7 S" j  {  o% z7 u5 S/ @8 Pmy softirq function is been called!....1 Q: ~# \: t" b% X8 K; L7 V
=========================, N' A0 T' O3 A
*************************
0 R: T- }2 }, `' h% T2013-4-22 14:5:2' t+ {4 [2 @- ?/ k2 L, n8 l) J
testsoftirq is exited!- B) L7 p6 |! H# q7 v& O: J* Q
*************************. F* C5 f  x7 r* K
复制代码
- A8 c5 h& O, A# `- _! ^
2 S% \% Y) J" F* Q/ w6 d: {: X9 o# I) m5 `
4.2 tasklet的实现% d( G; t  E0 e2 D9 e
tasklet的实验用默认的内核即可,我们切换到centos6.3的默认内核(uname -r: 2.6.32-279.el6.x86_64)3 U: k  n! n  s2 K- q/ c/ o

2 [8 U' b! d5 E$ L" p从中我们也可以看出,内核之所以没有导出open_softirq和raise_softirq函数,可能还是因为提倡我们尽量用tasklet来实现中断的下半部工作。
0 k. S5 }" d* j- l: q! @2 C5 f  j9 U' h8 r9 k. u

% Z; J: G+ A  D3 h7 \  Q( @/ u& E7 m! o6 T# t6 [6 c4 E; Q
tasklet测试代码:testtasklet.c3 c9 N% L. b- T6 e* V" I
5 o- \5 k0 V5 _" z7 I: r5 t8 K4 h/ g
复制代码
" k8 O" |" W  [; g2 i#include <linux/interrupt.h>
9 P$ Q" b( `* R& O+ p0 g5 [/ ~#include "kn_common.h". ?% n! `/ j6 |0 {* X
2 Z4 Z' y; Y8 }1 O
MODULE_LICENSE("Dual BSD/GPL");$ R; f) {8 `% w5 y7 G: N6 k% E

! D# {. V& G- w! Mstatic void my_tasklet_func(unsigned long);% B  p4 q0 x8 e2 p, G4 H

6 O& p: e6 X: [" ^1 j/* mytasklet 必须定义在testtasklet_init函数的外面,否则会出错 */* K8 H: X1 D: A, ?% }
DECLARE_TASKLET(mytasklet, my_tasklet_func, 1000);
$ K7 m6 r9 \  U1 R& o% _  O. t. M7 n
static int testtasklet_init(void)
- q! z; J' Q/ i; {, c{* e/ C+ n/ }- |/ o1 d$ \) I( c
    printk(KERN_ALERT "interrupt's top half!\n");
, ^: Y0 `( L0 o0 Y8 G/ s7 V" L, v
! b& y/ |: Q% N( D: C# N    // 如果在这里定义的话,那么 mytasklet是函数的局部变量,
6 `$ n2 h+ `: N( j! W$ |% @    // 后面调度的时候会找不到 mytasklet
$ H  s& m' Y* P/ `    // DECLARE_TASKLET(mytasklet, my_tasklet_func, 1000);7 ]; Y1 @6 _6 w
) i0 |2 W: D) \) C$ E
    // 调度tasklet, 处理器会在适当时候执行这个tasklet; l3 u: G; ~1 y8 A
    tasklet_schedule(&mytasklet);9 b1 y& ]8 o! s$ M
    # f* B6 m3 T1 g; l, B) T
    return 0;
. D4 T' y: |3 p4 u   
, [7 T2 i$ F% z8 m}
9 m) y4 I$ e$ f+ A; a/ @2 A8 o0 m, X$ g. ]1 N; s$ b/ T4 O
static void testtasklet_exit(void)
8 @- ?3 G$ Q! A8 o4 R, k4 M{
* a5 H+ u, i4 l2 b" Y    printk(KERN_ALERT "*************************\n");
$ b8 Q6 H" j) d2 [9 m- O* W    print_current_time(0);
, h: D* g2 W9 W3 E+ J1 @6 `& P    printk(KERN_ALERT "testtasklet is exited!\n");8 d% J1 }. u/ P7 I5 ]
    printk(KERN_ALERT "*************************\n");0 Y' C9 {( P1 G$ a( _4 r
        ! U& b9 h5 u+ u+ H; R1 h) [
}
& z. g! B0 S4 k) w/ K3 F$ g  n8 J" ?. s- k# V4 E3 ^% {
static void my_tasklet_func(unsigned long data)
0 C* t4 Z3 z! p{
8 L, d) J6 A5 {) Q& Q& @    printk(KERN_ALERT "=========================\n");
/ e+ {. ^$ F  z$ V' F* _    print_current_time(0);9 u* U4 _8 R" X
    printk(KERN_ALERT "my tasklet function is been called!....\n");
$ P* j; v1 V4 e# e* t5 G    printk(KERN_ALERT "parameter data is %ld\n", data);
0 q- j) U' R3 @+ B8 s' t! M    printk(KERN_ALERT "=========================\n");  F5 u3 \9 v. @6 l8 `1 Y6 H
}
6 }3 i. c) E, E) g( n+ d
3 C, V/ H' `8 T. E! @1 ^8 n9 N+ {1 |8 Y
module_init(testtasklet_init);
% B2 H7 J+ e, j* ^( K' h4 a& J8 Mmodule_exit(testtasklet_exit);) C& R$ S" D6 P+ o7 R% n
复制代码( }1 S# J8 {6 `+ R6 @6 E% X0 c

1 U9 l: N6 I0 }9 z; L
" F% W, h* \- G9 A+ MMakefile:
$ L- x  h) T" n5 u
2 N# W4 B9 g: C9 N# W( b0 s! a复制代码
. ~7 a  o9 ^$ ^4 ^# Q! ^: Q/ H' Wobj-m += mytasklet.o
1 F' @7 @7 W1 @& l) b  Wmytasklet-objs := testtasklet.o kn_common.o" g+ K3 g* k: m

) i+ P1 {' G' [2 P. j#generate the path7 g) _$ S8 T4 s  r
CURRENT_PATH:=$(shell pwd)) s' i, o( H7 M$ r3 P  q: T% ?
#the current kernel version number
  S, U7 J9 [$ S1 P, sLINUX_KERNEL:=$(shell uname -r)
  o! B3 {5 T' m- g#the absolute path) A- P2 B0 o* |- Q# L7 q
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
5 x) E) X; i, y#complie object% V5 K* ?) A( l; a+ S* m
all:
+ H" m% J4 W: ]2 Z$ Y0 S    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules8 e1 p; o, h; t* v
    rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned$ p  S6 J! O1 L$ }* A5 \# e9 F
#clean
: k6 F; }" q' i9 cclean:
: g& V8 Y2 A( ^    rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
9 J# b8 E8 a+ `6 R% D  J" v7 N) `! m复制代码$ \9 \8 h+ x: b( ]7 x0 ^4 t! s" [1 l

) d& I+ e0 k7 p9 W$ E+ ?( j6 L2 A% b
测试tasklet的方法如下:2 g+ U# y9 K" \/ S3 q
  c- @0 ^( R+ I" H7 C' p1 Q* v
复制代码6 P/ O! Q$ m* @* u1 n9 P( G" f
make
3 Q" p/ F6 ?2 k9 }insmod mytasklet.ko
( J! n+ G- J. _0 ]1 yrmmod mytasklet$ h, M: ]; M% |+ ~1 \
dmesg | tail -10% L2 e6 b+ A( W, ]6 [/ ?1 u4 y

0 U$ m" ]' I) _7 y# 运行结果
+ c3 }$ E' m# ?1 ?interrupt's top half!0 @( T8 w3 w1 t4 o. r
=========================8 s' z* Z+ x3 \. |/ S
2013-4-22 14:53:14
( ]" f0 X+ Y9 c( z* @3 |3 cmy tasklet function is been called!....
9 d% _  h' R/ ?' @5 j9 \parameter data is 10008 I! _* G' f, O( o7 d
=========================2 P7 s' N) D& G
*************************
* u$ F+ O. Q2 u2013-4-22 14:53:204 t8 U' H8 [8 H# R" p
testtasklet is exited!
; L  V; a  W6 k" P1 O*************************
- U* c% J0 D6 B. I- x6 [0 e; A复制代码. F& s. q  n( y
+ `& p/ g6 l. o* g" x& K9 F
' W7 p8 F; ]0 t- M9 L
4.3 工作队列的实现
* J3 i: U7 b- ?, j& @workqueue的例子的中静态定义了一个工作,动态定义了一个工作。
+ M' |0 }, v8 x# n  H' k
  Y, F: j. _1 w3 C- P静态定义的工作由系统工作队列(events/n)调度,: B# ?# ~2 f( ~7 _1 [. t* P' T
. {9 m/ m9 ?$ e
动态定义的工作由自定义的工作队列(myworkqueue)调度。5 j% h& k3 U% O

8 S" H1 p! T& N& J
  z& Q5 K6 Y- ]" Y
, Z: J5 U$ V* H) K+ D6 @, U  t测试工作队列的代码:testworkqueue.c
3 Y8 U5 I' v3 `& d0 ~. k- e# n; z6 O! s" `5 @
复制代码
6 v0 q2 L, I) G#include <linux/workqueue.h>1 N/ T1 D+ e2 a
#include "kn_common.h"
. W; [, a8 k2 t/ s. P
" ^$ \" Y% S) `5 b3 w* k. `) u4 z3 aMODULE_LICENSE("Dual BSD/GPL");5 j. |* J! l( T. x5 Q* e0 K3 V

$ @1 n' g0 c4 ystatic void my_work_func(struct work_struct *);
: G, ^- P: B3 [! e. @8 b7 A( Lstatic void my_custom_workqueue_func(struct work_struct *);( N2 Z  u+ c& o' I
% M8 e0 X8 w3 }1 d5 Q, v3 [: z
/* 静态创建一个工作,使用系统默认的工作者线程,即 events/n */5 L. w; q! c. C8 `# O- s$ w: _
DECLARE_WORK(mywork, my_work_func);
' v* O) O6 B( y+ q9 N3 i
! a4 `+ j- ?* ?$ Nstatic int testworkqueue_init(void)* x  ?: H4 O: I: I
{# j1 m+ V: N( f- y( S9 r% @
    /*自定义的workqueue */; W; z# M7 D- q/ |+ t
    struct workqueue_struct *myworkqueue = create_workqueue("myworkqueue");: ?- U9 [! C0 P* k8 _. `) j
. A# w* J# O2 M+ |# D- l
    /* 动态创建一个工作 */
, M. p: B$ @* a' D! X  _    struct work_struct *mywork2;$ \- Y/ r0 M7 T: o  P6 B
    mywork2 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
; C  q4 B2 S$ c+ R: I    INIT_WORK(mywork2, my_custom_workqueue_func);9 Z4 L+ K  F. e9 O( d
                  
8 W4 o  C: y& Q+ q" ^    printk(KERN_ALERT "interrupt's top half!\n");& P+ m7 Y9 J1 r  M' l9 _! |" L

7 P, D9 ^. y/ s7 g* K    /* 刷新系统默认的队列 */
2 Z: m8 O/ g8 N0 C2 a    flush_scheduled_work();5 [1 m' W0 T1 W0 v- M: h7 @
    /* 调度工作 */, u5 Z! t2 S7 q$ ~
    schedule_work(&mywork);& U8 m( F/ w0 W5 C" \; e; N

, V1 [5 g( z( ^; Z    /* 刷新自定义的工作队列 */  m) }; _) B* f' H  O" b1 `) E9 D
    flush_workqueue(myworkqueue);
" L- U& L% l' d' g5 j2 j    /* 调度自定义工作队列上的工作 */
  ]5 j3 L# B8 v3 J- x# F0 |6 i% X    queue_work(myworkqueue, mywork2);
1 l5 [8 F) v7 L! B  U0 \% }
+ U1 D" M; u& L  I    return 0;9 y, c& X7 k" I2 q, m
}5 ]7 m, n2 s2 ?

( @! G$ w8 h6 [1 P% u( Xstatic void testworkqueue_exit(void): S, z' L" f# \- E
{' K; M* v- [! Z
    printk(KERN_ALERT "*************************\n");' R- R/ e; D; Y2 p) |# c- U
    print_current_time(0);* }- {9 P  J* \  D& `
    printk(KERN_ALERT "my workqueue test is exited!\n");
7 g9 H# |+ ^; u7 \. ?. g& p    printk(KERN_ALERT "*************************\n");
/ s8 r  W8 Z. n# k) |        2 m# H& m* ?# {* }2 D; L6 F
}
: a4 g) T1 @, {4 t3 w9 a) \* a% x2 w2 S7 ]; H% k
static void my_work_func(struct work_struct *work); t, Y+ }3 S8 ]- j- n" S! C
{& d& m7 L* P6 C7 M
    printk(KERN_ALERT "=========================\n");
7 }) `' y; Y$ h1 v1 E, m9 `    print_current_time(0);
0 E& H: z7 a7 Y3 W    printk(KERN_ALERT "my workqueue function is been called!....\n");
$ u8 O( @" @8 l; R- J- b    printk(KERN_ALERT "=========================\n");4 }# g; u9 `, A/ n
}5 t& b- U$ a( H& Q. U5 d
1 P" f1 d' J8 z9 z
static void my_custom_workqueue_func(struct work_struct *work), |: [' F3 g' @3 s# S! A/ l: E
{! Y" T. O" l2 V9 U  Z& M. A8 b% e% B
    printk(KERN_ALERT "=========================\n");& e" d2 t* s% f/ G, a
    print_current_time(0);
+ S; h- h9 M; Z    printk(KERN_ALERT "my cutomize workqueue function is been called!....\n");
+ X$ J" b3 e4 s+ C7 l    printk(KERN_ALERT "=========================\n");
; A7 f, z6 z3 T- c    kfree(work);4 Z$ Y  O! s/ \. l
}
: {3 m9 [2 u* W2 r% \4 q! m) W, N* @2 N1 @  q- Z
module_init(testworkqueue_init);, g2 y" _3 R: E1 N4 S3 V
module_exit(testworkqueue_exit);
: o+ q( `. u2 y4 r) P4 g复制代码4 {! o" J' ]; R% Y) \
6 S3 T8 L5 r0 |+ ^

# A9 k( K  U' k/ @# n& q& N$ pMakefile:! b# F! [: W/ Q! ?7 O* v

' A1 c9 c2 `. b" J9 m: h' A复制代码
* o8 y9 F* N% I$ Lobj-m += myworkqueue.o
- Q6 E7 }, w: H. s9 d- D2 zmyworkqueue-objs := testworkqueue.o kn_common.o
5 `* o9 }2 |8 b4 J# ]" ]: x- w4 T
#generate the path
1 M, S" k, G# u0 R. _. XCURRENT_PATH:=$(shell pwd)
3 h; A) ?( x& m+ Q5 W#the current kernel version number
- T4 @6 {' F2 J1 C' |2 R7 _2 oLINUX_KERNEL:=$(shell uname -r)3 d+ |5 q% g3 v2 h% ]
#the absolute path
  C! j8 c- x6 G; nLINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
& Z" M" ]- h$ e( K  D. Q#complie object0 d0 Q2 k5 C: l; l) ^  z* ^2 q
all:
! j/ A( n! L, x    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules9 W' }) i( W( ~3 g$ ~" j
    rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned+ r) l' \. g) a5 w6 `
#clean0 e, p# q) C4 E' p* [
clean:
. j5 ]: t1 k; i3 Z7 @/ ^4 Y4 G    rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
. g6 W. a; M+ {复制代码
) T+ a) `+ ^0 T" P9 S/ } 3 w4 P: @) D$ d8 c  \

- \- w% k# n* O8 @* u测试workqueue的方法如下:4 e% L, m; e9 p- k
' V) N+ G: q6 t: R7 Q; X0 Q4 `
复制代码; U0 R7 O) Z7 b3 B* A: g
make$ ~2 I/ `8 M3 _; A
insmod myworkqueue.ko6 n$ \: s5 I( @
rmmod myworkqueue
3 @( m" P$ y" y# e2 ldmesg | tail -13
; B# v) U0 u$ u- {' L' d* x( {, X, l" J  X4 c
# 运行结果
  O& D4 K& U) R6 g! `! X9 T( m8 r  w) minterrupt's top half!# d) u. R. ?6 ], s
; M' E8 ~, C: ]; J

该用户从未签到

2#
发表于 2020-11-30 18:54 | 只看该作者
Linux内核设计与实现之中断下半部的处理
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-24 20:00 , Processed in 0.218750 second(s), 27 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表