基础IO(总)

news/2024/11/20 7:29:39/

接口介绍

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);

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

flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行"或"运算( '|' )

参数:

  • O_RDONLY:只读打开
  • O_WRONLY:只写打开
  • O_RDWR:读,写打开。这三个参数必定指定一个且只能指定一个
  • O_CREAT:若文件不存在,则创建它,需要使用mode选项,来指名新文件的访问权限
  • O_APPEND:追加写

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

使用:open函数具体使用哪个分场景而定,如果目标文件不存在,需要open创建,就要使用三个参数,并且设置新创建文件的权限。

int open(pathname,O_CREAT,0666);//文件不存在,创建它并设置权限为0666

OPEN函数的返回值 

在学习返回值之前,先了解一下系统调用和库函数。

  • 像fopen,fclose,fread,fwrite这些都是C标准库中的函数 ,称之为库函数
  • 而open,close,read,write,lseek,这些都是OS提供的系统调用

 

 所以可以认为f#系列的函数,都是队系统调用函数进行了封装,方便进行二次开发

 


 

 文件描述符fd

通过上面的open函数就可以知道,文件描述符fd就是一个小整数

0&1&2:

在Linux下,默认会有三个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2

0对应的是键盘,1和2对应的是显示器,即0是从键盘读入,1和2是像显示器输出。

 

#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;
}

上面代码就是利用系统调用接口write像显示器输出buf中的内容,用read从键盘上读取内容保存到数组buf中. 

 

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

 

文件描述符的分配规则 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd = open("myfile", O_RDONLY);if(fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}

 最后输出的答案是" fd: 3"

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(0);//close(2);int fd = open("myfile", O_RDONLY);if(fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}

输出的是"fd: 0"。

可以得出结论:文件描述符的分配规则:在file_struct数组中,找到当前没有被使用的最小的下标,作为新的文件描述符


重定向

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{close(1);int fd = open("myfile", O_WRONLY|O_CREAT, 0644);if(fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);fflush(stdout);close(fd);exit(0);
}

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

一张图了解重定向本质:

 


 dup2系统调用

函数原型:

#include <unistd.h>
int dup2(int oldfd, int newfd);

dup系统调用分配的文件描述符是由系统分配的,遵循文件描述符的分配原则,并不能指定一个文件描述符,这是dup的一个缺陷,而dup2就很好的解决了这个问题

oldfd:需要被复制的文件描述符

newfd:指定一个文件描述符(需要指定一个当前进程没有使用到的文件描述符)

返回值:成功时返回一个新的文件描述符,也就是newfd;失败就返回-1 

示例:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {int fd = open("./log", O_CREAT | O_RDWR);if (fd < 0) {perror("open");return 1;}close(1);dup2(fd, 1);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;
}

 跟上面的代码一个效果。刚开始创建文件的时候,根据文件描述符分配原则,获得的fd应该为3,

这个时候调用dup2(fd,1)将fd的文件描述符改成1,并断开原先的标准输出,如上述重定向图一样。


FILE 

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。

借由下面一段代码来理解:

#include <stdio.h>
#include <string.h>
int main()
{const char *msg0="hello printf\n";const char *msg1="hello fwrite\n";const char *msg2="hello write\n";printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));fork();return 0;
}

运行结果:

hello printf
hello fwrite
hello write

但是如果对文件进行输出重定向的话。./test > file 则文件file中会有

hello write
hello printf
hello fwrite
hello printf
hello fwrite

可以发现printf和fwrite都输出了两次,这两个都是库函数,而write只输出了一次,write为系统调用

  •  一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲的
  • printf fwrite库函数会自带缓冲区,当发生重定向到普通文件时候,数据的缓冲方式由行缓冲变成了全缓冲
  • 而我们放在缓冲区中的数据,不会被立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入文件当中
  • 但是fork时候,父子数据会发生写时拷贝,所以当父进程准备刷新的时候,子进程就有了同样的一份数据,随机就会产生两份同样的数据
  • write没有变化说明,write没有所谓的缓冲

综上:printf fwite库函数会自带缓冲区,而write系统调用没有带缓冲区,另外,我们这里的缓冲区都是用户级的缓冲区,作用就是提升整机性能,OS也会提供相关内核级缓冲区。

那这个用户级缓冲区由谁提供?

显然是C标准库提供,上述中printf和fwrite都是库函数,write是系统调用,库函数在系统调用的上层,是对系统调用的封装,但是write没有缓冲区,而printf和fwrite有缓冲区,说明缓冲区是在封装的时候加上的,也就是由C标准库提供的。 


理解文件系统 

在linux下使用指令ls -l

 [root@localhost linux]# ls -l
-rwxr-xr-x. 1 root root 7438 "1月 1 14:56" a.out
-rw-r--r--. 1 root root 654 "1月 1 14:56" test.c

每行包含7列:

  •  模式
  • 硬连接数
  • 文件所有者
  • 大小
  • 最后修改时间
  • 文件名

inode:

为了解释清楚inode,先简单了解一下文件系统

磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,不可以更改。下图中的启动快(Boot Block)的大小是可以确定的

 

  • Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。每个BlockGroup都有相同的结构组成。
  • 超级块(Super Block):存放文件系统本身的结构信息,记录的信息主要有:block和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block中哪个数据块已经被占用,哪个数据块没有被占用
  • 块位图(Block BItmap):Block Bitmap中记录者Data Block中哪个数据块已经被占用,哪个数据块没有被占用
  • inode位图(inode Bitmap):每个bit表示一个inode是否可空闲使用
  • i节点表:存放文件属性 如,文件大小,所有者,最近修改时间
  • 数据区:存放文件内容

将属性和数据分开存放的想法看起来很简单,看下图。

