一.回顾c语言的接口
1.向文件中写入数据(hello world)
#include<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
//FILE --->它是c语言中的一个文件指针(结构体)
FILE * fp = fopen("log.txt","w");
if(!fp)
{
perror("fopen");
return 1;
}
const char* msg = "hello world\n";
int i = 0;
for(; i < 10 ; ++i)
{
fwrite(msg,1,strlen(msg),fp);
//第一个参数表示写入字符串的首地址,第二个参数是写入的基本单位是多少,第三个参数表示写入多少个
//第四个参数是给哪个文件写入。
}
fclose(fp);
}
2.向显示器上打消息的方法
1)任何进程启动时,会默认打开三个输入输出流,stdin,stdout,stderr;
三种的io流的返回值都是FILE*
2)stdin :标准输入------》键盘
stdout:标准输出-------》显示器
stderr:标准错误--------》显示器
3)Linux下,一切皆文件。
#include<stdio.h>
int main()
{
const char * msg = "hello world\n";
fputs(msg,stdout);
printf("%s\n",msg);
fprintf(stdout , "%s\n" ,msg);
}
3.打开文件的方式
“r”:只能从文件中读数据,该文件必须先存在,否则打开失败
“w”:只能向文件写数据,若指定的文件不存在则创建它,如果存在则先删除它再重建一个新文件
“a”:向文件增加新数据(不删除原有数据),若文件不存在则打开失败,打开时位置指针移到文件末尾
“r+”:可读/写数据,该文件必须先存在,否则打开失败
“w+”:可读/写数据,用该模式打开新建一个文件,先向该文件写数据,然后可读取该文件中的数据
“a+”:可读/写数据,原来的文件不被删去,位置指针移到文件末尾
fseek:操控文件的读写位置。
ftell:告诉当前写入的位置在哪里。
rewind:回到当前写入的最开始时。
二.系统文件I/O
1.操作文件的四个接口open,close,write,read,
open
int open(const char * pathname,int flags);
//第一种通常打开已经存在的文件,flags就是告诉你打开文件想要干什么(读写);
int open(const char * pathname,int flags, mode_t mode);
//第二种如果文件不存在,则继续创建,mode告诉文件的权限是什么。
pathname: 要打开或创建的目标文件 flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1
write
ssize_t write(int fd,const void * buf ,size_t count);
//fd写入那个文件描述符,buf表示写入的缓冲区,写多少个字符。
read
ssize_t read(int fd,void * buf,size_t count)
//fd读入的文件描述符,buf表示读到哪里,提供的缓冲区,第三个表示读多少个数据。
pathname:要打开或者要创建的目标文件。
flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量来进行“ | ” 运算,构成flags。
参数:
O_RDONLY:只读打开。
O_WRONLY:只写打开。
O_RDWR:读,写打开。
这三个常量,必须指定一个,且只能指定一个。
O_CREAT:若文件不存在,则创建它,需要使用mode选项,用来指定新文件的访问权限。
O_APPEND:追加写
返回值:
成功:新打开的文件描述符。
失败: - 1;
向一个文件中写入数据
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
umask(0);//将所写的文件权限清空
int fd = open("log.txt",O_WRONLY | O_CREAT,0644);
if(fd < 0)
{
printf("open error");
return 0;
}
const char* msg = "hello world\n";
write(fd, msg , strlen(msg));//c++中不需要加1;
close(fd);
}
三.文件描述符
1. 概念0 & 1 & 2
Linux下的进程默认会打开三个文件描述符,
标准输入0,
标准输出1,
标准错误2,
0,1,2对应的物理设备一般是:键盘,显示器,显示器。
文件指针(FILE*)在文件描述符的上级;
进程和文件的对应关系:一个进程可以打开多个文件。
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来 描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进 程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数 组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件 描述符,就可以找到对应的文件
重定向:
本来应该显示到显示器上的内容,当关闭显示器后,创建5个文件,那么5个文件的内容输出的话,全都输出到第一个文件中。
输出重定向的本质:先关闭1号文件,然后打开一个文件。
重定向只能把1号文件描述符内的文件内容重定向。
数据刷新方案缓冲方式:
1.无缓冲
2.行缓冲,显示器------》库函数
3.全缓冲,文件------》库函数
int main()
{
const char * msg1 = "hello printf\n";
const char * msg2 = "hello fwrite\n";
const char * msg3 = "hello write\n";
printf(msg1);
fwrite(msg2,1,strlen(msg2),stdout);
write(1,msg3,strlen(msg3));
fork();
}
显示器上一共打印了5个消息,printf和fwrite是多打印的。
多打印两个这种操作肯定是和fork()有关系的。
printf和fwrite库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。缓冲区的内容,并没有立即刷新出来,在调用fork()之后,进程退出,会统一刷新,写入文件中。fork()之后,创建子进程,代码共享,数据通过写时拷贝的方式各自独有一份。子进程就会有了同样的一份数据,即产生两份数据。
write时系统调用接口,没有缓冲区,库函数底层时系统调用。
四.理解文件系统
1.要把文件管好,就要把磁盘管好。
硬盘分为机械硬盘(HDD)和固态硬盘(SSD);
当我们使用ls -l时除了看到文件名,还看到了文件元数据。
rwxr-xr-x. 1 root root 7493 “9月 13:15:56” a.out
属性-----硬链接数----文件所有者-----组----大小-----最后修改时间
inode:表示文件的属性。
创建一个文件,就要有一个inode号;
文件系统简单了解
Boot block--------Block group 0------------Block group n
Boot block是操作系统的启动区,如果这块出现错误,磁盘就不能使用。
超级块----------------inode节点表--------------数据区
超级块中描述了inode的使用空间和剩余空间以及数据区的使用空间和剩余空间。
inode bitmap:给inode节点表中分配格子。
block bitmap:给数据区分配格子。记录那个格子被占用,那个没有占用。
创建一个新文件主要的4个操作:
1.存储属性:
在内核中找到一个空闲的i节点,内核把文件信息记录到其中。
2.存储数据:
该文件需要储存几个磁盘块,就在内核中找到几个空闲的磁盘块,将内核缓冲区的内容复制上去。
3.记录分配情况
文件内容依次存放,内核在inode节点表中记录了上述块列表。
4.添加文件名到目录
新创建的文件名叫abc 内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性链接起来。
创建或者写文件:
在inode位图中找到一个空闲位图,将0置为1,并在对应的inode中写属性,其次将inode数据区中的空闲格子,由0置为1,并在数据区中写内容,最后将文件和映射关系放在目录中。
查文件
已知inode号为1234,其次在inode节点表中找1234,通过找到的inode位置找到数据区所在的位置,从第一个位置开始,读取文件大小个数据。
删除文件
找到文件的inode;inode位图清0,将数据位图请0;
目录也是文件,维护好文件名和inode之间的映射关系。
五.理解软硬链接
文件名:link
硬链接: link-h
硬链接没有独立的inode;
我们看到,真正找到磁盘上文件的并不是文件名,而是inode。
在linux下可以让多个文件名对应于同一个inode;
就像是c++中的引用计数一样,当计数为0时,则删除文件。
软连接 link-s:里面放的是文件的路径。
软链接时通过名字引用另一个文件。
通过软连接可以直接从顶层目录指向底层的目录。
面试题:
1.fopen和open的区别?
- fopen 系列是标准的C库函数;open系列是 POSIX 定义的,是UNIX系统里的system call。
也就是说,fopen系列更具有可移植性;而open系列只能用在 POSIX 的操作系统上。 - 使用fopen 系列函数时要定义一个指代文件的对象,被称为“文件句柄”(file handler),是一个结构体;而open系列使用的是一个被称为“文件描述符” (file descriptor)的int型整数。
- fopen 系列是级别较高的I/O,读写时使用缓冲;而open系列相对低层,更接近操作系统,读写时没有缓冲。由于能更多地与操作系统打交道,open系列可以访问更改一些fopen系列无法访问的信息,如查看文件的读写权限。这些额外的功能通常因系统而异。
- 使用fopen系列函数需要"#include <sdtio.h>";使用open系列函数需要"#include <fcntl.h>" ,链接时要之用libc(-lc)