Linux驱动开发_设备文件系统详解

 

目录

何为设备管理器?

Linux下dev的作用

Devfs

sysfs

kobject

udev

proc


何为设备管理器?

设备管理器就是负责管理这台电脑上的外设,当我们通过电脑提供的USB口插入一个键盘、鼠标时设备管理器会与其通讯来确认你插入的到底是一个什么样的设备,然后在创建对应的设备驱动。

以上的前提下是你的设备是流行设备且被操作系统的设备管理器支持的情况下,倘若我们有一个未知的设备,或者是我自己开发的硬件产品,如我们自己写的键盘,我们不使用通用键盘通讯协议,我们非要自己创建一套我们键盘自己的协议,包括内部架构、PCB都是我们自己设计的,一个全新的键盘,虽然也是键盘但是协议,与硬件架构不同于ISO标准,所以操作系统的设备管理器无法识别,因为设备管理器里没有对应的驱动程序来与你建立通讯,同时你也不符合标准ISO的获取设备类型协议,所以它不知道你到底是个啥。

这个时候我们就要用到驱动了,驱动就是用来与你的设备建立通讯,并传递给操作系统,在Windows上需要一个配置文件,用来告诉设备管理器某些设备使用什么驱动,所以最初我们安装的时候一般windows上的驱动程序都由配置文件和驱动程序组成,配置文件用来告诉操作系统我们的设备描述信息,并告知操作系统如果遇到这样的设备请调哪个驱动来处理,在通过驱动将数据传递给操作系统,由驱动来解析协议。

所以驱动就是一个中间层,那么调用驱动就由设备管理器来调用,这一切是由设备管理器来负责完成从设备识别到驱动调用,在这个过程中你的设备要符合iso的通讯标准,否则操作系统不会去认,因为现代操作系统已经遵守iso国际化标准。

Linux下dev的作用

最早的Linux版本在Linux内核2.4之前,Linux内核支持外设时的方法是在内核里主动增加你的硬件设备,以及增加你的硬件交互代码,非常麻烦。

当时如果你有了一个新的设备,需要让Linux支持的话,你需要发邮件去联系Linux内核社区的维护人员,让他们去给你增加你的设备,然后在新的Linux内核上线之后才能看见你的设备,这个时候你的设备存在/dev目录下的。

用户无法主动去增加自己的设备,这导致了一个问题,当设备不存在时,/dev目录下仍有这个设备,时间久了你会发现/dev目录下充斥着巨多不知名的设备类型,当然因为Linux内核是开源的所以你可以自己手动去维护一个内核版本,但是这个成本太高了。

Devfs

为了解决dev的问题,所以到了Linux2.4版本以后,引入了一个devfs,也就是一个设备管理器,用于管理/dev目录,同时增加许多内核库文件,面向用户,允许用户手动调用函数向devfs注册你的设备,然后devfs在将设备注册到内核表中之后,在注册到/dev目录下。

这解决了一个很大的问题,让Linux内核灵活了很多,因为支持了内核驱动编写的lib库,用户可以通过调用对应的函数来注册自己的设备,并实例化open、write、read这些函数,这个方式是引用自unix技术,使得文件系统用户层不用关心底层代码是如何实现的,只需要调用用户态的open、write、read就能实现对硬件的打开,写入,读取控制方式,这一方法被引用自VFS系统。

这让Linux不在每次都充斥着许多不知名的设备,因为这样不仅拖累了内核,也让Linux变得非常没有扩展性。

Devfs很好的解决了这个问题。

Devfs只是负责管理与注册设备到vfs中。

如果想知道vfs是如何抽象调用不同的open和write、read函数的可以参见我的这篇文章:Linux嵌入式开发_主设备号与次设备号详解

sysfs

sysfs是Linux2.6推出的一个新的文件系统,它的文件挂载点在/sys目录下,在最早刚推出时是没有明确规定挂载在哪个目录下,可以挂载在任意目录下,sysfs的挂载方式与devfs有很大的不同,当我们挂载到/dev下时候,devfs会在这个目录下创建一个文件,并给对应的权限,c表示是字符设备,b是块设备文件, s是socket文件,你可以在/dev目录下使用ls命令看一下

