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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
Linux字符设备驱动结构3 Z  E! a( K- N3 ^: X$ g- I1 N

8 k& |) ^9 m- H! R* c; o1.1 cdev结构体" p, D- w; T% g3 P, ]

( q8 z& e6 q# R: ?- C      在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:' {4 W+ G7 i3 a6 M( \. X
: d5 P5 K- y+ }5 q  Q
struct cdev {
  M1 [/ r& S; k. j
2 \  a9 @) i7 |( ~) |      struct kobject kobj;( x3 y0 e. V+ Q! e7 T. J

+ |( G3 K3 W6 z! i6 I; L: w      struct module *owner;  /*通常为THIS_MODULE*/
* D! }$ Q; o5 }, ~0 m' i& e) Q% K, R2 {3 S3 }
      struct file_operations *ops; /*在cdev_init()这个函数里面与cdev结构联系起来*/
1 v' J4 h7 ?- ?. B4 P1 o7 x/ v! p' K; R$ M9 E
      struct  list_head list;7 B, y9 |( }) W, d3 d5 O

/ m2 n6 r# b' @3 `+ g      dev_t  dev;  /*设备号*/
* M% P& X) o0 `% |. A: j
2 n, G) t1 ?( E: J# A) E1 X, r      unsigned int count;
7 H# i( M* A, B& O' l3 ^
4 v, b, L0 s( y' A. d  p1 t! ~};
& s8 [7 X$ H/ c' o: h7 r
8 e( k5 o& g" c' a6 W: s) C! p8 @     cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号,我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:
( ?% m  k) C' ?0 I$ r
. n3 T0 O) ?% w. ]# OMAJOR(dev_t dev)  G: G, ]1 i' B& f6 d! E( b
, C0 H+ Q0 {8 V
MINOR(dev_t dev)
- \, C& K; L! M( V- b$ E- @- E; J+ w" J- t
相反地,可以通过主次设备号来生成dev_t:# n5 |/ {8 j* |
: Q8 p9 O7 p' s% k  z9 U) y
MKDEV(int major,int minor): m& t7 G0 \& |

. k% A" @9 H. |1 W1 g% x; m  ]2 D2 p5 y3 B9 i* I) l4 b8 x

, Z. }1 E  B- I  C1.2 Linux 2.6内核提供一组函数用于操作cdev 结构体:
* `8 Q  ^, D0 q4 v; Z! h7 J5 v" w; h. P% o7 N( u# C
1:void cdev_init(struct cdev*,struct file_operations *);
' ~( j. l# @+ S6 i1 D
( U9 t' w# ~( ^& S1 l2:struct cdev *cdev_alloc(void);, `: {0 g! V# S9 t
& ~) q" @4 q& i; `% `5 _
3:int cdev_add(struct cdev *,dev_t,unsigned);
- b% g8 r1 ]6 k# L6 a% W6 ~& s+ J9 A/ K: S2 o
4:void cdev_del(struct cdev *);8 c0 _3 _/ z. I: O

' q6 c% I5 U; s+ `* i- j其中(1)用于初始化cdev结构体,并建立cdev与file_operations 之间的连接。(2)用于动态分配一个cdev结构,(3)向内核注册一个cdev结构,(4)向内核注销一个cdev结构
* f, q, C6 |5 w5 V1 x0 C# m% [
  h: k* h  j  `& ]! q( f) X0 W9 }: J8 x4 f

* L# h) E; t0 U% L1.3  Linux 2.6内核分配和释放设备号5 G7 N: i- D6 H- d7 @$ f1 F) k
3 N; B. ^- ^& j5 k/ j3 o
      在调用cdev_add()函数向系统注册字符设备之前,首先应向系统申请设备号,有二种方法申请设备号,一种是静态申请设备号:6 I' d, o0 e7 o; y* t& m+ r$ R0 L1 o9 p
1 a$ F2 K: \2 V4 x
5:int register_chrdev_region(dev_t from,unsigned count,const char *name)7 Q2 ]5 g* e4 {- Z5 f0 O
7 f/ A% `3 I& l8 n* M0 ]3 X
另一种是动态申请设备号:9 i/ F0 G8 ~9 P# Q

/ ~# a) K1 [) w, ?+ H  _5 G. ]6:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);. x5 ~- ]% j8 z8 f: I6 b# Y

