潍坊网站建设wfxtseo,接外贸单的平台有哪些,北京网站建设华大,网站开发需要什么基础知识理解 文件
狭义理解
文件在磁盘里 磁盘是永久性存储介质#xff0c;因此文件在磁盘上的存储是永久性的 磁盘是外设#xff08;即是输出设备也是输入设备#xff09; 磁盘上的文件 本质是对文件的所有操作#xff0c;都是对外设的输入和输出 简称 IO
广义理解…理解 文件
狭义理解
文件在磁盘里 磁盘是永久性存储介质因此文件在磁盘上的存储是永久性的 磁盘是外设即是输出设备也是输入设备 磁盘上的文件 本质是对文件的所有操作都是对外设的输入和输出 简称 IO
广义理解
Linux 下一切皆文件键盘、显示器、网卡、磁盘…… 这些都是抽象化的过程
文件操作的归类认知
对于 0KB 的空文件是占用磁盘空间的 文件是文件属性元数据和文件内容的集合文件 属性元数据 内容 所有的文件操作本质是文件内容操作和文件属性操作
系统角度
对文件的操作本质是进程对文件的操作 磁盘的管理者是操作系统 文件的读写本质不是通过 C 语言 / C 的库函数来操作的这些库函数只是为用户提供方便而是通过文件相关的系统调用接口来实现的
C语言的fopenfwritefread是库函数并不是系统调用这三个接口都是封装了操作系统底层的系统调用
回顾C文件接口
打开文件
#include stdio.h
int main()
{FILE *fp fopen(myfile, w);if(!fp){printf(fopen error!\n);}while(1);fclose(fp);return 0;
}写文件
#include stdio.h
#include string.h
int main()
{FILE *fp fopen(myfile, w);if(!fp){printf(fopen error!\n);}const char *msg hello bit!\n;int count 5;while(count--){fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}读文件
#include stdio.h
#include string.h
int main()
{FILE *fp fopen(myfile, r);if(!fp){printf(fopen error!\n);return 1;}char buf[1024];const char *msg hello bit!\n;while(1){//注意返回值和参数此处有坑仔细查看man⼿册关于该函数的说明 ssize_t s fread(buf, 1, strlen(msg), fp);if(s 0){buf[s] 0;printf(%s, buf);}if(feof(fp)){break;}}fclose(fp);return 0;
}管理文件
一个进程可以打开多个文件所以系统中存在大量被打开或正要关闭的文件所以操作系统需要管理文件先描述再组织
操作系统通过一个结构体struct file来管理文件在文件被打开的时候就会创建一个struct file其中存放着文件的各种属性结构体之间互相链接形成链表所以操作系统对文件的管理就转换成了对链表的管理
task_struct中不止存在页表还存在着一个文件描述符表struct files_struct其中存放着一个 struct file数组数组的每一个的下标都链接着一个文件的地址系统就通过数组来管理每一个进程打开的文件。
相对应一个文件也可以被多个进程打开那么关闭文件是否会对进程造成影响毕竟进程是具有独立性的而struct file使用和智能指针类似的做法就是引用计数只有计数为0的时候才会真正关闭文件
文件的内容会加载到文件缓冲区文件的属性会加载到struct file
系统文件 I/O
初识标志位
打开文件的方式不仅仅是 fopenifstream 等流式语言层的方案其实系统才是打开文件最底层的方案。不过在学习系统文件 IO 之前先要了解下如何给函数传递标志位该方法在系统文件 IO 接口中会使用到
#include stdio.h
#define ONE 0001 //0000 0001
#define TWO 0002 //0000 0010
#define THREE 0004 //0000 0100
void func(int flags) {if (flags ONE) printf(flags has ONE! );if (flags TWO) printf(flags has TWO! );if (flags THREE) printf(flags has THREE! );printf(\n);
}
int main() {func(ONE);func(THREE);func(ONE | TWO);func(ONE | THREE | TWO);return 0;
}通过这个代码能够了解标志位的作用
状态表示每个标志位都能独立地表示一种状态。在这个例子中ONE、TWO 和 THREE 分别代表不同的状态。
状态组合通过按位或运算符 | 可以把多个标志位组合起来以此表示多种状态的组合。
状态检查借助按位与运算符 能够检查某个标志位是否被设置从而依据不同的状态执行不同的操作。
系统调用读文件
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.h
int main()
{int fd open(myfile, O_RDONLY);if(fd 0){perror(open);return 1;}const char *msg hello bit!\n;char buf[1024];while(1){ssize_t s read(fd, buf, strlen(msg));//类⽐write if(s 0){printf(%s, buf);}else{break;}}close(fd);return 0;
}系统调用写文件
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.h
int main()
{umask(0);int fd open(myfile, O_WRONLY|O_CREAT, 0644);if(fd 0){perror(open);return 1;}int count 5;const char *msg hello bit!\n;int len strlen(msg);while(count--){write(fd, msg, len);//fd: 后⾯讲 msg缓冲区⾸地址 len: 本次读取期望写⼊多少个字节的数据。 返回值实际写了多少字节数据 }close(fd);return 0;
}接口认识
open
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
第一个参数是文件第二个参数是标志位 第三个参数表示新创建文件的权限如果新创建的文件不带mode会造成新创建的文件权限位乱码
通过使用标志位用偶有效减少传参的个数
open标志位介绍
O_RDONLY以只读模式打开文件。文件只能被读取不能进行写入操作 O_WRONLY以只写模式打开文件。文件只能被写入不能进行读取操作 O_RDWR以读写模式打开文件。文件既可以被读取也可以被写入 O_CREAT如果文件不存在则创建该文件。使用此标志位时需要提供第三个参数 mode 来指定新文件的权限。 O_EXCL和 O_CREAT 一起使用时如果文件已经存在则 open 调用会失败并返回 -1同时将 errno 设置为 EEXIST。这可用于确保文件是新创建的。 O_TRUNC如果文件存在且以可写模式打开则将文件长度截断为零即清空文件内容 O_APPEND以追加模式打开文件。每次写操作都会将数据追加到文件末尾而不是覆盖原有内容 O_NONBLOCK以非阻塞模式打开文件。在进行读写操作时如果没有数据可读或无法立即写入数据函数不会阻塞而是立即返回 -1同时将 errno 设置为 EAGAIN 或 EWOULDBLOCK。这在处理多个文件描述符或需要异步操作时非常有用
而标志位的组合使用|
write
ssize_t write(int fd, const void *buf, size_t count);
第一个参数是文件操作符第二个参数是写入文件的内容第三个参数是需要写入的字节数
read
ssize_t read(int fd, void *buf, size_t count);
第一个参数是文件操作符第二个参数是存放读到的数据第三个参数是读入的字节数
文件操作符
writeread的第一个参数都是fdfd就是文件操作符
操作系统只认fd那么fd是什么呢在文件描述符表中存在struct file数组fd就是数组的下标。
当我们打开多个文件的时候查看他们的fd会发现fd是从3开始的。这是由于数组中默认的012号下标分别是标准输入标准输出标准错误。
fd的数据是从小到大的只要前面有多余的位置系统就会将文件分配到靠前的下标
open的时候就会找到进程的文件描述符表中的数组找到一个未被使用过的位置将struct file的地址填入其中fd就是数组的下标
read就是用fd找到数组的下标访问对应文件的地址从文件缓冲区中将数据拷贝到buffer文件缓冲区的数据是由磁盘中文件的内容预加载到缓冲区中。
重定向
Linux中存在函数dup2,用于复制文件描述符
int dup2(int oldfd, int newfd);
oldfd需要被复制的源文件描述符。 newfd复制到的目标文件描述符。如果newfd已经打开会先将其关闭。
使用dup2会将newfd位置的文件覆盖到oldfd上
可以通过dup2来替换标准输入标准输出来实现重定向操作。
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#includeunistd.h
int main()
{int fd open(test.txt, O_RDWR);dup2(fd,1);close(fd);printf(fd: %d\n, fd);return 0;
}
当我们将文件夹中的test.txt替换掉标准输出此时使用printf 则不会将内容输出到显示器上而是将内容输出到test.txt中
操作系统只认识fdprintf就是为了往stdout中输出内容但是stdout被关闭此时fd为1的文件是log.txtprintf输出的内容就会输出到log.txt中
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#includeunistd.h
int main()
{int fd open(test.txt, O_RDWR);dup2(fd,0);close(fd);char s[100];scanf(%s,s);printf(%s\n,s);return 0;
}将test.txt文件替换标准输入文件此时scanf一般是从键盘上读取数据此时就只能从test.txt中读取数据将test.txt的数据填写到s中。
操作系统只认fdscanf就是从键盘中读取数据但是标准输入被关闭此时fd为0的文件是test.txt此时scanf就会从 test.txt中读取数据 通过上面两端代码可以大致了解重定向的原理就是将文件进行替换从而将一下本该输出到显示屏中的数据输出到其他文件中将从键盘中读取的数据转换成从其他文件中读取数据
正确的重定向操作方法
例如存在一个文件log.txt需要将原本输出到显示屏的数据输出到log.txt中
ls -l 1 log.txt
将原本输出到1号文件的内容输出到log.txt中 而1号文件就是stdout
#includecstdio
#includeiostream
using namespace std;
int main()
{couthellocoutendl;cerrhellocerrendl;return 0;
}当我们使用这段代码重定向到一个文件时cerr的内容并不会重定向到文件而是输出在显示屏上这是由于cerr对应的文件时stderr它的fd为2而默认的重定向只有1
所以可以通过将1重定向到log.txt2追加重定向到log.txt
./a 1 log.txt 2 log.txt
此时cout和cerr的内容就都输出到log.txt中了
也可以将标准错入重定向到标准输出的目标位置1 表示「当前标准输出的目标位置」
./a 1 log.txt 21
效果与第一种一样
理解“一切皆文件”
首先在 Windows 中是文件的东西它们在 Linux 中也是文件其次一些在 Windows 中不是文件的东西比如进程、磁盘、显示器、键盘这样硬件设备也被抽象成了文件你可以使用访问文件的方法访问它们获得信息甚至管道也是文件将来我们要学习网络编程中的 socket套接字这样的东西使用的接口跟文件接口也是一致的。
这样做最明显的好处是开发者仅需要使用一套 API 和开发工具即可调取 Linux 系统中绝大部分的资源。举个简单的例子Linux 中几乎所有读读文件读系统状态读 PIPE的操作都可以用 read 函数来进行几乎所有更改更改文件更改系统参数写 PIPE的操作都可以用 write 函数来进行。
在操作系统中每个外设都有一套属于自己的读写操作。操作系统中的struct file的内容中存在函数指针他们会指向对应外设的读写操作而每个函数指针的命名参数都一样
一切皆文件是进程认为一切皆文件进程中存储着文件描述符表文件描述符表指向struct file从硬件角度上来看struct file的函数指针指向硬件的读写操作所以管理好文件也就能管理好硬件进程无需接触到硬件只需接触到struct file即可 上图中的外设每个设备都可以有自己的 read、write但一定是对应着不同的操作方法但通过struct file 下 file_operation 中的各种函数回调让我们开发者只用 file 便可调取 Linux 系统中绝大部分的资源这便是 “linux 下一切皆文件” 的核心理解。
缓冲区
什么是缓冲区
缓冲区就如同日常中的快递站可以帮助我们在空闲时间再去拿快递而非在快递送到家门口的时候无论在做任何事情都要回去拿快递这样也严重影响了快递员的效率
缓冲区是内存空间的一部分。也就是说在内存空间中预留了一定的存储空间这些存储空间用来缓冲输入或输出的数据这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备分为输入缓冲区和输出缓冲区。
可以查看Linux中的FILE结构体 它的指针就指向了缓冲区的位置
为什么要引入缓冲区机制
系统调用也是有成本的系统调用是需要操作系统进行操作的而操作系统平时需要进行大量的资源管理如果没有缓冲区当我们进行文件写入时操作系统无论做什么事情都需要停下来进行写入操作
读写文件时如果不会开辟对文件操作的缓冲区直接通过系统调用对磁盘进行操作 (读、写等)那么每次对文件进行一次读写操作时都需要使用读写系统调用处理此操作即需要执行一次系统调用执行一次系统调用将涉及到 CPU 状态的切换即从用户空间切换到内核空间实现进程上下文的切换这将损耗一定的 CPU 时间频繁的磁盘访问对程序的执行效率造成很大的影响。
为了减少使用系统调用的次数提高效率我们就可以采用缓冲机制。比如我们从磁盘里取信息可以在磁盘文件进行操作时可以一次从文件中读出大量的数据到缓冲区中以后对这部分的访问就不需要再使用系统调用了等缓冲区的数据取完后再去磁盘中读取这样就可以减少磁盘的读写次数再加上计算机对缓冲区的操作 快于对磁盘的操作故应用缓冲区可 大提高计算机的运行速度。
又如我们使用打印机打印文档由于打印机的打印速度相对较慢我们先把文档输出到打印机相应的缓冲区打印机再自行逐步打印这时我们的 CPU 可以处理别的事情。可以看出缓冲区就是一块内存区它用在输入输出设备和 CPU 之间用来缓存数据。它使得低速的输入输出设备和高速的 CPU 能够协调工作避免低速的输入输出设备占用 CPU解放出 CPU使其能够高效率工作。
缓冲类型
标准 I/O 提供了 3 种类型的缓冲区。
・全缓冲区这种缓冲方式要求填满整个缓冲区后才进行 I/O 系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。
・行缓冲区在行缓冲情况下当在输入和输出中遇到换行符时标准 I/O 库函数将会执行系统调用操作。当所操作的流涉及一个终端时例如标准输入和标准输出使用行缓冲方式。因为标准 I/O 库每行的缓冲区长度是固定的所以只要填满了缓冲区即使还没有遇到换行符也会执行 I/O 系统调用操作默认行缓冲区的大小为 1024。
・无缓冲区无缓冲区是指标准 I/O 库不对字符进行缓存直接调用系统调用。标准出错流 stderr 通常是不带缓冲区的这使得出错信息能够尽快地显示出来。
缓冲区的工作
当使用printf/fprintf/fwrite等等的库函数进行文件操作的时候数据并非直接写入到文件内核缓冲区中c标准库中存在一个库缓冲区使用库函数进行操作的时候数据会先占时写入到库缓冲区中直到某种条件的触发会将fopen返回值中的fd找出来通过fd进行系统调用将库缓冲区的内容写到文件内核缓冲区中。
刷新缓冲区的条件
1.强制刷新
2.进程退出
3.刷新条件满足-全缓冲行缓冲无缓冲