Linux基础IO【文件操作】

Linux基础IO【文件操作】

前面讲过C语言的文件操作,文件操作是基础IO的重要部分,下面我们就来学习一下Linux中的文件操作

1. 文件认识

先来讲述对于文件的几个疑问

文件是怎么构成的,放在哪里?

  • 文件 = 内容 + 属性
  • 文件被分为两大类:磁盘文件、内存文件(被打开的文件)
  • 文件没有被操作的时候,一般放在磁盘上
  • 文件被操作的时候,文件属性会被加载到内存中,冯诺依曼体系规定

文件操作的本质是什么?

  • 对文件的操作无非就两种:对内容进行操作或者对属性进行操作,这里讲述后者
  • 文件操作本质就是将需要的文件属性加载到内存中,操作系统一定同时存在大量的被打开的文件,同时操作系统也要管理这些被打开的文件,通过先描述再组织的方式
  • 先描述就是构建在内存中的文件结构体struct file,来存储文件属性进行管理,这个结构体可以从磁盘上拿到,再组织就是通过数据结构来组织,比如:链表来连接结构体节点

文件是谁打开的?

  • 文件是被操作系统打开的,是由用户创建进程,进程让操作系统完成打开文件的任务

所有语言的文件操作,本质上都是调用系统级接口进行操作,要针对底层系统级文件操作进行学习

2. 回顾C文件接口

先来回顾一下C语言的文件操作

2.1 打开文件

使用fopen函数打开文件

FILE * fopen ( const char * filename, const char * mode );

fopen函数

  • 返回值:打开文件失败返回NULL

  • filename参数:要打开的文件名,直接使用文件名,此文件需要位于当前程序目录下,可以使用绝对路径来指定目录存放

  • mode参数:文件打开方式

    • w:只写,文件写入前会先清空文件原内容,如果文件不存在会自动创建它
    • a:追加,在文件末尾对文件追加写入内容,不会清空原内容
    • r:只读,打开指定文件进行读取操作,如果文件不存在则会打开失败
    • w+a+r+:可读可写、可读可追加、可读可写,其中只有r+不会自动创建文件

2.2 关闭文件

文件打开使用完后需要关闭,使用fclose函数关闭文件

int fclose ( FILE * stream );

FILE*指针进行操作,只能关闭已打开的文件,文件不存在会报错

FILE* fd = fopen("text.txt", "w"); //打开文件

//...文件使用...

fclose(fd);  //关闭文件

2.3 文件写入

C语言中的文件写入方式

int fputc ( int character, FILE * stream );  //逐字符写入

int fputs ( const char * str, FILE * stream );  //逐行写入

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );  //二进制格式化写入

int fprintf ( FILE * stream, const char * format, ... );  //格式化写入

int snprintf ( char * s, size_t n, const char * format, ... );

前几个都比较简单,来讲解一下snprintf

  • s参数:缓冲区
  • n参数:缓冲区大小
  • format参数:格式化输入

使用snprintf函数将数据写到缓冲区后,可以通过fputs函数将缓冲区中的数据写入文件中

#include <stdio.h>

#define TEXT "text.txt"

int main()
{
  FILE* fd = fopen(TEXT,"w");
  if(fd == NULL)
  {
    perror("fopen fail!");
    exit(-1);
  }
  
  const char* message = "hello world";
  
  char buffer[256]; //缓冲区
  int n = 5;
  while(n--)
  {
    
    snprintf(buffer, sizeof(buffer), "%s %d\n", message, n); //向缓冲区写入内容
    fputs(buffer, fd); //将缓冲区中的内容写入文件
  }

  fclose(fd);
  return 0;
}

2.4 文件读取

C语言中的文件读取方式

int fgetc ( FILE * stream );

char * fgets ( char * str, int num, FILE * stream );

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

int fscanf ( FILE * stream, const char * format, ... );

int sscanf ( const char * s, const char * format, ...);

这里的sscanf用于按照一定规则格式化读取字符串

#include <stdio.h>

int main()
{
  char s[] = "2023,7,20";
  int arr[3];
  char* buffer[4];
    
  sscanf(s, "%d,%d,%d", arr, arr + 1, arr + 2);
  printf("%d %d %d\n", arr[0], arr[1], arr[2]);
  return 0;
}

更多C语言文件操作细节可以查看文章《C语言文件操作详解》

3. 系统文件操作接口

3.1 打开文件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);	

