|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
Linux字符设备驱动结构
* g( a; i8 x3 {/ m' x& X* S V
: @) ?- t- R7 K+ j; P3 V1.1 cdev结构体
& r; Z9 n4 s: Y& e1 b* N* q
& `3 q+ @/ l$ }* }% { 在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:
9 M$ U5 i. o8 H& t' j1 R2 o7 b) v; _+ a) A3 r6 P+ h3 t4 C
struct cdev {
/ B$ A) J, J% z5 k8 ^- E* J0 t# c, V' |. P+ t
struct kobject kobj;; S1 N" O" B1 F: a8 o! t& z L+ k: X
! k* I% `+ s) f2 E4 f, Y: g struct module *owner; /*通常为THIS_MODULE*/0 ~8 j5 v2 [. Y
. I* y( F I2 Y1 q3 B h2 g0 t
struct file_operations *ops; /*在cdev_init()这个函数里面与cdev结构联系起来*/
& K3 _; g8 j7 I3 C h* I; @$ ]2 [! Y* Y* W
struct list_head list;
9 w* Y v# B2 c) K0 ?8 c% |. ~, H n% |* e1 R
dev_t dev; /*设备号*/
: Q/ q! {2 T1 S& L
: \- i4 w1 h) d4 y1 n) b' i unsigned int count;
1 c- L7 h: R" j: f3 `) F) Z3 B2 g: [
};" B. ]7 l& O% O
: X9 H. ^# g0 ]9 ~" S: r' d
cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号,我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:0 E" N& p& S' b+ t& g
2 {6 I9 e% ~: h- HMAJOR(dev_t dev)
/ k) s: n7 |6 {5 F/ K6 w% B6 Z+ M8 z/ e5 G4 T# s
MINOR(dev_t dev)
$ r6 {& I" a# h; k7 W2 x
/ {3 F/ K6 f8 A3 i. v/ ]' J相反地,可以通过主次设备号来生成dev_t:! y- N' R) k- T7 F7 ~( Y s
. `% o* B5 P6 IMKDEV(int major,int minor); h1 Z( w) K) E3 N* p
/ O3 x1 v2 x! c; v( T3 Q; R# ^. I8 L& \7 x+ p2 j
& x' a: I! b3 h' _: z g0 K0 n- u9 q1.2 Linux 2.6内核提供一组函数用于操作cdev 结构体:6 T2 x5 d5 H) f! ]. s
/ }; M$ I' u4 y5 r1:void cdev_init(struct cdev*,struct file_operations *);
+ _/ u$ e) e- S+ |2 m8 n6 }$ Y* p$ \5 y- O, ]4 d
2:struct cdev *cdev_alloc(void);
. ?1 A1 Q+ S6 s" O* |- ?
7 M) o( p" z; E9 }0 g1 d( P; R3:int cdev_add(struct cdev *,dev_t,unsigned);
/ w- a: @" }& L, ^
) m* m* a. k* s2 ~0 ^' ~! [4:void cdev_del(struct cdev *);
2 o# s1 X* E* r( m8 B; X& L+ A% _6 y' x# X
其中(1)用于初始化cdev结构体,并建立cdev与file_operations 之间的连接。(2)用于动态分配一个cdev结构,(3)向内核注册一个cdev结构,(4)向内核注销一个cdev结构0 T. n- c6 `( i* O8 j6 G
3 R& W( u0 S4 f* h, V* Z0 Q8 m" S: Z* u- _
: _2 J3 N0 n, h# N. U0 b" }1.3 Linux 2.6内核分配和释放设备号# b% a1 g7 G! [& \1 q
6 U% J; |+ E" D& q" z2 p! ~5 |
在调用cdev_add()函数向系统注册字符设备之前,首先应向系统申请设备号,有二种方法申请设备号,一种是静态申请设备号:, \& y0 [ r9 i# H$ _; v1 ^% R5 A
8 ?1 f% h8 V( R" H* \5:int register_chrdev_region(dev_t from,unsigned count,const char *name)
0 e( v0 `# x6 V( W5 } P. `: @6 [$ o; O* @* [" z
另一种是动态申请设备号: e p$ A/ W/ Z/ }$ y
! {0 Q. f- c3 u$ q9 i! c. A
6:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);. F* A' i1 I8 B6 G5 J' {- R D
# P3 y8 R- L% t; l% y 其中,静态申请是已知起始设备号的情况,如先使用cat /proc/devices 命令查得哪个设备号未事先使用(不推荐使用静态申请);动态申请是由系统自动分配,只需设置major = 0即可。
' k. e6 A% N6 ]0 F
. L" d8 s" A0 x4 k 相反地,在调用cdev_del()函数从系统中注销字符设备之后,应该向系统申请释放原先申请的设备号,使用:
2 F* ]8 J: f. U" ~$ \0 B; {! T. `$ F
7:void unregister_chrdev_region(dev_t from,unsigned count);
4 X3 g- |% r0 D) |1 a0 |1 ]! ?% W+ W/ J: U4 w4 A
& N- e1 A$ t; I3 K: k: |8 M" e# w' {- K/ V( c
: L) f+ I8 n7 w9 S$ T$ Z; C2 O# C& _
& _( j! `: K1 J* F
1.4 cdev结构的file_operations结构体8 o6 [" {9 L Q, F! l% _
" d4 `% `/ ~7 ]4 W) V8 J# [
这个结构体是字符设备当中最重要的结构体之一,file_operations 结构体中的成员函数指针是字符设备驱动程序设计的主体内容,这些函数实际上在应用程序进行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系统调用时最终被调用。在include/linux/fs.h文件中定义,这里不一一详解,仅仅解析一些常用的API。
3 n+ j7 K5 S1 _0 x0 ]# C
/ w) R% `/ `8 k% Ostruct file_operations {3 X, }) K' m9 a0 S. x1 M1 Y+ a. L
! J: _ A$ V; R3 ]/*拥有该结构的模块计数,一般为THIS_MODULE*/
& s v. W. k1 b# P struct module *owner;
) P } r* s$ ]) y8 V, `, K- N. V3 c2 [0 m0 ?1 L8 H6 [- Z
/*用于修改文件当前的读写位置*/* A+ ^/ A# `/ R; [ r% ^
loff_t (*llseek) (struct file *, loff_t, int);
1 B% w; n+ b- D4 c; S5 p! h8 \* ^
+ W4 {- [; D |" D8 x* k) j/*从设备中同步读取数据*/ y+ d4 [$ K6 S; @6 [& \% K2 A3 E' k
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
( {3 `' I2 n# i+ m( \9 L3 J" w7 j; x2 z. E* b% v5 H
/*向设备中写数据*/
- e8 e/ U3 ^2 B: w* W7 {. Q0 s ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
7 {8 [" ^. B. f# U- r4 F& j* n
3 [& I; e C! C* l6 v
1 }3 Q& M" G. y7 ], A; K n ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);* Y) c0 C h( n
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
" L/ T( v5 O. `, p8 l% k int (*readdir) (struct file *, void *, filldir_t);2 b& x2 S; ]; M0 h
7 e) I/ r2 a/ F' [/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
; N0 O5 @6 b; E- U2 C/ R6 y v3 S unsigned int (*poll) (struct file *, struct poll_table_struct *);
6 Q* m9 D# \" I- ]% J4 h( q) [
- P, c, y; |2 d. [7 y/*执行设备的I/O命令*/% s! ~; {. C1 c: s
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
& \. F# M7 r- h: Q( ?: R+ y+ a w! `1 Z9 f% p+ V$ u* Q4 D5 a% u
! Z! h$ D4 r+ ]1 I
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
; N" m# S* n8 g' R6 i' o long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
5 z0 G8 W3 X1 k* i8 Q& t8 Q
' |1 W' E4 m" t. Q/ W7 i/*用于请求将设备内存映射到进程地址空间*/
! n4 R+ Q& f1 I3 ?2 A int (*mmap) (struct file *, struct vm_area_struct *);: s* R1 {2 p! B0 e" u8 @; R' g& @
9 P1 _6 o3 b+ n; y8 u& f u. D& i/*打开设备文件*/3 W$ A) e+ Z/ ~. U6 y5 T5 Z
int (*open) (struct inode *, struct file *);. m8 Z1 H+ c8 T0 |& m: g- @
int (*flush) (struct file *, fl_owner_t id);, p+ j! r! X. j
1 C. a# K% g2 q" `- b I# m5 s! U5 T/*关闭设备文件*/
: v8 j* i6 o# p/ g int (*release) (struct inode *, struct file *);8 \$ s G8 b- q7 U
9 [) H" O4 D1 _6 w9 [7 R9 {/ g
+ _. Z; @( D ?6 ]" R int (*fsync) (struct file *, struct dentry *, int datasync);( ~- Z; H+ k( |1 k; C0 E8 T9 S
int (*aio_fsync) (struct kiocb *, int datasync);
' @5 K6 z2 T. W9 F- V+ j3 S. }. |% k( B int (*fasync) (int, struct file *, int);
1 ?# m: s6 S/ v9 p+ I, G8 T3 G V int (*lock) (struct file *, int, struct file_lock *);
5 n3 Y0 [ d+ f ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
& N4 e- ?* N2 V+ J/ l6 R unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);( t$ F+ q$ Z7 y/ c( M, X
int (*check_flags)(int);
8 ^/ F) D L6 u" O; g int (*flock) (struct file *, int, struct file_lock *);
3 f: G) o+ ?- P0 I& n ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
s! x+ Y, d6 h ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);+ O. {$ f( q: H9 g D
int (*setlease)(struct file *, long, struct file_lock **);
6 Y0 y/ y9 i: i% l7 t: W5 w ]( x};
9 }# z, W" j* ]% C8 C
6 m* o: x( U; o& m5 O# x% Q
5 F1 y/ t( K _2 [5 |" W5 j8 ~" \! W, ?7 X& e( y* S1 \, N% m) E
1.5 file结构$ b5 N& Y F5 X" M ?
: |9 y0 M/ _1 C2 P9 ?2 K file 结构代表一个打开的文件,它的特点是一个文件可以对应多个file结构。它由内核再open时创建,并传递给在该文件上操作的所有函数,直到最后close函数,在文件的所有实例都被关闭之后,内核才释放这个数据结构。
7 [2 T) K4 f# m5 f9 b7 n8 c6 j" e$ W- o V6 O* s
在内核源代码中,指向 struct file 的指针通常比称为filp,file结构有以下几个重要的成员:
2 r1 M R5 A9 b+ e5 u( d- Y9 n
4 b. N' S! P, s: ?2 m2 X+ W, E/ a& cstruct file{1 X& L9 w6 @4 o0 S( a5 ^
$ l c' S& p9 \8 q5 A% Pmode_t fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/
0 ~0 L+ Q0 j9 l6 c# s q: U: f5 _1 S" Z( `: D6 i) ] k
......
" t3 G/ Y- i8 H( J; H3 a" d
; G8 x. g$ |1 w5 z! Q) ?0 C8 Rloff_t f_pos; /*loff_t 是一个64位的数,需要时,须强制转换为32位*/ h* q6 q; k2 z
' C' H9 o0 c* M# y2 k' k* W
unsigned int f_flags; /*文件标志,如:O_NONBLOCK*/
8 h, y3 i* Z+ A9 E4 Q' y8 {; }7 V7 Q- n
struct file_operations *f_op;3 Z, v" s8 K7 K5 e
N* @5 j3 ?, s l( Q# N& Nvoid *private_data; /*非常重要,用于存放转换后的设备描述结构指针*/5 @" o0 ?) [1 L2 s7 w
; [6 v8 c8 V3 E5 i8 c
.......6 F; M$ y/ A) t; E
1 `1 H) a% C9 c7 {
};# c6 r3 C! @6 h3 J3 _' H, u
+ \. ?! r/ g" @- I3 u. E$ P6 k: X% ]
/ d6 l% q6 O! b; n4 ^2 d8 @/ @: E9 u( c9 ~/ C/ w$ E
& n) y: K) Z2 i/ R- U
* K, b0 v2 ?' ~4 D2 c$ j9 ^; [" ~' R1.6 inode 结构/ O7 E* t; u& ^3 Z( @
: K; w( [9 E9 ~7 C, n" `7 u 内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:- F# F# w$ {0 _3 ]' v! Q# o0 h9 w
1 \# ^# V" y$ o4 c9 {
struct inode{( O$ { g: U5 @# s
$ Q; X* U- R. h) \. S' i, l. Gdev_t i_rdev; /*设备编号*/
. [- |; m) D/ O$ {: a, p& U
# f% a' f$ _% u5 m) Bstruct cdev *i_cdev; /*cdev 是表示字符设备的内核的内部结构*/
( O) O2 b3 ?' |
) Y6 |5 O1 z- o9 U# m};0 G" L# G; K2 |& n
( `, f. C1 G0 M, _& V& h
可以从inode中获取主次设备号,使用下面二个宏:. r$ y& p2 P# G1 L1 {8 m& J3 \
U9 r3 M) v/ ^. z; \! {7 P/ B( E$ U
/*驱动工程师一般不关心这二个宏*/2 d$ e8 D5 j5 l
. o* c! X6 ~! n2 _. t8 P X% a
unsigned int imajor(struct inode *inode);/ Z- x. T1 i9 X
+ G: @& Q0 M) y- g6 p
unsigned int iminor(struct inode *inode);
% i. [" V8 w$ Q; J. M: D$ f l0 W! ?0 O
8 `, y' H* s$ z6 W
, o7 H# c: @5 D, m2.1 Linux字符设备驱动的组成; u. _9 c L* f% h0 G
6 M% L, k$ |$ [" {
1、字符设备驱动模块加载与卸载函数
) U- U8 r! ?/ I0 p( A+ u, ?) {
0 ~. b1 }- n' q2 f 在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册,而在卸载函数中应该实现设备号的释放与cdev结构的注销。
' I" E9 v2 c9 p# O* y/ w) m/ q- M8 {, D' X6 Y
我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:
% f4 V) }0 `+ P4 t; T" T1 d7 P% {9 i# {
/*设备结构体*/) c6 ^" Z9 L, M, x2 a
! _$ U! J5 ~1 I g& xstruct xxx_dev{6 S1 R! X t# j. E) f( M; P: I
9 G- g! @$ R, F8 W! i% }) N9 P" i struct cdev cdev;
. j+ K/ c: ^# o1 E$ C- ?( o
/ k2 g1 i5 V0 R* W% H, u8 Q char *data;
% b5 }: D& D/ x6 B _4 N# h& f& p4 H3 x! \, C+ [" M2 s
struct semaphore sem;" j1 O/ o% E/ j% a$ G" P. p( i- q
$ y& ?5 v/ m- `# X
......
# a3 s, P" _' E7 L" H
1 v' }1 S5 d; d$ v};
. ~0 `; i" [/ O- s9 V# E! Z% x( Z' [) V7 b% u
, H+ y8 @2 W1 I6 U) d
. S5 j' u0 K8 E. X1 Y+ H7 D2 u
/*模块加载函数*/* k F' O. d( t0 P6 c
0 }: W( X+ x) ~! y/ j% X, }( x0 ?static int __init xxx_init(void)9 z( ?5 {$ x( d& a) ]
o: Q$ y6 S3 S9 y0 P, a{
: G0 F9 P6 y" S" j+ s0 ~, t3 ^$ O9 ]; @2 m2 P# C/ [) h
.......+ K+ u% h% K# D: Q- U1 u B
- ~9 W! U2 ]( l2 ~) [ 初始化cdev结构;1 O: p3 q$ h: |# ^! X0 x
: L( F) R% ^7 H( }8 K: }! c6 _
申请设备号;
! X) f* M/ _' \5 x3 _5 D6 B; e4 _4 \# S9 H. _ Z0 h+ K
注册设备号;
/ N, E+ o$ m# w* E3 F
: u! U3 n( p) n, D( H1 l+ @* D7 W7 Z! D$ `* p
( O- L* x2 y/ t. t, o
申请分配设备结构体的内存; /*非必须*/% }0 }: P( Z. d4 |2 T& p1 B+ E, b
/ K/ f/ s% z% Q! h, G2 G/ y}
, W( ]9 l9 J% s1 C2 m$ w6 D3 G
. [$ g& y& i$ R' A
6 W5 U6 g' R, a: C
; C# `2 j# ~3 G0 ^$ M/*模块卸载函数*/
0 d( X5 t7 j+ ]5 C: x8 X- C. e. Z% y' A- {" m" W/ f% O
static void __exit xxx_exit(void)1 k# ~4 ~; [! z; u
9 ~7 K% i# H# \' p" d{/ J1 n# ?- Q/ j
6 l0 h2 e& ^( B2 s9 v0 f .......
6 C- w9 Y9 ?+ u0 e/ t- W+ }2 y. D2 _( k0 @& P k E# R+ T6 K. a q
释放原先申请的设备号;
# a2 O2 E3 R) R4 c [. C3 X/ H5 k9 O6 g: `# U/ J& u4 {
释放原先申请的内存;# V B% o8 ], O1 q
4 r) r( v* Y: h0 _( D
注销cdev设备;
$ \7 }8 U- s& ^" P* u+ k
+ p+ J! q3 {/ H4 o, [6 ^( v/ {}
6 I ?5 b" U1 a. x
/ l5 @* \9 J! o9 E x' X8 r
' M* d$ y! V/ ~6 c
8 b9 Z2 V5 Z9 O# |6 s- _9 }1 i
9 q2 u6 {: o5 Q2 z/ U% W. |9 q `' M& A! n& s. g/ L2 c
2、字符设备驱动的 file_operations 结构体重成员函数
4 W/ f1 F6 W6 E* k1 z/ x
2 Y! ?5 v( T0 ]/*读设备*/4 T6 p/ } d2 q$ K4 a
/ S* P) }6 h, E* o. Z
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)+ e. l, r2 O7 y
; l8 D: n: S. O% }. P; A
{
- W& Y/ |; |) k$ [# a. T# [) b
0 u4 P) [, O# @$ U; E ......3 B. W ]% D/ _& X/ C
, P1 o" ~& k5 P" P
使用filp->private_data获取设备结构体指针;
4 _" J$ W0 y0 e& t2 p" W' j5 x4 Z/ \' O
分析和获取有效的长度;# |, F; p/ M5 S" H
% d0 F2 e6 H4 k- [ /*内核空间到用户空间的数据传递*/4 ^! w: s* J6 A9 m" r. G3 [
$ t" [ m+ V' M8 f- b copy_to_user(void __user *to, const void *from, unsigned long count);1 ?/ m& a8 |. H$ z
+ f" {$ S4 Y1 F( m' @* V. M) i
......) @5 x: b. i: \* U: v# `1 y! x
+ t) k& p% E2 Y; E( e/ M, ~6 G# B}3 q$ q ~, Q3 r" j6 T
$ v+ o# B" P( v O7 L, S4 J: |/*写设备*/) B: Z8 d5 Y7 s- z) N
" C* w0 Q2 O% y) R/ u/ \% p, }& G
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
+ R; y+ \; m, `8 v6 B" Q4 j* p" H8 R2 c1 i1 ^
{
6 v% {" o+ J1 B o: W! R/ x
$ |# s, N5 G% \ T ......
9 X6 j7 D s! Y% v& l3 G0 s0 R' e& B4 v
使用filp->private_data获取设备结构体指针;
5 B( R- @+ k8 n/ |7 O& B- ]* |, F0 A1 O7 Q2 C
分析和获取有效的长度;
+ p6 U8 i F; r9 Z. [- W4 f9 J# U: L0 h. i
/*用户空间到内核空间的数据传递*/
" x8 d5 [ {4 j4 _1 H1 K& {: m: A4 n
; j" R9 F: J3 a% i1 V4 a copy_from_user(void *to, const void __user *from, unsigned long count);
2 C7 n6 |; \3 O( x7 K4 J! b- p2 P, _% @6 S7 Q
......+ |( Q8 ?5 C8 c+ F
% b( Z+ w, }; s$ }2 ^}6 O$ H6 t7 K% u9 y0 s9 w' `
1 i; I0 a, C% |4 l% B/*ioctl函数*/1 {6 H" s3 A; q4 d
: A- F+ k4 H u( M9 h
static int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)4 E) o1 {* X* x1 e+ _$ p0 O
2 Y9 }6 A8 P p$ J/ u{
5 }& u; H( x. [# l4 v. i. I4 C% m7 x1 q S( u) j: X* R4 z) u
......8 p; J: j8 ^: y: Q% J: `
( p9 O' a+ S1 v* _7 A: i
switch(cmd){0 a: ^/ n$ J( E1 x
4 ~, ^- z% ^( K' V* k8 c" w
case xxx_CMD1:' o9 q6 m7 X8 N
, B/ {) g9 p1 k/ E ......
6 X! o) X1 i. {/ n% A' @+ T) B! S- W6 |/ F
break;3 x V( E+ g: C I5 z
) r4 B+ s. u2 M2 N) @: P case xxx_CMD2:
" v$ V! _, \3 ?! i7 @, q& i* {9 V" ~( ?# R8 u( m2 o
.......
8 N" \2 j' R" C; W& `$ d2 |, e, X4 g: T
break;$ p, i3 R- A6 E$ |1 q
5 o8 v/ M9 w: E5 W
default:6 A' Z( M# N6 `' x4 W( m0 g
, X( I3 W, {; D6 ]! V, N
return -ENOTTY; /*不能支持的命令*/
: O, E7 {* B S/ j1 L- r! ]( t* |
}' Z, I! r4 f' Y! E. w
: X4 \" o, e9 n. a5 S
return 0;
, ~. n' Y3 H2 h9 o9 D
4 C! }+ l/ ~$ o1 k* u}
/ J0 K, m# ?' [. a# [8 r _5 V% c/ I- g9 \6 E- j/ I
& G# a4 x' x5 ~1 O* k* c
. |. S: s" n9 O& l
3、字符设备驱动文件操作结构体模板. l/ o! j W- B- i s
+ z1 {- D& l4 C# t( f" p' i' Rstruct file_operations xxx_fops = {
: n( c! i: W- O8 u- B
7 N" y+ `# I- d1 q9 t6 R. l% Y .owner = THIS_MODULE,
6 ~1 h8 ?( Q1 X! d h! F; U: P# s2 S$ O% j: Z
.open = xxx_open,$ U- J) E: a8 A0 m0 k, h
9 c: Q L3 B5 {- S7 n. V
.read = xxx_read,
) F0 W4 D2 q/ G! b2 V
; J ?% B4 Z' q7 s4 U4 B5 i0 D% r. v .write = xxx_write,
- n( r# |; X( @; w: F% q
" n$ O: i9 v* z6 G3 @3 v .close = xxx_release,- ?+ Z7 Q- V1 {2 W+ F
$ z" C2 R. t" U; b, e* c4 Q& Y
.ioctl = xxx_ioctl,% Z3 j/ I o5 Y7 R3 H2 `
6 }$ z _: N" j- s9 X2 {
.lseek = xxx_llseek,: u; p0 P7 e% d% c4 d$ P. x
$ h$ w4 x5 m- ?7 z' t z8 A};4 @" M( E$ {' L8 {6 y( ?
; u3 M; w! |1 x1 P# P
上面的写法需要注意二点,一:结构体成员之间是以逗号分开的而不是分号,结构体字段结束时最后应加上分号。7 M* e# ?9 Q$ p
4 |! a) Y. b# K
, N- H$ g4 L/ w4 I6 M1 r
# T8 X. O/ _* I/ v% q( J! t& U结束语:
( \9 G, A5 A+ i6 L7 S" f# k3 u& ~) w1 V
字符驱动的原理分析大概就这么多,下一篇我们详解一个简单字符驱动程序。推荐二本Linux驱动的书给大家,《Linux设备驱动程序》魏永明译,另一本是《Linux设备驱动开发详解》宋宝华著,最后,祝大家学习愉快。
% k$ U' n+ e4 v" @
3 i0 ]* m9 W5 [6 Q7 u3 U+ J9 \8 t: F! z; {: O# E
/ b2 [, X5 C5 ~2 E/ i9 [
|
|