2013-08-28 17:42:18 sunnyyouknow 阅读数 385

linux下主要有两套异步IO,一套是由glibc实现的(以下称之为glibc版本)、一套是由linux内核实现,并由libaio来封装调用接口(以下称之为linux版本)。


比较

从上面的流程可以看出,linux版本的异步IO实际上只是利用了CPU和IO设备可以异步工作的特性(IO请求提交的过程主要还是在调用者线程上同步完成的,请求提交后由于CPU与IO设备可以并行工作,所以调用流程可以返回,调用者可以继续做其他事情)。相比同步IO,并不会占用额外的CPU资源。
而glibc版本的异步IO则是利用了线程与线程之间可以异步工作的特性,使用了新的线程来完成IO请求,这种做法会额外占用CPU资源(对线程的创建、销毁、调度都存在CPU开销,并且调用者线程和异步处理线程之间还存在线程间通信的开销)。不过,IO请求提交的过程都由异步处理线程来完成了(而linux版本是调用者来完成的请求提交),调用者线程可以更快地响应其他事情。如果CPU资源很富足,这种实现倒也还不错。

还有一点,当调用者连续调用异步IO接口,提交多个异步IO请求时。在glibc版本的异步IO中,同一个fd的读写请求由同一个异步处理线程来完成。而异步处理线程又是同步地、一个一个地去处理这些请求。所以,对于底层的IO调度器来说,它一次只能看到一个请求。处理完这个请求,异步处理线程才会提交下一个。而内核实现的异步IO,则是直接将所有请求都提交给了IO调度器,IO调度器能看到所有的请求。请求多了,IO调度器使用的类电梯算法就能发挥更大的功效。请求少了,极端情况下(比如系统中的IO请求都集中在同一个fd上,并且不使用预读),IO调度器总是只能看到一个请求,那么电梯算法将退化成先来先服务算法,可能会极大的增加碰头移动的开销。

最后,glibc版本的异步IO支持非direct-io,可以利用内核提供的page cache来提高效率。而linux版本只支持direct-io,cache的工作就只能靠用户程序来实现了。


2009-03-17 11:35:00 iceshirley 阅读数 7499

 

Linux IO调度程序是块设备I/O子系统的主要组件,它介于通用块层和块设备驱动程序之间,所图2-1所示。当Linux内核组件要读写一些数据时,并不是请求一发出,内核便立即执行该请求,而是将其推迟执行。延迟的设定是块设备性能的关键机制!当传输一个新数据块时,内核检查能否通过扩展前一个一直处于等待状态的请求而满足新请求。

 

 

图 IO调度层所在系统中的位置

       2.6内核中,内核中实现了四种IO调度算法,分别为预期(Anticipatory)算法、最后期限(Deadline)算法、完全公平队列(CFQ)算法以及NOOP算法(No Operation)。可以在内核引导式指定一种I/O调度算法,也可以在运行时通过sysfs文件系统/sys/block/sda/queue/scheduler来为块设备定制一个特定的I/O调度算法或查看块设备目前所使用的何种IO调度算法。

由于Linux IO调度程序是介于通用块层和块设备驱动程序之间,所以它接收来自通用块层的请求,试图合并请求,并找到最合适的请求下发到块设备驱动程序中。之后块设备驱动程序会调用一个策略函数来相应这个请求。这个策略函数是由设备类型来决定的,例如,如果设备是SCSI硬盘,那么scsi总线层便将scsi_request_fn注册到该策略函数中。

linux2.6.21的内核源代码中,用于实现IO调度程序的文件有:

文件路径

描述

/include/linux/elevator.h

定义了调度算法一些公用的数据结构以及操作函数接口

/block/elevator.c

实现elevator.h中声明的一些函数

/block/noop-iosched.c

用于实现Noop算法

/block/as-iosched.c

用于实现预期算法

/block/deadline-iosched.c

用于实现最后期限算法

/block/cfq-iosched.c

用于实现完全公平队列算法

elevator.h中定义了一些重要的数据结构,包括elevator_opselevator_type以及elevator_queue

