首页 > 系统相关 >Linux工作原理3设备

Linux工作原理3设备

时间:2023-05-31 21:23:15浏览次数:42  
标签:dev 内核 Linux 原理 SCSI udevd 设备

本章是对正常运行的Linux系统中内核提供的设备基础设施的基本考察。纵观Linux的历史,在内核如何向用户展示设备方面已经有了许多变化。我们将从传统的设备文件系统开始,看看内核如何通过sysfs提供设备配置信息。我们的目标是能够提取系统中的设备信息,以便了解一些基本的操作。后面的章节将更详细地介绍与特定类型设备的交互。
了解当出现新设备时,内核如何与用户空间进行交互是很重要的。udev系统使用户空间的程序能够自动配置和使用新设备。你将看到内核如何通过udev向用户空间进程发送消息的基本工作原理,以及该进程如何处理这些消息。

3.1 设备文件

操作Unix系统中的大多数设备是很容易的,因为内核将许多设备的I/O接口以文件形式呈现给用户进程。这些设备文件有时被称为设备节点。除了程序员使用常规的文件操作来处理设备外,一些设备也可以被像cat这样的标准程序访问。然而,你能用文件接口做的事情是有限的,所以不是所有的设备或设备功能都能用标准的文件I/O访问。

设备文件在/dev目录下,运行ls /dev可以看到/dev中的相当多的文件。那么,你是如何处理设备的呢?


$ echo blah blah > /dev/null

就像其他带有重定向输出的命令一样,这个命令将标准输出中的内容发送到文件中。然而,这个文件是/dev/null,是一个设备,所以内核绕过了通常的文件操作,对写入这个设备的数据使用了设备驱动程序。在/dev/null的情况下,内核只是接受了输入的数据并将其丢弃。
要识别一个设备并查看其权限,可以使用ls -l。下面是一些例子:

$ ls -l
brw-rw---- 1 root disk 8, 1 Sep 6 08:37 sda1
crw-rw-rw- 1 root root 1, 3 Sep 6 08:37 null
prw-r-r-- 1 root root 0 Mar 3 19:17 fdata
srw-rw-rw- 1 root root 0 Dec 18 07:43 log

注意每一行的第一个字符(文件模式的第一个字符)。如果这个字符是b、c、p或s,则该文件是一个设备。这些字母分别代表块、字符、管道和套接字:

  • 块设备
    程序以固定的块来访问块设备的数据。前面例子中的sda1是一个磁盘设备,是块设备的一种类型。磁盘可以很容易地被分割成数据块。因为块设备的总大小是固定的,并且容易索引,程序在内核的帮助下可以快速随机访问设备中的任何块。

  • 字符设备
    字符设备与数据流一起工作。你只能从字符设备中读取字符或向字符设备中写入字符,就像前面用/dev/null演示的那样。字符设备没有大小之分;当你从设备中读出或写入时,内核通常对它进行读或写操作。直接连接到你的计算机上的打印机是由字符设备表示的。值得注意的是,在字符设备的交互过程中,内核在将数据传递给设备或进程后,不能备份和重新检查数据流。

  • 管道设备
    命名的管道就像字符设备,在I/O流的另一端是另一个进程,而不是内核驱动。

  • 套接字设备
    套接字是特殊用途的接口,经常用于进程间通信。它们经常在/dev目录之外被发现。套接字文件代表Unix域套接字;你将在第10章中了解更多关于这些套接字的信息。
    在来自ls -l的块和字符设备的文件列表中,日期前面的数字是主要和次要的设备号,内核用它来识别设备。类似的设备通常有相同的主设备号,比如sda3和sdb1(都是硬盘分区)。

注意
不是所有的设备都有设备文件,因为块和字符设备的I/O接口不是在所有情况下都合适。例如,网络接口没有设备文件。理论上,使用单一的字符设备与网络接口交互是可能的,但是由于这很困难,内核提供了其他的I/O接口。

3.2 sysfs设备路径

传统的Unix的/dev目录是一种方便的方式,用户进程可以引用和连接内核支持的设备,但这也是一种非常简单的方案。在/dev中的设备名称告诉你关于该设备的一些情况,但通常不够有用。另一个问题是,内核是按照找到设备的顺序来分配设备的,所以设备在重启之间可能有不同的名字。

为了提供基于实际硬件属性的附加设备的统一视图,Linux内核通过文件和目录系统提供sysfs接口。设备的基本路径是/sys/devices。例如,位于/dev/sda的SATA硬盘在sysfs中可能有如下路径:


/sys/devices/pci0000:00/0000:00:17.0/ata3/host0/target0:0:0/0:0:0:0/block/sda

你可以看到,与/dev/sda文件名相比,这个路径相当长,它也是一个目录。但你不能真正比较这两个路径,因为它们有不同的目的。/dev文件使用户进程能够使用设备,而/sys/devices路径是用来查看信息和管理设备的。如果你列出设备路径的内容,比如前面的那个,你会看到类似下面的内容:


alignment_offset  discard_alignment  holders   removable  size       uevent
bdi               events             inflight  ro         slaves
capability        events_async       power     sda1       stat
dev               events_poll_msecs  queue     sda2       subsystem
device            ext_range          range     sda5       trace

这里的文件和子目录主要是供程序而不是人阅读的,但是你可以通过查看/dev文件这样的例子来了解它们包含和代表的内容。在这个目录下运行cat dev会显示数字8:0,这恰好是/dev/sda的主设备号和次设备号。
在/sys目录下有一些快捷方式。例如,/sys/block应该包含一个系统上所有可用的块设备。然而,这些只是符号链接;你应该运行ls -l /sys/block来显示真正的sysfs路径。
要在/dev中找到一个设备的sysfs位置可能很困难。使用如下的udevadm命令来显示路径和其他一些有趣的属性:


$ udevadm info --query=all --name=/dev/sda

你会在第3.5节找到更多关于udevadm和整个udev系统的细节。

3.3 dd和设备

当你在处理块和字符设备时,dd程序是非常有用的。它的唯一功能是从输入文件或流中读出,然后写到输出文件或流中,在这个过程中可能会进行一些编码转换。对于块设备来说,dd的特别有用的功能是,你可以在文件的中间处理一大块数据,而忽略之前或之后的内容。

警告:
dd的功能非常强大,所以当你运行它时,请确保你知道你在做什么。如果不小心犯了错误,很容易损坏设备上的文件和数据。如果你不确定它将会做什么,通常可以将输出写入一个新的文件。
dd以固定大小的块复制数据。下面是如何在字符设备上使用dd,利用一些常见的选项:


$ dd if=/dev/zero of=new_file bs=1024 count=1

正如你所看到的,dd的选项格式与大多数其他Unix命令的选项格式不同;它是基于旧的IBM工作控制语言(JCL)风格。你不是用破折号(-)来表示一个选项,而是用等号(=)来命名一个选项并设置其值。前面的例子从/dev/zero复制了一个1,024字节的块(一个连续的零字节流)到new_file。
这些是重要的dd选项:

  • if=file 输入文件。默认是标准输入。
  • of=file 输出文件。默认是标准输出。
  • bs=size 区块大小。dd一次读写这么多字节的数据。为了缩写大块的数据,你可以用b和k分别表示512和1024个字节。因此,前面的例子可以读取bs=1k,而不是bs=1024。
  • ibs=size, obs=size 输入和输出块的大小。如果你能对输入和输出使用相同的块大小,请使用bs选项;如果不能,请对输入和输出分别使用ibs和obs。
  • count=num 要复制的块的总数。当处理巨大的文件时,或者处理提供无尽数据流的设备,比如/dev/zero,你希望dd在固定的点上停止;否则,你可能会浪费大量的磁盘空间、CPU时间,或者两者都浪费。使用count和跳过参数,从大文件或设备中复制一小段。
  • skip=num 跳过输入文件或数据流中的第一个num块,不把它们复制到输出。

3.4 设备名称摘要

有时很难找到设备的名称(例如,在给磁盘分区时)。这里有几个方法可以找出它是什么:

  • 用udevadm查询udevd(见第3.5节)。

  • 在/sys目录下寻找设备。

  • 从journalctl -k命令(打印内核信息)或内核系统日志(见第7.1节)的输出中猜测其名称。这个输出可能包含了对你系统中设备的描述。

  • 对于系统已经可见的磁盘设备,你可以检查mount命令的输出。

  • 运行cat /proc/devices来查看你的系统目前有驱动的块和字符设备。每一行都包括编号和名称。数字是设备的主要编号,如第3.1节所述。如果你能从名称中猜出设备,在/dev中寻找具有相应主要编号的字符或块设备,你就找到了设备文件。

在这些方法中,只有第一个方法是可靠的,但它确实需要udev。如果你遇到udev不可用的情况,可以尝试其他方法,但要记住,内核可能没有适合你的硬件的设备文件。
下面的章节列出了最常见的Linux设备和它们的命名规则。

3.4.1 硬盘: /dev/sd*

大多数连接到当前Linux系统的硬盘对应于带有sd前缀的设备名,比如/dev/sda,/dev/sdb,等等。这些设备代表整个磁盘;内核为磁盘上的分区制作单独的设备文件,如/dev/sda1和/dev/sda2。
这个命名规则需要解释一下。名称中的sd部分代表SCSI磁盘。小型计算机系统接口(SCSI)最初是作为一种硬件和协议标准开发的,用于磁盘和其他外围设备等设备之间的通信。虽然传统的SCSI硬件在大多数现代机器中没有使用,但由于SCSI协议的适应性,它无处不在。例如,USB存储设备使用它进行通信。SATA(串行ATA,PC上常见的存储总线)磁盘上的情况要复杂一些,但Linux内核在与它们交谈时,仍然在一定程度上使用SCSI命令。
要列出你系统上的SCSI设备,可以使用一个工具来行走由sysfs提供的设备路径。其中一个最简洁的工具是lsscsi。当你运行它时,你可以看到以下内容:


$ lsscsi
[0:0:0:0]1  disk2  ATA     WDC WD3200AAJS-2  01.0  /dev/sda3
[2:0:0:0]    disk    FLASH   Drive UT_USB20    0.00  /dev/sdb

第一列标识了系统中设备的地址,第二列描述了它是什么类型的设备,最后一列3出在哪里可以找到设备文件。其他的都是厂商信息。
Linux按照其驱动程序遇到设备的顺序将设备分配给设备文件。所以,在前面的例子中,内核首先找到了磁盘,其次才是闪存驱动器。
不幸的是,当你重新配置硬件时,这种设备分配方案历来会引起问题。举例来说,你有一个有三个磁盘的系统: /dev/sda,/dev/sdb,和/dev/sdc。如果/dev/sdb爆炸了,你必须把它移走,以便机器能够重新工作,那么以前的/dev/sdc就会移到/dev/sdb上,而不再有/dev/sdc了。如果你直接参考fstab文件中的设备名称(见第4.2.8节),你就必须对该文件做一些修改,以便使事情(大部分)恢复正常。为了解决这个问题,许多Linux系统使用通用唯一标识符(UUID;见第4.2.4节)和/或逻辑卷管理器(LVM)来稳定磁盘设备映射。
关于如何在Linux系统上使用磁盘和其他存储设备,本文的讨论几乎没有触及表面。关于使用磁盘的更多信息,见第4章。在本章的后面,我们将研究SCSI支持在Linux内核中是如何工作的。