可以看到最前面有一个c,代表这是一个字符设备,通过使用open函数打开这些文件,然后在通过devfs去在vfs表里去寻找我们的设备对应的模块,调用模块里的open以及read、write的函数指针。

sys的挂载文件就是一个目录,目录里包含了许多文件,每个文件对应不同的信息,这些文件里描述着设备的所有信息,但是不是由sysfs来注册到vfs内核文件表里的,sysfs只是提供了一组文件操作函数,这系列函数用于注册到VFS虚拟文件系统中关于sysfs的相关表里。

说到sysfs的具体实现就不得不提一个结构体:kobject

这里给大家补充一下这个知识

在VFS虚拟文件系统这块里有这样一个结构体:

struct kobject {
	const char		*name;
	struct list_head	entry;
	struct kobject		*parent;
	struct kset		*kset;
	struct kobj_type	*ktype;
	struct kernfs_node	*sd; /* sysfs directory entry */
	struct kref		kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	struct delayed_work	release;
#endif
	unsigned int state_initialized:1;
	unsigned int state_in_sysfs:1;
	unsigned int state_add_uevent_sent:1;
	unsigned int state_remove_uevent_sent:1;
	unsigned int uevent_suppress:1;
}

这个结构体原本是用来存储Linux 设备文件描述信息的,在Linux内核中,使用四大模型来描述Linux设备模型:Bus(总线)、Class(设备类)、Device(设备)、Driver(驱动),这四个都是数据结构体,组成一个Linux设备模型,针对这四个模型,我后面会写文章介绍,这里大家先大致知道这个概念。

为了方便管理这四个结构体所以就诞生了kobject结构体,这个结构体里对这四大模型结构体进行了封装,并且通过parent指针把所有的层次结构关联起来,可以利用这个结构体找到sys目录下的相联目录

同时它也具有引用计数的算法,当被引用时计数+1,当被解除一次引用时-1,到引用为0时Linux内核会释放掉这个设备模型,因为Linux内核不可能因为多个用户使用一个驱动设备就去给你分配多个实例化吧,太浪费了。

并且这个结构体是专门为sysfs诞生的。

这里给大家简单描述一下kobject结构体里的成员作用:

kobject

name:该Kobject的名称,同时也是sysfs中的目录名称。由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。如果需要修改Kobject的名字,需要调用kobject_rename接口,该接口会主动处理sysfs的相关事宜。

entry:用于将Kobject加入到Kset中的list_head。

parent:指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。

kset:该kobject属于的Kset。可以为NULL。如果存在,且没有指定parent,则会把Kset作为parent(别忘了Kset是一个特殊的Kobject)。

ktype:该Kobject属于的kobj_type。每个Kobject必须有一个ktype,或者Kernel会提示错误。
sd:该Kobject在sysfs中的表示。

kref:"struct kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。

state_initialized,指示该Kobject是否已经初始化,以在Kobject的Init,Put,Add等操作时进行异常校验。

state_in_sysfs:指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。

state_add_uevent_sent/state_remove_uevent_sent:记录是否已经向用户空间发送ADD uevent,如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,以便让用户空间正确处理。

uevent_suppress:如果该字段为1,则表示忽略所有上报的uevent事件,此成员是为udev提供的

可以看到有许多针对sysfs的变量,最早它是没有的,在Linux2.6版本推出sysfs时,为了支持sysfs文件系统,对kobject进行了修改。

因为kobject在内核中的主要作用就是描述通过sysfs注册文件设备模型。

在内核寻找实现时在VFS中会遍历所有的文件链表,其中在2.6以后的版本就会遍历这个链表。

这里给大家看一下各成员结构体原型:

kset

