linux设备模型介绍

合集下载

Linux的电源管理架构

Linux的电源管理架构

Linux的电源管理架构Linux的源代码里,大部分都属于设备驱动程序的代码,因此,大多数电源管理(PM)的代码也是存在于驱动程序当中。

很多驱动程序可能只做了少量的工作,另外一些,例如使用电池供电的硬件平台(移动电话等)则会在电源管理上做了大量的工作。

这份文档对驱动程序如何与系统的电源管理部分交互做了一个大概的描述,尤其是关联到驱动程序核心中的模型和接口的共享,建议从事驱动程序相关领域的人通过本文档可以了解相关的背景知识。

设备电源管理的两种模型===================================驱动程序可以使用其中一种模型来使设备进入低功耗状态:1. 系统睡眠模型:驱动程序作为一部分,跟随系统级别的低功耗状态,就像”suspend”(也叫做”suspend-to-RAM”),或者对于有硬盘的系统,可以进入”hibernation”(也叫做”suspend-to-disk”)。

这种情况下,驱动程序,总线,设备类驱动一起,通过各种特定于设备的suspend和resume 方法,清晰地关闭硬件设备和各个软件子系统,然后在数据不被丢失的情况下重新激活硬件设备。

有些驱动程序可以管理硬件的唤醒事件,这些事件可以让系统离开低功耗状态。

这一特性可以通过相应的/sys/devices/…/power /wakeup文件来开启和关闭(对于Ethernet驱动程序,ethtool通过ioctl接口达到同样的目的);使能该功能可能会导致额外的功耗,但他让整个系统有更多的机会进入低功耗状态。

2. Runtime 电源管理模型:这种模型允许设备在系统运行阶段进入低功耗状态,原则上,他可以独立于其他的电源管理活动。

不过,通常设备之间不能单独进行控制(例如,父设备不能进入suspend,除非他的所有子设备已经进入suspend状态)。

此外,依据不同的总线类型,可能必须做出一些特别的操作来达到目的。

如果设备在系统运行阶段进入了低功耗状态,在系统级别的电源状态迁移时(suspend或hibernation)就必须做出特别的处理。

Linux设备驱动程序原理及框架-内核模块入门篇

Linux设备驱动程序原理及框架-内核模块入门篇

Linux设备驱动程序原理及框架-内核模块入门篇内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块内核模块介绍Linux采用的是整体式的内核结构,这种结构采用的是整体式的内核结构,采用的是整体式的内核结构的内核一般不能动态的增加新的功能。

为此,的内核一般不能动态的增加新的功能。

为此,Linux提供了一种全新的机制,叫(可安装) 提供了一种全新的机制,可安装) 提供了一种全新的机制模块” )。

利用这个机制“模块”(module)。

利用这个机制,可以)。

利用这个机制,根据需要,根据需要,在不必对内核重新编译链接的条件将可安装模块动态的插入运行中的内核,下,将可安装模块动态的插入运行中的内核,成为内核的一个有机组成部分;成为内核的一个有机组成部分;或者从内核移走已经安装的模块。

正是这种机制,走已经安装的模块。

正是这种机制,使得内核的内存映像保持最小,的内存映像保持最小,但却具有很大的灵活性和可扩充性。

和可扩充性。

内核模块内核模块介绍可安装模块是可以在系统运行时动态地安装和卸载的内核软件。

严格来说,卸载的内核软件。

严格来说,这种软件的作用并不限于设备驱动,并不限于设备驱动,例如有些文件系统就是以可安装模块的形式实现的。

但是,另一方面,可安装模块的形式实现的。

但是,另一方面,它主要用来实现设备驱动程序或者与设备驱动密切相关的部分(如文件系统等)。

密切相关的部分(如文件系统等)。

课程内容内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块应用层加载模块操作过程内核引导的过程中,会识别出所有已经安装的硬件设备,内核引导的过程中,会识别出所有已经安装的硬件设备,并且创建好该系统中的硬件设备的列表树:文件系统。

且创建好该系统中的硬件设备的列表树:/sys 文件系统。

(udev 服务就是通过读取该文件系统内容来创建必要的设备文件的。

)。

Linux设备驱动模型与sysfs---platform总线设备驱动

Linux设备驱动模型与sysfs---platform总线设备驱动

Linux在2.6版本引入了设备驱动模型,设备驱动模型负责统一实现和维护一些特性,诸如:热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施1.设备驱动模型基本概念设备驱动模型主要包含:类(class)、总线(bus)、设备(device)、驱动(driver),它们的本质都是内核中的几种数据结构的“实例”∙类的本质是class结构体类型,各种不同的类其实就是class的各种实例∙总线的本质是bus_type结构体类型,各种不同的总线其实就是bus_type的各种实例∙设备的本质是device结构体类型,各种不同的设备其实就是device的各种实例∙驱动的本质是device_driver结构体类型,各种不同的驱动其实就是device_driver的各种实例2.sysfs基本概念sysfs其实就是/sys目录,其主要作用就是:展示设备驱动模型中各组件的层次关系,并将各组件的本体——内核中的数据结构以文件形式呈现,方便用户层查看及操作3./sys目录结构与设备驱动模型∙/sys目录结构很好的展示了驱动设备模型,如图:∙注意:同一个设备可能在/sys中存在多个设备文件,比如一颗led的设备文件可能在/sys/bus/platform/devices/led1,同时还有一个在/sys/class/leds/led1。