3.4.2 虚拟磁盘: /dev/xvd, /dev/vd

一些磁盘设备为虚拟机进行了优化,如AWS实例和VirtualBox。Xen虚拟化系统使用/dev/xvd前缀,而/dev/vd是一种类似的类型。

3.4.3 非易失性内存设备: /dev/nvme*

一些系统现在使用非易失性内存快车(NVMe)接口来与某些类型的固态存储进行对话。在Linux中,这些设备显示在/dev/nvme*。你可以使用nvme list命令来获得你系统上这些设备的列表。

3.4.4 设备映射器: /dev/dm-, /dev/mapper/

在一些系统上,比磁盘和其他直接块存储更高级别的是LVM,它使用一个叫做设备映射器的内核系统。如果你看到以/dev/dm-开头的块设备和/dev/mapper的符号链接,你的系统可能使用了它。你将在第4章中了解这一切。

3.4.5 CD和DVD驱动器: /dev/sr*

Linux将大多数光存储驱动器识别为SCSI设备/dev/sr0、/dev/sr1,等等。然而,如果驱动器使用旧的接口,它可能会显示为一个PATA设备,如下所述。/dev/sr*设备是只读的,它们只用于从磁盘上读取。对于光学设备的写入和重写能力,你将使用 "通用 "SCSI设备,如/dev/sg0。

3.4.6 PATA硬盘: /dev/hd*

PATA(Parallel ATA)是一种较早的存储总线。Linux块设备/dev/hda、/dev/hdb、/dev/hdc和/dev/hdd在旧版本的Linux内核和旧硬件上很常见。这些是基于接口0和1的设备对的固定分配。 有时,你可能会发现SATA驱动器被识别为这些磁盘之一。这意味着该SATA驱动器在兼容模式下运行,这阻碍了性能。检查你的BIOS设置,看看你是否可以将SATA控制器切换到其原始模式。

3.4.7 终端: /dev/tty, /dev/pts/, 和 /dev/tty

终端是在用户进程和I/O设备之间移动字符的设备,通常用于向终端屏幕输出文字。终端设备接口可以追溯到很久之前,当时的终端是基于打字机的设备,很多都是连接在一台机器上的。
大多数终端是伪终端设备,是理解真正终端的I/O特性的仿真终端。内核不是与真正的硬件对话,而是将I/O接口呈现给软件,例如你可能在其中输入大部分命令的shell终端窗口。
两个常见的终端设备是/dev/tty1(第一个虚拟控制台)和/dev/pts/0(第一个伪终端设备)。/dev/pts目录本身是一个专门的文件系统。
/dev/tty设备是当前进程的控制终端。如个程序当前正在从终端读写,这个设备就是该终端的同义词。进程不需要连接到终端。

  • 显示模式和虚拟控制台

Linux有两种主要的显示模式:文本模式和图形模式(第14章介绍了使用这种模式的窗口系统)。尽管Linux系统传统上是以文本模式启动的,但现在大多数发行版使用内核参数和临时图形显示机制(bootsplashes,如plymouth)来完全隐藏系统启动时的文本模式。在这种情况下,系统会在启动过程接近尾声时切换到全图形模式。
Linux支持虚拟控制台来复用显示。每个虚拟控制台可以在图形或文本模式下运行。当处于文本模式时,你可以通过ALT功能键组合在控制台之间进行切换--例如,ALT-F1会带你到/dev/tty1,ALT-F2会到/dev/tty2,以此类推。许多这样的虚拟控制台可能被运行登录提示的getty进程所占据,如第7.4节所述。

在图形模式下使用的虚拟控制台略有不同。除非被指示使用一个特定的虚拟控制台,否则图形环境会接管空闲的虚拟控制台,而不是从初始配置中获得一个虚拟控制台的分配。例如,如果你有getty进程在tty1和tty2上运行,新的图形环境会占用tty3。此外,一旦进入图形模式,你通常必须按CTRL-ALT-功能键组合来切换到另一个虚拟控制台,而不是更简单的ALT-功能键组合。
所有这些的结果是,如果你想在系统启动后看到你的文本控制台,按CTRL-ALT-F1。要返回到图形环境,按ALT-F2、ALT-F3,以此类推,直到你进入图形环境。

注意:有些发行版在图形模式下使用tty1。在这种情况下,你将需要尝试其他控制台。

如果你在切换控制台时由于输入机制的故障或其他情况而遇到麻烦,你可以尝试用chvt命令强迫系统改变控制台。例如,要切换到tty1,以root身份运行以下命令:


# chvt 1

3.4.8 串行端口: /dev/ttyS, /dev/ttyUSB, /dev/ttyACM*

较早的RS-232类型和类似的串行端口被表示为真正的终端设备。你不能在命令行上对串口设备做很多事情,因为有太多的设置需要担心,比如波特率和流量控制,但是你可以使用screen命令,通过添加设备路径作为参数来连接到终端。你可能需要该设备的读写权限;有时你可以通过将自己添加到特定的组(如dialout)来实现。
在Windows上被称为COM1的端口是/dev/ttyS0;COM2是/dev/ttyS1;以此类推。插入式USB串行适配器显示为USB和ACM,名称为/dev/ttyUSB0、/dev/ttyACM0、/dev/ttyUSB1、/dev/ttyACM1,等等。
一些涉及到串行端口的最有趣的应用是基于微控制器的板子,你可以把它插入你的Linux系统进行开发和测试。例如,你可以通过USB串行接口访问CircuitPython板的控制台和读-评-印循环。你所需要做的就是插上一个,寻找设备(通常是/dev/ttyACM0),然后用屏幕连接到它。

