Linux Kernel in a Nutshell - 7

Customizing a Kernel

原文链接

我的博客

以·问题·做关键字搜索,还有问题

构建你自己的 Linux 内核版本最困难的部分,应该就是确定哪一个驱动以及配置选项是你的设备需要的。本章将会手把手带你查找选择合适的驱动。

Using a Distribution Kernel

一个最简单确定需要模块的方法之一,是从发行版内核包附带的内核配置开始。这一方法也能更简单地确定在一个运行的系统上需要哪些驱动,哪些驱动已经与硬件绑定。

如果你的设备上尚且没有安装一个 Linux 发行版本,可以使用 LiveCD 来安装一个。这允许你在你的设备上启动 Linux,并确定哪些内核配置选项是必须的。

Where Is the Kernel Configuration?

几乎所有的发布版本都提供内核配置文件作为内核发布包的一部分。读取发布版本指定文档,来获取找到这些配置的信息。通常是位于 /usr/src/linux/ 目录树下。

如果内核配置难以查找到,查看内核自身。大部分发布版本编译的内核,都将配置信息保存在 /proc 文件系统中。使用下面的命令,来确定是否有相关信息:

$ ls /proc/config.gz

如果存在 /proc/config.gz 文件,可以将它复制到内核源码树下并解压:

$ cp /proc/config.gz ~/linux/
$ cd ~/linux
$ gzip -dv config.gz

将这个配置文件复制到内核目录中,并将其重命名为 .config。之后使用它作为基础内核配置文件,来编译内核。

使用这个配置文件,总是能够生成能够在你的设备上正常工作的内核。这个方法的缺点是,你需要编译内核源码树中的所有内核模块以及驱动。而对于单个设备而言,很多都是不需要的,因此你需要开始关闭那些不需要的驱动与选项。建议你关闭那些你确定不需要的内容,因为系统的有些功能可能会依赖于某些选项,需要将其使能。

Finding Which Module is Needed

对于来自一个发布版本的配置文件配置的工程,需要编译很长时间,因为所有驱动都会被编译。你希望只编译你有的那些硬件驱动,这会节省编译内核的时间,并允许你选择是将部分驱动还是全部驱动编译到内核中,这可以节省内存,并在某些架构中,令系统的运行速度变快。想要裁剪驱动,就先要确定你的硬件需要哪些模块。我们将要介绍两个例子,来讲解如何查找到所控制硬件的驱动。

在你的系统的某些位置,存储着运行的内核确定设备绑定的对应驱动的信息。最重要的位置是称作 sysfs 的虚拟文件系统。sysfs 总是被 Linux 的初始化脚本挂载在文件系统的 /sys 路径下。sysfs 提供了一个视角,让我们看到不同区块的内核是如何使用指向整个文件系统的符号链接钩到一起的。

在下面所有例子中,展示了硬件类型以及 sysfs 路径。你的设备可能不同,但是信息的相对位置还是相同的。如果你的设备上的 sysfs 文件名不太一样,不要惊讶。

需要另外说明的是,sysfs 文件系统的内部结构总是变来变去,以向重新识别设备并以最佳方式向用户空间展示内核信息。因此,随着时间的推移,本章中提到的东西可能都不存在了。不过,信息依旧是存在的,只是修改了一点点。

Example: Determining the network driver

系统中最常见也是最重要的设备是网络接口控制卡。它用来确定哪一个驱动控制这个设备,并在内核配置中使能这个驱动,保证网络能够正常工作。

首先,回退到网络链接名找到是什么 PCI 设备控制着它。使用下面的命令查看不同的网络名:

$ ls /sys/class/net/
eth0 eth1 eth2 lo

其中 lo 目录为网络回环设备,并不会真正连接到真实的网络设备。eth0eth1eth2 目录是我们需要注意的内容,它们代表着真实的网络设备。

更深入的查看网络设备,来确定你关心的内容,使用 ifconfig 工具:

