找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

巢课
电巢直播8月计划
查看: 7|回复: 0
打印 上一主题 下一主题

[硬件] 驱动基础知识学习笔记

[复制链接]

551

主题

1470

帖子

3万

积分

EDA365管理团队

Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9

积分
39487
跳转到指定楼层
1#
发表于 2019-9-27 15:09 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您!

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

x
一,知识结构
/ O5 k3 V! S: K0 A/ a
  D3 d- |) y& O. F: a- v: [5 o
; v5 y% L) ^0 a8 q8 U8 Q" S二、驱动分类 可分为 :字符驱动、块设备驱动、网络设备驱动
2 X+ ^+ x8 _: u
1 P6 M  ]* |2 b  G9 f# N( D( Y( }
  P5 T) n4 q& F$ l字符设备驱动以字符为访问单位(一个字符可能对应多个字节)进行顺序访问,不能随机读取。. h9 g% r( F- E$ w- o* @
% ]. ]' m7 J2 |% ^; m; q' K' T2 |

1 n9 E; |; l4 h# p+ p7 k- I块设备驱动:以块为访问单位(块可以在内核中进行配置),通常512字节,或者更大的2的N次方。
: L5 A5 D1 A1 K3 A/ F+ ^3 ^* B+ B4 {, j
* D; U3 p! S* Q/ J6 g" ~: L" A0 u( _
块设备可以随机读取。在linux中块设备也可以以字节为单位进行访问,块设备跟字符设备; t( M# X  p7 y" {) M. g! y/ v% F
$ o/ T; [2 }2 W# ?7 j7 ~" _

) `1 {6 Z' ]$ B6 K( y( D主要区别是访问接口的不同,并且块设备可以随机访问。' l1 F: k0 V% D$ `/ d- ~

0 p" g% X7 K, K# h5 `
3 U! X4 G+ d& }/ }! z! q网络设备:网络设备是以网络接口为访问对象的。可以是一个物理实体,也可以是存软件,例如linux下的lo设备是个存软件的网络设备。' E3 K3 o" h* [7 d( t4 p

8 _2 _6 t! q+ c8 ^; y5 V
# O" o- }) {5 T  d' \三、驱动的安装方式1 p) N$ c6 F; v' k" @/ T. s

% p; k- z% I$ T, W
) }/ M6 B9 c" n  K: x1.驱动可以直接被编译内核,也可以以模块的方式安装,驱动设计的模型跟内核模块设计一样,入口都为module_init(),出口为module_exit();如果想要把驱动编译进内核,需要配置相应的Kconfig 跟config还有makefile文件。
# u! U( t3 i8 o3 I4 M7 B+ b2 x& J4 l( p  B8 ]

