Linux:关于FILE那些事
流和FILE对象
对于标准I/O库来说,它们的操作基于flow而非传统的文件对象。当我们打开或创建一个文件时,默认使该flow与该文件关联起来。flow的方向决定了读写操作涉及的是单字节还是多字节(宽字符)操作。
为了重置flow的方向性状态,请使用freopen函数;如果需要设置宽字符模式,则应使用fwide函数。
每个ANSI C程序运行时,默认提供三个特定flow:stdin、stdout及stderr。这些flows都是指向FILE结构的指针。
#include<stdio.h>
#include<wchar.h>
int fwide(FILE *fp,int mode);
若流是宽定向返回正值,若流是字节定向返回负值,若流是未定向的返回0
fd与FILE结构体
此外,在Linux系统中,默认情况下每个进程会开启3个默认文件描述符:分别为标准输入(通常指键盘)、标准输出以及标准错误(通常指向屏幕或终端)。这些默认设置有助于确保基本I/O操作能够得到顺畅执行。通常指键盘;而通过管道或其他连接机制,则可以让这些信息流向其他程序以便进一步处理。

当我们将一个文件打开时,在内存中创建相应的数据结构用于表示操作系统。这些数据结构包括名为file的结构体,并且它代表了已连接到内存中的文件对象。在内核态下,进程通过执行open系统调用来打开文件,并且必须将进程与这些文件建立关联关系。每个进程都拥有一个指针变量files,并且这个变量指向了一张名为files_struct的数据表。其中最为关键的部分是一个包含多个指针数组的数据块:每个元素都指向一个已打开的文件描述符!因此说,在这种情况下我们就可以通过使用特定的数据块索引来快速定位对应的已打开文件。
[a@localhost ~]$ vim fd.c
[a@localhost ~]$ cat fd.c
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd = open("myfile",O_RDONLY);
if(fd<0){
perror("open");
return 1;
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
[a@localhost ~]$ gcc fd.c
[a@localhost ~]$ ./a.out
fd:3
稍作改动:关闭0
[a@localhost ~]$ vim fd.c
[a@localhost ~]$ cat fd.c
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile",O_RDONLY);
if(fd<0){
perror("open");
return 1;
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
[a@localhost ~]$ gcc fd.c
[a@localhost ~]$ ./a.out
fd:0
不言而喻的是说,在file_struct数组中确定文件描述符的分配规则:即从file_struct数组中找到当前未被占用的最小可用索引位置,并将其设为新的文件描述符。
重定向
本质:

所有被1号文件描述符写的字符都被移动至myfile中,则不会出现在标准输出上。
输入重定向采用的方式是:将输入来源改为文件或外部设备读取。
“<”标识的是单个字符输入重定向运算符,“<<”则指示当前的标准输入来源于命令行控制台的一对分隔符之间的区域。
eg:
[root@localhost a]# wc</etc/inittab
26 149 884
wc统计行数,单词数,字符数
[root@localhost a]# wc<<aa
> 1
> 11
> 111
> 1111
> aa
4 4 14
输出重定向:避免直接连接至终端界面,默认采用文件存储机制处理信息流。“>”标识用于配置输出重定向功能,“若文件路径不存在则会自动创建相应目录结构”。当执行操作时,“>”标志下的设置将被应用并执行指定操作。
“>>”标志则用于将后续的操作结果附加到现有记录后端。若已有相关数据存档,则新数据将被追加至原有数据集末尾;若无对应存档,则创建新的数据条目并开始记录。
[root@localhost a]# ps -ef>a.txt
[root@localhost a]# ps -ef>>a.txt//把进程信息追加到a.txt的文件中去
典例分析:
(1)a.out > outfile 2 > &1
首先将 a.out 转向 outfile 文件。接着将 2 > &1 的 错误信息 发送给 标准输出 ,随后 标准输出 已经接收了这个信息。
(2)a.out2 > &1 > outfile
首先将所有异常信息从错误流转移到了标准流中,在此状态下该流程的标准流仍在当前终端窗口显示。随后将该信息转导至 outfile 流域,并确保在此过程中 terminal 端口仍持续接收相关信息。
缓冲
标准I/O库提供缓冲的目的是尽可能减少使用read和write调用次数。
(1)全缓冲 :在填满标准I/O缓冲区后才进行的实际I/O操作。
flash:在标准I/O方面,flush意味着将缓冲区中的内容写到磁盘上。在终端驱动程序方面,flush丢弃已存储在缓冲区中的数据。
任何时候,我们都可强制冲洗一个流。
#include<stdio.h>
int fflush(FILE *fp);
(2)换行处理机制:每当输入或输出遇到换行符时,标准I/O库会自动处理相应的I/O操作。
这种机制允许逐个字符发送到输出端,在完成一行的输入后才会执行实际的I/O操作以发送该行内容到外设或存储设备中。
(3)未实现缓存机制的标准I/O库:为了提高效率或避免资源浪费,在某些情况下会暂时不将输入数据存储起来直接发送出去以减少延迟或占用内存空间。(同样具备这一特点的是错误信息流以保证及时反馈)。
相关调用接口
(1)open
调用open函数可以打开或者创建一个文件。
int open(const char *path,int flags);
int open(const char *path,int flags,mode_t mde);
路径名:指定用于打开或创建的文件名
flags参数:在打开文件时可传入多个选项参数,并通过下面提到的一个或多个常量进行“或”运算组合成flags参数
参数设置:
- O_RDONLY常量表示仅读模式下的打开操作
- O_WRONLY常量表示仅写模式下的打开操作
- O_RDWR常量表示读取与写入并存的操作模式
这些常量中必须选择并指定其中一个且只能选择其中一个 - O_CREAT常量用于指示如果待创建的文件不存在则会创建该文件
- O_APPEND常量用于指示允许对已存在的文件进行追加方式的写入操作
返回信息: - 成功状态会返回一个新的描述符号指向该已成功创建好的或者被修改过的文件
- 失败状态则会返回数值-1来表示无法完成操作
(2)write函数:
调用write函数将所需的数据传递给已经打开了的文件进行 writes操作
#include<unistd.h>
size_t write(int fildes,const void *buf,size_t nbytes);
参数列表如下:
(1)文件描述符fd
(2)数据缓冲区buf用于指定要写入的数据
(3)字节数nbytes表示要被写入的数量
返回值说明:
如果成功,则返回实际传输的字节数
如果发生错误,则返回-1
无任何数据被传输时则返回0
[a@localhost ~]$ cat write.c
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd = open("./file.txt",O_WRONLY|O_CREAT,0600);
assert(fd != -1);
printf("fd = %d\n",fd);
write(fd,"xjhzjx",5);//写入
close(fd);
}
[a@localhost ~]$ gcc write.c
[a@localhost ~]$ ./a.out
fd = 3
(3)read
调用read函数从打开文件中读数据
#include<unistd.h>
size_t read(int fd,void *buf,size_t nbytes);
参数:
(1) fd: 文件标识符
(2) buf: 所使用的数据缓存区域
(3) nbytes: 需处理的数据长度
返回值:
若成功,则返回已读取的字节数
若出现错误情况,则返回-1
如果未能读取任何数据,则返回0
[a@localhost ~]$ cat read.c
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd = open("file.txt",O_RDONLY);
assert(fd != -1);
printf("fd = %d\n",fd);
char buff[128] = {0};
read(fd,buff,127);
printf("read:%s\n",buff);
close(fd);
}
[a@localhost ~]$ gcc read.c
[a@localhost ~]$ ./a.out
fd = 3
read:xjhzjx
[a@localhost ~]$
(4)close
可用close函数关闭一个打开的文件
#include<unistd.h>
int close(int fd);
并未执行实质性的操作;该内核缓冲区未曾被刷新;仅用于使文件描述符得以重新利用。
文件系统
使用ls -l的时候看到的除了文件名外,还看到了文件元数据。
[a@localhost ~]$ ls -l
total 140
-rw-r--r--. 1 root root 160 Dec 8 16:41 \
-rw-r--r--. 1 root root 304 Nov 27 05:57 01.c
每行包含:模式,硬链接数,文件所有者,组,大小,最后修改时间,文件名
读取信息过程图:

除此之外,还可以通过stat命令看到更多信息
[root@localhost a]# stat 01.c
File: `01.c'
Size: 304 Blocks: 8 IO Block: 4096 regular file
Device: 802h/2050d Inode: 269872 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-11-27 05:57:07.140031048 -0500
Modify: 2017-11-27 05:57:07.140031048 -0500
Change: 2017-11-27 05:57:07.169030849 -0500
为清楚上述inode所示信息,先简单了解文件系统

file system block: 存储文件系统本身的组织架构信息。
i node table: 存储文件属性信息, 包括文件长度, 所有者, 最新更改时间等。
data area: 被存储了文件内容。
属性和数据分开存放如上图看起来很简单,实际如何工作?
[root@localhost a]# touch abc
[root@localhost a]# ls -i abc
270124 abc

生成一个新的文件主要包含以下四个步骤:包括:①存储属性信息;②存储数据记录;③记录资源分配情况;④添加文件名称至目录。
