|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
Linux字符设备驱动结构: M5 m b! D2 X3 ^0 n2 Q; c
: g) o* i% I* w$ \" K4 S* Y
1.1 cdev结构体& _% \2 H- f1 M5 l5 T3 D/ Y
x" G; T" r% a) p- g( x4 E 在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:$ R9 h6 S, J' B6 z* Z- D4 S
* ^. m- B+ ?9 Estruct cdev {! H. e8 a5 Q4 B4 d0 z
J6 v8 z% H9 s* W, _# f, l
struct kobject kobj;- u3 _/ J0 J4 A, w
# I$ F/ v- ^0 L struct module *owner; /*通常为THIS_MODULE*/. l( N' y! u" E4 r/ I
- E( c" k& U" ?5 J
struct file_operations *ops; /*在cdev_init()这个函数里面与cdev结构联系起来*/
' V1 O6 Z& i% k2 v4 U# }( `1 e4 E0 e. w7 A
struct list_head list;
7 }1 C6 P7 r% r7 M: A- k6 m, _9 C0 |. x
dev_t dev; /*设备号*/
2 M* z9 }( l5 a* @* y6 P2 C9 R7 O' _
unsigned int count;! Z; z$ @4 S5 ~# Z
2 T4 l. \- m) ^; |% t. J};
; O! s$ J* M- y) k) O5 y, D8 e9 O' R" s
cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号,我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:
% T3 r5 V2 S- _, @0 s6 b' v8 P& Y% o# b( Z" P4 S! O: o
MAJOR(dev_t dev)5 q2 `1 D/ A% ? G2 C L
: H! ^; K+ B4 i2 y3 E `. FMINOR(dev_t dev)4 ~% o5 V5 {( `
4 s7 Y: G& Y1 D相反地,可以通过主次设备号来生成dev_t:
5 \4 `4 J1 v) |
% k( @7 A. V' e: m4 F' o/ nMKDEV(int major,int minor)
" G" t8 R5 U6 R( O5 D, T/ k2 `
% k. Q) h! c7 i# w/ {5 a% Z8 ~
# s2 l/ j, i$ r/ t' K1 R4 M% t, `; ^9 q- Z, O
1.2 Linux 2.6内核提供一组函数用于操作cdev 结构体:
. |6 ^# k% I+ c7 [$ j M8 l
( t/ w8 f5 J4 |! [# t. u1:void cdev_init(struct cdev*,struct file_operations *);
/ K# o1 u6 L/ V5 P, Z' P7 u: y B
2:struct cdev *cdev_alloc(void);
: V! t3 o* ~. h. @" K2 _. E) Q" k0 Z* Z
3:int cdev_add(struct cdev *,dev_t,unsigned);& x" F1 @- h) W& H C$ Z
8 G: Q1 g. n! L% W- c$ R4:void cdev_del(struct cdev *);6 D: [, R! H [! t. C
2 x5 d- R' i, z6 x1 @
其中(1)用于初始化cdev结构体,并建立cdev与file_operations 之间的连接。(2)用于动态分配一个cdev结构,(3)向内核注册一个cdev结构,(4)向内核注销一个cdev结构& V! X5 J, O* h" d
0 T) b0 ^7 s+ a* I) A) w. X% Z: N! X7 W
) |- J$ G" Z! n4 q* h( v
1.3 Linux 2.6内核分配和释放设备号; t) z- t. s6 w7 W: G
6 h9 V2 j) R/ I0 u: [: w 在调用cdev_add()函数向系统注册字符设备之前,首先应向系统申请设备号,有二种方法申请设备号,一种是静态申请设备号:0 L+ H1 D% C. K/ x1 t4 v
2 i' N, I: c9 r
5:int register_chrdev_region(dev_t from,unsigned count,const char *name)
5 J0 n I% @5 x2 }5 `; q, D# C$ _6 ?" b# b" [) W
另一种是动态申请设备号:
2 I# g% T0 i1 g! h8 t. Q
; L4 R$ A* J) Y, X6:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
/ b; _" U) v9 a" U4 F5 H |: P2 \! v
其中,静态申请是已知起始设备号的情况,如先使用cat /proc/devices 命令查得哪个设备号未事先使用(不推荐使用静态申请);动态申请是由系统自动分配,只需设置major = 0即可。
6 o) x- \) o+ w7 ?8 X* s# j) U9 \8 J* u4 o: _# g# ~
相反地,在调用cdev_del()函数从系统中注销字符设备之后,应该向系统申请释放原先申请的设备号,使用:
* j6 K( T% l$ v+ {. B3 K1 \+ J! ~
7:void unregister_chrdev_region(dev_t from,unsigned count);
/ G* P( _% g. @1 H2 J$ b$ |+ s1 f4 i3 u3 V; y) S1 q
. ~. t7 G- |6 R, o# ]7 W) j1 a
+ ~8 w$ v Z1 v E/ x# x" S7 a9 ~9 s: B3 V
- B# C }9 z2 _, l1.4 cdev结构的file_operations结构体: ]6 ~0 n( A' Z& M0 l
: }9 u/ ]# q& T1 r1 ~$ }9 C 这个结构体是字符设备当中最重要的结构体之一,file_operations 结构体中的成员函数指针是字符设备驱动程序设计的主体内容,这些函数实际上在应用程序进行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系统调用时最终被调用。在include/linux/fs.h文件中定义,这里不一一详解,仅仅解析一些常用的API。7 E3 x1 o/ W: ~ y8 O
7 x0 A/ d# Y1 o2 t- b( i: b+ ]. C& T
struct file_operations {- Y3 R ^# p7 U6 b' W; o
' G& r* e& y) B$ s/*拥有该结构的模块计数,一般为THIS_MODULE*/) C* o! L1 F2 x9 {4 b
struct module *owner;* g# T& i% b' ]
& T6 \6 T- ~% y+ j: m q! E" J4 o4 p/*用于修改文件当前的读写位置*/+ h% H9 |" f# u4 }$ _4 Z9 |2 ]
loff_t (*llseek) (struct file *, loff_t, int);
- U6 w( k% |6 l% r# C
; K0 ?9 E+ W8 }5 R. m: u/*从设备中同步读取数据*/7 ~7 M) ^& O$ {- l8 a# S/ ]* F/ V3 P- y
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);; F! `, V2 a5 K6 ]. U B5 m4 I* W8 t
7 D3 t/ y. U1 E0 R" D! p$ v9 z
/*向设备中写数据*/
, B5 z8 h. l A1 V ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
; @: Q: z( V) V: N5 {# e+ p/ f6 c8 i$ I
& t8 b" e9 ]" O, `4 O ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);' \: w- M2 q P8 t+ Q
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);1 e/ _' A0 \% L+ ]+ S8 I1 S
int (*readdir) (struct file *, void *, filldir_t);( P$ W4 `& X' k& l9 D
4 O8 S+ n, N0 ^* A: I/ s/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/* _, T$ G a) C+ X
unsigned int (*poll) (struct file *, struct poll_table_struct *);
+ v; t, U* m* ?* W
2 m8 e& a- G- e; h4 c2 U; g6 z/*执行设备的I/O命令*/
1 |, W4 m5 C2 Q" k; n! k int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
; r# B% e: d2 j& ]* l
/ e, y; f6 p6 a; u @& W5 D% g! c
% u5 h! q$ K% I U# s long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);$ O" z M5 }& U2 E1 Y
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
( N& y( h% M/ } v# B/ }0 a; a3 J1 ]4 x
/*用于请求将设备内存映射到进程地址空间*/
: p5 Z+ f5 L+ a int (*mmap) (struct file *, struct vm_area_struct *);' X c2 L9 V# G+ z
3 p; C3 C0 ?* p/ ~' ^: f
/*打开设备文件*/
6 p0 \' ~0 i- G0 p1 z# }; p' B int (*open) (struct inode *, struct file *);
& u, O* b4 w- d0 z. k: j! L4 h q* n int (*flush) (struct file *, fl_owner_t id);2 Q- o" b7 _( g$ |/ H! s
, F& V O) M5 F/*关闭设备文件*/* Q" _2 i- k! u; [! V H
int (*release) (struct inode *, struct file *);
8 V; E( H* j. S `1 T: H
3 d7 X( W, N1 j. X: o8 Q6 M4 a5 x6 h( e+ e
int (*fsync) (struct file *, struct dentry *, int datasync);
3 i v6 _2 G# Q6 R# D3 g1 K int (*aio_fsync) (struct kiocb *, int datasync);5 G, X+ h; l# n! x$ k
int (*fasync) (int, struct file *, int);4 I; o2 s! T/ t# `7 G* i7 Q; G
int (*lock) (struct file *, int, struct file_lock *);( } S: P6 @/ j% r! C- e7 Z1 y4 |
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); c- p+ ]/ H4 j3 N+ i; {
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);2 i! [5 i3 z6 x& B: z
int (*check_flags)(int);& K ~5 \- Q* k# H
int (*flock) (struct file *, int, struct file_lock *);1 D4 u: V/ P7 o
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);' E8 z3 _* B9 p6 @" ^5 @6 K
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);, ~+ k) Z2 U; |: z
int (*setlease)(struct file *, long, struct file_lock **);) \. k, H1 X7 u* m5 ~0 d8 \
};
/ o9 A( z' ~; Z" g0 D& S0 E9 @3 L# A F2 f
( z3 o3 j2 p0 M' y# L4 P
4 _4 Q' O$ @& L$ J) Y( q& F1.5 file结构
+ k9 F, A8 N1 s. e9 ?9 m; \% J. C$ a% U @9 d" [
file 结构代表一个打开的文件,它的特点是一个文件可以对应多个file结构。它由内核再open时创建,并传递给在该文件上操作的所有函数,直到最后close函数,在文件的所有实例都被关闭之后,内核才释放这个数据结构。1 `1 n* Q( ?. S! H X
1 w0 T) Z, @+ g! b; [ 在内核源代码中,指向 struct file 的指针通常比称为filp,file结构有以下几个重要的成员:$ Q; t/ m) ?+ {: u; r: S- l6 |
! V7 {% Q; m0 qstruct file{
! [" t. v/ X5 l' S
7 J v2 m7 T v ?* p. cmode_t fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/* [8 R6 e+ T6 K, A) ]
i% ?9 K7 z* Y8 Z0 ]% C......
+ B: K: `" w0 N' r: @( z2 F. U3 s: W. w
loff_t f_pos; /*loff_t 是一个64位的数,需要时,须强制转换为32位*/
% O5 T; i2 Z' N$ R
3 U3 b, m0 H4 g% J/ Eunsigned int f_flags; /*文件标志,如:O_NONBLOCK*/
7 ]% L) a% v$ Z( U1 W1 y& t, j' J# \' A2 N% a8 C" `
struct file_operations *f_op;" n( B# D% \" d) \, ?6 D& W- \
) e6 k" c$ m1 I) _$ @0 [7 Avoid *private_data; /*非常重要,用于存放转换后的设备描述结构指针*/
5 ~2 s+ f v. A0 B+ B# l& N" e9 l
/ e( N+ I, X* S4 g5 g.......
' N6 S% o/ y% d& u) H- @
! T4 B% w! W: J/ ]- q, I4 K};
6 J+ D& |3 @! J6 d
8 U+ S/ `- h3 }9 [
3 m5 i# c8 S! @0 m1 Z- P p3 Y! o2 N" f: P7 V- W+ n
" k# K! V! @& E b: }+ ^& T9 K6 w. u
; \4 f% ]: d6 `6 `$ m m
1.6 inode 结构
+ B2 R. g9 \- P
8 F- L0 s( Y- M! A: ` 内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:3 r5 Y; U& r- m: ?
E+ q4 w! k- S
struct inode{0 V; C3 d6 W3 ?; W( [$ l
7 W5 T9 P7 q% D& p6 V" }dev_t i_rdev; /*设备编号*/
6 b0 _0 W( `8 Q. q4 x& o
6 q% C1 D: B6 r3 l( Istruct cdev *i_cdev; /*cdev 是表示字符设备的内核的内部结构*/
& ?% Z$ F2 ]1 |( d8 H
% q& Q4 c! ^& }5 b6 h};: h3 A/ H) Y* C8 T. i
& y! n& F2 c; a6 O5 v9 h可以从inode中获取主次设备号,使用下面二个宏:
' N' R: z0 s, q4 q3 D2 B7 } {3 S- I. @3 O1 q9 @0 s
/*驱动工程师一般不关心这二个宏*/
) h$ |7 O, z$ J% f* x
. h7 j7 _* J, _) r$ K" `unsigned int imajor(struct inode *inode);! {( R- X2 i7 g6 b0 e! X3 ?: I
0 B+ f! T% O( h" M
unsigned int iminor(struct inode *inode);
+ I# D. V0 v+ Y8 L
; H: r( }, Z+ O- c
8 c- {0 A! I- A; @. q$ h' O2 X( [( r! u5 O
2.1 Linux字符设备驱动的组成/ z$ L8 D( p: i% z% L
9 f! e. n) n2 D$ K
1、字符设备驱动模块加载与卸载函数
' Y# K+ c! x7 _ b/ h$ a! g. y: N4 I* b5 N7 B) Q! Z* S5 b
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册,而在卸载函数中应该实现设备号的释放与cdev结构的注销。1 L- a8 \' I. @4 r& S
# ^7 z# s) o$ n6 Q
我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:
6 R7 ?" Z# c( n W6 l) f, Q% v+ t
# F/ v* _4 j/ i! X- k. e* e5 A/*设备结构体*// _! n0 W9 g+ ^5 T+ o O6 x9 e
+ Q9 i: h/ g0 }4 O m& Y8 X3 T* O% Kstruct xxx_dev{6 D. k$ p5 Y3 b, F. U
. i& P5 A. ^% C$ w( n struct cdev cdev;
$ k! b' ?1 i' l/ |: q. ~2 N% l
char *data;+ ]6 B5 v: U. Y% E2 O
2 B( M) k3 N- }( } n1 ^
struct semaphore sem;
- k% x8 n$ R( j! g% ~- @ p2 Y7 i- N$ A% Y( P f- F
......# E) J; i6 r" F( m& I8 F/ {; F
5 F$ V% H; R: r$ l* ?
};
! R/ D, h) g3 t) i! R. j; E( M! B0 w# o2 }' m: V
7 Q* [2 J0 L/ u* u
m; T. q) b! D2 q' B' V* r, G. C- _/*模块加载函数*/
& |: |1 E6 j0 h n) _* E# j( [1 }; O" u, w! l! O+ G2 ?4 u5 n: Z
static int __init xxx_init(void)
8 j! q9 B$ w0 w5 e* c! ?+ L+ A6 }+ ]* y) u- m& V" t4 ]
{
0 ]$ o0 h4 b' o3 H9 B1 S! T9 S& f* B6 V) z6 X5 X
.......
s: C, `5 W: P. _
: O# e8 ^& I8 g# w3 J" B 初始化cdev结构;) ~5 f* e( R; u0 [: k8 \
2 j+ n" c5 ^8 R8 ]' Z' Y
申请设备号;/ a' W$ K. W @1 N( N: a# W1 t
4 |8 v" R! X- q4 Q* V6 l
注册设备号;$ S$ d" H, }; Y* L
0 J( _2 C8 X# s# J: h, p! }3 M2 W1 Z
4 r" v1 A% H+ d
7 G0 E1 I" y, E 申请分配设备结构体的内存; /*非必须*/
5 ` I# p8 h2 | I2 \' j: a
+ z9 h0 ]2 L. M" T}
5 N0 v4 H% ]9 J* }7 D0 K+ H+ w1 {9 `
0 \* p4 k* n1 w: c+ @
: [0 e9 c9 q4 t, i4 @7 f/*模块卸载函数*/
: L& E1 v. U. B3 C$ x( B& J5 E1 ?+ [' M" U
static void __exit xxx_exit(void)! g) X2 ]8 U: Z L* i$ u
' P$ h' n/ n* c+ E7 n* ~" U3 g+ Q{; i4 F! R5 {& U5 E* M9 }
, l6 C& L F) H/ ]- c; E. j
.......
& ^2 j& ^. d& g- T9 W" h
1 d3 ?! @( M! F! y! G+ ~ 释放原先申请的设备号;
9 n6 T8 S6 E( W: B8 e
, m6 o' M/ I5 g: W4 f 释放原先申请的内存;
$ O8 x6 ?7 {* D8 J5 d' Q/ y) `7 N$ g
" E, M9 U9 j8 U. I, o: R 注销cdev设备;& ^8 \' c8 U9 ]7 Q5 W6 Y
4 B) I2 g' Q9 V) T: d7 [}+ @+ }+ ?4 ^6 Z) X4 `+ N
3 N& f8 P8 e8 o" E3 f4 J ]/ Z$ k7 v0 B: H( y0 p
2 w; r5 |4 A1 v6 b
8 q, Q$ ?* L4 K( F, e
* N$ k% ~4 f& S2、字符设备驱动的 file_operations 结构体重成员函数: w, |1 f& X- T
0 i) T6 A/ N2 M2 I A" I
/*读设备*/
: d) z3 \: _" t) s$ \* c$ C N# ^* s, d& m$ c
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) g$ S! O- G! z% F+ h- h' F
2 D7 ], A/ U8 W) L3 ~{" |5 J. u8 E1 |9 }7 g$ c
( Z2 L$ {! F8 ^3 a& b* l ......9 {2 t6 W) Y5 e/ Z
3 ?: N. W1 i5 g* U* `* r0 l' E 使用filp->private_data获取设备结构体指针;( B9 Q# f8 Z4 r
5 y" l7 ]' Y# d0 _, v% ~ 分析和获取有效的长度;4 Q) `9 d, E& y9 R5 S* k6 f* }
3 @ k" e" l; M! D) V /*内核空间到用户空间的数据传递*/
- M9 w5 J" X8 E; I( y" P
' L1 B$ T0 |! ^: q% y9 @6 i7 R5 m copy_to_user(void __user *to, const void *from, unsigned long count);
& A8 C9 u1 r% Q/ m& J8 X( u [
# }) n+ N6 _ ~! n& P& f) p ......
/ P2 N+ l3 J2 U) N6 t& P) T' e/ e3 Z
}
- }4 }5 V% E. @- r& ~7 E1 {( @# U: v% g6 O$ h9 W5 S2 S
/*写设备*/. z! ~6 F7 P0 m8 F; Z/ v2 C5 }
+ X0 f# [2 {6 ?: tssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
6 l( E! m. |; c: w% D- v. \) M1 |% @5 o! Q2 F, f
{+ A! x6 }& F* M3 O0 Y% s8 ^1 l
7 C0 k, F. b; `- `+ ~ T& a9 s" F ......
2 j$ i/ u" F$ m' q% T$ P5 |
6 X' A; x, F/ k: A/ h% ? s 使用filp->private_data获取设备结构体指针;
/ w/ _% y* P( I. @; r+ Q6 W; ?0 p+ \$ n7 I2 y y
分析和获取有效的长度;# Z& ? h' G: {3 f2 Z/ @0 A
! f, T3 N& [5 a3 z8 } /*用户空间到内核空间的数据传递*/
# H4 i* q2 x% H$ m+ W4 i, w
& F$ Y; u& {% D copy_from_user(void *to, const void __user *from, unsigned long count);
% q9 C) M+ a+ b7 J A& x n
! `/ P, _! Z) H6 Y: [ ......
* [3 y4 r6 _4 c2 [% t( I1 M; A: p
}) _9 r1 Y, I+ h- w& G1 z% E
, }8 C1 c% I; Q& I3 K/*ioctl函数*/8 t, U/ w. E7 h; ]! Z. _
( s, V& E! l3 m) u' M" p; o0 _
static int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
: P( p$ n" P% M' W9 n8 V: l& [ h7 N- q# S
{# K A: S, f' q% E$ m" |: H
9 A- F+ O3 ?# y! b4 U5 H. ]
......
. B& Q- K7 e" i/ B ?' w* y3 O1 j1 Z4 S8 w' s5 g8 [ u
switch(cmd){
& ^4 `( w+ e7 s+ Z" r7 {+ {
. }' i# E0 W h K; [; ~: H) n% z case xxx_CMD1:% J9 R+ c" [7 K" y6 [
) V4 ]) d5 G0 K
......
0 G' V- W0 q$ q/ z# g: S% @* _( w% l. |
break;+ i7 X- L# h0 o+ |& a
2 B% B/ i6 y6 o. ~& ?* W% f* x case xxx_CMD2:
7 `: p- W z5 |) T8 E& x& T& `9 M7 ?0 l5 L
.......4 B- g# \9 O0 \; P' R% U" ?2 B, y
1 k: c' C1 m% w! ]' w0 Z+ v. s t
break;
4 `* N ?6 m5 x' y; M
( }# F8 ?- x3 e default:2 K8 o/ E1 Q' `7 F3 ]
. l( E" C0 V5 B8 c5 u
return -ENOTTY; /*不能支持的命令*/
" R; Q% o. o0 M: E2 ] }7 P. \! u
+ H% ^9 A4 L! t7 Y }0 {( N4 j1 D5 E) [5 g- a7 |9 ?
; X1 d, U+ d( V) B& A3 r+ @$ l
return 0;- {7 O" J4 R% F1 u
: y+ h# I3 c5 _! f ~}& r- S1 r$ V- l( O5 P! C
0 N' T6 F4 m( C$ }# a( [% c
7 T- _; ~$ N+ M5 k$ a6 ~0 P* M9 y2 B
( J" w3 t/ S! X* f3、字符设备驱动文件操作结构体模板
( c+ k: V7 k1 d+ }
4 |' c( b& X1 J# d; \9 K7 }struct file_operations xxx_fops = {2 n# ` J W9 E1 u: b% R
/ b' o2 l3 Y: Y .owner = THIS_MODULE,
, _1 Y3 K M* C4 i. X& T+ s, B- q8 v) f3 Y
.open = xxx_open,& W* \$ b& H5 i1 w8 d
' ]: X' l% c& ^
.read = xxx_read,
9 m7 q8 g* B3 t: _
* a( f( i3 |' J2 U- Y .write = xxx_write,8 V l2 b' V0 `! P- {( M
3 K/ n* m( M7 x
.close = xxx_release,. H6 _: I5 Y8 \7 x
' `: H8 j1 w- y5 T: \5 ^8 G
.ioctl = xxx_ioctl,
9 y7 d: |7 e/ X9 U! H8 V$ n! {7 F; z2 D
.lseek = xxx_llseek,
) u) H) O {% b S
P$ S1 D: @7 W1 U};0 ?: J2 }4 } i$ U2 U
" D2 c) C& v F# a3 _- _) M$ V上面的写法需要注意二点,一:结构体成员之间是以逗号分开的而不是分号,结构体字段结束时最后应加上分号。$ |. l/ d. X0 V; Z* O$ f h* y1 L
0 [3 V& c1 W2 o8 Y& `) ^# N, J: t8 K: G4 c6 S
8 X) M7 v; w1 U" Y3 d* [结束语:
5 S$ D+ v7 I+ L1 F" x. h7 h, M8 E3 ?7 ~( Q0 X3 M
字符驱动的原理分析大概就这么多,下一篇我们详解一个简单字符驱动程序。推荐二本Linux驱动的书给大家,《Linux设备驱动程序》魏永明译,另一本是《Linux设备驱动开发详解》宋宝华著,最后,祝大家学习愉快。
5 _. h' N; y: Q! ?+ F+ d3 s: h% B( i% N3 r2 m; \0 `, I+ L5 W
Y! |8 x. @7 d" A. K7 M0 [" a+ j- B4 O& A1 t
|
|