a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 214|回复: 4

[综合辅导] Linux系统管理:内核等待队列机制介绍2

[复制链接]
发表于 2012-8-4 12:07:07 | 显示全部楼层 |阅读模式
flag = 0;/ I  z% `* c) K3 m! k$ D
    9 }4 i$ t% S' D8 A  `
    wp = rp = buf;
/ j! D& n" A8 z8 K3 w7 s8 F" N   
" s" p: t# T7 J3 l% \- V    result = register_chrdev( 54,"buf",&buf_fops );
6 G3 o' u* M  U/ Q1 p    6 I/ s$ N# B3 q) `- W4 `
    if ( result < 0 ) {2 V4 {: |' _2 {6 Z  B8 U! b
   
  I+ Y* K: J2 m0 e    printk( "buf: cannot get major 54 " );
, i+ i/ @: b/ d8 f* x3 h   
2 h  ?6 ~$ x' Q8 i+ _5 T# a. E    return result;1 K% H9 T6 t6 I$ w
    / p, h# i0 W( T1 g6 \2 \: q: [
    }* ]5 G) [7 h+ o% r: p
   
( ]" i  X. E- o8 I, `    return 0;5 f1 U) ^" c8 X: w7 B/ Q6 l) A: W
   
  V$ y  Q, j' [1 B/ ?' V" O. F    init_buf() 做的事就是去注册一个 character device driver.在注册一个 character device driver 之前,必须要先准备一个型别为 file_operations 结构的变量,file_operations 里包含了一些 function pointer.driver 的作者必须自己写这些 function.并将 function address 放到这个结构里。如此一来,当 user 去读取这个 device 时,kernel 才有办法去呼叫对应这个 driver 的 function.其实,简要来讲。character device driver 就是这么一个 file_operations 结构的变量。file_operations 定义在 这个档案里。它的 prototype 在 kernel 2.2.1 与以前的版本有些微的差异,这点是需要注意的地方。
% E2 u1 G/ p  E   
7 \* R, w( l/ E2 w/ j: |    register_chrdev() 看名字就大概知道是要注册 character device driver.第一个参数是此 device 的 major number.第二个是它的名字。名字你可以随便取。第三个的参数就是一个 file_operations 变量的地址。init_module() 必须要传回 0,module 才会被加载。) A: u# k" u- A, a8 o0 U; `  L" P
   
5 u) P7 R$ l' Y2 r# c+ p    在 cleanup_module() 的部分,我们也是只呼叫 buf_clean() 而已。它做的事是 unregister 的动作。, z& y) |; V+ K7 V, n6 `' e
   
$ _! Y* f+ i  o2 ~! [) w1 S    if ( unregister_chrdev( 54,"buf" ) ) {
5 I) b4 q" h) `0 X    # H3 H) n+ Z0 L/ S' n
    printk( "buf: unregister_chrdev error " );