虽然他们都是同一颗led的设备文件,但是他们的来源、机制、原理都是不同的,不能混为一谈4.各组件的特性与联系∙kobject:设备驱动模型各实例的最基本单元,提供一些公用型服务如:提供该实例在sysfs中的操作方法(show和store);提供在sysfs中以文件形式存在的属性,其实就是应用接口;提供各个实例的层次架构,让sysfs中弄出目录结构。

设备驱动模型中每个实例内部都会包含一个kobject∙总线、设备、驱动,这三者有着密切的联系。

在内核中,设备和驱动是分开注册的,注册设备的时候,并不需要驱动已经存在,而驱动被注册的时候,也不需要对应的设备已经被注册。

linux系统结构框架

linux系统结构框架

linux系统结构框架
Linux系统一般有4个主要部分:内核、shell、文件系统和应用程序。

内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。

1.内核:内核是操作系统的核心,具有很多最基本功能,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。

Linux 内核由如下几部分组成:内存管理、进程管理、设备驱动程序、文件系统和网络管理等。

2.Shell:shell是命令行解释器,可以为用户提供对系统的访问,也可以被用作程序或者脚本的命令行环境。

有多种shell可以选择,比如bash,zsh,ksh等。

3.文件系统:Linux系统使用一个基于文件的层级结构来组织和存储系统资源。

每个文件和目录都从根目录“/”开始,然后层层嵌套。

4.应用程序:Linux系统上可以运行各种应用程序,包括文本编辑器、浏览器、开发工具等。

应用程序为用户提供了使用系统的接口。

在更细致的层次结构上,Linux系统的内存管理分为几个主要组件,包括物理内存管理、虚拟内存管理以及内核内存管理等。

物理内存管理负责物理内存的分配和回收,虚拟内存管理则将物理内存映射到虚拟地址空间,并实现内存的共享和保护。

内核内存管理则负责内核空间的分配和释放,以及内核页面的交换等。

Linux设备模型的关键之一:Uevent

Linux设备模型的关键之一:Uevent

Linux设备模型的关键之一:Uevent1. Uevent的功能Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。

用户空间程序收到这样的事件后,会做相应的处理。

该机制通常是用来支持热拔插设备的,例如U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态的支持该设备。

2. Uevent在kernel中的位置下面图片描述了Uevent模块在内核中的位置:由此可知,Uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发Uevent提供的接口。

Uevent模块准备好上报事件的格式后,可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。

注1:有关kmod和netlink,会在其它文章中描述,因此本文就不再详细说明了。

3. Uevent的内部逻辑解析3.1 Source Code位置Uevent的代码比较简单,主要涉及kobject.h和kobject_uevent.c两个文件,如下:include/linux/kobject.hlib/kobject_uevent.c3.2 数据结构描述kobject.h定义了uevent相关的常量和数据结构,如下:kobject_action1: /* include/linux/kobject.h, line 50 */2: enum kobject_acTIon {。

Linux设备模型浅析之uevent篇

Linux设备模型浅析之uevent篇
}
/* 在本例中是 devices_kset 容器,详细介绍可参照《Linux 设备模型浅析之设备篇》,后 面将列出 devices_kset 的定义 */ kset = top_kobj->kset; uevent_ops = kset->uevent_ops; // 本例中 uevent_ops = &device_uevent_ops
struct sk_buff *skb; size_t len;
/* allocate message with the maximum possible size */ len = strlen(action_string) + strlen(devpath) + 2; skb = alloc_skb(len + env->buflen, GFP_KERNEL); if (skb) {
return 0; }
// 获得用于存放环境变量的 buffer /* environment buffer */ env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); if (!env)
return -ENOMEM;
/* 获取该 kobj 在 sysfs 的路径,通过遍历其父 kobj 来获得,本例是/sys/devices/platform/ s3c2410-rtc/rtc/rtc0 */ /* complete object path */
goto exit;
/* 如果配置了网络,那么就会通过 netlink socket 向用户空间发送环境标量,而用户空间 则通过 netlink socket 接收,然后采取一些列的动作。这种机制目前用在 udev 中,也就是 pc 机系统中,后面会分析*/ #if defined(CONFIG_NET) /* send netlink message */ /* 如果配置了 net,则会在 kobject_uevent_init()例程中将全局比昂俩 uevent_sock 初试化 为 NETLINK_KOBJECT_UEVENT 类型的 socket。*/ if (uevent_sock) {

linux分层设计体系结构

linux分层设计体系结构Linux是一种开源的操作系统,其设计采用了分层的体系结构。

