我是靠谱客的博主 无语电话,最近开发中收集的这篇文章主要介绍linux篇【8】:基础IO—<前序>一.文件相关知识储备二.c语言文件操作复习三.系统接口四.文件描述符五.文件描述符应用特征六.缓冲区,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

一.文件相关知识储备

二.c语言文件操作复习

1.基本写入

2.当前路径

chdir(“路径”):更改当前进程的工作路径

3.文件操作

(1)a: 追加写入,不断的往文件中新增内容->追加重定向!

(3)读取

4.回归理论

三.系统接口

1.open

 (1)宏标记位示例:

(2)打开曾经不存在的文件用int open(const char *pathname, int flags, mode_t mode); 

错误示例:

示例2:初始权限给666,结果却是-rw-rw-r--:因为有权限掩码

示例3:用umask() 系统接口设置权限掩码

2.close 关闭文件

3.write 写入文件

 write使用示例:

(1)O_TRUNC——将原文件内容置空的宏

(2)O_APPEND——追加内容

4.read

四.文件描述符

1.为什么文件描述符fd从3开始,0, 1, 2是什么?

系统接口和c语言接口对应关系:​编辑

(1)证明:0对应标准输入,1对应标准输出,2对应标准错误

(2)验证012和stdin,stdout,stderr的对应关系

结论:

2.文件描述符究竟是什么?——就是指针数组的数组下标

3.虚拟文件系统(VFS)

五.文件描述符应用特征

1.文件描述符的分配规则

2.重定向

(1)重定向原理 >

(2)dup2 重定向

(3)追加重定向:>>

 (4)输入重定向 <

六.缓冲区

1.缓冲区的本质:就是一段内存

2.缓冲区的意义

3.缓冲区在哪里

例1:

例2:如果在刷新之前,关闭了fd会有什么问题?

4.刷新策略的问题

缓冲区和fork引发的小问题:

5.fflush()函数:更新缓存区。

6.sync,syncfs和fsync函数

(1)sync

(2)syncfs 刷盘

(3)fsync

7.模拟实现一下自己封装C标准库——打开文件的模拟实现

第二个main测试:


一.文件相关知识储备

1.文件=文件内容+文件属性
文件属性也是数据->即便你创建一个空文件,也要占据磁盘空间

2.文件操作=文件内容的操作+文件属性的操作
有可能在操作文件的过程中,既改变内容,又改变属性

3. 所谓的“打开”文件,究竟在干什么?
答:将文件的属性或内容加载到内存中! ——冯诺依曼体系决定:CPU只能从内存中对数据做读写!

4.是不是所有的文件,都会处于被打开的状态?
——答:绝对不是!
没有被打开的文件,在哪里?——答:只在磁盘上静静的存储着!

5.对文件的宏观分类:
打开的文件(内存文件) 和 (未打开文件)磁盘文件

6.通常我们打开文件,访问文件,关闭文件,是谁在进行相关操作?——是进程在操作!
c语言接口fopen,fclose, fread, fwrite... -> 写出代码 -> 形成程序 -> 当我们的文件程序,运行起来的时候,才会执行对应的代码,然后才是真正的对文件进行相关的操作
                                        是进程在操作!!!

7.学习文件操作:就是学习——进程和打开文件的关系!        内存级

r+和w+都是既读又写,只是w+多了一个文件不存在就创建文件

二.c语言文件操作复习

1.基本写入

fprintf(fp, "%s: %dn", msg, cnt++); 向fp中写入msg, cnt两个值、

#include<stdio.h>
#include<unistd.h>
int main()
{
  FILE *fp=fopen("log.txt","w");
  if(fp==NULL)
  {
    perror("fopen");
    return 1;
  }
  const char *msg="hello linux";
  int cnt=1;
  while(cnt<=5)
  {
    fprintf(fp,"%s:%d",msg,cnt++);
  }

  return 0;
}

36c287210d4449c8a947718f8ab232d4.png

2.当前路径

(当前路径)cwd——correct work director42c65a7151f64edeb40827dfad29f639.png

当前路径定义:当前进程所处的工作路径!

默认的进程工作路径是在它所处的路径下,不过这个路径是可以改的

chdir(“路径”):更改当前进程的工作路径

ls /proc/(pid) 查看文件进程;ls /proc/(pid) -al 查看文件进程详细信息;

#include <stdio.h>
#include <unistd.h>

//myfile filename
int main(int argc, char* argv[])
{
	//chdir("/home/whb"); // 更改当前进程的工作路径
	//1. 默认这个文件会在哪里形成呢?当前路径
	//2. r, w, r+, w+, a(?), a+
	//3. 关注一下文件清空的问题
	FILE *fp = fopen("log.txt", "w"/*?*/); //写入
	if (fp == NULL)
	{
		perror("fopen"); //?
		return 1;
	}

	printf("mypid: %dn", getpid());

	while (1)
	{
		sleep(1);
	}

	const char* msg = "hello 104 ";
	int cnt = 1;
	while (cnt <= 5)
	{
		fprintf(fp, "%s: %dn", msg, cnt++);
	}

	fclose(fp);
}

3.文件操作

7c332e295eb941c48df24c159207e8b6.png

(1)a: 追加写入,不断的往文件中新增内容->追加重定向!


(2)当我们以w方式打开文件,准备写入的时候,其实文件已经先被清空!w:有文件就清空再从头写;没文件,就创建从头写

80d4017b9b29411b9ab0ce54fbd44108.png

(3)读取

利用格式./myfile log.txt ,读取log.txt文件的内容:

fgets(buffer, sizeof(buffer), fp)        按行读取

把读取的内容放到buffer,fp流中一行读多少字节,从fp流读出数据;读取失败返回NULL,成功返回buffer

#include <stdio.h>
#include <unistd.h>

//myfile filename
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s filenamen", argv[0]);
        return 1;
    }

    FILE *fp = fopen(argv[1], "r");
    if(fp == NULL)
    {
        perror("fopen"); //?
        return 1;
    }

    char buffer[64];
    while(fgets(buffer, sizeof(buffer), fp) != NULL)
    {
        printf("echo:%s", buffer);
    }
}

4ff7176feef746f789cb47b879cba363.png

4.回归理论

1.当我们向文件写入的时候,最终是不是向磁盘写入?——答:是

2.磁盘是硬件吗?——是硬件

3.只有谁有资格向硬件写入呢?——操作系统!

4.能绕开操作系统对文件直接写入吗?——不能,所有的上层访问文件的操作,都必须贯穿操作系统

5.操作系统是如何被上层使用的?——必须使用操作系统提供的相关系统调用!
封装了系统接口
1.如何理解printf
2.我们怎么从来没有见过?——因为所有的语言都对系统接口做了封装

为什么要封装?
1.直接使用原生系统接口,使用成本比较高!
2.语言不具备跨平台性!
封装是如何解决跨平台问题的呢?——穷举所有的底层接口+条件编译!
 

三.系统接口

接口介绍

1.open

int open(const char *pathname, int flags); 只能打开已有文件

int open(const char *pathname, int flags, mode_t mode);   打开曾经不存在的文件,可以初始化权限
——① pathname 文件名;

② flags 打开文件传递的选项:O_RDONLY 只读(read only), O_WRONLY 只写(write only),O_RDWR 读写(read write),O_APPEND 追加(append), O_CREAT 如果文件不存在就创建(creat)(这些都是宏,系统传递标记位,是用位图结构来进行传递的! 每一个宏标记,一般只需要有一个比特位是1,并且和其他宏对应的值,不能重叠)O_TRUNC 将原文件内容置空(truncate)

③ mode 初始权限

返回值:创建成功返回新的文件描述符fd,或者打开已有文件就返回已有文件的文件描述符;错误返回-1

[zsh@ecs-78471 ~]$ man 2 open

b1df5cbf3d00480f8410b27a54b40263.png

 (1)宏标记位示例:

跟上面的O_RDONLY等等一样,下面的这些也是宏,系统传递标记位,是用位图结构来进行传递的! 每一个宏标记,一般只需要有一个比特位是1,并且和其他宏对应的值,不能重叠

#include <stdio.h>

#define PRINT_A 0x1 //0000 0001
#define PRINT_B 0x2 //0000 0010
#define PRINT_C 0x4 //0000 0100
#define PRINT_D 0x8 //0000 1000
#define PRINT_DFL 0x0


//open
void Show(int flags)
{
    if(flags & PRINT_A) printf("hello An"); //如果flags是PRINT_A,就打印hello A
    if(flags & PRINT_B) printf("hello Bn"); //如果flags是PRINT_B,就打印hello B
    if(flags & PRINT_C) printf("hello Cn"); //如果flags是PRINT_C,就打印hello C
    if(flags & PRINT_D) printf("hello Dn"); //如果flags是PRINT_D,就打印hello D

    if(flags == PRINT_DFL) printf("hello Defaultn");
             //如果flags是PRINT_DFL,就打印hello Default
}


int main()
{
    printf("PRINT_DFL:n");
    Show(PRINT_DFL);

    printf("PRINT_An");
    Show(PRINT_A);

    printf("PRINT_Bn");
    Show(PRINT_B);

    printf("PRINT_A 和 PRINT_Bn");
    Show(PRINT_A | PRINT_B);

    printf("PRINT_C 和 PRINT_Dn");
    Show(PRINT_C | PRINT_D);

    printf("PRINT all:n");
    Show(PRINT_A | PRINT_B | PRINT_C | PRINT_D);

    return 0;
}

a824abc0f48d4554b8022e5146e16263.png

可以得出结论:Show(PRINT_A | PRINT_B); 我们传入宏PRINT_A | PRINT_B时,则A和B都满足,下面同理,如果我们既需要只读,又需要文件不存在就创建,就传入O_RDONLY | O_CREAT

(2)打开曾经不存在的文件用int open(const char *pathname, int flags, mode_t mode); 

mode给初始权限, 不能用   int open(const char *pathname, int flags); 否则权限是乱码。  int open(const char *pathname, int flags); 只能打开已有文件

错误示例:

此时没有log.txt文件

0d79c4664d214f638ca825a940a8a431.png

示例2:初始权限给666,结果却是-rw-rw-r--:因为有权限掩码

umask=0002,就是希望把other的写权限屏蔽掉,本来是-rw-rw-rw-,屏蔽other的w后就是    :        -rw-rw-r--

18ab51ff12f443b5a85802bf78a4134b.png

示例3:用umask() 系统接口设置权限掩码

 umask(0)把权限掩码设成0

176bdf810e8a4375a0587eb3a8cfab01.png

2.close 关闭文件

[zsh@ecs-78471 ~]$ man 2 close

d794b2c9d5834a9fa824ee6b452c0b31.png

传入整形fd关闭文件

3.write 写入文件

 ssize_t write(int fd, const void *buf, size_t count);

fd:特定的文件描述符。buf:(缓冲区)字符串的起始地址。 count:(缓冲区)字符串的大小

返回值:写入成功返回实际写入的字节数,什么都没写返回0,写入失败返回-1 错误码被设置。

 man 2 write

703da19aa78041d69899f6efd41e9145.png

 write使用示例:

注意:此处size_t count传strlen(str),不传,在vim中没有对应的实际字符串,打印出来就是乱码

cc8c42fbb901493b9575fe7cc9c5d406.png

f6cddfd0df6244c68135d59a08f72e2b.png

(1)O_TRUNC——将原文件内容置空的宏

细节1:c在w方式打开文件的时候,会清空的!这里却是覆盖:(上次写入5次hello 104 后,重新写入1行“aaaaaa”时发现会覆盖)

 71eb4cea9382420ca35d87cec2c0cabb.png

解决方法:O_TRUNC:将原文件内容置空

1faa005bcfb4488abecae0cd1c5ffb09.png

fbdf6a401ea24a8aa63a2373dea35ad7.png

log.txt中显示 "aaaaaa"

(2)O_APPEND——追加内容

1604a3aae7dd4705b8bbc28414ea232a.png

4.read

 ssize_t read(int fd, void *buf, size_t count); 

—— ssize_t是有符号整数类型。——int fd:文件描述符。——void *buf:(缓冲区)字符串的起始地址。—— count:(缓冲区)字符串的大小

返回值:成功后,返回读取的字节数;0表示文件结束;信号出现错误返回-1。

管道中返回值=0的情况:读端一直在读,写端不写了,并且关闭了写端,读端会使 read的返回值 == 0,代表写端关闭

s == 0: 代表对方关闭,client 写端退出

390909c3e97b4b639538f8078ab0b6fa.png

例子:把log.txt文件的内容读到数组buffer中

 e26f20353d4546dc8f2de912923738ac.png

四.文件描述符

每个文件描述符都是一个内核中文件描述信息数组的下标

文件描述符fd:

fd<0:failed
fd>=0:success

当我们打开一堆文件时,发现他们的fd从3开始往后排:

359be474b2e24ed6a161b55b650cced1.png

引入以下问题:

1.为什么文件描述符fd从3开始,0, 1, 2是什么?

系统接口和c语言接口对应关系:
a91a3822b7bf49ac9093b0319cb08550.png

FILE* ->文件指针-> FILE是什么呢?
FILE:C库提供的结构体,它封装了多个成员,并且FILE内部,必定封装了fd!

(1)证明:0对应标准输入,1对应标准输出,2对应标准错误

① 0对应标准输入

404f9787dd0346ed9704b4c1cd16d3ae.png

② 1对应标准输出,2对应标准错误,

 350225e9ffd940eda5ac89807256ddf2.png

(2)验证012和stdin,stdout,stderr的对应关系

_fileno 就是结构体FILE内部封装的文件描述符

7eff04c6b83d4319b7e281c905307ccc.png

结论:

上层的c语言函数接口 fopen/ fclose/ fread/ fwrite... 底层封装的是系统接口open/close/ read/write;上层的c语言类型对应的结构体FILE底层封装的是系统文件描述符fd;c库函数调用的系统接口

c196e80ec90f4d39aa8ebed2c1514221.png

2.文件描述符究竟是什么?——就是指针数组的数组下标

每个文件描述符都是一个内核中文件描述信息数组的下标

进程:内存和文件的关系 ——> 被打开的文件是在内存里面的! !

一个进程可不可以打开多个文件?——:当然可以,所以在内核中,进程:打开的文件 = 1:n ——>    所以系统在运行中,有可能会存在大量的被打开的文件! ——>    OS要对这些被打开的文件进行管理——>操作系统如何管理这些被打开的文件呢? ?——:先描述,在组织

一个文件被打开,在内核中,要创建该被打开的文件的内核数据结构 就是:先描述
c70ab91bede14a50951b8993d506f25d.png

解释下图:

进程中存在一个结构体指针 struct files_struct* files,指向结构体 struct files_struct,结构体 struct files_struct内部存着一个指针数组(文件描述符表) struct file* fd_ array[ ],他的各个位置存着被打开的文件的地址,[0]指向的打开文件就是上面说的标准输入;[1]指向的打开文件就是标准输出;[2]指向的打开文件就是上面说的标准错误;文件描述符就是这里的指针数组的数组下标。

4a6649bcba1042cbab704ecb83f183e2.png

3.虚拟文件系统(VFS)

 接着解释上面的struct file如何实现调用硬件的,同时证明 Linux下一切皆文件!

 如何使用C语言,实现面向对象(类):

