1 Linux 文件系统

1.1 Linux 文件系统与设备驱动关系

下图表明了 Linux 中虚拟文件系统、磁盘/Flash文件系统以及一般的设备文件与设备驱动程序之间的关系。

文件系统与设备驱动之间的关系

应用程序与 VFS 之间的接口是系统调用,而 VFS 与文件系统以及设备文件之间的接口是 file_operations 结构体成员函数,此结构体包含了对文件进行打开、关闭、读写、控制的一系列成员函数。由于字符设备的上层没有类似于磁盘的 ext2 等文件系统,所以字符设备的 file_operations 成员函数就直接由设备驱动提供了。

由此可以看出对于块设备的访问有两种方法:

一种方法是不通过文件系统直接访问裸设备,Linux 内核实现了统一的 def_blk_fops这一个 file_operations,他的源码位于 fs/block_dev.c。所以当运行类似于 "dd if=/dev/sd1 of=sdb1.img" 的命令把整个 /dev/sdb1裸分区复制到 sdb1.img 的时候,内核走的是 def_blk_fops这个 file_operations;

另一种方法是通过文件系统来访问设备,file_operations 的实现则位于文件系统内,文件系统会把正对文件的读写转换为针对设备原始扇区的读写。ext2、fat、btrfs 等文件系统中会实现针对 VFS 的file_operations 成员函数,设备驱动层将看不到 file_operations的存在。

1.2 设备驱动程序

在设备驱动程序设计中,一般而言,会关心 file 和 inode 这两个结构体。

file 结构体

file 结构体代表一个打开的文件,系统中每个打开的文件在内核空间都有一个与之关联的 struct file。它由内核在打开文件时创建,并传递给在文件上进行任何操作的函数。在文件的所有实例都关闭后,内核释放此结构体的数据。结构体原型如下:

struct file {

union{

struct llist_node fu_llist;

struct rcu_head fu_rcuhead;

} f_u;

struct path f_path;

#define f_dentry f_path.dentry;

struct inode *f_inode; //cached value

const struct file_operations *f_op; //和文件关联的操作

//Protects f_ep_links, flags.

//must not be taken from IRQ context

spinlock_t f_lock;

atomic_long_t f_count;

unsigned int f_flags; //文件标志,如 O_RDONLY O_NONBLOCK OSYNC

fmode_t f_mode; //文件读写模式 FMODE_READ FMODE_WRITE

struct fown_struct f_owner;

const struct cred *fcred;

struct file_ra_state f_ra;

u64 v_version;

#ifdef CONFIG_SECURITY

void *f_security;

#endif

//need for tty driver, and maybe others

void *private_data; //文件私有数据

#ifdef CONFIG_EPOLL

//used tye fs/eventpoll.c to link all the hooks to this file

struct list_head f_ep_links;

struct list_head f_tfile_llink;

#endif

struct address_space *f_mapping;

} __attribute__((aligned(4))); //lest something weird decides that 2 is OK

文件读/写模式 mode、标志 f_flags 都是设备驱动关心的内容,而私有数据指针 private_data 在设备驱动中被广泛应用,大多被指向设备驱动自定义以用于描述设备的结构体。

inode 结构体

VFS node 包含文件的访问权限、属性、组、大小、生成时间、访问时间、最后修改时间等信息。它是 Linux管理文件系统的最基本的单位,也是文件系统连接任何子目录、文件的桥梁。其定义如下:

struct inode{

...

umode_t i_mode; //inode 的权限

uid_t i_uid; //inode 的拥有者id

gid_t i_gid; //inode 所属的群组 id

dev_t i_rdve; //若是设备文件,此字段将记录设备的设备号

loff_t i_size; //inode 所代表文件的大小

unsigned int i_blkbits;

blkcnt_t i_blocks; //inode 所使用的block数, 一个block为 512 个字节

union{

struct pipe_inode_info *i_pipe;

struct block_device *i_bdev; //若是块设备,为其对应的block_device结构体指针

struct cdev *i_cdev; //若是字符设备,为其对应的 cdev 的结构体指针

};

...

};