6 N$ t& N8 Q. Q: A2 M       其中,静态申请是已知起始设备号的情况,如先使用cat /proc/devices 命令查得哪个设备号未事先使用(不推荐使用静态申请);动态申请是由系统自动分配,只需设置major = 0即可。
% I: C6 G+ e" Y3 N4 v
: }7 B! @) i5 b: t' ]& v3 S* v- s1 T: e      相反地,在调用cdev_del()函数从系统中注销字符设备之后,应该向系统申请释放原先申请的设备号,使用:* v( g# r& x8 H/ x9 t7 u5 r

' l9 M8 D+ p% |( s5 R: i7:void unregister_chrdev_region(dev_t from,unsigned count);9 f2 O6 v  r0 O. }

& x% i/ }+ e2 ^: W7 g3 R; d5 H! }( S( M4 o3 s- w, {2 E8 v) A

8 y4 V) _6 H" f7 M3 {9 D
  _5 _6 X, h) t6 W3 x1 d5 ~" l  s, X9 i* O; _% \6 g- i$ b
1.4 cdev结构的file_operations结构体) ?6 d3 i8 e( g9 ~& j! e0 }  j. f

; e1 G; Z3 v9 w; \      这个结构体是字符设备当中最重要的结构体之一,file_operations 结构体中的成员函数指针是字符设备驱动程序设计的主体内容,这些函数实际上在应用程序进行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系统调用时最终被调用。在include/linux/fs.h文件中定义,这里不一一详解,仅仅解析一些常用的API。
6 {9 K& V, P: k3 F
, F9 C3 w  R# z% ?3 estruct file_operations {
% v) |/ r1 Z7 L1 ^+ J
0 w2 j- M9 p' L" L/*拥有该结构的模块计数,一般为THIS_MODULE*/
, V% j) G$ @4 v, w struct module *owner;
& |! k# E) N/ f9 O3 [, \) D9 W3 R. ~1 N* a' n
/*用于修改文件当前的读写位置*/
- X. g9 U* x9 T0 F! C' Z6 w" E loff_t (*llseek) (struct file *, loff_t, int);
# S3 v! C( ]3 z5 g7 x
0 b% z" T7 ]  ?/*从设备中同步读取数据*/' y: f7 R4 F. A7 M
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
9 p% ?/ z4 z6 W4 d
5 c4 f3 b& V  V. z  l/*向设备中写数据*/, d: q/ }4 @- }6 Y# A+ o9 C
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
8 K* Z$ x/ V9 V5 d7 Z) P! _# ~/ A% h$ K6 k* ?9 ]) T
8 ?9 v; @: Z" c# _0 K4 |4 h
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
' b1 K) v; O% P6 W5 P4 K: L" P ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
, b! _& i, X7 q* ~0 I( W int (*readdir) (struct file *, void *, filldir_t);/ U. a" h6 R8 }. f  d- [
# E3 @% u' [2 v2 h+ D
/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
- g, ]- z5 v" d7 D& T( v unsigned int (*poll) (struct file *, struct poll_table_struct *);
/ g- M. [' W9 m/ S- A; ~
' V9 j$ ^9 a+ \5 q4 B/*执行设备的I/O命令*/
7 B. a. C1 e& ]1 f9 o int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
3 |3 S: ~/ A9 M6 I/ ?! `9 n5 Z
! t+ \/ w8 h: N9 j9 R7 D; b* i+ a6 [5 z/ O! G
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
! H; ^) b4 G8 z4 p, ?; { long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
# `6 S4 N. ?0 t7 T8 S2 R1 l8 q' t) W( g
/*用于请求将设备内存映射到进程地址空间*/1 P$ r3 V' m, ]0 p8 o
int (*mmap) (struct file *, struct vm_area_struct *);0 }" E4 V/ h# G

4 Z/ f7 J; a0 o* x/ T+ c3 G) b/*打开设备文件*/9 i7 q4 @3 r- ~: S- H9 g
int (*open) (struct inode *, struct file *);
) r1 ~) d2 B5 Y2 Y8 [( }+ A int (*flush) (struct file *, fl_owner_t id);1 ]- X3 o" X" N4 |2 |! }5 h
4 N, e2 i( @/ \
/*关闭设备文件*/
! u6 i. m2 ?- U- W0 b: ] int (*release) (struct inode *, struct file *);  R& C0 ~. }+ r