+ `* [+ S6 q9 o* @* P四、字符设备驱动程序' X# N, S4 H3 U" f# ^

% Y: a8 [* X- X8 t4 d, L( ]# ^* g5 E( v$ Q$ y$ `4 E4 R* V
字符设备驱动设计流程:! [+ S7 j* A! B7 [" L

. r  v/ q+ j  Z1 A9 R
8 D2 Z6 T9 O+ h7 E设备号:设备号是一个unsigned 型,高12位为住设备号,低20位为次设备号,linux系统提供了MAJOR(dev_t num) ,MINOR(),MKDEV(major,minor).来提取跟分离设备号。其中,主设备号是表明设备类型,是建立应用程序跟设备程序的纽带,往往我们有一个产品中有多个一样的设备,那么我们通常只有一套驱动程序,一个主设备号。通过不同的次设备号来区分访问不同的物理接口。一个主+次设备号对应一个设备文件。. D  J( `: @3 @' t" k
* z3 J4 f; j- n2 I8 p+ m0 V
( m5 |. }. R: ^5 ^: ~, d1 \6 T4 S
首先申请设备号,可以通过 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);
6 Z& l- x% `8 J' J0 U
1 B1 {2 O" z8 Z4 g7 }7 m4 N! M3 E, w8 Q  g
2.设备初始化申明结构体 struct cdev cdev(如果申明为指针在使用前要注意分配内存),定义结构体 struct file_operations file_ops={9 U0 Y6 M6 @, t& d+ r( o

$ C7 |$ k8 @/ K% x7 H
1 L" F* ~# V, c6 S- p.open = mem_open,: ^: D* M# g% ]
* h/ ~% L, }9 m/ U% {& j

# P/ L% `$ b' N3 }: Z7 v.read = mem_read,1 q+ a! f2 U0 @4 C
  w' W% M& I4 }+ j% D* a+ K3 J8 W0 j
/ s, w- H+ G8 ]. ?" n) F1 f
.write = mem_write,5 N& {2 `, ?+ ]( E3 N6 ?8 T, e
  A2 H6 _, W! H) s- p7 a

* w+ \5 K  }: B- ~7 B.ioctl = mem_ioctl,. |. v+ w4 i! H$ P8 Y9 a, w
* i$ v" W; a* O4 [3 j# W/ u

" L1 [8 A% ]) I& B8 R! l" ].release = mem_release,6 s; i# q' [: x) V" X5 `8 S
. {! p/ j( J; ]" n# d9 ]4 C5 Z
! p; [5 v1 T7 _6 k
.llseek = mem_lseek," x; ^2 k& P1 \$ C; c# _; i

: K. r' N! Z  d. Y5 ?6 m% o8 {- h: Q0 q; S* H- k
};并初始化功能函数,为对功能函数初始化的,默认为NULL,
5 Q3 [# N$ O  V8 q/ O9 {1 y, r, j# u" r: n$ M" Q5 \
3 c. C# n4 j1 [+ p5 R/ ^* ?
然后初始化设备 init_cdev(&cdev,&file_ops),指定模块所有者为模块本身 cdev.owner = THIS_MODULE;1 N0 b! K9 `4 D- s

" q. h& }: r, R% |
. y5 N0 `1 N3 M: u初始化完成后,添加模块:cdev_add(struct cdev* cdev,dev_t dev_num,unsigned count);此时,模块注册已经完成。
* P* }' ]. |3 Q# N& h! h: q3 M5 |5 d4 e: N+ r
( c0 b1 U# O. E  n' j
在设备卸载时需要释放模块cdev_del(struct cdev* cdev);
3 w5 Q# m0 A& u9 W& n" V2 k( K) z9 C. i. _* A* R7 u

0 q7 M4 S: F2 Y3 d3 a) m" f( ?7 ~3\需要注意的三个重要结构  K  X/ m( v7 P% Z4 @
6 z( s0 w- ^, [& {/ Y4 |$ t
( ]  V% H1 p) ^$ C
1.struct file 在打开文件时由系统创建,代表当前打开文件的相关读写信息。在文件关闭后释放。重要成员 loff_t f_pos;当前读写位置,struct file_operations* f_op;2 M/ H4 Z- y" Z& c7 P8 H1 ^4 o. c

' x  L  ~( Y/ W2 k; U
( A4 a$ y- Q) F. G% p2.struct innode 结构,表示文件的物理信息,一个文件可以对应多个struct file 但只能对应一个 struct innode。重要成员 dev_t ir_dev;" U) x# {) F" h) A* V

# `& h0 n& R. R4 Y# m! ]& i4 z: V* Z2 T5 k1 z
3.struct file_operations 是一个函数指针集合,实现驱动相关函数。在设备添加中,我们讲 file_ops结构传给了cdev结构,但如何再传给struct file结构体的,还不明确,需要再深入研究。
+ O" M$ W, A) z  a' R9 V  Z, t2 ?) l+ V( }; A7 _

0 W4 E4 G' F9 \应用程序在调用库函数fread时最后都会使用到系统调用read然后关联vfs_read,最后将file_operation结构里面的函数关联起来。& U% n& P6 p! t$ h' N

9 \. q6 I2 F  @+ N- y$ L- s/ G* {4 A  O' I5 P4 r: l
设备文件的创建:设备文件可以通过两种方式创建,一种是手工创建设备文件 mknod name c major minor
2 C( G+ w. ?& I, h) Y! b" C1 B
# j4 ^" g) f4 E# k' }
. g+ S) |! W- O$ F5 K. R另一种是自动创建设备文件,分三部完成:7 q! W8 l+ A: A; _- k

) {. t, u: o2 r, j, Z- ^6 a3 H7 L8 G) v. A1 r* O2 ~" q
struct class *myclass ;" @: k# P& ?; I9 J4 T. B5 I

! t. G+ V9 O1 Y2 C% e
& I( W2 W8 e- Fclass_create(THIS_MODULE, “my_device_driver”);2 A8 W9 Q) H% h6 K

3 }9 w( _: J/ F# P9 y, x) j, H- R0 N7 Z
device_create(myclass, NULL, MKDEV(major_num, minor_num), NULL, “my_device”);
" J7 B3 `, d* r' c+ i2 c1 j
6 M+ W; h0 m; R
2 @/ f  @$ `7 L' P' m这样的module被加载时,udev daemon(在嵌入式linux中是mdev,自动创建设备节点实际上是应用层面进行的。 需要在busybox里面配置号相关选项才可可以)就会自动在/dev下创建my_device设备文件。( F) D& J5 }$ d5 j2 R6 @+ f; J
8 j1 r* S5 A) d8 [- R' q

" ?4 W) P; o- ^( @我们在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在 /dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev。# _. J1 M* O( }2 n

3 G0 {% m9 F% ^# o
* S; ^5 J! U4 D内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。. O2 O# v# J6 u' @. ~3 S

; H" S! I7 O/ V* t0 e; D
, m: j1 S4 m! p* f* {8 P注意,在2.6较早的内核版本中,device_create(…)函数名称不同,是class_device_create(…),所以在新的内核中编译以前的模块程序有时会报错,就是因为函数名称 不同,而且里面的参数设置也有一些变化。8 Q6 I9 @9 P$ u# F3 L* U/ ]

; k  d# U3 g* t/ x
3 r+ n/ W" \) \. u5 x' H% C! M其中,读写等,有从用户空间向内核空间传递地址的,地址在使用前必须做有效性检测(因为应用空间使用的是虚拟地址,有可能地址已经被释放了)3 k% _( W) l. E6 O5 O8 Y7 X

2 ^  r  `/ p5 r* W: F
: r2 Y' A+ X1 G( c6 w7 S7 a检测方法: _access_ok(void* start,)_access_ok(unsigned long addr, unsigned long size)/ A6 S0 V& f; R2 ^2 y" @: ?* W- D

5 O; Z% \; t' D6 n3 p2 B4 A( R# c6 b; b7 }0 i
其中 copy_from_user(void*to ,void* from,size_t size),copy_to_user 都包含了参数检测功能,但__puts_user(),__get_user(),函数并未做参数有效性检测。
. J4 L" w: w( Z' t4 P" W% Y. g. U
- n9 \) _: g  t  f/ X( @0 W9 h0 i0 ]: {& M  J; z! a  q
file_operations 重要函数指针原型:% H: `/ e- c# v$ |6 g* V3 T" Z
2 K0 Y, m" ^' I! ]1 ?0 k- \

' f0 W0 i) l+ P+ A2 |" _" xloff_t (*llseek) (struct file *, loff_t, int);+ M3 E4 P4 \6 C2 a

% t" p+ X! c" A( d! i
7 {+ @8 |6 @+ |3 M$ l$ yssize_t (*read) (struct file *, char __user *, size_t, loff_t *);9 V  i. ?9 a: r4 [% f  p' e% w1 K

8 {8 k  @2 o1 z6 f/ A( B1 w/ @' u. e
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);3 G/ r; p. G* b% o( h
5 W& c* H" Z2 Y( g
2 D, H% l* F1 o! R
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);+ h8 P3 b9 x  ?" ^$ T. P0 s
3 J: Q$ i2 i/ D' x3 G- w8 G4 ]
, t) Q; q( q" n/ w8 I" V
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);/ r; ]! L1 o8 K4 K0 O4 W
& j, A6 M! r; R5 A+ `, H7 q  Y

8 \3 k# l8 K2 U: f1 k- Zint (*readdir) (struct file *, void *, filldir_t);. y9 K0 l" H7 B$ z7 a
5 I* G4 t) I& ?& Z4 {2 P  c! H

* \* k+ A7 ^3 t& O4 k. F" @' }unsigned int (*poll) (struct file *, struct poll_table_struct *);7 y% q# {* y9 l5 ^( C- n. g
8 Z, \+ p& h/ g- j0 n9 H5 U

! l3 a7 B" l1 J+ h8 A) m# ~( aint (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);, K# f  G' A1 y$ P0 U
! u( i0 [/ h7 c5 M4 _3 I
  u+ l, D* @& F5 R; Z$ m+ @
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);0 t! k* ^: r% A4 z

1 ~% ^) X- [0 c: U3 _% u, u
) F/ b) G1 w& ]% Y2 H5 Clong (*compat_ioctl) (struct file *, unsigned int, unsigned long);* W, b% v9 W9 ~
) i" U8 t' `2 ~8 v( R1 [$ A

) S+ j% B/ I" m9 [# rint (*mmap) (struct file *, struct vm_area_struct *);
! I& D4 @/ u& M% M1 j" ~3 P
) y; a! G% T6 q
* d6 f( _, `6 E1 [! Jint (*open) (struct inode *, struct file *);% t# I! M, ~! Y- w3 ?; K
, q. q, q# h4 ?( j. A4 A

" _  s: l+ ]4 a0 {6 Zint (*flush) (struct file *, fl_owner_t id);
  J# I3 W: Y2 e% I' n1 B* J5 k" r8 ^0 _) Q( [' C4 \
2 ]- m0 a( T& l1 B8 J( f) h
int (*release) (struct inode *, struct file *);* J0 Z* M0 [- ~+ @' n* m

) Y& f2 V( t3 N* i
: o  n! z6 Y, L3 \: x! uint (*fsync) (struct file *, struct dentry *, int datasync);6 A& X3 `  D% j/ O

- p; R2 w4 r) `, @5 h. X
% z. c4 v! a6 n/ C% G% Tint (*aio_fsync) (struct kiocb *, int datasync);! L# a2 u3 `, m& ^

* i3 [+ k/ p/ b. p5 V0 E2 S
7 L. j7 Y; w& ?# `. Mint (*fasync) (int, struct file *, int);5 c  m5 r  B" B  W/ u
7 T9 {0 Q5 H/ J7 z
# T( K% H$ T; P2 ^% }" }4 j# d
int (*lock) (struct file *, int, struct file_lock *);
0 V& ?4 }8 ]  N0 x+ L5 c* R+ V+ @1 w: J$ i6 o0 s
$ @5 R  h* \5 X5 @9 v( S4 }
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
6 y# N. Y9 I* R$ h
9 O' C' }+ a' M* u0 X
6 m* d% B( i- p. \; S1 @8 qunsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);8 T1 {$ ~, [2 z" ]7 g' Y4 a

1 _; |4 I1 S" l0 V. G" R5 u. B; R- ]6 J3 A
int (*check_flags)(int);
/ H  u# N, q. f. b& F6 [( G& [
$ `0 g3 Y# P' C: w) U- r! x2 {6 K7 A/ d0 |$ `5 p; D% g$ `" n. l: G& |! n
int (*flock) (struct file *, int, struct file_lock *);7 w- s& `, P" C, x/ ~

- y1 w. \1 u3 O6 [1 Q  y1 j( N" k+ L
4 f$ E" w) l) O' Z* ^1 q. wssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);2 r4 w+ l: Z3 U9 s5 o
0 e* V  Q! b$ g& S5 u3 u% Z
9 v' ]5 _7 @$ {' g8 p
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1 k) N" V! r$ x5 S: t  [( P. K/ m* c$ r' R
* _$ K6 x8 w4 K" ]8 R9 v
int (*setlease)(struct file *, long, struct file_lock **);
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 支持!支持! 反对!反对!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

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

巢课

技术风云榜

关于我们|手机版|EDA365 ( 粤ICP备18020198号 )

GMT+8, 2025-4-7 07:30 , Processed in 0.056848 second(s), 33 queries , Gzip On.

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

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

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