对于表示设备文件的 inode 结构,i_rdev 字段包含设备编号。linux 内核设备编号分为主设备编号和次设备编号,前者为 dev_t 的高 12 位,后者位 dev_t 的低 20 位。下列操作用于从设备号中获取主设备号和次设备号:

unsigned int iminor(struct inode *inode);

unsigned int imajor(struct inode *inode);

查看 /proc/devices 文件可以获知系统中注册的设备,第一列位主设备号,第 2 列为设备名称。如:

Character devices:

1 mem

4 /dev/vc/0

4 tty

4 ttyS

5 /dev/tty

5 /dev/console

5 /dev/ptmx

5 ttyprintk

6 lp

7 vcs

10 misc

Block devices:

259 blkext

7 loop

8 sd

9 md

128 sd

134 sd

135 sd

253 device-mapper

254 mdp

查看/dev 目录可以获知系统中包含的设备文件,日期的前两列分别给出了主设备号和次设备号。如:

crw--w---- 1 root tty 4, 29 4月 9 16:39 tty29

crw--w---- 1 root tty 4, 3 4月 9 16:39 tty3

crw--w---- 1 root tty 4, 30 4月 9 16:39 tty30

crw--w---- 1 root tty 4, 31 4月 9 16:39 tty31

crw--w---- 1 root tty 4, 32 4月 9 16:39 tty32

主设备号是与驱动对应的概念,同一类设备使用相同的主设备号。

同一驱动可支持多个同类设备,因此次设备号就是用来表示同一驱动下不同设备。

2 devfs

devfs(设备文件系统)是由 linux2.4 内核引入,它使得设备驱动程序能自主管理自己的设备文件。具体来说,devfs具有如下优点:

可以通过程序在设备初始化时在 /dev 目录下创建设备文件,卸载设备时将它删除。

设备驱动程序可以指定设备名、所有者和权限位,用户空间程序仍可以修改所有者和权限位。

不再需要为设备驱动程序分配主设备号以及处理次设备号,在程序中可以直接给 register_chr_dev() 传递 0 主设备号以获得可用的主设备号,并在 devfs_register() 中指定次设备号。

驱动程序应调用下面这些函数来进行设备文件的创建和撤销工作。

//创建设备目录

devfs_handle_t devfs_mk_dir(devfs_handle_t dir, const char *name, void *info);

//创建设备文件

devfs_handle_t devfs_register(devfs_handle_t dir, const char *name, unsigned int flags, \

unsigned int major, unsigned int minor, umode_t mode, void *ops, void *info);

//撤销设备文件

void devfs_unregister(devfs_handle_t de);

在 Linux 2.4 的设备编程中,分别在模块加载、卸载函数中创建和撤销设备文件是被普遍采用并值得大力推荐的好方法。以下为示例代码:

static devfs_handle_t devfs_handle;

static int __init xxx_init(void)

{

int ret;

int i;

//在内核中注册设

ret = register_chr_dev(XXX_MAJOR, DEVICE_NAME, &xxx_fops);

if(ret < 0)

{

printk(DEVICE_NAME " can't register major number\n");

return ret;

}

//创建设备文件

devfs_handle = devfs_register(NULL, DEVICE_NAME, DEVFS_FL_DEFAULT,

XXX_MAJOR, 0, S_IFCHR | S_IWUSR | S_IRUSR, &xxx_fops, NULL);

...

printk(DEVICE_NAME " initialized.\n");

return 0;

}

static void __exit xxx_exit(void)

{

devfs_unregister(devfs_handle); //撤销设备文件

unregister_chrdev(XXX_MAJOR, DEVICE_NAME); //注销设备

}

module_init(xxx_init);

moudule_exit(xxx_exit);