这种设计使得Linux具有高度的灵活性和可扩展性,同时也方便了系统的维护和管理。

本文将详细介绍Linux的分层设计体系结构。

在Linux的分层设计中,最底层是硬件层。

硬件层包括计算机的各种硬件设备,如处理器、内存、硬盘、网络接口等。

Linux通过设备驱动程序来管理和控制这些硬件设备,使其能够与操作系统进行交互。

在硬件层之上是内核层。

内核是操作系统的核心,负责管理系统的资源和提供各种系统服务。

Linux的内核是一个单独的模块,可以独立于其他软件进行开发和维护。

内核提供了各种系统调用接口,以及对进程、文件系统、网络和设备的管理和控制功能。

在内核层之上是库层。

库是一组共享的代码和函数,可以为应用程序提供常用的功能和服务。

Linux提供了许多不同的库,如C库、数学库、网络库等。

这些库可以被开发人员用来开发应用程序,提高开发效率和代码复用性。

在库层之上是应用层。

应用层包括各种应用程序和工具,如文本编辑器、图形界面、网络浏览器等。

这些应用程序可以通过系统调用接口与内核进行交互,并利用库提供的功能来实现各种任务和操作。

除了以上四个层次外,Linux还有其他一些重要的组件和模块。

例如,系统初始化和启动过程中,会加载引导程序和初始化程序;文件系统是用来组织和管理文件和目录的;网络协议栈是用来实现网络通信的;系统服务是用来提供各种系统功能和服务的。

这些组件和模块与其他层次之间相互关联,共同构成了Linux的完整体系结构。

Linux的分层设计体系结构具有许多优点。

首先,分层设计使得系统的各个组件和模块之间相互独立,可以分别进行开发、测试和维护,提高了开发和维护效率。

其次,分层设计使得系统的各个层次之间的接口清晰明确,方便了系统的扩展和升级。

此外,分层设计还提高了系统的稳定性和可靠性,一旦某个层次出现问题,不会对其他层次造成影响。

Linux的分层设计体系结构是一种高效、灵活和可扩展的设计方式。

Linux设备模型 热插拔、mdev 与 firmware

Linux设备驱动程序学习(15)-Linux设备模型(热插拔、mdev 与firmware)热插拔有2 个不同角度来看待热插拔:从内核角度看,热插拔是在硬件、内核和内核驱动之间的交互。

从用户角度看,热插拔是内核和用户空间之间,通过调用用户空间程序(如hotplug、udev 和mdev)的交互。

当需要通知用户内核发生了某种热插拔事件时,内核才调用这个用户空间程序。

现在的计算机系统,要求Linux 内核能够在硬件从系统中增删时,可靠稳定地运行。

这就对设备驱动作者增加了压力,因为在他们必须处理一个毫无征兆地突然出现或消失的设备。

热插拔工具当用户向系统添加或删除设备时,内核会产生一个热插拔事件,并在/proc/sys/kernel/hotplug文件里查找处理设备连接的用户空间程序。

这个用户空间程序主要有hotplug:这个程序是一个典型的bash 脚本,只传递执行权给一系列位于/etc/hot-plug.d/ 目录树的程序。

hotplug 脚本搜索所有的有 .hotplug 后缀的可能对这个事件进行处理的程序并调用它们, 并传递给它们许多不同的已经被内核设置的环境变量。

(基本已被淘汰,具体内容请参阅《LDD3》)udev :用于linux2.6.13或更高版本的内核上,为用户空间提供使用固定设备名的动态/dev目录的解决方案。

它通过在sysfs 的/class/ 和/block/ 目录树中查找一个称为dev 的文件,以确定所创建的设备节点文件的主次设备号。

所以要使用udev,驱动必须为设备在sysfs中创建类接口及其dev属性文件,方法和sculld模块中创建dev属性相同。

udev的资料网上十分丰富,我就不在这废话了,给出以下链接有兴趣的自己研究:mdev:一个简化版的udev,是busybox所带的程序,十分适合嵌入式系统。

因为hotplug现在也在被慢慢地淘汰,udev不再依赖hotplug了,所以这里不再介绍;udev较mdev复杂,不太适合嵌入式使用。

Linux设备模型 热插拔mdev 与 firmware

Linux设备模型热插拔、mdev 与 firmwareLinux设备驱动程序学习(15)-Linux设备模型(热插拔、mdev 与 firmware)热插拔有 2 个不同角度来看待热插拔:从内核角度看,热插拔是在硬件、内核和内核驱动之间的交互。

从用户角度看,热插拔是内核和用户空间之间,通过调用用户空间程序(如hotplug、udev 和 mdev)的交互。

当需要通知用户内核发生了某种热插拔事件时,内核才调用这个用户空间程序。

现在的计算机系统,要求 Linux 内核能够在硬件从系统中增删时,可靠稳定地运行。

这就对设备驱动作者增加了压力,因为在他们必须处理一个毫无征兆地突然出现或消失的设备。