/**
 * struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
 *
 * A kset defines a group of kobjects.  They can be individually
 * different "types" but overall these kobjects all want to be grouped
 * together and operated on in the same manner.  ksets are used to
 * define the attribute callbacks and other common events that happen to
 * a kobject.
 *
 * @list: the list of all kobjects for this kset
 * @list_lock: a lock for iterating over the kobjects
 * @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
 * @uevent_ops: the set of uevent operations for this kset.  These are
 * called whenever a kobject has something happen to it so that the kset
 * can add new environment variables, or filter out the uevents if so
 * desired.
 */
struct kset {
	struct list_head list;
	spinlock_t list_lock;
	struct kobject kobj;
	const struct kset_uevent_ops *uevent_ops;
};

参数介绍:

list/list_lock:用于保存该kset下所有的kobject的链表。

kobj:该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。

uevent_ops:该kset的uevent操作函数集。当任何Kobject需要上报uevent时,都要调用它所从属的kset的

uevent_ops:添加环境变量,或者过滤event(kset可以决定哪些event可以上报)。因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。

kobj_type

struct kobj_type {
	void (*release)(struct kobject *kobj);
	const struct sysfs_ops *sysfs_ops;
	struct attribute **default_attrs;
	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
	const void *(*namespace)(struct kobject *kobj);
};

参数介绍:

release:通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。

sysfs_ops:该种类型的Kobject的sysfs文件系统接口。

default_attrs:该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的文件)。将会在Kobject添加到内核时,一并注册到sysfs中。

child_ns_type/namespace:和文件系统(sysfs)的命名空间有关

可以看到Linux内核把许多通用的结构体类型都抽象到一个结构体中,在VFS进行遍历时会寻找通用类型在结构体里进行遍历寻找对应的vfs open、wirte、read的实现。

其中kobj_type里的release用来释放当前模型的内存空间,只有在引用为0的情况下,release才会真正释放,否则只会递减引用。

当我们在驱动开发时若要使用这个驱动模型的话也提供了对应的函数:

void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...)
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,              struct kobject *parent, const char *fmt, ...)
struct kobject *kobject_create(void)
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)//设置kobject名称

这里只是告诉大家kobject与sysfs的对应关系,sysfs依赖kobject结构体,sysfs会把所有的信息注册到kobject中,供内核维护使用。

上面说过sysfs注册的文件会挂载在/sys目录下,我们打开sys目录看看:

这些目录对应不同的类型设备:

devices:该目录下是全局设备结构体系,包含所有被发现的注册在各种总线上的各种物理设备。一般来说,所有的物理设备都按其在总线上的拓扑结构来显示,但有两个例外即platform devices和system devices。

dev:该目录下存放主次设备号文件,其中分成字符设备、块设备的主次设备号码(major:minor)组成的文件名,该文件是链接文件并且链接到其真实的设备(/sys/devices)。

class:该目录下包含所有注册在kernel里面的设备类型,这是按照设备功能分类的设备模型,每个设备类型表达具有一种功能的设备。每个设备类型子目录下都是这种设备类型的各种具体设备的符号链接,这些链接指向/sys/devices/下的具体设备。 设备类型和设备并没有一一对应的关系,一个物理设备可能具备多种设备类型;一个设备类型只表达具有一种功能的设备,比如:系统所有输入设备都会出现在/sys/class/input之下,而不论它们是以何种总线连接到系统的。

block:该目录下的所有子目录代表着系统中当前被发现的所有块设备。按照功能来说放置在/sys/class下会更合适,但由于历史遗留因素而一直存在于/sys /block,但从linux2.6.22内核开始这部分就已经标记为过去时,只有打开了CONFIG_SYSFS_DEPRECATED配置编译才会有 这个目录存在,并且其中的内容在从linux2.6.26版本开始已经正式移到了/sys/class/block,旧的接口/sys/block为了向后兼容而保留存在,但其中的内容已经变为了指向它们在/sys/devices/中真实设备的符号链接文件。

