您的位置:首页 > 新闻 > 会展 > 上海天华建筑设计有限公司官网_建筑工程有限公司_全国疫情突然又严重了_网站seo优化教程

上海天华建筑设计有限公司官网_建筑工程有限公司_全国疫情突然又严重了_网站seo优化教程

2025/3/10 12:43:05 来源:https://blog.csdn.net/m0_51952310/article/details/144450649  浏览:    关键词:上海天华建筑设计有限公司官网_建筑工程有限公司_全国疫情突然又严重了_网站seo优化教程
上海天华建筑设计有限公司官网_建筑工程有限公司_全国疫情突然又严重了_网站seo优化教程

前言:大佬写博客给别人看,菜鸟写博客给自己看。我是菜鸟

认知1:

stdin   → 标准输入 → 键盘文件

stdout → 标准输出 → 显示器文件

stderr  → 标准错误 → 显示器文件

:把显示器以及键盘看作是文件,广义的说:Linux下一切都是文件

1.系统接口的使用(open/close/write/read)

写一段简单的代码来实现一下:

  #include <stdio.h>                                                                                                                                                              #include <sys/types.h>#include <sys/stat.h>#include <sys/fcntl.h>#include <string.h>#include <stdlib.h>#include <unistd.h>int main(){int fd = open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);if(fd < 0){perror("open");exit(1);}const char* msg = "hello kivotos\n";write(fd,msg,strlen(msg));close(fd);return 0;}
  #include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/fcntl.h>#include <string.h>#include <stdlib.h>#include <unistd.h>int main(){int fd = open("log.txt",O_RDONLY);if(fd < 0){perror("open");exit(1);}// const char* msg = "hello kivotos\n";// write(fd,msg,strlen(msg));char buffer[64];int n = read(fd,buffer,sizeof(buffer)-1);if(n>0)                                                                                                                                                                     {buffer[n] = 0;printf("%s",buffer);}close(fd);return 0;}

int open(const char *pathname, int flags, mode_t mode);

pathname:任意路径

flag:常用的有以下几个

O_RDONLY  → 只读

O_WRONLY → 只写

O_RDWR     → 读写

O_CREAT    → 文件不存在则创造(若路径下文件不存在,则一定要加)

O_TRUNC   → 将文件中的内容删除并重写

O_APPEND → 在文件新的一行追加

mode:当创建文件时,必须要给文件赋予权限

返回值

成功时,会返回给一个大于2的数给fd(稍后说)

失败时,返回-1

注1:O_TRUNC和O_APPEND二者只能存在其中之一

注2:flag是通过位段操作来提供对应权限的(例如a的权限是001,b的权限是010,c的权限是100,那么a|b|c 就获得了abc三个权限,a|b就只获得了ab的权限)

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

fd:需要写入文件对应的fd

buf:buf指针,指向需要写入的数据,可以是任意类型,但通常是字符数组或字节数组

count:写入数据的字节数,不需要 +1 处理(这是写入到文件中,不需要预留\0)

返回值

成功时,返回写入的字节数大小

失败时:返回-1

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

fd:需要写读取文件对应的fd

buf:用于存储读取到的文件中的数据

count:所读取到的最大的字节数,需要做-1处理,给\0做预留

:当我们将数据写入到文件时,不要预留\0,因为文件没有这项规定,预留\0只会造成乱码后果,而当我们读取文件中的数据时,需要预留\0,因为这是C语言的语法规定,要给字符串预留\0的位置

close(int fd);

关闭指定文件

2.文件描述符(fd)

认知2:每一个文件都是通过进程打开的

用户通过系统接口创建进程(PCB)时,PCB中包含了一个指针,该指针指向struct files_struct(文件描述符表),该结构体中含有一个指针数组(struct file* fd_array[]),数组中不同的下标指向了不同的文件,这样就建立了映射关系。我们能够通过文件描述符找到对应的文件,再将文件缓冲区的内容拷贝出来。因此有:文件描述符  =  数组下标。系统也是通过上述方法来确定文件是由哪个进程打开的

