Skip to content
Published at:

File文件IO

文件描述符

在 Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数(I:input,输入;O:output,输出),对文件进行相应的操作( open()、close()、write() 、read() 等)。

打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。

程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。

c
#define STDIN_FILENO  0 //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符

在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。

最大打开的文件个数

Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用 close() 函数关闭文件。

  • cat /proc/sys/fs/file-max:查看当前系统允许打开最大文件个数
  • ulimit -a: 当前默认设置最大打开文件个数1024
  • ulimit -n 4096:修改默认设置最大打开文件个数为4096

错误处理函数

errno 是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定义。查看错误代码errno是调试程序的一个重要方法。

当Linux C api函数发生异常时,一般会将errno全局变量赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因。

c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    FILE* fp = fopen("xixi.txt", "r");
    if (NULL == fp) {
        printf("%d\n", errno);            // 打印错误码
        printf("%s\n", strerror(errno));  // 把errno的数字转换成相应的文字
        perror("fopen err:");             // 打印错误原因的字符串
    }
    return 0;
}

errno.h

c
/*
 * Error codes
 */
#define EPERM           1               /* Operation not permitted */
#define ENOENT          2               /* No such file or directory */
#define ESRCH           3               /* No such process */
#define EINTR           4               /* Interrupted system call */
#define EIO             5               /* Input/output error */
#define ENXIO           6               /* Device not configured */
#define E2BIG           7               /* Argument list too long */
#define ENOEXEC         8               /* Exec format error */
#define EBADF           9               /* Bad file descriptor */
#define ECHILD          10              /* No child processes */
#define EDEADLK         11              /* Resource deadlock avoided */
                                        /* 11 was EAGAIN */
#define ENOMEM          12              /* Cannot allocate memory */
#define EACCES          13              /* Permission denied */
#define EFAULT          14              /* Bad address */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define ENOTBLK         15              /* Block device required */
#endif
#define EBUSY           16              /* Device / Resource busy */
#define EEXIST          17              /* File exists */
#define EXDEV           18              /* Cross-device link */
#define ENODEV          19              /* Operation not supported by device */
#define ENOTDIR         20              /* Not a directory */
#define EISDIR          21              /* Is a directory */
#define EINVAL          22              /* Invalid argument */
#define ENFILE          23              /* Too many open files in system */
#define EMFILE          24              /* Too many open files */
#define ENOTTY          25              /* Inappropriate ioctl for device */
#define ETXTBSY         26              /* Text file busy */
#define EFBIG           27              /* File too large */
#define ENOSPC          28              /* No space left on device */
#define ESPIPE          29              /* Illegal seek */
#define EROFS           30              /* Read-only file system */
#define EMLINK          31              /* Too many links */
#define EPIPE           32              /* Broken pipe */

/* math software */
#define EDOM            33              /* Numerical argument out of domain */
#define ERANGE          34              /* Result too large */

/* non-blocking and interrupt i/o */
#define EAGAIN          35              /* Resource temporarily unavailable */
#define EWOULDBLOCK     EAGAIN          /* Operation would block */
#define EINPROGRESS     36              /* Operation now in progress */
#define EALREADY        37              /* Operation already in progress */

/* ipc/network software -- argument errors */
#define ENOTSOCK        38              /* Socket operation on non-socket */
#define EDESTADDRREQ    39              /* Destination address required */
#define EMSGSIZE        40              /* Message too long */
#define EPROTOTYPE      41              /* Protocol wrong type for socket */
#define ENOPROTOOPT     42              /* Protocol not available */
#define EPROTONOSUPPORT 43              /* Protocol not supported */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define ESOCKTNOSUPPORT 44              /* Socket type not supported */
#endif
#define ENOTSUP         45              /* Operation not supported */
#if !__DARWIN_UNIX03 && !defined(KERNEL)
#define EOPNOTSUPP       ENOTSUP        /* Operation not supported on socket */
#endif /* !__DARWIN_UNIX03 && !KERNEL */

#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define EPFNOSUPPORT    46              /* Protocol family not supported */
#endif
#define EAFNOSUPPORT    47              /* Address family not supported by protocol family */
#define EADDRINUSE      48              /* Address already in use */
#define EADDRNOTAVAIL   49              /* Can't assign requested address */

