您的位置:

深入探究Linux文件系统I/O

一、基础概念

什么是文件系统?

文件系统,即 File System,是指计算机使用的一类存储媒介的物理组织和逻辑管理方式,它维护了对存储媒介的访问和管理。

那么什么是 Linux 文件系统?

Linux 文件系统是在 Linux 操作系统上,对存储设备进行的一种逻辑组织形式。Linux 文件系统一般由三部分构成:超级块、索引节点(inode)、数据块。其中超级块记录着文件系统的元信息;索引节点记录着文件和目录的元数据;数据块储存着文件和目录的实际内容。

Linux 文件系统中的 I/O 是什么?

I/O 即 Input/Output,也就是 Linux 文件系统中的读写操作。读操作是指从存储设备中读取数据到内存中,而写操作则是将内存中的数据写入到存储设备中。I/O 操作是 Linux 文件系统中最基本的操作方式。

二、I/O 操作方式

Linux 文件系统中的 I/O 操作可以分为同步和异步两种方式。

1. 同步 I/O

同步 I/O 是指应用程序在执行 I/O 操作时,必须等待 I/O 操作结束后才能继续执行后续的操作。同步 I/O 的特点是操作可靠,但一旦发生阻塞,就会严重影响应用程序的性能。

#include 
#include 
   
#include 
    
#include 
     

int main(void)
{
    int fd;
    char buf[1024];

    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        printf("File open error!\n");
        return -1;
    }

    read(fd, buf, 1024);

    printf("%s", buf);

    close(fd);
    return 0;
}

     
    
   
  

2. 异步 I/O

异步 I/O 是指应用程序在执行 I/O 操作时,无须等待 I/O 操作结束,可以先执行后续的操作,待 I/O 操作完成后再执行回调函数。异步 I/O 的特点是操作效率高,但开发难度较大。

#include 
#include 
   
#include 
    
#include 
     
#include 
      

void handle_read(int signum)
{
    printf("Asynchronous I/O operation completed!\n");
}

int main(void)
{
    int fd;
    char buf[1024];
    struct sigaction sa;

    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        printf("File open error!\n");
        return -1;
    }

    sa.sa_flags = 0;
    sa.sa_handler = handle_read;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGIO, &sa, NULL) == -1) {
        printf("Signal handler registration error!\n");
        return -1;
    }

    fcntl(fd, F_SETOWN, getpid());
    fcntl(fd, F_SETFL, O_ASYNC);

    while (1) {
        sleep(1);
    }

    close(fd);
    return 0;
}

      
     
    
   
  

三、I/O 多路复用

I/O 多路复用是指让一个或多个进程能够同时监听多个文件描述符的可读可写状态,从而实现多路 I/O 服务。在 Linux 文件系统中,I/O 多路复用主要有 select、poll 和 epoll 三种机制。

1. select

select 是最早实现 I/O 多路复用的方法之一。通过 select 实现 I/O 多路复用需要将待监控的文件描述符加入到 fd_set 集合中,并设置超时时间。select 函数将会阻塞进程,直到有文件描述符发生变化或超时。select 函数会返回发生变化的文件描述符的个数。

#include 
#include 
   
#include 
    
#include 
     
#include 
      

int main()
{
    fd_set rfds;
    struct timeval tv;
    int retval;

    FD_ZERO(&rfds);
    FD_SET(0, &rfds);

    tv.tv_sec = 5;
    tv.tv_usec = 0;

    retval = select(1, &rfds, NULL, NULL, &tv);
    if (retval == -1) {
        printf("select error!\n");
        exit(EXIT_FAILURE);
    } else if (retval) {
        printf("Data is available now.\n");
    } else {
        printf("No data within five seconds.\n");
    }

    return 0;
}

      
     
    
   
  

2. poll

poll 是比 select 更加高效的 I/O 多路复用方法之一。poll 的实现方式与 select 类似,但其使用链表来存储被监控的文件描述符集,解决了 select 方法中文件描述符数量的限制问题。

#include 
#include 
   
#include 
    
#include 
     

int main()
{
    struct pollfd fds[1];
    int retval;

    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;

    retval = poll(fds, 1, 5000);
    if (retval == -1) {
        printf("poll error!\n");
        exit(EXIT_FAILURE);
    } else if (retval) {
        printf("Data is available now.\n");
    } else {
        printf("No data within five seconds.\n");
    }

    return 0;
}

     
    
   
  

3. epoll

epoll 是 Linux 的高级 I/O 多路复用方法。epoll 通过 epoll_create 函数创建 epoll 实例,然后使用 epoll_ctl 函数来向 epoll 中添加、删除、修改被监控的文件描述符。epoll_wait 函数则是阻塞等待文件描述符状态变化的发生,并返回变化的文件描述符集。

#include 
#include 
   
#include 
    
#include 
     

#define MAX_EVENTS 10

int main(int argc, char *argv[])
{
    int sockfd;
    struct epoll_event event;
    struct epoll_event event_array[MAX_EVENTS];
    int epoll_fd, nfds, i;

    sockfd = create_and_bind("8080");
    if (sockfd == -1) exit(EXIT_FAILURE);

    if (make_socket_non_blocking(sockfd) == -1) exit(EXIT_FAILURE);

    if (listen(sockfd, SOMAXCONN) == -1) exit(EXIT_FAILURE);

    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) exit(EXIT_FAILURE);

    event.data.fd = sockfd;
    event.events = EPOLLIN | EPOLLET;

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &event) == -1) exit(EXIT_FAILURE);

    while (1) {
        nfds = epoll_wait(epoll_fd, event_array, MAX_EVENTS, -1);
        for (i = 0; i < nfds; i++) {
            if (event_array[i].events & EPOLLIN) {
                if (event_array[i].data.fd == sockfd) {
                    while (1) {
                        int conn_sock = accept(sockfd, NULL, NULL);
                        if (conn_sock == -1) {
                            if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                                break;
                            } else {
                                perror("accept");
                                break;
                            }
                        }

                        make_socket_non_blocking(conn_sock);

                        event.data.fd = conn_sock;
                        event.events = EPOLLIN | EPOLLET;

                        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_sock, &event) == -1) exit(EXIT_FAILURE);
                    }
                } else {
                    do_use_fd(event_array[i].data.fd);
                }
            }
        }
    }

    close(sockfd);
    return 0;
}

     
    
   
  

四、文件映射技术

文件映射(Memory Mapping)是指操作系统将文件的一段内容直接映射到进程的地址空间,使得进程可以直接访问文件内容,从而加快了文件 I/O 的速度。在 Linux 文件系统中,文件映射可以使用 mmap 函数来实现。

#include 
#include 
           
#include 
              
#include 
     
#include 
      
#include 
       
        #include 
        
         int main(int argc, char *argv[]) { int fd; char *mapped; struct stat file_stat; fd = open(argv[1], O_RDONLY); if (fd == -1) exit(EXIT_FAILURE); if (fstat(fd, &file_stat) == -1) exit(EXIT_FAILURE); mapped = mmap(NULL, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (mapped == MAP_FAILED) exit(EXIT_FAILURE); printf("%s", mapped); if (munmap(mapped, file_stat.st_size) == -1) exit(EXIT_FAILURE); close(fd); return 0; }