EDA365电子论坛网

标题: Linux设备驱动开发总结(一)字符设备驱动结构 [打印本页]

作者: haidaowang    时间: 2020-4-23 09:36
标题: Linux设备驱动开发总结(一)字符设备驱动结构
Linux字符设备驱动结构4 T: K& e6 A7 x% T4 @) n0 v. s

# y/ X. B1 M# X. [+ c+ M6 t" H1.1 cdev结构体
! K% [6 o' @* Q7 L9 V5 h! V/ ]2 p; R$ b. a
      在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:" k. U0 r  U# n

0 U8 |) S5 A( Z- y. B. l/ i/ K1 dstruct cdev {
) e$ T& h+ @6 R4 n; j' ~
( c* J" a# B+ ]' V$ b      struct kobject kobj;
  e; T8 u- N2 r2 M# t5 i5 t  x. U; }0 T! @
      struct module *owner;  /*通常为THIS_MODULE*/+ N; Z  d6 x5 Q$ S+ h$ j0 V, {% [

7 N+ E- b3 n. }, H      struct file_operations *ops; /*在cdev_init()这个函数里面与cdev结构联系起来*/
! m5 H* Y, n1 `/ Y" M! t2 m6 o
4 \: K. Q9 g' a9 j+ a      struct  list_head list;3 D* L% V. Y- K2 e1 C
# @: z' V" X0 S5 A
      dev_t  dev;  /*设备号*/7 L1 _' E, E$ H) t

5 p8 M) f4 F' v2 n* k      unsigned int count;6 q# F. _3 c4 N3 l

) A% I* y1 z9 {' s/ L};, {1 I% z- ~7 x5 {- ?6 [

0 M8 Y+ {) v6 @0 E. w& F! [+ o     cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号,我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:
3 R4 R; G, d0 S& i8 x4 C* ^
! U. o0 U) @% `' L, KMAJOR(dev_t dev)
: {8 M+ Y- p- G3 ^; ~# [8 Q5 I
: O& X' B* f) HMINOR(dev_t dev)
2 B) b0 p" q* j9 Y  c* \+ ^: I, V7 }. I% P' K/ S
相反地,可以通过主次设备号来生成dev_t:
$ T5 V8 e4 A& l
0 v, s  k# j; BMKDEV(int major,int minor)% D9 s! _* T! h% |) V, E
% e, A0 U- f/ q

, t% `3 y; F3 ^+ Q: k: r9 S( l
5 [9 B) X! K/ X# y* L4 L1.2 Linux 2.6内核提供一组函数用于操作cdev 结构体:$ ~' Y/ x3 U. j2 z4 V( T

/ G( F. G& m( C8 \; d1:void cdev_init(struct cdev*,struct file_operations *);! n& |( ~3 a( s) j% c8 Z+ J

2 [! U! t5 |9 m/ h1 @4 D- V2:struct cdev *cdev_alloc(void);
1 Q) P8 V* l7 _+ L+ c6 F, M6 e
3:int cdev_add(struct cdev *,dev_t,unsigned);. @  p9 A6 a. `' w7 z* y9 a
4 B: I7 l% z( R7 }( P4 G
4:void cdev_del(struct cdev *);
5 |2 ^: ~6 G% G4 L; [9 x) @
* x3 g1 _9 A* g1 G# z7 N其中(1)用于初始化cdev结构体,并建立cdev与file_operations 之间的连接。(2)用于动态分配一个cdev结构,(3)向内核注册一个cdev结构,(4)向内核注销一个cdev结构
6 h* ]  r/ `/ {( K
" T# o8 a6 B3 z$ |8 w1 c
2 u7 i/ R9 \, a8 V+ U( T" t: A: ?$ O( Z# C; V0 v# v
1.3  Linux 2.6内核分配和释放设备号
# ?, z9 q9 Y) u2 n' \* F" r& G& p; v3 `3 f! w
      在调用cdev_add()函数向系统注册字符设备之前,首先应向系统申请设备号,有二种方法申请设备号,一种是静态申请设备号:
' u7 e/ l/ \, S7 d4 t7 k1 o- W; ^$ v2 L% ]; h4 }3 |6 S! b; t
5:int register_chrdev_region(dev_t from,unsigned count,const char *name)
# D" J& f* \/ W5 S# Z2 _* y
: f* n/ }; h& N! z4 N; T另一种是动态申请设备号:
6 h/ J# C  G. W+ Q( T6 W1 s1 ^- W' ]1 |- u( w+ D
6:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);  h1 B& U0 n5 C, ?+ X6 j( J

2 Z, {: j# V3 t) l4 m) Y% p& l& {       其中,静态申请是已知起始设备号的情况,如先使用cat /proc/devices 命令查得哪个设备号未事先使用(不推荐使用静态申请);动态申请是由系统自动分配,只需设置major = 0即可。
' v2 N3 Z8 L# F, t* B& A
5 ?, m" \% i6 @# {( J8 `      相反地,在调用cdev_del()函数从系统中注销字符设备之后,应该向系统申请释放原先申请的设备号,使用:
" ^3 W1 {5 h- ]& o( E
. ?* Y7 y% Z- }4 u: w3 D# u7:void unregister_chrdev_region(dev_t from,unsigned count);
1 H8 V" I6 B5 u5 ]* `
' r* j* @2 B1 d9 L, ]: I( U! Z# D8 f) P, @

8 q) a) S5 r3 ^" b% V7 p# J' R0 `7 m1 y
0 Q: `9 z4 I# Y4 Y" C7 w. ?4 T
1.4 cdev结构的file_operations结构体$ U) D% Z4 i" e

: g- Z* G6 E& _. q3 q  b3 _      这个结构体是字符设备当中最重要的结构体之一,file_operations 结构体中的成员函数指针是字符设备驱动程序设计的主体内容,这些函数实际上在应用程序进行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系统调用时最终被调用。在include/linux/fs.h文件中定义,这里不一一详解,仅仅解析一些常用的API。3 B  j8 l0 r( o( V% A" W# _9 h( D
0 }" A* }  B1 d- {; J
struct file_operations {
+ S9 [& Z5 G: w2 a. _, @' d1 i7 M; s; J5 J% E
/*拥有该结构的模块计数,一般为THIS_MODULE*/( \* G( l! t/ A7 `4 n& F: g' v
struct module *owner;
& v$ F+ [: D- p, x# L
. V' ?1 X" L- o- q* t" o/*用于修改文件当前的读写位置*/
6 S: V" Q* @0 ]3 V4 g* V0 s loff_t (*llseek) (struct file *, loff_t, int);
! T- w3 q( y" E5 c$ H- C: W- D" g( ]- o% G
/*从设备中同步读取数据*/
- S) i* S: d/ H$ A" a7 [% { ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
: y1 `2 {, R5 y+ K: y
5 f8 z1 b  B7 Z9 m  J1 c/*向设备中写数据*/0 R1 I7 `( {6 }1 E6 p' x' o
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);/ ]* X% h( J# q$ }

  g. c5 h8 \& I" K, @/ ~9 Y" I3 K9 Z' c( H. l  ], N& G  v) w. M& u
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
. m, u$ ~  C% U9 M$ B( e ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);6 |1 l  h% h0 L5 A9 n9 `
int (*readdir) (struct file *, void *, filldir_t);; }0 U- o( x- N1 C  L$ t* S0 L
; {/ y  T: e5 i% s
/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
$ K. q' E; D, M; h' x3 d$ A unsigned int (*poll) (struct file *, struct poll_table_struct *);" s2 M0 z, q2 V0 X1 b0 V# O. p

# k  z! b9 ~2 k5 g/*执行设备的I/O命令*/) V0 S+ R1 u9 z0 d1 p3 t
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);' [% @0 J% v$ H6 @

4 P2 x0 d; J; t: k$ }8 B7 w6 f
3 I; v8 O- }: M. Z5 N6 l long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
5 t0 h' Q3 |. c6 K long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
" }. w, Q5 E# z% o7 B. s" ^
- d* U  t' u% c0 _2 f9 `/*用于请求将设备内存映射到进程地址空间*/$ n8 [. e$ t1 |1 ^5 ?
int (*mmap) (struct file *, struct vm_area_struct *);
8 H  F" l# I  z' r9 o( j0 k
8 E% ?4 u: L3 @/*打开设备文件*/
5 O% t, S5 M& e* `: j- B" B int (*open) (struct inode *, struct file *);; i, p/ |( C: A8 h0 O
int (*flush) (struct file *, fl_owner_t id);
) b3 P4 @# f( u2 F3 ^( t. K+ k0 J$ w  @
/*关闭设备文件*/$ ]. a% q; Z3 X
int (*release) (struct inode *, struct file *);
1 o  [' H# T/ s$ Z) X( \$ \, G
) r4 y& N; a0 s# _! k9 L- J5 w+ L" k8 u: a
int (*fsync) (struct file *, struct dentry *, int datasync);
5 C1 O( u5 }  m  J% I int (*aio_fsync) (struct kiocb *, int datasync);
" k( Y3 c# U* d% k: [6 ~ int (*fasync) (int, struct file *, int);3 J0 @+ ~& U( b' F
int (*lock) (struct file *, int, struct file_lock *);
1 l1 U- n, Q* T6 k ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
8 Y5 }1 w6 u7 U, A6 H unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);( L9 p  @- R* O" @1 n
int (*check_flags)(int);
& p8 j0 P) m% ^- y+ B) Z int (*flock) (struct file *, int, struct file_lock *);* c) D1 Z* `8 ^8 z+ Y$ |
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);3 H% e) f4 l& \$ [
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
) C: p$ S& K' q int (*setlease)(struct file *, long, struct file_lock **);
/ N% A& Q6 \4 ?0 X# U: l};
  \- t- B" T7 z. `2 h! A/ u0 B