3 udev 用户空间设备管理

3.1 udev 和 devfs 的区别

在 Linux 2.6 内核中,devfs 被认为是过时的方法,并最终被淘汰了,udev 取代了它。Linux VFS 内核维护者 Al viro 指出了几点 udev 取代 devfs 的原因:

devfs 所做的工作被确信可以在用户态来完成。

devfs 被加入内核之时,大家期望它的质量可以迎头赶上。

发现 devfs 有一些可修复和无法修复的bug。

对于无法修复的 bug 在相当长的一段时间内没有改观。

devfs 的维护者和作者对齐感到失望并停止对其维护。

Linux 设计中强调的一个基本观点是机制和策略的分离。机制是稳定的,而策略是灵活的,机制应该在内核完成,而策略最好在用户空间实现。

3.2 热插拔事件

udev 完全在用户态工作,利用设备加入或移除时内核所发送的热插拔事件(Hotplug Event)来工作。在热插拔时,设备的详细信息会由内核通过 netlink 套接字发送出来,发出来的事情叫 uevent。udev 的设备命名策略、权限控制和事件处理都是在用户态下完成的,它利用从内核收到的信息进行创建设备文件节点等工作。以下是一个获取热插拔事件 uevent的范例:

#include

#include

#include

#include

#include

#include

#include

#include

static void die(char *s)

{

write(2, s, strlen(s));

exit(1);

}

int main(int argc, char* argv[])

{

struct sockaddr_nl nls;

struct pollfd pfd;

char buf[512];

//open hotplug event netlink socket

memset(&nls, 0, sizeof(struct sockaddr_nl));

nls.nl_family = AF_NETLINK;

nls.nl_pid = getpid();

nls.nl_groups = -1;

pfd.events = POLLIN;

pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

if(-1 == pfd.fd)

die("Not root\n");

//Listen to netlink socket

if(bind(pfd.fd, (void *)&nls, sizeof(struct sockaddr_nl)))

die("Bind failed.\n");

while(-1 != poll(&pfd, 1, -1))

{

int i, len;

len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT);

if(-1 == len)

die("recv\n");

//print the data to stdout

i = 0;

while(i < len){

printf("%s\n", buf + i);

i += strlen(buf + i) + 1;

}

}

die("poll\n");

return 0;

}

编译上述代码,在虚拟机上运行,插拔光盘等设备,会打印热插拔事件。

udev 就是采用这种方式接收 netlink 消息,并根据它的内容和用户设置给 udev 的规则做匹配来进行工作。

对于冷插拔的设备来说,Linux 内核提供了 sysfs 下面的一个 uevent 节点,可以往该节点写一个 “add”,导致内核重新发送 netlink,之后 udev 就可以收到冷插拔的 netlink 消息了。上述程序会dump 出如下信息:

ACTION=add

DEVPATH=/module/psmouse

SEQNUM=1682

SUBSYSTEM=module

UDEV_LOG=3

USEC_INITIALIZED=220903546792

devfs 与 udev 的一个显著区别:

采用 devfs,当一个并不存在的 /dev 节点被打开的时候,devfs 能自动加载对应的驱动,而udev不这么做。

udev 任务驱动应该在设备被发现时,而不是设备被访问时加载。故 udev 获取设备发现时的热插拔事件,在设备发现时加载设备的驱动。

3.3 sysfs 文件系统与 Linux 设备模型

Linux 2.6 以后内核引入了 sysfs 文件系统,与 proc 类似。sysfs 把连接在系统上的设备和总线组织成为一个分级的文件,它们可以由用户空间存取,向用户空间导出内核数据结构以及它们的属性。sysfs 的一个目的就是展示设备驱动模型中各组件的层次关系。其顶级目录包含 block、bus、devices、class、fs、kernel、power 和 firmware 等。