bus:该目录下的每个子目录都是kernel支持并且已经注册了的总线类型。这是内核设备按照总线类型分层放置的目录结构,/sys/devices中的所有设备都是连接于某种总线之下的,bus子目录下的每种具体总线之下可以找到每个具体设备的符号链接,一般来说每个子目录(总线类型)下包含两个子目录,一个是 devices,另一个是drivers;其中devices下是这个总线类型下的所有设备,这些设备都是符号链接,它们分别指向真正的设备(/sys/devices/下);而drivers下是所有注册在这个总线上的驱动,每个driver子目录下 是一些可以观察和修改的driver参数。

fs:按照设计,该目录使用来描述系统中所有的文件系统,包括文件系统本身和按照文件系统分类存放的已挂载点。

kernel:这个目录下存放的是内核中所有可调整的参数。

firmware:这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API,在附录 LDD3 一书中有关于内核支持固件加载机制的更详细的介绍;

module:该目录下有系统中所有的模块信息,不论这些模块是以内联(inlined)方式编译到内核映像文件中还是编译为外模块(.ko文件),都可能出现在/sys/module中。即module目录下包含了所有的被载入kernel的模块。

power:该目录是系统中的电源选项,对正在使用的power子系统的描述。这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机/重启等等。

你可以根据你的设备类型注册到对应目录下,当你注册到对应目录下时,都会生成一个与你设备名一致的名字的目录,这个目录会在内核里注册一个kobject结构体与其对应起来,同时里面的parent成员用来指向子目录的层次结构。

甚至你可以通过修改目录下的一些文件属性来控制硬件.

如msp700修改背光

这样做的前提是这个设备驱动支持你这样做,可以根据厂商给的手册。

因为msp700的驱动是读取这个文件来设置背光的。

echo 20 > /sys/class/backlight/pwm-backlight/brightness;

udev

其实在介绍完sysfs后,udev就非常简单不难理解了,udev是在2.6.x之后推出的,它是在sysfs之后推出的,因为它基于sysfs。

用来管理优化devfs的,前面也说过devfs存在一些问题,如不够灵活,无法自动识别设备,当我们插入一个通用usb时,它不能给出usb的名字,当我们设备较多的情况下压根找到哪个是哪个设备,甚至无法知道哪个设备是usb,无法区分设备类型,同时也无法提供对设备的热拔插事件。

所以udev的推出解决了这个问题,它是依赖sysfs的。

它通过解析sysfs的目录,并把设备注册到/dev目录下。

同时会监听热拔插事件提供给用户态,用户就可以知道当前是否有设备插入与弹出。

它是运行在用户态的,以守护进程的方式运行。

同时也具有自动识别设备的功能。

如我们插入一个硬盘,会自动识别为/dev/hda1

所以我们就可以知道我们的硬盘在哪儿,同时它还为每个设备提供了一个唯一的文件系统统一ID,同时也支持自定义规则。

如果想修改udev的规则可以在这个文件中修改:

/etc/udev/udev.conf

proc

proc是一种伪虚拟文件系统,存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。

为什么说它是伪虚拟文件系统,因为它会实时监听系统里内核以及进程的状态,并实时更新文件。

当我们查看时会发现目录下全是以数字为命名的目录:

这些数字是PID,这些PID对应进程,也就是说这些目录里存储着每个PID进程的运行状态。

我们可以随便打开一个看看:

每个文件对应不同的属性,如就拿cmdline来说,它对应的是启动时的完整命令,我们看打开看看:

/usr/libexec/goa-identity-service

fd目录里包含了这个程序目前使用的文件描述符,这些文件描述符是一个符号链接:

/dev/null
socket:[58]

可以看到socket的描述符,port也能看到

所以在这个目录下你能看到所有的进程相关信息。

这里给大家介绍一下常见的文件目录作用:

cmdline:启动当前进程的完整命令,但僵尸进程目录中的此文件不包含任何信息

cwd:指向当前进程运行目录的一个符号链接