+ o8 p8 E: B9 p& _: b. }9 o
# Q2 L, K- e" s& U$ n8 Z, @ int (*fsync) (struct file *, struct dentry *, int datasync);* r( J$ E4 d- b+ ^  v) f+ B. v8 M
int (*aio_fsync) (struct kiocb *, int datasync);9 x$ d, u& z6 L# K
int (*fasync) (int, struct file *, int);
1 Y. g5 e: z5 a( U9 T int (*lock) (struct file *, int, struct file_lock *);9 L+ T6 y8 d) q& f6 @5 h; f- b4 C
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
5 Z! D2 \9 `9 D, D4 y) N; ?  ?0 K unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
4 y* x& g# G# ~, `+ d2 M& j int (*check_flags)(int);
9 y2 s0 q1 {3 ?# w: s int (*flock) (struct file *, int, struct file_lock *);
  I  ?: e$ z4 a( y ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);% `: I- k; G( J+ q* h: @1 n  B$ c/ o
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
4 z9 c; t' i0 r# U& n, | int (*setlease)(struct file *, long, struct file_lock **);
8 x9 z( D$ [! }( T. u};; R7 C# J" o1 z/ b% r) d! }  p
5 |/ J6 {# t# z2 g, g' B. Y

+ v4 R4 s/ c$ ?/ Y3 R; }  J
5 c5 K2 Q5 L: ?% Q) h1.5 file结构
9 c7 d% }6 I' C, r0 h/ H7 @+ j
% z( c. p+ g  _$ x1 K1 i8 C     file  结构代表一个打开的文件,它的特点是一个文件可以对应多个file结构。它由内核再open时创建,并传递给在该文件上操作的所有函数,直到最后close函数,在文件的所有实例都被关闭之后,内核才释放这个数据结构。* d' e0 H; E& m6 o) z) m% m  s' P$ W5 Y

: Z" ~/ K9 a3 g: a    在内核源代码中,指向 struct file 的指针通常比称为filp,file结构有以下几个重要的成员:
6 x' o$ ^# h5 G8 j- _( R2 Z
2 s- P6 x+ ?2 O( K4 Jstruct file{
. w( g& P: L% D9 [
% G6 `. b+ d1 c2 }mode_t   fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/, n. x6 m  \" Z3 H# H8 {1 x
; M$ s3 ?8 n# B* ~: V5 M
......
* j9 v4 }* c, e# Q2 h+ |, c! c6 A! B  t: G
loff_t   f_pos;  /*loff_t 是一个64位的数,需要时,须强制转换为32位*/1 ^( O; ~4 r8 S* h& y; q

! H# o* u0 D1 w$ J) ^9 ~$ ?unsigned int f_flags;  /*文件标志,如:O_NONBLOCK*/
% b- H+ C6 E' g/ w  _, V$ S/ V0 {$ {( ^
struct  file_operations  *f_op;+ {8 K- J# z& P+ J; ?: V
+ H; i$ Z1 t: J
void  *private_data;  /*非常重要,用于存放转换后的设备描述结构指针*/( c/ q! f2 p! m9 t; C

! H  K- s# P6 p2 Z: f8 W1 _; _.......
* \5 z5 }- H, U4 r7 F9 _
2 J& P1 L6 [$ _% J) m& h};
$ U% A: b6 Y: D' R+ q) Z( F7 T6 f, e- S1 x7 m

6 `( u6 E- }1 W& X$ }. I5 ^% m9 j  f  s( V  R* R( H

4 M& E  j! z' E' L; d; K* v1 c
- _2 G$ |+ P5 P3 A  ^$ E1.6 inode 结构0 t; I/ Y" Z$ ?, c. c$ x' ^/ g: Z
: d7 `9 G5 u# d* @+ A% D; o2 D
      内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:
! _3 t7 Z  O+ X5 F! \8 D
/ L( E! i8 F9 K' n; h2 Sstruct inode{$ _5 @+ h' W. d( `

. R- f/ C# p! C7 _% U# sdev_t  i_rdev;            /*设备编号*/2 G3 j( s; j2 k% \1 o: r

: j* r2 C+ t+ R+ p: Ustruct cdev *i_cdev;  /*cdev 是表示字符设备的内核的内部结构*/& p! w/ ~" A; q1 P9 A, H0 [
/ N& \  \9 B5 K7 v  f- O% r, x# U- W
};
! m% T! M( {/ I
  x- _/ q! f8 O% t9 p: ~可以从inode中获取主次设备号,使用下面二个宏:7 z/ T* V* `9 v5 y% H' H1 G2 b
& e2 s( f) j/ w- l; T
/*驱动工程师一般不关心这二个宏*/0 H) f6 j7 a  W4 p$ f
; G+ l% m* F+ \+ f- V, m
unsigned int imajor(struct inode *inode);
) L  e" l. M1 g( B* f$ W1 f
+ k! ^+ y( B% C; f1 y9 qunsigned int iminor(struct inode *inode); 9 T, t8 W- b! L9 s$ }6 J

) z6 [9 L+ i: ]  R; F
# X  M: Q' R; {( i- K' I+ F9 V# C1 V1 s- I- \3 A
2.1 Linux字符设备驱动的组成
$ r+ }' d  b4 ]- j5 s0 L2 K" p+ H2 Y* y
1、字符设备驱动模块加载与卸载函数& b. K/ ~  o" f1 Q  U- v0 i* U

