2. 硬件管理和设备驱动

Linux 系统通过设备驱动管理硬件设备。如果添加了新的硬件设备,那么需要编写相应的硬件驱动来管理它。对于一些常见的硬件设备,系统已经自带了相应的驱动,编译内核时,选中它们,然后编译成内核的一部分或者以模块的方式编译。如果以模块的方式编译,那么可以在系统的 /lib/modules/$(uname -r)目录下找到对应的模块文件。

范例:查找设备所需的驱动文件

比如,可以这样找到相应的 scsi 驱动和 usb 驱动对应的模块文件

  • 更新系统中文件索引数据库(有点慢)$ updatedb
  • 查找 scsi 相关的驱动 $ locate scsi*.ko
  • 查找 usb 相关的驱动 $ locate usb*.ko

这些驱动以 .ko 为后缀,在安装系统时默认编译为了模块。实际上可以把它们编译为内核的一部分,仅仅需要在编译内核时选择为[*]即可。但是,很多情况下会以模块的方式编译它们,这样可以减少内核的大小,并根据需要灵活地加载和卸载它们。下面简单地演示如何卸载模块、加载模块以及查看已加载模块的状态。

可通过 /proc 文件系统的 modules 文件检查内核中已加载的各个模块的状态,也可以通过 lsmod 命令直接查看它们。$ cat /proc/modules 或者 $ lsmod

范例:查看已经加载的设备驱动

查看 scsi 和 usb 相关驱动,结果各列为模块名、模块大小、被其他模块的引用情况(引用次数、引用它们的模块)

$ lsmod | egrep "scsi|usb"
usbhid                 29536  0
hid                    28928  1 usbhid
usbcore               138632  4 usbhid,ehci_hcd,ohci_hcd
scsi_mod              147084  4 sg,sr_mod,sd_mod,libata

范例:卸载设备驱动

下面卸载 usbhid 模块看看(不要卸载scsi的驱动!因为你的系统可能就跑在上面,如果确实想玩玩,卸载前记得保存数据),通过 rmmod 命令就可以实现,先切换到 Root 用户:

$ sudo -s
# rmmod usbhid

再查看该模块的信息,已经看不到了吧

$ lsmod | grep ^usbhid

范例:挂载设备驱动

如果有个 usb 鼠标,那么移动一下,是不是发现动不了啦?因为设备驱动都没有了,设备自然就没法用罗。不过不要紧张,既然知道原因,那么重新加载驱动就可以,下面用 insmodusbhid 模块重新加载上。

$ sudo -s
# insmod `locate usbhid.ko`

locate usbhid.ko 是为了找出 usbhid.ko 模块的路径,如果之前没有 updatedb,估计用它是找不到了,不过也可以直接到 /lib/modules 目录下用 findusbhid.ko 文件找到。

# insmod $(find /lib/modules -name "*usbhid.ko*" | grep `uname -r`)

现在鼠标又可以用啦,不信再动一下鼠标 :-)

到这里,硬件设备和设备驱动之间关系应该是比较清楚了。如果没有,那么继续下面的内容。

范例:查看设备驱动对应的设备文件

Linux 设备驱动关联着相应的设备文件,而设备文件则和硬件设备一一对应。这些设备文件都统一存放在系统的 /dev/ 目录下。

例如,scsi 设备对应/dev/sda/dev/sda1/dev/sda2... 下面查看这些设备信息。

$ ls -l /dev/sda*
brw-rw---- 1 root disk 8, 0 2007-12-28 22:49 /dev/sda
brw-rw---- 1 root disk 8, 1 2007-12-28 22:50 /dev/sda1
brw-rw---- 1 root disk 8, 3 2007-12-28 22:49 /dev/sda3
brw-rw---- 1 root disk 8, 4 2007-12-28 22:49 /dev/sda4
brw-rw---- 1 root disk 8, 5 2007-12-28 22:50 /dev/sda5
brw-rw---- 1 root disk 8, 6 2007-12-28 22:50 /dev/sda6
brw-rw---- 1 root disk 8, 7 2007-12-28 22:50 /dev/sda7
brw-rw---- 1 root disk 8, 8 2007-12-28 22:50 /dev/sda8

可以看到第一列第一个字符都是 b,第五列都是数字 8 。 b 表示该文件是一个块设备文件,对应地,如果是 c 则表示字符设备(例如 `/dev/ttyS0),关于块设备和字符设备的区别,可以看这里:

  • 字符设备:字符设备就是能够像字节流一样访问的设备,字符终端和串口就属于字符设备。
  • 块设备:块设备上可以容纳文件系统。与字符设备不同,在读写时,块设备每次只能传输一个或多个完整的块。在 Linux 操作系统中,应用程序可以像访问字符设备一样读写块设备(一次读取或写入任意的字节数据)。因此,块设备和字符设备的区别仅仅是在内核中对于数据的管理不同。

数字 8 则是该硬件设备在内核中对应的设备编号,可以在内核的 Documentation/devices.txt/proc/devices 文件中找到设备号分配情况。但是为什么同一个设备会对应不同的设备文件(/dev/sda 后面为什么还有不同的数字,而且 ls 结果中的第 6 列和它们对应起来)。这实际上是为了区分不同设备的不同部分。对于硬盘,这样可以处理硬盘内部的不同分区。就内核而言,它仅仅需要通过第 5 列的设备号就可以找到对应的硬件设备,但是对于驱动模块来说,它还需要知道如何处理不同的分区,于是就多了一个辅设备号,即第 6 列对应的内容。这样一个设备就有了主设备号(第 5 列)和辅设备号(第 6 列),从而方便地实现对各种硬件设备的管理。

因为设备文件和硬件是对应的,这样可以直接从 /dev/sda (如果是 IDE 的硬盘,那么对应的设备就是 /dev/hda 啦)设备中读出硬盘的信息,例如:

范例:访问设备文件

  • dd 命令复制出硬盘的前 512 个字节,要 Root 用户
    $ sudo dd if=/dev/sda of=mbr.bin bs=512 count=1
    
  • file 命令查看相应的信息
    $ file mbr.bin
    mbr.bin: x86 boot sector, LInux i386 boot LOader; partition 3: ID=0x82, starthead 254, startsector 19535040, 1959930 sectors; partition 4: ID=0x5, starthead 254, startsector 21494970, 56661255 sectors, code offset 0x48
    
  • 也可以用 od 命令以 16 进制的形式读取并进行分析
    $ od -x mbr.bin
    
  • bs 是块的大小(以字节 bytes 为单位),count 是块数
  • 因为这些信息并不直观(而且下面会进一步深入分析),那么先来看看另外一个设备文件,将可以非常直观地演示设备文件和硬件的对应关系。还是以鼠标为例吧,下面来读取鼠标对应的设备文件的信息。
    $ sudo -s
    # cat /dev/input/mouse1 | od -x
    

你的鼠标驱动可能不太一样,所以设备文件可能是其他的,但是都会在 /dev/input 下。

移动鼠标看看,是不是发现有不同信息输出。基于这一原理,我们经常通过在一端读取设备文件 /dev/ttyS0 中的内容,而在另一端往设备文件 /dev/ttyS0 中写入内容来检查串口线是否被损坏。

到这里,对设备驱动、设备文件硬件设备之间的关联应该是印象更深刻了。如果想深入了解设备驱动的工作原理和设备驱动的编写,那么看看下面列出的相关资料,开始设备驱动的编写历程吧。

参考资料: