11块IO和设备

1、块设备

(1)块设备和字符设备的区别

块设备:系统中能够随机(不需要按数据)访问固定大小数据片的硬件设备称作块设备,这些固定大小的数据片就称作块。最常见的块设备是硬盘。

字符设备:字符设备按照字符流的方式被有序访问,像串口和键盘就属于字符设备。

块设备和字符设备的区别:区别在于是否可以随机访问数据。内核管理块设备比管理字符设备细致得多。因为字符设备仅仅需要控制当前位置。而块设备访问的位置必须能够在介质的不同区间前后移动。块设备的管理需要有一个专门的提供服务的子系统,字符设备不需要。不仅因为块设备的复杂性高,块设备对执行性能的要求很高。对硬盘每多一份利用都会对整个系统的性能带来提升。

(2)剖析一个块设备

扇区:块设备中的最小的可寻址单元是扇区,扇区的大小是设备的物理属性,常见的是512字节。扇区是所有块设备的基本单元——块设备无法对比扇区还小的单元进行寻址和操作。

块:软件的最小逻辑可寻址单元是块。块是文件系统的一种抽象,只能基于块来访问文件系统。块一般是2的整数倍,不能超过一个页的长度。所以块 必须是扇区的整数倍,并且要小于页面大小。因此其通常大小是512字节、1KB或者4KB。

物理磁盘寻址按照扇区级进行,但是内核执行的所有磁盘操作都是按照块进行的。

2、缓冲区和缓冲区头结构体buffer_head

缓冲区作用:当一个快被调入内存时,要存储在一个缓冲区中。每个缓冲区与一个块对应,相当于是磁盘块在内存中的表示。

缓冲区头:每一个缓冲区都有一个对应的描述符。该描述符用buffer_head结构体表示,称作缓冲区头,包含了内核操作缓冲区所需要的全部信息。描述磁盘块和物理内存缓冲区之间的映射关系。

buffer_head进化到bio:因为缓冲区比较大,操作不方便,使用效率底下。仅仅能描述单个缓冲区,当作为所有I/O容器使用时,缓冲区头会促使内核把对大块数据的I/O操作(比如写操作)分解为对多个buffer_head结构体进行操作。造成不必要的空间浪费。为了避免这种情况,用了bio结构体,灵活且轻量级;当前的内核在向块设备层提交读写请求时,都会将buffer_head封装在bio结构中,而不再使用原来的buffer_head。

3、bio结构体

目前内核中块I/O操作基本容器由bio结构体表示,该结构体代表了正在现场的(活动的)以片段(segment)链表形式组织的块I/O操作。一个片段是一小块连续的内存缓冲区。使得进程可以通过片段来描述缓冲区;即使缓冲区分散在内存的多个位置上,bio结构体也能对内核保证I/O操作的执行。bio结构体的目的是代表现场正在执行的I/O操作,结构体中的主要域都是用来管理信息的,其中关键是bi_io_vecs、bi_vcnt和bi_idx:

IO向量:bi_io_vecs指向一个bio_vec结构体数组,该结构体链表包含了一个特定I/O操作所需要使用到的所有的片段。每个bio_vec结构都是一个形式为<page,offset,len>的向量。描述了片段对应的物理页、块在物理页中的偏移位置、从给定偏移量开始的块长度。整个结构体数组表示了一个完整的缓冲区。

struct bio_vec{
    /* 指向这个缓冲区所驻留的物理页 */
    struct page     *bv_page;
    /* 这个缓冲区以字节为单位的大小 */
    unsigned int    bv_len;
    /* 缓冲区所驻留页中以字节为单位的偏移量 */
    unsigned int    bv_offset;
}

bi_vcnt用来描述bi_io_vec指向的数组中的片段数目。bi_idx域指向数组的当前索引。每个I/O请求通过一个bio结构体描述,其中包含多个块(bio_vec).其操作的第一个片段由bi_io_vec结构体所指向,然后不断更新bi_idx直到达到bi_vcnt的最后一个片段。bi_cnt域记录bio结构体使用计数,如果其值为0,就应该撤销该bio结构体。

现在基本以bio结构体代替了buffer_head结构体有一下好处:

  • 容易处理高端内存,它处理的是物理页而不是直接指针。
  • bio结构体可以代表普通页I/O,同时也可以代表直接I/O
  • 便于执行分散-集中(矢量化)块I/O操作,操作中的数据可取自多个物理页面
  • 轻量级,它仅仅是一个矢量数组。

4、请求队列

块设备将它们挂起的块I/O请求保存在请求队列中,该队列由reques_queue结构体表示,请求队列只要不为空,队列对应的块设备驱动程序就会从队列头获取请求,然后将其送入对应的块设备上去。请求队列表中每一项都是一个单独的请求,由request结构体表示。request结构体:队列中的请求由结构体request表示。因为一个请求可能要操作多个连续的磁盘块,所以每个请求可以由多个bio结构体组成。每个bio结构体都可以描述多个片段。

5、IO调度程序

磁盘寻址是整个计算机中最慢的操作之一,每一次寻址需要花费不少时间,尽量缩短寻址时间是提高系统性能的关键。内核不会简单地按请求接收次序,也不会立即将其提交给磁盘。而是在提交前执行名为合并与排序的与操作。这种预操作可以极大地提高系统的整体性能。在内核中负责提交I/O请求的子系统称为I/O调度程序。I/O调度程序将磁盘I/O资源分配给系统中所有挂起的块I/O请求。这种资源分配是通过将请求队列中挂起的请求合并和排序来完成的。

I/O调度和进程调度区别:

  • 共同点:将一个资源虚拟给多个对象。
  • 不同点:进程调度程序将处理器资源分配给系统中的运行进程。I/O调度程序虚拟块设备给多个磁盘请求,以便降低磁盘寻址时间。

I/O调度程序的工作是管理块设备的请求队列。有利于减少磁盘寻址时间,从而提高全局(可能牺牲某些请求性能)吞吐量。I/O调度程序通过两种方法减少磁盘寻址时间:合并与排序。该算法类似于电梯调度:电梯不能随意地从一层跳到另一侧,它应该一个方向移动,当抵达了同一方向上的最后一层厚,再掉头向另一个方向移动。

  • 合并:将两个或多个请求结合成一个新请求。可以减小请求次数,并且减少寻址次数。例如两个请求访问的磁盘扇区相邻,就可以合并为一个新请求。
  • 排序:整个请求队列将按扇区增长方向有序排列。使所有请求按硬盘上扇区的排列顺序有序排列,不仅可以缩短单独一次请求的寻址时间。更重要的在于,通过保持磁盘头以直线方向移动,缩短了所有请求的磁盘寻址时间。

(1)电梯调度算法

当一个请求加入到队列中时,有可能发生四种操作,它们依次是:

  • 如果队列中已经存在一个相邻磁盘扇区操作的请求,那么新的请求将会和这个已经存在的请求进行合并
  • 如果队列中存在一个驻留时间过长的请求,那么新的请求将被插入到队列尾部,已防止其他旧的请求存在饥饿现象
  • 如果队列中以扇区方法为序存在合适的插入位置,那么新的请求将被插入到该位置,保证队列中的请求是以被访问磁盘物理位置为序进行排列的
  • 如果队列中不存在合适的请求插入位置,请求将被插入到队列尾部

(2)最终期限I/O调度程序

为了避免饥饿,设置最后期限,每个请求都有一个超时时间(默认为读500ms,写为5s),根据读写插入到特定的读/写FIFO队列中。新队列总是被加入到队列尾部,这样就避免了饥饿。最后期限I/O调度程序将请求从排序队列的头部去下,再推入到派发队列中,派发队列然后将请求提交给磁盘驱动,从而保证了最小化的请求寻址。如果请求超时,在从FIFO中提取请求进行服务。

(3)预测I/O调度程序

读写分开造成了两次寻址,损害了全局吞吐量。预测调度在最终期限的基础上,添加了一个派发队列。并为每个队列设置了超时时间。主要是增加了预测启发能力。提交请求之后们并不直接返回处理其它请求,而是会有意空闲片刻(默认为6ms).可以上引用程序来提交其它读请求–任何对相邻磁盘位置操作的请求都会立刻得到处理。等待时间结束后,预测调度程序会重新返回原来的位置,继续执行以前剩下的请求。相邻的请求到来,可以减少I/O的操作次数。Linux内核中缺省的I/O调度程序,对大多数工作负荷来说执行良好,对服务器也是理想的。不过在某些非常见的服务器上(如数据库挖掘服务器),这个调度程序执行的效果不好。