" L! E& Q' b  C5 V$ T  \. k, ~      在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册,而在卸载函数中应该实现设备号的释放与cdev结构的注销。9 [  {2 F/ \' g5 j; _; B
  q9 I! p& c$ y
      我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:
4 f& y- F0 x9 e5 K, Z5 n# J; N' D" T; N  x/ S7 ?* l
/*设备结构体*/9 [5 U7 O* W/ F8 I# y5 \3 L7 E# R  H
4 ^: l( f9 j" e! i
struct  xxx_dev{4 ?  c& J8 f: M

( J6 u  }* T5 c) Q# F      struct   cdev   cdev;- E' w1 p  v9 J

% r! J' S0 h/ s8 M; n      char *data;3 f# [$ e1 d; z3 A0 W

# l+ f9 u  v  b      struct semaphore sem;
% b# C' w) o5 t% \1 v; h2 g
7 h% L+ J) v6 t( P+ ^) L2 |6 f$ w      ......
5 s- R+ C4 D$ {9 Y, p, U" Q7 K; \# X4 Y6 N0 w
};
% g4 L) b+ i" q
  f# v3 l/ ^0 R5 b4 J" \9 p$ w* B$ y% X, E" C; h9 R
# f) {& x$ i1 j1 f: R0 B
/*模块加载函数*/+ E; ~9 f# y3 D/ {
0 |8 U9 {3 {/ B0 w8 a2 W4 m& W
static int   __init  xxx_init(void)
. }1 ?  L- C( Y( P, ^( ?* ^, M# H* v9 T% p- A
{
* M7 C2 X) p; q
- I. S+ P2 M  H' @( M1 M- v      .......
* H. U, M4 U/ c) i+ V# M
* R! _6 y# `: T" r      初始化cdev结构;
! P* S1 t/ _; M' B' p
* F" n2 ^  g4 Y9 W$ [  S      申请设备号;: b# w& n& m$ v5 O4 n

, a8 U/ d9 g# }; n0 A0 S% x2 u      注册设备号;
6 ^' ?- ~4 ]. i& ^4 \
% n" \) s0 j7 i, i1 i5 f$ ~5 b0 B' y/ P% z, L; |
6 J9 K1 W7 G$ D
       申请分配设备结构体的内存;  /*非必须*/
  o* G9 f5 W, l. ~# i# v) f( v8 Z% |. w# s
}
' o) L( O/ X, }5 c' F
6 I1 t1 q9 s& D7 a/ E. S
/ ~: R( C% r" Z( n7 n7 k
! j3 Y8 X5 K7 C2 A' Z- V. o; g/*模块卸载函数*/7 k. ?, s. k0 V

; J; V% W3 m( x! N4 D9 L, c! nstatic void  __exit   xxx_exit(void)9 x6 C/ ^! e8 h% K  J* b

) g9 Q5 z1 X  B* U% N{2 Z2 ~, F) r. U* E
/ _" M) ~& ?" J
       .......
% L( w) u3 g. Q+ {  U% _( J0 P) Y. X! x0 v0 [! f7 L+ B. J0 _; [: j
       释放原先申请的设备号;
; Y& H' @! R+ [! W6 m6 h9 r  L1 I5 J& p5 P- ~) t* w6 {
       释放原先申请的内存;
7 Y: Z9 t  T6 H1 Z6 X' E$ i, O
: e! Z' i& u. q- ~% t; K       注销cdev设备;
5 V& e) ^+ y8 Y' R& I' a6 |9 N# q2 S: X' l6 k! L$ d
}5 M; }' G6 s! q5 S
: [  G9 J- M: x9 T) y& ]" w( ^

3 g- I" L7 S/ h8 w0 S" U2 s( n1 v! T1 f) f2 g; k4 ?- f4 p

6 i6 D+ I. g) ^2 o* p! x! r# Z  j  x! ?9 @" Y
2、字符设备驱动的 file_operations 结构体重成员函数
, a" a! H  c; X4 ?3 \' `0 r2 j/ v2 {8 [7 L- Y& E4 i0 \1 r0 Q
/*读设备*/+ P8 W; t4 T# G" T% b4 o' l% P3 D

& @4 G5 n; ]" \1 H4 \# Yssize_t   xxx_read(struct file *filp,  char __user *buf,  size_t  count,  loff_t *f_pos); k2 ^+ o' r0 v- `+ C5 d
* a9 g  J4 H% p- c. @1 p
{% i6 z8 a& [. T- M

: m  A* Q) g! y        ......( F, U- F0 x. K7 \1 q3 w

! V0 s2 e/ K) |5 Y        使用filp->private_data获取设备结构体指针;
) k" b8 n- S  A1 E. u5 ~/ q6 E% ^! _: B2 q" Y
        分析和获取有效的长度;8 Z  F" G. c+ g9 O% y( Z* n