[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
261234 abc

 

 创建一个新文件主要有以下四个操作

  1. 存储属性内核先找到一个空闲的i节点(这里是261234)。内核把文件信息记录到其中
  2. 存储数据该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推.
  3. 记录分配情况文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. 添加文件名到目录

新的文件名字abc。内核将入口(261234,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来(建立映射关系)。


软硬链接  

硬链接 :

 特点:

  • 具有相同inode节点号的多个文件互为硬链接 文件
  • 删除硬链接 文件或者删除源文件任意之一,文件实体并未被删除
  • 只有删除了源文件和所有对应的硬连接文件,文件实体才会被删除
  • 硬链接 ​​​​​​文件是文件的另一个入口
  • 可以用过给文件设置硬连接文件来防止重要文件被误删
  • 可以通过ls -i看到index;
  • 硬链接文件是普通文件,可以用rm删除
  • 对于静态文件(没有进程正在调用),当硬链接数为0时文件就被删除。注意:如果有进程正在调用,则无法删除或者及时文件名被删除空间也不会释放

我们看到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个inode
 [root@localhost linux]# touch abc

[root@localhost linux]# ln abc def

[root@localhost linux]# ls -1i
abc def 261234 abc 261234 def

  1. abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个链接数,inode261234 的硬连接数为2
  2. 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬链接数-1,如果为0,则将对应的磁盘释放

 

软链接 :

特点:

  • 软链接 类似windows系统的快捷方式
  • 软链接里面存放点的是源文件的路径
  • 删除源文件,软链接依然存在,但无法访问源文件内容
  • 软链接失效时一般是白字红底闪烁
  • 创建软链接命令ln -s 源文件 软链接文件;
  • 软链接文件和源文件是不同的文件,稳健类型也不同,inode号也不同
  • 软链接的文件类型是"I",可以用rm删除

硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件,在shell中的做法

263563 -rw-r--r--. 2 root root 0 9月 15 17:45 abc
261678 lrwxrwxrwx. 1 root root 3 9月 15 17:53 abc.s -> abc
263563 -rw-r--r--. 2 root root 0 9月 15 17:45 def


http://www.ppmy.cn/news/89115.html

相关文章

02数字图像基础

文章目录 2数字图像基础2.4图像取样和量化2.4.4图像内插 2.5像素间的一些基本关系2.5.1相邻像素2.5.2邻接性、连通性、区域和边界2.5.3距离度量 2.6 数字图像处理2.6.1阵列和矩阵操作2.6.2线性操作和非线性操作2.6.3算术操作2.6.5空间操作2.6.6向量与矩阵操作2.6.7图像变换2.6.…

Nginx一网打尽:动静分离、压缩、缓存、黑白名单、跨域、高可用、性能优化...

干货&#xff01;文章有点长&#xff0c;建议先收藏 目录索引 引言一、性能怪兽-Nginx概念深入浅出二、Nginx环境搭建三、Nginx反向代理-负载均衡四、Nginx动静分离五、Nginx资源压缩六、Nginx缓冲区七、Nginx缓存机制八、Nginx实现IP黑白名单九、Nginx跨域配置十、Nginx防盗链…

加密后的敏感字段还能进行模糊查询吗?该如何实现?

前言 有一个问题不知道大家想过没&#xff1f;敏感字段数据是加密存储在数据库的表中&#xff0c;如果需要对这些敏感字段进行模模糊查询&#xff0c;还用原来的通过sql的where从句的like来模糊查询的方式肯定是不行的&#xff0c;那么应该怎么实现呢&#xff1f;这篇文章就来…

[已解决] 决定系数R2为何为负 from sklearn.metrics import r2_score

最近在炼丹发现一件很有趣的现象&#xff0c;决定系数R2竟然为负&#xff0c;小学生都知道任何一个常数的平方绝不可能为负&#xff0c;潜意识里告诉我这里面必有蹊跷&#xff0c;因此查阅许多资料得知&#xff0c;决定系数R2不是r相关系数的平方这么简单&#xff0c;实际上当非…

天猫订单之数据分析与挖掘——认识数据

天猫订单之数据分析与挖掘——认识数据 文章目录 天猫订单之数据分析与挖掘——认识数据0. 写在前面1. 案例(数据集)总体介绍1.1 案例介绍1.2 数据集大小2. 总体分析2.1 框架图2.2 认识数据2.2.1 数据集类型2.2.2 数据对象及数据属性2.2.3 数据的基本统计描述0. 写在前面 Win…

c++使用yaml -基于windows10

参考&#xff1a;Windows10下使用VS2017编译和使用yaml-cpp库_雪域迷影的博客-CSDN博客 1. 下载yaml-cpp 建议在github下载其最新的官方版本&#xff0c;不要在其他平台下载该工具软件&#xff0c;下载地址如下&#xff08;其中的一个版本&#xff09;&#xff1a; Release …

C语言算法--桶排序

1-什么是桶排序法 什么是桶排序法&#xff1f;其实说白了就是把需要排列的元素分到不同的桶中&#xff0c;然后我们对这些桶里的元素进行排序的一种方式&#xff0c;然后我们在根据桶的顺序进行元素的合并。&#xff08;不过前提是要确定桶的数量以及大小&#xff09; 按照稍…

TLS 加速技术:Intel QuickAssist Technology(QAT)解决方案

作者&#xff1a;vivo 互联网服务器团队- Ye Feng 本文介绍了 Intel QAT 技术方案&#xff0c;通过Multi-Buffer技术和QAT硬件加速卡的两种方式实现对TLS的加速 一、背景 当前 TLS 已经成为了互联网安全的主要传输协议&#xff0c;TLS带来更高的安全性的同时&#xff0c;也带…