92327e0c822e449ca72b283e3dbf6ef7.png

 虚拟文件系统通过函数指针的方式将上层的操作系统层和底层的硬件驱动层进行逻辑解耦:

53efd64c3866431488ceba6a100c5407.png

五.文件描述符应用特征

1.文件描述符的分配规则

从头遍历数组fd_array[], 找到一个最小的,没有被使用的下标,分配给新的文件!

如果close(0),即关闭标准输入,发现分配给log.txt的文件描述符就是0;

a236c3b646ec4a80a7064624edf9d15b.png

如果close(2),即关闭标准错误,发现分配给log.txt的文件描述符就是2;

69544c07f62c46a9b22a83755750ce8d.png

但是如果close(1),即关闭标准输出,发现什么也不打印;因为printf->stdout->1->不再指向对应的显示器了,而是已经指向了log.txt的底层struct file对::

1016d286ceed4a96884f29db2ce36cf2.png

2.重定向

重定向概念:

每个文件描述符都是一个内核中文件描述信息数组的下标,每个文件描述符对应有一个文件的描述信息用于操作文件,而重定向就是在不改变所操作的文件描述符的情况下,通过改变描述符对应的文件描述信息进而实现改变所操作的文件

(1)重定向原理 >

0ee14cd3f38b4ac2ad7294d6dd71c921.png

重定向原理:stdout中有一个结构体FILE,他的fd天然就是1,结合上下图,当close(1)时相当于把指针数组的[1]置为空,int fd = open("log.txt",O_ WRONLY | O_ CREAT | O_TRUNC, 0666); 时遍历指针数组,因为[1]有空余,就把[1]指向myfile(log.txt)。当 fprintf (stdout, "打开文件成功,fd: %dn", fd) ; 时,就把内容输入stdout存的文件描述符中,就是输入到[1]中,可是此时[1]指向myfile(log.txt),所以,内容就输入到myfile(log.txt)中了。

86fb8788f3124dbf873cd7965a3ea85d.png

如果我们要进行重定向,上层只认0, 1, 2, 3, 4, 5这样的fd,我们可以在OS内部,通过一定的方式调整数组的特定下标的内容(指向),我们就可以完成重定向操作!

(2)dup2 重定向

不用dup,只看dup2

17168b2575c742c1848b14fc7170e166.png

介绍:dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:
介绍的解释: int dup2(int oldfd, int newfd); newfd是oldfd内容的拷贝,意思是把 oldfd 内容拷贝进 newfd,都是oldfd了。即:原本数据向newfd文件中输入,现在数据向oldfd中输入了。

5439cd90fddd4a31a5b11eeeea092829.png

如果想把stdout重定向成myfile(log.txt),拷贝是:fd—>1,int dup2(int oldfd, int newfd); oldfd—>[3], newfd—>[1],需要这样传参:dup2(fd, 1); fd这里就是3

c3f247737e8a47f9b47ec4830134638f.png

(3)追加重定向:>>

打开文件 log.txt ,dup2(fd,1)后,再向stdout中打印数据,会发现数据传到了文件log.txt中了

9e2e54988c424952bd5401bf95a3e669.png

 (4)输入重定向 <

先在给文件 log.txt 输入一堆“打开文件成功,fd: 3”

cd429d0e5a8942028a95c1f503c2ae78.png

六.缓冲区

1.缓冲区的本质:就是一段内存

2.缓冲区的意义

a. 解放使用缓冲区的进程的时间
b.缓冲区的存在可以集中处理数据刷新,减少IO的次数,从而达到提高整机的效率的目的

送书的例子:你在云南,需要把书送到北京的朋友那里:你(进程)把书(数据)交给顺丰快递(缓冲区),此时进程返回并继续执行自己后续的代码,缓冲区积累够一堆数据以后才开始