: T% o8 U6 }0 ^  E: k6 v* S        /*内核空间到用户空间的数据传递*/
/ y& ]$ l4 ^% V& d* T; a5 d/ ]( u1 x. I6 }8 `) s7 I0 K/ B
        copy_to_user(void __user *to,  const void *from,  unsigned long count);
4 F$ L4 w' c" F+ U9 j0 E9 Q/ D" u2 O" R
        ......
( Q( o% H+ Z) k7 h) g# }' \3 m% p, o% c9 |9 {
}0 j8 }* \) Q. |1 z4 B8 c
9 t' ^. O& Y0 h) t7 ?" p: F
/*写设备*/
" h3 z& J* K- H* K: _# j5 o! y6 ~
ssize_t   xxx_write(struct file *filp,  const char  __user *buf,  size_t  count,  loff_t *f_pos): M0 Z/ M9 e9 I; W; `

: }3 e8 L" i1 s* V7 y# p{
. }- z! `. {9 ~) c) l4 R
3 x3 l" E9 _/ n. |4 T        ......5 W  I- @9 y# c% T
  q3 A  |* j; h1 Y8 k) h
        使用filp->private_data获取设备结构体指针;
, K0 x5 J3 R% F# c$ J/ Y) Y+ d" ]6 }# }# \3 y7 t
        分析和获取有效的长度;
5 g) x2 L( c0 R  X: ~4 Y; D- J
( v' n+ P+ L9 M3 c3 Y        /*用户空间到内核空间的数据传递*/
) h- ~- U2 N3 J+ P* S& O" a( ~6 K- u
        copy_from_user(void *to,  const  void   __user *from,  unsigned long count);+ W& m: [3 U, Z
0 g0 l# w) t  k: m2 ?0 O
        ......
, d* A: G2 O2 U0 a1 B
7 v: l" P, G5 r. F- H0 _+ t2 b}
4 G8 O3 T; K7 V5 Y
( U. U, @0 r5 z4 O2 {. h3 m/*ioctl函数*/
! p- T* a) V1 H; d* I3 h$ Z
% O# Y: S4 a" Nstatic int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
  m$ L0 l, _7 w1 T3 S8 p3 V6 d  X9 ~( p! h
{
8 a6 a" g; G) M- w9 U+ Z
5 {0 ]" u/ o/ l3 j# B      ......
  @( Y0 k9 P5 I/ c% X
  r1 Q0 ^- J6 I2 A/ K! R7 ?      switch(cmd){
& D( [" F/ q3 G  u. I
# W2 b* f9 C9 S1 o' f5 i           case  xxx_CMD1:
' s2 B; E8 @% J1 f( ^4 D; l
# B1 s8 ^0 B6 L+ k. }" R" P. n                        ......( L9 V" ~* _0 @& a) y: {/ V2 w

4 v+ E1 z5 c# R2 A                        break;
: n, \) T0 n+ o  Z3 G9 e8 G0 }) G. ]: I3 u3 D# N
           case  xxx_CMD2:, x: w( j0 b/ f) G/ O8 E( {* P* O% ?
& Z& E; Q7 m1 F% X
                       .......  Y5 t  ]. V0 _8 M5 e7 e/ i# g
0 F3 `: s2 e& S
                      break;