block 目录包含所有的块设备;devices 目录包含系统所有的设备,并根据设备挂接的总线类型组织称层次结构;bus 目录包含系统中所有的总线类型;class 目录包含系统中的设备类型(如网卡设备、声卡设备、输入设备等)。

在 /sys/bus 的 pci 等子目录下,又会再分出 drivers 和 devices 目录中的文件是对 /sys/devices 目录中文件的符号链接。同样地,/sys/class 目录下也包含许多对 /sys/devices 下文件的链接。Linux 2.6 以后内核的设备模型。

Linux 设备模型

Linux 内核中,分别使用 bus_type、device_driver 和 device 来描述总线、驱动和设备,这 3 个结构体定义于 include/linux/device.h 头文件中,其定义如代码清单:

struct bus_type{

const char *name;

const char *dev_name;

struct device *dev_root;

...

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; //type of bus device is on

struct device_driver *driver; //which driver has allocated this device

...

};

device_driver 和 device 分别表示驱动和设备,而这两者都必须依附于一种总线,因此都包含 struct bus_type 指针。在 Linux 内核中,设备和驱动都是分开注册的,注册一个设备时并不需要驱动已经存在,而 1个驱动被注册的时候,也不需要对应设备已经存在,设备和驱动各自涌向内核,并寻找自己的另一半,而正是 bus_type 的match() 成员函数将两者捆绑在一起,一旦配对成功,对应总线的xxx_driver 的probe() 就被执行(xxx是总线名,如platform、pci、i2c、spi、usb等)。

总线、驱动和设备最终都会落实为 sysfs 中的 1 个目录,因为进一步追踪代码会发现,它们实际上可以认为是 kobject 的派生类,kobject 可以看作是所有总线、设备和驱动的抽象基类,1 个 kobject 对应 sysfs 中的一个目录。

总线、设备和驱动中的各个 attribute 则直接落实为 sysfs 中的 1 个文件,attribute 会伴随着 show() 和 store() 这两个函数,分别用于读写 attribute 对应的 sysfs 文件,代码给出了 attribute、bus_attribute、driver_attribute 和 device_attribute 这几个结构体的定义。

struct attribute{

const char *name;

umode_t mode;

#ifdef CONFiG_DEBUG_LOCK_ALLOC

bool ignore_lockdep:1;

struct lock_class_key *key;

struct lock_class_key *key;

#endif

};

struct bus_attribute{

struct attribute sttr;

ssize_t (*show)(struct bus_type *bus, char *buf);

ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);

};

struct driver_attribute{

struct attribute attr;

ssize_t (*show)(struct device_driver *driver, char *buf);

ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count);

};

struct device_attribute{

struct attribute attr;

ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);

ssize_t (*store)(struct device *dev, struct device_attribute *attr,

const char *buf, size_t count);

};

事实上,sysfs 中的目录来源于 bus_type、device_driver、device,而目录中的文件则来源于 attribute。

比如,我们在 drivers/base/bus.c文件中可以找到这样的代码:

static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);

static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,

show_drivers_autoprobe, store_drivers_autoprobe);

static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);

3.4 udev 的组成

udev 在用户空间执行,动态建立/删除设备文件,允许每个人都不用关心主/次设备号而提供LSB(Linux 标准规范, Linux Standard Base)名称,并可以根据需要固定名称。

udev 的工作过程如下:

当内核检测到系统中出现了新设备后,内核会通过 netlink 套接字发送uevent。

udev 获取内核发送的信息,进行规则匹配。匹配的事物包括 SUBSYSTEM、ACTION、attribute、内核提供的名称(通过 KERNEL ==),以及其它环境变量。

以下是利用命令行工具 udevadm monitor --kernel --property --udev 捕获到一个虚拟机 Ubuntu 16.4.1 退出虚拟 ISO 文件的打印:

KERNEL[15730.816373] change /devices/pci0000:00/0000:00:1f.1/ata2/host1/target1:0:0/1:0:0:0/block/sr0 (block)

ACTION=change

DEVNAME=/dev/sr0