Ø           elevator_ops定义了一些操作函数接口,这些接口函数由不同的IO调度算法重载,下面是该数据结构中一些主要字段的意义:

字段

类型

描述

elevator_merge_fn

elevator_merge_fn *

bio合并到合适的request中(准确地说,插入到request链表的首部)

elevator_merged_fn

elevator_merged_fn *

执行插入bio之后的一些附加操作

elevator_merge_req_fn

elevator_merge_req_fn*

合并两个request操作

elevator_allow_merge_fn

elevator_allow_merge_fn*

判断bio是否允许合并到request

elevator_dispatch_fn

elevator_dispatch_fn*

在队列中取出最合适的request

elevator_add_req_fn

elevator_add_req_fn*

向队列中插入一个新的request

elevator_queue_empty_fn

elevator_queue_empty_fn*

判断队列是否为空

elevator_completed_req_fn

elevator_completed_req_fn*

请求完成时进行的一些操作

elevator_former_req_fn

elevator_request_list_fn*

取前一个request操作

elevator_latter_req_fn

elevator_request_list_fn*

取后一个request操作

elevator_init_fn

elevator_init_fn*

初始化操作

elevator_exit_fn

elevator_exit_fn*

退出操作

Ø           elevator_type结构用于描述调度的种类,其中主要字段描述如下:

字段

类型

描述

ops

struct elevator_ops

实现该调度算法函数操作

elevator_attrs

struct elv_fs_entry *

Sys接口

elevator_name

Char *

该调度算法的名称

Ø           elevator_queue结构描述了该块设备驱动使用何种IO调度算法。通常每个物理块设备都自己维护一个请求队列,每个请求队列上单独执行I/O调度。在请求对列中有一个字段elevator,它的类型指向sturct elevator_queue的指针。elevator_queue结构如下:

struct elevator_queue

{

       struct elevator_ops *ops;

       void *elevator_data;

       struct kobject kobj;

       struct elevator_type *elevator_type;

       struct mutex sysfs_lock;

       struct hlist_head *hash;

};

字段ops定义了调度算法的所有可能的操作:链接和断开elevator,增加和合并队列中的请求,从队列中删除请求,获得队列中下一个待处理的请求等等。

字段是elevator_type,它是指向struct elevator_type类型的指针,它描述了块设备所使用io调度算法。

字段hash包含了处理请求队列所需的所有信息,通过这个哈希表可以快速索引我们需要的某个request

字段elevator_data用于描述IO调度程序用来处理请求的附加数据结构,如果使用Deadline算法,elevator_data指向struct deadline_data结构。

按照我的理解,elevator.h定义了一个框架,它类似面向对象程序中的基类,每个具体的 IO调度程序可以看作是它的派生类。父类elevator中定义了一些公用的方法,而这些方法的实现依据不同的调度算法其实现的方式也不同。子类会实现父类elevator中定义的函数集elevator_ops。下一节以deadline算法为例来描述IO调度程序是如何处理来自通用块层的请求,并最终下发到物理设备上。

 

2014-09-08 09:01:51 joeyon 阅读数 827

Linux Kernel 2.6.20 以上的内核支持进程 IO 统计,可以用类似 iotop 这样的工具来监测每个进程对 IO 操作的情况,就像用 top 来实时查看进程内存、CPU 等占用情况那样。但是对于 2.6.20 以下的 Linux 内核版本就没那么幸运了,根据 Stack Overflow 的这篇回帖 给出的方法,VPSee 写了一个简单的 Python 脚本用来在 linux kernel < 2.6.20 下打印进程 IO 状况。

Kernel < 2.6.20

这个脚本的想法很简单,把 dmesg 的结果重定向到一个文件后再解析出来,每隔1秒钟打印一次进程 IO 读写的统计信息,执行这个脚本需要 root:

#!/usr/bin/python
# Monitoring per-process disk I/O activity
# written by http://www.vpsee.com 

import sys, os, time, signal, re

class DiskIO:
    def __init__(self, pname=None, pid=None, reads=0, writes=0):
        self.pname = pname 
        self.pid = pid
        self.reads = 0
        self.writes = 0

def main():
    argc = len(sys.argv)
    if argc != 1:
        print "usage: ./iotop"
        sys.exit(0)

    if os.getuid() != 0:
        print "must be run as root"
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    os.system('echo 1 > /proc/sys/vm/block_dump')
    print "TASK              PID       READ      WRITE"
    while True:
        os.system('dmesg -c > /tmp/diskio.log')
        l = []  
        f = open('/tmp/diskio.log', 'r')
        line = f.readline()
        while line:
            m = re.match(\
                '^(\S+)\((\d+)\): (READ|WRITE) block (\d+) on (\S+)', line)
            if m != None:
                if not l:       
                    l.append(DiskIO(m.group(1), m.group(2)))
                    line = f.readline() 
                    continue            
                found = False   
                for item in l:  
                    if item.pid == m.group(2):
                        found = True            
                        if m.group(3) == "READ":
                            item.reads = item.reads + 1 
                        elif m.group(3) == "WRITE":
                            item.writes = item.writes + 1
                if not found:   
                    l.append(DiskIO(m.group(1), m.group(2)))
            line = f.readline()
        time.sleep(1)
        for item in l:
            print "%-10s %10s %10d %10d" % \
                (item.pname, item.pid, item.reads, item.writes)

def signal_handler(signal, frame):
    os.system('echo 0 > /proc/sys/vm/block_dump')
    sys.exit(0)

if __name__=="__main__":
    main()

Kernel >= 2.6.20

如果想用 iotop 来实时查看进程 IO 活动状况的话,需要下载和升级新内核(2.6.20 或以上版本)。编译新内核时需要打开 TASK_DELAY_ACCT 和 TASK_IO_ACCOUNTING 选项。解压内核后进入配置界面:

# tar jxvf linux-2.6.30.5.tar.bz2
# mv linux-2.6.30.5 /usr/src/
# cd /usr/src/linux-2.6.30.5

# make menuconfig

选择 Kernel hacking –> Collect scheduler debugging info 和 Collect scheduler statistics,保存内核后编译内核:

# make; make modules; make modules_install; make install

修改 grub,确认能正确启动新内核:

# vi /boot/grub/menu.lst

出了新内核外,iotop 还需要 Python 2.5 或以上才能运行,所以如果当前 Python 是 2.4 的话需要下载和安装最新的 Python 包。这里使用源代码编译安装:

# tar jxvf Python-2.6.2.tar.bz2
# cd Python-2.6.2
# ./configure
# make; make install

别忘了下载 setuptools:

# mv setuptools-0.6c9-py2.6.egg.sh setuptools-0.6c9-py2.6.egg
# sh setuptools-0.6c9-py2.6.egg

附:

linux@joeyon:~$ sudo iotop

16075 be/4 www-data    0.00 B/s    7.71 K/s  0.00 %  0.00 % nginx: worker process
Total DISK READ:       0.00 B/s | Total DISK WRITE:     193.00 K/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND                                                                                                  
  943 be/4 root        0.00 B/s   84.92 K/s  li%  5.34 % [kjournald]
16479 be/4 linux      0.00 B/s    0.00 B/s  0.00 %  0.12 % java -Djava.util.logging.config.file=/data/websites/a~-back/temp org.apache.catalina.startup.Bootstrap start
15941 be/4 linux      0.00 B/s    3.86 K/s  0.00 %  0.00 % java -Djava.util.logging.config.file=/data/websites/a~slave/temp org.apache.catalina.startup.Bootstrap start
16075 be/4 www-data    0.00 B/s   11.58 K/s  0.00 %  0.00 % nginx: worker process
16295 be/4 linux      0.00 B/s    3.86 K/s  0.00 %  0.00 % java -Djava.util.logging.config.file=/data/websites/a~eiver/temp org.apache.catalina.startup.Bootstrap start
16384 be/4 linux      0.00 B/s    0.00 B/s  0.00 %  0.00 % java -Djava.util.logging.config.file=/data/websites/a~ister/temp org.apache.catalina.startup.Bootstrap start
    1 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % init
    2 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kthreadd]
    3 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/0]
16388 be/4 linux      0.00 B/s    0.00 B/s  0.00 %  0.00 % java -Djava.util.logging.config.file=/data/websites/a~t-app/temp org.apache.catalina.startup.Bootstrap start
16389 be/4 linux      0.00 B/s    0.00 B/s  0.00 %  0.00 % java -Djava.util.logging.config.file=/data/websites/a~t-app/temp org.apache.catalina.startup.Bootstrap start
    6 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/0]
    7 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/0]
    8 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/1]
    9 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kworker/1:0]
   10 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/1]
   12 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/1]
   13 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/2]
   15 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/2]
   16 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/2]
   17 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/3]
   18 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kworker/3:0]
   19 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/3]
   20 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/3]
   21 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/4]
   23 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/4]
   24 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/4]
   25 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/5]
   26 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kworker/5:0]
   27 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/5]
   28 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/5]
   29 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/6]
   30 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kworker/6:0]
   31 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/6]
   32 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/6]
   33 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/7]
   34 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kworker/7:0]
   35 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/7]
   36 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/7]
   37 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [cpuset]
   38 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [khelper]
   39 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kdevtmpfs]
   40 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [netns]
   42 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [sync_supers]


参考:http://www.vpsee.com/2009/08/monitor-process-io-activity/

2019-03-22 21:44:38 bjgaocp 阅读数 300

noop ssd磁盘推荐 调整为noop 不需要调度

deadline 调度最小延迟 一般情况下推荐使用 (3.0内核默认调度算法)

anticipatory 预期算法 有一个IO发生,如果另一个进程请求IO操作,它会生成一个默认的6毫秒来猜下道工序要做的IO请求

cfq 完全公平队列跑桌面的情况下(2.6内核默认调度算法)

临时修改方法
echo deadline > /sys/block/sda/queue/scheduler
永久更改方法
vim /boot/grub/menu.lst
在 rhgb quiet前面加入下面内容 重启后生效
elevator=deadline
重启后检查
cat /sys/block/sda/queue/scheduler

2017-06-08 16:42:58 zxh87 阅读数 189
网络应用需要处理的无非两大类问题,网络io,数据计算。相对于后者,网络i/o的延迟,给应用带来的性能瓶颈大于后者。网络i/o模型大致有如下几种:

同步模型 (synchronous i/o)
阻塞i/o(blocking i/o)
非阻塞io(non-blocking i/o)
多路复用io (mulitplexing i/o)
信号驱动方式i/o (signal-drivern i/o)
异步io (asynchronous io)

网络i/o的本质是socket的读取,socket在linux系统被抽象为流,i/o可以理解为对流的操作。这个操作又分为两个阶段:
1、等待流数据准备(waiting for the data to be ready)
2、从内核向进程复制数据 (copying the data from the kernel to the process)。

对于socket流而已,
第一步通常涉及等待网络上的数据分组到达,然后复制到内核的某个缓冲区。
第二步把数据从内核缓冲区复制到应用进程缓冲区。

i/o模型

举个简单比喻,来了解这几种模型。网络io好比钓鱼,等待鱼上钩就是网络中等待数据准备好的过程,鱼上钩了,把鱼拉上岸就是内核复制数据阶段。钓鱼的人就是一个应用进程。

阻塞i/o (blocking io)

阻塞i/o是最流行的i/o模型。阻塞就是进程被修复,cpu处理其他进程去了。在网络io的时候,进程发起revform系统调用,然后进程就被阻塞了,什么也干不了,直到数据准备好,并且将数据从内核复制到用户进程,最后进程再处理数据,在等待数据到处理数据的两个阶段,整个进程都被阻塞。不能处理别的网络i/o,大致如下图:


阻塞式io的特点是io执行的两个阶段都被block了。
非阻塞io (non-blocking io)
在网络io的时候,非阻塞io也会进行recvform系统调用,检查数据是否准备好,于阻塞io不一样。“非阻塞将大的整片时间的阻塞分成n多个小的阻塞,所以进程不断的有机会别cpu光顾。

