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;
    }
    
  • 相关阅读:
    boost::asio在VS2008下的编译错误
    Java集合框架——接口
    ACM POJ 3981 字符串替换(简单题)
    ACM HDU 1042 N!(高精度计算阶乘)
    OneTwoThree (Uva)
    ACM POJ 3979 分数加减法(水题)
    ACM HDU 4004 The Frog's Games(2011ACM大连赛区第四题)
    Hexadecimal View (2011ACM亚洲大连赛区现场赛D题)
    ACM HDU 4002 Find the maximum(2011年大连赛区网络赛第二题)
    ACM HDU 4001 To Miss Our Children Time (2011ACM大连赛区网络赛)
  • 原文地址:https://www.cnblogs.com/kika/p/10851503.html
Copyright © 2011-2022 走看看