0 e/ M- E( n2 ]5 Z# f0 h( z; |   
/ X, n, v' K8 z6 l9 n    }
- U, H2 h- `; D& Y0 t6 V4 }   
" R$ ?% f. b+ p" M: `3 V+ |    也就是将原本记录在 device driver table 上的资料洗掉。第一个参数是 major number.第二个则是此 driver 的名称,这个名字必须要跟 register_chrdev() 中所给的名字一样才行。* \: q' V$ E8 Z$ q
    " a9 X) I* [; K# g! p" z7 P( @. `. [
    现在我们来看看此 driver 所提供的 file_operations 是那些。
1 z& Q$ E' S- n" R! z    : Q4 Q$ G+ S0 s6 I' T1 S+ P
    static struct file_operations buf_fops = {+ g. [& P5 Q- a0 b, C. n
    + t# B- [! g) n. r* n
    NULL, /* lseek */
8 y  I' G, d9 k* R) z. N) G, r   
3 I3 w; ^0 v9 r8 {5 \    buf_read,
' U3 `9 F9 a" z$ y$ D   
% O1 _( A1 B( k4 f# `2 a    buf_write,
/ H' [0 d7 }* Y- L8 ?0 s, Y) Y) ~/ F    , j8 E5 I$ W4 V! ?
    NULL, /* readdir */
1 y* U- P$ W+ Q    ! ~7 P! N' V0 C$ }+ P
    NULL, /* poll */0 w$ T! J' Q2 I  p& U% \# d4 i
   
8 f. @! X- G3 {" X0 O    NULL, /* ioctl */$ z9 D4 |: Z6 T' ~* G  \- p3 o
   
# M! X3 V9 e# x' \5 e  H    NULL, /* mmap */% D& `6 K* T+ y" I( H& s
    1 o7 N) K$ ~' W! G/ C$ P9 C( q
    buf_open, /* open */
9 f* |4 k! P+ e& }   
( v+ m% X# N; [, s0 B; p1 ~    NULL, /* flush */
1 F! @6 B' z9 p1 L   
. q- K; i! o2 `/ Z" `( _( P    buf_release, /* release */" P3 A7 S, \' @& N- A
   
; }! B: r4 F& M* H( R& o* ~/ I6 a    NULL, /* fsync */
4 n: q3 ], p% h2 T. q    - [! l3 p+ w; U5 u
    NULL, /* fasync */. Y9 o( U# i  d
    6 M; z7 B. |2 \: N) y. J2 e
    NULL, /* check_media_change */
# d$ N$ T* i/ Q# x    & ~0 s$ [- x: |+ Y! q# y' ?
    NULL, /* revalidate */
: @  [9 C9 u+ {# `1 N3 O5 g0 ~  A   
8 n/ [; _0 F( l/ r    NULL /* lock */
! g1 {4 {. l+ \8 P/ z: G   
  @* a4 e; k' q8 G  z+ L$ g# Z    };
回复

使用道具 举报

 楼主| 发表于 2012-8-4 12:07:08 | 显示全部楼层

Linux系统管理:内核等待队列机制介绍2

在此,我们只打算 implement buf_read(),buf_write(),buf_open,和 buf_release()等 function 而已。当 user 对这个 device 呼叫 open() 的时候,buf_open() 会在最后被 kernel 呼叫。相同的,当呼叫 close(),read(),和 write() 时,buf_release(),buf_read(),和 buf_write() 也都会分别被呼叫。首先,我们先来看看 buf_open()。. t) N8 g( C* F5 y0 g/ S" j
    % S# c9 s4 c* |) T/ y6 E1 m
    static int buf_open( struct inode *inode,struct file *filp )
  Z" H6 l4 v' X. b0 U: z/ d1 Y   
' R- r4 i% T/ w# \    MOD_INC_USE_COUNT;
/ x7 t  c; V5 W8 t! A   
  m6 Y5 z- j- {6 C0 b" `0 R0 B& h    return 0;- P! D  v" s; K+ j9 z' c& q8 K
   
- v$ O" I/ d+ M# u    }" X( H+ K' {8 @) R9 s% ^: y
   
3 J- D6 O+ s5 @    buf_open() 做的事很简单。就是将此 module 的 use count 加一。这是为了避免当此 module 正被使用时不会被从 kernel 移除掉。相对应的,在 buf_release() 中,我们应该要将 use count 减一。就像开启档案一样。有 open(),就应该要有对应的 close() 才行。如果 module 的 use count 在不为 0 的话,那此 module 就无法从 kernel 中移除了。
! Q) _* Y' N' Q2 _    ; `& X9 ~+ g% B) S: l/ n
    static int buf_release( struct inode *inode,struct file *filp )
& v" @$ Z5 \) F4 S+ U3 m0 ]/ b   
2 q; o- g# h% n1 h8 z    {
1 P- P+ U) [$ v5 \   
$ o3 N& w  D- v5 {; Q    MOD_DEC_USE_COUNT;  M! j$ N. ]: H' _. H3 u& `
   
. @! v: H* `* P0 x) {# U+ V    return 0;
0 f# y2 j6 [# E4 }    * U6 h" I5 Y# O' g
    }# e3 D  A% U2 Q+ i* M5 I
    ( U' Y8 O; c  d8 R
    接下来,我们要看一下buf_read()和buf_write()。* l- ]4 K% Z' O& @# S5 A. y
    & y0 _) `8 U* [( V4 ^/ |
    static ssize_t buf_read( struct file *filp,char *buf,size_t count,  s! u) f$ F! w$ N" q7 m$ r2 r
   
+ k& E' n. z" C" X3 Y: ]    loff_t *ppos )
$ ^! Z# D3 A( e# Q" a2 ~) s% @9 T   
. r5 G! f7 Q- y+ U. X    {
3 e  T4 a$ F9 n! G   
3 N9 B' N- `% ^- X- o9 j, {2 U    return count;  A; N- Y1 t+ p$ `" z! @  f3 \' y" [
   
! B( O8 a" }4 d' o6 I    }
1 _) {, ]3 _+ b  t$ X. l      v- }0 h2 H+ d  {& {6 R# G1 a
    static ssize_t buf_write( struct file *filp,const char *buf,$ Z. S, j: `: T* G; p: p( d
    5 \% n- h; s8 l. L$ G
    size_t count,loff_t *ppos )3 [' s  E. i4 {
    8 h+ B0 E1 e- [% @& V0 `. h
    {: M: Z4 b4 d) T( x! \
    : Z; n& m: ]# l% W( D
    return count;  n$ B3 X, L, M- b3 k
   
  l5 @5 b- u- ~$ N7 `7 `2 D5 ^    }
9 B" n5 ?% ~/ U# ?0 v# S/ B9 E      P: K6 N: I: S6 ~
    在此,我们都只是回传 user 要求读取或写入的字符数目而已。在此,我要说明一下这些参数的意义。filp 是一个 file 结构的 pointer.也就是指我们在 /dev 下所产生的 buf 档案的 file 结构。当我们呼叫 read() 或 write() 时,必须要给一个 buffer 以及要读写的长度。Buf 指的就是这个 buffer,而 count 指的就是长度。至于 ppos 是表示目前这个档案的 offset 在那里。这个值对普通档案是有用的。也就是跟 lseek() 有关系。由于在这里是一个 drvice.所以 ppos 在此并不会用到。有一点要小心的是,上面参数 buf 是一个地址,而且还是一个 user space 的地址,当 kernel 呼叫 buf_read() 时,程序在位于 kernel space.所以你不能直接读写资料到 buf 里。必须先切换 FS 这个 register 才行。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-8-4 12:07:09 | 显示全部楼层

Linux系统管理:内核等待队列机制介绍2

Makefile
' @5 I( T/ f& w1 v1 B   
' U: `' a3 {+ k" m  f% Y    P = buf
- q' r5 H9 a. H+ k    " p$ J0 n8 Y5 _
    OBJ = buf.o
$ D5 {0 j3 {/ r* T6 m   
6 i% \( |/ Z2 A& _2 W  D* V    INCLUDE = -I/usr/src/linux/include/linux
/ t  B9 a; }0 T    6 y& D+ R2 b: h# Z" {" L
    CFLAGS = -D__KERNEL__ -DMODVERSIONS -DEXPORT_SYMTAB -O $(INCLUDE)6 Y* Y$ O# y  y3 y, u
   
$ _1 B$ z( P7 `0 E+ p    -include /usr/src/linux/include/linux/modversions.h6 A  ~+ e1 s0 U2 x4 s: C. y
   
( H1 `* E; R% \    CC = gcc
; C3 j" w( A0 C! O. b6 \- L4 h    ; ~4 g- M$ `, s* n
    $(P): $(OBJ)
/ u4 S1 z: ]5 t. A4 o   
  Z" m/ ^- y! F9 M* t1 Z- t" s: w    ld -r $(OBJ) -o $(P)。o
3 Q! D3 e& K# z9 c3 r- |   
9 X) ^" ^5 ]; k/ r, p7 G    .c.o:0 U, R9 m, p2 Q$ e) k
   
, |' N0 O! Q1 s& P$ W; m8 t    $(CC) -c $(CFLAGS) $
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-8-4 12:07:10 | 显示全部楼层

Linux系统管理:内核等待队列机制介绍2

if ( ( rp != wp ) && ( count > 0 ) )8 S! ]0 h/ u6 V- C% X4 Q
   
9 T8 U1 v% O- G0 a& C    goto repeate_reading;
* O3 {9 g2 h+ U; ^3 {- M: v2 t  @# X   
  ^& h8 g. k( C$ L* K$ C( O7 j    flag = 0;& |8 w2 q6 S) n
    * D3 W$ }$ x7 [" W
    wake_up_interruptible( &write_wq );: M# E* `! x! e3 ]/ T& I; Q
    1 `/ a" R, e0 l3 u& q8 d
    return nRead;
- K  B! j( ?( h# ?) [   
% x" Y  V' J3 Y+ X5 t6 j! h" K- q8 L5 k    }
; y- J! x4 h# y' S$ B  f) t   
3 p+ L! [% N( [    在前头我有提到,buf 的地址是属于 user space 的。在 kernel space 中,你不能像普通写到 buffer 里一样直接将资料写到 buf 里,或直接从 buf 里读资料。Linux 里使用 FS 这个 register 来当作 kernel space 和 user space 的切换。所以,如果你想手动的话,可以这样做:
1 `/ W7 h* z- g7 h9 T4 y  C   
0 Q4 [! ^$ S- }4 Q7 z$ c. m    mm_segment_t fs;( f1 R+ {7 K' Z9 }
    $ ?, J* Y4 g; A: D2 f
    fs = get_fs();
6 u8 w5 }" X3 y; `    + \) m; s( i' {' [& A
    set_fs( USER_DS );8 g$ `. A, @8 [6 s# g
    & S& c. o( A. Q; C( Q
    write_data_to_buf( buf );
* n0 ?& |" Y. T    5 N! [. l2 B- q: E) W
    set_fs( fs );
, X( t. ~0 g2 e" e$ l5 W    * P5 r/ v0 h: B. B: O' U
    也就是先切换到 user space,再写资料到 buf 里。之后记得要切换回来 kernel space.这种自己动手的方法比较麻烦,所以 Linux 提供了几个 function,可以让我们直接在不同的 space 之间做资料的搬移。诚如各位所见,copy_to_user() 就是其中一个。* j- ~! b& g. w' s
   
/ A( n* w  g2 N    copy_to_user( to,from,n );# ]+ f& h* `  |# B" e
    , M; U7 L. {4 n7 J4 @
    copy_from_user( to,from,n );9 h" m9 s( G8 C- \5 u# Z
    2 ]6 g/ e' G! Y3 {  j
    顾名思义,copy_to_user() 就是将资料 copy 到 user space 的 buffer 里,也就是从 to 写到 from,n 为要 copy 的 byte 数。相同的,copy_from_user() 就是将资料从 user space 的 from copy 到位于 kernel 的 to 里,长度是 n bytes.在以前的 kernel 里,这两个 function 的前身是 memcpy_tofs() 和 memcpy_fromfs(),不知道为什么到了 kernel 2.2.1之后,名字就被改掉了。至于它们的程序代码有没有更改就不太清楚了。至于到那一版才改的。我没有仔细去查,只知道在 2.0.36 时还没改,到了 2.2.1 就改了。这两个 function 是 macro,都定义在 里。要使用前记得先 include 进来。
$ x$ Q" q1 v/ p+ n8 O1 v( l! l   
0 h! t4 _7 a1 L- r# V1 y" ~    相信 buf_read() 的程序代码应当不难了解才对。不知道各位有没有看到,在buf_read() 的后面有一行的程序,就是. J5 f4 C3 ?0 k% O9 `
    + }& P; ~0 E" k; k7 C2 ~6 W8 N# M
    wake_up_interruptible( &write_wq );
# A2 C& Z' k3 B0 ~/ R6 h) t9 f    4 E1 h3 I2 ?: d& F4 M1 F# z, F1 j
    write_wq 是我们用来放那些想要写资料到 buffer,但 buffer 已满的 process.这一行的程序会将挂在此 queue 上的 process 叫醒。当 queue 是空的时,也就是当 write_wq 为 NULL 时,wake_up_interruptible() 并不会造成任何的错误。接下来,我们来看看更改后的 buf_write()。
% Q, V9 ~1 R% V8 n/ w    ' y1 z( R, t" e+ }: l9 \
    static ssize_t buf_write( struct file *filp,const char *buf,size_t count,loff_t *ppos )
: u1 Y% s" N! m1 b   
, |9 _$ l) W5 v  \    {
! V/ ]2 y: [4 H' J  D" B" \    , f  r# ]- S- d$ Y2 _
    int num,nWrite;
5 u2 `& F0 p6 q9 \   
- J+ O5 z0 ?5 w3 x    nWrite = 0;
; a1 G- P/ v- u+ w/ d    ( }: n' K2 @3 z, b) }
    while ( ( wp == rp ) && flag ) {6 r  R! h- V3 r( b: Z) h+ v
    + j  Q5 Y( f( Z) v3 n1 ~% g0 g
    interruptible_sleep_on( &write_wq );+ C0 N+ _" R3 V0 n" q& e! e6 I
   
) K2 r. O% V, R1 T- K    }
2 @& S3 b2 G6 k& [& B, B) l$ _$ w   
5 O% ^9 l; _1 X7 \1 j4 E0 |$ l    repeate_writing:
' \( k4 y- n* V( e1 A! c   
& x$ E4 J# o# b    if ( rp > wp ) {
0 ^3 a# }+ H( n2 U% M   
, C! A- v6 [: l- I/ H    num = min( count,( int ) ( rp - wp ) );3 ^' [/ |' S- y! w+ [
   
0 `; X# T/ p9 l, w/ @& E  X7 E    }* T4 G% p  Z- f/ I
   
/ Q; J  J( U( L) {' m) Y8 X    else {! s! d9 T5 s4 m0 S7 C0 k
   
( G/ i% e& U" {) w* [8 p6 h! q    num = min( count,( int ) ( buffer + BUF_LEN - wp ) );  c* }3 R' l0 u
    % _# Z' O# q* q: \+ U% V" L
    }1 t# l+ |' Y9 b4 ~
   
' |4 H$ E7 u7 d: n* ^    copy_from_user( wp,buf,num );
, F8 ]) g6 B/ ^  J0 L    8 {9 O9 W6 w8 q0 K* m+ ^
    wp += num;
7 r' s0 I/ h. P5 y    2 X/ S- K: U; G
    count -= num;
: q, ~# a. N4 R# q4 a# x) Z$ K8 r   
% P4 [4 O3 _. Y    nWrite += num;
& I$ E3 k5 a$ T( [3 u   
4 d: p) ^: M5 [5 z8 v    if ( wp == ( buffer + BUF_LEN ) ) {) ^; d$ L% o; @$ g
   
# P0 n7 E/ n* p( Z8 m1 C: l    wp = buffer;
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-8-4 12:07:11 | 显示全部楼层

Linux系统管理:内核等待队列机制介绍2

}5 q, B4 A* \  Z8 s# j3 J0 n
    7 Q3 a' W& M  K! e
    if ( ( wp != rp ) && ( count > 0 ) ) {. }' b2 f) `( G  t+ s
    * A$ @5 s) X, x. J# e: e, Z
    goto repeate_writing;
# e' D% L# r& I' C$ _  F# O    $ @1 \# }$ E2 f4 ~2 p
    }
+ k! p$ Z6 M" \7 K   
  U; y/ M; r/ \8 `) D1 C7 ?    flag = 1;2 ]" l' W% u! T2 t- ]1 x
    ! k- _' h3 y& A7 v* }) Q
    return nWrite;
9 x% G/ J4 R2 t! e$ s! q   
& d( H7 M/ a# z+ m1 J% y: c    }' j8 k+ n+ C; ~) s0 k! h+ v
   
- h, l) Y) g  b4 h) ^3 J    我们把 process 丢到 write_wq 的动作放在 buf_write() 里。当 buffer 已满时,就直接将 process 丢到 write_wq 里。
3 a8 b; `. Z( J0 Y: J0 `, Q   
& f, P: [6 u7 v# B    while ( ( wp == rp ) && flag ) {
- |7 H$ y) e( o6 j, X   
/ D) U9 F, }6 ]* }3 q' h" J    interruptible_sleep_on( &write_wq );
1 d3 i% p5 d0 E  z0 p. B/ m/ v    , q) w2 G' q9 f1 U/ c
    }" [3 o4 o3 X4 \2 v* ]7 Z
    2 [/ z6 m6 x/ ]' z
    好了。现在程序已经做了一些修改。再重新 make 一次,利用 insmod 将 buf.o 载到 kernel 里就行了。接着,我们就来试验一下是不是真正做到 block IO.
; A: r0 d- U. P5 P; i8 b   
- p$ `0 |: E% G! R' d    # cd /dev2 b2 f3 L- a- t1 V# F9 o7 v
   
& H- _% D$ G" {. v    # ls -l ~/WWW-HOWTO
- K% j* t3 d( e0 D) b" E   
, H! v# o8 |( v4 Y: U    -rw-r--r-- 1 root root 23910 Apr 14 16:50 /root/WWW-HOWTO/ b' G" A. t" D2 z- a7 U7 ]
    6 C+ P1 D# k. m1 c" z8 i
    # cat ~/WWW-HOWTO > buf3 m3 w0 _! U0 x& z- J8 l
    # {  H8 i; _" \) D' a+ |' A4 t9 v
    执行到这里,应该会被 block 住。现在,我们再开一个 shell 出来。
: X& `$ g$ R- o' c1 J$ `* T   
/ E) g. J- b) J& G    # cd /dev$ T' l: Z) C; m* t$ W/ i) e
    ' u  H; V0 e+ t
    # cat buf9 E7 c6 H. G# z5 ]* C3 x
    $ u. _) f3 r+ A3 ?! s0 W6 N1 X
    …( contents of WWW-HOWTO ) …skip …
) g6 r5 a/ t' i4 C3 p: z% f  c    * T: L9 O% d1 n1 ]7 W
    此时,WWW-HOWTO 的内容就会出现了。而且之前 block 住的 shell 也已经回来了。最后,试验结束,可以下8 v' h# }; Z) k( U% l" \
    - L( S3 p) k, G$ |. b! A9 @
    # rmmod buf. o- \8 L2 L, J8 |# a+ x1 [
   
, t# W- q& i' `  F. q    将 buf 这个 module 从 kernel 中移除。以上跟各位介绍的就是 wait_queue 的使用。希望能对各位有所助益。
8 H) R1 O; j) t/ g- N0 ^# p" h    # z9 N* B* l' B- K* h% Z( x% T
    我想对某些人来讲,会使用一个东西就够了。然而对某些人来讲,可能也很希望知道这项东西是如何做出来的。至少我就是这种人。在下面,我将为各位介绍 wait_queue 的 implementation.如果对其 implementation 没兴趣,以下这一段就可以略过不用看了。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-6-3 02:21 , Processed in 0.256373 second(s), 30 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

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