environ:当前进程的环境变量列表,彼此间用空字符(NULL)隔开;变量用大写字母表示,其值用小写字母表示

exe:指向启动当前进程的可执行文件(完整路径)的符号链接,通过/proc/N/exe可以启动当前进程的一个拷贝

fd:这是个目录,包含当前进程打开的每一个文件的文件描述符(file descriptor),这些文件描述符是指向实际文件的一个符号链接

limits:当前进程所使用的每一个受限资源的软限制、硬限制和管理单元;此文件仅可由实际启动当前进程的UID用户读取;(2.6.24以后的内核版本支持此功能)

maps:当前进程关联到的每个可执行文件和库文件在内存中的映射区域及其访问权限所组成的列表

mem:当前进程所占用的内存空间,由open、read和lseek等系统调用使用,不能被用户读取

root:指向当前进程运行根目录的符号链接;在Unix和Linux系统上,通常采用chroot命令使每个进程运行于独立的根目录

stat:当前进程的状态信息,包含一系统格式化后的数据列,可读性差,通常由ps命令使用

statm:当前进程占用内存的状态信息,通常以“页面”(page)表示

status:与stat所提供信息类似,但可读性较好,如下所示,每行表示一个属性信息;其详细介绍请参见 proc的man手册页

task:目录文件,包含由当前进程所运行的每一个线程的相关信息,每个线程的相关信息文件均保存在一个由线程号(tid)命名的目录中,这类似于其内容类似于每个进程目录中的内容;(内核2.6版本以后支持此功能)

proc常见目录介绍:

apm:高级电源管理(APM)版本信息及电池相关状态信息,通常由apm命令使用

buddyinfo:用于诊断内存碎片问题的相关信息文件

cmdline:在启动时传递至内核的相关参数信息,这些信息通常由lilo或grub等启动管理工具进行传递

cpuinfo:处理器的相关信息的文件

crypto:系统上已安装的内核使用的密码算法及每个算法的详细信息列表

devices:系统已经加载的所有块设备和字符设备的信息,包含主设备号和设备组(与主设备号对应的设备类型)名

diskstats:每块磁盘设备的磁盘I/O统计信息列表;(内核2.5.69以后的版本支持此功能)

dma:每个正在使用且注册的ISA DMA通道的信息列表

execdomains:内核当前支持的执行域(每种操作系统独特“个性”)信息列表

fb:帧缓冲设备列表文件,包含帧缓冲设备的设备号和相关驱动信息

filesystems:当前被内核支持的文件系统类型列表文件,被标示为nodev的文件系统表示不需要块设备的支持;通常mount一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型

interrupts:X86或X86_64体系架构系统上每个IRQ相关的中断号列表;多路处理器平台上每个CPU对于每个I/O设备均有自己的中断号

iomem:每个物理设备上的记忆体(RAM或者ROM)在系统内存中的映射信息

ioports:当前正在使用且已经注册过的与物理设备进行通讯的输入-输出端口范围信息列表;如下面所示,第一列表示注册的I/O端口范围,其后表示相关的设备

kallsyms:模块管理工具用来动态链接或绑定可装载模块的符号定义,由内核输出;(内核2.5.71以后的版本支持此功能);通常这个文件中的信息量相当大

kcore:系统使用的物理内存,以ELF核心文件(core file)格式存储,其文件大小为已使用的物理内存(RAM)加上4KB;这个文件用来检查内核数据结构的当前状态,因此,通常由GBD通常调试工具使用,但不能使用文件查看命令打开此文件

kmsg:此文件用来保存由内核输出的信息,通常由/sbin/klogd或/bin/dmsg等程序使用,不要试图使用查看命令打开此文件

loadavg:保存关于CPU和磁盘I/O的负载平均值,其前三列分别表示每1秒钟、每5秒钟及每15秒的负载平均值,类似于uptime命令输出的相关信息;第四列是由斜线隔开的两个数值,前者表示当前正由内核调度的实体(进程和线程)的数目,后者表示系统当前存活的内核调度实体的数目;第五列表示此文件被查看前最近一个由内核创建的进程的PID

