8 W2 |/ ], z/ L* @/ h1 c8 q" J8 k) x, B5 Q, c
第6章 简单的字符设备驱动程序
$ y* Q: A4 z" \7 m6 u2 T- [
- s6 d$ e) C N) T2 I C0 r/ i h 在Linux设备驱动程序的家族中,字符设备驱动程序是较为简单的驱动程序,同时也是应用非常广泛的驱动程序。所以学习字符设备驱动程序,对构建Linux设备驱动程序的知识结构非常重要。本章将带领读者编写一个完整的字符设备驱动程序。+ M4 E; `% @# t+ Z) M
; t: A" w% s. n 61 字符设备驱动程序框架' k$ B2 O6 v+ g8 r& K3 T, S! k
& t9 R! A0 J' L! T6 c
本节对字符设备驱动程序框架进行了简要的分析。字符设备驱动程序中有许多非常重要的概念,下面将从最简单的概念讲起:字符设备和块设备。) d5 h- N$ y4 v, d0 }
- b) A: D. [& ~. D+ l; [& C& s
611 字符设备和块设备
! q, \: ?# c9 S0 i6 T7 C: e8 D+ j- i: R5 K
Linux系统将设备分为3种类型:字符设备、块设备和网络接口设备。
$ M# u* X, T7 U" @* P# `* [" Q3 ^
其中字符设备和块设备难以区分,下面将对其进行重要讲解。
7 g3 q& e( q) S3 [# V d5 ?; b- u' }" E% E1 K& b
1.字符设备
8 L* W6 Y: q x( X. l$ r4 l, J
* q/ @+ r" w% ~ 字符设备是指那些只能一个字节一个字节读写数据的设备,不能随机读取设备内存中的某一数据。其读取数据需要按照先后顺序,从这点来看,字符设备是面向数据流的设备。. b6 @0 r# U# j/ s
" ]7 M# c0 z/ Y5 ]
常见的字符有鼠标、键盘、串口、控制台和LED等设备。
1 e; h1 F1 l, V1 D9 y6 m |6 s! i4 N" q6 E3 t4 T
2.块设备+ A4 p0 v7 G, B" v) n3 @
7 @8 F, u% _$ X C" d/ D# I' m 块设备是指那些可以从设备的任意位置读取一定长度数据的设备。其读取数据不必按照先后顺序,可以定位到设备的某一具体位置,读取数据。9 t7 x1 G6 n2 \7 I; b
9 W3 I" R- ? R 常见的块设备有硬盘、磁盘、U盘、SD卡等。* p' {: h: m% t/ ?0 c$ y' b
_! g& o1 _ v; r 3.字符设备和块设备的区分
- L% t* `' ?. [3 i& x P0 x* w& W( O: \7 a$ [* k6 b6 q
每一个字符设备或者块设备都在/dev目录下对应一个设备文件。读者可以通过查看/dev目录下的文件的属性,来区分设备是字符设备还是块设备。
/ Q: |* h) S: W& {" j9 P; M4 `. n; S% E! V( Y$ S8 k
使用cd命令进入/dev目录,并执行ls -l命令就可以看到设备的属性。$ e3 N( c: f& V
: O1 [( @9 s7 r" `' _* \/ A( S/ l [root@tom /]# cd /dev /*进入/dev目录*/
4 \+ e4 F5 w% Z( m! f" V, \. C( m6 [, R7 {# k
[root@tom dev]# ls -l /*列出/dev中文件的信息*/、
" M, q v% r( ]6 R9 Q# k
) o- l1 K1 ]% J! [- b+ u8 p9 ` /*第1字段 2 3 4 5 6 7 8 */
2 i9 P! G) D' v5 r) T! O$ q- G( X T# \% Q% a1 L
crw-rw----+ 1 root root 14, 12 12-21 22:56 adsp
2 @( h1 Y# J- G# `: M: o( }; L% |4 Y2 f' o- X% w3 q/ H3 I
crw------- 1 root root 10, 175 12-21 22:56 agpgart
: L2 K* T4 s9 S
0 O. ?4 D# a2 t3 n! s7 X crw-rw----+ 1 root root 14, 4 12-21 22:56 audio( {' u5 [% ?5 |' |6 n
, }' Z7 f9 F8 w
brw-r----- 1 root disk 253, 0 12-21 22:56 dm-0' k8 _1 e) i9 s& ]- m1 V0 _/ n
; O ]" T5 I% m9 {/ R1 q6 @ brw-r----- 1 root disk 253, 1 12-21 22:56 dm-1
1 I3 n" s: S$ Q1 b/ W
- [! f8 b1 P3 J7 o crw-rw---- 1 root root 14, 9 12-21 22:56 dmmidi. ?1 J$ d5 z- `+ A8 {
$ s, h! _: B! u: @
ls -l命令的第一字段中的第一个字符c表示设备是字符设备,b表示设备是块设备。第234字段对驱动程序开发来说没有关系。
' l- t" f8 R2 U! B5 X3 S8 F9 w7 }
9 f2 W( t' q1 u; `& S; e9 Q 第5,6字段分别表示设备的主设备号和次设备号,将在612节讲解。第7字段表示文件的最后修改时间。第8字段表示设备的名字。% c( H; `8 N" L; ]. v
2 x, w9 W0 d) w
由第1和8字段可知,adsp是字符设备,dm-0是块设备。其中adsp设备的主设备号是14,次设备号是12。" W4 C4 N8 _) T- ~4 G
& }$ L& R% O. J7 l. q6 l 612 主设备号和次设备号* S- K. ~+ M9 x0 b5 L6 o5 o, @
: A1 m) r3 D7 Y. A/ L. z6 i
一个字符设备或者块设备都有一个主设备号和次设备号。+ X* P$ R0 m3 a
# q9 _% B) Y' J, C. x% d
主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。; ?3 I: q# X8 R8 A3 l
7 j2 P8 j4 {: ?$ Y3 P' O
例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。 u! T$ y+ Y3 h# S
^( }% r; d. T& n2 u8 r0 B2 G! E 1.主设备号和次设备号的表示% x3 G% `# ^) V! n% |/ K
3 i+ ?2 z8 s. Q3 C
在Linux内核中,dev_t类型用来表示设备号。在Linux 26294中,dev_t定义为一个无符号长整型变量,如下:- o5 Q0 @0 U9 ?) {# K6 N5 u
! O# _ O( [2 x) @ q typedef u_long dev_t;
# d9 r$ _- @" l. c
7 z. s* |: l3 M- ` u_long在32位机中是4个字节,在64位机中是8字节。以32位机为例,其中高12表示主设备号,低20为表示次设备号,如图61所示。
( j) H! j. f5 g- }; P) n3 {
' I9 S R8 M. o! o, M0 k$ i 图61 dev_t结构# p* I6 w2 ?' @4 I) e" W* N
2 C0 `9 c4 o J5 p0 w
2.主设备号和次设备号的获取) `! W5 N1 R5 y) L7 E6 |+ a
6 o0 R: k; E% t: Z 为了写出可移植的驱动程序,不能假定主设备号和次设备号的位数。不同的机型中,主设备号和次设备号的位数可能是不同的。应该使用MAJOR宏得到主设备号,使用MINOR宏来得到次设备号。 v) M) K+ @+ p. _
1 \: {$ z7 P6 X" y5 i& L
大发888下面是两个宏的定义:2 J8 J, p& O' g/ q
/ j. \3 ^! P8 i/ W$ F. _* f, S
#define MINORBITS 20 /*次设备号位数*/4 B4 u. O) h _8 B$ o5 X
) j, Q! g" z! ~3 J, g* j+ J) [
#define MINORMASK ((1U > MINORBITS))5 g$ k0 t" C" E6 n
9 F2 o) }/ t1 Y
/*dev右移20位得到主设备号*/
! R8 u( Q1 q4 [! z: r. @" D
4 ^& F {+ ]' h #define MINOR(dev) ((unsigned int) ((dev) MINORMASK))
1 r2 Y- ?( r# l# R5 G1 h: D+ i- h4 j/ A' N6 l* \
/*与次设备掩码与,得到次设备号*/1 I' e. x8 `5 x- K- o: q
- l' `' A" n+ c4 w- ^1 M) m" P( H; H& C
MAJOR宏将dev_t向右移动20位,得到主设备号;MINOR宏将dev_t的高12位清零,得到次设备号。相反,可以将主设备号和次设备号转换为设备号类型(dev_t),使用宏MKDEV可以完成这个功能。9 Q+ ?) [5 u; p; z X* d
7 w9 w" P& J/ g6 l9 j8 v #define MKDEV(ma,mi) (((ma) : W9 ?* F$ n i- u7 y+ @4 c L+ b
8 R f1 F9 V$ V1 k! _! ?' U MKDEV宏将主设备号(ma)左移20位,然后与次设备号(mi)相与,得到设备号。& i2 @, u4 o7 C3 g
# \! L& P" [4 u& T# E& b7 L 3.静态分配设备号, v5 T% y4 z; y* r5 i
3 O- x- D( T+ @3 \( e4 d7 h6 [& u
静态分配设备号,就是驱动程序开发者,静态地指定一个设备号。对于一部分常用的设备,内核开发者已经为其分配了设备号。这些设备号可以在内核源码documentation/ devicestxt文件中找到。
8 V* d" L4 y2 q4 ^6 Y1 l
" R$ ~% ^$ T1 G; [ 如果只有开发者自己使用这些设备驱动程序,那么其可以选择一个尚未使用的设备号。在不添加新硬件的时候,这种方式不会产生设备号冲突。但是当添加新硬件时,则很可能造成设备号冲突,影响设备的使用。( e4 @& O: E1 r
0 d6 t0 D: f- z- e# M" D
4.动态分配设备号" b& v E6 Y! @3 _& L
& K8 k( X& `0 I 由于静态分配设备号存在冲突的问题
' d$ h8 p) j8 L r2 L3 I }9 D$ |" f' `- `. y' m
|