DEVPATH=/devices/pci0000:00/0000:00:1f.1/ata2/host1/target1:0:0/1:0:0:0/block/sr0

DEVTYPE=disk

DISK_MEDIA_CHANGE=1

MAJOR=11

MINOR=0

SEQNUM=4324

SUBSYSTEM=block

UDEV [15730.923482] change /devices/pci0000:00/0000:00:1f.1/ata2/host1/target1:0:0/1:0:0:0/block/sr0 (block)

ACTION=change

DEVLINKS=/dev/cdrom /dev/disk/by-id/ata-VBOX_CD-ROM_VB2-01700376 /dev/dvd /dev/disk/by-path/pci-0000:00:1f.1-ata-2

DEVNAME=/dev/sr0

DEVPATH=/devices/pci0000:00/0000:00:1f.1/ata2/host1/target1:0:0/1:0:0:0/block/sr0

DEVTYPE=disk

DISK_MEDIA_CHANGE=1

ID_ATA=1

ID_BUS=ata

ID_CDROM=1

ID_CDROM_CD=1

ID_CDROM_DVD=1

ID_CDROM_MRW=1

ID_CDROM_MRW_W=1

ID_FOR_SEAT=block-pci-0000_00_1f_1-ata-2

ID_MODEL=VBOX_CD-ROM

ID_MODEL_ENC=VBOX\x20CD-ROM\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

ID_PATH=pci-0000:00:1f.1-ata-2

ID_PATH_TAG=pci-0000_00_1f_1-ata-2

ID_REVISION=1.0

ID_SERIAL=VBOX_CD-ROM_VB2-01700376

ID_SERIAL_SHORT=VB2-01700376

ID_TYPE=cd

MAJOR=11

MINOR=0

SEQNUM=4324

SUBSYSTEM=block

SYSTEMD_READY=0

TAGS=:uaccess:systemd:seat:

USEC_INITIALIZED=1513182

可以根据以上信息,创建一个规则,以便每次插入的时候,为该盘创建一个 /dev/testISO 的符号链接。

#iso

SUBSYSTEM=="block", ACTION=="change", KERNEL="*sr?", ENV{ID_TYPE}=="cd", SYMLINK+="testISO"

TO-DO: 实验未成功

3.5 udev 规则文件

udev 的规则文件以行为单位,以 “#” 开头的行代表注释行。其余的每一行代表一个规则。每个规则分成多个匹配部分和赋值部分。匹配部分用匹配专用的关键字来表示,匹配关键字包括:ACTION、KERNEL、BUS、SUBSYSTEM、ATTR等;赋值部分用赋值专用关键字,赋值关键字包括:NAME(创建的设备文件文件名)、SYSLINK(符号创建链接名)、OWNER(设置设备的所有者)、GROUP(设置设备的组)、IMPORT(调用外部程序)、MODE(节点访问权限)等。

由此可知,udev 可以实现设备的名称或者符号链接的自定义,通过规则文件,匹配相应设备,从而实现自定义设备名称,而devfs 则无法做到。

