当前位置: 首页 > news >正文

Linux_基础IO详解

18fde01fee5e4278981004762ce48cc4.png

✨✨ 欢迎大家来到小伞的大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:LInux_st
小伞的主页:xiaosan_blog

制作不易!点个赞吧!!谢谢喵!!

1. 理解“文件”

  • 文件在磁盘里
  • 磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的
  • 磁盘是外设(即是输出设备也是输入设备)
  • 磁盘上的文件本质是对文件的所有操作,都是对外设的输入和输出简称IO

在C语言中,我们知道键盘,显示器,磁盘都是文件,在Linux中也是如此,键盘、显示器、网卡、磁盘·…..这些都是抽象化的过程

1.1 文件操作的认识

  • 当我们创建文件时,对于OKB的空文件是占用磁盘空间的,磁盘会保存文件的属性(创建时间,创建路径......)
  • 文件是文件属性(元数据)和文件内容的集合(文件=属性(元数据)+内容)
  • 所有的文件操作本质是文件内容操作和文件属性操作

2. 回顾C语言文件接口

2.1 打开文件 

#include <stdio.h>
int main()
{FILE *fp = fopen("myfile", "w");if (!fp){printf("fopen error!\n");}while(1);//这里我们等待,查看进程fclose(fp);return 0;
}

cwd:指向当前进程运行目录的一个符号链接。

exe:指向启动当前进程的可执行文件(完整路径)的符号链接。

2.2 写入文件

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "w");if (!fp){printf("fopen error!\n");}const char *msg = "hello IO!\n";int count = 5;while (count--){fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}

2.3 读取文件

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "r");if (!fp){printf("fopen error!\n");return 1;}char buf[1024];const char *msg = "hello bit!\n";while (1){// 注意返回值和参数,此处有坑,仔细查看man⼿册关于该函数的说明size_t s = fread(buf, 1, strlen(msg), fp);if (s > 0){buf[s] = 0;printf("%s", buf);}if (feof(fp)){break;}}fclose(fp);return 0;
}

我们也能通过读取文件实现cat指令

#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])
{if (argc != 2)//cat+文件{printf("argv error!\n");return 1;}FILE *fp = fopen(argv[1], "r");if (!fp){printf("fopen error!\n");return 2;}char buf[1024];while (1){int s = fread(buf, 1, sizeof(buf), fp);if (s > 0){buf[s] = 0;printf("%s", buf);}if (feof(fp)){break;}}fclose(fp);return 0;
}

2.4 输出到显示器

int main()
{const char *msg = "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");return 0;
}

2.5 stdin & stdout & stderr

  • 在C语言中,会默认打开三个输出流,stdin & stdout & stderr
  • 仔细观察发现,这三个流的类型都是FILE*,fopen返回值类型,文件指针
#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

标准输入stdin:0001

标准输出stdout:0010

标准错误stderr:0100

#include <stdio.h>
#define ONE 0001   // 0000 0001
#define TWO 0002   // 0000 0010
#define THREE 0004 // 0000 0100
void func(int flags)
{if (flags & ONE)printf("flags has ONE! ");if (flags & TWO)printf("flags has TWO! ");if (flags & THREE)printf("flags has THREE! ");printf("\n");
}
int main()
{func(ONE);func(THREE);func(ONE | TWO);func(ONE | THREE | TWO);return 0;
}

3. 系统调用-open接口

 pathname:要打开或创建的目标文件

flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags

参数:
        O_RDONLY:只读打开
        O_WRONLY:只写打开
        O_RDWR:读,写打开
        
        这三个常量,必须指定一个且只能指定一个

        O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
        O_APPEND:追加写

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

3.1 open写入 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{umask(0);//权限一般0002int fd = open("myfile", O_WRONLY | O_CREAT, 0644);if (fd < 0){perror("open");return 1;}int count = 5;const char *msg = "hello bit!\n";int len = strlen(msg);while (count--){write(fd, msg, len); // fd: 后⾯讲, msg:缓冲区⾸地址, len: 本次读取,期望写⼊多少个字节的数据。 返回值:实际写了多少字节数据}close(fd);return 0;
}

