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

Linux-基础IO(1)

在讲解基础IO的知识前,首先,我们先铺垫一些关于文件共识。

在C/C++我们也有了解过一些些文件的内容,但是我们并不知道它的原理,甚至对它有一些抵触的心理,所以,我们得先打破这种局面。

建立共识:

1.文件=内容+属性。

2.文件分为打开的文件和未打开的文件。

那么,打开的文件是谁打开的?进程,它实质上是关于进程和文件的关系。

未打开的文件,放在哪里呢,我们最关心的问题是什么?

未打开的文件放在磁盘当中,我们都知道,未打开的文件非常多,我们最关心的是文件如何被分门别类的储存好?来使得我们可以快速的进行增删查改。

3.大量文件被打开,必须先被加载到内存,因此,进程与打开文件的关系必然是1:n

即,操作系统的内部,一定存在大量的被打开的文件,OS肯定要管理这些被打开的文件,怎么管理,同样用到六个字:“先描述,再组织”

在内核中,一个被打开的文件,都必须有自己的文件打开对象,包含文件的很多属性。

eg:

struct xxxx(文件属性;struct xxxx*next(链表形式组织起来))。

大概流程:

1.先看C语言的文件接口:

2.认识文件系统接口

3.谈谈访问文件的本质

4.重定向+缓冲区

5.补充其他

C语言写文件

(先看现象)

#include<stdio.h>
#include<string.h>
//fopen  fwrite
int main()
{FILE*fp=fopen("myfile","w");//FILE*fp=fopen("myfile","a");if(fp==NULL){perror("fopen");return;}const char*msg="hello file";int n=fwrite(msg,strlen(msg),1,fp);if(n==0)perror("fwrite");fclose(fp);    return 0;
}

以“w”打开文件:

本来myfile文件有:这些内容的

编译执行后

以“a”打开文件

读文件

//fopen fread
int main()
{FILE*fp=fopen("myfile","r");if(fp==NULL){perror("fopen");return;}char buf[128];char*msg="hello Linux";while(1){size_t s=fread(buf,1,strlen(msg),fp);if(s>0){buf[s]=0;printf("%s\n",buf);}if(feof(fp))break;}fclose(fp);return 0;
}

以“r”读-打开文件

总结:

w:写入,清空并重头写

a:写入,以追加形式写入

另外,我们上面的路径都是当前路径,即进程的当前路径cwd,也就是说如果我更改了当前进程的cwd,就可以把文件新建到其他目录。

上面是写到文件中,那么如果想要输出信息到显示器中,我们有一下三个输入输出流

分别是:stdout(显示器文件),stdiin(键盘文件),stderr(显示器文件)

C程序默认在启动的时候,会打开三个标准输入输出流(文件)

这是OS的特性,进程默认会打开键盘,显示器,显示器。

ps:

看一下FILE的属性内容:

FILE是自己封装的结构体,这里面必须封装文件描述符!

认识文件系统调用:

文件其实是在磁盘上,磁盘是外部设备,访问磁盘文件其实就是访问硬件!

我们在之前了解到:

我们几乎所有的库只要是访问硬件设备,必定要封装系统调用。为什么一定要封装?

1.系统不相信任何人

2.程序不能直接绕过我们的操作系统,直接去访问硬件,必须要贯穿OS的方式去访问。

因此,open是操作系统的接口,而fopen是C/C++库的接口。

上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。

而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

那么,现在我们来认识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: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限(否则会乱码)
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1#include<unistd.h>
ssize_t write(int fd, const void *buf, size_t count);ssize_t read(int fd, void *buf, size_t count);
//open read
int main()
{int fd=open("myfile",O_RDONLY);if(fd<0){perror("open");return 1;}const char* msg="hello Linux!";printf("strlen:%d",strlen(msg));// printf("sizeof:%d",sizeof(msg));char buf[128];while(1){ssize_t s=read(fd,buf,strlen(msg));if(s>0){buf[s]=0;printf("%s\n",buf);}elsebreak;}close(fd);return 0;
}
int main()
{int fd=open("myfile",O_WRONLY,O_CREAT,0666);if(fd<0){perror("open");return 1;}const char*msg="hello Linux!";write(fd,msg,strlen(msg));return 0;
}

open函数的返回值

它返回的是一个数组的下标,叫做文件描述符

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

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

现在来证明一下:

int main()
{char buf[128];ssize_t s=read(0,buf,sizeof(buf));if(s>0){buf[s]=0;write(1,buf,strlen(buf));write(2,buf,strlen(buf));}else{perror("read");return 1;}return 0;
}

因为,Linux下默认标准输入,标准输出,标准错误分别占了文件描述符表的0,1,2.

对此,我们创建文件的文件描述符,即是从下表3开始的,现在我们来证明一下:

int main()
{int fd=open("myfile",O_RDONLY);printf("fd:%d\n",fd);close(fd);return 0;;
}

结论:

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

用户缓冲区的理解:

我们先来几段代码看看他们呈现出的不同现象,以此来引入缓冲区的概念:

一:带fork版本的:

int main()
{const char*msg1="hello printf!\n";const char*msg2="hello fwrite!\n";const char*msg3="hello write!\n";const char*msg4="hello fprintf!\n";printf("%s",msg1);fprintf(stdout,"%s",msg4);fwrite(msg2,strlen(msg2),1,stdout);write(1,msg3,strlen(msg3));fork();return 0;
}

重定向:

不带fork版本:

我们从上面不同现象可以看出:这一定与fork脱离不了关系!!(我们待会再说)

继续,如果我们

上面我们了解到,write是系统接口,当在write之前关闭了stdout标准输出时,没有打印出来。

这说明了printf,fprintf,fwrite这些缓冲区一定不在操作系统内部,不是系统级别的缓冲区。

因此,这些是C语言给我们提供的一个缓冲区。

我们来补充一下关于缓冲区的刷新问题:分为三种情况

1.无缓冲,直接刷新

2.行缓冲,不刷新,直接碰到\n就刷新--一般是显示器

3.全缓冲,缓冲区满了才刷新。---一般是普通文件写入

因此,对于显示器文件的刷新方案是行刷新,所以在printf执行完就会立即遇到\n的时候,将数据进行行刷新,即用户刷新的本质,就是将数据通过1+write写入到内核中。

1.进程退出的时候也会进行刷新!!

2. 缓冲区的作用(为什么要有这个缓冲区??)
- 解决效率问题:直接频繁地进行 I/O 操作(比如往磁盘写数据、从键盘读数据)效率很低。缓冲区可以先把数据暂存起来,攒到一定量后再一次性进行 I/O 操作,减少 I/O 次数,提升整体效率。
- 配合格式化:像  printf  这类带格式化输出的函数,需要先把数据按格式整理好,缓冲区能为这种“整理 - 输出”的过程提供暂存空间,让格式化和输出的节奏更合理。

2. 缓冲区的位置

C 语言中的  FILE  结构体(由  fopen  等函数返回的对象)里,包含了对应打开文件的用户级缓冲区字段和维护信息。 FILE  对象属于用户层(因为 C 语言本身是运行在用户空间的),所以它内部的缓冲区也属于用户级缓冲区,而非操作系统内核直接管理的缓冲区。

3. 缓冲区的刷新时机
- 调用  fflush(stdout)  可以主动刷新标准输出( stdout )的缓冲区,把暂存的数据立刻输出。
- 进程调用  exit()  退出时,也会自动刷新缓冲区,确保数据不会因为进程退出而丢失。

有了上面的了解,我们就可以来解释为什么带fork与不带fork重定向出来的结果不同的原因!

一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。

printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。

而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后

但是进程退出之后,会统一刷新,写入文件当中。

但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。

write 没有变化,说明没有所谓的缓冲

好了,本次先分享到这里,我们下次继续,希望我们都一起进步!

最后,到了本次鸡汤环节:

慢慢前进!

http://www.dtcms.com/a/521801.html

相关文章:

  • 如何上传网站网站开发价格明细
  • 深圳英文网站制作定西建设厅网站
  • 面向边缘AI视觉系统的低成本硬件方案
  • 医疗网站建设市场网站维护中是怎么回事
  • 网站开发费如何入账课程培训网站建设
  • dw做的网站怎么放到服务器上网站设计应遵循的原则
  • 南宁工程建设网站有哪些网站建设中模板
  • php网站开发用什么工具wordpress 创建分类
  • xml的网站地图织梦制作网站建设7
  • 乌兰察布市建设工程造价网站网站注册域名
  • 北京网站备案要求吗运营实力 网站建设
  • 41.渗透-Kali Linux-工具-Xhydra(爆破攻击)
  • 公众号视频网站开发外国教程网站有哪些
  • seo网站页面f布局免费的网页设计代码模板
  • 制作商城版网站开发手机百度网页版登录入口
  • 搭建网站硬件要求外贸建站 知乎
  • 网站标准字体企业网站开发项目策划书基本框架
  • 从 0 到 1 团队落地仓颉语言:一份可复制的工程化改造与度量驱动实践!
  • 国外域名建站WordPress5分钟建站
  • [Java数据结构与算法] 哈希表(Hash Table)
  • 嘉兴模板建站代理网站广告模板代码
  • 济南网站建设认可搜点网络能容桂做pc端网站
  • 百度手机模板网站软文推广渠道
  • 国外特效网站汕头网站建设制作公司
  • 如何解决 pip install 安装报错 invalid command ‘bdist_wheel’(缺少 wheel)问题
  • 个人网站如何搭建南山做网站公司哪家值得合作
  • 小九源码-springboot097-java付费自习室管理系统
  • 厦门大型服装商城网站建设哪个做企业网站
  • 做视频网站 许可做养生类项目的网站
  • 如何拿qq空间做网站wordpress iis 发布