$ /sbin/ifconfig -a
eth0 	Link encap:Ethernet HWaddr 00:12:3F:65:7D:C2
		inet addr:192.168.0.13 Bcast:192.168.0.255 Mask:255.255.255.0
		UP BROADCAST NOTRAILERS RUNNING MULTICAST MTU:1500 Metric:1
		RX packets:2720792 errors:0 dropped:0 overruns:0 frame:0
		TX packets:1815488 errors:0 dropped:0 overruns:0 carrier:0
		collisions:0 txqueuelen:100
		RX bytes:3103826486 (2960.0 Mb) TX bytes:371424066 (354.2 Mb)
		Base address:0xdcc0 Memory:dfee0000-dff00000
eth1 	Link encap:UNSPEC HWaddr 80-65-00-12-7D-C2-3F-00-00-00-00-00-00-
		00-00-00
		BROADCAST MULTICAST MTU:1500 Metric:1
		RX packets:0 errors:0 dropped:0 overruns:0 frame:0
		TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
		collisions:0 txqueuelen:1000
		RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
eth2 	Link encap:UNSPEC HWaddr 00-02-3C-04-11-09-D2-BA-00-00-00-00-00-
		00-00-00
		BROADCAST MULTICAST MTU:1500 Metric:1
		RX packets:0 errors:0 dropped:0 overruns:0 frame:0
		TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
		collisions:0 txqueuelen:1000
		RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
lo 		Link encap:Local Loopback
		inet addr:127.0.0.1 Mask:255.0.0.0
		UP LOOPBACK RUNNING MTU:16436 Metric:1
		RX packets:60 errors:0 dropped:0 overruns:0 frame:0
		TX packets:60 errors:0 dropped:0 overruns:0 carrier:0
		collisions:0 txqueuelen:0
		RX bytes:13409 (13.0 Kb) TX bytes:13409 (13.0 Kb)

从这个列表中列出的信息,可以看出 eth0 设备是活跃并处于工作中的网络设备,这可以从下面的信息中看到:

eth0 	Link encap:Ethernet HWaddr 00:12:3F:65:7D:C2
		inet addr:192.168.0.13 Bcast:192.168.0.255 Mask:255.255.255.0

这个输出信息展示了它是一个以太网设备,具有有效的 IP 地址。

现在,我们希望确保 eth0 设备能够在我们的新内核上正常工作,因此我们需要找到控制它的驱动。可以通过下面的命令来查看:

$ basename `readlink /sys/class/net/eth0/device/driver/module`
e1000

这个输出信息表示,名为 e1000 的模块控制着 eth0 网络设备。basename 命令将下面的命令压缩成单行命令:

  1. 跟着 /sys/class/net/eth0/device 符号链接,进入到 /sys/device/ 目录下,它包含着控制 eth0 的设备信息。注意到,/sys/class/net/eth0 在新版本的内核中可能也是一个符号链接

  2. 在这个目录中,描述了 sysfs 的设备,具有一个绑定这个设备的符号链接,这个符号链接称作 driver,因此我们去向这个链接

  3. sysfs 描述驱动的目录中,具有包含驱动的模块的符号链接,这个符号链接称作 module,我们需要的是符号链接的目标。为了获取到这个目标,我们使用 readlink 命令,产生类似下面的输出:

    $ readlink /sys/class/net/eth0/device/driver/module
    ../../../../module/e1000
    
  4. 因为我们只关心模块的名称,我们希望跳过 readlink 输出的那些路径信息,直取最右侧的信息,这就是 basename 命令做的操作,对这个路径执行下面的命令会产生想要的效果了:

    $ basename ../../../../module/e1000
    e1000
    

现在我们有了模块命了,我们需要找到内核配置选项,并控制它。可以查看不同的网络设备配置菜单或查看内核源码来确定具有正确的选项:

$ cd ~/linux/linux-2.6.17.8
$ find -type f -name Makefile | xargs grep e1000
./drivers/net/Makefile:obj-$(CONFIG_E1000) += e1000/
./drivers/net/e1000/Makefile:obj-$(CONFIG_E1000) += e1000.o
./drivers/net/e1000/Makefile:e1000-objs := e1000_main.o e1000_hw.o e1000_ethtool.o e1000_param.o

需要注意的是,将 e1000 替换为你自己查看到的模块信息。上面这个命令查找到的信息中,我们关注的信息为带有 CONFIG_ 的那个内容。这是内核需要的使能编译模块的配置选项,在上面的例子中,CONFIG_E1000 是我们要查找的配置选项。

现在,我们有了要配置的内核选项,运行配置菜单:

$ make menuconfig

进入配置菜单之后,按下 / 按键 (初始化查找),之后键入配置选项,省略字串中的掉 CONFIG_

内核配置系统会告知你去哪里选择使能这个模块。

 Symbol: E1000 [=y]                                                                                        
  | Type  : tristate                                                                                        
  | Defined at drivers/net/ethernet/intel/Kconfig:42                                                         
  |   Prompt: Intel(R) PRO/1000 Gigabit Ethernet support                                                     
  |   Depends on: NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_INTEL [=y] && PCI [=y]                     
  |   Location:                                                                                             
  |     -> Device Drivers                                                                                   
  |       -> Network device support (NETDEVICES [=y])                                                       
  |         -> Ethernet driver support (ETHERNET [=y])                                                       
  | (1)       -> Intel devices (NET_VENDOR_INTEL [=y])                                                       

location 信息表示如果希望将 E1000 模块编译进入内核,需要将下面的配置使能:

Device Drivers
	Network device support
		[*] Network device support
		[*] Ethernet driver support
		[*] Intel devices

上面的步骤适用于内核的任何设备。

Example: A USB device

作为一个例子,让我们查看一个 USB-to-serial 转换器是如何在我们系统中工作的。它现在被连接到 /dev/ttyUSB0 端口,因此,需要查看 sysfstty 部分内容:

$ ls /sys/class/tty/ | grep USB
ttyUSB0

我们可以追踪 sysfs 中这个设备使用的控制模块:

$ basename `readlink /sys/class/tty/ttyUSB0/device/driver/module`
p12303

之后再内核源码树种查找配置选项:

$ cd ~/linux/linux-2.6.17.8
$ find -type f -name Makefile | xargs grep p12303
./drivers/usb/serial/Makefile:obj-$(CONFIG_USB_SERIAL_PL2303) += pl2303.o

使用内核配置工具,查找合适的选项来使能 CONFIG_USB_SERIAL_PL2303 选项。

Summary of device discovery

作为总结,下面是我们依据一个已经可以正常工作的驱动查找到它在内核源码的选项的方法:

  1. 何理地查找设备绑定的 sysfs 类设备。网络设备在 /sys/class/nettty 设备在 /sys/class/tty 中,其他类型的设备都列在 /sys/class

  2. 循着 sysfs 的脉络,查找到控制这个设备的模块名称,可以在 /sys/class/class_name/device_name/device/driver/module 中查找到,使用 readlink 以及 basename 工具来将模块提取出来:

    $ basename `readlink /sys/class/class_name/device_name/device/driver/module`
    
  3. 在内核源码的 Makefile 中查找带有 CONFIG_ 的规则:

    $ find -type f -name Makefile | xargs grep module_name
    
  4. 在内核配置文件中查找模块配置选项所处的定位以及需要进行的配置

Let the kernel tell us what we need

我们已经使用 sysfs 以及符号链接来确定模块名,下面是一个简单的脚本实现这个工作:

#!/bin/bash
#
# find_all_modules.sh
#
for i in `find /sys/ -name modalias -exec cat {} \;`;do
	/sbin/modprobe --config /dev/null --show-depends $i ;
done | rev | cut -f 1 -d '/' | rev | sort -u

