Linux之基础IO文件系统讲解(上)

简介: 在C语言中,读文件和写文件是常见的操作,用于从磁盘读取数据到内存或将数据从内存写入磁盘文件中。这些操作需要使用标准库中的文件I/O函数。下面我将详细解释如何在C语言中进行读文件和写文件操作,并举例说明。

回顾C语言读写文件

在C语言中,读文件和写文件是常见的操作,用于从磁盘读取数据到内存或将数据从内存写入磁盘文件中。这些操作需要使用标准库中的文件I/O函数。下面我将详细解释如何在C语言中进行读文件和写文件操作,并举例说明。

读文件操作

在C语言中,读取文件的过程涉及以下步骤:

  1. 打开文件:使用fopen()函数打开一个文件,该函数需要指定文件名和打开模式("r"表示只读模式)。
  2. 读取数据:使用fread()fgets()函数从打开的文件中读取数据。
  3. 关闭文件:使用fclose()函数关闭已经打开的文件。

下面是一个读取文件的简单示例:

#include <stdio.h>
int main() {
    FILE *file = fopen("example.txt", "r");  // 打开文件
    if (file == NULL) {
        printf("无法打开文件\n");
        return 1;
    }
    char buffer[100];
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer);  // 打印读取的内容
    }
    fclose(file);  // 关闭文件
    return 0;
}

写文件操作

写文件的过程如下:

  1. 打开文件:使用fopen()函数打开一个文件,需要指定文件名和打开模式("w"表示写入模式,如果文件不存在则创建,如果存在则清空原内容)。
  2. 写入数据:使用fwrite()fprintf()函数将数据写入已打开的文件。
  3. 关闭文件:使用fclose()函数关闭已经打开的文件。

以下是一个写文件的简单示例:

#include <stdio.h>
int main() {
    FILE *file = fopen("output.txt", "w");  // 打开文件
    if (file == NULL) {
        printf("无法打开文件\n");
        return 1;
    }
    char data[] = "这是要写入文件的数据。\n";
    fprintf(file, "%s", data);  // 写入数据
    fclose(file);  // 关闭文件
    return 0;
}

输出信息到显示器的方法

在C语言中,你可以使用多种方法将信息输出到显示器(终端或控制台)。以下是几种常见的方法:

  1. printf函数:printf 是C语言标准库中的函数,用于格式化输出到标准输出(通常是终端或控制台)。它允许你使用格式字符串指定输出的样式和内容。
    示例:
#include <stdio.h>
int main() {
    printf("Hello, world!\n");
    int num = 42;
    printf("The number is: %d\n", num);
    return 0;
}
  1. puts函数:puts 函数用于输出一个字符串,并自动添加换行符。它不支持格式化输出。
    示例:
#include <stdio.h>
int main() {
    puts("Hello, world!");
    return 0;
}
  1. putc和putchar函数:putc 函数用于输出一个字符,而 putchar 函数用于输出一个字符并添加换行符。
    示例:
#include <stdio.h>
int main() {
    putc('H', stdout);
    putc('i', stdout);
    putchar('!');
    putchar('\n');
    return 0;
}
  1. fprintf函数:fprintf 函数允许你将格式化的输出写入到文件流中,包括标准输出流。
    示例:
#include <stdio.h>
int main() {
    FILE *file = stdout;  // 标准输出流
    fprintf(file, "Hello, world!\n");
    return 0;
}

这些方法可以根据你的需求和偏好来选择。通常情况下,你会使用 printf 来输出信息到控制台,因为它提供了丰富的格式化选项,使输出更加灵活和易读。

stdin & stdout & stderr

在C语言中,stdinstdoutstderr是三个标准的I/O流,用于处理标准输入、标准输出和标准错误输出。它们是在标准库中预定义的文件指针,在C/C++程序中是默认打开的

  1. stdin:
  • stdin 是标准输入流,用于从用户(或其他来源)读取输入数据。通常情况下,stdin 关联着键盘输入,但在重定向或管道等情况下,它可以来自其他来源。
  1. 示例:
#include <stdio.h>
int main() {
    int num;
    printf("请输入一个数字:");
    scanf("%d", &num);  // 从标准输入读取一个数字
    printf("你输入的数字是:%d\n", num);
    return 0;
}
  1. stdout:
  • stdout 是标准输出流,用于将程序的输出信息显示给用户。通常情况下,stdout 关联着终端或控制台。
  1. 示例:
#include <stdio.h>
int main() {
    printf("Hello, world!\n");  // 将信息输出到标准输出
    return 0;
}
  1. stderr:
  • stderr 是标准错误输出流,用于输出错误信息或警告信息。通常情况下,stderr 也关联着终端或控制台。与 stdout 不同的是,stderr 通常不会被重定向,这样可以确保错误信息能够及时显示给用户。
  1. 示例:
#include <stdio.h>
int main() {
    fprintf(stderr, "这是一个错误消息。\n");  // 将错误消息输出到标准错误输出
    return 1;  // 返回非零值表示程序出错
}

总之,stdinstdoutstderr 是在C语言中处理输入和输出的标准流。通过使用它们,你可以实现用户输入的处理、程序输出的显示以及错误消息的输出。