热插拔工具当用户向系统添加或删除设备时,内核会产生一个热插拔事件,并在/proc/sys/kernel/hotplug 文件里查找处理设备连接的用户空间程序。

这个用户空间程序主要有hotplug:这个程序是一个典型的 bash 脚本,只传递执行权给一系列位于 /etc/hot-plug.d/ 目录树的程序。

hotplug 脚本搜索所有的有 .hotplug 后缀的可能对这个事件进行处理的程序并调用它们, 并传递给它们许多不同的已经被内核设置的环境变量。

(基本已被淘汰,具体内容请参阅《LDD3》)udev :用于linux2.6.13或更高版本的内核上,为用户空间提供使用固定设备名的动态/dev目录的解决方案。

它通过在 sysfs 的 /class/ 和/block/ 目录树中查找一个称为 dev 的文件,以确定所创建的设备节点文件的主次设备号。

所以要使用udev,驱动必须为设备在sysfs中创建类接口及其dev属性文件,方法和sculld模块中创建dev属性相同。

udev的资料网上十分丰富,我就不在这废话了,给出以下链接有兴趣的自己研究:《UDEV Primer》(英文),地址:/decibelshelp/LinuxHelp_UDEVPrimer.html 《udev规则编写》(luofuchong翻译),地址:/luofuchong/archive/2021/12/18/37831.html 《什么是udev》地址:/steganography/archive/2021/04/10/657620.aspx《udev-FAQ 中文翻译》地址:/3225765.html 《udev轻松上路》地址:/user1/3313/archives/2021/1635169.shtml 《Udev (简体中文)》地址:/index.php/Udev_(????????-???) Udev官方主页:/pub/linux/utils/kernel/hotplug/udev.html 下载地址:/pub/linux/utils/kernel/hotplug/ 在《LFS》中也有介绍udev的使用,很值得参考!下载地址:/lfs/downloads/stable/mdev:一个简化版的udev,是busybox所带的程序,十分适合嵌入式系统。

Linux2.6内核设备模型分析

足这些需求 。为适应 这种 形势 的需要 , . 2 6内核 开发
挂接的总线类 型组织成层 次结构 ; B s目录 : u 包含 系统 中所有的总线类 型; D i r 目录 : r es v 包括 内核中所有 已注册 的设 备驱动
程序 ;
Cas目录 : l s 系统 中的设 备类型 ( 如网卡设备 , 卡 声 设 备等) 。
kb e 是 Ln X2 6引入 的新 的设 备 管理 机 制 , oj t ;u . e 在 内核 中 由 s utkb c 表示[ 3 ] t c oj t r e 1 。通 过这 个 数据 ,4 结构使所 有设备 在底层都 具有统 一 的接 口,oj t kb c 提 e 供 基本 的对象 管理 , 是构成 Ln x , iu 6设备模 型的核心 2 结 构 , 与 ss 文件 系统紧 密关联 , 它 yf s 每个 在 内核中注
LIZ e g pn XU a C N u — ig DAIGu n —h n h n — ig, Ch o, HE J nn n , a gz e
( col f l t nc c ne& T cn l y A hi ie i ,H f 30 9 C i ) Sh o o e r iS i c E co e eh o g , n u Unvr t o s y ee2 0 3 , hn i a
了全新 的设备模型。文中在 Lnx2 x . i 2 6内核设备模型 的架构和基 u 本原理 , 为深 入理解 2 6内核 的设备 管理 和驱动程 序 .
开发提供必要 的基础。
2 内核对象机制关键数据结构
2 1 k bet . oje 内核 对象
Ab ta tLiu 6k r e to u e nf dd vc rv rmo e ,whc at u p r tlg n we nge n ,h t lga dp p src : n x2. e ln rd c sau ie e ied ie d l n i i ihcrs p o tn el e tp i i o rma a me t o —pu n n
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

