欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > 【Linux】基础IO-下

【Linux】基础IO-下

2025/5/23 13:28:26 来源:https://blog.csdn.net/weixin_74792326/article/details/143222605  浏览:    关键词:【Linux】基础IO-下

目录

1、重定向

2、缓冲区

3、简单模拟实现C文件标准库


1、重定向

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#define filename "log.txt"int main()
{int fd = open(filename, O_CREAT|O_WRONLY|o_TRUNC,0666);if(fd < 0){perror("open"); //当打开出错时,就会显示open: 错误描述信息return 1;}printf("fd:%d\n",fd);//返回值是你实际写入到这个文件中的个数int cnt = 5;const char *msg = "hello Linux";while(cnt){write(fd, msg,strlen(msg));cnt--;}close(fd);return 0;
}

文件描述符的分配规则:从0下标开始寻找最小的没有被使用的数组位置,它的下标就是新文件的文件描述符。eg:flose(0),那么打开的新文件的fd就是0.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#define filename "log.txt"int main()
{close(1);//关闭显示器文件int fd = open(filename, O_CREAT|O_WRONLY|o_TRUNC,0666);if(fd < 0){perror("open"); //当打开出错时,就会显示open: 错误描述信息return 1;}int cnt = 5;const char *msg = "hello Linux";while(cnt){write(1, msg,strlen(msg)); //向显示器写入cnt--;}close(fd);return 0;
}

结果:./myproc

(没有任何显示)  但是cat  log.txt

显示:hello Linux

           hello Linux

           hello Linux

           hello Linux

           hello Linux

我们可以看到,需要向显示器打印的数据被打印到了文件里------这其实就是输出重定向

其实是因为:当flose(1)的时候,系统就会重新将1下标分配给log.txt,可是,在write的时候是向1写的,就会写入到 log.txt 文件(上图就是重定向的原理)   重定向其实是将数组的内容做修改。

数组的内容:就是文件的地址。  系统中有专门用作重定向的系统调用,本质就是将3号下标的log.txt file* 拷贝到1号下标,这样,就可以是log.txt分配的fd是1,这样写入的话就可以直接完成重定向。

#include <unistd.h>
int dup2(int oldfd, int newfd);// oldfd 是最后两个都变成的fd(即你要往哪个里面写)  newfd是需要将oldfd复制到的下标  其实是指针内容进行拷贝
//使用一下系统调用进行重定向。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#define filename "log.txt"int main()
{int fd = open(filename, O_CREAT|O_WRONLY|o_TRUNC,0666);//int fd = open(filename, O_CREAT|O_WRONLY|O_APPEND,0666);//追加重定向if(fd < 0){perror("open"); //当打开出错时,就会显示open: 错误描述信息return 1;}int cnt = 5;//重定向dup2(fd,1); //直接使用系统调用来进行重定向close(fd);  //可以关也可以不关,这样就不会造成文件描述符浪费const char *msg = "hello Linux";while(cnt){write(1, msg,strlen(msg)); //向显示器写入cnt--;}close(fd);return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#define filename "log.txt"int main()
{int fd = open(filename,O_RDONLY);//输入重定向if(fd < 0){perror("open");return 1;}//重定向  //dup2(int oldfd, int newfd); //可以理解为用fd替换0dup2(fd, 0);char inbuffer[1024];ssize_t s = read(0, inbuffer, sizeof(inbuffer)-1);//s保存的是实际读取的字节数if(s > 0){inbuffer[s] = '\0';//将第s个字节设置为\0  字符串结束printf("echo# %s\n", inbuffer);}close(fd);return 0;
}
//解释:本来是从键盘文件读的,但是写了输入重定向,就是直接从文件中读

运行的结果就直接将文件内容读出来了。//直接从文件里面读,就叫输入重定向

重定向的本质:其实就是对文件描述符表的内容的地址做修改

int fd = open(filename, O_CREAT|O_WRONLY|o_TRUNC,0666);

dup(fd,1);

printf("hello printf!\n");

fprintf(stdout,"hello fprintf\n");

//上面这两个函数是库函数,底层肯定有stdout

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

命令行上:
echo "hello Linux" > log.txt

echo "heoll Linux" >> log.txt //追加重定向

cat < log.txt 

进程历史打开的文件与进行的各种重定向关系 都和未来进行程序替换无关! 程序替换并不影响文件访问!这也相当于形成了文件管理和进程管理的解耦!

stout && sterr 的区别

stdin->fd: 0     标准输入
stdout->fd: 1   标准输出   向显示器输出
stderr->fd:  2    标准错误   向显示器输出

都是向显示器输出,那两个有上面区别?????

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>int main()
{fprintf(stdout, "hello normal message\n");fprintf(stdout, "hello normal message\n");fprintf(stdout, "hello normal message\n");fprintf(stdout, "hello normal message\n");fprintf(stderr, "hell error message\n");fprintf(stderr, "hell error message\n");fprintf(stderr, "hell error message\n");fprintf(stderr, "hell error message\n");return 0;
}

上面的代码:

直接运行:

但是,当我们进行重定向时: 我们会发现 error message 没有被重定向 只有normal message被重定向了 因为normal信息打印的时候用的是stdout  这是标准输出  而 stderr是标准错误 我们只是使用了 > 进行了输出重定向(这是1),也就是让1不要指向显示器,指向我创建的文件(本质是:指针数组存的地址被改变了)

 error message 没有被重定向 只有normal message被重定向了  ' > '  是输出重定向   就是让1不要指向显示器 而是   直接指向你创建的文件   也就是说,让本来应打印到标准输出的信息 打印到了文件里

./mytest  1> normal.log  2> err.log  这样写就可以将normal信息和error信息都重定向到文件中。1其实可以不写,因为 > 本来就是输出重定向,但是要想重定向err信息(即标准错误输出)就必须加2

也可以将两个一起重定向  ./mytest >all.log 2>&1    1已经做了前面的   &1的意思就是将1里面的内容写到2里面    即 1先指向新创的文件  然后让2 里面的地址 也变成和1一样的(即同一个文件的地址)  因此两个就指向了同一个文件.

这就是为什么有时候用printf有时候用perror

  • 使用printf时,你主要是输出程序运行中的各种信息。
  • 使用perror时,你是在处理错误,并希望获取系统提供的错误描述。

2、缓冲区

#include<stdio.h>
#include<string.h>
#include<unistd.h>int main()
{const char *fstr = "hello fwrite\n";const char *str = "hello write\n";//C语言提供的printf("hello printf\n");      //stdout->1fprintf(stdout, "hello fprintf\n");      //stdout->1fwrite(fstr,strlen(fstr), 1, stdout);      //stdout->1//操作系统提供的系统调用接口write(1, str, strlen(str));   //1fork();return 0;
}

补充:size_t fwrite(const void *ptr,  size_t size,   size_t nmemb,  FILE *stream);

fwrite的返回值其实是nmemb的个数.  4字节 10个4字节  返回的是写入到基本单位的个数.

运行上面的代码,正常打印四句.但是./myfile > log.txt  即重定向之后:

通过实验现象 我们可以发现 C接口被打印了两次.  但是系统调用接口的打印只有一次,也就是说,系统调用不会受其他东西的影响.  那么出现这样的现象是为什么呢??

我们再看一个代码.

#include<stdio.h>
#include<string.h>
#include<unistd.h>代码1 //
int main()
{const char *fstr = "hello fwrite\n";const char *str = "hello write\n";printf("hello printf\n");      //stdout->1fprintf(stdout, "hello fprintf\n");      //stdout->1fwrite(fstr,strlen(fstr), 1, stdout);      //stdout->1flose(1);return 0;
}// 代码2 
int main()
{const char *fstr = "hello fwrite";const char *str = "hello write";printf("hello printf");      //stdout->1fprintf(stdout, "hello fprintf");      //stdout->1fwrite(fstr,strlen(fstr), 1, stdout);      //stdout->1flose(1);return 0;
}
/ 代码3 
int main()
{const char *str = "hello write";write(1, str, strlen(fstr), 1, stdout);flose(1);return 0;
}

代码1 和 代码2 的区别就是1 有\n 而代码2 没有,那为什么有\n,就算close(1)  也可以打印出三条打印信息呢???     为什么3 可以打印出hello write  ??

printf/fprintf/fwrite/ fputs......---->C的库函数

他们的底层一定调用write(系统调用)

其实,调用printf  fprintf fwrite 这些函数是,其实已经将这些字符串 写进缓冲区  里了,只不过这个缓冲区一定不在OS内.  因为,如果在系统级别的缓冲区,当close的时候,因为close也是系统级别的,一定会将缓冲区的内容刷新出来,但是我们并没有看到这样的现象,因此,可以说明这个缓冲区不是系统级别的缓冲区!!!!!       他们不是通过write将数据写到内核缓冲区的.

就像write是系统调用接口,它是直接写到系统缓冲区里面close的时候将数据刷出来.  C语言其实会给我们提供一个缓冲区,这个缓冲区是用户级别的, 调用库函数就是将数据写入到用户级别的缓冲区里面,当到合适的时候,比如说:碰到了强制刷新 、close fclose文件描述符 、 或者字符串里面有\n   这样C库才会自动调用write将数据写到系统的缓冲区中,然后刷新出来.


但是,要刷的时候如果1号文件描述符被关闭了 就刷不出来了

显示器的文件的刷新方式是行刷新,所以在printf执行完的时候,如果遇到\n 将数据立即进行刷新  eg:将上面的字符串都带上\n  然后close(1)   即使这样还是能打印出来 (因为在close(1) 之前,数据就已经被写到了操作系统里) 但是去掉  \n  就只有write系统调用才能打印出来.

总结一下:当close(1)之后,用户级别的缓冲区的数据就不会刷新到内核级别的缓冲区中了。刷新的本质就是将数据通过1 + write 写入到内核中.而且,目前我们认为,只要将数据刷新到内核,数据就可以到硬件了。操作系统会自动帮我们把数据写到磁盘/显示器。

缓冲区的刷新策略(用户级别的缓冲区):a、无缓冲:直接刷新   b、行缓冲:不刷新,直到碰到\n(显示器的)   c、全缓冲:缓冲区满了才刷新  (文件写入)  根据缓冲区的刷新策略来决定什么时候调write()系统调用将数据写到缓冲区里。

补充问题:

1、进程退出的时候数据也会被刷新

2、为什么要有这个缓冲区(用户级别的)

a、解决效率问题---用户的效率问题

b、配合格式化

3、这个缓冲区在哪?
我们之前说过FILE里面肯定封装了fd,其实里面还有对应打开文件的缓冲区字段和维护信息

fprintf(stdout,"hello world\n");

stdout是FILE*类型的,这个结构体里面{int fd = 1; 缓冲区}  hello world就是先放到这个缓冲区里面的,等到合适的时候,调用write刷新到系统缓冲区。

这个FILE对象属于用户级别呢?还是操作系统级别?

语言都属于用户层

这个缓冲区,是不是属于用户级的缓冲区呢?

答案是:是的!FILE *fopen(const char *path, const char *mode)  fopen的返回值为什么是FILE*
fopen是C标准库给我们提供的接口, 调用open在内核层建立内核级别的文件对象,并且拿到文件描述符;在语言层 malloc(FILE) 所以返回的就是FILE*   这个FILE里面就帮我们封装了文件描述符和缓冲区  也就是说,这个空间在C标准库里面已经帮我们做好了。

那么,我们再回头看一下之前的问题

#include<stdio.h>
#include<string.h>
#include<unistd.h>int main()
{const char *fstr = "hello fwrite\n";const char *str = "hello write\n";//C语言提供的printf("hello printf\n");      //stdout->1fprintf(stdout, "hello fprintf\n");      //stdout->1fwrite(fstr,strlen(fstr), 1, stdout);      //stdout->1//操作系统提供的系统调用接口write(1, str, strlen(str));   //1fork();return 0;
}

重定向之后,由向显示器写入变成向文件写入。那么缓冲区的刷新策略也就由  行刷新变成全刷新。只有当缓冲区被写满的时候才进行刷新。 write是系统调用,所以hello write先被打印 而且他不会受其他东西的影响,所以只打印一次。fork()创建了子进程, 因为进程退出会刷新缓冲区,那么子进程也要对缓冲区的数据进行刷新,这也就相当于之前所说的对数据的写入,子进程就要将缓冲区需要刷新的数据给自己拷贝一份。 因此 子进程和父进程结束的时候都会刷出数据。

没有发生重定向时,是行刷新,在没有fork之前,这些数据就已经被刷到系统缓冲区,在调用fork之后,用户缓冲区是没有数据的。因此就只打印四句。

3、简单模拟实现C文件标准库

main.c

#include "Mystdio.h"int main()
{_FILE *fp = _fopen("test.txt", "w");if(fp == NULL) return 1;const char *msg = "hello world";_fwrite(fp, msg, strlen(msg));// _fclose(fp);_fclose(fp);return 0;
}

Mystdio.h

#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__#include <string.h>#define SIZE 1024
//刷新方式
#define FLUSH_NOW 1 
#define FLUSH_LINE 2 
#define FLUSH_ALL 4//创建FILE结构体
typedef struct IO_FILE{int fileno;int flag;//char inbuffer[SIZE];//输入缓冲区//int in_pos;char outbuffer[SIZE]; //输出缓冲区  //用一下这个int out_pos;//代表输出缓冲区被使用了多少//在系统看来这个buffer没有类型,由上层的printf、scanf自己去解释}_FILE_FILE *_fopen(const char*filename, const char *flag);//打开文件的名字  打开文件的模式
int _fwrite(_FILE *fp, const char *s, int len);
void _fclose(_FILE *fp);#endif

Mystdio.c

#include "Mystdio.h"
#inckude <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>#define FILE_MODE 0666  //权限_FILE *_fopen(const char*filename, const char *flag) //文件名称  打开模式
{//flag -> "w" "a" "r" 演示三种     assert(filename);assert(flag);int f = 0;int fd = -1;if(strcmp(flag, "w") == 0) {f = (O_CREAT|O_WRONLY|O_TRUNC);fd = open(filename, f, FILE_MODE);}else if(strcmp(flag, "a") == 0) {f = (O_CREAT|O_WRONLY|O_APPEND);fd = open(filename, f, FILE_MODE);}else if(strcmp(flag, "r") == 0) {f =  O_RDONLY;fd = open(filename, f);}else errno = 2; return NULL;if(fd == -1)   return NULL;//文件打开失败//文件打开成功//创建文件对象让别人用_FILE *fp = (FILE*)malloc(sizeof(_FILE));if(fp == nuLL)  return NULL;  //文件创建失败fp->fileno = fd;fp->flag = FLUSH_LINE;fp->out_pos = 0;return fp;
}int _fwrite(_FILE *fp, const char *s, int len)
{memcpy(&fp->outbuffer[fp->out_pos], s, len);  //将s复制到输出缓冲区里面fp->out_pos  += len;  //更新缓冲区    if(fp->flag & FLUSH_NOW) //立即刷新{write(fp->fileno, fp->outbuffer, len);//文件描述符  数据在哪里 要写入的字符的个数fp->out_pos = 0;//缓冲区刷新完之后清空pos}else if(fp->flag & FLUSH_LINE) {if(fp->outbuffer[fp->out_pos - 1] == '\n') {write(fp->fileno, fp->outbuffer, len);fp->out_pos = 0;}}else if(fp->flag & FLUSH_ALL){if(fp->out_pos ==SIAE){write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}}return len;
}_fflush(_FILE *fp)
{if(fp->out_pos > 0){write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}
}void _fclose(_FILE *fp)
{if(fp == NULL) return;//在关闭文件之前,需要强制对缓冲区中的数据进行刷新_fflush(fp);close(fp->fileno);free(fp);
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词