总结

r Open text file for reading.
  The stream is positioned at the beginning of the file.
r+ Open for reading and writing.
  The stream is positioned at the beginning of the file.
w Truncate(缩短) file to zero length or create text file for writing.
  The stream is positioned at the beginning of the file.
w+ Open for reading and writing.
  The file is created if it does not exist, otherwise it is truncated.
  The stream is positioned at the beginning of the file.
a Open for appending (writing at end of file).
  The file is created if it does not exist.
  The stream is positioned at the end of the file.
a+ Open for reading and appending (writing at end of file).
  The file is created if it does not exist. The initial file position
  for reading is at the beginning of the file,
  but output is always appended to the end of the file.

系统文件IO

在Linux中,可以通过调用系统提供的系统调用接口来进行文件的读写操作。系统调用是用户程序与操作系统之间的接口,允许用户程序直接与操作系统内核进行通信。下面是使用系统调用进行文件读写的简单示例,其中主要涉及到的系统调用包括 openreadwriteclose。其实,大多数编程语言的标准库中的 I/O 函数实际上会在底层调用操作系统提供的系统 I/O 接口。这是因为底层的文件操作、网络通信等需要与操作系统内核交互,而不同的操作系统可能在 I/O 处理方面有不同的实现方式。因此,编程语言的标准库提供了一种抽象层,使开发人员无需关注不同操作系统的细节,而可以使用统一的 API 进行文件和数据的读写。

这种抽象层的使用使得跨平台开发变得更加容易,因为开发人员可以在不同的操作系统上使用相同的函数调用,而不必关心操作系统的差异。

文件读取的示例:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
    int fd = open("example.txt", O_RDONLY);  // 打开文件以只读模式
    if (fd == -1) {
        perror("无法打开文件");
        return 1;
    }
    char buffer[100];
    ssize_t bytesRead = read(fd, buffer, sizeof(buffer));  // 从文件中读取数据
    if (bytesRead == -1) {
        perror("读取文件错误");
        close(fd);
        return 1;
    }
    printf("读取的内容:%.*s", (int)bytesRead, buffer);
    close(fd);  // 关闭文件
    return 0;
}

文件写入的示例:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
  umask(0);
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);  // 打开文件以写入模式(如果不存在则创建,存在则清空)
    if (fd == -1) {
        perror("无法打开文件");
        return 1;
    }
    char data[] = "这是要写入文件的数据。\n";
    ssize_t bytesWritten = write(fd, data, sizeof(data) - 1);  // 写入数据
    if (bytesWritten == -1) {
        perror("写入文件错误");
        close(fd);
        return 1;
    }
    printf("成功写入 %zd 字节数据\n", bytesWritten);
    close(fd);  // 关闭文件
    return 0;
}

这两个示例中,我们使用了 open 函数打开文件,分别用 readwrite 函数进行读取和写入操作,最后使用 close 函数关闭文件。需要注意的是,系统调用返回的错误码通常为负值,因此检查返回值是否小于0 来判断是否发生了错误。另外,perror 函数可以打印出对应的错误信息。

IO接口介绍

open 是一个在 Unix/Linux 系统中用于打开文件的系统调用接口。它是进行文件操作的重要接口之一,用于打开文件以进行读取、写入或其他操作。下面是关于 open 函数的详细介绍:

函数原型:

int open(const char *pathname, int flags, mode_t mode);

参数说明:

  • pathname:要打开的文件路径。
  • flags:打开文件的标志,用于指定打开模式和行为。这些标志可以使用按位或运算组合起来。
  • mode:当使用 O_CREAT 标志时,指定新文件的权限。这个参数通常需要八进制形式的权限值,如 0644

返回值:

  • 成功时,返回文件描述符(一个非负整数),用于以后的文件操作。
  • 失败时,返回 -1,并设置全局变量 errno 表示错误类型。

常用的 flags 参数:

  • O_RDONLY:只读模式打开文件。
  • O_WRONLY:只写模式打开文件。
  • O_RDWR:读写模式打开文件。
  • O_CREAT:如果文件不存在,则创建文件。
  • O_TRUNC:如果文件已存在,在打开时清空文件内容。
  • O_APPEND:在写入时追加到文件末尾。
  • O_EXCL:与 O_CREAT 一起使用,如果文件已存在,返回错误。
  • O_NONBLOCK:以非阻塞模式打开文件,读取和写入不会阻塞进程。
  • O_SYNCO_DSYNC:在每次写入操作后进行物理磁盘同步。

示例:

#include <stdio.h>
#include <fcntl.h>
int main() {
    umask(0);
    int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("无法打开文件");
        return 1;
    }
    // 打开文件后可以进行写入操作
    close(fd);  // 关闭文件
    return 0;
}

在这个示例中,open 函数以写入模式打开一个文件,并在文件不存在时创建它。如果文件打开成功,返回的文件描述符 fd 可以用于后续的文件操作,最后通过 close 函数关闭文件。