3.4.9 并行端口: /dev/lp0和/dev/lp1

单向并口设备/dev/lp0和/dev/lp1代表着一种接口类型,在很大程度上已经被USB和网络所取代,在Windows中对应于LPT1:和LPT2:。你可以用cat命令将文件(如要打印的文件)直接发送到并口,但你可能需要在之后给打印机一个额外的进纸或复位。像CUPS这样的打印服务器在处理与打印机的交互方面要好得多。
双向的并行端口是/dev/parport0和/dev/parport1。

3.4.10 音频设备: /dev/snd/*, /dev/dsp, /dev/audio, 以及更多

Linux有两套音频设备。有独立的设备用于高级Linux声音架构(ALSA)系统接口和较早的开放声音系统(OSS)。ALSA设备在/dev/snd目录下,但很难直接使用它们。使用ALSA的Linux系统支持OSS的后向兼容设备,如果目前加载了OSS的内核支持。
对OSS的dsp和音频设备可以进行一些基本的操作。例如,计算机会播放你发送到/dev/dsp的任何WAV文件。然而,由于频率不匹配,硬件可能做不到你期望的那样。此外,在大多数系统上,该设备往往在你登录后就开始忙碌。

注意:由于涉及到许多层次,Linux的声音是一个混乱的主题。我们只谈了内核级的设备,但通常还有用户空间的服务器,如 pulseaudio,管理来自不同来源的音频,作为声音设备和其他用户空间进程之间的中介。

3.4.11 创建设备文件

在任何合理的最近的Linux系统上,你都不会创建自己的设备文件;它们是由devtmpfs和udev创建的(见第3.5节)。然而,看看如何创建设备文件是很有意义的,在罕见的情况下,你可能需要创建命名的管道或套接字文件。
mknod命令创建设备。你必须知道设备的名称以及它的主号和次号。例如,创建/dev/sda1只需使用下面的命令:


# mknod /dev/sda1 b 8 1

b 8 1指定了一个主数为8、次数为1的块设备。对于字符或命名的管道设备,使用c或p而不是b(对于命名的管道,省略主要和次要数字)。
在旧版本的Unix和Linux中,维护/dev目录是一个挑战。随着每一次重要的内核升级或驱动程序的增加,内核可以支持更多种类的设备,这意味着将有一组新的主要和次要数字被分配给设备文件名。为了解决这个维护难题,每个系统都有一个MAKEDEV程序,在/dev中创建设备组。当你升级你的系统时,你将试图找到MAKEDEV的更新,然后运行它以创建新的设备。
这个静态的系统变得很难看,所以一个替换是必要的。解决这个问题的第一个尝试是devfs,一个内核空间的/dev实现,包含了当前内核支持的所有设备。然而,有一些限制,这导致了udev和devtmpfs的发展。

3.5 udev

我们已经谈到了内核中不必要的复杂性是很危险的,因为你太容易引入系统的不稳定性。设备文件管理就是一个例子:你可以在用户空间创建设备文件,那么你为什么要在内核中这样做?Linux内核可以在检测到系统中的新设备时(例如,当有人安装了U盘时)向udevd的用户空间进程发送通知。这个udevd进程可以检查新设备的特性,创建设备文件,然后执行任何设备初始化。

注意:你几乎肯定会看到udevd以systemd-udevd的形式在你的系统上运行,因为它是你将在第六章看到的启动机制的一部分。

这就是理论。不幸的是,这种方法有一个问题--设备文件在启动过程的早期是必需的,所以udevd也必须提前启动。但是为了创建设备文件,udevd不能依赖于任何它应该创建的设备,它需要非常快速地执行其初始启动,这样系统的其他部分就不会因为等待udevd的启动而被耽搁。

3.5.1 devtmpfs

devtmpfs文件系统是为了解决启动过程中的设备可用性问题而开发的(关于文件系统的更多细节,见第4.2节)。这个文件系统类似于较早的devfs支持,但有所简化。内核在必要时创建设备文件,但它也会通知udevd新的设备是可用的。收到这个信号后,udevd不会创建设备文件,但它会执行设备初始化,同时设置权限并通知其他进程新设备可用。此外,它在/dev中创建一些符号链接,以进一步识别设备。你可以在/dev/disk/by-id目录下找到例子,每个连接的磁盘都有一个或多个条目。

例如,考虑典型的磁盘(连接在/dev/sda)和它在/dev/disk/by-id中的分区的链接:


$ ls -l /dev/disk/by-id
lrwxrwxrwx 1 root root  9 Jul 26 10:23 scsi-SATA_WDC_WD3200AAJS-_WD-WMAV2FU80671 -> ../../sda
lrwxrwxrwx 1 root root 10 Jul 26 10:23 scsi-SATA_WDC_WD3200AAJS-_WD-WMAV2FU80671-part1 ->
../../sda1
lrwxrwxrwx 1 root root 10 Jul 26 10:23 scsi-SATA_WDC_WD3200AAJS-_WD-WMAV2FU80671-part2 ->
../../sda2
lrwxrwxrwx 1 root root 10 Jul 26 10:23 scsi-SATA_WDC_WD3200AAJS-_WD-WMAV2FU80671-part5 ->
../../sda5

udevd进程按接口类型命名链接,然后按制造商和型号信息、序列号和分区(如果适用)命名。

注意:devtmpfs中的 "tmp "表示文件系统驻留在主内存中,具有用户空间进程的读/写能力;这个特性使udevd能够创建这些符号链接。我们将在第4.2.12节看到更多细节。

但udevd如何知道要创建哪些符号链接,以及如何创建它们?下一节将描述udevd是如何工作的。然而,你不需要知道这些或本章中的任何其他材料来继续阅读本书。事实上,如果这是你第一次研究Linux设备,我们强烈建议你跳到下一章,开始学习如何使用磁盘。

udevd的操作和配置

udevd守护进程的运作方式如下:

  • 内核通过内部网络链接向udevd发送通知事件,称为uevent。
  • udevd加载uevent中的所有属性。
  • udevd解析其规则,根据这些规则过滤和更新uevent,并采取相应的行动或设置更多属性。
    udevd从内核收到的传入的uevent可能是这样的(你会在第3.5.4节学习如何用udevadm monitor --property命令获得这个输出):

ACTION=change
DEVNAME=sde
DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host4/
target4:0:0/4:0:0:3/block/sde
DEVTYPE=disk
DISK_MEDIA_CHANGE=1
MAJOR=8
MINOR=64
SEQNUM=2752
SUBSYSTEM=block
UDEV_LOG=3

这个特殊的事件是对设备的改变。在接收到uevent后,udevd知道了设备的名称、sysfs设备路径和其他一些与属性相关的属性;它现在准备开始处理规则。
规则文件在/lib/udev/rules.d和/etc/udev/rules.d目录下。/lib中的规则是默认的,而/etc中的规则是重写的。对规则的全面解释会很乏味,你可以从udev(7)手册中了解更多,但这里有一些关于udevd如何读取规则的基本信息:

  • udevd从规则文件的开始到结束读取规则。
  • 在读完一条规则并可能执行其动作后,udevd 继续阅读当前的规则文件,寻找更多适用的规则。
  • 有一些指令(如GOTO)可以在必要时跳过规则文件的部分内容。这些指令通常放在规则文件的顶部,如果它与udevd正在配置的特定设备无关,就跳过整个文件。

让我们看一下第3.5.1节中/dev/sda例子中的符号链接。这些链接是由/lib/udev/rules.d/60-persistent-storage.rules中的规则定义的。在里面,你会看到以下几行:


# ATA
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"

# ATAPI devices (SPC-3 or later)
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5",ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"

这些规则与通过内核的SCSI子系统(见第3.6节)呈现的ATA磁盘和光学介质相匹配。你可以看到有一些规则来捕捉设备可能的不同表现方式,但想法是udevd将尝试匹配以sd或sr开头但没有数字的设备(用KERNEL"sd[!0-9]|sr"表达式),以及子系统(SUBSYSTEMS"scsi"),最后还有一些其他属性,取决于设备的类型。如果所有这些条件表达式在任何一条规则中都为真,udevd就会转到下一个也是最后表达式:


IMPORT{program}="ata_id --export $tempnode"

这不是一个条件。相反,它是一个指令,从/lib/udev/ata_id命令中导入变量。如果你有这样一个磁盘,自己在命令行上试试。它看起来会像这样:


# /lib/udev/ata_id --export /dev/sda
ID_ATA=1
ID_TYPE=disk
ID_BUS=ata
ID_MODEL=WDC_WD3200AAJS-22L7A0
ID_MODEL_ENC=WDC\x20WD3200AAJS22L7A0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_REVISION=01.03E10
ID_SERIAL=WDC_WD3200AAJS-22L7A0_WD-WMAV2FU80671
--snip--

现在,导入设置了环境,使这个输出中的所有变量名都被设置为所示的值。例如,接下来的任何规则现在都会将 ENV{ID_TYPE} 识别为磁盘。

在我们到目前为止看到的两条规则中,特别值得注意的是ID_SERIAL。在每条规则中,这个条件都出现在第二条:

env{id_serial}!="?*"

如果ID_SERIAL没有被设置,这个表达式会评估为真。因此,如果ID_SERIAL被设置了,条件就是假的,整个当前规则就不适用,udevd就会转到下一条规则。
为什么会出现在这里?这两条规则的目的是运行ata_id来查找磁盘设备的序列号,然后将这些属性添加到uevent的当前工作副本中。你会在许多udev规则中发现这个一般模式。
设置了ENV{ID_SERIAL}后,udevd现在可以在后面的规则文件中评估这个规则,它寻找任何连接的SCSI磁盘:

KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*",SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"

你可以看到,这条规则要求ENV{ID_SERIAL}被设置,它有一个指令:

SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"

这个指令告诉udevd为进入的设备添加一个符号链接。所以,现在你知道设备的符号链接是怎么来的了!
你可能想知道如何区分条件表达式和指令。条件表达式用两个等号(==)或砰的一声等号(!=)表示,指令用一个等号(=)、一个加号(+=)或一个冒号等号(:=)表示。

3.5.3 udevadm

udevadm程序是udevd的管理工具。 你可以重新加载udevd规则和触发事件,但udevadm最强大的功能可能是搜索和探索系统设备的能力,以及udevd从内核接收uevent时监控uevent的能力。不过,命令的语法可能有点儿复杂。大多数选项都有长短两种形式,我们在这里使用长的。
让我们从检查一个系统设备开始。回到第3.5.2节中的例子,为了查看所有的udev属性,以及与/dev/sda这样的设备的规则一起生成的属性,运行以下命令:

$ udevadm info --query=all --name=/dev/sda
P: /devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda
N: sda
S: disk/by-id/ata-WDC_WD3200AAJS-22L7A0_WD-WMAV2FU80671
S: disk/by-id/scsi-SATA_WDC_WD3200AAJS-_WD-WMAV2FU80671
S: disk/by-id/wwn-0x50014ee057faef84
S: disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0
E: DEVLINKS=/dev/disk/by-id/ata-WDC_WD3200AAJS-22L7A0_WD-WMAV2FU80671 /dev/disk/by-id/scsi
-SATA_WDC_WD3200AAJS-_WD-WMAV2FU80671 /dev/disk/by-id/wwn-0x50014ee057faef84 /dev/disk/by
-path/pci-0000:00:1f.2-scsi-0:0:0:0
E: DEVNAME=/dev/sda
E: DEVPATH=/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda
E: DEVTYPE=disk
E: ID_ATA=1
E: ID_ATA_DOWNLOAD_MICROCODE=1
E: ID_ATA_FEATURE_SET_AAM=1
--snip--

每行的前缀表示设备的一个属性或其他特征。在这个例子中,顶部的P:是sysfs设备路径,N:是设备节点(也就是给/dev文件起的名字),S:表示udevd根据其规则放在/dev中的设备节点的符号链接,E:是udevd规则中提取的额外设备信息。(这个例子中的输出远远超过了这里需要展示的内容;自己尝试一下这个命令,感受一下它的作用)。

3.5.4 设备监控

要用udevadm监视uevents,请使用monitor命令:

$ udevadm monitor
KERNEL[658299.569485] add /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2 (usb)
KERNEL[658299.569667] add /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0 (usb)
KERNEL[658299.570614] add /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/host15 
(scsi)
KERNEL[658299.570645] add /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/ 
host15/scsi_host/host15 (scsi_host)
UDEV [658299.622579] add /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2 (usb)
UDEV [658299.623014] add /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0 (usb)
UDEV [658299.623673] add /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/host15 
(scsi)
UDEV [658299.623690] add /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/
host15/scsi_host/host15 (scsi_host)
--snip--

在这个输出中,每条信息都有两份,因为默认行为是同时打印来自内核的传入信息(用KERNEL标记)和来自udevd的处理信息。 要想只看到内核事件,请添加--kernel选项,要想只看到udevd处理事件,请使用--udev。要看到整个传入的uevent,包括3.5.2节中显示的属性,使用--property选项。--udev和--property选项一起显示处理后的uevent。
你还可以按子系统过滤事件。例如,要想只看到与SCSI子系统的变化有关的内核信息,使用这个命令:

$ udevadm monitor --kernel --subsystem-match=scsi

关于udevadm的更多信息,请看udevadm(8)手册页。
udev的内容还有很多。例如,有一个叫做udisksd的守护进程,它监听事件,以便自动连接磁盘,并通知其他进程有新的磁盘可用。

3.6 深入了解: SCSI和Linux内核

在这一节中,我们将看一下Linux内核中的SCSI支持,作为探索Linux内核结构的一部分。你不需要为了使用磁盘而了解这些信息,所以如果你急于使用磁盘,请继续阅读第四章。此外,这里的材料比你到目前为止所看到的更高级,更具有理论性,所以如果你想保持动手能力,你肯定应该跳到下一章。
让我们从一点背景开始。传统的SCSI硬件设置是一个主机适配器通过SCSI总线与一连串的设备相连,如图3-1所示。主机适配器被连接到计算机上。主机适配器和设备都有一个SCSI ID,根据SCSI版本,每条总线可以有8或16个ID。一些管理员可能会用SCSI目标这个词来指代设备和它的SCSI ID,因为在SCSI协议中,会话的一端被称为目标。

图3-1:带有主机适配器和设备的SCSI总线

任何设备都可以通过SCSI命令集以点对点的关系与另一个设备进行通信。计算机没有直接连接到设备链上,所以它必须通过主机适配器才能与磁盘和其他设备通信。通常情况下,计算机向主机适配器发送SCSI命令以转达给设备,而设备则通过主机适配器转达响应。
较新版本的SCSI,如串行连接SCSI(SAS),提供了卓越的性能,但你可能不会在大多数机器中找到真正的SCSI设备。你会更经常地遇到使用SCSI命令的USB存储设备。此外,支持ATAPI的设备(如CD/DVD-ROM驱动器)使用SCSI命令集的一个版本。
SATA磁盘也作为SCSI设备出现在你的系统上,但它们略有不同,因为它们中的大多数是通过libata库中的转换层进行通信的(见3.6.2节)。一些SATA控制器(特别是高性能的RAID控制器)在硬件中进行这种转换。
这一切是如何组成的?考虑一下下面系统中显示的设备:

$ lsscsi
[0:0:0:0]diskATAWDC WD3200AAJS-201.0/dev/sda
[1:0:0:0]cd/dvdSlimtypeDVD A DS8A5SHXA15/dev/sr0
[2:0:0:0]diskUSB2.0CardReader CF0100/dev/sdb
[2:0:0:1]diskUSB2.0CardReader SM XD0100/dev/sdc
[2:0:0:2]diskUSB2.0CardReader MS0100/dev/sdd
[2:0:0:3]diskUSB2.0CardReader SD0100/dev/sde
[3:0:0:0]diskFLASHDrive UT_USB200.00/dev/sdf

方括号内的数字从左到右分别是SCSI主机适配器号、SCSI总线号、设备SCSI ID和LUN(逻辑单元号,是设备的进一步细分)。在这个例子中,有四个连接的适配器(scsi0、scsi1、scsi2和scsi3),每个都有个总线(都是总线号0),每个总线上只有一个设备(都是目标0)。位于2:0:0的USB读卡器有四个逻辑单元,但每一种闪存卡都可以被插入。内核给每个逻辑单元分配了一个不同的设备文件。
尽管不是SCSI设备,NVMe设备有时会在lsscsi输出中显示出N作为适配器编号。
注意:如果你想自己尝试lsscsi,你可能需要把它作为一个额外的软件包来安装。
图3-2显示了这个特定系统配置的内核中的驱动和接口层次,从单个设备驱动到块驱动。它不包括SCSI通用(sg)驱动。
尽管这是一个庞大的结构,一开始可能会让人不知所措,但是图中的数据流是非常线性的。让我们从SCSI子系统和它的三层驱动开始剖析:
顶层处理一类设备的操作。例如,sd(SCSI磁盘)驱动程序就在这一层;它知道如何将来自内核块设备接口的请求翻译成SCSI协议中的磁盘特定命令,反之亦然。
中间层在顶层和底层之间调节和路由SCSI信息,并跟踪所有的SCSI总线和连接到系统的设备。
底层处理特定的硬件动作。这里的驱动程序向特定的主机适配器或硬件发送传出的SCSI协议信息,并从硬件提取传入的信息。与顶层分离的原因是,尽管SCSI消息对于一个设备类别(如磁盘类别)是统一的,但不同种类的主机适配器有不同的发送相同消息的程序。

图3-2: Linux SCSI子系统原理图
上层和下层包含许多不同的驱动,但是重要的是要记住,对于你系统上的任何一个设备文件,内核(几乎总是)使用一个上层驱动和一个下层驱动。在我们的例子中,对于位于/dev/sda的磁盘,内核使用sd顶层驱动和ATA桥层驱动。
有些时候,你可能会为一个硬件设备使用不止一个上层驱动(见第3.6.3节)。对于真正的硬件SCSI设备,比如连接到SCSI主机适配器或硬件RAID控制器的磁盘,下层驱动程序直接与下面的硬件对话。然而,对于你发现连接到SCSI子系统的大多数硬件来说,情况就不同了。

3.6.1 USB存储和SCSI

为了让SCSI子系统与普通的USB存储硬件对话,如图3-2所示,内核需要的不仅仅是一个低层的SCSI驱动。一个由/dev/sdf代表的U盘可以理解SCSI命令,但是为了和驱动器进行实际的通信,内核需要知道如何通过USB系统进行对话。
从抽象的角度来看,USB与SCSI非常相似--它有设备类别、总线和主机控制器。因此,Linux内核包括一个与SCSI子系统非常相似的三层USB子系统也就不足为奇了,它的顶端是设备类驱动程序,中间是总线管理核心,底部是主机控制器驱动程序。与SCSI子系统在其组件之间传递SCSI命令一样,USB子系统在其组件之间传递USB信息。甚至还有一个lsusb命令,与lsscsi相似。
我们在这里真正感兴趣的部分是顶部的USB存储驱动器。这个驱动充当了翻译者的角色。在一端,驱动程序说的是SCSI,而在另一端,它说的是USB。因为存储硬件在其USB信息中包含SCSI命令,所以驱动程序的工作相对容易:它主要是重新包装数据。
有了SCSI和USB子系统,你几乎拥有了与闪存盘对话所需的一切。最后缺失的环节是SCSI子系统中的下层驱动,因为USB存储驱动是USB子系统的一部分,而不是SCSI子系统。(由于组织上的原因,这两个子系统不应该共享一个驱动。)为了使子系统能够相互交谈,一个简单的、低层的SCSI桥接驱动连接到USB子系统的存储驱动。

3.6.2 SCSI和ATA

图3-2中的SATA硬盘和光驱都使用相同的SATA接口。为了将内核的SATA驱动连接到SCSI子系统,内核采用了一个桥接驱动,就像对待USB驱动器一样,但是有不同的机制和额外的复杂情况。光驱说的是ATAPI,这是ATA协议中编码的SCSI命令的一个版本。然而,硬盘不使用ATAPI,也不对任何SCSI命令进行编码!
Linux内核使用一个叫做libata的库的一部分来调和SATA(和ATA)驱动器与SCSI子系统。对于讲ATAPI的光驱来说,这是一个相对简单的任务,即把SCSI命令打包并提取到ATA协议中去。但对硬盘来说,这项任务要复杂得多,因为库必须做一个完整的命令转换。
光驱的工作类似于将一本英文书打入电脑。你不需要为了完成这项工作而理解书的内容,甚至也不需要理解英语。但硬盘的任务更像是阅读一本德语书,并将其作为英文翻译输入电脑。在这种情况下,你需要理解两种语言以及书中的内容。
尽管有这样的困难,libata还是执行了这项任务,并使将ATA/SATA接口和设备连接到SCSI子系统成为可能。(除了图3-2中所示的一个SATA主机驱动程序外,通常还涉及更多的驱动程序,但为了简单起见,我们没有显示出来)。

3.6.3 通用SCSI设备

当用户空间进程与SCSI子系统通信时,它通常是通过块设备层和/或其他位于SCSI设备类驱动(如sd或sr)之上的内核服务进行的。换句话说,大多数用户进程不需要知道任何关于SCSI设备或它们的命令。
然而,用户进程可以绕过设备类驱动程序,通过其通用设备直接向设备发出SCSI协议命令。例如,考虑第3.6节中描述的系统,但这次,看看当你为lsscsi添加-g选项以显示通用设备时会发生什么:

$ lsscsi -g
[0:0:0:0]   disk    ATA       WDC WD3200AAJS-2  01.0  /dev/sda 1/dev/sg0
[1:0:0:0]   cd/dvd  Slimtype  DVD A DS8A5SH     XA15  /dev/sr0   /dev/sg1
[2:0:0:0]   disk    USB2.0    CardReader CF     0100  /dev/sdb   /dev/sg2
[2:0:0:1]   disk    USB2.0    CardReader SM XD  0100  /dev/sdc   /dev/sg3
[2:0:0:2]   disk    USB2.0    CardReader MS     0100  /dev/sdd   /dev/sg4
[2:0:0:3]   disk    USB2.0    CardReader SD     0100  /dev/sde   /dev/sg5
[3:0:0:0]   disk    FLASH     Drive UT_USB20    0.00  /dev/sdf   /dev/sg6

除了通常的块设备文件外,每个条目在最后一列1中列出SCSI通用设备文件。例如,位于/dev/sr0的光驱的通用设备是/dev/sg1。
为什么你想使用一个通用设备?答案是与内核中代码的复杂性有关。随着任务变得越来越复杂,最好把它们留在内核之外。考虑一下CD/DVD的写入和读取。读取光盘是相当简单的操作,而且有专门的内核驱动。
然而,写光盘要比读光盘困难得多,而且没有关键的系统服务依赖于写光盘的操作。没有理由用这种活动来威胁内核空间。因此,要在Linux中写入光盘,你需要运行一个用户空间程序,与一个通用的SCSI设备对话,比如/dev/sg1。这个程序可能比内核驱动的效率低一些,但它更容易建立和维护。

3.6.4 单一设备的多种访问方法

图3-3展示了Linux SCSI子系统从用户空间访问光驱的两个点(sr和sg)(SCSI下层的任何驱动都被省略了)。进程A使用sr驱动从驱动器中读取,进程B使用sg驱动向驱动器写入。然而,像这样的进程通常不会同时运行来访问同一个设备。

图3-3:光学设备驱动原理图
在图3-3中,进程A从块设备中读取数据。但是,用户进程真的会以这种方式读取数据吗?通常情况下,答案是否定的,不是直接的。在块设备上面还有更多的层,甚至还有更多的硬盘访问点,你将在下一章中学习。

标签:dev,内核,Linux,原理,SCSI,udevd,设备
From: https://www.cnblogs.com/testing-/p/17439516.html

相关文章

  • Linux base64命令
    Linux常用命令base64命令用于编码/解码文件或标准输入输出用例:[root@localhost~]#echotest|base64#加密dGVzdAo=[root@localhost~]#echodGVzdAo=|base64-d#解密test ......
  • 35 KVM管理设备-管理虚拟网卡
    35KVM管理设备-管理虚拟网卡35.1概述虚拟网卡类型主要包含virtio-net、vhost-net、vhost-user等。用户在创建虚拟机后,可能会有挂载或者卸载虚拟网卡的需求。openEuler提供了网卡热插拔的功能,通过网卡热插拔,能够改变网络的吞吐量,提高系统的灵活性和扩展性。35.2操作步骤虚拟......
  • Linux之软件管理
    一、软件运行和编译1、软件相关概念ABI 应用程序二进制接口API应用程序接口POSIX可移植操作系统接口C语言程序的实现过程C程序源代码-->预处理-->编译-->汇编-->链接静态链接:把链接的库文件包括进现有的项目中。动态链接:可执行文件和库文件是分开的,执行的时候,根据链接关......
  • Linux之软件管理
    一、软件运行和编译1、软件相关概念ABI 应用程序二进制接口API应用程序接口POSIX可移植操作系统接口C语言程序的实现过程C程序源代码-->预处理-->编译-->汇编-->链接静态链接:把链接的库文件包括进现有的项目中。动态链接:可执行文件和库文件是分开的,执行的时候,根据链接关......
  • Linux之软件管理
    一、软件运行和编译1、软件相关概念ABI 应用程序二进制接口API应用程序接口POSIX可移植操作系统接口C语言程序的实现过程C程序源代码-->预处理-->编译-->汇编-->链接静态链接:把链接的库文件包括进现有的项目中。动态链接:可执行文件和库文件是分开的,执行的时候,根据链接关......
  • Linux之软件管理
    一、软件运行和编译1、软件相关概念ABI 应用程序二进制接口API应用程序接口POSIX可移植操作系统接口C语言程序的实现过程C程序源代码-->预处理-->编译-->汇编-->链接静态链接:把链接的库文件包括进现有的项目中。动态链接:可执行文件和库文件是分开的,执行的时候,根据链接关......
  • linux 文本分析工具---awk命令
    awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。awk有3个不同版本:awk、nawk和gawk,未作特别说明,一般指gawk,gawk是AWK的GNU......
  • Linux 初始化之 Systemd机制
    systemd是Linux下的一种init软件,由LennartPoettering带头开发,其开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替现在常用的SystemV与BSD风格init程序。传统sysvinit使用inittab来决定运行......
  • 以样本学习方法解决设备故障检测中的标签问题
    文章的主要内容针对这些问题,提出了一种主动领域自适应智能故障检测框架LDE-ADA,该框架利用迁移学习和主动学习相结合的方法来解决标签域扩展问题,从而提高模型的检测性能。同时,提出了一种改进的主动学习查询策略,以准确选择目标域中新增加的健康类别样本来辅助模型训练,解决标签域扩......
  • linux 2种方式修改tmp目录的内存大小
    起因,tmp是临时目录,重启系统后目录的文件会清空,但是有时候你安装的软件依赖tmp进行临时存放文件,但tmp目录又太小。使用df-h查看/tmp目录的挂载点是tmpfs,这说明没有物理挂载设备。tmpfs有官方的介绍文章可以在评论区补充,谢谢。方法1:修改/etc/fstab文件的内容。vim/e......