zoukankan      html  css  js  c++  java
  • 【Linux 应用编程】文件IO操作

    Linux 系统中的各种输入输出,设计为“一切皆文件”。各种各样的IO统一用文件形式访问。

    文件类型及基本操作

    Linux 系统的大部分系统资源都以文件形式提供给用户读写。这些文件可以分为:

    • 普通文件:即一般意义上的磁盘文件;
    • 设备文件:系统中的具体设备;
    • 管道文件、FIFO 文件:用于进程间通信;
    • 套接字(socket)文件:用于网络通信方面。

    文件的通用操作为:打开、关闭、读、写、创建。对应 Linux 系统的 API 接口函数分别为 open()close()read()write()create()。这些函数通过文件描述符 File Descriptor 实现 IO 操作。

    文件描述符和文件描述符表

    在进程中,通过 open 函数打开文件或通过 create 函数创建文件后,会返回一个整数,这个整数就是代表这个文件的文件描述符。通过 ulimit -n 命令可以查看每个进程最大支持同时打开多少文件。

    操作系统在进程控制块(PCB,Process Control Block)中,帮每个进程维护了一个文件描述符表,进程打开的所有文件,都会在这个表里登记。打开文件得到的整型返回值,实际上就是指向表里某条记录的索引。后续执行读写操作时,通过传入的文件描述符,在文件描述符表进行查找,从而定位到文件的具体位置。

    3个特殊的文件描述符

    Linux 系统在启动时,标准 IO 会占用掉前 3 个文件描述符的位置:

    • 标准输入 stdin 的文件描述符是 0
    • 标准输出 stdout 的文件描述符是 1
    • 标准错误stderr 的文件描述符是 2。

    文件 I/O 常用头文件

    部分函数需要同时引入多个头文件,是因为这些函数中用到的常量定义,跟函数定义不在同一个头文件里。

    #include <sys/types.h> /* 定义数据类型,如 ssize_t,off_t 等 */
    #include <fcntl.h> /* 定义 open,creat 等函数原型,创建文件权限的符号常量 S_IRUSR 等 */
    #include <unistd.h> /* 定义 read,write,close,lseek 等函数原型 */
    #include <errno.h> /* 与全局变量 errno 相关的定义 */
    #include <sys/ioctl.h> /* 定义 ioctl 函数原型 */
    

    open 和 creat 函数

    函数头文件:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    

    函数原型:

    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    // 如果 flags 包含 O_CREAT,则相对于 creat 函数,必须指定 mode 参数
    
    int creat(const char *pathname, mode_t mode);
    

    参数:

    • pathname:文件名称
    • flags:标志,包含三种:
      • 访问方式标志:必须包含。只读、只写或读写三种中的一个:
        • O_RDONLY:只读
        • O_WRONLY:只写
        • O_RDWR:读写
      • 文件创建标志:可以包含。
        • O_CLOEXEC
        • O_CREAT:如果文件不存在,则创建文件。文件的 UID 会被设置为进程的 UID,GID 会被设置为进程或父进程的 GID。此时必须指定第三个参数 mode。
        • O_DIRECTORY
        • O_EXCL:跟 O_CREAT 一同使用,确保一定会创建文件。如果文件已经存在则返回 -1。
        • O_NOCTTY
        • O_NOFOLLOW
        • O_TRUNC:文件截断。
        • O_TTY_INIT
      • 文件状态标志:可以包含。该标志可以通过 fcntl 函数重新检索 retrieve 和修改。
        • O_APPEND:追加模式。每次 write 或 lseek 函数执行前,指针会偏移到文件末尾。NFS 文件系统可能会有异常。
        • O_ASYNC
        • O_DIRECT
        • O_LARGEFILE
        • O_NOATIME
        • O_NONBLOCK or O_NDELAY:非阻塞。
        • O_PATH
        • O_SYNC
    • mode:创建文件时,设置文件权限。可以直接使用 0777 之类的数字,也可以用下面的常量:
      • S_IRWXU 00700 user (file owner) has read, write and execute permission
      • S_IRUSR 00400 user has read permission
      • S_IWUSR 00200 user has write permission
      • S_IXUSR 00100 user has execute permission
      • S_IRWXG 00070 group has read, write and execute permission
      • S_IRGRP 00040 group has read permission
      • S_IWGRP 00020 group has write permission
      • S_IXGRP 00010 group has execute permission
      • S_IRWXO 00007 others have read, write and execute permission
      • S_IROTH 00004 others have read permission
      • S_IWOTH 00002 others have write permission
      • S_IXOTH 00001 others have execute permission

    返回值:报错时返回 -1,否则返回文件描述符。

    示例:

    #include <fcntl.h>
    #include <stdio.h>
    
    int main()
    {
        int fd;
        char name[] = "666.txt";
        // 如果文件不存在,就创建文件,权限为所有者RWX,组和其他人无权限
        fd = open(name, O_RDONLY | O_CREAT, S_IRWXU);
        // fd 小于 0 表示出错,需要处理
        if (fd < 0) //...
        printf("%d
    ", fd);
        close(fd);
    	
    	// 如果文件已经存在,会把文件内容清空。权限为用户RW,组用户W,其他人R,即 0x321
    	fd = cerat(name, S_IRUSR | S_IWUSR | S_IWGRP | S_IROTH);
        return 0;
    }
    

    close 函数

    Linux 系统中,文件可以多次打开,例如多个进程同时打开一个文件,一个进程反复多次打开。内核记录了文件的打开次数,只要还有进程没关闭文件,就不会关闭文件。

    close(fd);
    

    read 函数

    头文件:

    #include <unistd.h>
    

    函数原型:

    ssize_t read(int fd, void *buf, size_t count);
    

    read 函数会尝试从文件描述符 fd 中读取 count 个字节到缓冲区 buf 中。

    返回值:
    成功时返回读到的字节数,0表示读到文件末尾了。如果返回字节数小于指定的字节数,不一定出错,有可能文件就剩这么多数据了。出错时返回 -1,并设置 errno 为合适值。
    示例:

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    
    int main()
    {
        int fd, res;
        char buf[20];
        char name[] = "666.txt";
    
        fd = open(name, O_RDONLY);
        res = read(fd, buf, sizeof(buf));
        printf("%s
    ", buf);
        return 0;
    }
    

    write 函数

    头文件:

    #include <unistd.h>
    

    函数原型:

    ssize_t write(int fd, const void *buf, size_t count);
    

    write 函数会尝试从缓冲区 buf 中读取 count 个字节,写到文件描述符 fd 中。

    返回值:
    成功时返回写入的字节数。如果返回字节数小于指定的字节数,不一定出错,有可能是磁盘满了。出错时返回 -1,并设置 errno 为合适值。
    示例:

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    
    int main()
    {
    	int fd, res;
    	char str[] = "hello world!";
    	char name[] = "666.txt";
    	fd = open(name, O_WRONLY); // 必须要有写权限
    	res = write(fd, str, sizeof(str));
    	printf("%d
    ", res);
    	return 0;
    }
    

    fsync 函数

    磁盘读写速度很慢,为了优化性能,Linux 在写磁盘时,加了一层缓存,数据攒够一定数量或程序结束后才将数据写入磁盘。write 函数每次只是将数据写到缓存,如果需要强制其写入磁盘,需要使用 fsync 命令。

    头文件:

    #include <unistd.h>
    

    函数原型:

    int fsync(int fd);
    int fdatasync(int fd);
    

    返回值:
    操作成功返回 0,否则返回 -1,同时设置全局变量 errno。

    sync() 函数同步整个系统修改过的缓存数据,而 fsync() 则只针对一个具体文件。

    lseek 函数

    有的设备支持随机读写文件,例如磁盘,而有的则只支持顺序读写,例如管道、套接字和 FIFO。支持随机读写的设备,可以通过 lseek 函数移动读写位置。之后的读写操作,将会从这个新位置开始。

    头文件:

    #include <unistd.h>
    

    函数原型:

    off_t lseek(int fd, off_t offset, int whence);
    

    参数:

    • offset:目标位置,其偏移的参照点,由第三个参数 whence 决定
    • whence:有效值是 SEEK_SET、SEEK_CUR、SEEK_END,含义如下:
      • SEEK_SET 设置新的读写位置为从文件开头算起,偏移 offset 字节;
      • SEEK_CUR 设置新的读写位置为从当前所在的位置算起,偏移 offset 字节,正值表示往文件尾部偏移,负值表示往文件头部偏移;
      • SEEK_END 设置新的读写位置为从文件结尾算起,偏移 offset 字节,正值表示往文件尾部偏移,负值表示往文件头部偏移。

    返回值:
    操作成功则返回新的读写位置,否则返回 -1。按顺序读写的文件不支持 lseek 操作,对这类文件调用 lseek(),将返回-1,且 errno=ESPIPE。

    如果只是想测试设备是否支持该操作,可以执行这个语句,只有返回值大于 -1,就是支持的:

    res = lseek(fd, 0, SEEK_CUR);
    

    示例:

    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    int main()
    {
    	int fd, res;
    	char name[] = "666.txt";
    	char buf[20];
    	fd = open(name, O_RDONLY);
    	lseek(fd, 5, SEEK_SET);
    	res = read(fd, buf, 10);
    	printf("%s
    ", buf);
    	return 0;
    }
    

    ioctl 函数

    ioctl 是文件 IO 的杂项函数,可以实现一些设备相关的操作,例如修改寄存器的值。

    头文件:

    #include <sys/ioctl.h>
    

    函数原型:

    int ioctl(int d, int request, ...);
    

    参数:

    • d:打开文件的描述符
    • request:文件的操作命令,参数值决定后面的参数含义,... 表示从参数是可选的、类型不确定的。不同的文件,cmd 一般不同,比如嵌入式系统中的设备文件,蜂鸣器(BUZZER)和模数转换(ADC)。

    返回值:
    操作成功则返回0,否则返回 -1。部分设备可能返回正数表示参数。

    stat 和 lstat 函数

    跟 Linux 终端中的 stat 命令作用一样,stat 函数也用来查看文件属性。

    # stat tmux-client-14353.log 
      File: ‘tmux-client-14353.log’
      Size: 54        	Blocks: 8          IO Block: 4096   regular file
    Device: fd01h/64769d	Inode: 256424      Links: 1
    Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
    Access: 2018-11-17 14:56:09.963358724 +0800
    Modify: 2018-11-17 14:56:16.992381417 +0800
    Change: 2018-11-17 14:56:16.992381417 +0800
     Birth: -
    

    头文件及函数原型:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    int stat(const char *path, struct stat *buf); /* 查看 path 文件名指向的文件的属性,放到 buf 中*/
    int fstat(int fd, struct stat *buf); /* 文件名变成文件描述符 */
    int lstat(const char *path, struct stat *buf); /* 文件名是一个符号链接,查看这个符号链接的属性 */
    

    返回值:

    struct stat {
        dev_t     st_dev;     /* 文件的设备编号,ID of device containing file */
        ino_t     st_ino;     /* Inode 编号,inode number */
        mode_t    st_mode;    /* 文件类型和权限,protection */
        nlink_t   st_nlink;   /* 硬链接个数,number of hard links */
        uid_t     st_uid;     /* 用户ID,user ID of owner */
        gid_t     st_gid;     /* 组ID,group ID of owner */
        dev_t     st_rdev;    /* device ID (if special file) */
        off_t     st_size;    /* 文件大小,total size, in bytes */
        blksize_t st_blksize; /* 块大小,blocksize for file system I/O */
        blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
        time_t    st_atime;   /* time of last access */
        time_t    st_mtime;   /* 最后一次修改时间,time of last modification */
        time_t    st_ctime;   /* time of last status change */
    };
    

    示例:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>
    
    int main()
    {
        char name[] = "666.txt";
        struct stat buf;
        int ret = stat(name, &buf);
        if (ret < 0)
        {
            printf("get file status error, errorno is:%d ", errno);
        }
        printf("UID is: %d
    GID is: %d
    size is: %d
    ", (int)buf.st_uid, (int)buf.st_gid, (int)buf.st_size);
        return 0;
    }
    

    access 函数

    检查当前用户对文件是否具有某个权限,还可以判断文件是否存在。

    头文件及函数原型:

    #include <unistd.h>
    
    int access(const char *pathname, int mode);
    

    参数:

    • mode 支持4个参数:
      • R_OK:是否有读权限
      • W_OK:是否有写权限
      • X_OK:是否有执行权限
      • F_OK:判断文件是否存在

    示例:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        char name[] = "666.txt";
        int ret = access(name, R_OK);
        if (ret == -1)
        {
            printf("you can not read "%s"
    ", name);
        }
        printf("you can read "%s"
    ", name);
    }
    

    chmod 和 chown 函数

    修改文件权限,修改所有者和所属用户。

    #include <sys/stat.h>
    
    int chmod(const char *path, mode_t mode);
    int fchmod(int fd, mode_t mode);
    
    #include <unistd.h>
    
    int chown(const char *path, uid_t owner, gid_t group);
    int fchown(int fd, uid_t owner, gid_t group);
    int lchown(const char *path, uid_t owner, gid_t group);
    

    rename 函数

    改变文件的名字或路径。

    #include <stdio.h>
    
    int rename(const char *oldpath, const char *newpath);
    

    getcwd 函数

    获取当前的工作目录。可以通过返回值或入参 buf 返回当前的绝对路径。

    #include <unistd.h>
    
    char *getcwd(char *buf, size_t size);
    char *getwd(char *buf); /* 已经废弃 */
    char *get_current_dir_name(void);
    

    chdir 和mkdir 函数

    更改当前目录,创建新目录。

    #include <unistd.h>
    
    int chdir(const char *path);
    int fchdir(int fd);
    

    示例:

    #include <unistd.h>
    
    int main()
    {
        char name[] = "new_dir";
        char buf[100];
        mkdir(name);
        chdir(name);
        char *pwd = getcwd(buf, 100);
        printf("%s
    ", pwd);
        printf("%s
    ", buf);
        return 0;
    }
    

    opendir 和 readdir 函数

    打开目录,读目录。man 2 opendir 没找到描述,最好别用,可以用封装好的 C 库函数。

    int readdir(unsigned int fd, struct old_linux_dirent *dirp, unsigned int count);
    

    dup 和 dup2 函数

    复制文件描述符。

    #include <unistd.h>
    
    int dup(int oldfd);
    int dup2(int oldfd, int newfd);
    

    fcntl 函数

    修改文件描述符。

    #include <unistd.h>
    #include <fcntl.h>
    
    int fcntl(int fd, int cmd, ... /* arg */ );
    

    综合示例

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <fcntl.h>
    #include <errno.h>
    
    int main(int argc, char* argv[])
    {
    	int fd, res;
    	char str[] = "hello world!";
    	char buf[20] = {0};
    	char name[] = "666.txt";
    	
    	fd = open(name, O_WRONLY);
    	if (fd < 0)
    	{
    		printf("open file %s failed, errorno = %d
    ", name, errno);
    		return -1;
    	}
    	res = write(fd, str, sizeof(str));
    	printf("write %d bytes to "%s"
    ", res, name);
    	fsync(fd);
    	close(fd);
    	
    	fd = open(name, O_RDONLY);
    	if (fd < 0) return -1;
    	res = read(fd, buf, sizeof(buf));
    	printf("read output is:
    %s
    ", buf);
    	printf("read %d bytes from "%s"", res, name);
    	return 0;
    }
    
  • 相关阅读:
    函数之返回值
    函数之初识函数
    三元运算符
    枚举enumerate
    模块
    迭代器
    斐波那契
    leetcode155 最小栈
    leetcode94 二叉树的中序遍历
    leetcode20 有效的括号
  • 原文地址:https://www.cnblogs.com/kika/p/10851503.html
Copyright © 2011-2022 走看看