第一节基本概念在设备模型里面,所有的东西都是kobject,这也是linux建立设备设计模型的目的(对比2.4之前),实现了统一的实体;我们理解上,却可以分为两个层次,一个是kobject,一个是管理kobject的kobject(可以把它叫做kset虽然有点绕,但是没有办法了,毕竟就像那个“世界上先有鸡还是先有蛋的哲学问题一下”);kobject结构1)前面两个顾名思义,就是name了,为什么会有两个呢?k_name就是指向name的,如何知道呢,呵呵,看一下代码2)kref就是一个内核的原子计数结构,因为涉及内核的操作基本都需要是原子性的,为了大家的方便,kobject就把它包括进来了,所以大家就不必要各自定义自己的计数了(一般情况下:),poll也是类似,把等待队列包括进来;3)entry 这个名字比较让人误解,其实看它的类型知道是list成员,它就是加入到kset的list 的那个零部件;4)ktpye 要理解这个成员就稍微麻烦些了,先看一下定义Default_attrs就是一种比较简单的设置属性文件的方法,它其实跟我们自己调用sysfs_create_file没有什么区别,呵呵,看一下代码就知道了,所以大家基本上可以把它忽略掉:),调用关系为kobject_add->create_dir->populate_dir把一个忽略掉,剩下的两个就比较重要了;每个对象一般都有多个属性,用面向对象的角度来看,我们可以把对属性的操作抽象为show和store这一对方法,那么多个属性就会有多对show和store的方法;那么,为了实现对这些方法的统一调用,就利用ktype中的sysfs_ops 实现了多态;这样一来,对于sysfs中的普通文件读写操作都是由kobject->ktype->sysfs_ops来完成的;release也是类似,大部分的设备在退出的时候也是需要清理资源的,这里也实现多态(不过和上面的那个多态实现上有所区别:),在kobject释放的时候,会调用一下这个release(如果不为NULL的话),如下:所以我们基本上可以这样理解,ktpye就是把kobject的一些方法给拿了出来,实现了这些方法的多态的统一接口;其实看名字ktpye大概也理解得到:),看一下get_ktpye函数的实现,一个kobject的ktype是优先选择kset->ktpye,如果它为NULL的话,才会去用kobject自己的ktpye,所以说,kset是管理kobject的kobject,就像一个小孩首先要有父母管,父母不管的话,那就自己管,但是自己都不管自己的话,那就麻烦了;这种情况是存在的,kobject 是可以没有ktpye的,这样的话相当于没有对外的属性接口和release方法了;5)parent 这个就好理解了,就是形成sys的树形结构,在sysfs中每一个目录都对应一个kobject.这些kobject都有自己的parent。