也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

非阻塞io的特点就是用户进程不断主动轮训内核是否准备好了数据。
多路复用io (multiplexing io)
可以看出非阻塞调用轮询占据了很大一部分过程。轮询会消耗大量的cpu时间,结合前2中模式。如果轮询不是进程的用户台,而是有人帮忙就好了。多路复用正好处理这样的问题。

多路复用有两个特别的系统调用select或poll。select调用是内核级别的,select轮询相对于费阻塞的区别在于,可以等待多个socket。当其中任何一个socket的数据准备好了,就能返回进行可读,然后进程再进行recvform系统调用,将数据有内核拷贝到用户进程。当然这个过程是阻塞的。多路复用有2个阻塞,select 或poll调用后,会阻塞这个进程,另一个阻塞是进行数据的拷贝,这里不会等到所有的socket的数据准备好,而是部分就好。如何知道有一部分数据到达了呢?监视的事情交给了内核,内核负责数据到达的处理。也可以理解为"非阻塞"吧。

对于多路复用,也就是轮询多个socket。钓鱼的时候,我们雇了一个帮手,他可以同时抛下多个钓鱼竿,任何一杆的鱼一上钩,他就会拉杆。他只负责帮我们钓鱼,并不会帮我们处理,所以我们还得在一帮等着,等他把收杆。我们再处理鱼。多路复用既然可以处理多个I/O,也就带来了新的问题,多个I/O之间的顺序变得不确定了,当然也可以针对不同的编号。

多路复用的特点是通过一种机制一个进程能同时等待IO文件描述符,内核监视这些文件描述符(套接字描述符),其中的任意一个进入读就绪状态,select, poll,epoll函数就可以返回。对于监视的方式,又可以分为 select, poll, epoll三种方式。

了解了前面三种模式,在用户进程进行系统调用的时候,他们在等待数据到来的时候,处理的方式不一样,直接等待,轮询,select或poll轮询,第一个过程有的阻塞,有的不阻塞,有的可以阻塞又可以不阻塞。当时第二个过程都是阻塞的。从整个I/O过程来看,他们都是顺序执行的,因此可以归为同步模型(asynchronous)。都是进程主动向内核检查。


异步I/O(asynchronous I/O)
相对于同步I/O,异步I/O不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。I/O两个阶段,进程都是非阻塞的。


比之前的钓鱼方式不一样,这一次我们雇了一个钓鱼高手。他不仅会钓鱼,还会在鱼上钩之后给我们发短信,通知我们鱼已经准备好了。我们只要委托他去抛竿,然后就能跑去干别的事情了,直到他的短信。我们再回来处理已经上岸的鱼。

同步和异步的区别
通过对上述几种模型的讨论,需要区分阻塞和非阻塞,同步和异步。他们其实是两组概念。区别前一组比较容易,后一种往往容易和前面混合。对于同步和异步而言,往往是一个函数调用之后,是否直接返回结果,如果函数挂起,直到获得结果,这是同步;如果函数马上返回,等数据到达再通知函数,那么这是异步的路程。
至于阻塞和非阻塞,则是函数是否让线程挂起不再往下执行。通常同步阻塞,异步非阻塞。什么情况下是异步阻塞呢?即函数调用之后并没有返回结果而注册了回调函数,非阻塞的情况下,函数也马上返回,可是如果此时函数不返回,那么此时就是阻塞的状态,等数据到达通知函数,依然是异步的过程。
区分阻塞和非阻塞只要区分函数调用之后是否挂起返回就可以了,区分异步和同步,则是函数调用之后,数据或条件满足之后如何通知函数。等待数据返回则是同步,通过回调则是异步。


对于同步模型,主要是第一阶段处理方法不一样。而异步模型,两个阶段都不一样。这里我们忽略了信号驱动模式。

IO-Polling的代码分析

阅读数 1477

IO调度器

阅读数 384

没有更多推荐了,返回首页