|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
Linux字符设备驱动结构
( S, I& n) ^- _' K! Z" Z8 B
& o9 K0 G8 ], r- ~1.1 cdev结构体2 g" y1 N1 ^' ?/ G& [
- S# V$ K3 l# }) p6 G% J 在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:
" M% N& _; N# c5 b+ O
0 g) t; f) E a/ mstruct cdev {
n) v* Q( P8 ]0 w+ K# ~% Q# [. Q6 h. Y( B2 |
struct kobject kobj;" f. g- e+ d; E, F. b
% V6 Z& v6 ?3 ^# ]: Z7 m
struct module *owner; /*通常为THIS_MODULE*/* Y. v2 D# D2 x& y3 j C
5 ]* C' x4 n% k+ D
struct file_operations *ops; /*在cdev_init()这个函数里面与cdev结构联系起来*/
4 c: s ?: y; a& M4 ^8 M: G& N
& r0 k. p8 ^9 ~$ | struct list_head list;
5 P+ [0 w. H1 U) A& M( l
3 L, l4 b* P7 }% k% V6 V' v dev_t dev; /*设备号*/
9 E2 i6 x) h! x9 o, u N* Q
+ k1 E& P( W$ S( R* ~& w/ i unsigned int count;
5 h0 | T3 ?0 x) w# L6 [2 t
8 P' x* o3 B, v3 ?5 x' m};1 i/ }1 `4 `% o, l
3 b- f3 A! T4 y3 `/ o' s
cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号,我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:
2 c$ V/ r6 @- |2 x9 q
% O' t' x; H' F2 y, g9 YMAJOR(dev_t dev)
( x6 s, L4 Q ^ D/ q# ~
& K) S, z' U) e5 dMINOR(dev_t dev)8 F& d; o1 g7 j$ x
" s" X' d( } e" w# @1 n, v相反地,可以通过主次设备号来生成dev_t:$ ^3 N [- r# }
: Y$ o! b* V7 S7 {" u
MKDEV(int major,int minor)* b6 V9 {8 M% G V$ F" g' |8 ~0 k1 _& S! _
N& s+ L) Y& q h) d1 I
: W9 m+ b2 A+ u
8 ^+ W, j& z4 k: F) S7 @
1.2 Linux 2.6内核提供一组函数用于操作cdev 结构体:
, A& z7 }6 u% c6 o1 ?. l4 i, d- l9 n3 X1 |/ \
1:void cdev_init(struct cdev*,struct file_operations *);3 Y0 B- ?3 M$ o. N1 r5 }3 f- o/ H
4 X1 E% H2 o& d% f! R/ U
2:struct cdev *cdev_alloc(void);
$ S( F. M6 B3 S: D5 V- w7 V& {* f, l: f* Z. \1 N
3:int cdev_add(struct cdev *,dev_t,unsigned);/ C% N [7 t6 K& d5 O3 Z
, @2 `3 ]! _8 G9 r4 r: I& t
4:void cdev_del(struct cdev *);/ R" H0 E: H# X6 k. B v
" ]8 U& |$ q8 r7 ?5 m+ a2 s其中(1)用于初始化cdev结构体,并建立cdev与file_operations 之间的连接。(2)用于动态分配一个cdev结构,(3)向内核注册一个cdev结构,(4)向内核注销一个cdev结构
J6 I, M, M# H( G' ]6 l5 C0 G- E. r! n0 o( C+ N
8 r- e7 x# }) m; N/ u' [1 m( A( v+ j. N% c5 Z h r) j- x1 q8 {
1.3 Linux 2.6内核分配和释放设备号, O2 q* n5 B) C; R* z! x
7 D0 C7 O* `: G m 在调用cdev_add()函数向系统注册字符设备之前,首先应向系统申请设备号,有二种方法申请设备号,一种是静态申请设备号:+ j2 M4 N% t( n2 T
1 n# W7 a" n3 r O
5:int register_chrdev_region(dev_t from,unsigned count,const char *name)
+ Z7 X) g9 M# X! J1 y
1 p: [. w) G8 k, f0 M8 }另一种是动态申请设备号:
$ x8 H* I; n+ V
4 j @) D2 ~' n7 }' N, n/ D( ]6:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
0 P4 l. _( x F, j/ e) f# M& ~, K& Q3 h9 H" c7 V
其中,静态申请是已知起始设备号的情况,如先使用cat /proc/devices 命令查得哪个设备号未事先使用(不推荐使用静态申请);动态申请是由系统自动分配,只需设置major = 0即可。
: S2 ~* `2 r& O5 `4 i
3 I* ^9 q) ]$ M* T" t 相反地,在调用cdev_del()函数从系统中注销字符设备之后,应该向系统申请释放原先申请的设备号,使用:
9 K3 A" u" `+ v) b1 o& @7 b
; s6 |2 H& T1 w( o7:void unregister_chrdev_region(dev_t from,unsigned count);5 q7 d3 U& F0 A+ b+ W
1 l. F8 p1 l: O K# v# c9 z( Z8 `
' @5 \% Q, `7 L
" |* I8 T0 }! I, a; t
$ S* G* S% A& Y0 `7 @; w0 ~# {# ^: _: t& c' i& T( m
1.4 cdev结构的file_operations结构体2 E* o( |! w# x3 ^. {3 a' V D9 q
* ?7 m, V7 E1 S5 h$ k$ R- W
这个结构体是字符设备当中最重要的结构体之一,file_operations 结构体中的成员函数指针是字符设备驱动程序设计的主体内容,这些函数实际上在应用程序进行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系统调用时最终被调用。在include/linux/fs.h文件中定义,这里不一一详解,仅仅解析一些常用的API。
. X6 ^: \- {! t5 i! r# v
0 p# f) O* i. I, ]! Rstruct file_operations {
: Y7 F- |& S; y: X% R
7 l$ {, G4 T5 Y$ V) w! y/*拥有该结构的模块计数,一般为THIS_MODULE*/
$ E- p5 d* M+ b/ ^ struct module *owner;2 u7 C1 `/ \# ]3 P$ }6 N% R6 V+ v
4 k( P* C* [8 U6 R9 v5 W
/*用于修改文件当前的读写位置*/$ l2 S+ _! c+ p" ~3 b: @- X
loff_t (*llseek) (struct file *, loff_t, int);
' F. c: n5 A. K7 b& o* g
. k5 ~. \0 J& c7 T4 m/*从设备中同步读取数据*/$ D$ f/ |7 X6 F9 m' W9 F
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
& S3 c0 a5 J: y/ }9 J
& w2 _/ Q$ X0 s2 W r/*向设备中写数据*/8 d/ B" B3 C- F' A) i
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);. e2 c) s# i- N# b" v; ^5 C7 F! e/ e
+ B# d8 e% l0 I9 Z6 p4 C
/ j$ ?0 y& y0 Q; X' ~$ T ~ ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
; W% q: T3 E& E ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
# B: Y2 a$ @# ~ Z int (*readdir) (struct file *, void *, filldir_t);
% q! k4 Q; ~5 s( i* y* Y& R' A2 m, Z P2 l: E3 x, ]( p
/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/, Q! ]; ]' r& r3 t+ ?, s! n, o
unsigned int (*poll) (struct file *, struct poll_table_struct *);3 F3 P2 Y0 y5 `1 @3 S$ B: r
9 w: B8 F/ k! |) L! z) Y# t/*执行设备的I/O命令*/, u; v' t( {) a# c4 \
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);( q+ o% M+ x' M! S/ }
: T6 }( c. x$ n' I% u
# x0 K# U) p9 t% r long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
; x, t7 C* A; n- e( t+ u+ }' d8 v7 g y; B long (*compat_ioctl) (struct file *, unsigned int, unsigned long); m& ]$ x- B+ D9 I3 D! ~
6 }- S* \5 s% r6 s$ x8 s0 q/*用于请求将设备内存映射到进程地址空间*/, J4 V# U% D: i' _1 K. Z' m/ x
int (*mmap) (struct file *, struct vm_area_struct *);
$ \& r& n' \! z4 @% v& r9 G8 z! N
E5 C2 q! H5 ]: t' f/*打开设备文件*/
; D4 q+ w( F% ]) l" t5 W int (*open) (struct inode *, struct file *);- ^5 j0 b8 E0 ? `
int (*flush) (struct file *, fl_owner_t id);) G+ F% F( g* y5 ^/ k& Y% U3 c
% e1 B S# s( P/ H4 H/*关闭设备文件*/) G+ o/ t4 q) s P& l
int (*release) (struct inode *, struct file *);8 C9 P7 U, a: `4 j
6 E3 P8 |' y' N7 ~& H* P& e2 j) [" V: U4 r9 \9 s% c+ j8 N/ t* E' q
int (*fsync) (struct file *, struct dentry *, int datasync);" k5 K% \( n& C- d$ H% C7 k' h$ h
int (*aio_fsync) (struct kiocb *, int datasync);
/ l- L" H% d: z+ }2 n/ p3 L# f int (*fasync) (int, struct file *, int);
: }2 x x& v; `0 n int (*lock) (struct file *, int, struct file_lock *);. a) e/ M& Z4 K- w; j. n
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
$ ~8 y f8 s: e" K. r: S/ ] unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
, ]+ k$ K7 _# ]9 \* z: n int (*check_flags)(int);
2 v# L% y$ B" } K* q2 ?) r int (*flock) (struct file *, int, struct file_lock *);
9 E7 c( K+ O! w ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
# i# ~& t" A1 X2 {2 N ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);: K. o3 E% @$ E8 m
int (*setlease)(struct file *, long, struct file_lock **);
$ ]2 ~7 z/ [$ ?) P3 P0 Z};
; D/ c6 ?- D9 ^/ T
! T$ L1 a$ J" C& z/ q! n/ W0 y3 W c# Q$ I" G$ j- r ^
4 E. ^, \" S$ @( G! L1.5 file结构
& X' z- [( m9 c8 l9 g+ J& S) i$ r a/ d3 o" g) P
file 结构代表一个打开的文件,它的特点是一个文件可以对应多个file结构。它由内核再open时创建,并传递给在该文件上操作的所有函数,直到最后close函数,在文件的所有实例都被关闭之后,内核才释放这个数据结构。' o, @1 z& m+ h! q3 X$ E
/ ]' M" ^9 k/ }. u' w6 e 在内核源代码中,指向 struct file 的指针通常比称为filp,file结构有以下几个重要的成员:
; e) s" k q$ L5 {7 Y |7 V$ d/ e: m
struct file{" q7 K# ?, y) O0 B) L
& {- F( C$ ~: w. w8 ]mode_t fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/% p E u8 t# z$ e$ F% g F
( L1 X( Z( v) I {0 M+ R
......9 b# D3 s4 p% Z, w
7 e8 \6 R- `4 @; A
loff_t f_pos; /*loff_t 是一个64位的数,需要时,须强制转换为32位*/
8 |7 ?- B2 q" C4 j5 A% |$ L. c8 G6 E- ~. Y9 |0 w3 r7 H
unsigned int f_flags; /*文件标志,如:O_NONBLOCK*/) t" D5 A2 C$ G K& U3 F- Y
# A& ~) U( ^# r3 K- Lstruct file_operations *f_op;2 k: K7 ]; V. _2 F- b# ]" K+ I
" {& \) U4 j! H+ n- rvoid *private_data; /*非常重要,用于存放转换后的设备描述结构指针*/
9 f& @% l& T; I/ ]! [# O, S ]9 n+ e; d" [ S; q3 ]9 r. |6 x$ l
.......
" `2 p! F2 }* i, o; i+ \6 v
4 H$ L) |. |9 o; Y};
. S9 k) W: U# m. v5 G8 i, x6 B* `9 v2 Y4 N
9 _* N0 x, J# N; g+ N1 U, }5 G' k
; }+ J6 }) g* ?* f9 a6 m
" ^2 c1 ^4 l, w+ H, n7 y
& |2 J, |+ L, b5 B1.6 inode 结构
- ^" Z7 R0 G5 ?
) O3 f/ Q {- u: p6 C. R 内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:6 F* v: K t0 d3 H$ ^* }
! m8 U% Z2 j2 ?% bstruct inode{; c9 N6 N6 B" Z* y% [1 ?1 g
+ P* r, w- _2 h8 w- `# adev_t i_rdev; /*设备编号*/* Y* ]! y, S6 a9 l, \8 [
* p! Q) F# F( @* O7 v' E
struct cdev *i_cdev; /*cdev 是表示字符设备的内核的内部结构*/
; [0 a' L( |$ c# ^
! Q0 H% u- O: l};
2 f) q. ?3 ?* Y0 _. p6 P1 Q6 \
可以从inode中获取主次设备号,使用下面二个宏:
( F: }+ |! Y5 E0 u, w" K9 i$ L: v1 _; b$ U9 L+ x
/*驱动工程师一般不关心这二个宏*/
& D. g" p+ m+ n
( P; d& @0 o6 funsigned int imajor(struct inode *inode);
% N( ~3 u" @1 N6 k* ]3 ~2 h# u
$ i% b @) [' L9 Munsigned int iminor(struct inode *inode);
1 V6 M7 _: ]$ H/ Z" A* Y& {* f8 J# h" y4 T% p4 Q( X) p
2 k2 H' `' G7 S- {) U4 B9 b
4 l, O; s* _( t5 O( r: T" x* G
2.1 Linux字符设备驱动的组成 K+ S5 }9 L" Y1 j* j
0 Q @6 v0 Z( b' P2 x
1、字符设备驱动模块加载与卸载函数
4 S% U z; c; n& q1 J, a) m/ l; u9 x, R3 a: o& V
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册,而在卸载函数中应该实现设备号的释放与cdev结构的注销。$ N: G& g7 h+ ~9 q/ C
5 C" u; l7 n3 o2 B% k: a 我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:/ k$ B. j) P3 y1 Z" d
) L' ~2 t+ _7 v4 }+ Y" x" Y/*设备结构体*/
: K; M' j( @, t; T% Y* z3 Z) j' z1 b0 ~
struct xxx_dev{
: N5 {1 o6 Z' d( ]
& D; d( \5 S: D7 W) N; K* G/ T5 o struct cdev cdev;
T* L7 B+ p7 [: E9 G: }$ W3 q6 O: a& |
char *data;
. c& P2 a# ?( H; ]+ ~) N( _' [9 ~) ]& c, _3 Y, a& k! Z3 X/ h
struct semaphore sem;
3 {7 J& l. o* Q8 @ Q" M* n6 a8 \1 Y+ e( G
......
$ h, M/ k) c) i0 \7 J0 l4 d% J6 F9 T: [4 U0 |& F' D
};
0 y: e6 s7 W3 a# ] u0 H M8 N2 v( k7 ^6 P& \7 C, }2 {
" w' l# G' @1 L) b n8 g$ h8 [
& b( }0 }( f: i" ^9 E. }/*模块加载函数*/" A' A/ I. W8 s4 e# |! W
Q* g' w6 m* a
static int __init xxx_init(void)8 e7 l! C c& Q1 {! Z
( ^0 W- m% W+ x" m0 j/ Y7 |; R, o{" |: C. E$ e& \) h6 P
* R7 }; N1 A; ~; `0 p9 Q1 i, P
.......& J# W2 n4 g) Z' c0 \6 m ^
^4 d; n, B4 O5 L( s" }$ g0 P 初始化cdev结构;# }! z* W2 e& Q% c% o
% z' G$ S4 F# ^( a0 M$ s6 }& C$ ` 申请设备号;
0 W/ w$ I2 L+ b! B8 [' r+ }) |% s% {6 L- t
注册设备号; f0 w) {6 ?+ w3 t2 ~2 y
# }% O' u/ ?- _5 N! `% Y3 ~
. W& c z4 h! `9 J% }# F' Z4 D- [
: C% o8 r2 j) N0 h1 _6 f8 H
申请分配设备结构体的内存; /*非必须*/
& M2 W/ {! o. o& I6 V, D" |+ q
}
+ m$ `( t+ L+ D* ]0 \% c& [1 E, ?8 |! D, K' f3 P! [+ y [" N
8 m6 G; R) ]& N0 E- z0 G
6 O2 ^, Z8 J! E& d( R
/*模块卸载函数*/$ w9 u1 [+ c) l/ X: L- r4 \( h
" E) D& |' s5 F
static void __exit xxx_exit(void)9 v# W3 \4 C. i @9 ~
% o. d; Q, p' ~1 O( J{
" |2 W! ?# z, K. l: h% f; ]7 J& [. H! @) T
.......
7 W" t0 {: L0 O( k7 U# t) U# z% |5 e Y) `0 h6 D* j9 |4 p1 n8 ?
释放原先申请的设备号;) f) j% r2 x- P0 H+ x" p6 T
0 L0 o! v4 P' U6 ]. f0 f 释放原先申请的内存;
1 R& e1 `! ?& P3 u" Z, a
- j1 r# Z: a3 `8 r, B5 c3 K 注销cdev设备;
+ m0 b5 M4 X( ]2 G3 q: z7 }" H7 h4 x! T
}
/ s( X* @6 v* U$ l2 J9 A( c7 s5 M% x6 o
! H3 | G+ T6 X1 X
0 u" t3 ?7 L" A b" s9 d% y: Y2 Q6 |2 g, A2 G) n) X
$ i1 @0 ^3 p9 |/ W
2、字符设备驱动的 file_operations 结构体重成员函数$ j* d' _& D0 @, `( ~
1 L! w$ a4 [% S0 g" u: f
/*读设备*/
" L7 k- g, q, n
+ @/ Y! l8 q+ P1 |' D6 zssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
$ Y* V3 |6 c) ^9 p3 u2 N
5 m: W- t" W) _8 L( j{9 ?9 F1 D+ {0 j1 w1 Q" U( [
3 H3 }7 u6 a% V, R( W* D
......
& ^/ n6 T0 [: ^1 z& D' W S! ~9 I N+ n+ o8 P s1 B
使用filp->private_data获取设备结构体指针;
+ f; T5 S! h7 R5 J# ~/ ~( C/ g, ^5 T) e+ Y; c2 X( Q u; f6 f* |
分析和获取有效的长度;1 z7 s5 P! F& }6 F
( U4 C' L3 E# s/ W- V /*内核空间到用户空间的数据传递*/9 y) b8 p8 R5 U/ Q8 ]% T, X
( {0 i$ o+ w9 \* z& q" E copy_to_user(void __user *to, const void *from, unsigned long count);( U6 y& ~% i& K$ X- l* _
( M x6 K" T8 h) g0 i ......( s1 X& H7 |1 j' k: N
: h1 \4 P0 f$ t1 g2 d
}
# Q8 p3 {% S# j( E+ F% _8 |3 d ]4 g4 t2 ^
/*写设备*/
. u c3 _: C' D" p& X$ w/ h+ \ |) G5 \0 w! N* k Y
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)1 p+ q; C0 j2 m, V1 s2 Z$ ]
+ ]! j3 k6 l6 M- Q9 C3 H1 |
{: V+ ~* p' H( u9 ^
+ C. I* E' g4 v0 D! I! z z$ P( O
......3 r- S( Q" G, Q4 m7 L) n
1 e, T+ l. j; H( H s+ z
使用filp->private_data获取设备结构体指针;
9 k4 O# G6 O. x# t; j' A% H% X& R+ n8 s
分析和获取有效的长度;
; r) k( t- O( D, J w( X# E7 h c( E* z' `- b# ^4 _
/*用户空间到内核空间的数据传递*/
$ m1 E: U) n% \; L, v# q" E9 ]6 I: U
copy_from_user(void *to, const void __user *from, unsigned long count);6 u3 h5 m: Z3 B1 g6 D
[9 P4 v( D4 s9 U5 u$ ] ......
/ C5 b7 M0 z7 b3 j9 Z9 r4 {0 g
7 S& [5 z9 P# P; r! C w% Q7 J}3 M% K% K- m. w; _7 q
M& \( c0 m/ P$ W/ n, C+ o0 b/*ioctl函数*/
& C2 Y6 ]6 ^0 t2 z& t: l
1 H5 U1 s; r# G* h8 Ustatic int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
1 T9 J c4 i% D; M- D- J( Y! _9 `) N/ \% _5 D; [
{' i D3 X( d) j6 \3 a
0 Y! _; W% Q6 c
......1 {/ T) i/ Z1 I3 L v; |4 E/ j/ g. ~
! U$ L3 d4 C( P" |0 K% V5 M switch(cmd){
: E1 {* ?2 @6 E9 u& e9 F! K( X% u0 S. i
case xxx_CMD1:7 o( k0 H! R6 p% F8 c* E
+ b# i" ]' \ b
......( C4 N% v8 Q' l: m
5 z3 R6 H' Y, X1 @0 f4 q break; }- J. ?: }9 s/ L0 H
% z+ d+ i) b5 O) w case xxx_CMD2:2 E! J9 }# X; x$ Y5 @7 w" L
% I$ C0 u2 y- ]# P: |9 Z .......6 }$ `, C" r6 I U+ G- m
6 ^5 ?" E( G& B0 X/ o4 m6 ~6 y2 J break;
( }' S, ^8 |, G, A, M* o9 K" w" {3 p+ |
default:2 d5 u6 N) [. v/ z: M% y7 \
$ j. I7 a) _, l4 g5 E& K& a) ` return -ENOTTY; /*不能支持的命令*/! C! v- u7 T+ V% b
1 g5 I9 D5 L1 }
}
' w1 K. n1 s( u( l9 T9 }8 b: W: C! h6 ~: G# d6 e8 X# a
return 0;
: A7 h) N5 @* O! a% j, Y
4 F5 Q& _+ A9 m/ _) ]1 E k}
5 ^9 f0 K F' h8 Z s
& o7 ]6 K' i- t; f
, Z. ~$ n- t* X& W7 F
$ c+ ^1 K' f, R# w9 j$ j4 m3 R3、字符设备驱动文件操作结构体模板
% U2 z6 D; P1 f! L. c/ @! {: d
$ D: ~8 g7 @& Ostruct file_operations xxx_fops = {
% C- V" h( @, L2 p9 w( ^
1 m& z2 i0 n4 B" Z) l .owner = THIS_MODULE,
% V; m( A5 u- b1 x& Z0 B2 o1 E; y4 J# x* H0 T1 T: a, ^4 M
.open = xxx_open,
. U% Y8 l5 V5 v; w; {; M8 _, V9 M2 m2 G6 q
.read = xxx_read,
M/ G* T, G: O4 ?) V
; j5 K1 r& O0 J .write = xxx_write,
z/ V' y2 `1 e; Y8 O' w
0 X+ i$ P" z0 p+ ^$ e e1 F .close = xxx_release,
9 Q+ M/ [4 _2 `* o. o
( R' e6 P, M0 ] t" y. A% W4 Q- F .ioctl = xxx_ioctl,! M) |+ J7 a3 Q7 N
: c: V# b: [3 Z \& j .lseek = xxx_llseek,0 l* M4 l) b& f
8 l8 u$ n6 u5 g- F4 f9 r
};
5 H. D2 p. o) _, ]6 b# m0 M
. |4 K3 d8 W, K! X4 ~上面的写法需要注意二点,一:结构体成员之间是以逗号分开的而不是分号,结构体字段结束时最后应加上分号。
2 S: ~' c. I4 E: b A3 v! [
; L2 h" i9 m9 j7 j
+ d( [- {+ }( Z/ j8 u1 f* d7 p$ [# M' _; x) J) h
结束语:* g6 J9 Q/ N5 e, U, u
+ ?# a+ Y5 [3 w6 ?# ]8 U
字符驱动的原理分析大概就这么多,下一篇我们详解一个简单字符驱动程序。推荐二本Linux驱动的书给大家,《Linux设备驱动程序》魏永明译,另一本是《Linux设备驱动开发详解》宋宝华著,最后,祝大家学习愉快。
' c; e( d# N& A: E Z: h/ @& \( v8 y: a( s' c; m7 w V
4 ~2 C/ h+ w. |& j' j! G2 D
& Y5 V# l, Z* ?3 `2 F5 p: ~ |
|