65d820e3df794fba9ee38823f9eb46b5.png

3.缓冲区在哪里

例1:

下面例子write和printf都不加n时,write有无n都会直接刷新,printf无n在sleep期间不刷新,而printf内部是封装了write,说明sleep期间printf内部是把数据存在缓冲区中,此时并没有调用write系统接口:那么这个缓冲区一定不在write内部!我们曾经谈论的缓冲区,不是内核/系统级别的。那么这个缓冲区在哪里???——只能是C语言提供的,语言级别的缓冲区

printf,fprintf,fpus这类函数的参数中都有stdout这个FILE*类型的参数(printf也有只是没显示出来),结构体struct FILE中封装很多的属性,除了封装了fd,还封装了该FILE对应的语言级别的缓冲区。即:缓冲区在结构体FILE的内部。

你的代码中调用了printf() ,fprintf(),fputs() 等时,是先把要打印的内容存在结构体FILE的缓冲区cache中,缓冲区内数据积累到一定大小会定期通过fd调用write()把数据刷新到内存中去。就引出了下面的刷新策略的问题~ 

缓冲区刷新草图:

560174722e274f5aab120345c05411c5.png

既然缓冲区在FILE内部,在C语言中,而我们每一次打开一个文件,都要有一个FILE*会返回!
意味着,每一个文件都有一个fd和属于它自己的语言级别缓冲区
 

例2:如果在刷新之前,关闭了fd会有什么问题?

在进程结束时printf() ,fprintf(),fputs() 会把数据刷新到stdout,把数据刷新到stdout之前关闭stdout的文件描述符fd就会导致缓冲区内数据无法通过fd调用write()把数据刷新到内存中。

 

 如果sleep之前加上fflush(stdout),就会立即刷新。

4.刷新策略的问题

缓冲区内数据积累到一定大小会定期通过fd调用write()把数据刷新到内存中去,如何定期刷新?(什么时候刷新?)
常规:
a.无缓冲(立即刷新)——系统接口write就是无缓冲
b.行缓冲(逐行刷新)——文件是显示器就是行刷新
c.全缓冲(缓冲区满,刷新)——写入到块设备对应的文件,即:写入到磁盘文件

特殊: ①进程退出 ②用户强制刷新fflush

缓冲区和fork引发的小问题:

解释:./myfile 的刷新策略就是正常的行缓冲,立即刷新了四条字符串;

主要解释——./myfile > log.txt :

先要知道:

1.刷新的本质,把缓冲区的数据write到OS内部,清空缓区! 
2.缓冲区,是自己的FILE内部维护的,属于父进程内部的数据区域,子进程会发生写时拷贝

解释./myfile > log.txt :        当./myfile输出的内容重定向写入 log.txt时,因为log.txt是磁盘文件,所以刷新策略由 无缓冲(立即刷新) 转为 全缓冲(缓冲区满,刷新) ,所以C库函数的printf() ,fprintf(),fputs()打印的3条字符串存在各自的FILE的缓冲区中,系统接口write则不受缓冲区影响,直接刷新出来;当fork()时创建子进程,随后main函数即将结束,此时进程退出是会刷新缓冲区的。因为 缓冲区是自己的FILE内部维护的,属于父进程内部的数据区域,所以子进程要发生写时拷贝,所以父子进程就会各自刷新一份(hello fputs  ,hello printf,hello fprintf)到OS内部

5.fflush()函数:更新缓存区。

头文件:#include<stdio.h>
函数定义:int fflush(FILE *stream);
函数说明:调用fflush()会将缓冲区中的内容写到stream所指的文件中去.若stream为NULL,则会将所有打开的文件进行数据更新。

fflush 是 C 标准库提供的函数,对输入输出流起作用,起作用的是C标准库管理的用户空间文件缓存。对输出流来说,会使用系统提供的写文件系统调用(write)把标准库缓存的数据写入文件,fflush并不能保证数据真正的写入文件系统。对输入流来说,会把从可寻址的文件读入标准库缓存的数据给清洗掉。

