|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
Linux字符设备驱动结构
4 L7 W9 X4 F0 C# t- a! Y: }7 q( j. R; u0 R2 W- t8 V
1.1 cdev结构体5 R+ ] ~4 g4 x. `- N
+ x! `2 c/ B; }9 R( @5 h 在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:
# f& i1 w J5 @' A1 J6 r7 L
% l/ Y6 \4 g) [# d9 cstruct cdev {
0 Q, O2 X3 ^% Z$ t! V
; ]& U9 N5 ~! j: U: r struct kobject kobj;
8 W7 c' k* P: q0 Z3 [5 L% @2 ?) g! K& S! A
struct module *owner; /*通常为THIS_MODULE*/
4 c- c+ D' r# m" i2 E5 j+ p! d( |# g% l2 n/ U2 ]) w
struct file_operations *ops; /*在cdev_init()这个函数里面与cdev结构联系起来*/
- b D2 m s8 U3 c/ t* d( q/ Z" k5 b a+ \0 }# z3 w
struct list_head list;: V; K9 U7 m% Y& A |2 J
\( V6 I: K7 @# h- k dev_t dev; /*设备号*/
/ [, Q9 v6 @; ?; i' o
( j0 L: M4 I2 {, @. `) l- l) g% b unsigned int count;
% B0 A' Q. C2 f; e c; Y% u" U, s
( x( s+ f: h2 j( k. O0 n. N};
# F0 A+ r8 c, |0 b! H0 b7 A7 B% Y: |: Q2 p) W
cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号,我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:
6 B; N8 {9 ]1 U% g0 B% f
0 [7 V% e& f! w! Q* iMAJOR(dev_t dev)( K/ R' f$ t& ^
" o( p* ]1 K0 sMINOR(dev_t dev)
( M% S$ S8 \5 y. A/ E/ B8 N/ @8 x3 { w1 l! t! Y
相反地,可以通过主次设备号来生成dev_t:8 o# f% U# V5 y; l5 @& Z
3 a8 H% j9 U" ]! ]# E3 GMKDEV(int major,int minor)
. i" A7 O% ^5 Y- I) q
6 B% \$ O; I. ]5 }% r- M; r, D! Q+ }7 i
9 A) R0 o% x+ ]+ _2 n
1.2 Linux 2.6内核提供一组函数用于操作cdev 结构体:
. I& O# t- z' h) ~
. A! f J8 A, E; T" Q1:void cdev_init(struct cdev*,struct file_operations *);. l* Y8 f0 | ^, `9 Y, ~
% w! Z7 i9 Q5 K( h5 Y" f9 X8 _2:struct cdev *cdev_alloc(void);
# w3 J6 d+ K' k. N% Z: B3 D0 q6 \/ i2 j5 m V1 z
3:int cdev_add(struct cdev *,dev_t,unsigned);, z0 M4 j+ {+ H& r5 A
# P: ]( _0 K9 C2 L3 q& O8 p: {
4:void cdev_del(struct cdev *);% \( @; }; N: B a4 E
: {# r( x8 i7 h+ z! C) b% j
其中(1)用于初始化cdev结构体,并建立cdev与file_operations 之间的连接。(2)用于动态分配一个cdev结构,(3)向内核注册一个cdev结构,(4)向内核注销一个cdev结构
! ]( D" D: s4 x( q" L, A6 z2 O6 X
: X0 |4 B2 f! P. {3 e6 ~1 V" {( ]1 M1 ^
1.3 Linux 2.6内核分配和释放设备号
/ M1 _$ ^6 b# c4 ]0 w4 A7 h' K1 k; U$ G6 R9 j: F! @
在调用cdev_add()函数向系统注册字符设备之前,首先应向系统申请设备号,有二种方法申请设备号,一种是静态申请设备号:
1 y; I" Y9 W& j5 {0 s" R$ A, L4 @9 B7 v0 t6 q" {) O
5:int register_chrdev_region(dev_t from,unsigned count,const char *name)" K: }8 e' {3 O6 \% K- t. G4 p
& i. Q" i; D/ C/ D
另一种是动态申请设备号:
/ P7 r. I1 E6 r. k$ l" f) \: _2 D0 m+ {2 q5 S* W' J7 C
6:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
i. u; ^) G/ d
/ T) r! Z" X4 Y' M: S- u' r 其中,静态申请是已知起始设备号的情况,如先使用cat /proc/devices 命令查得哪个设备号未事先使用(不推荐使用静态申请);动态申请是由系统自动分配,只需设置major = 0即可。5 a5 P8 U/ S8 Q2 D% x6 G9 e
D4 Q8 l/ W; W$ b* M, s2 j' }
相反地,在调用cdev_del()函数从系统中注销字符设备之后,应该向系统申请释放原先申请的设备号,使用:: k8 C. `1 v) ?7 l! Z
, R6 L3 s+ { C! x1 V7:void unregister_chrdev_region(dev_t from,unsigned count);
2 J+ t9 ^7 T+ h0 {6 g, T8 y
' u% r. h6 z; C. \" W5 _/ d+ s) o4 r0 g+ f3 l+ l
' o7 C+ ?6 J5 j0 |7 Q+ t
: [& W+ Y/ Q3 D2 x* y
" r1 [$ C- U( ], j
1.4 cdev结构的file_operations结构体
7 t2 W7 A" S0 e/ p7 \! i0 x; N! A, A; U" S7 O6 |' i, L
这个结构体是字符设备当中最重要的结构体之一,file_operations 结构体中的成员函数指针是字符设备驱动程序设计的主体内容,这些函数实际上在应用程序进行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系统调用时最终被调用。在include/linux/fs.h文件中定义,这里不一一详解,仅仅解析一些常用的API。
. w+ y% ]& d5 Z$ n9 R0 H3 O- ]8 ?/ C' Q) `% Q- }
struct file_operations {9 p/ `9 d4 }2 h- V
) k/ Y0 N7 N" x4 T
/*拥有该结构的模块计数,一般为THIS_MODULE*/5 D6 [' x. ?5 T- Z7 F
struct module *owner;! a7 j+ k7 ^$ h1 R% D
3 Y! ?: b& l2 }
/*用于修改文件当前的读写位置*/2 ], r( ]* V2 C; e' D
loff_t (*llseek) (struct file *, loff_t, int);
5 a+ h( O: D' Z5 w) I, h2 D+ N6 }& N
* c9 Q" ], ~, W/ k$ U3 Z. x/*从设备中同步读取数据*/
3 X. c, f3 t& j$ N ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
: b% Z# d: ?2 n. O( n; S* @' w @6 D* R, e1 \9 \
/*向设备中写数据*/5 v" y& J1 G; c2 }1 g! p
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
, `& Z0 A$ b# Q' {) j
) s6 o* T# f- Z9 D! I
5 C. r, u1 ]2 ]' I; \. p, I ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
3 G& s7 _' M; A; F/ X$ L- |8 N ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);" \2 b5 p \: h" |$ L- r( Z: r
int (*readdir) (struct file *, void *, filldir_t);' \0 ~. O5 G' ?, O- G. m
$ p+ l$ K( b2 Y3 Z" o% \! S2 o/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
( |4 U9 A4 S7 \/ E0 { unsigned int (*poll) (struct file *, struct poll_table_struct *);+ O5 _( I: z# k/ C, d' w
4 r" i$ i) v) u$ \/*执行设备的I/O命令*/ c4 I) S! I; K+ u/ Q" U
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); j4 }5 A( b+ e5 u; a# s0 L' X
" g7 r$ O; D0 |7 f
: F+ R1 W3 B; V2 A! W8 ~7 o
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
/ n6 K+ l- z) Z2 t" s0 w0 H; D long (*compat_ioctl) (struct file *, unsigned int, unsigned long);1 o6 W, x% W- k/ |
# n# M0 ?* l7 L3 e0 D$ P
/*用于请求将设备内存映射到进程地址空间*/
$ ^- |' v/ j# f. d int (*mmap) (struct file *, struct vm_area_struct *);
# ~! j( G" G9 T3 T& |7 q' C$ ]
3 x0 a0 {& ~* J7 @/*打开设备文件*/
9 F+ _4 O2 `# u int (*open) (struct inode *, struct file *);
6 E! o1 e4 e3 }; k( z int (*flush) (struct file *, fl_owner_t id);1 l2 H1 {8 g; k% j
5 Q" @. [: ]0 l% H8 ^ q; K
/*关闭设备文件*/+ q/ A, J! x C: o
int (*release) (struct inode *, struct file *);
1 c5 e @5 W2 Y1 v6 n4 P6 V, Z: Z) V% k& Q. U4 _9 H! S% _
2 d- l7 W( D* K! M; ? int (*fsync) (struct file *, struct dentry *, int datasync);
8 j) I3 _+ N' A! Y& a* e7 j int (*aio_fsync) (struct kiocb *, int datasync);; c% B" B! S/ G
int (*fasync) (int, struct file *, int);
' U3 }) C3 U% ^3 s+ V4 ~0 s+ s- D. n int (*lock) (struct file *, int, struct file_lock *);
( a% J4 U- X0 N) i E; { ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
) M% v( W+ F8 Z8 i, s& n4 W unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
- d- }! C/ Y e [( X/ d! m int (*check_flags)(int);
! o' @& v: V8 I7 F9 S4 p2 x1 ]$ V int (*flock) (struct file *, int, struct file_lock *); s9 a Q2 h2 h8 U
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);0 f, f' F$ r) y/ G
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
, z" G9 q/ [, T( ^* A+ M int (*setlease)(struct file *, long, struct file_lock **);
8 J+ |0 I9 s( M: s3 V};9 M# Y3 `! P3 G$ l: i* \; ^1 M
/ p" c9 l# x" {7 Y% W
0 Q- F) x) J0 x( W+ G$ V, }+ I0 T! ]! J/ ^
1.5 file结构
7 Q" i/ B' M0 t: a4 D# T' D. n+ F4 `. d0 B) f/ D8 y7 U5 n3 ^; n
file 结构代表一个打开的文件,它的特点是一个文件可以对应多个file结构。它由内核再open时创建,并传递给在该文件上操作的所有函数,直到最后close函数,在文件的所有实例都被关闭之后,内核才释放这个数据结构。
. }% H; @! a6 {" @8 @' u' t9 ?4 x, F$ w! o4 `% C3 X
在内核源代码中,指向 struct file 的指针通常比称为filp,file结构有以下几个重要的成员:! c1 e( H' c/ y1 N1 h6 d' a
) ]2 _4 g0 f7 t. V5 Q* l5 L) ~) istruct file{; {8 ~- C* N6 U% R, X0 U
2 J/ \# c7 s5 L% b6 U0 A o, bmode_t fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/+ v5 @# g" Z9 W1 Q- d, R
0 f1 o6 X; n/ n& I1 e F1 x4 J% m# k
......
9 Y( n. I. T# s4 Q8 J" `
- e2 _0 C0 G; ~. eloff_t f_pos; /*loff_t 是一个64位的数,需要时,须强制转换为32位*/
: v9 J! h# I& o, o. n" E0 p' r. ], F- x% u
unsigned int f_flags; /*文件标志,如:O_NONBLOCK*/
9 B( t, t. j( l2 x" S; b4 {( i$ [+ X
struct file_operations *f_op;
/ j% A3 ^# F+ G: n2 L& [# ^3 r
void *private_data; /*非常重要,用于存放转换后的设备描述结构指针*/
* n, z! q) w* G" F F) i
4 j* b+ P% J k) A" d$ e% i& i) @.......
& P) u& q& M( W" o9 h6 D W3 o7 `9 ^' i2 N q, b; C
}; o$ @; c" ]# ~ H
* H; j. x3 ?; B8 ]
) B% v6 x9 p- Y* r; h4 A$ r' @ F5 C" V5 B. m+ w$ P7 \. V7 `
# K! n! X' ^; i1 S' @
% ~/ C( L+ Z- z& L- z5 j' [1.6 inode 结构
/ Y5 N7 u1 U6 o G n/ L( J+ G& e( w5 h2 `0 Y) C) P7 X
内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:7 A2 x7 H1 h) s4 L7 c& h9 o I; |# M' C
2 E9 m2 m7 i' rstruct inode{
5 t- ?, P- ~- D7 G1 E0 r! W: ^8 g' g3 |4 T
dev_t i_rdev; /*设备编号*/( U7 t# h6 T; r& h7 ~
# }# k6 |" ] U3 M, S) t1 estruct cdev *i_cdev; /*cdev 是表示字符设备的内核的内部结构*/
: A% C! j2 J4 i* F: ]& ]6 h+ t% S, a( r/ e2 y
};/ G. _3 C6 a" U9 t
4 g8 e. L6 { `( R; r7 m可以从inode中获取主次设备号,使用下面二个宏:6 n; } [" c/ ~* r0 Z
( D# j. y7 l" j3 B2 L+ [/*驱动工程师一般不关心这二个宏*/
7 {+ G; U- V* s" L$ B4 y
: i9 K9 W& m6 E$ @unsigned int imajor(struct inode *inode);/ X# \+ z) q: V7 k
7 D& v4 Y- B' \) gunsigned int iminor(struct inode *inode); 4 l; V s, v" ~$ R& X
5 y2 w7 d& M) V4 |
( A) A- Q0 B- {) i8 [
- {& W* P8 c: e8 R- ~; R3 Z1 Q5 j2.1 Linux字符设备驱动的组成
% q" L8 B# T* ^
1 A/ v: H% ~& U- S0 T1、字符设备驱动模块加载与卸载函数/ \$ X' b( |4 L& Q* z5 C: V
; I- N7 Y; x7 `' w5 P
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册,而在卸载函数中应该实现设备号的释放与cdev结构的注销。
' C8 R+ S' V4 g4 \1 g' x& I1 ?6 W
: G* i/ }3 {, a1 w$ l) b 我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:
( B, j8 F! h, ^8 @3 V" U
$ X: o& S# o8 d1 r8 T, m8 y/*设备结构体*/9 F6 H9 d0 _3 e
4 M! A, Y6 X# y! gstruct xxx_dev{
: e# V1 K3 V' m
e. R& O/ o3 t7 D1 }) b: ^ struct cdev cdev;
0 B* R3 [: f# a% t
- A8 b: G2 D% l char *data;
5 v) q+ `1 E$ v2 p. q# v9 d8 r/ b7 V
struct semaphore sem;
. R# R( J& \1 f9 d
+ S7 U& ]) Y3 T: K/ K% y! G ......
( J% |/ U4 z9 s2 k/ Y0 M$ |, J
8 N. G: n- S6 `" N4 R' D/ D/ D};" n8 r- g$ y5 a* h& m
6 o3 e0 K4 m. ~! R5 K& l! _4 f. N
+ x* M, a+ ]$ _: F8 l' y
2 ]( @4 h% _ O/ k. J8 z/*模块加载函数*/
8 T& y( z, R- o" J( z! O1 x$ Y3 U/ z2 Z
static int __init xxx_init(void)3 d8 a0 z$ F. }
5 L5 |. }5 a- M( U( a{3 i" R7 ~" N% @2 T6 O w% X
% O$ x$ B9 r! B! o! I
.......( C: W; E' ^8 Q$ s9 U' C
# e4 Y! D: g2 Y. Z7 G
初始化cdev结构;
! @( R# \; J* P, [0 s$ R J7 G. E; t4 B$ q9 T; n b
申请设备号;
6 h; ^2 k- Z$ ]( F5 Y' E0 v8 j \! T
( B; {4 G1 j: U5 ~ 注册设备号;6 d" i$ w$ j# y' `7 k
0 e u! U, K0 V9 D; P* |
9 T' B I- ^5 I; i% q( w1 f
; U8 ]9 G" ^$ e, P: N% x; J
申请分配设备结构体的内存; /*非必须*/
# z( I. t6 t2 b. \, N" x% {' J! K% U) ]( |8 s
}
) W( Y/ U8 O6 ]$ D$ C, n- O9 V
% S5 M3 H& I, S; V% A l% f0 F" l; d. F3 ]3 w8 j o9 i
6 @( c, _' N( Y x
/*模块卸载函数*/# Q; W2 ?4 D) ^- v
; w- g& z7 e( Z) y, j
static void __exit xxx_exit(void)$ H$ k2 k. U3 j l2 N9 w
& d1 m) j% E, Z{* D3 f8 w! x( |
; D2 k' z" s" B; |2 m
......." b O3 E" z, c1 F& i1 Y" ^
5 i1 B% N9 d! n
释放原先申请的设备号;, v7 V8 \& h V3 I" w+ p
- ^ o7 V& g5 m* G/ D6 p
释放原先申请的内存;& h" F5 J/ u+ P) c# l
: L3 @* |' D2 P# U 注销cdev设备;
" o j5 B/ f$ {$ L- H' d3 x
- S4 m! [6 g$ G# _+ G0 w3 d" s( m}
0 e/ c0 Z3 r" l c% ^6 f" Z! g; F0 q% e( s0 b4 Y
) f, F# W/ n- Q/ P
. x/ l8 R; i4 r9 A7 r+ v% T: h4 q; i8 |1 `2 @
# k8 y$ {6 m) K! t7 c. ^$ q5 a2、字符设备驱动的 file_operations 结构体重成员函数+ H# L3 l+ `0 z( t
: `( b4 P& q7 L4 ~( ^
/*读设备*/
% w2 }7 U1 d, E
3 ?* p/ m% n& u& k+ ~1 Y. {ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)4 m: T! b* p0 h+ n( x3 t
5 k$ t. W3 s1 \& P4 @{* K; F$ g K/ k6 t* {
) p# R6 ^( r4 y* L. y, T6 s. d- U
......
# E+ ~' W+ _: m) o* V1 l$ z
& g2 l2 T# G% m! S1 N- ] 使用filp->private_data获取设备结构体指针;2 U4 X9 K* @8 ?* A0 {
+ Y& m" K" Z( t 分析和获取有效的长度;0 J" f! ?' g0 M% ^
_# b8 N) K5 F% c/ d' S
/*内核空间到用户空间的数据传递*/7 s I( i; L$ [0 f
# Y6 l# x; q J1 N copy_to_user(void __user *to, const void *from, unsigned long count);
9 F& f' P% x g, k* v0 c8 X, h
. y9 ? S4 n$ |" ]: x0 L0 A' | ......
$ D e4 E5 T& s# W3 Q& k. y: ]1 D
}% h( h: Z3 V; g3 O
7 l& E0 c- X5 V* B
/*写设备*/
# U! n2 O. z0 a o' u% ~7 J: \* |; U9 L! d4 T
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
|+ B# z# T* y# l# Q8 Q" q& ~: d
4 I; Q1 w! F, i1 o9 U{
" z! O) v& L4 f9 j( w' g1 ^8 g
( A& F9 w2 z& M+ M* u& b% h ......
" [2 }7 ]" N" F, q3 w1 f9 Z4 U" p q( N! }: k, h' E
使用filp->private_data获取设备结构体指针;
: g5 n; L* D2 k" L: ?- i' \1 A* d) F8 x
分析和获取有效的长度;
6 K; }- y& r" @7 ?* _$ Z
3 e) B% @9 Z4 J /*用户空间到内核空间的数据传递*/' x4 U: Q3 ^& h* m2 [# ~1 [- g
2 z9 n7 D; D- l" I$ K' ?! D
copy_from_user(void *to, const void __user *from, unsigned long count);
J3 D' T. L7 N/ o* a' G
& b7 N0 q/ Z2 f9 y1 O6 m ......% I4 K, J P: Q
/ G/ C" I8 X* ^% ?
}
& d9 `9 d; J& r, g* K) F# |+ q! F% |9 t
/*ioctl函数*/* V l }2 _7 }9 o. L0 k, V
% ^9 H( `2 x! p9 y
static int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)* s5 e' @% R; h6 h( g
- S2 o6 D+ R. z$ f8 q{" M8 v5 j$ j/ T: Y0 `4 t1 j
+ D6 {8 \" k6 S& Q
......
3 q' g. J7 ~# X6 y- |$ u! ?! L
: J! y& r3 L; h* m0 e switch(cmd){
: i! }2 o O: Y# I1 U2 q0 l5 S% T
9 ^/ P* A7 [: k6 E case xxx_CMD1:3 X& @# d( m5 s/ B3 |
$ p/ m1 U: _5 z$ M9 f
......
0 N, z* {. x4 h
' R6 e) l1 w. r: S3 _, { break;" P# `2 H B- \. ]5 A& M
0 M5 ~ z- ]" `+ E0 P9 E3 V case xxx_CMD2:
9 d. B, ^! ?% M r) T0 t8 V
: F8 `, [7 B' |6 I2 { .......4 E% {: R5 V$ M7 }, U7 A
) O. x o$ } y# l; F
break;
4 x/ B+ v$ B1 c0 U2 L& Q, M# y# E6 j$ e: p! m- k: m2 K I0 H
default:! ^) S* B# l* B s) m7 B
7 G; k% B) e+ Q8 `$ K' t return -ENOTTY; /*不能支持的命令*/, V( ]* q, o6 y
1 a% o* ?+ X6 D$ g# B E) C( S }
: D9 y2 `% W, C% h3 D" |8 H% J. F6 [3 S3 J1 k/ s9 y
return 0;
0 ^- n5 g! H9 G* u. w4 L+ s N3 O+ W- T; F
}
7 i7 U; l3 v: ? C, W
5 A4 P: A3 q9 O! }- T6 A6 [/ k6 [ I, M5 }3 f. f
/ e2 S- F, J6 O( T, A0 w6 ]- a3、字符设备驱动文件操作结构体模板) t$ d3 p. v* y6 ?8 {) e8 H- y
) q4 F2 A, P' U7 l' R3 [3 G3 `7 _
struct file_operations xxx_fops = {
4 B+ F% Z# \% w# e
& v9 A H: }0 J2 s K( H3 o. e2 P .owner = THIS_MODULE," \; E! F( A9 O
. N5 t3 d \6 P1 u3 @8 q
.open = xxx_open,
, D7 }% g. @6 z; I
6 N( I# z, d& L( M .read = xxx_read,
/ d# s7 f, k7 o2 d5 ^1 ` p! V8 }: r! E
.write = xxx_write,
$ _4 T. u/ ~, \$ G
) @% F% [1 @. N4 G; {% ^* I! H .close = xxx_release," c& b& N! M. |
5 I- ]) j1 T" [! v
.ioctl = xxx_ioctl,
& R0 [! A5 V4 J5 ^! D- p j6 U
# L8 e9 a, t0 S, E1 I5 e .lseek = xxx_llseek,: l U" ` ~7 [# r# _3 `
. b) R# m0 H$ l3 a- {: N: I
};, u+ ^5 Z3 ^7 B3 e; p1 w
# G8 a) K p; C- i上面的写法需要注意二点,一:结构体成员之间是以逗号分开的而不是分号,结构体字段结束时最后应加上分号。% C1 I. G: g( w* [$ R H
7 a$ j' V+ Q+ c) H4 @9 c
' c I' Y4 ?- s0 U+ ~, Z8 d- T2 ]+ M
结束语:
$ e' z/ n# _# H N0 X x
+ j5 }% r& L) |: L- L+ G 字符驱动的原理分析大概就这么多,下一篇我们详解一个简单字符驱动程序。推荐二本Linux驱动的书给大家,《Linux设备驱动程序》魏永明译,另一本是《Linux设备驱动开发详解》宋宝华著,最后,祝大家学习愉快。- B7 H1 j5 b- _* D8 Z! [0 [9 V
, ^ m- K- N1 P7 Z' b' d" _) `8 G) l& \ Y* R D& R7 h; D* g
5 T! n* R" P8 d. Q |
|