writereadcloselseek)是在Unix/Linux系统中用于文件操作的常用系统调用。

  1. write:
  • 函数原型:ssize_t write(int fd, const void *buf, size_t count);
  • 作用:用于将数据从缓冲区写入文件。
  • 参数:
  • fd:文件描述符,指示要写入的文件。
  • buf:要写入的数据的缓冲区。
  • count:要写入的字节数。
  • 返回值:返回实际写入的字节数,如果返回值为 -1,则表示出错。
  1. read:
  • 函数原型:ssize_t read(int fd, void *buf, size_t count);
  • 作用:从文件中读取数据到缓冲区。
  • 参数:
  • fd:文件描述符,指示要读取的文件。
  • buf:存储读取数据的缓冲区。
  • count:要读取的字节数。
  • 返回值:返回实际读取的字节数,如果返回值为 0,则表示已到达文件末尾;如果为 -1,则表示出错。
  1. close:
  • 函数原型:int close(int fd);
  • 作用:关闭打开的文件。
  • 参数:fd:文件描述符。
  • 返回值:成功返回 0,出错返回 -1。
  1. lseek:
  • 函数原型:off_t lseek(int fd, off_t offset, int whence);
  • 作用:改变文件的当前偏移量,通常用于文件随机访问。
  • 参数:
  • fd:文件描述符。
  • offset:偏移量的值。
  • whence:基准位置,可以是 SEEK_SET(文件开头)、SEEK_CUR(当前位置)或 SEEK_END(文件末尾)。
  • 返回值:返回新的文件偏移量,出错返回 -1。

这些接口是在Unix/Linux环境中进行文件操作的基本系统调用。它们提供了对文件的基本读写和定位操作,是文件处理的核心操作之一。需要注意的是,在实际使用中应该进行错误检查以确保操作的正确性和可靠性。

系统调用接口和库函数的关系,一目了然

所以,可以认为,f*系列的函数,都是对系统调用的封装,方便二次开发

文件描述符fd

在Unix/Linux系统中,文件描述符(File Descriptor,通常缩写为 fd)是一个用于标识打开文件或其他I/O资源的整数。它是操作系统内核用来跟踪文件和I/O流的一种方式。文件描述符在C语言中通常用于标识和操作文件、套接字、管道等。

以下是关于文件描述符的一些重要概念:

  1. 标准文件描述符:
  • 在Unix/Linux系统中,有三个标准的文件描述符,分别为 0(标准输入)、1(标准输出)和 2(标准错误输出)。
  • 这些标准文件描述符通常与终端或控制台相关联,用于用户输入和程序输出。
  1. 非标准文件描述符:
  • 除了标准文件描述符外,系统还为每个打开的文件、套接字等分配一个唯一的文件描述符。
  • 非标准文件描述符是非负整数,可以用于标识和操作特定的I/O资源。
  1. 文件描述符的范围:
  • 通常情况下,文件描述符从0开始递增,但并不是所有的非负整数都是合法的文件描述符。
  • 每个进程都有一定的最大文件描述符限制,可以通过 ulimit 命令查看。

在C语言中,当使用系统调用如 openreadwrite 等打开或操作文件时,返回的文件描述符用于后续的文件操作。例如:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main() {
    char buf[1024];  // 用于存储读取的数据
    ssize_t s = read(0, buf, sizeof(buf));  // 从标准输入读取数据
    if (s > 0) {
        buf[s] = 0;  // 在读取的数据末尾添加字符串结束符
        write(1, buf, strlen(buf));  // 将读取的数据写入标准输出
        write(2, buf, strlen(buf));  // 将读取的数据写入标准错误输出
    }
    return 0;
}

文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
93 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
86 1
Linux C/C++之IO多路复用(aio)
|
4月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
14天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
56 8
|
19天前
|
存储 Linux 文件存储
Linux文件系统
Linux文件系统 一切皆文件 在Linux中,“一切皆文件”的概念意味着系统中的所有资源,包括硬件设备、目录及进程等,均被视为文件。这种设计简化了操作和管理,具体包括: 普通文件:存储数据的常规文件。 目录文件:包含其他文件和子目录的文件。 进程文件:在/proc目录下代表系统中运行的进程。 设备文件:位于/dev目录,代表硬件设备。 网络字节流套接字文件:用于网络通信的数据流。 链接文件:指向另一个文件的符号链接或硬链接。 管道文件:用于进程间通信的文件。
46 7
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
25 0
Linux C/C++之IO多路复用(poll,epoll)
|
2月前
|
存储 Java API
【文件IO】文件系统操作
【文件IO】文件系统操作
43 1
|
3月前
|
存储 Linux 索引
Linux 下最主流的文件系统格式——ext
【9月更文挑战第8天】硬盘被划分为若干相同大小的块(Block),默认大小为4K,便于灵活管理文件数据。文件数据分散存放于这些块中,提高了数据添加、删除和插入的便利性。
|
4月前
|
编解码 Linux 程序员
深度探索Linux操作系统 —— 构建根文件系统2
深度探索Linux操作系统 —— 构建根文件系统
45 12
|
4月前
|
Linux Shell 网络安全
深度探索Linux操作系统 —— 构建根文件系统1
深度探索Linux操作系统 —— 构建根文件系统
55 6