locks:保存当前由内核锁定的文件的相关信息,包含内核内部的调试数据;每个锁定占据一行,且具有一个惟一的编号;如下输出信息中每行的第二列表示当前锁定使用的锁定类别,POSIX表示目前较新类型的文件锁,由lockf系统调用产生,FLOCK是传统的UNIX文件锁,由flock系统调用产生;第三列也通常由两种类型,ADVISORY表示不允许其他用户锁定此文件,但允许读取,MANDATORY表示此文件锁定期间不允许其他用户任何形式的访问

mdstat:保存RAID相关的多块磁盘的当前状态信息,在没有使用RAID机器上

meminfo:系统中关于当前内存的利用状况等的信息,常由free命令使用;可以使用文件查看命令直接读取此文件,其内容显示为两列,前者为统计属性,后者为对应的值

mounts:在内核2.4.29版本以前,此文件的内容为系统当前挂载的所有文件系统,在2.4.19以后的内核中引进了每个进程使用独立挂载名称空间的方式,此文件则随之变成了指向/proc/self/mounts(每个进程自身挂载名称空间中的所有挂载点列表)文件的符号链接

modules:当前装入内核的所有模块名称列表,可以由lsmod命令使用,也可以直接查看

partitions:块设备每个分区的主设备号(major)和次设备号(minor)等信息,同时包括每个分区所包含的块(block)数目

pci:内核初始化时发现的所有PCI设备及其配置信息列表,其配置信息多为某PCI设备相关IRQ信息,可读性不高,可以用“/sbin/lspci –vb”命令获得较易理解的相关信息;在2.6内核以后,此文件已为/proc/bus/pci目录及其下的文件代替

slabinfo:在内核中频繁使用的对象(如inode、dentry等)都有自己的cache,即slab pool,而/proc/slabinfo文件列出了这些对象相关slap的信息;详情可以参见内核文档中slapinfo的手册页

stat:实时追踪自系统上次启动以来的多种统计信息

swaps:当前系统上的交换分区及其空间利用信息,如果有多个交换分区的话,则会每个交换分区的信息分别存储于/proc/swap目录中的单独文件中,而其优先级数字越低,被使用到的可能性越大

uptime:系统上次启动以来的运行时间

version:当前系统运行的内核版本号

vmstat:当前系统虚拟内存的多种统计数据,信息量可能会比较大,这因系统而有所不同,可读性较好

zoneinfo:内存区域(zone)的详细信息列表

sys:与/proc下其它文件的“只读”属性不同的是,管理员可对/proc/sys子目录中的许多文件内容进行修改以更改内核的运行特性,事先可以使用“ls -l”命令查看某文件是否“可写入”。写入操作通常使用类似于“echo  DATA > /path/to/your/filename”的格式进行。需要注意的是,即使文件可写,其一般也不可以使用编辑器进行编辑。

/proc/sys/debug 子目录
此目录通常是一空目录;

/proc/sys/dev 子目录
为系统上特殊设备提供参数信息文件的目录,其不同设备的信息文件分别存储于不同的子目录中,如大多数系统上都会具有的/proc/sys/dev/cdrom和/proc/sys/dev/raid(如果内核编译时开启了支持raid的功能) 目录,其内存储的通常是系统上cdrom和raid的相关参数信息文件。

上面这些大多数虚拟文件可以使用文件查看命令如cat、more或者less进行查看,有些文件信息表述的内容可以一目了然,但也有文件的信息却不怎么具有可读性。不过,这些可读性较差的文件在使用一些命令如apm、free、lspci或top查看时却可以有着不错的表现。

除了udev是运行在用户空间以外,其它的所有文件系统都属于内核的一部分。

在VFS中,会把这些结构体抽象出来,并利于用户调用open函数发生中断时,遍历寻找对应的模块指针。

它是通过systemd服务进行管理的。

 

相关推荐
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页