这个脚本将会遍历 sysfs 并查找所有称为 modalias 的文件。modalias 文件包含模块别名,告知 modprobe 命令那个模块要被加载来控制这个设备。模块别名是设备的设备制造商、ID、类型以及其他唯一辨识号组成的字串。所有的内核驱动模块内部具有一个设备支持列表,告知内核它支持的设备。modprobe 程序查看这个设备列表,如果查找到匹配项,它会加载模块。

脚本在 modprobe 加载模块之前停止它,只是打印它需要做的行动。这会给到我们控制系统设备的模块列表。输出类似下面:

$ . find_all_modules.sh
8139cp.ko
8139too.ko
ehci-hcd.ko
firmware_class.ko
i2c-i801.ko
ieee80211.ko
ieee80211_crypt.ko
ipw2200.ko
mii.ko
mmc_core.ko
pcmcia_core.ko
rsrc_nonstatic.ko
sdhci.ko
snd-hda-codec.ko
snd-hda-intel.ko
snd-page-alloc.ko
snd-pcm.ko
snd-timer.ko
snd.ko
soundcore.ko
uhci-hcd.ko
usbcore.ko
yenta_socket.ko

这是一系列控制系统上硬件设备的模块。

脚本也可能会打印下面的一些错误信息:

FATAL: Module pci:v00008086d00002592sv000010CFsd000012E2bc03sc00i00 not
found.
FATAL: Module serio:ty01pr00id00ex00 not found.

这表示它不能找到控制这个设备的模块。无需关注这一内容,因为有些设备没有适用于它们的内核驱动。

Determining the Correct Module from Scratch

有时候我们没有机会获取到能够在系统上正常运行的内核,并依此来获取某个硬件设备需要的这却模块。或者你在系统上添加了一个新的硬件,需要确定要配置的内核配置选项来令硬件能够正确工作。本节将会帮助你去确定如何查找到那个配置选项,来令硬件工作起来。

最简单确定哪一个驱动控制这个新设备的方法就是编译源码树中的所有驱动作为模块,并令 udev 启动进程为设备匹配驱动。一旦完成,就可以使用前面介绍过的方法来配置内核选项了。

如果你不想编译所有驱动,或者某种原因这个方法不能使用,就需要更多的工作来确定这个设备的合适驱动了。下面的步骤是负载的,并需要深挖驱动源码。不要害怕,这会帮助你更好的了解你的硬件以及内核源码。

涉及的匹配步骤将会因你使用的设备的不同而不同。我们将会讨论两个最常用的设备: PCI 以及 USB 设备。其中描述的方法也适用于其他类型的设备。

有一个十分重要的点是,内核系统要能够查找到系统上所有的文件系统,最重要的一个为根文件系统。我们将在后面详细讨论。

PCI Devices

PCI 设备按照供应商 ID 以及设备 ID 区分;每一个供应商 ID 与设备 ID 的组合将需要一个唯一驱动。

对于本例,让我们使用一张 PCI 网卡,不能在当前的内核上正常工作。本例列举的内容可能与你的情况不同,使用不同的 PCI 设备与总线 ID,但是涉及的步骤总是相似的。

首先,查找系统上没有正常工作的的 PCI 设备。使用 lspci 命令查看系统上所有的 PCI 设备。因为我们只关注以太网 PCI 设备,所以我们会限定在以太网相关的 PCI 设备:

$ lspci | grep -i ethernet
06:04.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+ (rev 10)

这是我们希望能正常工作的设备。上面命令的打印信息的投几个位展示了这个 PCI 的总线 ID06:04.0。这个值是我们将要在 sysfs 中查找关于这个设备更多信息时使用的数据。

进入到 sysfsPCI 设备列表中,查看它们的名称:

$ cd /sys/bus/pci/devices/
$ ls
0000:00:00.0 0000:00:1d.0 0000:00:1e.0 0000:00:1f.3 0000:06:03.3
0000:00:02.0 0000:00:1d.1 0000:00:1f.0 0000:06:03.0 0000:06:03.4
0000:00:02.1 0000:00:1d.2 0000:00:1f.1 0000:06:03.1 0000:06:04.0
0000:00:1b.0 0000:00:1d.7 0000:00:1f.2 0000:06:03.2 0000:06:05.0

PCI 设备的内核编号以 0000: 开头,这不会展示在 lspci 程序打印的信息中。因此要向 06:04.0 前添加上 0000: 并进入到这个目录中:

$ cd 0000:06:04.0

在这个目录中,我们希望获取到供应商与设备文件名:

$ cat vendor
0x10ec
$ cat device
0x8139

这两个分别是这个 PCI 设备的供应商 ID 以及设备 ID。内核使用这些值来完成设备与驱动的匹配。PCI 驱动告知内核它们所支持的供应商与设备 ID,这样内核就直到如何将驱动与设备正确绑定。

现在我们知道了这个 PCI 设备的供应商与产品 ID 了,我们需要查找支持这个设备的合适的内核驱动。回到内核源码目录:

$ cd ~/linux/linux-2.6.17.8/

最常见的 PCIID 位置在内核源码的 include/linux/pci_ids.h 头文件中。在其中查找是否有这个内容:

$ grep -i 0x10ec include/linux/pci_ids.h
#define PCI_VENDOR_ID_REALTEK 0x10ec

上面的定义信息中,PCI_VENDOR_ID_REALTEK 可能就是内核驱动用来支持这个设备厂商的关键字。

为了保险起见,我们也查看一下设备 ID,它也在其中做了定义:

$ grep -i 0x8139 include/linux/pci_ids.h
#define PCI_DEVICE_ID_REALTEK_8139 0x8139

这个定义在后面会用到。

现在,让我们查找涉及到这个供应商的驱动源文件:

$ grep -Rl PCI_VENDOR_ID_REALTEK *
include/linux/pci_ids.h
drivers/net/r8169.c
drivers/net/8139too.c
drivers/net/8139cp.c

我们需要看的就是 drivers/net 目录下的这几个源文件。

在编辑器中打开这些文件,查找 PCI_VENDOR_ID_REALTEK 关键字。在 drivers/net/r8169.c 中具有如下代码:

static struct pci_device_id rt18169_pci_tbl[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8169), },
    { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8129), },
    { PCI_DEVICE(PCI_VENDOR_ID_DLINK,   0x4300), },
    { PCI_DEVICE(0x16ec,                0x0116), },
    { PCI_VENDOR_ID_LINKSYS,            0x1032, PCI_ANY_ID, 0x0024, },
    { 0, },
};

所有的 PCI 驱动包含系列支持的设备。这个列表被保存在 pci_device_id 结构体值中,就像上面这个例子中展示的一样。这是我们需要查看的内容,用以确定我们的设备是否被驱动支持。我们的设备值为 0x8139 但是匹配到的值确实 0x81690x8129。因此这个驱动不能支持我们的设备。

继续查看下面的文件,/drivers/net/8139too.c 我们查看到 PCI_VENDOR_ID_REALTEK 字串出现在下面的代码中:

if (pdev->vendor == PCI_VENDOR_ID_REALTEK &&
	pdev->device == PCI_DEVICE_ID_REALTEK_8139 && pci_rev >= 0x20) {
	dev_info(&pdev->dev, "This (id %04x:%04x rev %02x) is an enhanced 8139C+ chip\n", pdev->vendor, pdev->device, pci_rev);
	dev_info(&pdev->dev, "Use the \"8139cp\" driver for improved performance and stability.\n");
}

可以看到这个源文件中的源码提示我们可以使用 PCI_DEVICE_ID_REALTEK_8139 这个关键字作为线索。

我们接着看 drivers/net/8139cp.c 源文件:

static struct pci_device_id cp_pci_tbl[] = {
	{ PCI_VENDOR_ID_REALTEK, PCI_DEVICE_ID_REALTEK_8139,
	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{ PCI_VENDOR_ID_TTTECH, PCI_DEVICE_ID_TTTECH_MC322,
	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{ },
};
MODULE_DEVICE_TABLE(pci, cp_pci_tbl);

这里有使用我们的供应商与设备 ID 的组合在 pci_device_id 中出现。这个驱动应该会支持我们的设备。

现在,我们有了驱动名,我们可以参照本章前面小节介绍的内容,来查找要使能的合适的内核配置选项。

作为总结,下面列出了找到某个 PCI 设备驱动的步骤:

  1. 使用 lspci 命令,查找到这个设备所处的 PCI 总线

  2. 进入到 /sys/bus/pci/devices/0000:bus_id 目录中

  3. 读取 PCI 设备的供应商与设备 ID

  4. 进入到内核源码中,去 include/linux/pci_ids.h 中去查找供应商与设备 ID 的信息

  5. 查找在 pci_device_id 结构体中共同定义有供应商与设备 ID 的驱动

  6. 查看 Makefile 中带有 CONFIG_ 的信息

    $ find -type f -name Makefile | xargs grep DRIVER_NAME
    
  7. 在内核配置系统中查找对应的配置信息,并进行配置

USB Devices

USB 设备查找驱动类似于为 PCI 设备查找驱动。只在查找总线号时有所区别。

在本例中,让我们为一个无线 USB 设备查找驱动。就像在 PCI 设备例子中的一样,本例中的细节会与你遇到的情况有所不同,但是涉及的步骤是相似的。

就像在 PCI 设备中描述的一样,首先要找到 USB 设备的总线号。可以使用 usbutils 包中提供的 lsusb 程序来完成。

lsusb 程序展示所有连接到系统上的 USB 设备:

$ lsusb
Bus 002 Device 003: ID 045e:0023 Microsoft Corp. Trackball Optical
Bus 002 Device 001: ID 0000:0000
Bus 005 Device 003: ID 0409:0058 NEC Corp. HighSpeed Hub
Bus 005 Device 001: ID 0000:0000
Bus 004 Device 003: ID 157e:300d
Bus 004 Device 002: ID 045e:001c Microsoft Corp.
Bus 004 Device 001: ID 0000:0000
Bus 003 Device 001: ID 0000:0000
Bus 001 Device 001: ID 0000:0000

具有 ID0000:0000 的设备可以忽略,它们是 USB 主控制器,用来驱动总线自身的。将它们剔除之后,剩下四台设备:

$ /usr/sbin/lsusb | grep -v 0000:0000
Bus 002 Device 003: ID 045e:0023 Microsoft Corp. Trackball Optical
Bus 005 Device 003: ID 0409:0058 NEC Corp. HighSpeed Hub
Bus 004 Device 003: ID 157e:300d
Bus 004 Device 002: ID 045e:001c Microsoft Corp.

因为 USB 设备移除起来是十分方便的,因此,我们插拔我们的 USB 设备,再来查找一下:

$ /usr/sbin/lsusb | grep -v 0000:0000
Bus 002 Device 003: ID 045e:0023 Microsoft Corp. Trackball Optical
Bus 005 Device 003: ID 0409:0058 NEC Corp. HighSpeed Hub
Bus 004 Device 002: ID 045e:001c Microsoft Corp.

这样我们就确定了我们的 USB 设备了:

Bus 004 Device 003: ID 157e:300d

如果我们重新插上设备再次使用 lsusb,可以看到这个设备号发生了变化:

$ /usr/sbin/lsusb | grep 157e
Bus 004 Device 004: ID 157e:300d

这是因为 USB 设备号并不固定,而是在设备插入时候变化。稳定的内容是供应商与产品 ID,这里供应商 ID157e,设备 ID300d

就像 PCI 例中阐述的一样,我们将会在内核源码中查找 USB 供应商与产品 ID,并以此来查找控制这个设备的合适驱动。不幸的是,没有单个文件像 PCI 设备中那样包含所有的 USB 供应商 ID ,因此我们只能搜索所有的内核文件了:

$ grep -i -R -l 157e drivers/*
drivers/atm/pca200e.data
drivers/atm/pca200e_ecd.data
drivers/atm/sba200e_ecd.data
drivers/net/wireless/zd1211rw/zd_usb.c
drivers/scsi/ql1040_fw.h
drivers/scsi/ql1280_fw.h
drivers/scsi/qlogicpti_asm.c

我们知道我们的 USB 设备是一个无线设备,不是一个 ATMSCSI 设备,因此,我们可以忽略在 ztm 以及 scsi 目录中的内容。那么就剩下 drivers/net/wireless/zd1211rw/zd_usb.c 文件了。

zd_usb.c 源文件中字串 157e 包含在下面一簇代码中:

static struct usb_device_id usb_ids[] = {
	/* ZD1211 */
	{ USB_DEVICE(0x0ace, 0x1211), .driver_info = DEVICE_ZD1211 },
	{ USB_DEVICE(0x07b8, 0x6001), .driver_info = DEVICE_ZD1211 },
	{ USB_DEVICE(0x126f, 0xa006), .driver_info = DEVICE_ZD1211 },
	{ USB_DEVICE(0x6891, 0xa727), .driver_info = DEVICE_ZD1211 },
	{ USB_DEVICE(0x0df6, 0x9071), .driver_info = DEVICE_ZD1211 },
	{ USB_DEVICE(0x157e, 0x300b), .driver_info = DEVICE_ZD1211 },
	/* ZD1211B */
	{ USB_DEVICE(0x0ace, 0x1215), .driver_info = DEVICE_ZD1211B },
	{ USB_DEVICE(0x157e, 0x300d), .driver_info = DEVICE_ZD1211B },
	{}
};

PCI 驱动一样,USB 驱动也告知内核它们支持的设备有哪些,来让内核将驱动与设备进行绑定。这是通过 usb_device_id 结构体变量来实现的。在这个列表中有类似下面这样驱动支持的供应商与产品 ID:

{ USB_DEVICE(0x157e, 0x300b), .driver_info = DEVICE_ZD1211 },

这一行展示了我们使用的 USB 设备的供应商与产品 ID,表示本驱动支持这个 USB 设备。

一旦确定了这个设备需要的驱动名,后面的工作就是前面介绍过的内容了。

总结一下,对于 USB 设备的驱动选项配置过程如下:

  1. 使用 lsusb 来查看 USB 供应商以及产品 ID

  2. 在内核源码中查找包含供应商与产品 ID 的驱动 usb_device_id

  3. 在内核顶层 Makefile 中查找驱动对应的选项

    $ find -type f -name Makefile | xargs grep DRIVER_NAME
    
  4. 配置选项

Root Filesystem

根文件系统是运行系统启动的主文件系统分区。它包含发布版本的所有初始化程序,并包含设备的整个系统配置。简言之,它十分重要,必须能够在启动时被内核查找到,来使内核正常运行。

如果你的新配置内核在启动时挂掉,并有下面的报错:

VFS: Cannot open root device hda2 (03:02)
Please append a correct "root=" boot option
Kernal panic: VFS: Unable to mount root fs on 03:02

那么就是没能成功找到根文件系统。如果你在启动时没有使用一个虚拟盘 (ramdisk) 镜像,那么建议你将文件系统的根分区,盘控制器到内核中,而不是让它们作为模块。如果你在启动时使用虚拟盘 (ramdisk) 镜像,你需要将这些分区编译为模块。

如何确定你在启动时使用的是否是虚拟盘呢?在第五章中我们提到了使用安装脚本来安装内核,或者自己手动安装内核。如果你使用发布版本的安装脚本,那么就可能是使用的虚拟盘。如果你自己手动安装,那么可能就不是。

下面的小节会介绍如何在启动时让内核找到根文件系统。

Filesystem type

首先,根分区使用的文件系统类型是需要确定的。可以查看 mount 命令的输出来确定:

$ mount | grep " / "
/dev/sda2 on / type ext3 (rw,noatime)

我们感兴趣的是文件系统的类型,它位于 type 关键字后面。在本例中,类型为 ext3。这是根分区使用的文件系统类型。进入到内核配置系统,确保这个文件系统类型已使能。

Disk controller

前面我们使用了 mount 命令输出了一些信息,输出行的第一个信息展示了根文件系统是挂载在那个块设备上的。在本例中,是在 /dev/sda2 上。现在文件系统已经在内核中做了配置,现在需要保证这个块设备能够正常的工作。为了查找到这个块设备需要的驱动,需要再次查看 sysfs

所有 sysfs 中的块设备,要么是在 /sys/block 要么在 /sys/class/block 中,这依赖于你使用的内核版本。无论在哪个位置,块设备都是树状结构,不同的分区作为主设备的子节点:

$ tree -d /sys/block/ | egrep "hd|sd"
|-- hdc
|-- hdd
`-- sda
	|-- sda1
	|-- sda2
	|-- sda3

基于我们之前使用 mount 看到的信息,你需要确保 sd2 设备做了正确配置。因为这是一个分区 (盘分区是有编号的,主块设备是没有编号的),完整的 sda 设备需要被配置 (没有主块设备,显然对块设备的分区是没有意义的)。

sda 块设备就像我们之前看到的网络设备一样。在设备的目录中具有符号链接,指向控制这个块设备的逻辑设备:

$ ls -l /sys/block/sda
...
device -> ../../devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0
...

现在需要查看 sysfs 中的信息来确定哪个驱动控制着这个设备:

$ ls -l /sys/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0
...
driver -> ../../../../../../bus/scsi/drivers/sd
...

我们看到,SCSI 盘控制器驱动着这个设备工作。因此我们需要配置 SCSI 配置选项。

继续在 sysfs 目录链中尝试查找哪个驱动控制着这个硬件:

$ ls -l /sys/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0
...

在本级目录中没有称为 driver 的链接,因此去向上一层:

$ ls -l /sys/devices/pci0000:00/0000:00:1f.2/host0
...

依旧没有 driver,我们继续向上:

$ ls -l /sys/devices/pci0000:00/0000:00:1f.2
...
driver -> ../../../bus/pci/drivers/ata_piix
...

终于,这就是我们需要在内核配置中需要使能的盘控制器。

因此我们为了让根文件系统能够正常工作,我们需要使能 ext3sd、以及 ata_piix 驱动能够在内核中正常工作。

Helper Script

在本章开始时,我们提到,sysfs 随着内核版本的改变在不断变化。这里有一个脚本来确定系统中的设备节点需要的内核驱动于模块的模块名。

比如,它可以帮你完成 sda 块设备的需要的驱动查找:

$ . get-drivers.sh sda
looking at sysfs device: /sys/devices/pci0000:00/0000:00:1f.2/host0/
target0:0:0/0:0:0:0
found driver: sd
found driver: ata_piix

我们也可以用它来查找更加复杂的设备的合适的驱动,比如 USB-to-serial 设备:

$ . get-drivers.sh ttyUSB0
looking at sysfs device: /sys/devices/pci0000:00/0000:00:1d.3/usb4/4-2/4-2.
3/4-2.3:1.0/ttyUSB0
found driver: pl2303 from module: pl2303
found driver: pl2303 from module: pl2303
found driver: usb from module: usbcore
found driver: usb from module: usbcore
found driver: usb from module: usbcore
found driver: uhci_hcd from module: uhci_hcd

你可以在本书的 How to Contact Us 小节中提供的网站上下载这个脚本。

posted @ 2022-08-28 22:23  ArvinDu  阅读(127)  评论(0编辑  收藏  举报