linux设备管理之主设备号与次设备号

前言:
     本文经过一定的修正,有一些更改,通过本文你可以很清晰的理解设备唯一性是怎么确定的,同时也能理解
     /proc/devices和 /dev 的关系,即主从关系,是一对多的关系。
    
 什么是主设备号和次设备号?

  一个字符设备或者块设备都会有一个主设备号和次设备号(主设备号对应的就是某种驱动程序)。主设备号和次设备号统称为设备号。
主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的其他设备。(主设备号和控制这类设备的驱动是一一对应的)
通俗的说就是主设备号标识设备对应的驱动程序,告诉Linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务;而次设备号则用来标识具体且唯一的某个设备. /proc/devices类似一个类别,而/dev/下是某个类别下的项,由他们两个最终确定了一对多关系。
   以磁盘为例:
   在同一个系统中,磁盘设备的主设备号是唯一的。比如:scsi设备,次设备号只是在提供给scsi驱动程序内部使用,系统内核直接把次设备号传递给应用程序,scsi设备由驱动程序管理,我们可能有多个scsi设备,每个scsi设备都会分配一个次设备号。为了保证驱动程序的通用性,避免驱动程序移植过程中出现主设备号冲突,linux系统设定了主设备号的规则,包括保留的主设备号。
   主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号。
    如果你的系统内核是3.10,可以到 /usr/src/linux/include/linux/major.h 查看设备号的定义,但不是当前配置的设备号
    查看当前设备的设备号需查看 /proc/devices 
  注: 主设备号和次设备号,在不同磁盘的工具中都有输出显示,所以对于这个概念的理解还是很重要,否则你看不懂很多命令的输出。
  尤其是 cat /proc/devices 输出的意义等。

 

  设备的分类(区别于设备种类,种类是磁盘、内存等)

 以 Linux 的方式看待设备可区分为 3 种基本设备类型。 每个设备常常实现 3 种类型中的 1 种, 因此可分类成字符设备, 块设备, 或者一个网络设备。
 这种将设备分成不同类型或类别的方法并非是固定不变的。

 1、字符(char)设备

 一个字符( char ) 设备是一种可以当作一个字节流来存取的设备( 如同一个文件 ); 字符驱动模块负责实现这种行为。
 这样的驱动模块至少实现 open, close, read, 和 write 系统调用。文本控制台( /dev/console )和串口( /dev/ttyS0 及其他 )是字符设备的例子, 因为它们很好地展现了流的抽象。 字符设备通过文件系统结点来存取, 例如 /dev/tty1 和 /dev/lp0。

 2、块(block)设备

 如同字符设备, 块设备通过位于 /dev 目录的文件系统结点来存取. 一个块设备(例如一个磁盘)应该是可以驻有一个文件系统的。
 在大部分的 Unix 系统, 一个块设备只能处理特定的 I/O 操作, 即传送一个或多个长度经常是 512 字节( 或一个更大的 2 的幂的数 )的整块。
 Linux, 相反, 允许应用程序读写一个块设备象一个字符设备一样;它允许一次传送任意数目的字节。
 结果就是, 块和字符设备的区别仅仅在内核内部管理数据的方式上, 和在内核/驱动的软件接口上不同。
 如同一个字符设备, 每个块设备都通过一个文件系统结点被存取的, 它们之间的区别对用户是透明的。 块驱动和字符驱动相比, 与内核的接口完全不同。

 3、网络接口设备

 任何网络事务都通过一个接口来进行, 就是说, 一个能够与其他主机交换数据的设备。
 通常, 一个接口是一个硬件设备, 但是它也可能是一个纯粹的软件设备, 比如回环接口(lo)。一个网络接口负责发送和接收数据报文, 在内核网络子系统的驱动下,
 不必知道单个事务是如何映射到实际的被发送的报文上的。很多网络连接( 特别那些使用 TCP 的)是面向流的, 但是网络设备却常常设计成处理报文的发送和接收。
 网络驱动对单个连接一无所知; 它只处理报文。

设备名称

