精华内容
下载资源
问答
  • libuv_异步文件读写

    2018-04-27 06:21:55
    异步文件操作 1: uv_fs_t: 文件操作的请求对象; result是每次请求的结果; 2: uv_fs_open: 打开一个文件: 文件的打开模式 #include <fcntl.h>,以linux的模式为准; 打开文件成功后: 获得打开...

    架构图、事件循环loop

    这里写图片描述

    这里写图片描述


    异步文件操作

    这里写图片描述

    1: uv_fs_t: 文件操作的请求对象;  
         result是每次请求的结果;
    2: uv_fs_open: 打开一个文件:
         文件的打开模式 #include <fcntl.h>,以linux的模式为准;
    
        打开文件成功后: 获得打开文件的句柄uv_file对象 result
    3: uv_fs_req_cleanup: 清理这个请求所占的资源
    4: uv_fs_close: 关闭掉一个文件
    5: uv_fs_read: 异步的读文件
    6: uv_fs_write: 异步写文件

    这里写图片描述

    配置好存放文件的目录
    例如我的文件在bin文件夹

    这里写图片描述

    这里写图片描述

    打开与关闭文件

    这里写图片描述
    这里写图片描述

    异步读取文件
    这里写图片描述

    这里写图片描述


    stdin/stdout

    1: 学会标准输出的异步读写;
    stdin –> 0
    stdout–>1;

    2:如果要回掉:那么就回掉函数里面清理请求,否则的话,调完以后马上清理;

    异步写文件
    这里写图片描述

    这里写图片描述


    code

    展开全文
  • Linux 异步IO 读写编程

    2015-12-22 15:50:52
    使用AIO读写文件

    使用AIO读写文件

    编译时需连接以下动态库

    LIBS += /usr/lib/x86_64-linux-gnu/librt.so
    LIBS += -lpthread


    #ifndef CAIO_H
    #define CAIO_H
    #include <string>
    #include <aio.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <aio.h>
    #include <string.h>
    #include <errno.h>
    #include <stdlib.h>
    using namespace std;
    enum eAIOState
    {
        eBusyState,
        eReadyState
    };
    
    typedef void (*FucCallBackAIO)(sigval_t);
    class CAIO
    {
    public:
        CAIO();
        ~CAIO();
        bool Open(string strName, int nMode);
        bool  Close();
        int Write(const char* buf, int nSize, bool bAppend, eAIOState&  eIOState);
        int Read(char* buf );
        void SetCallbackFuc(FucCallBackAIO  fAIO);
        int  GetFileSize(const char *szFileName);
        void Seek(int offset, int __whence);
        int Write(const char* buf, int nSize);;
        int GetFD();
    private:
        struct aiocb m_aiocb;
        int m_fdFile;
        string m_strFilename;
        int m_nFileSize;        
    };
    
    #endif // CAIO_H
    
    #include <fcntl.h>
     #include <sys/stat.h>
    #include "caio.h"
    using namespace std;
    CAIO::CAIO()
    {
        m_fdFile =-1;
        m_nFileSize=0;
    }
    
    CAIO::~CAIO()
    {
    
    }
    
    int CAIO::GetFileSize(const char *szFileName)
    {
        int nSize=0;
        struct stat buffer;
        int         status;
        status = stat(szFileName, &buffer);
        if(status>=0)
        {
            nSize=buffer.st_size;
    
        }
        return nSize;
    }
    
    bool CAIO::Open(string strName, int nMode)
    {
        m_fdFile = open(strName.c_str(), nMode);
        if(m_fdFile < 0)
        {
            printf("Open %s failed\n", strName.c_str());
            return false;
        }
    
        bzero((void*) &m_aiocb, sizeof(struct aiocb));
        m_aiocb.aio_fildes = m_fdFile;
        m_aiocb.aio_offset =0;
        m_nFileSize= GetFileSize(strName.c_str());
        m_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
        //  指定回调函数    
        m_aiocb.aio_sigevent._sigev_un._sigev_thread._attribute = NULL;
        //  指定AIO请求    
        return true;
    }
    
    void CAIO::SetCallbackFuc(FucCallBackAIO  fAIO)
    {
        m_aiocb.aio_sigevent._sigev_un._sigev_thread._function =fAIO;
        m_aiocb.aio_sigevent.sigev_value.sival_ptr = &m_aiocb;
    }
    
    int CAIO::Write(const char* buf, int nSize, bool bAppend, eAIOState&  eIOState)
    {
    
        m_aiocb.aio_nbytes = nSize;
        m_aiocb.aio_buf=(void*)buf;
        m_aiocb.aio_offset=0;
        m_aiocb.aio_offset+=m_nFileSize;
        if(!bAppend)
            m_aiocb.aio_offset = 0;
        //  发送异步读请求
        int ret;
        if(eReadyState == eIOState)
        {
            ret = aio_write(&m_aiocb);
            if (ret < 0)
                  sleep(1);
            m_nFileSize +=nSize;
            eIOState = eBusyState;
        }
        return ret;
    }
    
    void CAIO::Seek(int offset, int __whence)
    {
        lseek(m_fdFile, offset, SEEK_SET);
    }
    
    int CAIO::Write(const char* buf, int nSize)
    {
        write(m_fdFile,buf,nSize);
    }
    
    
    int CAIO::GetFD()
    {
        return m_fdFile;
    }
    
    bool CAIO::Close()
    {
        if (close(m_fdFile) < 0)
        {
            return false;
        }
        return true;
    }
    

    #include <fcntl.h>
    #include <aio.h>
    #include  "caio.h"
    #include <iostream>
    using namespace std;
    void msleep( unsigned int ms )
    {
        int microsecs;
        struct timeval tv;
        microsecs = ms * 1000;
        tv.tv_sec  = microsecs / 1000000;
        tv.tv_usec = microsecs % 1000000;
        select( 0, NULL, NULL, NULL, &tv );
    }
    
    eAIOState  g_eIOState;
    void aio_completion_handler( sigval_t sigval )
    {
        int ret;
        struct aiocb *tAiocb;
        //  获取aiocb结构体指针
        tAiocb = (struct aiocb *)sigval.sival_ptr;
        //  获取异步读请求的返回值
        if ((ret = aio_return(tAiocb)) > 0)
        {
            //  获取并输出读取的数据
            printf("value:%s\n", (char*) (tAiocb->aio_buf));
            g_eIOState = eReadyState;
        }
    }
    
    #define BUFFER_SIZE 100
    //  AIO回调函数
    int main()
    {    
        CAIO aio;
        bool bOpen= aio.Open("/media/sda1/Videos/20151221/1/155332.mp4",O_WRONLY);
        aio.SetCallbackFuc(aio_completion_handler);
        if(false == bOpen)
            return 1;
        string strData="yes tes";
        g_eIOState = eReadyState;
        for(int i=0 ; i < 10; )
        {
            if(eReadyState==g_eIOState )
            {
                aio.Write(strData.c_str(), strData.length(), true, g_eIOState);
                i++;
            }
            msleep(100);
        }
        aio.Seek(4, 0);
        strData="fffff";
        msleep(1000);
        aio.Write(strData.c_str(), strData.length());
        aio.Close();
        return 0;
    }
    
    
    


    展开全文
  • linux下aio异步读写详解与实例

    万次阅读 2015-09-27 10:43:56
    1.为什么会有异步I/Oaio异步读写是在linux内核2.6之后才正式纳入其标准。之所以会增加此模块,是因为众所周知我们计算机CPU的执行速度远大于I/O读写的执行速度,如果我们用传统的阻塞式或非阻塞式来操作I/O的话,...

    1.为什么会有异步I/O

    aio异步读写是在linux内核2.6之后才正式纳入其标准。之所以会增加此模块,是因为众所周知我们计算机CPU的执行速度远大于I/O读写的执行速度,如果我们用传统的阻塞式或非阻塞式来操作I/O的话,那么我们在同一个程序中(不用多线程或多进程)就不能同时操作俩个以上的文件I/O,每次只能对一个文件进行I/O操作,很明显这样效率很低下(因为CPU速度远大于I/O操作的速度,所以当执行I/O时,CPU其实还可以做更多的事)。因此就诞生了相对高效的异步I/O

    2.异步I/O的基本概念

    所谓异步I/O即我们在调用I/O操作时(读或写)我们的程序不会阻塞在当前位置,而是在继续往下执行。例如当我们调用异步读API aio_read()时,程序执行此代码之后会接着运行此函数下面的代码,并且与此同时程序也在进行刚才所要读的文件的读取工作,但是具体什么时候读完是不确定的

    3.异步aio的基本API

    API函数 说明
    aio_read 异步读操作
    aio_write 异步写操作
    aio_error 检查异步请求的状态
    aio_return 获得异步请求完成时的返回值
    aio_suspend 挂起调用进程,直到一个或多个异步请求已完成
    aio_cancel 取消异步请求
    lio_list 发起一系列异步I/O请求

    上述的每个API都要用aiocb结构体赖进行操作
    aiocb的结构中常用的成员有

    struct aiocb
    {
        //要异步操作的文件描述符
        int aio_fildes;
        //用于lio操作时选择操作何种异步I/O类型
        int aio_lio_opcode;
        //异步读或写的缓冲区的缓冲区
        volatile void *aio_buf;
        //异步读或写的字节数
        size_t aio_nbytes;
        //异步通知的结构体
        struct sigevent aio_sigevent;
    }

    4异步I/O操作的具体使用

    (1)异步读aio_read

    aio_read函数请求对一个文件进行读操作,所请求文件对应的文件描述符可以是文件,套接字,甚至管道其原型如下

    int aio_read(struct aiocb *paiocb);

    该函数请求对文件进行异步读操作,若请求失败返回-1,成功则返回0,并将该请求进行排队,然后就开始对文件的异步读操作
    需要注意的是,我们得先对aiocb结构体进行必要的初始化
    具体实例如下

    aio_read

    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<fcntl.h>
    #include<aio.h>
    
    
    #define BUFFER_SIZE 1024
    
    int MAX_LIST = 2;
    
    int main(int argc,char **argv)
    {
        //aio操作所需结构体
        struct aiocb rd;
    
        int fd,ret,couter;
    
        fd = open("test.txt",O_RDONLY);
        if(fd < 0)
        {
            perror("test.txt");
        }
    
    
    
        //将rd结构体清空
        bzero(&rd,sizeof(rd));
    
    
        //为rd.aio_buf分配空间
        rd.aio_buf = malloc(BUFFER_SIZE + 1);
    
        //填充rd结构体
        rd.aio_fildes = fd;
        rd.aio_nbytes =  BUFFER_SIZE;
        rd.aio_offset = 0;
    
        //进行异步读操作
        ret = aio_read(&rd);
        if(ret < 0)
        {
            perror("aio_read");
            exit(1);
        }
    
        couter = 0;
    //  循环等待异步读操作结束
        while(aio_error(&rd) == EINPROGRESS)
        {
            printf("第%d次\n",++couter);
        }
        //获取异步读返回值
        ret = aio_return(&rd);
    
        printf("\n\n返回值为:%d",ret);
    
    
        return 0;
    }

    上述实例中aiocb结构体用来表示某一次特定的读写操作,在异步读操作时我们只需要注意4点内容
    1.确定所要读的文件描述符,并写入aiocb结构体中(下面几条一样不再赘余)
    2.确定读所需的缓冲区
    3.确定读的字节数
    4.确定文件的偏移量
    总结以上注意事项:基本上和我们的read函数所需的条件相似,唯一的区别就是多一个文件偏移量

    值得注意的是上述代码中aio_error是用来获取其参数指定的读写操作的状态的
    其原型如下

    int aio_error(struct aiocb *aiopcb);

    当其状态处于EINPROGRESS则I/O还没完成,当处于ECANCELLED则操作已被取消,发生错误返回-1

    而aio_return则是用来返回其参数指定I/O操作的返回值
    其原型如下

    ssize_t aio_return(struct aiocb *paiocb);

    如果操作没完成调用此函数,则会产生错误

    特别提醒在编译上述程序时必须在编译时再加一个-lrt

    上述代码运行结果如下

    (2)异步写aio_write

    aio_writr用来请求异步写操作
    其函数原型如下

    int aio_write(struct aiocb *paiocb);

    aio_write和aio_read函数类似,当该函数返回成功时,说明该写请求以进行排队(成功0,失败-1)
    其和aio_read调用时的区别是就是我们如果在打开文件是,flags设置了O_APPEND则我们在填充aiocb时不需要填充它的偏移量了
    具体实例如下

    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<fcntl.h>
    #include<aio.h>
    
    #define BUFFER_SIZE 1025
    
    int main(int argc,char **argv)
    {
        //定义aio控制块结构体
        struct aiocb wr;
    
        int ret,fd;
    
        char str[20] = {"hello,world"};
    
        //置零wr结构体
        bzero(&wr,sizeof(wr));
    
        fd = open("test.txt",O_WRONLY | O_APPEND);
        if(fd < 0)
        {
            perror("test.txt");
        }
    
        //为aio.buf申请空间
        wr.aio_buf = (char *)malloc(BUFFER_SIZE);
        if(wr.aio_buf == NULL)
        {
            perror("buf");
        }
    
        wr.aio_buf = str;
    
        //填充aiocb结构
        wr.aio_fildes = fd;
        wr.aio_nbytes = 1024;
    
        //异步写操作
        ret = aio_write(&wr);
        if(ret < 0)
        {
            perror("aio_write");
        }
    
        //等待异步写完成
        while(aio_error(&wr) == EINPROGRESS)
        {
            printf("hello,world\n");
        }
    
        //获得异步写的返回值
        ret = aio_return(&wr);
        printf("\n\n\n返回值为:%d\n",ret);
    
        return 0;
    }

    具体运行结果请读者自己去试试

    (3)使用aio_suspend阻塞异步I/O

    aio_suspend函数可以时当前进程挂起,知道有向其注册的异步事件完成为止
    该函数原型如下

    int aio_suspend(const struct aiocb *const cblist[],int n,const struct timespec *timeout);

    第一个参数是个保存了aiocb块地址的数组,我们可以向其内添加想要等待阻塞的异步事件,第二个参数为向cblist注册的aiocb个数,第三个参数为等待阻塞的超时事件,NULL为无限等待

    具体使用如下
    suspend:

    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<fcntl.h>
    #include<aio.h>
    
    
    #define BUFFER_SIZE 1024
    
    int MAX_LIST = 2;
    
    int main(int argc,char **argv)
    {
        //aio操作所需结构体
        struct aiocb rd;
    
        int fd,ret,couter;
    
        //cblist链表
        struct aiocb *aiocb_list[2];
    
    
    
        fd = open("test.txt",O_RDONLY);
        if(fd < 0)
        {
            perror("test.txt");
        }
    
    
    
        //将rd结构体清空
        bzero(&rd,sizeof(rd));
    
    
        //为rd.aio_buf分配空间
        rd.aio_buf = malloc(BUFFER_SIZE + 1);
    
        //填充rd结构体
        rd.aio_fildes = fd;
        rd.aio_nbytes =  BUFFER_SIZE;
        rd.aio_offset = 0;
    
        //将读fd的事件注册
        aiocb_list[0] = &rd;
    
        //进行异步读操作
        ret = aio_read(&rd);
        if(ret < 0)
        {
            perror("aio_read");
            exit(1);
        }
    
        couter = 0;
    //  循环等待异步读操作结束
        while(aio_error(&rd) == EINPROGRESS)
        {
            printf("第%d次\n",++couter);
        }
    
        printf("我要开始等待异步读事件完成\n");
        //阻塞等待异步读事件完成
        ret = aio_suspend(aiocb_list,MAX_LIST,NULL);
    
        //获取异步读返回值
        ret = aio_return(&rd);
    
        printf("\n\n返回值为:%d\n",ret);
    
    
        return 0;
    }

    (4)lio_listio函数

    aio同时还为我们提供了一个可以发起多个或多种I/O请求的接口lio_listio
    这个函数效率很高,因为我们只需一次系统调用(一次内核上下位切换)就可以完成大量的I/O操作
    其函数原型如下

    int lio_listio(int mode,struct aiocb *list[],int nent,struct sigevent *sig);

    第一个参数mode可以有俩个实参,LIO_WAIT和LIO_NOWAIT,前一个会阻塞该调用直到所有I/O都完成为止,后一个则会挂入队列就返回

    具体实例如下
    lio_listio

    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<fcntl.h>
    #include<aio.h>
    
    #define BUFFER_SIZE 1025
    
    int MAX_LIST = 2;
    
    
    int main(int argc,char **argv)
    {
        struct aiocb *listio[2];
        struct aiocb rd,wr;
        int fd,ret;
    
        //异步读事件
        fd = open("test1.txt",O_RDONLY);
        if(fd < 0)
        {
            perror("test1.txt");
        }
    
        bzero(&rd,sizeof(rd));
    
        rd.aio_buf = (char *)malloc(BUFFER_SIZE);
        if(rd.aio_buf == NULL)
        {
            perror("aio_buf");
        }
    
        rd.aio_fildes = fd;
        rd.aio_nbytes = 1024;
        rd.aio_offset = 0;
        rd.aio_lio_opcode = LIO_READ;   ///lio操作类型为异步读
    
        //将异步读事件添加到list中
        listio[0] = &rd;
    
    
        //异步些事件
        fd = open("test2.txt",O_WRONLY | O_APPEND);
        if(fd < 0)
        {
            perror("test2.txt");
        }
    
        bzero(&wr,sizeof(wr));
    
        wr.aio_buf = (char *)malloc(BUFFER_SIZE);
        if(wr.aio_buf == NULL)
        {
            perror("aio_buf");
        }
    
        wr.aio_fildes = fd;
        wr.aio_nbytes = 1024;
    
        wr.aio_lio_opcode = LIO_WRITE;   ///lio操作类型为异步写
    
        //将异步写事件添加到list中
        listio[1] = &wr;
    
        //使用lio_listio发起一系列请求
        ret = lio_listio(LIO_WAIT,listio,MAX_LIST,NULL);
    
        //当异步读写都完成时获取他们的返回值
    
        ret = aio_return(&rd);
        printf("\n读返回值:%d",ret);
    
        ret = aio_return(&wr);
        printf("\n写返回值:%d",ret);
    
    
    
        return 0;
    }

    5.I/O完成时进行异步通知

    当我们的异步I/O操作完成之时,我们可以通过信号通知我们的进程也可用回调函数来进行异步通知,接下来我会为大家主要介绍以下回调函数来进行异步通知,关于信号通知有兴趣的同学自己去学习吧

    使用回调进行异步通知

    该种通知方式使用一个系统回调函数来通知应用程序,要想完成此功能,我们必须在aiocb中设置我们想要进行异步回调的aiocb指针,以用来回调之后表示其自身

    实例如下
    aio线程回调通知

    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<fcntl.h>
    #include<aio.h>
    #include<unistd.h>
    
    #define BUFFER_SIZE 1025
    
    
    void aio_completion_handler(sigval_t sigval)
    {
        //用来获取读aiocb结构的指针
        struct aiocb *prd;
        int ret;
    
        prd = (struct aiocb *)sigval.sival_ptr;
    
        printf("hello\n");
    
        //判断请求是否成功
        if(aio_error(prd) == 0)
        {
            //获取返回值
            ret = aio_return(prd);
            printf("读返回值为:%d\n",ret);
        }
    }
    
    int main(int argc,char **argv)
    {
        int fd,ret;
        struct aiocb rd;
    
        fd = open("test.txt",O_RDONLY);
        if(fd < 0)
        {
            perror("test.txt");
        }
    
    
    
        //填充aiocb的基本内容
        bzero(&rd,sizeof(rd));
    
        rd.aio_fildes = fd;
        rd.aio_buf = (char *)malloc(sizeof(BUFFER_SIZE + 1));
        rd.aio_nbytes = BUFFER_SIZE;
        rd.aio_offset = 0;
    
        //填充aiocb中有关回调通知的结构体sigevent
        rd.aio_sigevent.sigev_notify = SIGEV_THREAD;//使用线程回调通知
        rd.aio_sigevent.sigev_notify_function = aio_completion_handler;//设置回调函数
        rd.aio_sigevent.sigev_notify_attributes = NULL;//使用默认属性
        rd.aio_sigevent.sigev_value.sival_ptr = &rd;//在aiocb控制块中加入自己的引用
    
        //异步读取文件
        ret = aio_read(&rd);
        if(ret < 0)
        {
            perror("aio_read");
        }
    
        printf("异步读以开始\n");
        sleep(1);
        printf("异步读结束\n");
    
    
    
        return 0;
    }

    线程会掉是通过使用aiocb结构体中的aio_sigevent结构体来控制的,
    其定义如下

    struct sigevent
    {
        sigval_t sigev_value;
        int sigev_signo;
        int sigev_notify;
        union {
            int _pad[SIGEV_PAD_SIZE];
             int _tid;
    
            struct {
                void (*_function)(sigval_t);
                void *_attribute;   /* really pthread_attr_t */
            } _sigev_thread;
        } _sigev_un;
    }
    
    #define sigev_notify_function   _sigev_un._sigev_thread._function
    #define sigev_notify_attributes _sigev_un._sigev_thread._attribute
    #define sigev_notify_thread_id   _sigev_un._tid
    展开全文
  • linux文件读写浅析

    千次阅读 2013-05-11 19:51:19
    在《linux内核虚拟文件系统浅析》这篇文章中,我们看到文件是如何被打开、文件的读写是如何被触发的。 对一个已打开的文件fd进行read/write系统调用时...linux内核响应一个块设备文件读写的层次结构如图(摘自ULK3):
    在《linux内核虚拟文件系统浅析》这篇文章中,我们看到文件是如何被打开、文件的读写是如何被触发的。
    对一个已打开的文件fd进行read/write系统调用时,内核中该文件所对应的file结构的f_op->read/f_op->write被调用。
    本文将顺着这条路走下去,大致看看普通磁盘文件的读写是怎样实现的。

    linux内核响应一个块设备文件读写的层次结构如图(摘自ULK3):


    1、VFS,虚拟文件系统。
    之前我们已经看到f_op->read/f_op->write如何被调用,这就是VFS干的事(参见:《linux内核虚拟文件系统浅析》);

    2、Disk Caches,磁盘高速缓存。
    将磁盘上的数据缓存在内存中,加速文件的读写。实际上,在一般情况下,read/write是只跟缓存打交道的。(当然,存在特殊情况。下面会说到。)
    read就直接从缓存读数据。如果要读的数据还不在缓存中,则触发一次读盘操作,然后等待磁盘上的数据被更新到磁盘高速缓存中;write也是直接写到缓存里去,然后就不用管了。后续内核会负责将数据写回磁盘。

    为了实现这样的缓存,每个文件的inode内嵌了一个address_space结构,通过inode->i_mapping来访问。address_space结构中维护了一棵radix树,用于磁盘高速缓存的内存页面就挂在这棵树上。而既然磁盘高速缓存是跟文件的inode关联上的,则打开这个文件的每个进程都共用同一份缓存。
    radix树的具体实现细节这里可以不用关心,可以把它理解成一个数组。数组中的每个元素就是一个页面,文件的内容就顺序存放在这些页面中。

    于是,通过要读写的文件pos,可以换算得到要读写的是第几页(pos是以字节为单位,只需要除以每个页的字节数即可)。
    inode被载入内存的时候,对应的磁盘高速缓存是空的(radix树上没有页面)。随着文件的读写,磁盘上的数据被载入内存,相应的内存页被挂到radix树的相应位置上。
    如果文件被写,则仅仅是对应inode的radix树上的对应页上的内容被更新,并不会直接写回磁盘。这样被写过,但还没有更新到磁盘的页称为脏页。
    内核线程pdflush定期将每个inode上的脏页更新到磁盘,也会适时地将radix上的页面回收,这些内容都不在这里深入探讨了(可以参考《linux页面回收浅析》)。

    当需要读写的文件内容尚未载入到对应的radix树时,read/write的执行过程会向底层的“通用块层”发起读请求,以便将数据读入。
    而如果文件打开时指定了O_DIRECT选项,则表示绕开磁盘高速缓存,直接与“通用块层”打交道。
    既然磁盘高速缓存提供了有利于提高读写效率的缓存机制,为什么又要使用O_DIRECT选项来绕开它呢?一般情况下,这样做的应用程序会自己在用户态维护一套更利于应用程序使用的专用的缓存机制,用以取代内核提供的磁盘高速缓存这种通用的缓存机制。(数据库程序通常就会这么干。)
    既然使用O_DIRECT选项后,文件的缓存从内核提供的磁盘高速缓存变成了用户态的缓存,那么打开同一文件的不同进程将无法共享这些缓存(除非这些进程再创建一个共享内存什么的)。而如果对于同一个文件,某些进程使用了O_DIRECT选项,而某些又没有呢?没有使用O_DIRECT选项的进程读写这个文件时,会在磁盘高速缓存中留下相应的内容;而使用了O_DIRECT选项的进程读写这个文件时,需要先将磁盘高速缓存里面对应本次读写的脏数据写回磁盘,然后再对磁盘进行直接读写。
    关于O_DIRECT选项带来的direct_IO的具体实现细节,说来话长,在这里就不做介绍了。可以参考《linux异步IO浅析》。

    3、Generic Block Layer,通用块层。
    linux内核为块设备抽象了统一的模型,把块设备看作是由若干个扇区组成的数组空间。扇区是磁盘设备读写的最小单位,通过扇区号可以指定要访问的磁盘扇区。
    上层的读写请求在通用块层被构造成一个或多个bio结构,这个结构里面描述了一次请求--访问的起始扇区号?访问多少个扇区?是读还是写?相应的内存页有哪些、页偏移和数据长度是多少?等等……

    这里面主要有两个问题:要访问的扇区号从哪里来?内存是怎么组织的?
    前面说过,上层的读写请求通过文件pos可以定位到要访问的是相应的磁盘高速缓存的第几个页,而通过这个页index就可以知道要访问的是文件的第几个扇区,得到扇区的index。
    但是,文件的第几个扇区并不等同于磁盘上的第几个扇区,得到的扇区index还需要由特定文件系统提供的函数来转换成磁盘的扇区号。文件系统会记载当前磁盘上的扇区使用情况,并且对于每一个inode,它依次使用了哪些扇区。(参见《linux文件系统实现浅析》)
    于是,通过文件系统提供的特定函数,上层请求的文件pos最终被对应到了磁盘上的扇区号。
    可见,上层的一次请求可能跨多个扇区,可能形成多个非连续的扇区段。对应于每个扇区段,一个bio结构被构造出来。而由于块设备一般都支持一次性访问若干个连续的扇区,所以一个扇区段(不止一个扇区)可以包含在代表一次块设备IO请求的一个bio结构中。

    接下来谈谈内存的组织。既然上层的一次读写请求可能跨多个扇区,它也可能跨越磁盘高速缓存上的多个页。于是,一个bio里面包含的扇区请求可能会对应一组内存页。而这些页是单独分配的,内存地址很可能不连续。
    那么,既然bio描述的是一次块设备请求,块设备能够一次性访问一组连续的扇区,但是能够一次性对一组非连续的内存地址进行存取吗?
    块设备一般是通过DMA,将块设备上一组连续的扇区上的数据拷贝到一组连续的内存页面上(或将一组连续的内存页面上的数据拷贝到块设备上一组连续的扇区),DMA本身一般是不支持一次性访问非连续的内存页面的。
    但是某些体系结构包含了io-mmu。就像通过mmu可以将一组非连续的物理页面映射成连续的虚拟地址一样,对io-mmu进行编程,可以让DMA将一组非连续的物理内存看作连续的。所以,即使一个bio包含了非连续的多段内存,它也是有可能可以在一次DMA中完成的。当然,不是所有的体系结构都支持io-mmu,所以一个bio也可能在后面的设备驱动程序中被拆分成多个设备请求。

    每个被构造的bio结构都会分别被提交,提交到底层的IO调度器中。

    4、I/O SchedulerLayer,IO调度器。
    我们知道,磁盘是通过磁头来读写数据的,磁头在定位扇区的过程中需要做机械的移动。相比于电和磁的传递,机械运动是非常慢速的,这也就是磁盘为什么那么慢的主要原因。
    IO调度器要做的事情就是在完成现有请求的前提下,让磁头尽可能少移动,从而提高磁盘的读写效率。最有名的就是“电梯算法”。
    在IO调度器中,上层提交的bio被构造成request结构,一个request结构包含了一组顺序的bio。而每个物理设备会对应一个request_queue,里面顺序存放着相关的request。
    新的bio可能被合并到request_queue中已有的request结构中(甚至合并到已有的bio中),也可能生成新的request结构并插入到request_queue的适当位置上。具体怎么合并、怎么插入,取决于设备驱动程序选择的IO调度算法。大体上可以把IO调度算法就想象成“电梯算法”,尽管实际的IO调度算法有所改进。
    除了类似“电梯算法”的IO调度算法,还有“none”算法,这实际上是没有算法,也可以说是“先来先服务算法”。因为现在很多块设备已经能够很好地支持随机访问了(比如固态磁盘、flash闪存),使用“电梯算法”对于它们没有什么意义。

    IO调度器除了改变请求的顺序,还可能延迟触发对请求的处理。因为只有当请求队列有一定数目的请求时,“电梯算法”才能发挥其功效,否则极端情况下它将退化成“先来先服务算法”。
    这是通过对request_queue的plug/unplug来实现的,plug相当于停用,unplug相当于恢复。请求少时将request_queue停用,当请求达到一定数目,或者request_queue里最“老”的请求已经等待很长一段时间了,这时候才将request_queue恢复。
    在request_queue恢复的时候,驱动程序提供的回调函数将被调用,于是驱动程序开始处理request_queue。
    一般来说,read/write系统调用到这里就返回了。返回之后可能等待(同步)或是继续干其他事(异步)。而返回之前会在任务队列里面添加一个任务,而处理该任务队列的内核线程将来会执行request_queue的unplug操作,以触发驱动程序处理请求。

    5、Device Driver,设备驱动程序。
    到了这里,设备驱动程序要做的事情就是从request_queue里面取出请求,然后操作硬件设备,逐个去执行这些请求。

    除了处理请求,设备驱动程序还要选择IO调度算法,因为设备驱动程序最知道设备的属性,知道用什么样的IO调度算法最合适。甚至于,设备驱动程序可以将IO调度器屏蔽掉,而直接对上层的bio进行处理。(当然,设备驱动程序也可实现自己的IO调度算法。)
    可以说,IO调度器是内核提供给设备驱动程序的一组方法。用与不用、使用怎样的方法,选择权在于设备驱动程序。

    于是,对于支持随机访问的块设备,驱动程序除了选择“none”算法,还有一种更直接的做法,就是注册自己的bio提交函数。这样,bio生成后,并不会使用通用的提交函数,被提交到IO调度器,而是直接被驱动程序处理。
    但是,如果设备比较慢的话,bio的提交可能会阻塞较长时间。所以这种做法一般被基于内存的“块设备”驱动使用(当然,这样的块设备是由驱动程序虚拟的)。


    下面大致介绍一下read/write的执行流程:
    sys_read。通过fd得到对应的file结构,然后调用vfs_read;
    vfs_read。各种权限及文件锁的检查,然后调用file->f_op->read(若不存在则调用do_sync_read)。file->f_op是从对应的inode->i_fop而来,而inode->i_fop是由对应的文件系统类型在生成这个inode时赋予的。file->f_op->read很可能就等同于do_sync_read;
    do_sync_read。f_op->read是完成一次同步读,而f_op->aio_read完成一次异步读。do_sync_read则是利用f_op->aio_read这个异步读操作来完成同步读,也就是在发起一次异步读之后,如果返回值是-EIOCBQUEUED,则进程睡眠,直到读完成即可。但实际上对于磁盘文件的读,f_op->aio_read一般不会返回-EIOCBQUEUED,除非是设置了O_DIRECT标志aio_read,或者是对于一些特殊的文件系统(如nfs这样的网络文件系统);
    f_op->aio_read。这个函数通常是由generic_file_aio_read或者其封装来实现的;
    generic_file_aio_read。一次异步读可能包含多个读操作(对应于readv系统调用),对于其中的每一个,调用do_generic_file_read;
    do_generic_file_read。主要流程是在radix树里面查找是否存在对应的page,且该页可用。是则从page里面读出所需的数据,然后返回,否则通过file->f_mapping->a_ops->readpage去读这个页。(file->f_mapping->a_ops->readpage返回后,说明读请求已经提交了。但是磁盘上的数据还不一定就已经读上来了,需要等待数据读完。等待的方法就是lock_page:在调用file->f_mapping->a_ops->readpage之前会给page置PG_locked标记。而数据读完后,会将该标记清除,这个后面会看到。而这里的lock_page就是要等待PG_locked标记被清除。);
    file->f_mapping是从对应inode->i_mapping而来,inode->i_mapping->a_ops是由对应的文件系统类型在生成这个inode时赋予的。而各个文件系统类型提供的a_ops->readpage函数一般是mpage_readpage函数的封装;
    mpage_readpage。调用do_mpage_readpage构造一个bio,再调用mpage_bio_submit将其提交;
    do_mpage_readpage。根据page->index确定需要读的磁盘扇区号,然后构造一组bio。其中需要使用文件系统类型提供的get_block函数来对应需要读取的磁盘扇区号;
    mpage_bio_submit。设置bio的结束回调bio->bi_end_io为mpage_end_io_read,然后调用submit_bio提交这组bio;
    submit_bio。调用generic_make_request将bio提交到磁盘驱动维护的请求队列中;
    generic_make_request。一个包装函数,对于每一个bio,调用__generic_make_request;
    __generic_make_request。获取bio对应的块设备文件对应的磁盘对象的请求队列bio->bi_bdev->bd_disk->queue,调用q->make_request_fn将bio添加到队列;
    q->make_request_fn。设备驱动程序在其初始化时会初始化这个request_queue结构,并且设置q->make_request_fn和q->request_fn(这个下面就会用到)。前者用于将一个bio组装成request添加到request_queue,后者用于处理request_queue中的请求。一般情况下,设备驱动通过调用blk_init_queue来初始化request_queue,q->request_fn需要给定,而q->make_request_fn使用了默认的__make_request;
    __make_request。会根据不同的调度算法来决定如何添加bio,生成对应的request结构加入request_queue结构中,并且决定是否调用q->request_fn,或是在kblockd_workqueue任务队列里面添加一个任务,等kblockd内核线程来调用q->request_fn;
    q->request_fn。由驱动程序定义的函数,负责从request_queue里面取出request进行处理。从添加bio到request被取出,若干的请求已经被IO调度算法整理过了。驱动程序负责根据request结构里面的描述,将实际物理设备里面的数据读到内存中。当驱动程序完成一个request时,会调用end_request(或类似)函数,以结束这个request;
    end_request。完成request的收尾工作,并且会调用对应的bio的的结束方法bio->bi_end_io,即前面设置的mpage_end_io_read;
    mpage_end_io_read。如果page已更新则设置其up-to-date标记,并为page解锁,唤醒等待page解锁的进程。最后释放bio对象;

    sys_write。跟sys_read一样,对应的vfs_write、do_sync_write、f_op->aio_write、generic_file_aio_write被顺序调用;
    generic_file_aio_write。调用__generic_file_aio_write_nolock来进行写的处理,将数据写到磁盘高速缓存中。写完成之后,判断如果文件打开时使用了O_SYNC标记,则再调用sync_page_range将写入到磁盘高速缓存中的数据同步到磁盘(只同步文件头信息);
    __generic_file_aio_write_nolock。进行一些检查之后,调用generic_file_buffered_write;
    generic_file_buffered_write。调用generic_perform_write执行写,写完成之后,判断如果文件打开时使用了O_SYNC标记,则再调用generic_osync_inode将写入到磁盘高速缓存中的数据同步到磁盘(同步文件头信息和文件内容);
    generic_perform_write。一次异步写可能包含多个写操作(对应于writev系统调用),对于其中牵涉的每一个page,调用file->f_mapping->a_ops->write_begin准备好需要写的磁盘高速缓存页面,然后将需要写的数据拷入其中,最后调用file->f_mapping->a_ops->write_end完成写;
    file->f_mapping是从对应inode->i_mapping而来,inode->i_mapping->a_ops是由对应的文件系统类型在生成这个inode时赋予的。而各个文件系统类型提供的file->f_mapping->a_ops->write_begin函数一般是block_write_begin函数的封装、file->f_mapping->a_ops->write_end函数一般是generic_write_end函数的封装;
    block_write_begin。调用grab_cache_page_write_begin在radix树里面查找要被写的page,如果不存在则创建一个。调用__block_prepare_write为这个page准备一组buffer_head结构,用于描述组成这个page的数据块(利用其中的信息,可以生成对应的bio结构);
    generic_write_end。调用block_write_end提交写请求,然后设置page的dirty标记;
    block_write_end。调用__block_commit_write为page中的每一个buffer_head结构设置dirty标记;
    至此,write调用就要返回了。如果文件打开时使用了O_SYNC标记,sync_page_range或generic_osync_inode将被调用。否则write就结束了,等待pdflush内核线程发现radix树上的脏页,并最终调用到do_writepages写回这些脏页;
    sync_page_range也是调用generic_osync_inode来实现的,而generic_osync_inode最终也会调用到do_writepages;
    do_writepages。调用inode->i_mapping->a_ops->writepages,而后者一般是mpage_writepages函数的包装;
    mpage_writepages。检查radix树中需要写回的page,对每一个page调用__mpage_writepage;
    __mpage_writepage。这里也是构造bio,然后调用mpage_bio_submit来进行提交;
    后面的流程跟read几乎就一样了……

    展开全文
  • c/c++ 文件异步读写

    千次阅读 2018-02-18 16:43:14
    文件异步读写 1: 普通的读写文件打开文件都是同步的,比如C的fopen, fclose, fread等; 2: 磁盘的访问速度远远的低于内存,所以OS要等待磁盘设备来读写。 3: 如果采用同步,那么任务将会挂机,等待磁盘读好数据好...
  • linux文件读写流程

    千次阅读 2017-03-29 17:26:56
    在《linux内核虚拟文件系统浅析》这篇文章中,我们看到文件是如何被打开、文件的读写是如何被触发的。 对一个已打开的文件fd进行read/write系统调用时...linux内核响应一个块设备文件读写的层次结构如图(摘自ULK3):
  • windows文件异步操作和Linux EPOLL模型

    千次阅读 2014-07-05 23:05:48
    在windows系统下面存在三种形式的异步文件操作: 1OVERLAPPED IO 2APC 3IO完成端口
  • 第一组:ifstream与CFile的效率 ... ifstream是标准C++中的文件输入流,在实际应用中,用它的read方法,是必须缓冲...在一次测试中,用它将一个800k的文件一次读入字符串中,和MFC中的CFile类的read方法,效率竟然相
  • linux文件读写的流程

    2016-10-09 23:15:10
    在《linux内核虚拟文件系统浅析》这篇文章中,我们看到文件是如何被打开、文件读写是如何被触发的。  对一个已打开的文件fd进行read/write系统调用时,内核中该文件所对应的file结构的f_op->read/f_op->write被...
  • Linux页高速缓存与文件读写

    千次阅读 2018-12-13 15:29:27
    了解到了页高速缓存之后,页高速缓存在内核中是怎样具体与文件读写挂钩的呢,在本文中就对这个进行探究,基于:Linux-4.4.4内核源码。 一、read分析 可参考博文:linux内核分析:read过程分析 ...
  • Linux下用C++流的方式读写文件

    千次阅读 2019-03-20 22:37:33
    本文主要总结在Linux下用C++流的方式读写文件,主要用到类ofstream和类ifstream以及输出操作符(<<)和输入操作符(>>)。下面将分别通过两个读写文件示例说明C++流方式读写文件用法。 一、C++流方式写...
  • 摘要:本次案例主要讲述java高性能读写文件,如何提高文件IO 关键词:java 读写文件 IO NIO 文件碎片 小文件 文件刷盘 顺序与随即读写
  • Linux C++内存映射方式读写文件 Linux C++内存映射方式读写文件 584851044 0人评论 5181人阅读 2014-03-01 15:12:08 内存映射就是将磁盘上的文件映射到系统内存中,对内存的修改可以同步到对磁盘文件的修改。...
  • python 文件读写

    千次阅读 2018-01-11 16:43:20
    博客园首页新随笔联系订阅管理 随笔 - 69 文章 - 3 评论 ...I/O操作概述文件读写实现原理与操作步骤文件打开模式Python文件操作步骤示例Python文件读取相关方法文件读写与字符编码 一、I/O操作概述 I/O
  • 文件读写流程

    千次阅读 2011-10-24 15:27:51
    在《linux内核虚拟文件系统浅析》这篇文章中,我们看到文件是如何被打开、文件的读写是如何被触发的。 对一个已打开的文件fd进行read/write系统调用时...linux内核响应一个块设备文件读写的层次结构如图(摘自ULK3):
  • 最近有一个题目需要在Linux下进行大量文件异步io读写操作,求问应该怎么实现比较好?? 查了一下网上有用到POSIX AIO、libaio原生异步IO、libeio这三种不同方法的,求问它们的区别是什么?现在比较推荐使用哪种呢...
  • Linux C++内存映射方式读写文件

    千次阅读 2017-01-16 16:44:05
    内存映射就是将磁盘上...可以对大数据文件处理,并且可以提高文件读写速度。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  • Linux文件异步I/O编程解析

    千次阅读 2013-07-11 15:15:03
    Linux文件异步I/O编程解析 1. 阻塞式IO过程 int fd = open(const char *pathname, int flags, mode_t mode); ssize_t pread(int fd, void *buf, size_t count, off_t offset); ssize_t pwrite(int fd, const ...
  • linux c++ 多线程代码 对文件读写

    千次阅读 2017-02-15 10:42:35
    #include #include #include using namespace std; pthread_mutex_t file_mutex; void* product(void * arg){  for(int i = 0; i  pthread_mutex_lock(&file_mutex);  ofstream
  • linux异步IO浅析

    千次阅读 2013-05-12 01:29:59
    知道异步IO已经很久了,但是直到最近,才真正用它来解决一下实际问题(在一个CPU密集型的应用中,有一些需要处理的数据可能放在磁盘上。...假此机会,也顺便研究了一下linux下的异步IO的实现。 linux
  • python的文件读写操作

    千次阅读 多人点赞 2019-04-28 08:30:49
    文件读写实现原理与操作步骤 文件打开模式 Python文件操作步骤示例 Python文件读取相关方法 文件读写与字符编码 一、I/O操作概述 I/O在计算机中是指Input/Output,也就是Stream(流)的输入和输出。这里的输入和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 58,538
精华内容 23,415
关键字:

异步文件读写linux

linux 订阅