- 1.C语言文件IO
- *当前路径的概念
- 标准输入、标准输出、标准错误
- 2.Linux系统文件IO
- 打开文件open(sys/types.h - sys/stat.h - fcntl.h)
- 关闭文件close(unistd.h)
- *标志位(方便函数传参)
- 从文件中读取文件read(unistd.h)
- 向文件写入数据write(unistd.h)
- 3.文件描述符(数组下标)
- 内存文件VS磁盘文件
- struct file结构体(内存文件)
- 文件的构成(磁盘文件)
- struct files_struct结构体
- 4.文件描述符的分配规则
- 5.输出重定向原理
- C语言FILE*与文件描述符的关系
- dup2函数调用实现重定向(fcntl.h / unistd.h)
- 6.缓冲区
- fflush(stdout)的原因
在C语言文件 *** 作时调用fopen函数以写的方式打开文件,会自动在当前路径下创建文件
#includeint main() { FILE*fp=fopen("Test","w"); fclose(fp); return 0; }
注意:当前路径并不是指可执行程序的位置,而是当前的工作目录
eg:
stdin:标准输入- - >键盘
stdout:标准输出- - >显示器
stderr:标准错误- - >显示器
每个进程在打开时都会默认打开这三个输入输出流
2.Linux系统文件IO 打开文件open(sys/types.h - sys/stat.h - fcntl.h)
两种初始化方式
参数解释:
pathname:要打开的文件名
flags:打开文件选项
- O_APPEND:追加的方式打开文件
- O_RDWR:读写方式打开文件
- O_CREAT:当文件不存在时自动创建文件
mode:打开文件默认权限
注意:这个权限收到系统umask值的影响,umask在Linux-文件权限中介绍不在赘述
返回值:
成功返回文件描述符(file descriptor),失败返回-1
参数解释:
fd:文件描述符
返回值:成功返回0,失败返回-1并设置错误码
一个整数有32位,每一个比特位代表一种标志,每一个标志通过 | 运算联系起来可以一起传入函数中
eg:
#define X 0x1;
00000000…1
#define Y 0x2;
0000000…10
X|Y就把
0000000…11传入,相当于把X和Y的信息一起传入函数中
在函数内部判断某一个比特位是否为1就代表是否传入这个信息
eg:
#include从文件中读取文件read(unistd.h)#include #include #include #include int main() { umask(0);//将umask值设为0 int fd=open("Test.txt",O_WRONLY|O_CREAT,0666); printf("%dn",fd); close(fd); return 0; }
参数解释
fd:从那个文件描述符中读数据
buf:读到的数据放到那个缓冲区
count:每次要读几个字节数据
返回值
返回实际读到的字节个数
返回值<=count
参数解释
fd:写到那个文件描述符中。
buf:写那个缓冲区中的数据
count:要写多大的字节数
返回值:实际上写了几个字节。
eg:
#include3.文件描述符(数组下标)#include #include #include #include #include int main() { umask(0);//将umask值设为0 int fd=open("Test.txt",O_RDWR|O_CREAT,0666); if(fd<0) { return -1; } char ch=0; while(1) { ssize_t size=read(fd,&ch,1);//每次读取一格字符 if(size<=0){ break; } write(1,&ch,1);//1文件描述符是标准输出(显示器)的文件描述符 } close(fd); return 0; }
在Linux中系统,默认一个进程会打开3个文件描述符
0,1,2、分别代表标准输入,标准输出,标准错误,对应C语言的(stdin,stdout,stderr)
这个数字本质是一个数组下标
Linux系统为了管理保存进程打开的文件,用struct file来描述每一个打开的文件,多个文件之间选择双链表的形式组织起来。这张双链表保存在内存中。
这种文件称为内存文件
一个文件不仅仅由文件的内容,还包括修改时间,文件大小等信息。这些信息统称为文件属性
所以:文件=文件内容+文件属性
这个文件称为磁盘文件
文件被打开时有两份,一份是加载到内存的内存文件,一份是磁盘文件。
内存文件:将磁盘文件中的属性信息加载到内存中,形成struct file数据结构。延后式加载数据(当进行文件 *** 作时才加载数据)。
每一个struct file结构体代表一个打开的文件。
每一个进程的task_struct这个结构体。
结构体中有结构struct file* fd_array[32]数组每个元素都是一个指向内存文件的指针
不同的进程有不同的files_struct但都指向同一张struct file双链表。文件描述符就是files_struct结构中fd_array数组的数组下标。
#include#include #include #include #include #include int main() { umask(0); int fd=open("a.txt",O_RDWR,0666); printf("fd=%dn",fd); close(fd); close(0);//关闭标准输出 fd=open("a.txt",O_RDWR,0666); printf("New fd=%dn",fd); close(fd); return 0; }
在进行文件描述符分配时:从上到下扫描,最小的但是没有被使用的位置开始分配
将要输出到显示器上的信息重定向到文件中
#include#include #include #include #include #include int main() { umask(0); close(1);//关闭标准输出,fd的值为1 int fd=open("a.txt",O_RDWR|O_CREAT,0666); write(1,"1n",2); write(1,"2n",2); close(fd); return 0; }
只要是往显示器上打印的数据都会写到文件中
重定向的本质:
修改文件描述符fd下标对应的struct file*所指向的内容
C语言中FILE是个结构体,内部封装了文件描述符
C语言中的stdout:
stdout在C语言中可以看作FILE*指针,指向的FILE结构体中的包含的文件描述符为1
stdin与stderr也类似
所以:
#include#include #include #include #include #include int main() { umask(0); close(1);//关闭标准输出,此时fd的值为1 int fd=open("a.txt",O_RDWR|O_CREAT,0666); fprintf(stdout,"Hello Linuxn");//因为关闭了1号文件描述符,所以改向文件中写入 fflush(stdout);//刷新缓冲区 close(fd); return 0; }
此时stdout中fd=1指向的是新文件,达到了重定向
根据上述可知:
调用C语言的fopen函数时
- 给调用用户申请FILE结构体,并且返回FILE*(结构体地址)
- 在底层调用open函数打开文件,并将open函数的返回值(文件描述符)填充到FILE结构体中的fd上。
类似的输入重定向原理与输出重定向类似。
dup2函数调用实现重定向(fcntl.h / unistd.h)
将oldfd下标对应的fd_array数组里函数指针拷贝到newfd下标对应的fd_array数组里函数指针。
也就是说最后fd_array[newfd]=fd_array[oldfd]
eg:dup2(fd,1);
将1号文件描述符所对应的函数指针变成fd号文件描述符所对应的函数指针。
向标准输出写入的数据会被重定向到文件中。
#include6.缓冲区#include #include #include #include #include int main() { umask(0); int fd=open("a.txt",O_RDWR|O_CREAT,0666); if(fd<0){ return -1; } dup2(fd,1); printf("Hello Linuxn"); fflush(stdout); close(fd); return 0; }
缓冲的分类
-
无缓冲
-
行缓冲:常见对显示器进行刷新数据。如C语言中的printf函数中的n刷新
-
全缓冲:常见对文件读写时采用全缓冲。当把缓冲区内容写满才刷新缓冲区
eg:
#include#include #include #include #include #include int main() { //C语言 printf("Hello printfn"); fprintf(stdout,"Hello fprintfn"); //系统 const char* mes="Hello writen"; write(1,mes,strlen(mes)); fork(); return 0; }
上述现象总结:
- 打印到显示器上是行缓冲,重定向到文件中是全缓冲。
- C语言接口在重定向到文件时打印了两次,系统接口重定向时打印了一次
原因:
- 当在显示器上打印时是行刷新,每次打印时n会刷新缓冲区,所以fork创建子进程时缓冲区里面没有打印的数据
- 重定向到文件时变成全缓冲,所以fork创建子进程时之前没有刷新缓冲区,子进程缓冲区里里面有打印的数据。程序退出时,进程具有独立性,父子进程刷新缓冲区,导致打印了两次。
- 系统调用没有缓冲区,只有printf和fprintf存在缓冲区,所以系统调用只打印了一次。
C库函数是对系统调用的封装。所以缓冲区是语言提供的。
这个缓冲区在内存中,C语言中FILE结构体中不仅有文件描述符,还包括缓冲区(用户缓冲区)
FILE中的缓冲区在用户区,刷新时先把用户区的缓冲区数据拷贝到内核缓冲区上,在刷新到显示器或磁盘上
fflush(stdout)的原因再次来分析代码:
#include#include #include #include #include #include int main() { umask(0); close(1);//关闭标准输出,此时fd的值为1 int fd=open("a.txt",O_RDWR|O_CREAT,0666); fprintf(stdout,"Hello Linuxn"); fflush(stdout);//刷新缓冲区 close(fd); return 0; }
之所以要fflush(stdout)的原因:
重定向文件后,语言缓冲模式变为全缓冲。如果最后不强制刷新的话,close函数将文件关闭,程序结束后,缓冲区里面的数据将没有办法刷新到文件中了。所以一定要强制刷新缓冲区
但如果用C语言的接口fclose时会自动刷新缓冲区,就不需要手动强制刷新缓冲区
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)