fflush(stdin):刷新缓冲区,将缓冲区内的数据清空并丢弃。fflush(stdin)不太常用,在有些编译器中是错误的用法,可以用以下方法替代:while(getchar()!='/n');
fflush(stdout):刷新缓冲区,将缓冲区内的数据输出到设备。

6.sync,syncfs和fsync函数

总结:sync是把所有文件刷盘;syncfs针对的是把 fd所在的文件系统的所有内核缓存 刷盘(文件系统不止一个,fd所在的某一个文件系统),fsync针对的是把 fd对应的单一文件 刷盘 

(1)sync

#include <unistd.h>
void sync(void);
int syncfs(int fd);

 sync 和 syncfs 起作用的是文件系统缓存,这些缓存是在内核空间管理的。sync 会把对文件系统的元数据、缓存的文件数据写入所有底层的文件,对所有文件系统有用syncfs 需要一个文件描述符,只写入文件描述符指向的文件所在的文件系统上的数据。有时候突然拔掉优盘,里面的文件会损坏,就是因为优盘上文件的更改没有从内核文件缓存写入优盘所导致的。

在linux系统上,sync 和 syncfs 都是阻塞的,会确保数据写入底层的文件系统。但是在POSIX标准里,sync可能在数据写入之前返回。

(2)syncfs 刷盘

 int syncfs(int fd);        系统接口write仅仅是写入内核中,syncfs才是写入到磁盘上

 sync 和 syncfs 起作用的是文件系统缓存,这些缓存是在内核空间管理的。sync 会把对文件系统的元数据、缓存的文件数据写入所有底层的文件,对所有文件系统有用syncfs 需要一个文件描述符,只写入文件描述符指向的文件所在的文件系统上的数据。有时候突然拔掉优盘,里面的文件会损坏,就是因为优盘上文件的更改没有从内核文件缓存写入优盘所导致的。

在linux系统上,sync 和 syncfs 都是阻塞的,会确保数据写入底层的文件系统。但是在POSIX标准里,sync可能在数据写入之前返回。

(3)fsync

#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);

fsync 把文件描述符fd指向的文件缓存在内核中的所有已修改的数据写入文件系统,包含数据与文件元数据(文件大小,文件修改时间等)。但是fsync不会写入对指向文件的目录项的修改,也就是说如果新创建了一个文件,要是确保下次能正确读出的话,就需要把所在目录也fsync一下。

fdatasync 把和fsync作用差不多,但是不会写入对下次正确读取文件作用不大的一些元数据(比如上次访问时间,上次修改时间等),但是大小如果改变了,是会写进去的。

这两个系统调用被调用后会阻塞,直到设备报告所有数据都已写入(设备可能本身也有缓存)。

7.模拟实现一下自己封装C标准库——打开文件的模拟实现

int syncfs(int fd);  sync, syncfs - commit buffer cache to disk (把缓冲区的数据直接提交到磁盘这个硬件上)

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

#define NUM 1024

#define NONE_FLUSH 0x0
#define LINE_FLUSH 0x1
#define FULL_FLUSH 0x2

typedef struct _MyFILE{
    int _fileno;
    char _buffer[NUM];
    int _end;
    int _flags; //fflush method
}MyFILE;

MyFILE *my_fopen(const char *filename, const char *method)
{
    assert(filename);
    assert(method);

    int flags = O_RDONLY;

    if(strcmp(method, "r") == 0)
    {}
    else if(strcmp(method, "r+") == 0)
    {}
    else if(strcmp(method, "w") == 0)
    {
        flags = O_WRONLY | O_CREAT | O_TRUNC;
    }
    else if(strcmp(method, "w+") == 0)
    {}
    else if(strcmp(method, "a") == 0)
    {
        flags = O_WRONLY | O_CREAT | O_APPEND;
    }
    else if(strcmp(method, "a+") == 0)
    {}

    int fileno = open(filename, flags, 0666);
    if(fileno < 0)
    {
        return NULL;
    }

    MyFILE *fp = (MyFILE *)malloc(sizeof(MyFILE));
    if(fp == NULL) return fp;
    memset(fp, 0, sizeof(MyFILE));
    fp->_fileno = fileno;
    fp->_flags |= LINE_FLUSH;
    fp->_end = 0;
    return fp;
}