linux设备文件,Linux 文件系统与设备文件相关推荐

  1. c++创建文件_JavaNote 文件系统及Java文件基本操作

    文件概述 文件系统是由OS(操作系统)管理的: 文件系统和Java进程是平行的,是两套系统: 文件系统是由文件夹和文件递归组合而成: 文件目录分隔符 Linux/Unix 用/隔开 Windows用隔 ...

  2. 【Unity3D】资源文件 ① ( Unity 中常用的文件类型 | Unity 文件操作 | 文件系统中查看文件 | 添加文件 | 删除文件 | 导入文件 | 复制文件 | 缩略图显示 )

    文章目录 一.Unity 中常用的文件类型 二.Unity 文件操作 1.文件系统中查看文件 2.添加目录 / 文件 3.删除目录 / 文件 4.导入资源 5.复制资源 6.缩略图显示 7.meta ...

  3. Linux TF卡ext4文件系统录制视频文件用拷贝不用移动的影响

    环境 TF卡ext4文件系统录制大量视频文件后,出现TF卡损坏不能写的情况,探究用拷贝不用移动的影响. 分析 录制5分钟视频,临时文件名为开始时间,录制结束后拷贝到TF卡其他目录,并把结束时间加在文件 ...

  4. linux内核源码实战_3.2理解设备驱动和文件系统

    linux内核源码实战_3.2理解设备驱动和文件系统 linux内核源码实战_理解设备驱动和文件系统 理解设备驱动和文件系统 理解设备驱动和文件系统详解 7-文件系统-proc文件系统实现 总结 li ...

  5. 《Linux4.0设备驱动开发详解》笔记--第五章:Linux文件系统与设备文件

    5.1 Linux文件操作 5.1.1 文件操作系统调用 创建 int create(const char *filename, mode_t mode); mode是存取权限,它同umask(在文件 ...

  6. 【Linux】Linux 文件系统与设备文件

    Ref: <Linux设备驱动开发详解:基于最新的Linux4.0内核>中的第5章<Linux 文件系统与设备文件> 基于Linux 5.10   本文结合源码和实例分析了Li ...

  7. Linux文件系统与设备文件

    打开 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode) ...

  8. linux下echo /dev/ttys* 到字符设备文件,linux之tty pty pts

    一.基本概念: 1> tty(终端设备的统称): tty一词源于Teletypes,或者teletypewriters,原来指的是电传打字机,是通过串行线用打印机键盘通过阅读和发送信息的东西,后 ...

  9. linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化

    因为Linux字符设备驱动主要依赖于struct cdev结构,原型为: 所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:[unsigned int count;].[de ...

最新文章

  1. SDN第二次上机作业
  2. 什么是跨域?怎么解决跨域问题?
  3. Hadoop 开源调度系统zeus(二)
  4. linux管道通信机制有哪两种,linux的管道通信机制
  5. Java解析xml的主要解析器: SAX和DOM的选择(附上新方法--Pull解析)
  6. vue 数据劫持 响应式原理 Observer Dep Watcher
  7. 开源DataGridView扩展(6) 简单实现checkbox行选级联。
  8. elasticsearch-head后台运行
  9. 领域搜索算法 是什么 和遗传算法、模拟退火算法、禁忌搜索算法、模糊优化 算法、微粒群算法关系
  10. Arduino控制微小的六足3D打印机器人
  11. nbu备份文件失败,提示信息NBU status: 2074, EMM status: Disk volume is down
  12. 通过Snap7与 LOGO!8PLC通信
  13. 计算机有损压缩编码,有损压缩格式有哪些
  14. 都是肿瘤模型,凭什么说肿瘤类器官是“试药替身”?
  15. adb 判断imei_获取设备序列号 SN码(对应:设置-关于手机-状态-序列号 )
  16. 算法基础--梯度消失的原因
  17. pytorch——梯度计算
  18. Photoshop插件-保存-8位通道-16位通道-脚本开发-PS插件
  19. 获取VM中win10虚拟机映像
  20. 浏览器内核、渲染引擎、x86与RAM架构等基本概念

热门文章

  1. [BZOJ1614][Usaco2007 Jan]Telephone Lines架设电话线
  2. 替换插件解决Microsoft Edge浏览器Flash Player地区不兼容问题
  3. 如何绘制员工数据管理业务流程图?分享员工数据管理业务流程图模板
  4. POJ - 1961 Period(nex数组的运用)
  5. 调用mathpix api识别图片中的公式
  6. Windows远程登陆Linux桌面的方法
  7. Java 复杂表头、表格导出 - EasyExcel
  8. 智能电子压力开关的组成
  9. 【FPGA】初探FPGA —— 入门过程的分享
  10. Android10 Launcher3调整文件夹图标的默认布局