r. K. M7 i& ` F" `9 u2 E) I2 [ G& y J) w! z' W
第6章 简单的字符设备驱动程序9 D, M2 N$ y" _( }$ J
( |5 E# F9 B# I" p7 W7 R 在Linux设备驱动程序的家族中,字符设备驱动程序是较为简单的驱动程序,同时也是应用非常广泛的驱动程序。所以学习字符设备驱动程序,对构建Linux设备驱动程序的知识结构非常重要。本章将带领读者编写一个完整的字符设备驱动程序。+ P; s r) s# a8 W4 A# S- u
# N( x' A6 P! X% b6 e! K 61 字符设备驱动程序框架
; O0 |! C) ~) Q( Y8 y7 k" ?. @7 ]. |2 z( M6 W" u
本节对字符设备驱动程序框架进行了简要的分析。字符设备驱动程序中有许多非常重要的概念,下面将从最简单的概念讲起:字符设备和块设备。
, e3 d$ N4 v1 y& K1 Z/ V
1 G) u: v4 ?8 u" E. R- }# A 611 字符设备和块设备3 d( X; s% U l
7 J' s5 {, S. w+ q2 n0 m. [5 | Linux系统将设备分为3种类型:字符设备、块设备和网络接口设备。- j+ ~) ]' F4 u
8 Y) Z4 J P: a2 t
其中字符设备和块设备难以区分,下面将对其进行重要讲解。! g4 I4 C* f. A
9 L* S# z* D( b% {
1.字符设备9 o F' w: ?& @+ ?$ U3 q
4 y* Y/ I r0 y* X
字符设备是指那些只能一个字节一个字节读写数据的设备,不能随机读取设备内存中的某一数据。其读取数据需要按照先后顺序,从这点来看,字符设备是面向数据流的设备。
# p0 o( e9 d) g* f! s6 y
, Z& B9 I) P' P1 X 常见的字符有鼠标、键盘、串口、控制台和LED等设备。* j; g. D5 k5 m# s& p& X% _7 M
0 x6 C8 f9 `( ^ 2.块设备
/ J5 M8 r3 z# b" H8 k, X/ D/ d* k4 i5 `3 A/ x. W3 d
块设备是指那些可以从设备的任意位置读取一定长度数据的设备。其读取数据不必按照先后顺序,可以定位到设备的某一具体位置,读取数据。
' ~9 ^1 D5 H7 ^" a! e
) ?1 ^+ D- @$ T6 J4 _5 h2 U 常见的块设备有硬盘、磁盘、U盘、SD卡等。# K' t' m3 ^ d3 d
4 L9 s: b& w, i( l+ t* s+ m \ 3.字符设备和块设备的区分
9 Q' }1 c6 u0 ?) O$ ^. t3 |9 V- w2 }: z3 `
每一个字符设备或者块设备都在/dev目录下对应一个设备文件。读者可以通过查看/dev目录下的文件的属性,来区分设备是字符设备还是块设备。
% R' h8 F8 Y5 T7 G
. b' n h W( I2 x4 l 使用cd命令进入/dev目录,并执行ls -l命令就可以看到设备的属性。& U! x4 T4 ~0 N+ W# q, \# i
' x0 l! t( Q. S' T! h! V$ N [root@tom /]# cd /dev /*进入/dev目录*/
4 y+ T) k$ ?" G0 t
; i" y( Z8 ]" M! F [root@tom dev]# ls -l /*列出/dev中文件的信息*/、
5 t/ W5 y% c+ V% _5 ~3 o% n# e
2 z; V" E h' a t% j, g4 ]( D /*第1字段 2 3 4 5 6 7 8 */
/ C! z h2 V8 f0 n
& v. ]$ i5 |3 O crw-rw----+ 1 root root 14, 12 12-21 22:56 adsp7 E: C1 l2 B; U9 P. X
4 h( \+ D6 ?2 u5 Q1 K crw------- 1 root root 10, 175 12-21 22:56 agpgart! {: N3 K' X' o s- m a2 X, g
( V: \" t+ w2 ]1 R2 C crw-rw----+ 1 root root 14, 4 12-21 22:56 audio
+ F8 p3 V4 `! T, F2 C9 D+ q) p# k' y; x7 Z
brw-r----- 1 root disk 253, 0 12-21 22:56 dm-05 ^& t; J: N" I$ K+ O- ^5 w5 a* E, e$ P
$ j) h% t# U+ E6 ~
brw-r----- 1 root disk 253, 1 12-21 22:56 dm-1
/ O) D4 t# n: W0 F/ V' f5 L
; r6 u. v2 H2 ^ crw-rw---- 1 root root 14, 9 12-21 22:56 dmmidi
7 P* l( u$ A/ ^3 `3 \+ n
* s. Q: Z, I/ @. @ ls -l命令的第一字段中的第一个字符c表示设备是字符设备,b表示设备是块设备。第234字段对驱动程序开发来说没有关系。
- a# S4 y3 U @* A# J: r7 U- o6 S* X$ u. s! T. k0 v
第5,6字段分别表示设备的主设备号和次设备号,将在612节讲解。第7字段表示文件的最后修改时间。第8字段表示设备的名字。" c. g/ {+ d$ C+ H. X
6 r/ \; J: C; T3 ]' t0 F+ V 由第1和8字段可知,adsp是字符设备,dm-0是块设备。其中adsp设备的主设备号是14,次设备号是12。2 w9 R" {: {# q- \( k
: n, d! t* c( `8 w$ m/ m) ]
612 主设备号和次设备号
r, D+ I3 E1 P2 m, y% z- P$ y/ z H# w# k2 }) o
一个字符设备或者块设备都有一个主设备号和次设备号。
! T' H o3 |- r: E" \, d2 m2 I& c3 @0 s4 h; ~1 `) v( i
主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。
3 u+ Q) C2 l. j& ]% \
' L9 ^9 i/ K4 k% ?3 G 例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。- m l# x& M2 L0 _9 C; W- s; K" \' ~
P4 L8 I3 I" s' `( b7 R 1.主设备号和次设备号的表示" ?4 I4 `8 p8 j4 h. `
* z3 t- `! V, H
在Linux内核中,dev_t类型用来表示设备号。在Linux 26294中,dev_t定义为一个无符号长整型变量,如下:
! `" k% N7 n2 _* p' q
V( L" B" M( g& ? typedef u_long dev_t;
# D9 U& c1 i. H$ D8 d$ [0 G
$ ~/ O! _+ i0 \* ] u_long在32位机中是4个字节,在64位机中是8字节。以32位机为例,其中高12表示主设备号,低20为表示次设备号,如图61所示。
; Z7 p. ]& ] X4 j# o
1 g, W# k$ `2 k7 M' w, k6 | 图61 dev_t结构" x9 o- {6 |4 J$ s+ M- C. C8 w
& c- Z$ _/ t7 n" M 2.主设备号和次设备号的获取
0 Z; K: m$ t3 n) \' f2 x- k1 Q: w, z7 O3 H4 w/ c2 Z
为了写出可移植的驱动程序,不能假定主设备号和次设备号的位数。不同的机型中,主设备号和次设备号的位数可能是不同的。应该使用MAJOR宏得到主设备号,使用MINOR宏来得到次设备号。
1 g7 |3 e5 X; E
; `% k3 \7 Y" u 大发888下面是两个宏的定义:
" B$ p9 q- a, F4 [- R0 U* y: D
#define MINORBITS 20 /*次设备号位数*/
$ K% i8 w5 N4 ]- J& _
9 ?/ x& [" `; n8 [8 O8 h7 j$ m #define MINORMASK ((1U > MINORBITS))2 l+ o. z, R7 G6 F
9 z& X" g3 G# L+ ?: d D; j /*dev右移20位得到主设备号*/4 ^4 z: ?% l _+ T6 f9 q' W
4 e: r2 R+ C X: E# B2 m4 @/ N2 B #define MINOR(dev) ((unsigned int) ((dev) MINORMASK))
& f; F* V* G/ ?# }1 b
D, q4 u5 _2 `3 b( Y% L /*与次设备掩码与,得到次设备号*/
9 ?6 _1 m$ Q* D' s) l
+ m* S, s% b# M: P; b6 a2 _ MAJOR宏将dev_t向右移动20位,得到主设备号;MINOR宏将dev_t的高12位清零,得到次设备号。相反,可以将主设备号和次设备号转换为设备号类型(dev_t),使用宏MKDEV可以完成这个功能。( p2 s1 R1 T* \+ [( t( [
- L% q, M1 Z# m$ H* l) n #define MKDEV(ma,mi) (((ma)
- L7 P/ O R8 ]' {$ f% v0 a8 U# p& [% E7 U( T2 H
MKDEV宏将主设备号(ma)左移20位,然后与次设备号(mi)相与,得到设备号。0 K Y8 Y" o$ V3 x7 r" [/ M
& W2 m* w7 h" G 3.静态分配设备号$ Y; C! Z+ Q5 z9 `9 m
* V6 e2 r. B& r% V m7 P 静态分配设备号,就是驱动程序开发者,静态地指定一个设备号。对于一部分常用的设备,内核开发者已经为其分配了设备号。这些设备号可以在内核源码documentation/ devicestxt文件中找到。
" J, J6 r% H9 J( x* A3 \0 G7 w9 F
7 h4 [: Z8 D- W, k# k 如果只有开发者自己使用这些设备驱动程序,那么其可以选择一个尚未使用的设备号。在不添加新硬件的时候,这种方式不会产生设备号冲突。但是当添加新硬件时,则很可能造成设备号冲突,影响设备的使用。0 U+ M6 k5 u! v5 w
7 C- Q. @* ^2 O. r4 E/ [$ {6 j 4.动态分配设备号( r; I9 Y, I* }+ V0 Y* m7 Y
& c- t1 k+ x' a6 f7 B6 ^5 b. Z
由于静态分配设备号存在冲突的问题# n0 m* ]; y' k4 _! z
$ Y# _* v# [' [4 R: o; D$ d
|