open函数讲解

  • 返回值:成功返回新打开的文件描述符,失败返回-1

  • pathname参数:待操作文件名

  • flags参数:打开文件时,可以传入多个参数选项构成

  • mode参数:权限设置,文件默认权限为0666

讲解一下flags参数

  • flags是一个int类型的变量,它有32个比特位,这里选项有很多,如O_APPENDO_CREATO_TRUNC
  • 32个比特位每一个都可以表示一个标志位,这样就有32个标志位,也就是对应的可以有32个选项。这种数据结构就是是位图,使用位图进行多参数传递

知道了特性,下面用一个小demo来展示一下位图

#include <stdio.h>

#define ONE 0x1
#define TWO 0x2
#define THREE 0x4

//模拟实现选项传递
void out(int flags)
{
  if(flags & ONE) printf("1111\n");
  if(flags & TWO) printf("2222\n");
  if(flags & THREE) printf("3333\n");
}

int main()
{ 
  out(ONE);
  printf("-----------\n");
  out(TWO);
  printf("-----------\n");
  out(ONE | TWO);
  printf("-----------\n");
  out(ONE | TWO | THREE);
  printf("-----------\n");
  return 0;
}

每个比特位来设置一个选项,可以直接通过传入参数的形式执行不同的功能

列举几个open系统调用中的flags选项

O_APPEND  //文件用追加的方式被打开
O_CREAT   //如果文件不存在就会自动创建
O_RDONLY  //只读模式
O_WRONLY  //只写模式
O_TRUNC   //清理文件

试用一下

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

#define TEXT "text.txt"

int main()
{
  //两种参数组合体现fopen中的a选项
  int fd = open(TEXT, O_CREAT | O_WRONLY, 0666); //权限最好加上
  if(fd == -1)
  {
    perror("open fail1");
    return;
  }
  else 
  {
    printf("fd: %d, errno: %d, errstring: %s\n",fd, errno, strerror(errno));
  }
  
  close(fd);
  return 0;
}

这里的mode是权限,有以下几种情况

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

注意:

  • 权限可以直接以数字的形式给出
  • 若文件不存在,mode参数最好设置,不然的话创建出的文件权限是随机值
  • umask默认为0002,可以自定义

3.2 关闭文件 close

系统级关闭文件接口close,它是根据文件描述符来关闭文件的

#include <unistd.h>

int close(int fildes);

Linux下的三个标准流为:stdinstdoutstderr,分别对应的文件描述符为:012,可以通过close(1)来关闭标准流

3.3 文件写入 write

系统级文件写入接口write

#include <unistd.h>

ssize_t write(int fildes, const void *buf, size_t nbyte);

使用方法与fwrite基本相同,返回值有所差异:成功返回写入的字节总数,失败返回-1并且errno被设置

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

#define TEXT "text.txt"

int main()
{
  //三种参数组合体现fopen中的w选择
  int fd = open(TEXT, O_WRONLY | O_CREAT | O_TRUNC, 0666);	
  if(fd == -1)
  {
    perror("open fail1");
    return;
  }
  else 
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  
  const char* message = "hello world\n";
  int count = 5;
  while(count--)
  {
    write(fd, message, strlen(message)); //这里不能将'\0'写入文件中
  }

  close(fd);
  return 0;
}

注意:使用write写入字符串时,不要加上 '\0'当作结尾,因为对于系统来说,'\0'也是个普通的字符,'\0' 作为字符串结尾只是 C语言 的规定

3.4 文件读取 read

系统级文件读取接口read

#include <unistd.h>

ssize_t read(int fildes, void *buf, size_t nbyte);

read函数在读取文件时,也是借助缓冲区进行读取,不过只支持按指定字符数读取,无法按行读取,返回值和write相同

//读取text.txt中内容
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT "text.txt"

int main()
{
  int fd = open(TEXT, O_RDONLY, 0664);
  if(fd == -1)
  {
    perror("open fail1");
    return;
  }
  
  char buffer[256]; //缓冲区
  size_t ret = read(fd, buffer, sizeof(buffer) - 1);
  if(ret > 0)
  {
    buffer[ret] = '\0';
    printf("%s\n", buffer);
  }

  close(fd);
  return 0;
}

//读取myfile.c中的66个字符
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT "myfile.c"

int main()
{
  int fd = open(TEXT, O_RDONLY, 0664);
  if(fd == -1)
  {
    perror("open fail1");
    return;
  }
  
  int n = 66; //要读取的字符数
  char buffer[256];
  int pos = 0;
  while(n--)
  {
    read(fd, (char*)buffer + pos, 1);
    pos++;
  }

  printf("%s\n", buffer);

  close(fd);
  return 0;
}