fb::frame缓冲
fd:—–:软驱
hd:—–:IDE 硬盘/光驱
md:—–:RAID设备(Metadisk)
dm:—–:LVM设备(DeviceMapper)
xd:—–:虚拟机中的硬盘驱动器
tty:—–:终端设备
psaux:—–:PS/2 鼠标设备
lp:—–:打印机
par:—–:并口
pt:—–:伪终端
s:—–:SCSI设备
scd:—–:SCSI音频光驱
sd:—–:SCSI硬盘
sg:—–:SCSI通用设备
sr:—–:SCSI数据光驱
st:—–:SCSI磁带
cdrom:—–:光驱的符号链接
mouse:—–:鼠标设备的符号链接
gpmdata:—–:伪设备
null:—–:写入消失
zero:—–:一直产生零


实验环境:

linux 系统采样,因为内核的版本对于/proc下的机制影响比较大,所以要注意版本
[root@fp-web-130 src]# cat /etc/redhat-release 
CentOS Linux release 7.2.1511 (Core)

[root@fp-web-130 src]# uname -r
3.10.0-1160.45.1.el7.x86_64


一、 查找主设备号位置文件 (内核创建)
      /proc/devices 文件显示当前配置的各种字符和块设备(不包括未加载模块的设备)

[root@fp-web-130 src]# cat /proc/devices
//显示结果

Character devices:  //字符设备
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
21 sg
29 fb
99 ppdev
128 ptm
136 pts
162 raw
180 usb
188 ttyUSB
189 usb_device
202 cpu/msr
203 cpu/cpuid
226 drm
249 hidraw
250 usbmon
251 bsg
252 watchdog
253 rtc 
254 tpm

Block devices:  //块设备
259 blkext
7 loop   //回环设备
8 sd     //scsi设备
9 md
11 sr    //只读光驱
65 sd  
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd  vcsa
129 sd  vcsa1
130 sd  vcsa2
131 sd  vcsa3
132 sd  vcsa4
133 sd  vcsa5
134 sd  vcsa6
135 sd
说明: sd,md,loop等就是设备名
      参看: https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt
实例:lsblk 命令可以查看块设备的主设备号和次设备号