. G- p2 x$ t5 a. d4 B" }9 J5 k& ]5 w6 ?5 K3 |! ~+ b- m+ S
           default:
  R0 r6 c( M; p# n' Z# B0 u/ p' l
                      return -ENOTTY;  /*不能支持的命令*/
5 ^# [' \/ P2 R5 K. }9 j. h' p* ~% @* _
      }7 r9 _' u5 X! ~) H/ J4 E

8 x  Y1 X3 Q! d2 u' W: J      return 0;0 \) m# y3 z0 ~4 u
6 f; n7 m+ R7 F! M" Z% X! A
}/ }' k* W+ m6 I) m$ Z, u8 V% D% f" S

7 Z1 C; w$ D" w  Q1 G! Q2 L& H
8 [, U9 Z9 M! @/ K  U- o8 M% e1 t. K" [! z7 T! p; c9 J1 n+ [8 |4 w7 b
3、字符设备驱动文件操作结构体模板) H: W! O  J- O- r

! Z: ^0 ~! V  N, [% q* x. G! [struct file_operations xxx_fops = {( u/ ]+ ^7 a' c

8 _6 p& [+ }& \6 h( b; T      .owner = THIS_MODULE,
" J) m% V2 N9 }5 t5 j) a4 H; \; Y5 P: O& l1 h  G5 a% ?
      .open = xxx_open,
0 u6 j4 i3 \  b
$ o9 I! O! x& {1 A' S$ y, ~% d      .read = xxx_read,! A" Z. Z$ J1 Q/ n& h

# j* \2 l' [$ ~/ f% R( V     .write = xxx_write,2 G' G  N5 ]4 `) y
. S4 ~, _) Y8 Z( `8 z. Y
     .close = xxx_release,; y" J, i4 R5 T' a

' h+ m5 u# P/ x2 o# o' C% |     .ioctl = xxx_ioctl,3 `; b) u" D% S" K% W

! e9 G+ Q  k9 m3 S5 k& }     .lseek = xxx_llseek,0 h5 F+ d' c2 H" e) s( ~' D% @' i( h) v* Q
4 l1 a/ e$ K4 y+ L/ h0 Z5 i
};
) u6 t4 d" u% b1 s3 y2 S: \, W* q6 K( T% ^1 X0 h
上面的写法需要注意二点,一:结构体成员之间是以逗号分开的而不是分号,结构体字段结束时最后应加上分号。
$ D& E+ X# }8 W# a' A6 D8 E7 P( w( t8 K1 L$ `

9 N4 X* C' o# }. G* T
# q. ?8 F8 g, Z6 \, S+ n$ q$ O, Z结束语:& d. X5 A" h  H

6 G; Z6 y% n& O9 b! C' _4 y            字符驱动的原理分析大概就这么多,下一篇我们详解一个简单字符驱动程序。推荐二本Linux驱动的书给大家,《Linux设备驱动程序》魏永明译,另一本是《Linux设备驱动开发详解》宋宝华著,最后,祝大家学习愉快。* c1 b( P. g- K) r/ x; A* }" B
* T0 ?3 r: \& e# k/ S+ C
3 ?& \8 h, x# [+ H3 Y2 O, N
. f; L( z/ @6 l! s& r+ X) j

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-11-26 05:49 , Processed in 0.156250 second(s), 23 queries , Gzip On.

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

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

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