4. 系统调用和库函数

我们上面回顾了C语言的文件操作库函数,讲解了文件操作系统级的接口,在Linux下,无论是什么语言,在进行文件操作时所使用的函数,本质上都是对系统调用接口的封装,在文件操作中,是无法直接与硬件(磁盘)交互的,必须经过系统调用接口到操作系统再到驱动程序这一途径


Linux基础IO【文件操作】,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Linux IO 模型是指 Linux 操作系统中的 IO 处理机制。它的目的是解决多个程序同时使用 IO 设备时的资源竞争问题,以及提供一种高效的 IO 处理方式。 Linux IO 模型主要分为三种:阻塞 IO、非阻塞 IOIO 多路复用。 阻塞 IO 指的是当程序进行 IO 操作时,会被挂起直到 IO 操作完成,这种方式简单易用,但是对于高并发环境不太适用。 非阻塞 IO 指的是程序进行 IO 操作时,如果无法立即完成,会立即返回一个错误码,程序可以通过循环不断地进行 IO 操作来实现轮询的效果。非阻塞 IO 可以提高程序的响应速度,但是会增加程序的复杂度。 IO 多路复用指的是程序可以同时监听多个 IO 设备,一旦有 IO 事件发生,就会立即执行相应的操作IO 多路复用可以提高程序的效率,但是需要程序员手动编写代码来实现。 Linux IO 模型还有其他的实现方式,比如信号驱动 IO 和异步 IO 等。但是这些方式的使用比较复杂,一般不常用。 ### 回答2: Linux中的IO模型是指操作系统在处理输入输出的过程中所遵循的一种方式。它主要包括阻塞IO、非阻塞IO、多路复用IO和异步IO四种模型。 阻塞IO是最简单的IO模型,当一个IO操作发生时,应用程序会被阻塞,直到IO操作完成才能继续执行。这种模型的特点是简单直接,但是当有多个IO操作时会造成线程的阻塞,影响系统的性能。 非阻塞IO是在阻塞IO基础上发展而来的,应用程序在发起一个IO操作后可以继续执行其他任务,不必等待IO操作的完成。但是需要通过轮询来不断地检查IO操作是否完成,效率相对较低。 多路复用IO使用select、poll、epoll等系统调用来监听多个IO事件,当某个IO事件就绪时,应用程序才会进行读写操作,避免了前两种模型的效率问题。多路复用IO模型适用于连接数较多时的场景,如服务器的网络通信。 异步IO是最高效的IO模型,应用程序发起一个IO操作后,立即可以执行其他任务,不需要等待IO操作的完成。当IO操作完成后,操作系统会通知应用程序进行后续处理。异步IO模型常用于高吞吐量、低延迟的应用,如高性能服务器和数据库等。 总之,Linux IO模型提供了多种不同的方式来处理输入输出,每种模型都有其适用的场景和特点。选择合适的IO模型可以提高系统的性能和效率。 ### 回答3: Linux IO模型是指操作系统中用于处理输入输出操作的一种方法或机制。在Linux中,常见的IO模型有阻塞IO、非阻塞IOIO多路复用和异步IO。 阻塞IO是最基本IO模型,当应用程序发起一个IO请求时,它将一直阻塞等待直到IO操作完成,期间无法做其他任务。虽然简单易用,但是对资源的利用不高。 非阻塞IO在发起一个IO请求后,不会阻塞等待IO操作完成,而是立即返回并继续做其他任务。应用程序需要不断地轮询IO操作状态,直到操作完成。由于需要不断轮询,对CPU的占用较高,但可以提高资源的利用率。 IO多路复用是通过一个线程同时监听多个IO事件,从而实现并发处理多个IO操作。在IO多路复用模型中,应用程序不需要进行轮询,而是通过调用select、poll或epoll等系统调用监听多个文件描述符的IO事件。这样可以在单个线程中处理多个IO操作,提高并发性能。 异步IO模型在发起一个IO请求后,应用程序不需要等待IO操作完成,而是继续做其他任务。当IO操作完成后,操作系统会通知应用程序。异步IO模型需要操作系统的支持,效率较高,但实现较为复杂。 通过选择合适的IO模型,可以根据不同的应用场景来提高IO操作的效率和性能。例如,对于需要同时处理大量连接的服务器应用,IO多路复用是一种常见的选择;而对于需要处理大量IO操作的高性能服务器,则可以考虑使用异步IO模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茉莉蜜茶v

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值