EDA365电子工程师网

标题: 驱动基础知识学习笔记 [打印本页]

作者: admin    时间: 2019-9-27 15:09
标题: 驱动基础知识学习笔记
一,知识结构- Z/ O  \. W" P$ g" ]4 `% A  g: t
# [( K* q  i7 _
' I0 N' E/ M8 Q: j8 D
二、驱动分类 可分为 :字符驱动、块设备驱动、网络设备驱动% a) x( ^; [2 o& g& [2 S7 u4 i

0 C; u0 o1 h' B; D5 N4 r6 b9 V* y! ]: ]; n0 {( \  n
字符设备驱动以字符为访问单位(一个字符可能对应多个字节)进行顺序访问,不能随机读取。  X* C' K9 f/ _# k9 f* \
# j& o& A8 X% @7 n. y1 [& d& r

0 G# b6 F  |" ^! a/ }块设备驱动:以块为访问单位(块可以在内核中进行配置),通常512字节,或者更大的2的N次方。
$ ~4 K. p5 y2 o+ q) M
( _: G) @8 F! `6 N) E9 d7 [) @
" d' l$ U; W5 `8 j$ J块设备可以随机读取。在linux中块设备也可以以字节为单位进行访问,块设备跟字符设备
! P0 F" N( X& r1 ^9 D8 v: r! F. g/ V2 ~, l* I* P  A3 z# |3 ~
$ h, n9 @8 g0 }3 @6 R6 b
主要区别是访问接口的不同,并且块设备可以随机访问。
8 L! P* O5 C! e, J+ N( `
- r2 C! G, z% H
! l- v; P' @4 U# J网络设备:网络设备是以网络接口为访问对象的。可以是一个物理实体,也可以是存软件,例如linux下的lo设备是个存软件的网络设备。) g# I$ d% U; p+ t
2 _9 U1 _0 y4 z! v: f

* {+ i8 J  K0 Y$ `5 N7 y! h三、驱动的安装方式  R% |# b7 @1 h/ K5 A" z
$ L# R! A) b2 j+ E+ p% U- e

! W, X2 r; X& p1 y, k1.驱动可以直接被编译内核,也可以以模块的方式安装,驱动设计的模型跟内核模块设计一样,入口都为module_init(),出口为module_exit();如果想要把驱动编译进内核,需要配置相应的Kconfig 跟config还有makefile文件。
" I" O) ?* {" p. D- j" X( R; \! S+ r' @$ f& d- u4 I$ d1 O

9 N% [7 \2 Z' A/ A/ R4 n' G四、字符设备驱动程序
3 H; K( g/ ]5 c: I
0 U0 q9 v& r, B, c& |+ J+ h3 y: ?) n3 y$ O0 }8 C
字符设备驱动设计流程:
: X- T* `% k3 ?! S# y7 r% u# _6 d% z, |) T. D' p

1 W' B: ~& Z) x& L6 }* R设备号:设备号是一个unsigned 型,高12位为住设备号,低20位为次设备号,linux系统提供了MAJOR(dev_t num) ,MINOR(),MKDEV(major,minor).来提取跟分离设备号。其中,主设备号是表明设备类型,是建立应用程序跟设备程序的纽带,往往我们有一个产品中有多个一样的设备,那么我们通常只有一套驱动程序,一个主设备号。通过不同的次设备号来区分访问不同的物理接口。一个主+次设备号对应一个设备文件。
  m7 _% A4 X8 p# D& Z0 H( k' W
3 g0 x, M+ i8 V2 _  K- z. Y# h7 i6 n* ^) A; X" q
首先申请设备号,可以通过 alloc_chdev_region(dev_t* dev,unsinged from ,unsigned count ,char* name)设备方法动态申请设备号,当然,也可以用函数 register_chdev_region(dev_t num,unsigned count,char* name)来静态注册设备号。在静态注册前需要通过cat /dev/ 来查看当前没有使用的设备号,才能分配给我们的设备,要不然,可能会产生设备号重复,而使我们的设备不能加载进内核。设备注销时需要释放设备号unregister_chdev_region(dev_t num,int cout);2 Q8 @% }, p" H: b0 k

+ }3 a6 I' c$ U% Z9 \/ M9 @# H. \7 }2 F0 {( {8 D3 H% x, }4 q
2.设备初始化申明结构体 struct cdev cdev(如果申明为指针在使用前要注意分配内存),定义结构体 struct file_operations file_ops={
$ \. K7 z$ ^0 m/ p, A2 s/ N( x* c" R' p( R. i

& v6 g% n5 x8 d8 y) f' V3 U.open = mem_open,5 X: O+ }5 g; L5 \

" L& ?! n8 u9 Y2 H2 |- @  S
$ y1 l& x- y8 J2 J) z2 H) @  \.read = mem_read,
) S5 c1 ?8 Z* S/ d2 c; N( H0 v) K; o$ v' K8 Q8 G% i4 S$ n
) F) F" _% p3 ?' Y/ u9 i0 W) C
.write = mem_write,) r. q1 M2 u% z5 i6 x
1 M1 ~( ^8 r  w# s* e4 q; i
- _& Z: }/ f- O5 [$ B, f5 h
.ioctl = mem_ioctl,: e1 @- W8 q5 c& g: {! ^0 P! T% Z- A% u
, R  M: m0 M0 L% s- e, z  w
) n/ }7 ^6 x; E2 n& n' k% q
.release = mem_release,
7 T2 L* w- a) @; c: S4 b
' s3 Q' \( s% W  R2 z- e% g  n4 g* n/ O7 ~. Y1 g
.llseek = mem_lseek,
; X, Y( @8 Y' ~) a6 g: {3 L! ~' R% x5 |
" X- f0 i4 i) B1 G
};并初始化功能函数,为对功能函数初始化的,默认为NULL,
% }8 \8 ^5 d% a$ P& T% |$ F% @8 W3 H4 Y$ z( @1 d3 F2 i: n
( a  [! {7 b! f  B) N( e- h$ G' n
然后初始化设备 init_cdev(&cdev,&file_ops),指定模块所有者为模块本身 cdev.owner = THIS_MODULE;
% S2 w$ b: ~% q  n, z& ~+ w: J
1 |, O; D$ \5 z3 A, v. Z* F- b/ n0 ^7 [/ z
初始化完成后,添加模块:cdev_add(struct cdev* cdev,dev_t dev_num,unsigned count);此时,模块注册已经完成。! G( E: V- Q( T" q; K7 J4 A

) J5 u% ?' o$ _6 [6 X  m- i: f2 M3 _; \2 z, x' A; ]( R4 C
在设备卸载时需要释放模块cdev_del(struct cdev* cdev);
$ U: U  o: T) [' q( W" J% w1 ]+ i8 b; |
8 d% A+ l. R3 d4 B; g; H/ |; U. |( R
3\需要注意的三个重要结构% K. Y$ [- ~6 r

1 ~) ]9 |* ^0 |( G( [7 w% x9 J3 v3 Z. H7 T
1.struct file 在打开文件时由系统创建,代表当前打开文件的相关读写信息。在文件关闭后释放。重要成员 loff_t f_pos;当前读写位置,struct file_operations* f_op;1 J+ Y! l$ X7 t- ~% F
$ L0 b8 G0 V/ m
8 k. b8 W1 W0 ?2 i3 Q# M
2.struct innode 结构,表示文件的物理信息,一个文件可以对应多个struct file 但只能对应一个 struct innode。重要成员 dev_t ir_dev;) v5 _- i4 B( f& B* {3 |

; i& r9 X& p* A& k: G% A8 p3 T4 e6 Y  D! \) E2 D2 e- y
3.struct file_operations 是一个函数指针集合,实现驱动相关函数。在设备添加中,我们讲 file_ops结构传给了cdev结构,但如何再传给struct file结构体的,还不明确,需要再深入研究。
. O1 ^, `& C% d- A- h3 E
* D6 N, [% ~3 E0 i6 p7 w* h
- U# W& R: Q9 D4 S应用程序在调用库函数fread时最后都会使用到系统调用read然后关联vfs_read,最后将file_operation结构里面的函数关联起来。' `6 q2 W* q* g# U" V

: s( R( q$ m# U! |$ Y) D% Q# ?% `5 M$ Q6 K9 S, d
设备文件的创建:设备文件可以通过两种方式创建,一种是手工创建设备文件 mknod name c major minor6 e8 f, w: B6 H5 ?. ?* C
. H1 G: p1 L" j7 o& `
3 H" u( f0 Q5 W" y$ z8 T2 n
另一种是自动创建设备文件,分三部完成:
# Q  G* I# p2 n( c* K2 S
: z7 S; }0 G, F4 I; c4 s4 g2 w9 j* z' f5 q- N( X' a
struct class *myclass ;
0 d( P; n' F/ F
. Z3 P8 I; |  k7 y$ ~4 k$ _; X5 i: ~( T' O
class_create(THIS_MODULE, “my_device_driver”);
, X! l. S5 s6 y2 \7 v2 L# b( W' @3 y
5 [+ `7 N! H# G9 Y
device_create(myclass, NULL, MKDEV(major_num, minor_num), NULL, “my_device”);
, I3 M2 w% D) p4 b' _) `3 i: i3 K2 @( |( ~
/ s  q% q6 ?/ t+ M  O! `4 [
这样的module被加载时,udev daemon(在嵌入式linux中是mdev,自动创建设备节点实际上是应用层面进行的。 需要在busybox里面配置号相关选项才可可以)就会自动在/dev下创建my_device设备文件。# x  _) Y7 u, P9 {* k

/ S. Y% j* f+ z6 r3 o$ P/ |# a2 @4 ]8 e$ ~2 P% r: M! @8 |$ A* S
我们在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在 /dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev。
& y- d7 z2 W; r/ h+ [3 f& z+ M# ]3 ~1 C4 K; T; K4 a  N
7 l6 K- X! N& D1 ?6 w6 Q( c
内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。# L2 P; e% ~5 N7 f$ n; a

$ Y3 ~, U6 \" Z  X0 Z
4 ?/ _. f1 N6 D% p, b+ W! w/ \# j/ x- @注意,在2.6较早的内核版本中,device_create(…)函数名称不同,是class_device_create(…),所以在新的内核中编译以前的模块程序有时会报错,就是因为函数名称 不同,而且里面的参数设置也有一些变化。
( n0 @$ X% T9 R0 t2 t( T, g. W9 i% E' S
0 C5 v7 q1 M* J3 E
其中,读写等,有从用户空间向内核空间传递地址的,地址在使用前必须做有效性检测(因为应用空间使用的是虚拟地址,有可能地址已经被释放了)
- B2 S, O* ^7 P; ?- G+ H5 b
( [0 O7 |* a0 x) T  P7 G  R  y; f: s% k# I8 D
检测方法: _access_ok(void* start,)_access_ok(unsigned long addr, unsigned long size)  K) t- n) ~3 n5 |9 w6 X+ V, g
0 H, H& l" O* v* ?$ {# o5 ~
, P% Z- i- {9 x# ^( E
其中 copy_from_user(void*to ,void* from,size_t size),copy_to_user 都包含了参数检测功能,但__puts_user(),__get_user(),函数并未做参数有效性检测。4 T2 F* K9 t# o& X9 h" y9 D% H
5 P# {/ V7 I5 r# y0 F2 w
! \7 k: y- v9 A+ I! W* Q
file_operations 重要函数指针原型:/ i0 g) E2 F: H% B& \* r
; x7 J6 K) [6 I
" C- v# _) T4 n
loff_t (*llseek) (struct file *, loff_t, int);$ O3 _% ]* o+ ^; h" }$ n3 ~8 @
: i8 F7 Y- D; v6 L" ^
0 ?- M' Q& l3 G" u8 g6 l  e
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
- N4 G5 \  d# o  w9 F. Y3 i
: i7 Z5 f' a# g. A5 I, B9 V* s' ?4 [) Q1 _. p6 y+ A, ^/ z7 c
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);4 \) O2 y+ Z* D* _: ]+ ^, o

) f! I6 [4 w, c& w/ T8 E' l/ A; M: C
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
; S" c+ G4 X+ _6 h2 s
& m+ E$ F7 X' Y) W0 D
0 ?/ n6 G2 Y" r& n5 mssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);% j/ f  B: z1 Z
% ?0 b9 k- k. E/ L, `% S: Y

9 C) M8 T9 r5 s" \: A4 H2 \* oint (*readdir) (struct file *, void *, filldir_t);# S6 N2 O0 _8 h& z
8 J5 F- {" b) V+ r6 a- {, {
+ W0 x8 w3 n( R4 y
unsigned int (*poll) (struct file *, struct poll_table_struct *);0 Y0 w( \; p7 N3 K6 p

- B( F, g: E; ^* P
. t- t/ ^; K4 f/ h5 iint (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
9 i) }5 a5 l3 X: ?: s# D" i( f5 t
) {' b/ k& [# m+ b! z! N( i, [* s
7 c. j4 B! D& _; Y& \9 blong (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
9 \- Q' g4 T' u2 h8 M; X; g- {. M5 `; p2 E6 E2 w

* Z, K; r$ z5 I/ N9 Llong (*compat_ioctl) (struct file *, unsigned int, unsigned long);
+ E% e6 \5 V$ p5 D3 j3 X0 a! ~" P9 B
" t; S4 ^/ h- F: S* _( s0 X
5 x6 P! }, Y6 Zint (*mmap) (struct file *, struct vm_area_struct *);% V- e: l# }5 Z4 m8 q5 v2 y" i) S

( ?; w) W8 N" w
) d' H: g/ W% g3 L; @int (*open) (struct inode *, struct file *);; Z$ \. i$ i4 T$ u

( T) N' {7 i5 |6 k8 J4 _8 y% g0 X: n. p1 b! g
int (*flush) (struct file *, fl_owner_t id);
$ C+ {7 O; X6 b% X# x6 E  Q) k# n7 O  i4 f
+ e% N$ ]3 Q1 M  p# o+ Q2 k/ ~6 K; p
int (*release) (struct inode *, struct file *);/ C/ `1 t4 u% {6 s/ A
6 k5 y. w/ i, y3 b$ ]2 i" A

# a* s$ n3 t( E. iint (*fsync) (struct file *, struct dentry *, int datasync);
1 ~: [0 W: y! M+ n# R3 a
8 D* L9 l) k/ J0 F& {9 e7 T9 P7 v4 I# B+ t
int (*aio_fsync) (struct kiocb *, int datasync);
5 i4 G2 z6 B' @) j7 U3 _/ q# Z1 h
( T: h+ F! n3 U) z
- c. ^! {6 y% N( O  J, \9 Z: N, qint (*fasync) (int, struct file *, int);
; l+ c- }$ b/ j. V5 [- r
, ]9 n* B$ V- d1 Y1 z% S- e5 h) r8 A* j6 j# g) }9 J
int (*lock) (struct file *, int, struct file_lock *);
- X  g) x6 |# Y/ s/ }8 r+ n0 `1 L' Q6 V$ S
/ o/ y. V0 o5 T" L& r& t8 b- Q
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);6 {, y& K5 x' D% L

$ z5 s  i- U: u; ^* j
5 I* X* x' [6 Vunsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);, A: K% h" o  _5 K( V8 x9 @

) ~4 O3 k4 a1 [  E% i6 T' w8 K& u/ T. N! k
int (*check_flags)(int);
$ k+ N1 g$ A! M1 y0 O* x, e
* P' g: I  {( `7 y* Y0 Z& @3 X+ N& n* \  \8 x1 E, a
int (*flock) (struct file *, int, struct file_lock *);* G4 k5 _2 X/ {9 J( w+ c& r9 r

% w" F4 x2 {% \! U; A) j9 D2 }6 p! ~) k+ O/ o6 g0 c
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
* ^5 I" V. u" {$ O# B. _9 ^/ c" x5 u5 g$ W6 |
7 E5 ?+ R4 R) A) j0 B( \8 ~
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);9 H7 F$ e1 g1 H  x" O+ h

; |) ?9 U: D* t/ k" ]
9 K' A/ F+ \  B+ \int (*setlease)(struct file *, long, struct file_lock **);




欢迎光临 EDA365电子工程师网 (http://bbs.elecnest.cn/) Powered by Discuz! X3.2