(4)完全工作的排队I/O调度程序(CFQ)

完全公正调度(CFQ):将进入的I/O请求放入特定的队列中。队列分类与请求来自的进程有关。每个队列中,刚进入的请求与相邻请求合并在一起,并行插入分类。然后以时间片轮转调度队列,从每个队列中选取请求数,然后进行下一轮调度。确保每个进程结构公平的磁盘贷款片段。一般用于多媒体。主要推荐给桌面工作负荷使用,但是如果没有其他异常,在几乎所有的工作负荷中都能很好地执行。

(5)空操作的I/O调度程序

空操作不进行排序,也不进行其它形式的寻址操作。只有执行合并这一点。主要针对块设备。比如闪存卡。等没有寻道复返的块设备。为随机设备而设计。

(5)IO调度程序的选择

6、设备与模块

关于设备驱动和设备管理,将讨论四种内核成分:

  • 设备类型:所有Unix系统中为了统一普通设备的操作所采用的分类
  • 模块:Linux内核中用于按需加载和卸载目标码的机制
  • 内核对象:内核数据结构中支持面向对象的简单操作,还支持维护对象之间的父子关系
  • sysfs:表示系统设备树的一个文件系统

(1)设备类型

在Linux以及所有Unix系统中,设备被分为以下三种类型:

  • 块设备(blkdev):以寻址块为单位;块大小随设备不同而不同;块节点通常被挂载为文件系统。
  • 字符设备:cdev,通常是不可寻址的;通过字符设备节点的特殊文件来访问
  • 网络设备(ethernet devices):不是通过设备节点来访问,而是通过套接字API这样的特殊接口来访问。
  • 虚拟设备:设备驱动是虚拟的,仅仅提供访问内核功能。如随机数发生器(/dev/random和/dev/urandom).空设备(/dev/null)、零设备(/dev/zero)、满设备(/dev/full)、内存设备(/dev/mem)

(2)模块

Linux内核是模块化的,允许内核在运行时动态地向其中插入或者从中删除代码。代码集成的二进制文件;即所谓的可装载内核模块。

构建模块步骤:

在2.6内核中,采用了新的”kbuild“构建系统,构建过程的第一步是决定在哪里管理模块源码,方式如下:把模块源码加入到内核源代码树种;在内核源代码树之外维护和构建模块源码。

  • 安装模块–make modules_install(需要root权限)
  • 产生模块依赖性–depmod
  • 载入模块–insmod/rmmod/modprobe
  • 管理配置选项–config选项
  • 模块参数–module_param()
  • 导出符号表–EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()

7、设备模型

2.6之后,提供了统一的设备模型(device model),来表示设备,并描述其在系统中的拓扑结构。

(1)kobject

它是设备模型的核心。由struct kobject结构体表示,定义于头文件<linux/kobject.h>中。它类似于C#或者java这些面向对象语言中的对象(object)类,提供了诸如引用计数、名称和父指针等字段。

struct kobject {
	/* 指向kobject的名称 */
	char			*k_name;
	/* 名称 */
	char			name[KOBJ_NAME_LEN];
	/* 引用计数 */
	atomic_t		refcount;
	/* 链表节点 */
	struct list_head	entry;
	/* 父指针 */
	struct kobject		*parent;
	/* 关键设置 */
	struct kset		*kset;
	/* 对象类型 */
	struct kobj_type	*ktype;
	struct dentry		*dentry;
};

设备常用的结构体cdev,嵌入kobject的结构体可以成为对象层次架构中的一部分:

struct cdev {
	struct kobject kobj;
	struct module *owner;
	struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

(2)ktype

对象模型中描述kobject所具有的普遍特性

struct kobj_type {
	void (*release)(struct kobject *);
	/* 指向系统文件选项结构体,描述了sysfs文件的读写时的特性 */
	struct sysfs_ops	* sysfs_ops;
	/* 指向了attribute结构体数组。定义了相关的默认属性,主要用于映射成文件 */
	struct attribute	**default_attrs;
};

(3)kset

kset是kobject对象的集合体。把它看成是一个容器,可将所有相关的kobject对象,置于同一个位置。

struct kset {
	struct subsystem	*subsys;
	struct kobj_type	*ktype;
	/* 链接set中所有的kobject对象 */
	struct list_head	list;
	struct kobject		kobj;
	struct kset_hotplug_ops	* hotplug_ops;
};

(4)管理和操作kobject

struct kobject *kobj;
kobj=kmalloc(sizeof(*kobj),GFP_KERNEL);
if(!obj) return -ENOMEM;
memset(kobj,0,sizeof(*kobj));
kobj->kset=my_set;
kobject_init(kobj,my_ktype);
/* 使用create函数直接创建 */
struct kobject *kobj;
kobj=kobject_create();
if(!kobj) return -ENOMEM;

(5)引用计数

当引用计数为0时,会调用release()函数。进行变量的重置和内存的释放。递增和递减引用计数的方式:

/* 增加一个引用计数 */
struct kobject *kobject_get(struct kobject *kobj);
/* 降低一个引用计数 */
void kobject_put(struct kobject *kobj);

8、sysfs

文件系统,是一个处于内存中的虚拟文件系统,他为我们提供了kobject对象层次结构的视图。其将kobject对象与目录项(directory entries)紧密联系起来,通过dentry字段来实现。将其映射到文件上。将kobject形成内存中的目录文件。最终形成对象目录树。不同的文件夹对应着不同的设备。

(1)sysfs中添加和删除kobject

/* 将kobject添加到sysfs中,kobject有父指针,则其文件存在在父文件夹下面 */
int kobject_add(struct kobject *kobj,struct kobject *parent,const char *fmt,...);
/* 将创建和添加放在一起使用 */
struct kobject *kobject_create_and_add(const char *name,struct kobject *parent);
/* 从中删除一个kobject目录 */
void kobject_del(struct kobject *kobj);

(2)向sysfs中添加文件

默认属性:默认的文件集合是通过kobject和kset中的ktype字段提供的。上文结构中的attribute属性包含将内核数据映射成为sysfs中的文件。其定义在<linux/sysfs.h>中:

struct attribute {
    /* 属性名称 */
    char                *name;
    /* 所属模块,如果存在 */
    struct module         *owner;
    /* 权限 */
    mode_t                 mode;
};

sysfs_ops字段则描述了如何使用它们。其定义在linux/sysfs.h中

struct sysfs_ops {
    /* 在sysfs文件时该方法被使用 */
    ssize_t    (*show)(struct kobject *, struct attribute *,char *);
    /* 在写sysfs文件时该方法被调用 */
    ssize_t    (*store)(struct kobject *,struct attribute *,const char *, size_t);
};

show会拷贝attr提供的属性值到buffer指定的缓冲区中,缓冲区大小为PAGE_SIZE字节;读取成功,返回实际写入buffer的字节数,如果失败,则返回负的错误码。store()在写操作时调用,它会从buffer中读取size大小的字节,并将其存入attr表示的属性结构体变量中。缓冲区的大小是PAGE_SIZE或者更小。这组函数必须对所有的属性都进行I/O请求处理,所以它们通常需要维护某些通用映射来调用每个属性所特有的处理函数。

创建新属性和删除属性:

/* 将新的属性添加到kobject中 */
int sysfs_create_file(struct kobject *kobj,const struct attribute *attr);
/* 创建一个符号链接 */
int sysfs_create_link(struct kobject *kobj,struct kobject *target,char *name);
/* 删除新属性 */
void sysfs_remove_file(struct kobject *kobj,const struct attribute *attr);
void sysfs_remove_link(struct kobject *kobj,char *name);

sysfs约定:当前sysfs文件系统代替了以前需要由ioctl()和procfs文件系统完成的功能。sysfs提供内核到用户空间的服务。

(3)内核事件层

内核事件层,由内核空间传递到用户空间需要经过netlink.netlink是一个用于传送网络信息的多点传送套接字。

/* 内核空间向用户空间发送信号使用函数kobject_uevent() */
int kobject_uevent(struct kobject *kobj,enum kobject_action action);
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值