文件操作
-
空文件,也要在磁盘占空间
-
文件=内容+属性
-
文件操作=对内容+对属性or对内容和属性
-
标定一个问题,必须使用:文件路径+文件名字【唯一性】
-
如果没有指明对应的文件路径,默认是在 当前路径 进行访问
-
当我们把open,fclose,fread,fwrite等接口写完之后,代码编译之后,形成二进制可执行程序之后,但是没有运行,文件对应的操作没有被执行,对文件操作的本质是:进程对文件的操作。
-
一个文件如果没有被打开,就不可以进行文件访问。
-
文件在磁盘上,需要使用os提供的文件级别的系统调用接口。
-
文件以w方式单纯打开文件,c会自动清空内部的数据。
-
文件操作中使用系统调用int open(const char *pathname,int flags, mode_t mode );
返回值是文件描述符,pathname是文件路径,flags有几种,其中3个为 :O_RDONLY,O_WRONLY,O_RDWR,mode是文件属性
还有一个概念,当你创建一个文件或目录时,操作系统将默认权限与 umask 进行“按位取反”的操作,然后将其应用到新创建的文件或目录上。
文件创建的默认权限:0666
目录创建的默认权限:0777
-
使用write()系统调用接口,在使用c语言写入到文件中的时候,不需要write( fd,outbaffer,strlen(outbaffer)-1);不需要-1,因为写入的文件的编码,和读取文件的编码是c语言自己定义的
-
三个标准输入输出流
stdin --》 键盘
stdout --》显示屏
stderr --》显示屏
stdin(标准输入):默认接收来自键盘的输入。
stdout(标准输出):默认将输出打印到显示屏。
stderr(标准错误):默认将错误信息打印到显示屏,通常用于错误或调试信息的输出。
-
FILE * fp=fopen(),这个FILE是结构体。
进程可以打开多个文件吗?可以-〉系统中一定存在大量的被打开的文件 -〉被打开的文锦啊,要不要被OS管理起来呢?-〉如何管理?-》先描述,在组织-》操作系统为了管理对应的打开文件,为文件创建对应的内核数据结构标识文件-〉struct file()-〉包含了文件的大部分属性 -
文件相关的结构体
task_struct 是每个进程的核心结构体,它用于描述进程的所有状态。在 task_struct 中有一个名为 files 的成员,它是一个指向 files_struct 的指针。
files_struct 是专门用于管理文件描述符的结构体。它维护了进程打开的文件描述符表,表中存储了文件描述符与文件的映射关系。
在 files_struct 中,有一个 fd_array,这是一个指向 struct file * 的指针数组,数组中的每一个元素指向一个打开的文件的 file 结构体。
进程通过文件描述符(一个整数值)来索引 fd_array,从而找到对应的文件对象(struct file)进行读写操作。
struct task_struct {
// 其他成员
struct files_struct *files; // 指向文件描述符表的指针
};
struct files_struct {
// 文件描述符的数组
struct file *fd_array[NR_OPEN]; // NR_OPEN 是系统允许的最大打开文件数
// 其他与文件描述符相关的成员
};
struct file {
// 描述打开的文件
const struct file_operations *f_op; // 文件操作
struct inode *f_inode; // 文件的 inode
// 其他与文件状态相关的成员
};
文件描述符的流程
当进程打开一个文件时,系统会为该文件分配一个文件描述符(整数值)。
这个文件描述符被作为索引,存入进程的 files_struct 中的 fd_array 数组。该数组的元素是指向 struct file 的指针。
每当进程使用文件描述符进行文件操作时(如读、写),操作系统通过 fd_array 查找对应的 struct file,然后调用与之关联的文件操作。
- print和fprintf都是向stdout打印,fd文件描述符是1。可以使用重定向的方式打印到文件中,上层用到的fd不变,在内核中更改fd对应的struct。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(){
umusk(0);
int fd=open("log.txt",O_WROXLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
return 1;
}
dup2(fd,1);
printf("open fd:%d\n",fd);
flush(stdout);
close(fd);
return 0;
}
- dup2(fd, 1); 是一个在 C 语言中使用的系统调用,主要用于重定向文件描述符。它的作用是将文件描述符 fd 复制到目标文件描述符 1(通常代表标准输出 stdout)。
主要作用:
重定向输出:通过将标准输出重定向到一个文件,可以使得程序的输出写入该文件,而不是打印到控制台。
替换文件描述符:如果目标文件描述符 1 已经被打开,dup2 会先关闭它,然后将 fd 复制到 1。这样可以确保只有一个文件描述符指向输出。
-
对于子进程的重定向,因为命令是由子进程执行的,真正重定向一定是子进程完成的。重定向不会影响父进程,进程具有独立性。对于子进程的重定向,是对子进程的pcb内的结构体的fd修改,与系统的内核代码中的无关,不会修改子进程之外的fd(0,1,2 )
-
进程关闭,pcb会被释放,如果只有一个进程打开这个文件,那么这个文件也会被关闭。
-
linux一切皆文件,键盘,显示器,磁盘,网卡。每一个设备都有一个结构体描述了属性,每个设备都会有io函数,每种设备都使用了一个统一抽象的结构体file,这个struct会有一些函数指针,来进行io,操作系统不需要关心底层代码,只需要调用int (readp){};int (writep){};
就可以抽象的调用不同的方法,其实就是c语言的多态方法. -
文件也有锁
-
缓冲区:
- 在我们执行fwrite的时候,与其说是写入到文件的函数,不如说是拷贝函数,将数据从进程拷贝到缓冲区中。
- 缓冲区的刷新策略的问题
- 立刻刷新-》无缓冲
- 行刷新-》有缓存-》显示器的方式
- 缓存区满-》全缓存-》磁盘文件
-
缓冲区一定不在内核中,如果在内核中,write也应该打印两次。
我们之前提到的缓冲区是用户语言层面给我们提供的缓冲区。这个缓冲区是stdout,stdin-》file* -〉file结构体-》fd&&还包括了一个缓冲区
。所以我们要通过刷新fflush(文件指针),来刷新缓冲区。 -
理解 I/O 缓冲区及 fflush 的作用
在程序设计中,输入输出(I/O)操作的效率是非常重要的,而为了提升这种效率,现代操作系统和编程语言通常会对 I/O 操作进行优化,其中一个重要的技术就是缓冲区。缓冲区的存在让数据能够在后台悄然传输,不需要每次都与底层的设备进行交互。然而,这也引出了一个有趣的现象:我们有时需要手动刷新缓冲区,这就是 fflush 函数的用武之地。
用户态与内核态的缓冲区
在讨论缓冲区时,我们需要明确不同层次的缓冲区:
用户态的缓冲区:这是编程语言的标准库为我们提供的高层缓冲机制。例如,当我们使用 printf 输出内容时,数据会先写入到用户态的缓冲区中,而不会立即输出到屏幕或文件中。这个缓冲区的存在是为了减少频繁的 I/O 系统调用,从而提升性能。
内核态的缓冲区:用户态的数据最终要通过系统调用(如 write)写入到内核态的缓冲区中,然后再由内核将这些数据提交给实际的硬件设备(如硬盘或显示器)。内核缓冲区的作用是进一步优化数据传输,以减少与设备的交互次数。
为什么缓冲区的存在很重要?
假设每次我们调用 printf 输出一行文本,都需要直接与硬件设备交互。这样会产生大量的上下文切换,增加程序的运行开销。而通过缓冲区,我们可以将多次 I/O 操作合并,从而降低系统调用的频率。例如,当缓冲区填满或手动刷新时,才会将数据真正提交给操作系统。
fflush 的作用
正因为用户态的缓冲机制,数据不会立即被写入到目的地。通常,程序的输出会在满足以下任意条件时才会被实际刷新:
缓冲区满了(缓冲区是有限大小的)。
程序正常结束(如 return 0;)。
手动调用 fflush 函数。
fflush 这个函数的作用是强制将用户态缓冲区中的数据立即写入到内核或文件。它对于一些需要即时输出结果的场景非常有用。例如:
即时显示输出:当你希望程序在运行时立刻在屏幕上显示信息,而不是等待程序结束或缓冲区满时才显示。例如,在调试代码时,你可能希望每次打印都立刻显示,而不是滞留在缓冲区中。
网络传输和数据库操作:在网络通信或数据库写入等场景中,延迟写入可能会导致数据滞后或丢失。调用 fflush 可以确保数据立即被发送或写入。
write 与 fflush 的区别
需要明确的是,write 是一个系统调用,它直接将数据从用户态传输到内核态,绕过用户态的缓冲区。所以,当我们调用 write 时,数据不会受到用户态缓冲区的影响,也不需要 fflush 来刷新。
与之相对,像 printf 这样的标准 I/O 函数使用的是用户态缓冲区。只有在缓冲区被刷新后,数据才会真正通过 write 等系统调用传递到内核。所以,如果我们在 printf 后不调用 fflush,那么数据可能仍停留在用户态的缓冲区中,尚未被写入内核。
缓冲策略:行缓冲与块缓冲
不同的输出设备有不同的缓冲策略:
行缓冲:标准输出(stdout)通常是行缓冲的,也就是说,只有在遇到换行符时才会刷新缓冲区。
块缓冲:文件 I/O 操作通常是块缓冲的,只有当缓冲区满或调用 fflush 时,才会将数据写入文件。
无缓冲:标准错误输出(stderr)则是无缓冲的,因为错误信息通常需要立即显示,而不能滞后。