; @5 f" P, U- t! Q; m
/ f8 j" ^2 b% @2 Y, h7 X& ^: U
第6章 简单的字符设备驱动程序) P7 g" T4 v/ }6 S( Y" v2 z
: ] v$ m. B4 ]( y) Q 在Linux设备驱动程序的家族中,字符设备驱动程序是较为简单的驱动程序,同时也是应用非常广泛的驱动程序。所以学习字符设备驱动程序,对构建Linux设备驱动程序的知识结构非常重要。本章将带领读者编写一个完整的字符设备驱动程序。4 e! ^' [2 l) U6 t: Q
3 [' C5 } \( C4 j8 }
61 字符设备驱动程序框架
9 Z: H* ~) O. C( n0 h! L! I5 v/ w! l& O1 f/ ]+ U
本节对字符设备驱动程序框架进行了简要的分析。字符设备驱动程序中有许多非常重要的概念,下面将从最简单的概念讲起:字符设备和块设备。( Y6 O z0 S+ I0 ~4 K$ x0 j) R
$ x7 R$ }8 Z0 {; R9 W3 ^ 611 字符设备和块设备
" S8 E$ H3 j6 i% G ]* e2 D$ o
- e% r3 _% L2 | i2 q P5 H Linux系统将设备分为3种类型:字符设备、块设备和网络接口设备。7 R7 O9 _5 ]$ r. C
$ b' z1 o+ h" n! q, z; g8 D: d 其中字符设备和块设备难以区分,下面将对其进行重要讲解。% {1 O. W* u( T* R3 E8 `& `: P! j, ]" J @
/ v8 h' H4 I6 ~5 t
1.字符设备
) D; d! t' \3 c, W
0 r& o4 q; i: R' ] 字符设备是指那些只能一个字节一个字节读写数据的设备,不能随机读取设备内存中的某一数据。其读取数据需要按照先后顺序,从这点来看,字符设备是面向数据流的设备。# X9 w" k; j `8 D$ `* g
. ~ z; _# r! ^: Y5 n* ]7 o 常见的字符有鼠标、键盘、串口、控制台和LED等设备。
( m) h) D( t' z- y& q" J4 w$ j F
2.块设备# D& g; x% l4 t( j4 Z
4 ?6 a' q% A; F8 L# ^ 块设备是指那些可以从设备的任意位置读取一定长度数据的设备。其读取数据不必按照先后顺序,可以定位到设备的某一具体位置,读取数据。
# c: k0 ]- Y5 g1 d/ k% M( s
' F1 O8 b9 \$ @% G 常见的块设备有硬盘、磁盘、U盘、SD卡等。7 z0 ^' ^/ ]- s8 \# A. R
4 B. a" G1 }3 y 3.字符设备和块设备的区分; S7 y( i: g) s6 j+ {
/ O/ U; O' S* |" Z% E4 x, Z0 U) Y
每一个字符设备或者块设备都在/dev目录下对应一个设备文件。读者可以通过查看/dev目录下的文件的属性,来区分设备是字符设备还是块设备。
! c2 w% N5 j* n
8 `3 x1 _0 V$ L 使用cd命令进入/dev目录,并执行ls -l命令就可以看到设备的属性。
( g2 y9 G0 Z$ I3 f3 h! T0 L4 n
[root@tom /]# cd /dev /*进入/dev目录*/4 `4 U0 s4 W& z
1 b( Y$ A3 T7 Q4 e [root@tom dev]# ls -l /*列出/dev中文件的信息*/、
$ k% b2 F) C1 p5 D9 \! f' G( S3 |
" S! g. |( d/ X2 W' t/ m# Y. M /*第1字段 2 3 4 5 6 7 8 */
+ r. ^0 y6 N5 D+ u7 G8 t3 n8 M
0 t' J( \: V% C1 G. u2 W6 |* i crw-rw----+ 1 root root 14, 12 12-21 22:56 adsp
9 K) c4 i' Z3 }9 h( y7 l/ B9 g' W4 Z) C: ]
crw------- 1 root root 10, 175 12-21 22:56 agpgart
8 x. u" U& @! s: }2 v# L5 D: s, N- ^+ k
crw-rw----+ 1 root root 14, 4 12-21 22:56 audio
7 n( K9 U4 e+ \- S. N' D. n% w$ U4 ?
brw-r----- 1 root disk 253, 0 12-21 22:56 dm-0" k/ w" e5 s2 p
3 m" A) Z6 o1 m0 v2 K: n
brw-r----- 1 root disk 253, 1 12-21 22:56 dm-1
( G% t! ~: ^& Z# v+ T2 i. t% L' V
# d. M$ v& n- k& [6 I crw-rw---- 1 root root 14, 9 12-21 22:56 dmmidi' q1 q5 y6 e# H( g4 _ z
7 x3 _$ k O* ]
ls -l命令的第一字段中的第一个字符c表示设备是字符设备,b表示设备是块设备。第234字段对驱动程序开发来说没有关系。0 E. B: t& h; |% Q% y
4 C$ E: a" M& b9 I9 c& L, H2 V( I 第5,6字段分别表示设备的主设备号和次设备号,将在612节讲解。第7字段表示文件的最后修改时间。第8字段表示设备的名字。
$ p5 Z3 d6 p6 \6 P Q# T
$ U* c O8 S Q5 B 由第1和8字段可知,adsp是字符设备,dm-0是块设备。其中adsp设备的主设备号是14,次设备号是12。
" \3 Z" Q) R3 D/ j( ]
5 G) o; G: O b8 f& M 612 主设备号和次设备号$ U% {1 K3 d% T) k$ \0 p
# J& k# b; _# i- g' N& H
一个字符设备或者块设备都有一个主设备号和次设备号。6 ]6 K. G u0 |8 Z' f
# _; v9 p; u) ^
主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。
# s8 W0 ^5 \- g
' b! r) g7 S5 p3 T 例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。
7 |4 `6 [, J0 `& r$ S( q% a
( t$ I% A4 ?% ~7 Z 1.主设备号和次设备号的表示
% p" f. p* r: P+ ?0 |3 g' d" u1 ^
9 o( w( A3 _- H8 f, E 在Linux内核中,dev_t类型用来表示设备号。在Linux 26294中,dev_t定义为一个无符号长整型变量,如下:# b( O( s3 _. O% g' O
7 [5 A8 u. ?% V/ a8 S
typedef u_long dev_t;
6 \% ^) Y) Y$ S8 L8 k! V- y# n. x% D: g3 ^
u_long在32位机中是4个字节,在64位机中是8字节。以32位机为例,其中高12表示主设备号,低20为表示次设备号,如图61所示。
! J4 i+ o) q5 y% L1 I' y* {
' k" f- `. g! f4 J( z: Z3 Q# L 图61 dev_t结构
! k+ g/ H! w. M- v
/ ]; }& t7 @* \' \1 L7 X 2.主设备号和次设备号的获取3 F k z1 j+ ^& B# A" c6 L
+ ?/ c, g$ p# E1 g4 z
为了写出可移植的驱动程序,不能假定主设备号和次设备号的位数。不同的机型中,主设备号和次设备号的位数可能是不同的。应该使用MAJOR宏得到主设备号,使用MINOR宏来得到次设备号。1 w/ }$ O: ^3 l* p u
5 Z5 P: p& ~6 H% d- E$ N 大发888下面是两个宏的定义:
* w- J( X: z7 n: \% ~+ W% i( {$ l) Z) ?" {. V7 j
#define MINORBITS 20 /*次设备号位数*/" ~% ?* }7 A. _! P ]5 X3 ~6 g3 o
$ `6 x& I( [7 n" ]! K% J/ Q; p
#define MINORMASK ((1U > MINORBITS))
- N4 R5 h- v& \8 e$ ]. K8 o: ^. X' G: Y- ]' F
/*dev右移20位得到主设备号*/* [% ^' \1 k6 D/ d5 k9 c& D
, ~ N" x9 q) c' [2 @/ q0 c: x #define MINOR(dev) ((unsigned int) ((dev) MINORMASK))
, c; x' M9 J) l7 Z8 z# }. f+ b2 l9 I/ x" H& d
/*与次设备掩码与,得到次设备号*/1 K, Z3 I+ |( Q# @
0 H1 s i; s, |' C5 z* G MAJOR宏将dev_t向右移动20位,得到主设备号;MINOR宏将dev_t的高12位清零,得到次设备号。相反,可以将主设备号和次设备号转换为设备号类型(dev_t),使用宏MKDEV可以完成这个功能。
, R4 H8 t; y! W v4 P$ b+ ^% c0 W8 Q( u
#define MKDEV(ma,mi) (((ma)
# w O# e3 v' w* V* g& f/ `3 Z+ K4 l* l% H$ I7 L# V* T
MKDEV宏将主设备号(ma)左移20位,然后与次设备号(mi)相与,得到设备号。: H n; |5 _+ z0 b& g" u7 F( r
. ^' _1 s& |% r; o1 I; b3 p- q/ ^
3.静态分配设备号* o1 [: l1 |( [/ |5 J
4 l! [: z* [! _. }/ e0 u 静态分配设备号,就是驱动程序开发者,静态地指定一个设备号。对于一部分常用的设备,内核开发者已经为其分配了设备号。这些设备号可以在内核源码documentation/ devicestxt文件中找到。
( \4 j( N( W+ N- }# [& [$ x/ ]0 m5 S# s4 ^0 P+ b5 V9 h
如果只有开发者自己使用这些设备驱动程序,那么其可以选择一个尚未使用的设备号。在不添加新硬件的时候,这种方式不会产生设备号冲突。但是当添加新硬件时,则很可能造成设备号冲突,影响设备的使用。1 {) N" n. x% v7 _! X
3 A% ]% I9 ~2 C( F9 S! ?) U. r
4.动态分配设备号9 ]9 T% z2 r2 g D; j3 R! i
; i+ C) I5 u) Q# Q* m5 Z
由于静态分配设备号存在冲突的问题& {! l3 ]7 ]& r# v3 {+ i- v
3 J6 y& q: z4 I% }& C* q' s0 B |