void my_fflush(MyFILE *fp)
{
    assert(fp);

    if(fp->_end > 0)
    {
        write(fp->_fileno, fp->_buffer, fp->_end);
        fp->_end = 0;
        syncfs(fp->_fileno);
    }

}


void my_fwrite(MyFILE *fp, const char *start, int len)
{
    assert(fp);
    assert(start);
    assert(len > 0);

    // abcde123
    // 写入到缓冲区里面
    strncpy(fp->_buffer+fp->_end, start, len); //将数据写入到缓冲区了
    fp->_end += len;

    if(fp->_flags & NONE_FLUSH)
    {}
    else if(fp->_flags & LINE_FLUSH)
    {
        if(fp->_end > 0 && fp->_buffer[fp->_end-1] == 'n')
        {
            //仅仅是写入到内核中
            write(fp->_fileno, fp->_buffer, fp->_end);
            fp->_end = 0;
            syncfs(fp->_fileno);
        }
    }
    else if(fp->_flags & FULL_FLUSH)
    {

    }
}

void my_fclose(MyFILE *fp)
{
    my_fflush(fp);
    close(fp->_fileno);
    free(fp);
}

int main()    //第一个测试例子
{
    MyFILE *fp = my_fopen("log.txt", "w");
    if(fp == NULL)
    {
        printf("my_fopen errorn");
        return 1;
    }

	const char* s = "hello my 111n";
	my_fwrite(fp, s, strlen(s));

	printf("消息立即刷新");
	sleep(3);

	const char* ss = "hello my 222";
	my_fwrite(fp, ss, strlen(ss));
	printf("写入了一个不满足刷新条件的字符串n");
	sleep(3);

	const char* sss = "hello my 333";
	my_fwrite(fp, sss, strlen(sss));
	printf("写入了一个不满足刷新条件的字符串n");
	sleep(3);


	const char* ssss = "endn";
	my_fwrite(fp, ssss, strlen(ssss));
	printf("写入了一个满足刷新条件的字符串n");
	sleep(3);

    const char *sssss =" aaaaaaa";
    my fwrite(fp, sssss, strlen(ssss));
    printf("写入了一个不满足刷新条件的字符串n");
    sleep(1);
    my_ fflush(fp);
    sleep(3);
    my_ fclose(fp);

}

int main()    //第二个测试例子
{    
    const char *s = "-aaaaaaa";
    my_fwrite(fp, s, strlen(s));
    printf("写入了一个不满足刷新条件的字符串n");
    fork();

    //模拟进程退出
    my_fclose(fp);
}

第二个main测试:

(1)有n时, 只有一串"aaaaaaa",原因:普通的行刷新策略,在fork之前就立即刷新了,所以只有一行。

(2)无n时, 有二串"aaaaaaa",原因:普通的行刷新策略,没有"n",则一直不刷新,在fork之后有父子两个进程,因为代码共享,在进程退出时,父子进程各自刷新了一行"aaaaaaa",所以有二行。

最后

以上就是无语电话为你收集整理的linux篇【8】:基础IO—<前序>一.文件相关知识储备二.c语言文件操作复习三.系统接口四.文件描述符五.文件描述符应用特征六.缓冲区的全部内容,希望文章能够帮你解决linux篇【8】:基础IO—<前序>一.文件相关知识储备二.c语言文件操作复习三.系统接口四.文件描述符五.文件描述符应用特征六.缓冲区所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部