/* ipc/network software -- operational errors */
#define ENETDOWN        50              /* Network is down */
#define ENETUNREACH     51              /* Network is unreachable */
#define ENETRESET       52              /* Network dropped connection on reset */
#define ECONNABORTED    53              /* Software caused connection abort */
#define ECONNRESET      54              /* Connection reset by peer */
#define ENOBUFS         55              /* No buffer space available */
#define EISCONN         56              /* Socket is already connected */
#define ENOTCONN        57              /* Socket is not connected */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define ESHUTDOWN       58              /* Can't send after socket shutdown */
#define ETOOMANYREFS    59              /* Too many references: can't splice */
#endif
#define ETIMEDOUT       60              /* Operation timed out */
#define ECONNREFUSED    61              /* Connection refused */

#define ELOOP           62              /* Too many levels of symbolic links */
#define ENAMETOOLONG    63              /* File name too long */

/* should be rearranged */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define EHOSTDOWN       64              /* Host is down */
#endif
#define EHOSTUNREACH    65              /* No route to host */
#define ENOTEMPTY       66              /* Directory not empty */

/* quotas & mush */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define EPROCLIM        67              /* Too many processes */
#define EUSERS          68              /* Too many users */
#endif
#define EDQUOT          69              /* Disc quota exceeded */

/* Network File System */
#define ESTALE          70              /* Stale NFS file handle */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define EREMOTE         71              /* Too many levels of remote in path */
#define EBADRPC         72              /* RPC struct is bad */
#define ERPCMISMATCH    73              /* RPC version wrong */
#define EPROGUNAVAIL    74              /* RPC prog. not avail */
#define EPROGMISMATCH   75              /* Program version wrong */
#define EPROCUNAVAIL    76              /* Bad procedure for program */
#endif

#define ENOLCK          77              /* No locks available */
#define ENOSYS          78              /* Function not implemented */

#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define EFTYPE          79              /* Inappropriate file type or format */
#define EAUTH           80              /* Authentication error */
#define ENEEDAUTH       81              /* Need authenticator */

/* Intelligent device errors */
#define EPWROFF         82      /* Device power is off */
#define EDEVERR         83      /* Device error, e.g. paper out */
#endif

#define EOVERFLOW       84              /* Value too large to be stored in data type */

/* Program loading errors */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define EBADEXEC        85      /* Bad executable */
#define EBADARCH        86      /* Bad CPU type in executable */
#define ESHLIBVERS      87      /* Shared library version mismatch */
#define EBADMACHO       88      /* Malformed Macho file */
#endif

#define ECANCELED       89              /* Operation canceled */

#define EIDRM           90              /* Identifier removed */
#define ENOMSG          91              /* No message of desired type */
#define EILSEQ          92              /* Illegal byte sequence */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define ENOATTR         93              /* Attribute not found */
#endif

#define EBADMSG         94              /* Bad message */
#define EMULTIHOP       95              /* Reserved */
#define ENODATA         96              /* No message available on STREAM */
#define ENOLINK         97              /* Reserved */
#define ENOSR           98              /* No STREAM resources */
#define ENOSTR          99              /* Not a STREAM */
#define EPROTO          100             /* Protocol error */
#define ETIME           101             /* STREAM ioctl timeout */

#if __DARWIN_UNIX03 || defined(KERNEL)
/* This value is only discrete when compiling __DARWIN_UNIX03, or KERNEL */
#define EOPNOTSUPP      102             /* Operation not supported on socket */
#endif /* __DARWIN_UNIX03 || KERNEL */

#define ENOPOLICY       103             /* No such policy registered */

#if __DARWIN_C_LEVEL >= 200809L
#define ENOTRECOVERABLE 104             /* State not recoverable */
#define EOWNERDEAD      105             /* Previous owner died */
#endif

#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define EQFULL          106             /* Interface output queue is full */
#define ELAST           106             /* Must be equal largest errno */
#endif

常用IO函数

  • int open(const char \*pathname, int flags);
  • int open(const char \*pathname, int flags, mode_t mode);
  • ssize_t read(int fd, void \*buf, size_t count);
  • ssize_t write(int fd, const void \*buf, size_t count);
  • off_t lseek(int fd, off_t offset, int whence);
  • int close(int fd);
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/**
 * @brief
 *
 * @param fd 文件描述符
 * @param offset 偏移
 *
 * @param whence 起始(从哪里开始)SEEK_SET SEEK_CUR SEEK_END
 * @return off_t
 */
// off_t lseek(int fd, off_t offset, int whence);

int main(int argc, char* argv[]) {

    // int open(const char *pathname, int flags);
    int fd = open("xixi.txt", O_RDWR);

    char buf[1024] = {0};

    // ssize_t read(int fd, void *buf, size_t count);
    int len = read(fd, buf, 1024);
    if (len < 0) {
        perror("Failed to read:");
        return len;
    }

    // ssize_t write(int fd, const void *buf, size_t count);
    int ret = write(fd, "xixi", 5);
    if (ret < 0) {
        perror("Failed to write:");
        return ret;
    }

    // hehe xixi
    printf("buf = %s\n", buf);

    // int close(int fd);
    close(fd);

    return 0;
}