3 s5 p6 a: Z& O& k8 i+ Z1 s9 \, _" ]7 n: U2 \; `5 F+ j  D" w
4 w5 n$ P% H6 t0 s' `6 o2 T2 B& V
1.5 file结构( v( F  X1 \$ x# W2 K; P

9 a$ W+ F: E$ d& A9 Z+ Q9 C     file  结构代表一个打开的文件,它的特点是一个文件可以对应多个file结构。它由内核再open时创建,并传递给在该文件上操作的所有函数,直到最后close函数,在文件的所有实例都被关闭之后,内核才释放这个数据结构。
/ H' e9 X% W) S) A# U: G
& F9 R$ E* A6 \" ?8 e    在内核源代码中,指向 struct file 的指针通常比称为filp,file结构有以下几个重要的成员:6 q8 Q1 X' D4 U6 {: T
7 N) ^/ m; `# J0 ]! u) m& t) o
struct file{! K9 n, r4 Z$ A! U; ~) E7 O

: g5 {. |! A1 h! S; C' Rmode_t   fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/. n, ^( z5 m. N. d9 V, G

! O, x/ n  J: Z( ^1 Z3 b8 o  _( D+ R# ]......
$ m' F" w! H" f7 P& Q
  [1 _; R( b+ t- T0 mloff_t   f_pos;  /*loff_t 是一个64位的数,需要时,须强制转换为32位*/
9 y8 f. O$ [& i$ g) O/ P0 |' H7 Q% \! H! t9 H. |' q8 Z
unsigned int f_flags;  /*文件标志,如:O_NONBLOCK*/6 f; {5 u) }4 u% f' f3 R/ n
) U' z" ~( P+ r, D* s, u7 c; s! E5 h8 X
struct  file_operations  *f_op;
" F# T* M. Z. A; M' \+ M0 Z9 q& Z4 b2 S2 K5 L" Q2 e( N+ f
void  *private_data;  /*非常重要,用于存放转换后的设备描述结构指针*/" m3 P7 f; W; U( g1 k: ]
- q5 g% p: Z- e% J
.......
. \5 V) Q: A. B8 t/ B7 h" U/ v# |
};0 B- N' Z4 u! }) E: r
1 H# S& i9 i6 }

: x5 c  w# E* b% v6 w. d) T
. ^" E7 E$ s( y# _( K/ B
% r4 b2 h* B- i1 B' L8 q
) B5 ^8 ^: s: q; S0 w6 U1.6 inode 结构- ]( J8 i9 }( h! b% P
. e# g# T/ a0 Z4 b  ]: |
      内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:9 {, g1 E; Y1 \

( i- c. X; E* O2 m$ `6 ~. c! c* |struct inode{3 {0 G6 q4 g4 k! G; V3 w: L* D

" V, ^: h/ o9 E, H  hdev_t  i_rdev;            /*设备编号*/: ^' ?1 |" ]2 \* H: H! }
. ^) A  ~* ]4 O7 o9 F9 |* f9 B  Y
struct cdev *i_cdev;  /*cdev 是表示字符设备的内核的内部结构*/9 T+ G( I  Y! X& Z

! X* m7 _9 U! o7 V};" i! i: J( b2 w3 o