3.2 open读取

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{int fd = open("myfile", O_RDONLY);if (fd < 0){perror("open");return 1;}const char *msg = "hello IO!\n";char buf[1024];while (1){ssize_t s = read(fd, buf, strlen(msg)); // 类⽐writeif (s > 0){printf("%s", buf);}else{break;}}close(fd);return 0;
}

3.3 文件描述符fd

3.3.1 0&1&2

  1. Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2.
  2. 0,1,2对应的物理设备一般是:键盘,显示器,显示器

所以输入输出还可以采用如下方式:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{//close(0);//当我们没有关闭标识符0,返回3。//当我们关闭标识符0,返回0,//说明标识符的打开是跟随从小到大,最小的优先占据的规则int fd = open("myfile", O_RDONLY);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}

描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{//close(1);//当我们关闭标准输出,此时不会打印到屏幕上,而是由文件占据1这个文件标识符int fd = open("myfile", O_WRONLY | O_CREAT, 00644);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);//会向1文件标识符打印,此时就会向myfile文本打印fflush(stdout);close(fd);exit(0);
}

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件myfile当中,其中,fd=1。这
种现象叫做输出重定向。常见的重定向有:>,>>,<

 //当我们关闭标准输出,此时不会打印到屏幕上,而是由文件占据1这个文件标识符

//会向1文件标识符打印,此时就会向myfile文本打印

3.4 使用dup2系统调用

#include <unistd.h>int dup2(int oldfd, int newfd);
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{//int fd = open("./log", O_RDWR | O_APPEND);//追加重定向int fd = open("./log", O_CREAT | O_RDWR);//输出重定向if (fd < 0){perror("open");return 1;}close(1);dup2(fd, 1);//此时1标识符关闭,fd的地址交给1,所以printf会向文本输出for (;;){char buf[1024] = {0};ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0){perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
}

4. 一切皆文件

在Linux中,一切皆文件(先描述,后组织),比如进程、磁盘、显示器、键盘这样硬件设备也被抽象成了文件,你可以使用访问文件的方法访问它们获得信息;甚至管道,也是文件;

这样的好处很直接,,Linux中几乎所有读(读文件,读系统状态,读PIPE)的操作都可以用
read函数来进行;几乎所有更改(更改文件,更改系统参数,写PIPE)的操作都可以用write函
数来进行。

struct file
{... struct inode *f_inode; /* cached value */const struct file_operations *f_op;... atomic_long_t f_count;                      // 表⽰打开⽂件的引⽤计数,如果有多个⽂件指针指向它,就会增加f_count的值。 unsigned int f_flags; // 表⽰打开⽂件的权限fmode_t f_mode;                                 // 设置对⽂件的访问模式,例如:只读,只写等。所有的标志在头⽂件<fcntl.h> 中定义loff_t f_pos; // 表⽰当前读写⽂件的位置...} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
struct file_operations
{struct module *owner;// 指向拥有该模块的指针;loff_t (*llseek)(struct file *, loff_t, int);// llseek ⽅法⽤作改变⽂件中的当前读/写位置, 并且新位置作为(正的)返回值.ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);// ⽤来从设备中获取数据. 在这个位置的⼀个空指针导致 read 系统调⽤以 -EINVAL("Invalid argument")失败.⼀个⾮负返回值代表了成功读取的字节数(返回值是⼀个"signed size" 类型,常常是⽬标平台本地的整数类型).ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);// 发送数据给设备. 如果 NULL, -EINVAL 返回给调⽤ write 系统调⽤的程序. 如果⾮负, 返回值代表成功写的字节数.ssize_t (*aio_read)(struct kiocb *, const struct iovec *, unsigned long,loff_t);// 初始化⼀个异步读 -- 可能在函数返回前不结束的读操作.ssize_t (*aio_write)(struct kiocb *, const struct iovec *, unsigned long,loff_t);// 初始化设备上的⼀个异步写.int (*readdir)(struct file *, void *, filldir_t);// 对于设备⽂件这个成员应当为 NULL; 它⽤来读取⽬录, 并且仅对**⽂件系统**有⽤.unsigned int (*poll)(struct file *, struct poll_table_struct *);int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);long (*compat_ioctl)(struct file *, unsigned int, unsigned long);int (*mmap)(struct file *, struct vm_area_struct *);// mmap ⽤来请求将设备内存映射到进程的地址空间. 如果这个⽅法是 NULL, mmap 系统调⽤返回 - ENODEV.int (*open)(struct inode *, struct file *);// 打开⼀个⽂件int (*flush)(struct file *, fl_owner_t id);// flush 操作在进程关闭它的设备⽂件描述符的拷⻉时调⽤;int (*release)(struct inode *, struct file *);// 在⽂件结构被释放时引⽤这个操作. 如同 open, release 可以为 NULL.int (*fsync)(struct file *, struct dentry *, int datasync);// ⽤⼾调⽤来刷新任何挂着的数据.int (*aio_fsync)(struct kiocb *, int datasync);int (*fasync)(int, struct file *, int);int (*lock)(struct file *, int, struct file_lock *);// lock ⽅法⽤来实现⽂件加锁; 加锁对常规⽂件是必不可少的特性, 但是设备驱动⼏乎从不实现它.ssize_t (*sendpage)(struct file *, struct page *, int, size_t, loff_t *,int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock)(struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *,size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *,size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);
};

5.文件缓冲区

缓冲区是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

读写文件时,如果不会开辟对文件操作的缓冲区,直接通过系统调用对磁盘进行操作(读、写等),那么每次对文件进行一次读写操作时,都需要使用读写系统调用来处理此操作即需要执行一次系统调用,执行一次系统调用将涉及到CPU状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这将损耗一定的CPU时间,频繁的磁盘访问对程序的执行效率造成很大的影响。

我们从磁盘里取信息,可以在磁盘文件进行操作时,可以一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

5.1 缓冲方式

  • 全缓冲区:这种缓冲方式要求填满整个缓冲区后才进行I/0系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。
  • 行缓冲区:在行缓冲情况下,当在输入和输出中遇到换行符时,标准I/O库函数将会执行系统调用操作。当所操作的流涉及一个终端时(例如标准输入和标准输出),使用行缓冲方式。因为标准1/O库每行的缓冲区长度是固定的,所以只要填满了缓冲区,即使还没有遇到换行符,也会执行1/0系统调用操作,默认行缓冲区的大小为1024。
  • 无缓冲区:无缓冲区是指标准l/O库不对字符进行缓存,直接调用系统调用。标准出错流stderr通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。

特殊缓冲触发

  • 缓冲区满时;
  • 执行flush语句;
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");return 0;}printf("hello world: %d\n", fd);close(fd);return 0;
}

由于缓冲区的原因,并没有数据打印到文本中

此时就使用fflush刷新

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");return 0;}printf("hello world: %d\n", fd);fflush(stdout) ;close(fd);return 0;
}

另一种方式:利用stderr不带缓冲区

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{close(2);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");return 0;}perror("hello world");close(fd);return 0;
}
http://www.lryc.cn/news/596319.html

相关文章:

  • 聊聊DevOps,开发与运维如何分工协作?
  • 什么是检索增强生成(RAG)?
  • 引擎动画系统设计
  • 【PTA数据结构 | C语言版】不相交集基本操作
  • Visual Studio Code 远端云服务器开发使用指南
  • 服务器连接Hugging Face
  • 【机器学习深度学习】微调量化与模型导出量化:区分与应用
  • HttpSecurity详解
  • 生存分析机器学习问题
  • Oracle定时清理归档日志
  • 通过 Docker 部署测试 CODESYS PLC示例
  • Linux_Ext系列文件系统基本认识(一)
  • 【实战】Dify从0到100进阶--文档解读(11)其他节点
  • 使用空间数据训练机器学习模型的实用工作流程
  • 时序数据库IoTDB好不好?
  • 使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第二十二讲)
  • 【LINUX】CentOS Stream 9 手动配置网络
  • CentOS 8文件描述符耗尽检测与处理实战指南
  • JMeter 实现 Protobuf 加密解密
  • vue2.0 + elementui + i18n:实现多语言功能
  • SpringBoot集成PDFBox实现PDF导出(表格导出、分页页码、电子签章与数字签名)
  • Excel file format cannot be determined, you must specify an engine manually.
  • SparkSQL 聚合函数 COUNT 对 NULL 值的处理
  • MDC(Mapped Diagnostic Context) 的核心介绍与使用教程
  • CMake项目中的main函数重复定义错误
  • 拆分、合并PDF
  • 实现分布式锁
  • 数据库表介绍
  • 金仓数据库风云
  • Docker 安装、常用命令、应用部署