7 K1 G/ E4 T3 c+ U
$ |" w# [. x; v, c) O
第6章 简单的字符设备驱动程序: L+ A' t1 w- {6 P* r1 O* u
5 U+ c4 f7 d8 B 在Linux设备驱动程序的家族中,字符设备驱动程序是较为简单的驱动程序,同时也是应用非常广泛的驱动程序。所以学习字符设备驱动程序,对构建Linux设备驱动程序的知识结构非常重要。本章将带领读者编写一个完整的字符设备驱动程序。
) k" L% x6 j F! x. Q2 P6 U. Q! U! i5 P
61 字符设备驱动程序框架
. i$ D' x( Z9 I( b) l- V- V& K2 }% `3 @5 Z. V ]: w2 I# `
本节对字符设备驱动程序框架进行了简要的分析。字符设备驱动程序中有许多非常重要的概念,下面将从最简单的概念讲起:字符设备和块设备。
9 {$ G [ M4 L. ~8 Z
: u, {7 l. }! S5 K* ?" S& o 611 字符设备和块设备+ \4 l Z+ ]2 d; H( W% _9 B- ^
[2 a2 s# Z6 [% x' v L Linux系统将设备分为3种类型:字符设备、块设备和网络接口设备。- o9 F4 T: x, t- C2 f" O0 u3 Y8 q
/ x6 E7 G6 u9 j0 V9 c
其中字符设备和块设备难以区分,下面将对其进行重要讲解。- k( i# m& L) K1 P6 ]) W/ n0 N
1 U% ~1 B' J+ M- S( x: i ] 1.字符设备# w. f6 Y; h, v9 e
( F) s( X# Q# Z2 R* E" X( @ 字符设备是指那些只能一个字节一个字节读写数据的设备,不能随机读取设备内存中的某一数据。其读取数据需要按照先后顺序,从这点来看,字符设备是面向数据流的设备。
' M; G4 A' J+ E% Z
' ~- A% f) }1 U( K1 c2 I9 J+ _ 常见的字符有鼠标、键盘、串口、控制台和LED等设备。# O* }8 v" {3 _0 L4 j2 h
0 S- [+ q4 P+ g. q 2.块设备( W2 D8 z0 a2 i& D
1 ~5 V, ]3 g9 T' u, H4 T2 k+ { 块设备是指那些可以从设备的任意位置读取一定长度数据的设备。其读取数据不必按照先后顺序,可以定位到设备的某一具体位置,读取数据。2 O. w* V5 S1 u/ S4 Y: F" F
7 V, T' x8 m" [! _6 N7 M 常见的块设备有硬盘、磁盘、U盘、SD卡等。
! ]/ A( k( y2 b6 J4 |. R# r2 i& n
9 ^7 q+ W* n! m a 3.字符设备和块设备的区分
! b5 G t4 f4 w9 g0 P
9 M0 [8 m, T% t6 r2 C 每一个字符设备或者块设备都在/dev目录下对应一个设备文件。读者可以通过查看/dev目录下的文件的属性,来区分设备是字符设备还是块设备。( K/ N4 h' I1 b" _; I
' K/ H" Y7 f) t! v
使用cd命令进入/dev目录,并执行ls -l命令就可以看到设备的属性。: D) N- H" n8 E1 U1 W
0 R" o/ B- h' j2 C* W! n [root@tom /]# cd /dev /*进入/dev目录*/- ]0 y' a" L) u, b6 X+ |: ~
! g' e8 w& s8 d9 ^7 H4 M
[root@tom dev]# ls -l /*列出/dev中文件的信息*/、
" ~. `& g ~+ \: p X0 X3 n" |* Q
/*第1字段 2 3 4 5 6 7 8 */
6 d; R% \& e J- n$ Y6 D, h. k0 s, M1 E* G" g, T7 q$ Z" `( E e
crw-rw----+ 1 root root 14, 12 12-21 22:56 adsp
7 Y0 Q9 w; o& T/ W8 ?' S& a3 [ }/ B! \- j
crw------- 1 root root 10, 175 12-21 22:56 agpgart9 p9 a0 [7 ~$ S# S8 g9 E1 ~
2 R: L* s5 m& F" m6 o; F1 X/ j$ Y, f
crw-rw----+ 1 root root 14, 4 12-21 22:56 audio% ?) f* p9 P8 f8 `8 H) J C
% E5 p( Y$ f: `/ m! v5 N+ k brw-r----- 1 root disk 253, 0 12-21 22:56 dm-0' S+ X6 E! x; h" }4 N
# u2 A6 l' X' ^: U brw-r----- 1 root disk 253, 1 12-21 22:56 dm-1
, Z* a* i& b$ S; q$ |) Y- @( l; P- p) U* [" s7 _) k
crw-rw---- 1 root root 14, 9 12-21 22:56 dmmidi" Y! b* s( k9 n# y; `
! E; s' e0 B9 o6 U' V& T }! R ls -l命令的第一字段中的第一个字符c表示设备是字符设备,b表示设备是块设备。第234字段对驱动程序开发来说没有关系。9 P5 u K0 c( X& f' }% d
4 O! ]" N9 A7 s* v/ p6 G$ _& V 第5,6字段分别表示设备的主设备号和次设备号,将在612节讲解。第7字段表示文件的最后修改时间。第8字段表示设备的名字。
' z" R; \+ K8 B* g! c9 D( A% b6 ]
/ I. F; ?9 O. @1 M 由第1和8字段可知,adsp是字符设备,dm-0是块设备。其中adsp设备的主设备号是14,次设备号是12。
* _) z! p, [' s2 `7 e
, \. C8 M5 f; R2 k- G' \ 612 主设备号和次设备号, v. d1 y7 F+ H) {' t
( Y* G+ ^ d+ W; u {# t3 [9 W 一个字符设备或者块设备都有一个主设备号和次设备号。4 a7 N4 ^- s; H* a6 v
( w8 _3 q- I2 n2 z1 Q3 R 主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。
0 _ K5 x& a' M0 x$ K3 I8 w( x6 X0 G) \7 T g5 W T2 N9 x6 a! q8 K
例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。
6 G* c/ ^: g5 o$ _6 Q9 e" r5 ^6 N0 J& _ q
1.主设备号和次设备号的表示
8 \0 i" @/ v6 D% J% M/ W
* G" V1 }& K, u. f/ c( Y 在Linux内核中,dev_t类型用来表示设备号。在Linux 26294中,dev_t定义为一个无符号长整型变量,如下:" H8 @! o: P; m% W) [+ [3 q
9 @$ y1 X) w, g
typedef u_long dev_t;" u+ w k. u. F0 H5 D, e7 l
2 S+ r, E6 w+ t3 T# j
u_long在32位机中是4个字节,在64位机中是8字节。以32位机为例,其中高12表示主设备号,低20为表示次设备号,如图61所示。
7 V) q) }. {+ C+ f6 h
" v4 A7 s9 z+ r* l8 Y 图61 dev_t结构
% A% T' [$ I# V0 @( f4 [3 A
5 R1 q- m3 t3 z 2.主设备号和次设备号的获取% q1 ], A% S% j7 H7 x* Z+ @
' a" W* a/ L/ A0 w4 G" \% Q5 x
为了写出可移植的驱动程序,不能假定主设备号和次设备号的位数。不同的机型中,主设备号和次设备号的位数可能是不同的。应该使用MAJOR宏得到主设备号,使用MINOR宏来得到次设备号。" Y( w) H9 P/ j& n* S
+ w! x: C( m8 c- d3 ?
大发888下面是两个宏的定义:
9 z2 h+ o& t* c' s" u" p, ?, X0 B6 ]/ R0 W9 ?+ W
#define MINORBITS 20 /*次设备号位数*/
5 v1 J9 g% \. q3 u. @+ M, P
9 c! P& L7 L2 I) o #define MINORMASK ((1U > MINORBITS))
0 a! ^% z6 V8 b7 k5 {) l& u
, G0 _ w4 J: E/ w /*dev右移20位得到主设备号*/
9 g+ k, L @6 ~$ i4 }% x
/ w i9 Q8 ~$ ~7 o* X1 W' ] #define MINOR(dev) ((unsigned int) ((dev) MINORMASK))- L, T7 F: N' C
* l/ W( ^, v- n: q9 n; j+ a
/*与次设备掩码与,得到次设备号*/, A) v! l6 |8 k. y
( ?: x% @" l5 \1 I4 N MAJOR宏将dev_t向右移动20位,得到主设备号;MINOR宏将dev_t的高12位清零,得到次设备号。相反,可以将主设备号和次设备号转换为设备号类型(dev_t),使用宏MKDEV可以完成这个功能。
7 j. a! ?& y! @ E9 k2 W
, F7 T: ~7 p5 h3 W& N9 m: p' a #define MKDEV(ma,mi) (((ma)
# b* Z/ P* x% _4 X/ T, g
: D9 }9 b0 N' B3 r& e MKDEV宏将主设备号(ma)左移20位,然后与次设备号(mi)相与,得到设备号。$ z4 J [+ _ U1 g; G" Y7 I1 B
: q+ I! ]' e/ i" l- u% x3 B 3.静态分配设备号4 _$ u5 ~/ A- E3 o
$ {! P& ^1 _+ O0 j4 ]
静态分配设备号,就是驱动程序开发者,静态地指定一个设备号。对于一部分常用的设备,内核开发者已经为其分配了设备号。这些设备号可以在内核源码documentation/ devicestxt文件中找到。& K- V; H9 _# N9 E* O( V
0 F0 Y5 j' F2 q" C3 V ^: n 如果只有开发者自己使用这些设备驱动程序,那么其可以选择一个尚未使用的设备号。在不添加新硬件的时候,这种方式不会产生设备号冲突。但是当添加新硬件时,则很可能造成设备号冲突,影响设备的使用。
' C: z; C; k/ _6 G0 \ E. R0 P. y$ k1 L/ b" E3 p2 g8 Z( [/ N
4.动态分配设备号
% A$ D( d* C* a5 U: K6 u: t9 ~. P( h5 _, I4 F9 n/ V
由于静态分配设备号存在冲突的问题
5 `- p D+ D5 t2 D
9 z- P u! O: s2 @, z |