4 D4 U& p! Q" `$ n' m9 P( H7 ]可以从inode中获取主次设备号,使用下面二个宏:
' d" Y! y# Y1 O( I4 Y0 i) z  I5 f. ?% n! V! S! A# m
/*驱动工程师一般不关心这二个宏*/2 P: k( I. w1 K6 r/ z7 S
* f5 [1 `- ?$ U8 ^# R0 M& O
unsigned int imajor(struct inode *inode);7 V- R3 J0 R& g3 y1 M/ {
1 G1 u9 y9 Y$ `( B
unsigned int iminor(struct inode *inode); ) N' @5 q6 P- }3 u  V) ^$ ^
  b1 `% J  w' Q! K0 H5 T
: J& D4 U: _# p; Z
5 ~% |- x2 `6 ~
2.1 Linux字符设备驱动的组成* X& s( ~3 L3 I0 e7 X: T- b# p

% g$ V- S, v( Q1、字符设备驱动模块加载与卸载函数3 k8 `( Z+ n. C% X2 ]

% X" U! S, K" f: r+ f1 G2 z1 A      在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册,而在卸载函数中应该实现设备号的释放与cdev结构的注销。
) e. _7 A* F( Q! p" R% }2 e5 ~: S- K- O- w
      我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:
& g" N/ G: H# u" }' \) P
1 G; s2 t! H2 K  b* m/*设备结构体*/
% q1 U# E9 d* j) P2 X
+ S* v! Q, v% ~. F: \" mstruct  xxx_dev{2 Q4 c+ ^0 Q& `

, u! T! t* O9 P5 r" M$ P2 P/ b4 U! q      struct   cdev   cdev;
% @5 p( n6 Q; f+ g8 s
' w( j% }; B. o5 E- M5 o- p8 H      char *data;
! N+ i) @( {- x: J5 l; A
9 M2 k( |/ D9 k" U& U      struct semaphore sem;6 C+ N- v% e, h. `
- f- k1 ~! [- y  {
      ......
  K/ k+ U8 v/ H2 G
' z$ D9 ^  e$ [; ]/ U- D% M8 s& C};. n( O5 {# z, _9 Y: ?
, Y$ h$ m( I+ i$ C
* e+ q2 A# m/ F! x* x, @
+ }: g6 f! s8 A( o% [/ }
/*模块加载函数*/
/ T, j. c1 p7 c* g
! L) Y% Q' M5 o5 w. U! h0 z  Y% w2 Sstatic int   __init  xxx_init(void)
0 z! |% L. g3 J5 {) Q9 n+ C$ P9 f3 G* K8 Z6 m7 o
{6 v6 r% O! _3 _

- t% Q7 a  {' u5 L      .......
$ O, J4 f  j& d5 u+ X) |
  m/ h+ E4 x5 J  l& ?7 L# B      初始化cdev结构;
3 m% V  \6 J8 U5 J' a% d% I* |% P
2 m  E( W  t3 g  I      申请设备号;
  k1 D; H0 D# W% M0 b! ?* a, `2 z1 Z0 z) s
      注册设备号;
5 I7 s' R+ t9 g. s. f; o2 |
' o' C6 a1 }9 s% t. w, C4 L3 i0 H; f" d/ R' D6 c- t: Q

5 Y' {7 Q( J+ A6 g2 {+ I* W2 W       申请分配设备结构体的内存;  /*非必须*/
: @7 r7 ]- l* y  x" I: p% x
+ D3 h- y0 C7 T6 }}
( p! Q  P+ g" i# n3 w! I% r# D" U+ M, S$ l

) A. J$ x9 d$ P+ C; }! Q
1 F# I7 V. b) y/*模块卸载函数*/
/ G& D$ O1 ~$ V6 J) K: w
0 q" d9 @# h4 {7 ]& T3 {' A* J8 vstatic void  __exit   xxx_exit(void)
3 m3 O8 @9 H# U' {, v- \! S' C3 q1 z+ o' j1 ]& t
{1 O$ M4 R% r/ ?$ u

( I- p4 x5 L) _0 n       .......
0 ~* z/ g5 X* P" V8 _7 o( E+ f% C/ N2 k& y9 v: U( {
       释放原先申请的设备号;
) U7 I+ @' G+ t1 J1 |  X4 |6 r3 a! a  L6 @9 D7 Q+ Q; A9 ~
       释放原先申请的内存;' B, P! F+ t" H+ Y" g

% O4 n) c# ?$ N; U       注销cdev设备;  z& a5 j) l; ?/ S% R$ {+ n* n

8 }, c2 ?1 F7 e: L}, E. h# w  n* k
4 K9 C& z" X+ O; Y

# B0 l7 H6 `" a- i! h) z
8 W: P1 o4 E% [' {! @0 N: g# S; \) N7 }9 m
) Z/ G/ t- x! f( V2 A+ f: ]+ |
2、字符设备驱动的 file_operations 结构体重成员函数& ]" x6 b- G7 e/ T  x
4 e2 d0 V( I$ Q' \3 u3 i
/*读设备*/6 e4 k$ |3 u2 I3 \! V
' w8 p' ~4 R3 J8 g* T
ssize_t   xxx_read(struct file *filp,  char __user *buf,  size_t  count,  loff_t *f_pos)) ^$ p# s$ c4 }9 u# o  K& `1 j

! c% T+ w8 E0 w" V{5 }) p/ H- g  q- y
, {; A1 b) Z4 w+ f* X+ R3 R
        ......# r1 A( K' S2 ^" `
8 o. M$ w3 \! K1 t! W" h" b9 @
        使用filp->private_data获取设备结构体指针;; O% b+ x! [$ e9 ~) o. g) n8 W6 M, ?