注1:对于冯偌伊曼体系而言,文件内容是保存在磁盘上的,因此对文件内容做任何操作都必须先把文件加载(从磁盘到内存的拷贝)到内核对应的文件缓冲区当中(从磁盘拷贝到内存中,再交给CPU处理)

注2:文件(file)的属性和数据不是直接存储在file中,而是通过一个指针指向一个叫inode的数据结构中,该数据结构用于保存文件的属性,而对于数据而言,存储在内核文件缓冲区

注3:file是一个结构体,内部包含fd,以及缓冲区,当打开一个文件时,内部会自动开辟一块空间,同时提供相应fd和缓冲区

接下来我们看这样一段代码:

int main()
{int fd1 = open("log1.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);int fd2 = open("log2.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);int fd3 = open("log3.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);printf("%d\n",fd1);printf("%d\n",fd2);printf("%d\n",fd3);return 0;
}

他的运行结果为:

:0、1、2去哪了?

:对于操作系统而言,下标0 → 标准输入;下标1 → 标准输出;下标2 → 标准错误 

正如我们在认知1中所说的,对应计算器而言,标准输入对应键盘,标准输出和标准错误对应显示器。

注:需要注意的是,下标是不变的,也就是说,0、1、2永远对应标准输入、输出、错误,

但是下标对应的文件是可以改变的,也就是说,我们可以将原先1指向显示器文件,改变为指向我们自己写的文件,这就是重定向。

3.重定向

重定向的分配原则:系统会把下标最小的且没被使用的下标作为新的fd分配给新的文件

int dup2(int oldfd, int newfd);用于重定向的函数

newfd 将被 oldfd 覆盖,如果oldfd无用,也可以通过 close 删除 oldfd

我们来看看以下代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>int main()
{         int fd = open("log1.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);dup2(fd,1);                                                 printf("hello kivotos\n");                                  return 0;                 
}                                                               

上面提过,1对应的标准输出,而对于计算器而言,默认情况下,他的标准输出(下标1)对应的文件为显示器文件,当我们执行dup2(fd,1);时,此时标准输出指向了我们自己写的文件,而printf()函数只认下标,不认你是不是显示器,所以当我们执行 printf()函数时,显示器没有任何输出,而当我们 cat log.txt 我们所创建的文件时,能够发现以下结果:

这就是所谓的输出重定向。

我们还可以实现输入重定向,代码如下:

int main()
{int fd = open("log.txt",O_RDONLY);if(fd < 0){perror("open");exit(1);}char buffer[64];dup2(fd,0);while(1){if(!fgets(buffer,sizeof(buffer),stdin)) break;printf("%s",buffer);}return 0;
}

可以看到代码中,我们从指定输入流读取数据,转变为从自己写的文件中读取数据并打印。

复习

char *fgets(char *str, int n, FILE *stream);

str:用于存储文件流中读取的字符串

n:读取的最大字符数,包含\0,在末尾会自动添加0

stream:指定输入流

注:

问:原先1(标准输出)指向显示器文件,当我们通过输出重定向,使得1指向别的文件时,此时显示器文件会发生什么?

答:一个文件可以被多个进程打开,当我们重定向了一个,还有其他进程也会打开当前文件,文件中一个计数器,专门用于记录当前有多少个进程打开了该文件,当计数器为0时,此时文件才会被释放掉空间。

4.操作系统对外设的访问

:不同的外设都可以抽象成文件,例如显示器文件、键盘文件、磁盘文件等,那么操作系统是如何对不同文件进行访问的?

:上面我们提到,用户创建进程会生成PCB,PCB内部包含一个指针指向文件描述符表,文件描述符表中又包含了一个指针数组,不同的下标对应不同的文件,其中这每个文件又是一个结构体每个结构体包含了指向文件属性的指针,以及指向内核文件缓冲区的指针,同时还包含了两个函数指针void(*read)(int fd,char* , int ); void(*write)(int fd,char* , int );

通过这两个指针可以找到对应外设的读写方法,从而实现操作系统对外设的访问,大致结构入下图

认知3:struct file 以及 struct file_operation被称为虚拟文件系统(VFS),这与虚拟地址空间有着一定的类似之处,上层与下层建立映射关系,操作系统通过这种映射关系,再通过相应的读写方式进行上层对下层的访问。

认知4:对于计算机世界,任何问题都可以通过增加一层软件层来解决,越靠近上次越抽象。

想想刚才所讲的虚拟文件层,如果没有这一层,那么需要多写多少个if else 语句?大佬加了VFS一层,从而达到了屏蔽底层差异的作用。

:怎么个屏蔽法?

:每一个设备都对应有一个 struct device的结构体,内部包含了设备的所有属性以及数据,但是无论你底层硬件的数据是怎样的,对于操作系统而言,我只需要通过文件描述符以及驱动程序跟你建立起了映射关系后,通过调用void(*read)(int fd,char* , int ); void(*write)(int fd,char* , int ); 这两个函数指针,就能够访问对应的外设。

5.缓冲区

缓冲区分为语言层缓冲区文件内核缓冲区,我们口中常说的缓冲区其实是语言层缓冲区。对于文件而言,文件有着自己的文件内核缓冲区,而对于用户级语言(printf/fprintf/fputs/fwirte)有着语言层缓冲区。对于语言层缓冲区而言需要在文件关闭前,通过fd+系统调用(例如write),或者通过强制刷新(fflush(stdout))进程退出或是刷新条件满足,才能正常显示结果。

针对刷新条件满足做三点补充

1.立即刷新 ——无缓冲

2.缓冲区写满——全缓冲,一般用于普通文件

3.行刷新——行缓冲,一般用于显示器

语言层缓冲区的目的:减少系统调用,系统调用是有成本的,如果没有语言缓冲区,十行语句要通过十次系统调用,而有了语言缓冲区,可以先将这十行语句拷贝到语言缓冲区中,当满足一定刷新条件时,再通过系统调用,将其拷贝到文件内核缓冲区中,以达到减小成本的目的。

以显示器为例(此时的fd就是1):用户想把"aaaa"字符串打印到显示器上,可以直接通过系统调用(write)来实现,但是一旦系统调用次数过多,会增加成本,所以得通过语言层缓冲区。具体过程是,通过(printf/fprintf/fputs/fwirte)函数,将内容拷贝到语言层缓冲区,再通过fd+系统调用,例如write(),系统通过fd找到对应文件内核缓冲区,将语言层缓冲区中的内容拷贝到文件内核缓冲区实现结果。

接下来我们看这样一串代码,如下图:

#include <iostream>                  
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <stdio.h>
int main()
{printf("hello\n");fprintf(stdout,"kivotos\n");const char* msg = "world\n";fwrite(msg,strlen(msg),1,stdout);const char* msg2 ="Sensei\n";write(1,msg2,strlen(msg2));fork();return 0;
}

当我们执行分别执行下述指令时,会得到两个完全不同的结果,如下图:

:为什么右图比左图多了三行输出?

:在分析左图和右图之前,需要先知道一个概念,那就是对于用户级语言(printf/fprintf/fputs/fwirte),当他们没有被刷新时(或者说被系统调用时),他们会暂存在语言层缓冲区中,而子进程同样会继承语言层缓冲区。但对于系统级函数write而言,因为他是直接拷贝到文件内核缓冲区中,所以会立即刷新,子进程无法继承,因此右图我们能够看到七条语句。左图是将结果打印在显示器上,而显示器属于行刷新,只要有\n就会刷新,因此子进程创建之前,所有语言层缓冲区中的内容都已刷新完毕,因此只有四条语句。

不妨我们验证一下,如下代码:

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

结果如下图:

可以看到,当我们不打印\0时,显示器只显示两条,而当我们打印\0时,显示器上显示了三条。

补充:

重定向会改变刷新方式

版权声明:

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

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