解释:
loop0 的主设备号是 7:0 7表示的主设备号,0是系统为loop0回环设备编了号.这个主从就表示了唯一性([root@fp-web-130 src]# cat /proc/devices 得到的回环设备loop是7)
loop1 的主设备号是 7:1 7表示的主设备号,1是系统为loop1回环设备编了号.

基本上如果主设备号和次设备号明白了,那么lsblk这个命令的基本输出结果理解上就没有难度了

 


 

为了使应用程序区分所控制设备的类型,内核使用主设备号。而存在多台同类设备时,为了选择其中的一种,设备驱动程序就使用次设备号
举例: 这里sd是8(cat /proc/devices查的),看到有一列是8,16 这里8就是主设备号、16就是次设备号 


有三个目录说下 /dev/* , /proc/devices, /sys/devices

1./proc/devices/中的设备是通过insmod加载到内核的,它可产生一个major供mknod作为参数(那么必然可以通过lsmod查看)
2./dev/*.* 是通过mknod加上去的,设备节点,mknod(创建设备节点)后在此添加一子目录
   格式:mknod device1 c/b major minor 如:mknod dr1 c 254 0,用户通过此设备名来访问你的驱动。
   /dev,设备文件存储目录,应用程序通过对这些文件的读写和控制,可以访问实际的设备
3./sys/devices ,内核设备按总线类型分层放置的目录结构,按照设备挂接的总线类型,组织成层次结构,保存了系统所有的设备,
/sys/dev 下有两个子目录block和char,存放的是块设备和字符设备的主次号码,形式为(major:minor),
它指向 /sys/devices 目录下的设备。
注:sysfs最主要是用来描绘Linux kernel 2.6中的设备驱动模型,
用户态的后台程序(udev)会动态地周期性的扫描/sys目录中的属性项来自动管理设备文件(也称为设备节点),
从而在/dev目录会建立或者删除对应的设备文件。

 lsmod用于显示已载入系统的模块信息和模块间的依赖关系(sd举例)

[root@ht8 include]# lsmod  | grep sd
sd_mod                 46281  3 
crc_t10dif             12912  1 sd_mod
//lsmod命令实际上是读取并分析/proc/modules文件 [root@ht8 include]# cat
/proc/modules | grep sd sd_mod 46281 3 - Live 0xffffffffc0392000 crc_t10dif 12912 1 sd_mod, Live 0xffffffffc02ae000
Module: 模块的名字 Size: 模块的大小,单位是字节。
Live表示模块可用 模块的起始地址

内核已加载的模块信息也存在与/sys/modules目录下
[root@ht8 include]# cd /sys/module/sd_mod/ [root@ht8 sd_mod]# ll //该模块参数 total 0 -r--r--r-- 1 root root 4096 Mar 26 19:11 coresize drwxr-xr-x 2 root root 0 Jan 5 19:39 holders -r--r--r-- 1 root root 4096 Mar 26 19:11 initsize -r--r--r-- 1 root root 4096 Mar 26 19:11 initstate drwxr-xr-x 2 root root 0 Mar 26 19:10 notes -r--r--r-- 1 root root 4096 Mar 26 19:11 refcnt -r--r--r-- 1 root root 4096 Mar 26 19:11 rhelversion drwxr-xr-x 2 root root 0 Mar 26 19:10 sections -r--r--r-- 1 root root 4096 Mar 26 19:11 srcversion -r--r--r-- 1 root root 4096 Mar 26 19:11 taint --w------- 1 root root 4096 Mar 26 19:11 uevent

二、linux 次设备号查看,我只留下一部分来说明问题

[root@fp-web-130 src]# ls -l /dev
total 0
.....
brw-rw---- 1 root disk 7, 0 Feb 7 14:58 loop0
brw-rw---- 1 root disk 7, 1 Feb 7 14:58 loop1
brw-rw---- 1 root disk 7, 2 Feb 9 11:21 loop2
....
brw-rw---- 1 root disk 8, 0 May 27 2021 sda
brw-rw---- 1 root disk 8, 1 May 27 2021 sda1
brw-rw---- 1 root disk 8, 2 May 27 2021 sda2
brw-rw---- 1 root disk 8, 16 Feb 5 01:02 sdb
brw-rw---- 1 root disk 8, 17 Feb 5 01:02 sdb1
brw-rw---- 1 root disk 8, 18 Feb 5 01:02 sdb2
brw-rw---- 1 root disk 8, 21 Feb 5 01:04 sdb5
crw-rw-rw-  1 root tty 5,   0 Apr 27  2022 tty

.....
我们看到用,号分割的两个数字,比如 sda是 8,0 这个两个数字就是特定设备的主设备号和次设备号
sda设备主设备号在/proc/devices定义为8,次设备号为0,所以主设备号和次设备号确定了唯一的设备
更详细的你可以下载源码: E:\linux内核\linux-3.12.37\linux-3.12.37\Documentation\devices.txt 来查看
那么具体主设备号和次设备号为什么这么设计了呢,简单描述就是
1)主设备号是关联驱动程序的,比如 scsi硬盘的驱动,那么就是8来管理,一个主备号关联多个次设备号
2)次设备号仅供主设备号指定的驱动程序(底层硬件接口)使用,这里就是8不能提供给例如cpu等设备使用。
  内核的其他部分不使用它,只是将它传递给主设备号对应的驱动程序。
另外:
brw标识块设备,crw标识为字符设备。

         我们下面用简单表格更能阐述上面说的内容: 

/proc/devices /dev下的设备(以逗号分割的两列) 主设备号 次设备号 设备名称 设备驱动
8 8,0 8   sda 8对应scsi内核磁盘驱动程序
  8,1   1 sda1  确定唯一设备sda1
  8,2   2 sda2  确定唯一设备sda2
  8,3   3 sda3  确定唯一设备sda3
  8,4    4 sda4  确定唯一设备sda4

       
     

 

                                                           图来来自这里 


三、保留的主设备号

linux 2.0内核支持128个设备;2.2 和 2.4将该数字增加到 256(同时保留值0和255以供将来使用) 
60 到 63120127240254 范围内的主编号保留供本地和实验使用:不会为实际设备分配这样的主编号

 

四、主设备号定义:

[root@aozhejin /]locate major.h
[root@aozhejin /]/usr/include/linux/major.h   //定义在这里
[root@aozhejin /]/usr/src/kernels/3.10.0-1160.62.1.el7.x86_64/include/uapi/linux/major.h //内核开发环境
[root@aozhejin /]#cat /usr/include/linux/major.h | more
#ifndef _LINUX_MAJOR_H
#define _LINUX_MAJOR_H
/*
 * 这个文件定义了主设备号对于主设备号的分配,可以参看内核的文档 Documentation/devices.txt.
下载内核源码的 Documenttation下即有这个文件.
*/
#define UNNAMED_MAJOR        0
#define MEM_MAJOR            1
#define RAMDISK_MAJOR        1
#define FLOPPY_MAJOR         2
#define PTY_MASTER_MAJOR     2
#define IDE0_MAJOR           3
#define HD_MAJOR        IDE0_MAJOR
#define PTY_SLAVE_MAJOR        3
#define TTY_MAJOR           4
#define TTYAUX_MAJOR        5
#define LP_MAJOR            6
#define VCS_MAJOR           7
#define LOOP_MAJOR          7
#define SCSI_DISK0_MAJOR    8   //scsi设备,去到/proc/devides里面看下是否对应上?
#define SCSI_TAPE_MAJOR     9   
#define MD_MAJOR            9
#define MISC_MAJOR         10
#define SCSI_CDROM_MAJOR   11
#define MUX_MAJOR          11    /* PA-RISC only */
#define XT_DISK_MAJOR      13
#define INPUT_MAJOR        13
#define SOUND_MAJOR        14
#define CDU31A_CDROM_MAJOR 15
#define JOYSTICK_MAJOR     15
#define GOLDSTAR_CDROM_MAJOR    16
......

 

五、内核文档(kernel-doc)中查询各主次设备号代表的意义.

[root@ht8 sd_mod]# rpm -qa | grep kernel-doc
kernel-doc-3.10.0-1160.59.1.el7.noarch
//没有的话,yum install kernel-doc
[root@ht8 sd_mod]#cat /usr/share/doc/kernel-doc-3.10.0/Documentation/devices.txt

    Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘的主设备号是3。 一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。 

    Linux为所有的设备文件都提供了统一的操作函数接口,方法是使用数据结构struct file_operations。这个数据结构中包括许多操作函数的指针,如open()、close()、read()和write()等,但由于外设 的种类较多,操作方式各不相同。Struct file_operations结构体中的成员为一系列的接口函数,如用于读/写的read/write函数和用于控制的ioctl等。打开一个文件就是 调用这个文件file_operations中的open操作。不同类型的文l件有不同的file_operations成员函数,如普通的磁盘数据文件, 接口函数完成磁盘数据块读写操作;而对于各种设备文件,则最终调用各自驱动程序中的I/O函数进行具体设备的操作。这样,应用程序根本不必考虑操作的是设 备还是普通文件,可一律当作文件处理,具有非常清晰统一的I/O接口。所以file_operations是文件层次的I/O接口。

  内核中如何标识设备号

  设备号是在驱动module中分配并注册的,而/dev目录下的设备文件是根据这个设备号创建的,因此,当访问/dev目录下的设备文件时,驱动module就知道,自己该出场服务了(当然是由内核通知)。在Linux内核看来,主设备号标识设备对应的驱动程序,告诉Linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务;而次设备号则用来标识具体且唯一的某个设备。在内核中,用dev_t类型(其实就是一个32位的无符号整数)的变量来保存设备的主次设备号,其中高12位表示主设备号,低20位表示次设备号。

设备获得主次设备号有两种方式:一种是手动给定一个32位数,并将它与设备联系起来(即用某个函数注册);另一种是调用系统函数给设备动态分配一个主次设备号。

     对于手动给定一个主次设备号,使用以下函数:
     int register_chrdev_region(dev_t first, 
     unsigned int -count, 
     char *name)
     其中first是我们手动给定的设备号,count是所请求的连续设备号的个数,而name是和该设备号范围关联的设备名称,它将出现在/proc/devices和sysfs中。
比如,若first为0x3FFFF0,count为0x5,那么该函数就会为5个设备注册设备号,分别是0x3FFFF0、 0x3FFFF1、 0x3FFFF2、 0x3FFFF3、 0x3FFFF4,其中0x3(高12位)为这5个设备所共有的主设备号(也就是说这5个设备都使用同一个驱动程序)。而0xFFFF0、 0xFFFF1、 0xFFFF2、 0xFFFF3、 0xFFFF4就分别是这5个设备的次设备号了。需要注意的是,若count的值太大了,那么所请求的设备号范围可能会和下一个主设备号重叠。比如若 first还是为0x3FFFF0,而count为0x11,那么first+count=0x400001,也就是说为最后两个设备分配的主设备号已经不是0x3,而是0x4了!用这种方法注册设备号有一个缺点,那就是若该驱动module被其他人广泛使用,那么无法保证注册的设备号是其他人的 Linux系统中未分配使用的设备号。

 与主次设备号相关的3个宏:

MAJOR(dev_t dev):根据设备号dev获得主设备号;
MINOR(dev_t dev):根据设备号dev获得次设备号;
MKDEV(int major, int minor):根据主设备号major和次设备号minor构建设备号 


   从Linux2.6内核版本开始,/proc文件系统迁移至改进后的/sys文件系统。/sys文件系统中加入对动态变更的支持,如添加和删除LUN,而无需重新加载主机适配器驱动或重启主机。
六、一些主要设备号被静态分配给最常见的设备。
     Documentation/devices.txt (参见内核源码)这些设备的列表可以在内核源代码树中找到 。由于已经分配了许多编号,因此为新驱动程序选择一个唯一编号可能很困难

 七、开发底层驱动程序

     开发内核驱动程序的时候,你最好使用动态分配的方式获取您的主设备号,而不是从当前可用的主设备号中随机选择一个。如果您的驱动程序希望被官方内核自动分配,则你需要申请分配一个主设备编号以供独占使用。当然如果你是为了测试你完全可以使用保留的主设备号,一旦你在开发时被分配到了主设备号,则在/proc/devices文件中会看到,这样你就可以创建唯一设备在/dev目录下。
     你需要关注: 
    E:\linux内核\linux-3.12.37\linux-3.12.37\Documentation\devices.txt
     系统里面的 /usr/include/linux/major.h
     当前系统的/proc/devices /dev
     

sysfs(/sys目录)-sysfs 的设计原则是一个属性文件只做一件事情,sysfs属性文件一般只有一个值,直接读取或写入

sysfs 虚拟文件系统提供了一种比 proc(老的机制) 更为理想的访问内核数据的途径。sysfs 文件系统总是被挂载在 /sys 挂载点上

sysfs 与 proc 相比有很多优点,最重要的莫过于设计上的清晰。一个 proc 虚拟文件可能有内部格式,如 /proc/scsi/scsi ,它是可读可写的,并且读写格式不一样,代表不同的操作,应用程序中读到了这个文件的内容一般还需要进行字符串解析,而在写入时需要先用字符串格式化按指定的格式写入字符串进行操作。
相比而言.

SYS 系统目录结构

/sys/sys/下的目录结构是经过精心设计的:
在 /sys/devices 下是所有设备的真实对象,包括如视频卡和以太网卡等真实的设备,也包括 ACPI 等不那么显而易见的真实设备、还有 tty, bonding 等纯粹虚拟的设备;在其它目录如 class, bus 等中则在分类的目录中含有大量对 devices 中真实对象引用的符号链接文件。
/sys/devices这是内核对系统中所有设备的分层次表达模型,也是 /sys 文件系统管理设备的最重要的目录结构
/sys/dev这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件,它是在内核 2.6.26 首次引入;
/sys/bus这是内核设备按总线类型分层放置的目录结构,devices 中的所有设备都是连接于某种总线之下,
在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;
/sys/class这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。
它也是构成 Linux 统一设备模型的一部分;
/sys/block这里是系统中当前所有的块设备所在,按照功能来说放置在 /sys/class 之下会更合适,
在 2.6.26 内核中已正式移到 /sys/class/block
/sys/firmware这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API
/sys/fs这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,
但目前只有 fuse,gfs2 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl (/proc/sys/fs) 接口中中;
/sys/kernel这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等几项较新的设计在使用它,
其它内核可调整参数仍然位于 sysctl (/proc/sys/kernel) 接口中
/sys/module这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module 中
/sys/power这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。
/sys/slab(内核2.6.24 以后移至 /sys/kernel/slab)SLAB 内存分配器的实现

 

参考资料:
   https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch03s02.html
  https://distroid.net/mknod-command-linux/
  http://www.cs.columbia.edu/~sedwards/classes/2023/4840-spring/device-drivers.pdf
  https://static.lwn.net/images/pdf/LDD3/ch16.pdf

posted @ 2022-02-09 12:17  jinzi  阅读(5686)  评论(0编辑  收藏  举报