# z+ ?! d+ L0 d2 n
        分析和获取有效的长度;! T# V% l4 p5 E% P; l

( H8 \; K. X$ ^4 P* F' g! @- o        /*内核空间到用户空间的数据传递*/
7 x8 n6 j* p+ |7 \6 ]3 g$ w2 I+ U- w6 Y' U8 @8 E
        copy_to_user(void __user *to,  const void *from,  unsigned long count);! V  p; _4 D0 f2 W# M

6 o3 _  k0 V6 }        ......+ A7 F& j% E0 q# t6 Y
1 M/ e6 `* X8 y2 b& g
}+ t0 B, y, P% m+ e
1 X7 v! t) L+ J( S3 k; K- m, v
/*写设备*/9 q. Y7 u+ P- p6 L2 [( k; R6 t1 D
8 p6 A7 B" T% [" s! i+ T
ssize_t   xxx_write(struct file *filp,  const char  __user *buf,  size_t  count,  loff_t *f_pos)
1 x' b+ L, c8 p4 @8 N( }8 [# b# t4 c9 B
{8 [- |4 a. Y8 M% F

: a; ~( ]1 P+ C4 ]4 Y' O        ......* n! X- T0 _  f) P& h: g

" [" _& A8 \4 X. C        使用filp->private_data获取设备结构体指针;8 U* B) v$ i  ?. S6 s9 R# Y
6 t7 U) ^5 [( O7 i1 [5 u. d3 W8 P
        分析和获取有效的长度;
2 j' g" i' R/ h# G
- x* Q  P/ c9 |& W+ }        /*用户空间到内核空间的数据传递*/
7 u8 U9 O5 ]6 v# Y% `: u5 v  k  C
        copy_from_user(void *to,  const  void   __user *from,  unsigned long count);