lseek_demo.c

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/**
 * @brief
 *
 * @param fd 文件描述符
 * @param offset 偏移
 *
 * @param whence 起始(从哪里开始)SEEK_SET SEEK_CUR SEEK_END
 * @return off_t
 */
// off_t lseek(int fd, off_t offset, int whence);

int main(int argc, char* argv[]) {

    // int open(const char *pathname, int flags);
    int fd = open("xixi.txt", O_RDWR);  // hehexixi

    char buf[1024] = {0};

    // ssize_t read(int fd, void *buf, size_t count);
    int len = read(fd, buf, 2);
    if (len < 0) {
        perror("Failed to read:");
        return len;
    }
    printf("first read: %s\n", buf);  // hehe

    // off_t lseek(int fd, off_t offset, int whence);
    int ret = lseek(fd, 0, SEEK_SET);
    if (ret == -1) {
        perror("Failed to lseek:");
        close(fd);
        return ret;
    }

    printf("current position: %d\n", ret);
    len = read(fd, buf, 1024);

    // hehexixi
    printf("second read = %s\n", buf);  // hehexixi

    // int close(int fd);
    close(fd);

    return 0;
}

文件描述符的复制

dup() 和 dup2() 是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件。

这个过程类似于现实生活中的配钥匙,钥匙相当于文件描述符,锁相当于文件,本来一个钥匙开一把锁,相当于,一个文件描述符对应一个文件,现在,我们去配钥匙,通过旧的钥匙复制了一把新的钥匙,这样的话,旧的钥匙和新的钥匙都能开启这把锁。

对比于 dup(), dup2() 也一样,通过原来的文件描述符复制出一个新的文件描述符,这样的话,原来的文件描述符和新的文件描述符都指向同一个文件,我们操作这两个文件描述符的任何一个,都能操作它所对应的文件。

函数:

  • int dup(int oldfd);
  • int dup2(int oldfd, int newfd);

dup和dup2区别:dup是内核分配一个最小可用的文件描述符;dup2是自己指定newfd,如果newfd被占用了,会先close这个newfd,然后在复用这个newfd

c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
    int dup(int oldfd);
    int newfd = dup(STDOUT_FILENO);
    write(newfd, "xixi", 4);

    // int dup2(int oldfd, int newfd);
    int fd = open("xixi.txt", O_RDWR);
    int ret = dup2(fd, STDOUT_FILENO);
    int len = write(fd, "from fd\n", 8);

    len = write(STDOUT_FILENO, "from stdout", 11);
    if (len == -1) {
        perror("Failed to read:");
        return len;
    }

    close(fd);
    close(STDOUT_FILENO);
    return 0;
}

操作文件的特性

操作文件描述符:

  • int fcntl(int fd, int cmd, ... /_ arg _/);

操作命令cmd:

  • 复制一个现有的描述符(cmd=F_DUPFD)==> 相当于dup
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  • 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)

fcntl_demo.c

把stdin改成nonblocking

c
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

// 修改stdin成非阻塞
// read stdin --> 阻塞(等待键盘输入) (网络...)
// read 文件 --> 非阻塞()
int main(int argc, char* argv[]) {
    char buf[1024] = {0};

    // 1.block(default)
    int len = read(STDIN_FILENO, buf, 1024);
    if (len < 0) {
        perror("Failed to read\n");
        return len;
    }
    printf("buf = %s\n", buf);
    return 0;

    // 2. fcntl
    // int fcntl(int fd, int cmd, ... /* arg */ );
    int flag = fcntl(STDIN_FILENO, F_GETFL);
    flag |= O_NONBLOCK;  // 追加flag:O_NONBLOCK
    int ret = fcntl(STDIN_FILENO, F_SETFL, flag);
    if (ret < 0) {
        perror("Failed to set fd flag");
        return ret;
    }

    while (true) {
        int len = read(STDIN_FILENO, buf, 1024);  // non-blocking
        if (len < 0) {
            if (errno == EAGAIN) {  // 或EWOULDBLOCK
                sleep(2);
                printf("try again\n");
                continue;
            }
            perror("Failed to read\n");
            return len;
        } else if (len > 0) {
            break;
        }
    }

    printf("buf = %s\n", buf);

    return 0;
}