找回密码
 注册
关于网站域名变更的通知
查看: 248|回复: 1
打印 上一主题 下一主题

Linux设备驱动开发总结(一)字符设备驱动结构

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-4-23 09:36 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

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

该用户从未签到

2#
发表于 2020-4-23 13:37 | 只看该作者
字符设备驱动结构
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-26 03:24 , Processed in 0.171875 second(s), 23 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表