& `  _( R: p; z) a
# T) J9 m+ L$ w% s  x/ F# u  d+ S. E        ....... c/ i! t0 Q* n

6 M/ L. W% a% v3 J$ n}
. O2 n3 ?$ N; c9 t5 |' _9 s. z1 l% e! W; G" \
/*ioctl函数*/8 V1 M+ D$ Q" q( f* y5 F0 h
. r0 z' N5 ]) O: o3 c& Y
static int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
# _5 _5 e4 S/ n: O2 w, V# U6 R( E3 O: w; ~; W% p' J& R
{
( W3 L7 h: C" @, l2 y$ u; ?. B4 z! W: i8 d8 T6 {
      ....../ @- w2 b3 r4 [1 S! C  o

2 \" }3 f" ]4 T) @5 M      switch(cmd){
: e( }  n) Q( C; G6 g
( H8 f, E- V/ b1 p: }& G: i' X           case  xxx_CMD1:
9 q/ L$ A$ r% \- p- L
% A* `, O8 i* o8 |                        ......
/ A) x: x. }" [: M
9 R- L" ?0 ^6 O1 @! n2 x                        break;: Y# [; k$ i# w: M# H  `9 ?

9 q. o0 ~# @' F4 u1 s( V7 }# k           case  xxx_CMD2:
& x3 u8 ~, g. }6 m4 r1 m3 `( H- z( x
                       .......
! P; [9 A7 L" O: H
% T2 T9 A- R' h  r5 K! A                      break;
4 _6 x/ H+ o2 @; X3 R% t; }. N% {0 a: ?4 a' O9 ^+ d: f
           default:$ K% J" K" p$ z( i* z" A
1 a. V( G- L1 J. `# c
                      return -ENOTTY;  /*不能支持的命令*/
