阿里做网站,带搜索的下拉框网站,如何建站,seo做的好的网站 知乎目录 一、C语言中的文件操作
二、系统文件操作I/O
三、文件描述符fd
1、文件描述符的引入
2、对fd的理解
3、文件描述符的分配规则
四、重定向
1、重定向的原理
2、重定向的系统调用dup2
五、Linux下一切皆文件 一、C语言中的文件操作
1、打开和关闭
在C语言的文…目录 一、C语言中的文件操作
二、系统文件操作I/O
三、文件描述符fd
1、文件描述符的引入
2、对fd的理解
3、文件描述符的分配规则
四、重定向
1、重定向的原理
2、重定向的系统调用dup2
五、Linux下一切皆文件 一、C语言中的文件操作
1、打开和关闭
在C语言的文件操作中我们要对一个文件进行写入和读写的前提是打开文件。我们使用fopen来打开文件打开失败将会返回NULL 而打开成功则返回文件的指针 FILE*。最后要进行的操作就是关闭fclose文件
函数原型FILE *fopen(const char *path, char *mode)。path为文件名也可以是文件路径mode为打开方式它们都是字符串。
int fclose(FILE *fp)。
下面我们来看一看下面的代码 上面的代码中我打开了一个文件log.txt。但是我的当前目录下并没有这个文件。 这个文件并不存在但是我们要使用那么fopen会在当前路径下给我们创建出这个文件。那么这个当前路径是什么呢
简单来说当前路径一个进程运行起来的时候每个进程都会去记录自己当前所处的工作路径。所以当前路径也就是当前进程的工作路径。
有了这个概念我们就能理解了test.c形成的可执行程序在运行后会成为一个进程该进程会通过调用系统接口帮助我们创建文件因此新文件所在的路径就是当前进程的工作路径。 第一个红色方框就是当前进程的工作路径exe就是当前的可执行文件。第二个红色方框就是表示执行的是进程工作路径下的那个可执行程序。
注单纯以w方式打开文件会自动清空文件原有的数据。r读写代表文件不存在则出错w读写代表文件不存在则创建。带有的表示读写。a代表向文件中追加内容。
2、读写文件
我们知道在C语言中我们可以通过fgets和fputs以字符串形式进行读写也可以通过fprint和fscanf进行格式化读写。下面的函数在C语言中我们已经学过了这里就不一一演示了。
int fputs (const char * str, FILE * stream ) char * fgets (char * str, int num, FILE * stream ) int fprintf (FILE * stream, const char * format, ... ) int fscanf (FILE * stream, const char * format, ... )
二、系统文件操作I/O
操作文件除了上述C接口当然C也有接口其他语言也有我们还可以采用系统接口来进行文件访问。其实真正能够直接访问文件的只有操作系统而各种编程语言能够访问文件的函数的本质都是去调用了操作系统提供的各种系统接口。
1、open
//头文件
#include sys/types.h
#include sys/stat.h
#include fcntl.hint 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 : 追加写
O_TRUNC打开时清空文件内容。
返回值成功新打开的文件的文件描述符 失败-1
~ 使用比特位传递选项
但是flags是一个整型他只表示一个参数那么我们是怎么通过flags传入多个参数呢这里我们使用了一种数据结构叫做比特位一个整数有32个比特位所以我们可以通过比特位来传递选项。
下面我们通过一个例子来看看是怎么实现的。 因此我们可以使用 | (或)来帮助我们传递多个参数以此实现不同的功能。
mode参数
如果你使用O_CREAT参数创建一个新的文件那么你还可以通过第三个参数mode来设置该文件的权限。 2、close
//所在头文件
#include unistd.h//原型
int close(int fd);
3、read和write
文件打开后我们就业对文件进行读取或者写入了。
write写入
#include unistd.hssize_t write(int fd, const void *buf, size_t count);
fd要写入的文件 buf要写入的内容 count所写内容的大小。
read读取
//头文件
#include unistd.hssize_t read(int fd, void *buf, size_t count);
fd要读取的文件 buf存放读取内容的数组 count读取的内容大小
三、文件描述符fd
我们通过上面的学习发现open的返回值是一个整型其实这个整型其实就是代表文件描述符fd。
1、文件描述符的引入
我们来看看下面的代码 运行结果如下 我们知道fd是一个整型了那么为什么是从3开始的呢那012跑哪里去了呢
在C语言阶段我们知道在程序运行时操作系统会默认打开三个标准输入输出流标准输入标准输出标准错误。对应到C语言当中就是stdin、stdout以及stderr。在C中则是cin、cout、cerr而在其他的语言中也有相应的输入输出流。
我们知道C语言中的stdin、stdout以及stderr这三个家伙实际上都是FILE*类型的并不是int类型。因为FILE*是一个结构体指针是C语言进行了封装的是为了方便用户使用。而在操作系统层面比如在Linux下只认fd而且只有操作系统才能直接访问文件那么各种语言为了既方便用户使用又要遵循操作系统的规则必定在FILE结构体里面封装了fd这样才能在系统层面去使用文件。
所有各种语言都有封装自己的输入输出流实际上这种特性并不是某种语言所特有的而是由操作系统所支持的。
那么说到这里我们已经有一点感觉了012哪去了呢会不会是分别代表着标准输入标准输出、标准错误呢答案是肯定的。在Linux下012就是表示这个意思。
那么我们也就能够理解了012所表示的文件操作系统已经帮我们打开了
下面通过代码来验证一下 所以说在系统层面我们只能用0123等整数来确定一个文件。
2、对fd的理解
进程想要访问文件那么要先打开文件。而文件是由进程运行时打开的一个进程可以打开多个文件而系统当中又存在大量进程也就是说在系统中任何时刻都可能存在大量已经打开的文件。所以操作系统务必要对这些已经打开的文件进行管理。那么怎么进行管理呢先描述再组织
操作系统会为每个已经打开的文件创建各自的struct file结构体其中包含了该文件几乎全部的内容然后将这些结构体以双链表的形式连接起来之后操作系统对文件的管理也就变成了对这张双链表的增删查改等操作。
所以为了区分被打开的文件各自属于那个进程操作系统必定会将进程与文件之间建立某种联系。
那么进程和文件是怎么建立联系的呢
首先我们来想一想fd为什么是连续的整数呢我们学过的知识中有什么是和连续的整数有关且从0开始的呢我们很容易就可以想到一个——数组的下标没错fd就是数组的下标那么是什么数组的下标呢
我们知道当一个程序运行起来时操作系统会将该程序的代码和数据加载到内存然后为其创建对应的task_structtask_struct中的一个指针变量指向该进程的mm_struct进程地址空间通过页表建立虚拟内存和物理内存之间的映射关系。
而task_struct当中有一个指针该指针指向一个名为files_struct的结构体在该结构体当中就有一个名为fd_array的指针数组该数组的下标就是我们所谓的文件描述符。
当进程打开一个文件时该文件从磁盘当中加载到内存形成对应的struct fileOS将该struct file连入文件双链表并将该结构体的首地址填入到fd_array数组当中下标为3的位置使得fd_array数组中下标为3的指针指向该struct file最后返回该文件的 fd 给进程。 所以我们只要有某一文件的文件描述符就可以找到该文件相关的内容进而对文件进行一系列输入输出操作。
3、文件描述符的分配规则
一般情况下操作系统默认为进程打开标准输入输出错误分别对应fd为012。所以012位置已经被占用了所以只能从3开始进行分配。之后打开的文件按顺序fd为345 ......
若我们在打开新的文件前先关闭文件描述符为0的文件此后文件描述符的分配又会是怎样的呢 结果如下 可以看到新打开的文件获取到的文件描述符变成了0。
我们再多打开几个文件 结果如下第一个打开的文件获取到的文件描述符变成了0而之后打开文件获取到的文件描述符还是从3开始依次递增的。 所以文件描述符是从最小但是没有被使用的fd_array数组下标开始进行分配的。
四、重定向
1、重定向的原理
~ 输出重定向 运行结果如下 根据运行结果我们发现printf函数本应该将结果输出到显示器标准输出让我们看见但是结果并没有在显示器上显示出来但是结果却被打印到了 log.txt 里面。 这就是我们所说的输出重定向。
输出重定向将我们本应该输出到一个文件的数据重定向输出到另一个文件中。
具体原理如下图 close的本质其实是将进程和文件的关联关系解除。close(1)就是将1位置的指针设成NULL但是语言层的 stdout或者cout等指向的是一个struct FILE类型的结构体结构体中存储文件描述符的变量的值仍然是1。
接着创建了一个新的文件log.txt从0开始遍历数组发现1位置为空所以将1位置的指针指向log.txt这就建立了新的关联关系。
所以当你使用C语言的printf向stdout写入的时候stdout的fd仍然是1但是底层的1位置已经指向log.txt了所以就写到了log.txt里面。
~ 追加重定向
追加重定向就是在输出重定向的基础上将“清空”的参数改成“追加”的参数。 2、重定向的系统调用dup2
上面是我们根据文件描述符的分配规则来进行重定向的。下面我们使用系统调用接口dup2来帮助我们实现重定向。 功能 dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中如果有必要的话我们需要先使用关闭文件描述符为newfd的文件。
返回值 dup2如果调用成功返回newfd否则返回-1。
我们使用下面的代码举例 五、Linux下一切皆文件
广义上的文件站在操作系统Linux的角度能够被input读取或者能够被output写出的设备就叫做文件。
所以显示器、键盘、网卡、显卡、磁盘等几乎所有的外设都可以称为文件。
在Linux下我们将文件分为1、内存文件文件已经打开已经加载到了内存中2、磁盘文件文件还没有被打开没有被加载到内存中。
那么Linux下一切皆文件具体是怎么体现的呢
首先Linux内核是用C语言写的。每个外设的硬件结构是不一样的那么我们通过操作系统访问外设的方式肯定是不一样的。但是操作系统仅仅通过提供四个系统调用openclosewriteread就可以帮助用户访问显示器、磁盘等文件。那么看似相同的方法是怎么访问不同的硬件设备的呢
我们在学习了C或者Java等能够面向对象的编程语言后我们知道可以使用类来描述一个文件然后使用多态达到使用相同接口而产生不同效果所以这些语言可以做到上面的事。可是Linux内核是使用C语言写的C语言可是没有面向对象的特点的也没有多态的概念那么它是怎么做到的呢
任何一个被打开的文件的有自己的结构体对象struct file{ //各种文件的属性 }不同的文件对应的读写方法不一样struct file对象里面可以有很多的*readp()、(*writep)()函数指针通过函数指针指向具体的读写方法。 这样用户就可以不用关心底层差别统一使用文件的接口方式进行文件操作。
所以在Linux下一切皆文件