|
EDA365欢迎您!
您需要 登录 才可以下载或查看,没有帐号?注册
x
一,知识结构1 M; c2 K u/ ]8 }6 T6 S9 J2 x! A
1 q6 t9 d! q, _ B, Z9 ^
0 z# j& D8 W3 G: A二、驱动分类 可分为 :字符驱动、块设备驱动、网络设备驱动8 p) H' h. C+ Z+ D) Q
' R7 s; O0 Z( f# T5 i: A% a) Q. V& P s
字符设备驱动以字符为访问单位(一个字符可能对应多个字节)进行顺序访问,不能随机读取。
8 B- n; G+ b# R( a
) ?6 n1 @' j/ p- ]# n1 a3 V2 V4 K& `" ~$ P& r1 n
块设备驱动:以块为访问单位(块可以在内核中进行配置),通常512字节,或者更大的2的N次方。) ^" g8 |; \; A$ i: C, T) j
5 G1 O* |# N+ W2 m/ q( q1 x) Z. u/ ?, O, W- [
块设备可以随机读取。在linux中块设备也可以以字节为单位进行访问,块设备跟字符设备+ `1 H s# h, Q5 X2 t; r {
6 |, P7 P' O6 |. D+ H3 x: R! B9 ]/ A1 s j/ V+ s* k
主要区别是访问接口的不同,并且块设备可以随机访问。1 P( M$ V- S7 w
0 \3 P: i8 w% W- t: I" `* H. M* H
1 K- I! q: Y4 t: u5 Y! h
网络设备:网络设备是以网络接口为访问对象的。可以是一个物理实体,也可以是存软件,例如linux下的lo设备是个存软件的网络设备。' J L! o& B" }* }: x9 c6 i1 t
) z6 o$ b% g* G7 d: i4 `
. L8 S& [7 ]0 P
三、驱动的安装方式; e% z& I% v, G
( |- `' n& o' b5 u2 Q/ ~. [ L7 O5 R: i, C+ ]3 A+ w" M2 S
1.驱动可以直接被编译内核,也可以以模块的方式安装,驱动设计的模型跟内核模块设计一样,入口都为module_init(),出口为module_exit();如果想要把驱动编译进内核,需要配置相应的Kconfig 跟config还有makefile文件。
# q2 p" F# j3 g
$ Q( r* e4 D( ~" C% X! @7 ]+ z: i8 |( V T- ~! `4 c4 Q
四、字符设备驱动程序
( Y% d% T& @/ c7 u# p& y+ z
, ^2 I2 G3 l/ s9 X+ ]
) i+ V! L4 m# N0 e字符设备驱动设计流程:
K6 M, t6 o8 Q0 g# P
! G1 r* g. {. F/ o! ~: ]* A( w
8 I9 w/ G: T+ e" E设备号:设备号是一个unsigned 型,高12位为住设备号,低20位为次设备号,linux系统提供了MAJOR(dev_t num) ,MINOR(),MKDEV(major,minor).来提取跟分离设备号。其中,主设备号是表明设备类型,是建立应用程序跟设备程序的纽带,往往我们有一个产品中有多个一样的设备,那么我们通常只有一套驱动程序,一个主设备号。通过不同的次设备号来区分访问不同的物理接口。一个主+次设备号对应一个设备文件。1 K5 Q) `3 q! |: M' l, W( ~! G& e, D
8 A+ \0 W {0 \" N2 ?, ]' ?+ d% W2 f
{0 X3 ]$ o0 {* l: @# [. H
首先申请设备号,可以通过 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);
5 j# x4 k: V( F3 |6 K. f8 S# }( l; \1 c( r
1 v- q4 b) I5 h. H2.设备初始化申明结构体 struct cdev cdev(如果申明为指针在使用前要注意分配内存),定义结构体 struct file_operations file_ops={
k1 R, f8 U: u. T; m8 j" v; _; P
7 Z: @8 u2 W4 \
3 n8 }5 p2 p3 e* c- g$ w5 {6 B7 G.open = mem_open,; z d& V+ v/ ~
# I. ?. e1 w$ J$ C2 C
; _+ P! A3 l7 J: }+ Z# r$ J+ \! Y.read = mem_read,
0 I- k1 D: m' I C: j
! O- m0 h6 D% T3 { R( g. H3 C' ?& g# C9 P8 M1 q' ]! L
.write = mem_write,
5 e6 T+ ]' c* r8 `( E+ O
6 a% Y2 d& g4 r6 H! i/ d1 Z( W2 I1 X% l2 W/ `
.ioctl = mem_ioctl,
. [5 c+ r, G, _9 H, A( N
" A0 l* M' q- C' R6 C) Y% P( e% L+ j% _ D0 ^
.release = mem_release,
9 l, e2 a' n' h- X& t# O0 e6 k; s- w) ]; J0 U
# Y$ s( ^ X3 ?* I9 Y. K# U( J- ~.llseek = mem_lseek,
9 M4 P9 N8 J: e$ b
* r7 ~7 y; R' X" w H- a
# _: d S- B# i0 c% {};并初始化功能函数,为对功能函数初始化的,默认为NULL,) t. J0 s9 L, W/ K
6 [' N9 R) m# m
$ \. T/ \) G' l$ I5 c) |0 R然后初始化设备 init_cdev(&cdev,&file_ops),指定模块所有者为模块本身 cdev.owner = THIS_MODULE;" J$ W9 A7 J; \
/ b4 H* g8 H$ b6 Y6 y9 P* Q1 T
# H+ A9 {8 ?7 Z初始化完成后,添加模块:cdev_add(struct cdev* cdev,dev_t dev_num,unsigned count);此时,模块注册已经完成。; y2 O, A: ]- w Y
% m/ c& l& r: F1 m i
6 T! Y. `# k& ~0 `0 z在设备卸载时需要释放模块cdev_del(struct cdev* cdev);2 t4 Q' g* G4 {+ o
! d% z5 ^! n: o! x6 c+ K% M& K- ]
G! n$ P" B+ o! I3\需要注意的三个重要结构
9 N, ?/ C1 f+ ]6 |: m
$ A% k/ i* S4 Y+ V
, w0 S3 u& C% c& E7 Z1.struct file 在打开文件时由系统创建,代表当前打开文件的相关读写信息。在文件关闭后释放。重要成员 loff_t f_pos;当前读写位置,struct file_operations* f_op;
/ ?- Q3 D+ Z, Z& n/ h
0 x5 ]3 d: z5 M5 l! v. D- G
2 y4 b6 l0 }5 i7 k2 ~# F2.struct innode 结构,表示文件的物理信息,一个文件可以对应多个struct file 但只能对应一个 struct innode。重要成员 dev_t ir_dev;
; ~4 q8 d) j) x: x; s' v' \) k5 {0 @& \7 @8 {
4 r4 s2 c% b4 p* U( l! p6 E3.struct file_operations 是一个函数指针集合,实现驱动相关函数。在设备添加中,我们讲 file_ops结构传给了cdev结构,但如何再传给struct file结构体的,还不明确,需要再深入研究。
# O- V; A+ F D5 j# @* I$ r' T$ j! }
* e6 v: Q7 G* P* t- @应用程序在调用库函数fread时最后都会使用到系统调用read然后关联vfs_read,最后将file_operation结构里面的函数关联起来。
7 z" W, {0 r W
+ Z! x& J# s3 c5 r6 Y3 a9 R% S `5 m) Q0 ^3 c8 o7 s
设备文件的创建:设备文件可以通过两种方式创建,一种是手工创建设备文件 mknod name c major minor
( x/ `( u7 E3 ?% F- T1 q+ n! [( D) t' X( K1 o0 _6 e
+ `2 k: D6 D. w A( z; k
另一种是自动创建设备文件,分三部完成:+ j) k* ]) o; E4 M8 t0 l
( X, u; V+ j# W% b& ~, K/ V
2 K' m- y# F0 ?9 T: Z7 Z
struct class *myclass ;; z6 }" m4 x; B- V- {
, t( {. N) j# \+ |: k m" {8 z) W% t$ ?1 E, \4 n( L' V$ T
class_create(THIS_MODULE, “my_device_driver”);
8 |* o4 p: X8 Z& i! |. s6 `
3 ~* V$ a& a. X: s4 T3 u; b5 S7 T/ o
device_create(myclass, NULL, MKDEV(major_num, minor_num), NULL, “my_device”);
* k, d! A: Z; L& k% e
h/ t# i$ E$ k" J9 }$ `9 L# I% i' l
这样的module被加载时,udev daemon(在嵌入式linux中是mdev,自动创建设备节点实际上是应用层面进行的。 需要在busybox里面配置号相关选项才可可以)就会自动在/dev下创建my_device设备文件。
d* ^! L8 C1 ?9 n8 h( h6 n
& m) j7 [* n0 w
2 c5 E" M1 x$ D! G" ?! r我们在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在 /dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev。# J; G4 V ?) O! h
5 G9 b0 p: }; ^! o" z( Z
" [: S- K' W; h' u v* d内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
' n1 U1 y/ X7 n) j, s0 ~3 N# U2 v3 l
$ c6 F8 |# T3 A: a6 [ i0 X注意,在2.6较早的内核版本中,device_create(…)函数名称不同,是class_device_create(…),所以在新的内核中编译以前的模块程序有时会报错,就是因为函数名称 不同,而且里面的参数设置也有一些变化。$ b6 ]% } H8 e& C& R
' e: A3 L# ~: l
! [# k @' p1 i7 U, T# q
其中,读写等,有从用户空间向内核空间传递地址的,地址在使用前必须做有效性检测(因为应用空间使用的是虚拟地址,有可能地址已经被释放了)
: m$ b7 U' U) O) X% x2 W: g- g) U1 f1 N. M$ o& t
: X9 k5 m4 S, y6 e5 L检测方法: _access_ok(void* start,)_access_ok(unsigned long addr, unsigned long size)
! H! x" s% B i6 s4 M3 |
7 C% |- @7 n9 x0 O4 Y0 p& T, x
" ~4 u% r$ a0 r9 L* N# q& ^其中 copy_from_user(void*to ,void* from,size_t size),copy_to_user 都包含了参数检测功能,但__puts_user(),__get_user(),函数并未做参数有效性检测。
: P1 l% E/ A" u; {! y7 C
. V+ n: f* Z# b8 u5 J' Q; ~ U8 w2 `# o* Q. M
file_operations 重要函数指针原型:
- V- l$ X8 m" N1 X5 E7 _5 x: b! K, ~: g
# t- G l6 e* x+ X M0 x8 x
loff_t (*llseek) (struct file *, loff_t, int);
8 ^& O! E& Y$ N& W3 j! w' c3 j5 N" U8 Q1 `. G: `( j0 F
K* d) C/ n4 \1 N) \5 T3 Pssize_t (*read) (struct file *, char __user *, size_t, loff_t *);* S3 O) ?/ o# E' p1 D
' s0 Z7 _4 p- \" L, X
' U U/ E- V$ m+ m* r8 l# ^ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
) W0 O* v& H" _& G6 ^/ L* x* w
. f; P4 Z3 D4 U! y2 \9 D/ m
3 R2 \2 m# B2 a! v$ i. ~$ {" Kssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);/ `1 |' `. h: t! h9 b) H
; C9 }- H. |$ p' a7 O* z# S0 u( u* J
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
+ p% @5 O4 v7 A) R" Y0 Q3 ?$ I P$ _* H7 b; l) ~ m9 B
- ?) k M L z0 p7 ^! n
int (*readdir) (struct file *, void *, filldir_t);6 l( r4 U9 \6 \0 B# o! P
1 B+ ]0 v1 V& i6 D
5 g U. d, D# m0 ]2 B! h7 K, U
unsigned int (*poll) (struct file *, struct poll_table_struct *);
. p; f8 C3 T) ^+ f2 D! D( x' n# t! _8 i1 |& G) X
- k$ J: r. e' }int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);5 S3 t) ~% J* B- j- f, `
L" {- Z5 ]# h& L% l
+ v7 R0 J r8 g- U6 I6 Xlong (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);% F0 s4 s6 p" p* v3 O8 J& _& t
4 q% b) n1 ]6 V9 p) _5 w; W: {8 I4 u4 P1 z# J
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);* _! W/ G! b# F- M$ Z& A: p# g* z
6 V- W" @5 Y" T- Q6 e
& F, e1 Z6 o" oint (*mmap) (struct file *, struct vm_area_struct *);
! N* H1 x4 d$ v+ l3 H! S9 `+ p- d* G2 H. A8 F4 D, j
2 l0 q8 q ?" M; @int (*open) (struct inode *, struct file *);
9 M( I2 O4 J2 @5 Q+ _6 r
7 {; [! {( |, t; y) r$ I/ {7 w# y
3 r Z* g+ Q. c( ]8 M7 R1 w7 @int (*flush) (struct file *, fl_owner_t id);
8 K9 `) d6 C5 H! d3 x% a9 Y% ^, l. Q% @; E- N2 Y
+ G2 c) J# A/ w% n' A/ y$ r8 }, ^
int (*release) (struct inode *, struct file *);
1 J- o% \' C, e4 O* b% `8 y* M4 `2 A( h, V- g" ~
1 G# Q' _2 q; l+ D" i/ hint (*fsync) (struct file *, struct dentry *, int datasync);
* w2 d( ?, M; R9 @7 y8 x
1 Z1 y) }3 c. U. U
, _2 E u3 p$ K( z3 ^int (*aio_fsync) (struct kiocb *, int datasync);
) x7 f" F4 y5 p5 I& q% M7 c6 _ s# X$ V& d7 ^9 B! ~
& R! q; F" d8 N( W* ^int (*fasync) (int, struct file *, int); k- X" c8 X( T4 i
0 b5 c5 I$ m9 [+ U* S, X! N4 I B9 n! o8 C( Q$ E ~5 N
int (*lock) (struct file *, int, struct file_lock *);+ ]( B/ F8 X7 L6 A2 U
2 {. D7 P: O* Z8 o
7 | I8 G. j0 q5 pssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
0 N. J) Y7 a' F& x1 P. h( D! J; ^: ~. Y8 ?- o; E2 R
; O( ]6 R+ H- ^8 |4 j' f
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
7 }# F- I$ D- n" Q s/ @: n" C( H/ l
& f1 v% b/ \7 X: K4 |% A- u5 T% n' Gint (*check_flags)(int);
, z' R U6 K4 ], U4 W- s
" F V- P8 O2 k2 p ^, \2 Y+ \! q* O
int (*flock) (struct file *, int, struct file_lock *);8 S# n2 I5 D! Y3 ]; @ `2 ~9 f
: v/ W" w1 }/ _4 Q8 l4 x# e U. m
7 C, X& Y: z6 J6 l, _3 s' Assize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);: Z. [/ c5 B5 r* L4 W% Z1 ^
) b( H- g$ d+ D% o8 `6 j+ c) g/ d- S6 x1 `1 m5 c
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
, `& h8 b3 q2 b
6 I9 S) ~$ y, |( f0 n0 |" w& w/ B5 X) S/ ~
int (*setlease)(struct file *, long, struct file_lock **); |
|