* _( B% Y+ M, a* a- m6 }" E8 \% S* X  Q  v4 g! l" A! ?
      }
! m# e' c" g* |) D/ r0 S6 W3 W
7 e4 M9 A2 Y) [& j' g; @      return 0;
* n( S' I7 ^: `, W' {4 k' I8 y! _% c- S8 E! q2 ]9 z) J
}
3 H" g: ]  `+ @2 ~
/ c7 c. s; P+ f& l5 {% _0 n8 C1 _; @) _$ W/ Z1 G
# E: S2 u7 O" ^1 l
3、字符设备驱动文件操作结构体模板: n- O/ W/ I6 O2 Z9 E/ M; k1 B) [
* l: k: [0 M2 ~. w/ j7 p
struct file_operations xxx_fops = {, r9 p2 z! b& q$ K" {: x  I

) S9 W& V  J; k9 E+ \$ c      .owner = THIS_MODULE," @0 z* Z) f7 Q8 Y2 ^1 t

( Y; w2 W' G1 u  I3 J% g, E" S# P* w      .open = xxx_open,& [9 e7 J  N; m# r# k; a

+ R" r/ \0 Q$ A" W. T7 q0 B      .read = xxx_read,
0 b% L/ J  d# f; @9 l1 V/ d9 c+ R  `; N% h1 r% R- H3 i
     .write = xxx_write,
' h1 W6 c7 d. ^4 d+ t+ h
0 a, S* `5 [* V0 Z( s4 D2 d     .close = xxx_release,
% w" |: x9 ?. W1 ~7 \5 f7 U' v
8 M/ ]2 s  \- b     .ioctl = xxx_ioctl,  N! X  _+ M1 j

! C2 ^9 }. v6 o4 {" }2 z7 @     .lseek = xxx_llseek,  t8 q( T- S8 C3 J3 b  }
, E/ z/ K- L/ r* Z% @
};
  Z) \% b. |8 S: k6 M
; n; P0 P2 m, s( R4 \上面的写法需要注意二点,一:结构体成员之间是以逗号分开的而不是分号,结构体字段结束时最后应加上分号。
( U8 n, B* b/ f2 n6 D9 r
) ?# S! _- a4 @# s7 R
7 I% F( V* \- M' B5 i* Y  e$ U/ k: d' m  a$ ^6 `
结束语:
$ C$ q) @$ R+ h, T1 E, ~( E
3 B, c2 \+ _: N" K6 _; o            字符驱动的原理分析大概就这么多,下一篇我们详解一个简单字符驱动程序。推荐二本Linux驱动的书给大家,《Linux设备驱动程序》魏永明译,另一本是《Linux设备驱动开发详解》宋宝华著,最后,祝大家学习愉快。7 v1 W' U. B5 \: [4 W5 f$ H

- K9 m3 {' n6 l# E7 E7 C2 n2 w# _
4 d! j$ C9 {2 h# s7 {
, S' Y9 h% \  W2 Z
作者: NingW    时间: 2020-4-23 13:37
字符设备驱动结构




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2