1、Linux文件系统目录结构
- bin:存放基本命令,如
ls
、cp
等。 - sbin:存放系统命令,如
modprobe
、ifconfig
等,大多涉及系统管理的命令。 - dev:设备文件存储目录,应用程序通过读写、控制这些文件来访问实际的设备。
- etc:系统配置文件,如用户账号、密码、启动脚本。
- lib:系统库文件。
- mnt:一般用于存放挂载储存设备的挂载目录。
- opt:可选目录,一般用于安装软件包。
- proc:进程及内核信息。是伪文件系统
proc
的挂载目录,只存在于内存中。 - tmp:存放用户程序运行时产生的临时文件。
- usr:系统存放程序的目录,如用户命令、用户库等。
- var:该目录内容经常变动,可用于存放系统日志等。
- sys:
Linux2.6
以后的内核所支持的sysfs
文件系统会被映射到此目录。设备驱动模型中的总线、驱动、设备都可在该目录中找到相应的节点。
2、Linux文件系统与设备驱动
文件系统与设备驱动之间的关系如下图所示:
应用程序与虚拟文件系统VFS之间的接口是系统调用;
虚拟文件系统VFS与文件系统和设备文件之间的接口是struct file_operations
结构体,其包含了对文件进行打开open
、关系close
、读写read/write
、控制ioctl
等成员函数。
字符设备的访问方法:
字符设备的file_operations
成员函数直接由设备驱动提供,虚拟文件系统调用这些成员函数访问字符设备。
块设备的访问方法:
方法一:不通过文件系统,而是直接访问裸设备。
Linux内核实现了统一的、用于块设备的struct file_operations
结构体实例def_blk_fops
。
当运行类似dd if=/dev/sdb1 of=sdb1.img
命令将整个/dev/sdb1
裸分区复制到sdb1.img
时,内核就是使用def_blk_fops
实例。
裸分区、裸设备:个人理解,就是没有在这个存储空间上套一层文件系统,操作这块存储空间时,数据是无结构的、无组织的。
方法二:通过文件系统访问块设备。
通过文件系统访问块设备,struct file_operations
结构体的实现由文件系统(ext2
、fat
等)完成,设备驱动层感知不到struct file_operations
的存在,也不需要实现这个结构体。
文件系统会把针对文件的读写转换为针对块设备原始扇区的读写。
设备驱动程序设计中,需关注两个结构体struct file
和struct inode
。
file
结构体代表一个打开的文件,每次打开一个文件时,内核都会创建一个file
实例与该文件关联,并将该实例传递给在文件上进行操作的任何函数。
struct file
结构体中的主要内容:
struct file{
...
struct inode *f_inode;
const struct file_operations *f_op; // 和文件关联的操作
unsigned int f_flags; // 文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC等
fmode_t f_mode; // 文件读写模式
void *private_data; // 文件私有数据,大多指向设备驱动自定义的用于描述设备的结构体
...
};
inode
结构体表示设备文件,VFS的inode
结构体包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。是管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。
struct inode{
...
dev_t i_rdev;
union{
struct block_device *i_bdev;
struct cdev *i_cdev;
}
...
};
成员i_rdev
包含设备编号,分为主设备编号和次设备编号。
主设备编号占dev_t
的高12位,次设备编号占dev_t
的低20位。可通过如下两个函数从inode
中获得主设备号和次设备号。
unsigned int imajor(struct inode *inode);//返回主设备号
unsigned int iminor(struct inode *inode);//返回次设备号
查看/proc/devices
可获知系统中注册的设备信息,如设备类型、名称、主设备号。
查看/dev
目录可获取系统中包含的所有设备文件,其中有设备的主设备号和次设备号信息。
主设备号:用于区分不同类型的设备。同类型的设备使用相同的主设备号,一个主设备号通常对应一个驱动。
次设备号:用于区分同一类型设备下的多个设备
例如:有多个LED,它们都使用同一个驱动程序,那主设备号相同,次设备号区分LED0、LED1。
设备文件的管理方式:
动态建立/删除设备节点文件,不需要用户再手动使用mknod
命令在/dev
目录下创建设备节点文件。
devfs
设备文件系统:Linux2.6及以后的内核中已抛弃该管理方式。udev+sysfs
文件系统:
devfs
设备文件系统(了解即可):
优点:
- 可以通过程序在设备初始化时在
/dev
目录下创建设备文件,卸载设备时将它删除。 - 设备驱动程序可以指定设备名、所有者和权限位,用户空间程序仍可以修改所有者和权限位。
- 不再需要为设备驱动程序分配主设备号以及处理次设备号,可通过向
register_chrdev()
函数传递0主设备号,由内核分配可用的主设备号,并在devfs_register()
函数中指定次设备号。
缺点:
- 存在无法修复的bug
- 运行在内核空间,但其工作内容完成可在用户空间实现
遗留的两个API:
/* 申请主设备号 */
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
/* 释放主设备号 */
void unregister_chrdev(unsigned int major, const char *name)
udev
设备管理方式:
udev
完全在用户态
下工作,利用设备加入或移除时内核所发送的热插拔事件(Hotplug Event)进行工作。
工作过程:
当热插拔时,内核通过套接字netlink
将设备的详细信息(uevent
)发送出来。udev
进程接收netlink
发送的uevent
信息,根据该信息内容以及用户设置的udev
规则完成匹配工作(包括SUBSYSTEM
、ACTION
、attribute
等),并在/dev
目录下创建设备文件节点。
对于冷插拔设备,由于其在开机时已经存在,且是在udev
启动前就已经被插入。为了获得该设备的uevent
信息,内核提供了sysfs
下的uevent
节点(/sys/module/xxx/uevent
)。向该节点写一个add
,内核会重新发送该设备相关的netlink
消息。
udev
的特点:
udev
在设备被发现时(即产生热插拔事件)加载驱动模块,并创建对应的设备节点,而不是在访问设备时加载驱动模块。- 工作在用户空间
- 动态建立/删除设备文件
- 允许每个不用关心主/次设备号,而提供
LSB
名称(Linux标准规范) - 可固定
/dev
下的设备节点文件名称
udev
规则文件:
- 以行为单位,每一行代表一个规则
#
开头的行表示注释行- 每条规则可分成一个或多个匹配部分和赋值部分
- 匹配部分用匹配专用的关键字表示:
ACTION
行为、KERNEL
内核设备名、BUS
总线类型、SUBSYSTEM
子系统名、ATTR
属性等。 - 赋值部分的关键字:
NAME
创建的设备文件名、SYMLINK
符号创建链接名、OWNER
设置设备所有者、GROUP
设置设备的组、IMPORT
调用外部程序、MODE
节点访问权限等。
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="08:00:27:35:be:ff", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME=="eth1"
udev
和devfs
在命名方面的差异:
如果系统中有两个USB
打印机,设备节点文件分别为/dev/usb/lp0
和/dev/usb/lp1
,但哪个文件对应哪台打印机是无法确定的。而且,打印机与设备节点文件的映射关系也会因设备发现的顺序、打印机本身关闭等原因而不确定。
因此,最好是基于打印机的序列号或其他标识信息来确定打印机与设备节点文件的映射关系,devfs
无法实现这一点,而udev
可以做到。
udevadm
是udev
配套的工具 。
嵌入式系统中,可使用mdev
(轻量版udev
),mdev
集成在busybox
中。
sysfs
文件系统:
该文件系统是虚拟的文件系统,可以产生一个包括所有系统硬件的层级视图,可以展示设备驱动模型中各组件的层次关系。
sysfs
文件系统把连接在系统上的设备和总线组织成一个分级的文件,这些文件可由用户空间存取,向用户空间导出内核数据结构以及它们的属性。
sysfs
文件系统顶层目录结构:
/sys
|---block //包含所有块设备
|---bus //包含系统中所有总线类型
|---devices //挂载在该总线上的设备,是指向/sys/devices目录中文件的符号链接
|---drivers //挂载在该总线上的驱动,是指向/sys/drivers目录中文件的符号链接
|---class //包含系统中的设备类型,也包含许多对/sys/devices下文件的符号链接
|---dev
|---devices //包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
|---firmware
|---fs
|---hypervisor
|---kernel
|---module
|---power
Linux设备模型与设备、驱动、总线和类的现实状况是直接对应的,如图所示。
Linux 2.6
以后的内核的设备驱动核心层代码已处理好设备、总线和类的关系,内核中的总线和其他内核子系统会完成与设备模型的交互。因此,驱动工程师编写底层驱动代码时几乎不需要关心设备模型,只需要按照每个框架的要求,填写xxx_driver
里面各种回调函数(xxx
表示总线名)。
结构体bus_type
、device_driver
和device
分别描述总线、驱动和设备:
struct bus_type{
...
int (*match)(struct device *dev, struct device_driver *drv); /* 匹配驱动和设备 */
...
};
struct device_driver{
...
const char *name;
struct bus_type *bus; /* 驱动挂载的总线 */
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
...
};
struct device{
...
struct bus_type *bus; /* 设备挂载的总线 */
struct device_driver *driver; /* 设备使用的驱动 */
...
};
Linux内核中,设备和驱动分别独立注册,注册设备或驱动时,不要求相应的驱动或设备已经存在。
当向内核注册设备或驱动时,总线bus_type
的match()
成员函数将为该设备或驱动匹配相应的驱动或设备。
当驱动和设备匹配成功后,结构体xxx_driver
的probe()
函数就被执行(xxx
表示总线名,如platform
、i2c
)。
总线、驱动和设备分别对应sysfs
下的一个目录,这三者都可认为是kobject
的派生类,kobject
可看作所有总线、驱动和设备的抽象基类,一个kobject
对应sysfs
中的一个目录。
总线、驱动和设备中各个attribute
对应sysfs
中一个文件。
sysfs
中的目录来源于bus_type
、device_driver
、device
,而目录中的文件则来源于attribute
。