个人主页~
文件管理
- 一、回顾C文件管理
- 二、系统文件IO
- 0、序
- 1、open函数
- flags标识位的方法
- 2、文件描述符fd
- 3、重定向
- (一)dup2
- (二)标准输出vs标准错误
一、回顾C文件管理
有关于c文件操作的详细内容可阅拙作《C语言文件操作》
特别指出的是,文件写函数我们重点要讨论,关于文件读的函数我们会用就行,因为没有什么其他可以谈的
关于fopen("text.txt","w");
和fwrite(message,strlen(message),1,fp);
的使用,文件的写
关于fread(buf,1,strlen(message),fp);
的使用,文件的读
还有一个我们要注意的是,使用C的时候,我们的文件默认会打开三个流,分别是stdin标准输入流、stdout标准输出流、stderr标准错误流
二、系统文件IO
0、序
我们已经学完了Linux三大金刚之一的进程,现在我们来学习同为Linux三大金刚之一的文件系统相关的内容,我们知道,进程的管理是先描述后组织,我们推己及人一下,这里对于文件系统的管理当然也是先描述后组织,我们也有一个结构体来管理文件
1、open函数
open函数有两个版本,上面那个和下面这个差了一个参数,我们主要讲下面这个版本
int open(const char *pathname, int flags, mode_t mode);
//pathname:打开或创建的目标文件名//flags:打开文件时的参数(前三个必须要选一个)
//O_RDONLY: 只读打开
//O_WRONLY: 只写打开
//O_RDWR : 读,写打开//O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
//O_APPEND: 追加写
//O_TRUNC : 如果文件已经存在,并且以可写模式打开,则将文件长度截断为 0,即清空文件内容//mode:设定新文件的访问权限//返回值:成功为新打开的文件描述符fd,失败为-1
一般来说,如果我们使用open的时候新创建文件就要用下面那个open,如果我们对已有文件进行打开,那么我们用上面那个open
下面我们来使用一下open系统调用,以只写模式并且在目标文件没有的情况下创建一个myfile文件,文件权限为0666,然后定义一个字符串message,使用write(fd,message,len);
作用是将message中的len个字节写到fd所指向的对象,也就是刚刚open的目标文件
myfile文件被创建并写入
这里新创建的文件权限为0664,因为我们对应的文件掩码umask为2
这里我们在C语言文件中的f开头的函数,例如fopen啦、fwrite啦其实都是对系统调用open、write等的封装
flags标识位的方法
这里的flags标识位用的是二进制标识,这是我们常用的一种标识方式,通过按位或 | 的方式来进行组合
标志位 | 八进制值 | 二进制表示(假设为 16 位整数) | 含义 |
---|---|---|---|
O_RDONLY | 00 | 0000 0000 0000 0000 | 只读模式 |
O_WRONLY | 01 | 0000 0000 0000 0001 | 只写模式 |
O_RDWR | 02 | 0000 0000 0000 0010 | 读写模式 |
O_CREAT | 0100 | 0000 0001 0000 0000 | 如果文件不存在则创建 |
O_EXCL | 0200 | 0000 0010 0000 0000 | 与 O_CREAT 联用,若文件已存在则打开失败 |
O_TRUNC | 01000 | 0000 1000 0000 0000 | 若文件存在则截断为长度 0 |
O_APPEND | 02000 | 0001 0000 0000 0000 | 追加模式 |
O_NONBLOCK | 04000 | 0010 0000 0000 0000 | 非阻塞模式 |
这样我们可以看到,每个比特位代表着一个状态,假设二进制表示为16位整数后四位代表着文件读写状态,前八位表示这文件打开的附加选项
通过按位或 | ,我们可以将我们需要的选项全部搞到一个数字上,例如我们open("myfile", O_WRONLY|O_CREAT, 0666);
这里的flags==0000 0001 0000 0001
2、文件描述符fd
我们将收到的文件描述符全部打印看一下效果
我们发现这好像有点顺序啊,只要open成功打开,fd就必然是大于等于0的数字,我们第一个打开的文件是3,第二个是4,那么我们的0,1,2去哪了?
其实我们的0就是标准输入,1标准输出,2标准错误,对应的硬件设备分别是0为键盘,1,2为显式屏,举一个简单的例子,我们在打开电脑的时候Windows操作系统是不是先给我们连上键盘和显示屏呢
我们程序访问文件,实际上就是进程访问文件,我们的进程PCB有指向files_struct的指针*files,而我们的files_struct中有一个数组fd_array用来存放我们的文件描述符,其中012默认调用的,其他的3456指向我们的新打开的各个文件,本质上文件描述符就是该数组的下标
我们进程在进行open系统调用的时候,就会找到files指针然后拿到我们对应的fd_array的位置然后默认打开012,然后打开我们的myfile1等我们的文件
打开的文件会在内核创建一个file对象,存储比如文件的读写位置,文件的访问模式,文件操作函数指针,对底层文件系统或设备的引用等关键信息,这里说明一下,open函数的核心任务之一就是将用户指定的访问模式写入内核的file结构体
文件描述符的分配原则:在fd_array中找到一个没有被使用的最小下标,比如我们打开fd为3,4,5的三个文件,我们将fd为4的文件关闭后新建文件打开,此时这个新建的被打开打开的文件的fd为4
3、重定向
我们说fd==1对应的是标准输出,也就是屏幕,那么我们将这个标准输出关掉,会发生什么呢
我们发现,这里标准输出被关掉了,程序的打印果然没有打印到屏幕上,并且我们都fflush刷新缓冲区了,说明打印的信息也不再缓冲区里,而且我们发现,这个程序如果不关闭1的话,打印到文件中的信息应该是打印在屏幕上的,为啥打印到文件中了呢?
我们在前面文件描述符的分配原则中说过,新文件会被整个数组中一个最小的fd指向,close就是将1置为NULL,然后open函数被调用,mytext自然的被分配给了1,此时fd==1,打印出来的fd自然也是1,不过是打印到了mytext文件中,这就是输出重定向
当然这是被动的重定向,如果我们不想去关掉标准输出,又想进行重定向呢
通过系统调用接口dup2就可以实现
(一)dup2
#include <unistd.h>int dup2(int oldfd, int newfd);
//你可以把比作一个复制函数
//这个函数被复制的是oldfd,最后两个值都是oldfd
我们逐句分析,先open mywork,此时0->标准输入,1->标准输出,2->标准错误,3->mywork,然后将1关掉就是0->标准输入,1->NULL,2->标准错误,3->mywork,然后使用dup2将3复制到1,就是0->标准输入,1->mywork,2->标准错误,3->mywork,此时不管是用printf函数还是fprintf函数,所打印的内容全部都会打印到mywork文件当中
这里有几个值得注意的地方:
①dup2的使用,是将3位置的指针拷贝到1位置,让1和3共同指向mywork
②printf是C库当中的IO函数,一般往stdout中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfile的地址,不再是显示器文件的地址
③由②引申出来的:显示屏也是文件,Linux下一切皆文件
(二)标准输出vs标准错误
通过学习上面的内容我们知道,标准输出和标准错误都可以将信息打印到屏幕上,它们俩有什么区别呢?答案就是除了默认情况下,它们的fd值不同,其他都一样
它们两个都指向显示器,只是我们比较习惯用两个支流,一个用来正常工作,另一个用来打印错误信息所以他们本质上是没有什么区别的
看看小区别
这里的命令应该比较容易看懂,就是打印到stdout流的也就是1中的内容打印到normal.txt文件当中,打印到stderr流的也就是2中的内容打印到err.txt当中
我们分解开来看这条命令:
./myproc
这部分表示执行当前目录下名为 myproc 的可执行程序,在 Linux 中,当前目录并不在默认的可执行文件搜索路径中,所以如果要执行当前目录下的程序,需要显式地使用 ./ 来指定程序所在的路径
1>all.txt
重定向符号 >:> 是输出重定向符号,它的作用是将命令的输出内容从默认的标准输出重定向到指定的文件中,1> 明确表示将标准输出进行重定向,重定向的目标文件为 all.txt,如果该文件不存在,系统会自动创建它;如果文件已经存在,其原有内容会被新的输出内容覆盖
2>&1
重定向符号 &>:& 在这里是一个特殊的符号,用于引用文件描述符,2>&1 的意思是将标准错误输出重定向到和标准输出相同的地方,结合前面的 1>all.txt,就是把标准错误输出也重定向到 all.txt 文件中
这里先输出标准错误信息的原因是:标准错误一般输出没有缓冲区,而标准输出是有缓冲区的,而它们并没有谁先谁后的定性要求,所以标准输出搁缓冲区里缓冲了一下当然要稍微慢一点了
今日分享就到这里啦~