|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
Linux字符设备驱动结构* Q/ V# x9 d! F
\$ S% ~! z4 P& A _* Q
1.1 cdev结构体
& h, R, K1 b0 I: k$ Q) n) l$ |. d9 {5 n5 j% n6 g8 S
在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:0 A' U* _+ A# h$ n5 M- b( e0 Y
6 T" t$ Z$ h/ t/ O) t4 {
struct cdev {
+ Y' L! P/ h; F& ]0 C
8 U! A0 s" Z' c4 z7 U, Y struct kobject kobj;# A$ i# D) X0 f1 ~
% l- E1 [: u4 ^' Y2 W struct module *owner; /*通常为THIS_MODULE*/
* c/ A+ x v3 H: d5 A2 ]
9 k# B( n5 e' {. c- t struct file_operations *ops; /*在cdev_init()这个函数里面与cdev结构联系起来*/+ D. \' W: }" ~) e
! m' i$ T' F. {% H7 L! ]
struct list_head list;; O9 X0 Y% A Z1 a( W6 P
e$ P2 g8 L. K4 ?$ J
dev_t dev; /*设备号*/
) V' Z* n) u7 ~, k2 f4 k, z
0 E# N8 @0 n s; ]" j( H unsigned int count;# G1 Z* I3 F4 W+ w
' X& U/ s$ [' l& H- C+ M" `
};
+ O8 s5 h) k& y& q* f( i
% w- ?: e- V! f3 J% M- Y cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号,我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:
: B# h: T: Z2 D) m/ N% u3 x; a8 \3 C# _% B' |& Y+ e3 l
MAJOR(dev_t dev)
- f6 X0 X; `0 A% Q; M
7 ~* C# x: W, L* U+ \MINOR(dev_t dev)
9 c$ P! H3 M: a: t3 |" u# T( Y% D0 O$ O k. B# [( c
相反地,可以通过主次设备号来生成dev_t:
' \" M" n8 m$ |
/ Q' ^8 d& l- A. JMKDEV(int major,int minor)* f) s! H/ I, R5 R5 m! K- f1 d
8 t/ U5 H3 ^. o& b% D- s
( S9 ~: Z6 O3 w) D! k
( y. F) d' X5 Z ~ o9 A* M1.2 Linux 2.6内核提供一组函数用于操作cdev 结构体:( | |& e1 d8 O
8 @0 W9 v8 t7 V- h1:void cdev_init(struct cdev*,struct file_operations *);
3 W: H& U/ v! v: p
) F4 @. z; A8 V( S1 q7 O u2:struct cdev *cdev_alloc(void);# L. G$ @3 a$ Z# Q
& [ ?- M0 n& d8 s1 ?$ R: ^
3:int cdev_add(struct cdev *,dev_t,unsigned);
, O8 l3 _' e. I5 P7 `1 ]. p9 Q5 t) q( e
4:void cdev_del(struct cdev *);2 |1 h! Y8 d: l/ g0 M- j' ]- ^
0 U; m5 ~7 K/ N- O2 p1 L2 D其中(1)用于初始化cdev结构体,并建立cdev与file_operations 之间的连接。(2)用于动态分配一个cdev结构,(3)向内核注册一个cdev结构,(4)向内核注销一个cdev结构
3 a+ c4 c# a9 m2 Y# F) P" H. J# |
. G) L( Z3 r& E2 ]5 Y8 C
& b' k# F8 g; P/ v% D9 f! M/ ]' J7 Q4 k
1.3 Linux 2.6内核分配和释放设备号$ g4 l; T; [- T9 I
5 O* _+ ~, C6 Q3 X* u* W8 | 在调用cdev_add()函数向系统注册字符设备之前,首先应向系统申请设备号,有二种方法申请设备号,一种是静态申请设备号:+ r- @7 m. n1 M; i( r! K9 I0 Z
# i5 q2 ?% T9 p v" n4 @# B
5:int register_chrdev_region(dev_t from,unsigned count,const char *name): Z: b/ `" c8 p! k) \2 M
( z/ P" x4 Q, d# h+ N0 G8 t另一种是动态申请设备号:
* O, H/ n+ K" Z- R1 C9 [& ?3 h7 y% N/ F7 Q# L5 d0 f* Y! Y
6:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
. G8 Y+ ]3 S( ?' e+ C5 n2 K1 H2 w$ W' U' m( ]
其中,静态申请是已知起始设备号的情况,如先使用cat /proc/devices 命令查得哪个设备号未事先使用(不推荐使用静态申请);动态申请是由系统自动分配,只需设置major = 0即可。3 P& `8 R0 j$ w7 c
. S: Q& D4 e) x3 Q
相反地,在调用cdev_del()函数从系统中注销字符设备之后,应该向系统申请释放原先申请的设备号,使用:
7 q) w6 G: W, B: r/ B( H2 D; R, f: B6 D4 Q2 f$ ?. V
7:void unregister_chrdev_region(dev_t from,unsigned count);
. R$ y7 j; |+ d- @% C. m4 O6 D2 R0 u
/ u& @7 l0 i. }- X# g7 @3 t! d/ _
) \& A, R F' X/ [4 x. r0 P, j5 @& L# c$ c
* B, j# K& [: s, f! E6 s {4 F6 W
1.4 cdev结构的file_operations结构体
. g" i' j; ^4 K P- E9 v1 I( i) m0 m+ L2 C
这个结构体是字符设备当中最重要的结构体之一,file_operations 结构体中的成员函数指针是字符设备驱动程序设计的主体内容,这些函数实际上在应用程序进行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系统调用时最终被调用。在include/linux/fs.h文件中定义,这里不一一详解,仅仅解析一些常用的API。
2 N$ ^; ^0 v$ p1 A* f1 ?6 i5 ^4 e9 G2 H# |! L
struct file_operations {
+ Q) Q8 S5 x# L. ?$ r' F) m3 X7 U* I. x9 \ k9 q7 ]
/*拥有该结构的模块计数,一般为THIS_MODULE*/- q, F/ Y9 K& ?* N o
struct module *owner;0 i( z% N& ]% b) N6 Q0 y
$ z1 n* p0 s& w3 s) C+ T( o( A
/*用于修改文件当前的读写位置*/
% I* b; B5 K: \, {6 ^ loff_t (*llseek) (struct file *, loff_t, int);
! Q3 p) Z7 ?0 a$ N9 B/ t7 V( F, a# s! v5 w* ?. j
/*从设备中同步读取数据*/
$ |2 P: H, B) d) T" g' [/ C ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
' U. K. j/ L0 {2 E8 L' Z; p# e# e( \3 E2 A3 f/ L4 E
/*向设备中写数据*/
- c+ H# L# i) D8 ~ ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
0 g9 S Q6 o! Q8 k: U2 a0 i9 J" e& n- R
! T/ W0 I9 e& e r ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
% ?7 o3 D9 W: z& x$ h" e( a ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);6 n$ O; [# w; S" b; {: h1 }; B
int (*readdir) (struct file *, void *, filldir_t);* l: ~) `2 H( g4 K" N) |( Q
f" h5 k; E. p U* V7 m
/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/% y: X) t$ P- u* `
unsigned int (*poll) (struct file *, struct poll_table_struct *);) u) u# s) ^2 _5 Z7 m2 }, o
8 O: G4 p6 a, U# s/*执行设备的I/O命令*/
( D8 k8 X! L" a& y! H int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);: d$ u3 x% K% H0 U2 M2 ~6 n5 F; `
* | C' W' Y& S3 l' w/ \. B
` Y. }& I' ^0 H" M' @
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);- h* q) I0 Q8 L0 u8 }
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);6 m$ p6 ^4 d$ R; Q
) ]# Y9 S- p, G' \
/*用于请求将设备内存映射到进程地址空间*/* ]- f2 S' r a, v8 M2 ~6 e1 H( R2 T7 x
int (*mmap) (struct file *, struct vm_area_struct *);
: g: p5 W- F7 D5 N" P9 V% h, r1 E7 B# V; ^3 F# O$ Z
/*打开设备文件*/6 z8 ^/ Q9 D( H% W# e) L
int (*open) (struct inode *, struct file *);
6 b( x$ G& k" Z1 i4 `4 E int (*flush) (struct file *, fl_owner_t id);
% R3 ?) b! F8 m- v, a2 B% ?7 t& m; @" u0 W
/*关闭设备文件*/" o) C0 J% g/ O; b
int (*release) (struct inode *, struct file *);; H5 G# G8 q' }' B9 ? \# T
$ U* f0 c& a8 u5 z' H& @% n( M& W+ I) o
int (*fsync) (struct file *, struct dentry *, int datasync);
/ b7 r: V5 y u) S) e int (*aio_fsync) (struct kiocb *, int datasync);
% ~7 M3 K1 `. Z C" q/ ? int (*fasync) (int, struct file *, int);
6 d9 F$ Z( c( {4 g ]. b# a int (*lock) (struct file *, int, struct file_lock *);
0 @. t6 p. U |( V ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
2 A; W9 {1 H( {2 t/ v& ] x unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);2 G$ z0 q$ x6 }- {& l" p( h% A
int (*check_flags)(int);
. P! g2 U( S; _8 v int (*flock) (struct file *, int, struct file_lock *);
+ s+ W: u1 \' o U$ Y ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);2 L( \# y+ G& x- d
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);1 ~6 P# ~. {- \5 t
int (*setlease)(struct file *, long, struct file_lock **);2 E1 ?# F& M' n' {, g
};
1 w5 M6 B) e5 ~3 a- | u9 _
8 Y2 s: T& N5 [- U
* p/ V5 Y3 s# Y G) ~/ S
, r5 _; _3 F' I+ L4 _# D1.5 file结构
+ }9 W% k( o% C6 N% ]" n# B' k& h' D1 ?4 v
file 结构代表一个打开的文件,它的特点是一个文件可以对应多个file结构。它由内核再open时创建,并传递给在该文件上操作的所有函数,直到最后close函数,在文件的所有实例都被关闭之后,内核才释放这个数据结构。) V2 d6 O& x E2 ?" p& c) S
2 E- h+ F* n$ m 在内核源代码中,指向 struct file 的指针通常比称为filp,file结构有以下几个重要的成员:, r; P1 j! x# A" E
) x% E# r" {) ^& l7 e! y
struct file{% z0 l7 @1 {# M2 |8 a$ A# [
. ]" a5 S# ^, z: L* ~8 K
mode_t fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/1 d( b/ P+ R8 s: f+ w/ F9 J7 _$ j
9 d2 d& g" {9 d/ d1 h ^
......
" {9 H4 n: e; x& n! f0 R: ^! B! i9 J% {
loff_t f_pos; /*loff_t 是一个64位的数,需要时,须强制转换为32位*/
# m8 f; S4 P' n4 Z( g3 [3 Y8 n `
+ \+ D, x, m0 [+ z. @; Z( `unsigned int f_flags; /*文件标志,如:O_NONBLOCK*/
. Y) m& }# A) e- B1 |0 f' a
& |* N2 L( J, S/ R9 l+ C" qstruct file_operations *f_op;$ z, f$ D5 I) y4 @: Q
+ _1 i2 Z. W- I4 p# wvoid *private_data; /*非常重要,用于存放转换后的设备描述结构指针*/
! L! N- i( y* Z/ B/ W8 Z- F4 N7 Q- m% k+ N# A. G& E
.......
3 r8 t7 d4 t. e- I; T, C; o$ ?. T5 s# U
};) \ r( N/ A7 m8 }, ~6 H. X
. M8 }3 g2 T+ h6 W: G: F
3 f& z$ d4 i C3 j9 W
4 G& ]& P# E* s N; J: r" r* ?/ ~# _' `' D
$ x- `6 V8 n: _$ V9 S1.6 inode 结构
6 n: B5 @# S( ~+ a6 r! k" F. i2 P4 A5 O" U: H% S* F0 i
内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:
+ k4 ~; o9 J9 z' X# \ n4 [( D- A$ `$ P. R9 I
struct inode{
. P8 h% G, J5 o* H e' R ^8 z' Q& u8 x& _2 b4 z" J0 ^
dev_t i_rdev; /*设备编号*/0 i8 d# b, }7 \
, x5 \. \: B3 k' y$ x& Y; U* O7 v6 }
struct cdev *i_cdev; /*cdev 是表示字符设备的内核的内部结构*/6 p; u# f7 H7 T: ^7 c w0 H
" V r! S; q2 m4 j/ a
};. Q2 i) H( L7 t) M
# m( v7 e, a2 \4 `& o/ g9 B! _
可以从inode中获取主次设备号,使用下面二个宏:3 j% B( }7 T h. m) X+ |: N
* K7 r$ E% D# {1 Z u
/*驱动工程师一般不关心这二个宏*/
4 `0 Z }0 V+ @
1 Q) d9 G, l- Y2 F" z& G0 l. Hunsigned int imajor(struct inode *inode);3 }" i, T; F$ d8 c9 ~% T" Y8 l1 [
' C/ R( h7 g2 o3 N; c& `8 Funsigned int iminor(struct inode *inode);
5 |0 M) r! J, K2 @1 C" V2 M. D" c% A- D. ^4 n" S0 j
; ]7 A: `& Z: }) a" l+ o. S3 q# g0 Z0 z
2.1 Linux字符设备驱动的组成( A* C6 Z- X! k
) z. \$ P( U4 W& f1 l+ [) |$ g$ `3 Q* {1、字符设备驱动模块加载与卸载函数5 p7 h$ _. K" b; W; _4 @
0 q6 ]7 H) c+ t* B
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册,而在卸载函数中应该实现设备号的释放与cdev结构的注销。
# P' _0 G6 B$ `: J# H" M3 U5 c- f; Q0 T" A
我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:$ R. V/ t3 N4 G. `% U
8 L9 ?. ]! T0 ~3 s. v9 ~
/*设备结构体*/
" O3 X/ N1 ~$ k
P- b( m. I5 `. E5 U( ~ i) jstruct xxx_dev{( k% O9 C' S1 W5 Z1 H
/ {3 ]5 Q+ S* h' j( B) j# N struct cdev cdev;. f1 R# q0 L+ @! J
+ |- v7 N" c* t" h char *data;. U, k+ Y9 ^8 s6 {( @
* k. ?' ^# f$ J8 t& Y, Y9 ?6 C struct semaphore sem;1 `8 k* M5 B* M# v0 o
( @& L+ g8 S; O% G: g; ~: V: Z ......+ L! q r4 \* W& h% V& F9 S2 @
/ {8 a. M) N# g! B/ M
};
+ B4 r( `$ x2 g) H4 U% A+ f9 C# g* x @' d, s) r& o5 l- k
. h, ]* k, J5 V& f) O
% h k- C. |+ S8 T7 ?5 D* j* G/*模块加载函数*/5 Y6 r2 G7 P A5 c5 s+ J; ?4 U
^, A& D. x# P( K8 K3 m# qstatic int __init xxx_init(void)2 A; W) ~- X3 b; c
# n. M8 h! i8 a I9 Z/ B- g
{5 ]9 v9 `$ |" G
5 {9 e; t+ w/ y+ T8 y .......
( e$ ^) s& A! g$ B: _" T* G L: n
# C( I$ D/ m/ G6 ~) z: Y3 y 初始化cdev结构;
# |4 V% q0 G9 ?2 E" {% o/ G6 v- l/ x3 D& m* ]3 S
申请设备号;
c! w- B5 C# I: n5 ^/ {* q
& X* o. X2 u* Q0 ?$ X7 C. C5 R 注册设备号;5 b$ Z- [7 m p H5 y
. Q! N/ k! [- W* m0 W( i; i1 |
9 s' `5 z8 w, n4 i4 @$ ]. t* \1 S' W& F
: l3 e$ r" B3 i0 W5 W 申请分配设备结构体的内存; /*非必须*/" C4 {2 U% {: j. c/ @, v- H8 K
4 F0 }# ]; Y# ?. E3 Y/ p( p% A}
9 c: j; V: Z8 b* o1 O4 Q+ c
' O# y5 n9 C) y5 x: d" b: M9 @' y. |% ?9 O) S
$ Z5 E1 `$ u+ w+ ]/*模块卸载函数*/
) w1 Y* [. @# P- `( H5 {
/ q( s7 x5 Y) ^ xstatic void __exit xxx_exit(void)7 p0 m* L+ T* J' A% t' \$ R
2 s. ^2 g0 a$ k, t. _5 L
{2 Y/ Z. F, l) }) C1 [
9 t j0 P) Z F2 A) ~. \6 Q% s* \
.......$ S: T+ n/ f7 R3 l: h
4 ?' {* J5 \0 n3 c: a
释放原先申请的设备号;
2 [ S7 |7 t G$ X* ?: P, H9 U1 D! l- {% C# d* T; ~: n% O
释放原先申请的内存;
( [4 \7 r s" r* r3 p1 @& s0 O
# k1 }0 g! b) ^& g( W+ T 注销cdev设备;
- M" f2 m0 Z! Y% y
9 B: c' K7 `1 F}) J- ~2 S. u) f
c/ K4 \+ { K; u+ o' U
1 L6 `$ V" }8 Y( ~8 s
1 P8 |. [& b% t" k+ V
: u x+ ^. c, n, D# N/ j" D! x- \
5 n9 K% Z2 d" I& H* n4 L. f2、字符设备驱动的 file_operations 结构体重成员函数' P5 n' {9 z& J0 H: ]9 l
8 S( h8 J2 r! S/*读设备*/
" l9 f4 c+ q; n; X2 R0 H; Q5 n
9 ]0 ]4 O. d: p9 Wssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)1 S+ F* Y- Y8 j
+ ]8 e* c9 J! [{+ p& {) z6 p" j4 ?
& c+ Q; b9 x0 B$ F ......
( B% R7 I. N: r0 k9 u: H x, M; ]
% x8 w5 X3 D4 b0 g2 c 使用filp->private_data获取设备结构体指针;6 j" J) r6 u" I. J v0 [9 ]
5 T ?& @/ q2 g9 G$ N6 d7 ]# X+ ? 分析和获取有效的长度;) F2 K% |. f0 f5 k
) V0 r& W3 K1 U7 r c7 s /*内核空间到用户空间的数据传递*/; A6 N# j' X0 i+ ~
" a! _1 M" P/ a- `7 f& e copy_to_user(void __user *to, const void *from, unsigned long count);9 \8 Q9 \3 @4 K L# h% c2 y
+ }( m7 r1 L/ l( w. y& o ......
. ~3 N, l6 Q7 Z$ Z* B7 |& |# {
. l* Q8 q" ]2 L9 Z, n, V}
0 i0 u1 U r2 l% {
$ z+ w6 g* s2 L9 t. \# g* c+ t/*写设备*/- @. y0 h0 g; ?& y( Z, g
9 R, N- z3 ^0 T$ T
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
D6 j! m/ K$ D: P* _9 z
* ?/ t9 m6 K7 V6 G& I2 o* l3 r9 N{
# H1 j' ?2 p) o: x1 Q" G$ s' q% s: l2 W2 V
......" C( S- o( K& `
4 C8 s1 v1 c. M/ W6 Y
使用filp->private_data获取设备结构体指针;% ?! ~) @4 g" _
$ Q7 f: `7 w- [' S! @' {
分析和获取有效的长度;. \2 H; h/ Z2 J4 Y, l
* X- i1 i. i- u9 G1 ~; t: P% H* K
/*用户空间到内核空间的数据传递*/! A# M$ C9 y5 |! K
3 B- r, ?2 K9 P3 ?: K
copy_from_user(void *to, const void __user *from, unsigned long count);
+ |9 \( ~2 P# @5 l. s6 w4 w6 X6 e) T8 ~! g
...... |, t3 e& ?. A. |
& ] q7 a* F ^' B: K% z" S% J1 _
}/ Y& i& F% q0 `$ g
8 @4 m7 \2 Y! u
/*ioctl函数*/+ g/ I& w$ ?& K' s W; `
" V5 r9 r0 u' k* X ]
static int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)9 `# ?9 K# p6 n9 W8 t! h
) q6 v0 k9 K' I3 U* u9 j4 ~% B
{
, w9 w: V5 q& T) q& b" J7 Y. ^; N, @$ u& [4 O$ G/ E/ W
......
* q4 Y5 p) w- d- c; V4 j
) |! G, e$ i& W( H* E# J; {- Y switch(cmd){' W$ s, v* b7 I5 M
- j0 Q3 `; V: ?3 I case xxx_CMD1:
' ~. F1 E H9 r7 H a
) A6 m" |$ R9 @- ` ......; i% |( }: l# g6 Y3 O- w
/ s3 E/ C! K5 `; E2 {7 { break;4 n6 N+ s7 A# `8 t, O& G
' P' Y& g I4 A2 S- @; ^9 t& M case xxx_CMD2:" g" ^- V3 f3 F4 [
: o) U5 j. [' B3 F! e. f .......
% b$ |8 H2 L7 _" u. s8 \. k
8 c% K: c/ G6 ~; D( K0 E break;1 e6 }) s: f. p8 L% i
3 O. G4 Y4 [" F! M) \9 T1 o# K
default:
8 c/ C5 m5 z1 u3 m! w9 F% h! ?+ k) c0 ?- |5 D+ A; P
return -ENOTTY; /*不能支持的命令*/; q6 l* z8 X' U. w. J$ y
. Y# Y' L9 Q1 m# G: S }
8 c! ^0 b" W6 `9 x+ {: z
J0 Y5 Z$ u# V7 b+ `1 Q" d7 } return 0;. S0 U# Z& }3 y( s$ V
- W- d$ w5 W; ^" Y! M}
+ K e" w( |6 l
3 ~8 r/ k% D1 L {$ N, G' ?* }' V$ O7 r8 H, I: J9 I
4 d1 a1 H: r4 e* J3 [& }3、字符设备驱动文件操作结构体模板" C( z: Q* w0 H: d
# ~# t* R1 P# Y) cstruct file_operations xxx_fops = {& I( v1 [- w2 R( Z& i' s
% B' a0 Q7 u; i. g, ?' @9 o) q
.owner = THIS_MODULE,
" C' r. i' u/ E* ~9 n
( a( g- J9 N& ^ .open = xxx_open,& b/ ^* Z; `( M5 J/ Q5 e M4 m9 O3 N4 z
/ H, z( g9 L% D9 D Y6 N/ q# O .read = xxx_read,1 z. U2 P% J6 y) Q( r- X! m
* U: N( |- z1 @* R0 Z/ ]5 v
.write = xxx_write,0 X. K$ S8 ~; L3 d
) t- D; ^( x* J( X
.close = xxx_release,
# R& P* ^6 c! K1 T0 L8 g
' w8 \# U/ Q# M l# Z& p .ioctl = xxx_ioctl,
; Q/ q7 E+ T" C! a, i6 j+ [4 r1 Q5 K5 v/ P
.lseek = xxx_llseek,! |+ V3 \" Y9 d# F" e" Q ^( _
" P9 r1 _; Y3 K4 v/ T; l8 Q5 D};: R% H+ y5 a. v+ `
# Q# X/ o: F; }
上面的写法需要注意二点,一:结构体成员之间是以逗号分开的而不是分号,结构体字段结束时最后应加上分号。
. ~& B" U# _. \: g
) A% j$ B% K6 I8 B0 t# i
. J2 x9 f& \! w0 m1 K8 r. o# ^; R% Q$ {/ u
结束语:
, C: h% g! n1 Y- ` v0 Y$ T& F; Q! g
字符驱动的原理分析大概就这么多,下一篇我们详解一个简单字符驱动程序。推荐二本Linux驱动的书给大家,《Linux设备驱动程序》魏永明译,另一本是《Linux设备驱动开发详解》宋宝华著,最后,祝大家学习愉快。6 h" [ w: s; ^: ?, z
F% i. [& W7 |" t8 k, b% Z N! t8 u
6 m) H! k: G2 [3 S, G4 x
0 E @2 y$ U! W- B& J3 K |
|