在没有指定parent的情况下,都会指向它所属的kset->object,要是这个也没有的话,那就会向组织靠拢,直接挂到/sys目录下;注意,这里的优先级顺序跟前面的那个ktpye是相反的;6)Dentry 我们可以先不用太关心,大概知道dentry是跟文件系统目录有关的就可以了管理kobject的kojbect(即是kset)1)subsys这个概念基本上就是kset的概念,在比较新的内核版本里面已经去掉了,所以忽略它2)Ktype 这个在介绍kobject的时候已经讲过了,这里只说一点,kset里面也有一个kobject,那么这个kobject里面也会有一个ktype,表明这个kset(其实也是kobject)的ktype是什么,大家不要把这两个概念混淆;3)List 就是用来管理kobject的链表头4)Uevent_ops 又是一个麻烦的东西,看一下定义里面无非就是些函数指针,作用是什么呢?要讲清楚的话,就得费一番功夫了,且容我一一道来:)前面说过,现在的内核驱动模型里面,所有的东西都是kobject,以前的/dev目录是由内核维护的(现在时代不同了,什么东西都得热插拔,甚至包括cpu),今天/dev的维护工作交给了一个叫做/sbin/udevd的用户进程(udev文件系统是针对2.6 内核,提供一个基于用户空间的动态设备节点管理和命名的解决方案),但是任何东西都是从底下向上面传递的,内核需要一个机制向用户态发送消息,确切的说是kobject需要一个途径向用户态传递,那么这个途径便是uevent ops了;但是为什么它不放到kobject或者ktpye里面呢?其实我也说不太上来,套用我前面说的那个概念就是这个kset是一个管理kobject的kobject吧:)当一个kobject的事件发生时(比如创建或者删除等等),就会调用到一个叫做kobject_uevent 的接口,如下这个接口里面有一个比较关键的地方或许能够进一步帮助我们理解kset的功能,可以看出来,代码里面会尽力(不断向上循环,找parent的parent等等)找到管理这个kobject 的kset或者是kobject的parent的kset或者是parent的parent的等等,,然后得到它的uevent ops(它有可能为NULL),所以从这里大概能够感觉到这个kset(管理kobject的kobject)的重要性了,其实也可以没有,但是什么都没有的话,那我们就什么功能都没有实现了:)最后,会通过netlink发送到用户态,代码如下讲到这里,终于可以告一段落了,总结一下,就是开始讲的那一句话:所有的东西都是kobject,理解上可以分为两个层次,一个是kobject,一个是管理kobject 的kobject有了这些基本概念后,下一节给大家讲讲具体实践(字符设备和块设备)第二节字符设备和块设备字符和块设备的proc文件,下面两个数据结构其实也就仅仅是为了proc存在的,大家看到它们可以忽略:)这样我们可以把注意力集中到核心的地方1)charstatic struct char_device_struct {struct char_device_struct *next;unsigned int major;unsigned int baseminor;int minorct;char name[64];struct file_operations *fops;struct cdev *cdev; /* will die */} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];2)blockstatic struct blk_major_name {struct blk_major_name *next;int major;char name[16];} *major_names[BLKDEV_MAJOR_HASH_SIZE];以上这两个函数名字取的是挺好的,register xxx ,但是我们千万不要被它们骗了,这年头不要以貌取人,看看它里面究竟做了什么才是王道,最终我们发现,其实里面做的事情就是仅仅跟proc文件的显示有关,其他的事情都没有做,所以我们忽略它们:)主次设备号与kobject的联系1)什么是主次设备号(20)#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))2)桥梁为kobj_mapstruct kobj_map {struct probe {struct probe *next;dev_t dev;unsigned long range;struct module *owner;kobj_probe_t *get;int (*lock)(dev_t, void *);void *data;} *probes[255];struct mutex *lock;};根据主设备号进行(% 255)hash,按照range(可以理解为注册时候的次设备号的个数,注册的时候通常不只是一个)的大小进行升序排列(所以,最后形成的list与主设备号或者次设备号没有直接的映射逻辑,但是有间接逻辑:),是按照主设备号hash);关键点在于data,其实它就是一个kobject的子类对象,通过get方法可以得到kobject;桥梁kobj_map的建立1)字符设备通过cdev_add这个接口实现,这里的kobject是个桥梁,字符设备通过设备号最终要找到的koject不是它,而是由各个字符设备自己维护的kobject(比如在/sys/class/misc/iodir_control下),所以这个充当桥梁功能的kobject不会出现到sys系统下(并不是全部的kobject都会出现到sys下,因为sys系统只是kobject的一个子集功能而已);而一个字符设备最终会有一个设备号与/dev下面的设备文件名相对应(在各个字符设备产生自己的kobject实体的时候,会通过kobject_uevent(&class_dev->kobj, KOBJ_ADD)接口给用户态的一个维护/dev目录的进程发送消息,进而产生/dev相关的文件),用户态可以通过对/dev/xx的open操作打开设备文件,最终会通过设备号和这个kobject桥梁到达字符设备真正的kobject;2)块设备通过add_disk-> blk_register_region 这个接口实现桥梁,(这里的kobject是出现在sys系统里面的,struct gendisk,因为add_disk-> register_disk会挂载到sys系统)可以看到,一个disk的kobject节点为(1+N),其中N为分区数,不过这里的N不是直接包含在disk里面的,而是disk作为parent的形式,所以struct gendisk 里面有个part指针;disk 的核心结构如下:分区对象的数据结构如下:Add partition 的函数,可以看到1+N的关系如下:在disk的核心结构里面,我们可以看一下几个成员的意义,其中major为主设备号(块设备比较特殊,不像字符设备一样,一个主设备号代表一种类型,块设备通常是好几个主设备号都是一种类型,比如说sd设备,就包括好几个主设备号),first_minor为这块disk的起始minor号(通过它可以知道自己是第几块disk,方法就是除以n,n为调用alloc_disk的时候的参数,这个参数就是minors,是为了记录最大的可分区数目,最大的可分区数为minors-1,之所以减一是出于一个disk的kobject节点为(1+N)的考虑,因为一个disk设备却可以拥有(1+N)个设备号,比如说上面的一个disk拥有三个设备号,其对应于/sys/block/hda /sys/block/had/hda1 /sys/block/had/hda2 三个kobject);如果我们调用alloc_disk的时候的参数为1,那么分区数为minors-1 = 1 -1 =0,那么就相当于不能有分区,但是仍然有一个设备号对应到目录/dev/hda;同时也只有一个kobject对应到/sys/block/hda桥梁kobj_map的使用1)字符设备在open的时候通过设备号dev_t,可以找到kobject;进一步可以找到字符设备(通过设备号映射为kobject)(得到struct cdev)(调用struct cdev的f_op->open,在这里面就是找到真正的字符设备的kobject实体)2)块设备与字符设备不一样,struct cdev可以理解为kobject的子类,但是struct block_device却没有kobject成员,那么如何找到关系呢?blkdev_open->do_open->get_gendisk这里找到了我们关心的bdev_map桥梁了,通过它找到的是struct gendisk 这个kobject实体,当然这个实体是以disk为单位的,而不是以设备号为单位(前面已经解释过了:)第三节block 层呵呵,前面讲了这么久,始终没有讲到我们关心的重点问题,大家是否觉得我写跑题了:)但是相信各位通过前面的介绍,现在对设备驱动模型已经基本理解了,个人觉得这点还是比较重要的,因为内核源码到处都涉及到相关的驱动模型,以后大家在看到相关代码的时候就可以举一反三,融会贯通了(授人以渔);好了,不罗嗦了,进入主题吧其实对于block层,自己接触的时间不长,没有太深入的研究过,只能就目前的理解大概写写,望能够达到抛砖引玉的作用,入口点是哪里呢?三个关键的数据结构1)Request Queue Descriptors2)Request Descriptors3)Bio关系图如下:三个数据流入口1)generic_make_request (通用块层的数据入口,一次传入一个bio?)2)scsi_execute scsi_execute_async (这两基本上是差不多的,只是一个同步和异步的差别,底层的调用都是blk_execute_rq_nowait)三个关键的异步点说明:这里说的异步点描述的是普遍可能的一种情况,并不是说一个命令下发的过程中一定会发生(好像有点绕,但是大家阅读完之后便明白了);1)blk_get_request 入队列需要的资源(request)2)在进入队列的时候,内核实现了一种叫做Activating the Block Device Driver的机制,其实就是一种延迟技术,这样就有利于把相邻的块请求(bio)进行集中合并;如前面的图所示,每个request包含一个或者多个bio结构,从通用块层generic_make_request路径仅仅是包含一个bio(其它的路径就除外),它会通过队列的q->make_request_fn向下进行,其实就是这个函数里面如果发现请求队列为空,就get 一个request,然后把bio加入端,要么就将bio加入到这个request的list,形成bio的list;因为有了这个需要,所以就产生了上面说的Activating the Block Device Driver的机制,目的就是为了合并请求(一定程度上也可以说是优化了调度,呵呵,当然,这里的调度还比不上后面的电梯调度);那么,如何实现这个Activating the Block Device Driver的机制呢?其实比较简单,就是利用了一个定时器进行延时(3ms);说的这里,必须要注意到一个东西,我们都知道内核定时器其实是利用软中断实现的,那么软中断的祖训就是“千万别让我睡眠”,如果利用定时器去实现上面讲的机制,那不是违背了祖训了吗?那是万万不行的!!所以,一个叫做kblockd的东西出场了,它的俗称叫做工作队列,每个cpu有一个,是在系统初始化的时候准备好的,“万物存在皆有理有”,那么它存在的理由就是为了让我们不用违背祖训,于是kblockd和定时器一起联合工作,让睡眠这个有可能发生的事情发生在它可以发生的地方(kblockd进程里面);呵呵,这里进行了一系列的比喻,下面我们开始跟踪一下代码,这里就参考那位叫做linux 的那些事的网友相关部分:)每个队列初始化的时候,会注册一个unplug_fn的函数指针,其实就是generic_unplug_device,最后调用就是看到了吧,我们最想要的就是这个q->request_fn了(就是出队列的接口);另外,队列初始化的时候还做了这个事情大家只需要关系最后三行,其实就是做了两件事情:一、注册了这个请求的工作队列的处理函数blk_unplug_work;(每个请求队列里面会有一个成员struct work_struct unplug_work)二、注册了定时器的处理函数就是blk_unplug_timeout,跟踪下去blk_unplug_timeout->blk_unplug_timeout到这里,我们又找到了我们想要的东西,就是queue_work,这里就是不继续展开介绍工作队列的相关知识,概括的讲这个函数的功能就是将具体的工作内容加入到对应的cpu的工作队列,然后wakeup这个工作队列(当然这里多说一句,wakeup它不代表它就马上能够运行,它(工作队列)会在恰当的时机被调度运行,这里也不展开了);所以当kblockd这个工作队列运行的时候,会运行我们的具体work,调用blk_unplug_work,然后调用队列的unplug_fn,还记得吗,它就是上面讲的generic_unplug_device了:)前面写了这么多,我都有点写糊涂了,总结一下,上面我们讲的是异步的第二个地方;所以下面应该是异步的第三个地方:)3)队列的出口q->request_fn,这里拿scsi device的来说就是scsi_request_fn,在讲出口的异步之前,需要先看一下request是如何转换成scsi cmd的,调用关系为scsi_request_fn-> elv_next_request,从请求队列取出一个request,但是在返回之前需要做一个处理,如下:这里关注一下最后一行的q->prep_rq_fn,这个函数其实就是scsi_prep_fn,它实现的功能就是把request转换为scsi cmd ,线索为这样的话,出队列后就可以按照scsi这个统一的协议进行与设备接口驱动打交道了;在这之后,我们便可以高高兴兴的把这个request拿出来,返回到scsi_request_fn,接着交个下面的驱动程序了:)大家接着看两个关键点,如下:从注释可以猜测到,它的作用便是把request从请求队列中remove,因为time to leave,之前scsi cmd 都已经准备好了,还不离开就不厚道了:)那么下一步就可以猜到便是丢给具体的硬件驱动程序,如下没错,我们到这个时候给人家的必须是标准协议scsi,所以理所当然的必须是scsi cmd了;呵呵,又写了一大堆,好像又没有到主题“第三个异步”,答案就在上面这个scsi_dispatch_cmd ,关于这个函数我们就不详细展开了,不然的话就天亮了,看一下我们比较关系的地方便可以了:上面的最后一行,便是硬件驱动的调用接口,注意到它的参数,有一个函数指针便是scsi_done,同时注意到第一行,类型的利用了内核定时器进行超时处理,可以看到,它们似乎都与一个叫做__scsi_done的东西扯上关系,直觉告诉我们,必须对它打破砂锅问到底:)调用关系如下__scsi_done->blk_complete_request到这里,验证了我们的直觉是正确的,证据就是看到了一个叫做raise_softirq_irqoff的东西,为啥它就是证据呢?记得long long ago,在学习软中断的时候就接触过一个特殊的软中断,便是BLOCK_SOFTIRQ,看一下定义,当初对这个神秘的东西没有去过多的理会,当时理解它是一种软中断的类型就跳过去了;到今天,终于“车到山前疑无路,柳暗花明又一村”了;详细的说就是这样的:当初一个叫做A的request在准备好了scsi cmd后,就意气风发的要离开组织(request queue),出去闯荡一番事业;在外面干出一番事业(硬件驱动程序进行读写scsi_dispatch_cmd)之后,但是发现,叶落归根,做人不要忘本,于是乎便请求回到组织,希望request queue能够重新接纳他;但是“法不立则国不治”,既然当初已经离开了组织(request queue),现在是不能够重头来过的,但是“法律不外乎人情”,另外一个叫做blk_cpu_done的组织实在不忍心看到一个人无家可归,于是乎便把这些要求回到组织的request们重新用list安排起来(注意,是每个cpu一个的,软中断的一贯作风),同时启动了一列叫做raise_softirq_irqoff的列车,在下一次软中断的执行时机把这些重回大家庭的request们接回家;那么从列车下来回到家后,这些request们第一件要做的事情是什么呢?那肯定就是rq->q->softirq_done_fn(rq)了;这件事情其实就是当初的约定scsi_softirq_done了,如下:在scsi_finish_command 里面cmd->done会被调用,它对应到scsi_blk_pc_done;因为,当初在scsi_setup_blk_pc_cmnd()中有这么一行cmd->done = scsi_blk_pc_done;接着scsi_blk_pc_done->scsi_io_completion->scsi_end_request->end_that_request_last,历经千辛万苦,终于找到了要做的事情便是而这个req->end_io,就是好了,这个request的故事讲完了,总结一下就是第三个异步点实际上就是当scsi cmd下发到硬件的驱动程序的时候,有可能产生超时处理,但是无论是否超时,最后都会叶落归根,通过一列叫做BLOCK_SOFTIRQ的软中断列车,回到家里去做一件叫做rq->end_io的事情,这就是我们在进行块设备读写的时候注册的一个回调接口:)另外,还有一点需要注意的,就是一个request可能是由几个bio组成的,那么我们在done request之前,还应该把相关的bio都done回去,相关的线索如下:scsi_end_request-> scsi_end_request-> __end_that_request_first-> bio_endio;至此,比较重要的三个异步点讲完了,相信大家把这三个异步的地方理解后,自行阅读相关代码就没有太多的障碍了:)设备模型的角度看block前面的基本上是等于代码review,下面就大体上说说从一个较高的层次(设备模型的角度)来分析一下block;看一张经典的关系图:图中涉及的数据结构包括1)struct block_device2)struct gendisk3)struct hd_struct每个分区会对应到一个设备号,一个设备号会对应到一个block device,在实际读写的时候(open的时候不会),generic_make_request会通过进行重新映射(比如说起始的sector、bio 对应的block device),但是无论是否是分区,后台都只会有一个gendisk,是通过前面讲的get_gendisk实现的:看完上面那张图,有了一个大体的概念,下面就可以继续分析了,我们选择scsi device(sd.c)来展开吧,(sd)s是scsi的简写,d是device还是disk呢,感觉叫device比较符合源码里描述的东西,不管这么多了,目前就这样叫着先吧;从上面的代码可以看到,做了三个事情1)register_blkdev接口其实是可以忽略的,因为它做的事情就是为了在proc文件下可以查询;2)class_register 会在/sys/class目录下生成一个/sys/class/scsi_disk的目录,在中间层调用sd_probe接口的时候会在/sys/class/scsi_disk目录下生成kobject的目录,名字为这个class 设备对应的device的名字,看sd_probe函数的相关实现如下说到这里,可能大家就有点不太理解了,什么叫做class设备对应的device呢?这里涉及到三个数据结构类型的实体,分别为struct scsi_device 、struct scsi_disk 、struct gendisk,它们之间的关系从高层的视图抽象出来的概貌如何,目前理解不上来,只能按照现有的微观角度进行一些观察了:)看一下/sys的结构,大家就能感觉出些端倪,如下:/sys/class/scsi_disk/0:0:0:0(这个目录是在class里面的scsi_disk的目录里面的,每一个struct scsi_disk对应一个逻辑设备目录,提供了一个逻辑视图观察设备的角度)上面的目录里面有一个软链接到/sys/device目录那么这个device就是对应于struct scsi_device这个数据结构,我们进入该软链接的目录,从上面的目录结构可以看出,struct scsi_device这个结构是一个从系统全局角度的一个device 管理的实体,它继承了device这个基础的设备模型部件;同时我们可以看到bus driver device 这三个经典的设备模型框架(它们是设备模型的核心组件);所以说,struct scsi_device这个数据结构的意义在于维护一个系统全局的统一视图(目前理解);同时,我们还可以看到一个叫做block:sda的软链接,这就是到了/sys/block/sda的目录,它对应于struct gendisk结构,是一个块设备最终的归属,进入该目录如下:。

相关文档
最新文档