• Linux C 应用编程学习笔记——(2)文件 I/O 基础


    《【正点原子】I.MX6U嵌入式Linux C应用编程指南》学习笔记

    文件描述符

    内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

    ——百度百科

    当调用 oepn() 函数打开或创建一个文件时,内核会向进程返回一个文件描述符,用于代指被打开的文件,所有执行 I/O 操作的系统调用都是通过文件描述符来索引到对应的文件。

    每一个被打开的文件在同一进程中都对应一个唯一的文件描述符,当文件被关闭后,它对应的文件描述符就会被释放。

    每个线程的前三个文件描述符(0 ~ 2)是固定的,分别表示标准输入(0)、标准输出(1)和标准错误(2),所以我们创建或打开文件时分配的文件描述符最小为 3。

    open() 打开文件

    open() 的函数可以打开或创建一个文件,原型如下:

    #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);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参数介绍

    • pathname: 要操作的文件,可以填文件路径(绝对路径或相对路径都行),如果 pathname 是一个符号链接,会对其进行解引用。

    • flags: 操作文件时的一些标志,open() 函数有很多种标志,下面介绍一些常用的:

    标志作用说明
    O_RDONLY以只读方式打开文件指定文件的访问权限,三种权限必须选一种
    O_WRONLY以只写方式打开文件指定文件的访问权限,三种权限必须选一种
    O_RDWR以可读可写方式打开文件指定文件的访问权限,三种权限必须选一种
    O_CREAT如果文件不存在,这创建文件使用此标准时,需要传入第 3 个参数 mode,用于指定新文件的权限
    O_EXCL如果文件已存在,则返回错误,一般与 O_CREAT 一起使用-
    O_APPEND以追加的方式打开文件每次使用 write() 时,文件指针自动先移动到文件尾

    除了上面这些,还有很多其他标志,如 O_ASYNC、OASYNC、O_DSYNC 等。

    如果要同时使用多个标志,中间使用按位或运算符(‘|’) 连接。

    • mode: 该参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE(创建临时文件) 标志时才有效。

    该参数支持的选项包括:

    宏定义说明
    S_IRUSR允许文件所有者读文件
    S_IWUSR允许文件所属者写文件
    S_IXUSR允许文件所属者执行文件
    S_IRWXU允许文件所属者读、写、执行文件
    S_IRGRP允许同组用户读文件
    S_IWGRP允许同组用户写文件
    S_IXGRP允许同组用户执行文件
    S_IRWXG允许同组用户读、写、执行文件
    S_IROTH允许其他用户读文件
    S_IWOTH允许其他用户写文件
    S_IXOTH允许其他用户执行文件
    S_IRWXO允许其他用户读、写、执行文件
    S_ISUIDset-user-ID 设置 UID
    S_ISGIDset-group-ID 设置 GID
    S_ISVTXsticky

    如果要同时使用多个标志,中间使用按位或运算符(‘|’) 连接。

    返回值
    成功时返回文件描述符,文件描述符是一个非负整数,失败时返回 -1。

    write() 写文件

    write() 函数可以向已经打开的文件写入数据,函数原型如下:

    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
    
    • 1
    • 2

    参数介绍

    • fd: 文件描述符
    • buf: 要写入的数据
    • count: 要写入的字节数

    返回值

    成功时返回写入的字节数,失败时返回 -1。如果返回的字节数小于参数 count,可能是因为一些意外错误,比如磁盘空间已满。数据写入后,文件指针也会跟着偏移(比如写入前文件指针偏移量为 0,写入 100 字节后,文件指针偏移量将会变成 100)。

    read() 读文件

    read() 函数的作用是从已经打开的文件中读取数据,其原型如下:

    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
    
    • 1
    • 2

    参数介绍

    • fd: 文件描述符
    • buf: 读出的数据缓存区
    • count: 要读取的字节数

    返回值

    返回成功读取到的字节数,实际读取字节数可能会小于参数 count(比如文件指针指向的位置后的字节数小于 count),也有可能是 0(文件指针已经指向文件末尾)。和 write() 函数类似,每次使用 read() 读取数据,文件指针会移动到上一次 read() 结束时的位置,比如一次性读完文件的字节数,再次使用 read(),将会返回 0,因为文件指针已经指向文件末尾。

    close() 关闭文件

    close() 函数可以关闭一个已经打开的文件,原型如下:

    #include <unistd.h>
    int close(int fd);
    
    • 1
    • 2

    参数介绍

    • fd: 文件描述符

    返回值

    返回 0 表示关闭成功,返回 -1 表示关闭失败。

    在很多代码中,程序一直在一个 while(1) 死循环中执行文件操作,而 close() 函数在循环之外,也就是说程序并不会执行 close(),但这种情况文件的数据并不会意外丢失。这是因为在 Linux 系统中,当一个进程结束时,内核会自动关闭它所有打开的文件。但即便如此,在不需要使用文件时调用 close() 是一个良好的编程习惯,因为它能增强程序的可读性和可靠性。

    lseek() 文件指针偏移

    每个打开的文件,系统都会记录下它们读写位置偏移量,可以把它成为读写偏移量,它记录了文件当前读写位置。每次调用 read() 或者 write() 函数时,就会从读写偏移量所在位置进行数据读写。文件头的偏移量为 0。

    lseek() 函数可以调整读写偏移量,该函数原型如下:

    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
    
    • 1
    • 2
    • 3

    参数介绍

    • fd: 文件描述符
    • offset: 偏移量,单位为字节,可正可负,正表示右移,负表示左移
    • whence: 偏移量的参考值,参考值可以为以下宏定义的其中之一:
    • SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处;
    • SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头开始算);
    • SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处

    返回值

    成功时返回从文件头部开始算起的位置偏移量(字节),错误时返回 -1。

    一个简单的练习

    实现的功能:新建一个文件 src_file.txt,向里面写入 1K Byte 数据,然后打开一个现有文件 dest_file.txt,获取该文件的大小,然后将 src_file.txt 的内容追加拷贝到 dest_file.txt 文件中,最后再获取一次 dest_file.txt 文件的大小。

    我的测试代码(仅供参考)

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    
    #define SRC_FILE "./src_file.txt"
    #define DEST_FILE "./dest_file.txt"
    
    char buff[1024];
    
    int main()
    {
    	int i = 0;
    	int src_fd, dest_fd;
    	int cnt = 0, offset = 0;
    	char tmp[256];
    
    	/* 填充 buff */	
    	for(i = 0; i < 1024; i++)
    	{
    		buff[i] = 'a' + i % 26;
    	}
    
    	/* 创建 src_file.txt */
    	src_fd = open(SRC_FILE, O_RDWR|O_CREAT, S_IRWXU);
    
    	if(src_fd < 0)
    	{
    		printf("%s open failed.\n", SRC_FILE);
    		return -1;
    	}
    	
    	/* 向 src_file.txt 写入数据 */
    	cnt = write(src_fd, buff, sizeof(buff));
    
    	printf("Successfully wrote %d bytes data to %s.\n", cnt, SRC_FILE);
    	
    	/* 打开 dest_file.txt */
    	dest_fd = open(DEST_FILE, O_RDWR);
    
    	if(dest_fd < 0)
    	{
    		printf("%s open failed.\n", DEST_FILE);
    		return -1;
    	}
    	
    	/* 获取 dest_file.txt 文件末尾的文件指针偏移量, 同时也将指针指向文件尾部 */
    	offset = lseek(dest_fd, 0, SEEK_END);
    
    	printf("The size of %s is %d bytes.\n", DEST_FILE, offset);
    
    	/* 将 src_file.txt 的文件指针指向文件头 */
    	lseek(src_fd, 0, SEEK_SET);
    
    	/* 将 src_file.txt 的数据写入到 dest_file.txt 中 */
    	while(1)
    	{
    		cnt = read(src_fd, tmp, sizeof(tmp));
    		if(cnt > 0)
    			write(dest_fd, tmp, sizeof(tmp));
    		else
    			break;
    	}
    	
    	/* 获取 dest_file.txt 文件末尾的文件指针偏移量 */
    	offset = lseek(dest_fd, 0, SEEK_END);
    
    	printf("The size of %s is %d bytes.\n", DEST_FILE, offset);
    	
    	/* 关闭文件 */ 
    	close(src_fd);
    	close(dest_fd);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    程序运行结果:

    在这里插入图片描述

  • 相关阅读:
    el-input一些校验 & 事件
    【海思SS626 | 开发环境】VMware17安装Ubuntu 18.04.6
    数据库锁及批量更新死锁处理
    vue-router 学习知识汇总
    Apache Flink怎样保证数据是一致性的
    编写Linux设备驱动程序的注意事项
    QT报错The inferior stopped because it received a signal from the operating system
    python基础之函数__name__属性
    怎么把录音转换成mp3格式?
    RocketMQ高性能核心原理与源码架构剖析
  • 原文地址:https://blog.csdn.net/weixin_43772810/article/details/125593178