文章目录
- 用户缓冲区
- 磁盘
- 磁盘分区
- EXT2文件系统的存储方案
- inode
- 软链接
- 硬链接
用户缓冲区
代码一:
1 #include<stdio.h>2 #include<unistd.h>3 #include<string.h> 4 int main()5 {6 const char * fstr = "hello fwrite\n";7 const char * str =" hello write\n";// C语言接口8 printf("hello printf\n"); //stdout ->19 fprintf ( stdout ,"hello fprintf\n" );//stdout ->110 fwrite( fstr , strlen(fstr), 1, stdout);//操作系统提供的系统调用接口11 write(1, str, strlen(str));12 13 return 0 ;14 }
代码三:
去掉了\n
1 #include<stdio.h>2 #include<unistd.h>3 #include<string.h>4 int main()5 {6 const char * fstr = "hello fwrite "; 7 //const char * str =" hello write\n";8 //C9 printf("hello printf "); //stdout ->110 fprintf ( stdout ,"hello fprintf " );//stdout ->111 fwrite( fstr , strlen(fstr), 1, stdout);12 close(1);13 //操作系统提供的系统调用接口14 //write(1, str, strlen(str));15 // fork();16 return 0 ;17 }
代码四:
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<string.h> 4 int main() 5 { 6 //const char * fstr = "hello fwrite "; 7 const char * str =" hello write"; 8 //C 9 // printf("hello printf "); //stdout ->110 // fprintf ( stdout ,"hello fprintf " );//stdout ->1 11 // fwrite( fstr , strlen(fstr), 1, stdout); 12 //操作系统提供的系统调用接口 13 write(1, str, strlen(str)); 14 close(1); 15 // fork(); 16 return 0 ; 17 }
printf/fprintf/fwrite/fputs 等, 这些函数都是C接口,底层一定调用了write(操作系统提供的系统调用接口)
const char * str =" hello write";
write(1, str, strlen(str));
write能看到打印的结果
原因:
写入的字符串通过write()这样的系统调用接口直接写到了内核的文件缓冲区中 ,close()关闭文件描述符对write不影响
printf/fprintf/fwrite/fputsC接口看不到
原因:调用C接口时,写入的字符串写到了C语言提供的缓冲区,用户缓冲区,如果遇到了\n或者强制刷新,此时C接口才会调用write()接口,把用户缓冲区的数据写入到系统中,
如果此时调用了close(1) ,把1号文件描述符关闭了,就不能刷新数据 ,所以不能显示结果
显示器的文件的刷新方案是行刷新,所以在printf执行完就会立即遇到\n的时候,将数据进行刷新
缓冲区刷新问题
1、无缓冲—直接刷新数据
2、行缓冲—不刷新,直到碰到\n才刷新数据,向显示器文件打印,一般是行
刷新
3、全缓冲—缓冲区满了,才刷新数据 ,向普通文件写入,一般是全缓冲
4、进程退出的时候,也会刷新
例如:这段代码并没有写\n,但是当进程退出的时候,也会刷新
用户刷新的本质,就是将数据通过1 (stdout)+ write写入到内核中
目前我们认为,只要将数据刷新到了内核,数据就到可以硬件了
语言都属于用户层
这个FILE对象属于用户,这个缓冲区是用户级缓冲区
为什么要有这个缓冲区?
1、解决用户的效率问题
2、配合格式化
例如:
int a =123 ;
printf("hello %d\n",a);
printf ,fprintf 这些格式化输出接口 , 如果此时需要在显示器上打印123 ,其实在显示器上打印的是字符1 ,字符2 ,字符3, ,将hello %d ,a 格式化成" hello 123" 写到缓冲区中
缓冲区在什么位置?
FILE里面有对应打开文件的缓冲区字段和维护信息
FILE结构体当中不仅保存了对应文件的文件描述符还保存了用户缓冲区的相关信息
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
exit :C语言接口,exit可以看到用户缓冲区,exit能将用户缓冲区的数据刷新
_exit: 系统调用接口 ,看不到用户缓冲区 ,_exit把文件描述符关闭,将进程释放,
代码:
1 #include<stdio.h>2 #include<unistd.h>3 #include<string.h>4 int main()5 {6 const char * fstr = "hello fwrite\n";7 const char * str =" hello write\n";8 //C9 printf("hello printf\n"); //stdout ->110 fprintf ( stdout ,"hello fprintf\n" );//stdout ->111 fwrite( fstr , strlen(fstr), 1, stdout);12 // close(1);13 //操作系统提供的系统调用接口14 write(1, str, strlen(str));15 fork(); 16 return 0 ;17 }
./myfile > log.txt , 重定向完成之后, 本来向显示器打印,现在打印到log.txt文件
而且缓冲方案从行缓冲变成了全缓冲 , 也就是遇到\n不在刷新,而是等缓冲区被写满才刷新
C接口的hello printf、 hello fprintf 、hello fwrite 先写入到用户级缓冲区中 ,因为此时是全缓冲,所以数据还未刷出
write是操作系统提供的系统调用接口 ,直接写到了内核的文件缓冲区中,所以hello write数据先刷出来,当进程退出时, 将用户缓冲区的数据全部刷出,即C接口的hello printf、 hello fprintf 、hello fwrite
为什么C接口打印两次?
一旦执行fork ,操作系统就会创建子进程 ,父进程和子进程的代码是共享的,数据会以写时拷贝的方式被父子进程各自私有一份 ,用户缓冲区属于用户,也就是属于进程地址空间的堆空间的一段内存缓冲区。当操作系统对用户缓冲区进行操作时,此时发生写时拷贝,父子进程对这段缓冲区各自私有一份,当进程退出时,双方都需要对数据刷新,父进程先刷新,就刷父进程的,子进程先刷新,就刷子进程的
[cxq@iZ7xviiy0goapxtblgih6oZ lesson20]$ while :; do cat log.txt ;sleep 1 ; echo "-----------"; done
模拟实现一下fopen ,fwrite ,fclose
main.c
1 #include<stdio.h>2 #include"Mystdio.h"3 #define myfile "test.txt"4 int main()5 {6 _FILE * fp = _fopen( myfile , "a" );7 if( fp ==NULL )8 {9 perror("fopen fail\n");10 }11 12 const char *msg = "hello world\n";13 14 int cnt =5 ;15 while(cnt)16 {17 _fwrite(fp, msg, strlen(msg));18
E> 19 sleep(1);20 21 cnt --;22 }23 24 25 _fclose(fp);26 27 return 0 ;28 }
Mystdio.c
1 #include"Mystdio.h"2 #include<assert.h>3 #include<unistd.h>4 #include <sys/types.h>5 #include <sys/stat.h>6 #include <fcntl.h>7 #include<stdlib.h>8 #define FILE_MODE 06669 _FILE*_fopen ( const char*filename, const char *flag ) 10 {11 assert(filename);12 assert(flag);13 int f =0 ;14 int fd = -1 ;15 if(strcmp( flag,"w" ) ==0) 16 {17 f = (O_CREAT | O_WRONLY | O_TRUNC) ; 18 fd = open (filename , f, FILE_MODE );19 }20 21 else if(strcmp( flag,"a" ) ==0) 22 {23 f = (O_CREAT | O_WRONLY | O_APPEND) ; 24 fd = open (filename , f, FILE_MODE );25 }26 27 else if(strcmp( flag,"r" ) ==0) 28 {29 f = O_RDONLY ; 30 fd = open (filename , f, FILE_MODE );31 } 32 else 33 {34 return NULL;35 }
36 37 if( fd ==-1 )38 {39 return NULL ; 40 }41 42 _FILE * fp = (_FILE *) malloc (sizeof(_FILE));43 if(fp ==NULL)44 {45 return NULL ;46 }47 fp->fileno = fd ;48 //fp->flag =FLUSH_LINE ; 49 fp->flag =FLUSH_ALL ; 50 fp->out_pos = 0;51 return fp;52 53 }54 55 void _fflush ( _FILE * fp )56 {57 if( fp->out_pos >0 )58 {59 //刷新 60 write ( fp->fileno , fp->outbuffer , fp->out_pos );61 fp->out_pos=0; //缓冲区清零62 }63 }
64 65 66 void _fclose ( _FILE *fp )67 {68 if(fp == NULL) return;69 70 _fflush(fp);71 close ( fp->fileno );72 free(fp) ;73 }74 int _fwrite(_FILE *fp, const char *s, int len)75 {76 // "abcd\n"77 78 //拷贝79 memcpy( &fp->outbuffer[fp->out_pos] , s,len );80 fp->out_pos +=len;81 82 //无缓冲83 if(fp->flag&FLUSH_NOW) // & ??84 {85 write(fp->fileno , fp->outbuffer , fp->out_pos);86 fp->out_pos =0 ;87 88 }89 90 else if( fp->flag&FLUSH_LINE )91 {92 //行缓冲93 if( fp->outbuffer[fp->out_pos-1] == '\n')94 {95 write(fp->fileno, fp->outbuffer, fp->out_pos);96 fp->out_pos = 0; 97 }98 99
100 }
101 //全缓冲
102 else if( fp->flag & FLUSH_ALL )
103 {
104 if(fp->out_pos == SIZE)
105 {
106 write(fp->fileno, fp->outbuffer, fp->out_pos);
107 fp->out_pos = 0;
108 }
109 }
110 return len ;
111 }
Mystdio.h
1 //防止头文件重复包含2 #ifndef __MYSTDIO_H__3 #define __MYSTDIO_H__ 4 #define SIZE 10245 6 7 #define FLUSH_NOW 1 // 无缓冲8 #define FLUSH_LINE 2 //行缓冲9 #define FLUSH_ALL 4//全缓冲10 #include<string.h>11 12 typedef struct IO_FILE13 {14 int fileno ;// 文件描述符fd15 int flag ; //缓冲方式16 // char inbuffer[SIZE] ;//接收缓冲区 17 char outbuffer[SIZE] ; // 18 // int in_pos ; 19 int out_pos; //outbuffer这个缓冲区使用了多少,由out_pos决定 20 }_FILE;21 22 _FILE * _fopen ( const char * filename, const char * mode ) ;23 int _fwrite ( _FILE * fp , const char * ptr , int len ) ;24 void _fclose(_FILE* fp ) ;25 #endif
磁盘
磁盘是一种永久性存储介质,在计算机中,磁盘几乎是唯一的机械设备。与磁盘相对应的就是内存,内存是掉电易失存储介质,目前所有的普通文件都是在磁盘中存储的
磁盘被访问的最基本单元是扇区,一个扇区的大小通常为512字节
可以把磁盘看做由无数个扇区构成的永久性存储介质
任意—个扇区都有下标
要把数据存到磁盘,需要三个步骤
1、确定读写信息在磁盘的哪一面(定位用哪个磁头)
2、这一面的哪一个磁道
3、这个磁道的哪一个扇区
Cylinder (磁道或者柱面)Header(磁头) Sector(扇区)
通过磁道、磁头、扇区来定位 ,把这种寻址方式叫CHS寻址方式
磁盘分区
计算机为了更好的管理磁盘,于是对磁盘进行了分区,盘片一旦划分成数个分区,不同的目录与文件就可以存储进不同的分区,分区越多,就可以将文件的性质区分得越细
磁盘通常被称为块设备,一般以扇区为单位,一个扇区的大小通常为512字节。我们若以大小为512G的磁盘为例,该磁盘就可被分为十亿多个扇区
EXT2文件系统的存储方案
格式化:每一个分区再被使用之前,都必须提前先将部分文件系统的属性信息提前设置进对应的分区中,方便我们后续使用这个分区或者分组
Super Block:文件系统的基本信息,里面包含的是整个分区的文件系统基本使用情况,Super Block不会在每个组都存在
例如:一共有多少个组,每个组的大小,每个组的inode数量,每个组的block数量,每个组的起始inode ,文件系统的类型与名称等
Group Descriptor Table: 块组描述符表,描述该分区当中块组的属性信息。
整个分组的相关属性和具体使用情况
例如该分组一共有多少个块,一共有多少inode,这些inode中被使用的数量,未被使用的数量,整个数据块中被使用的具体数量,未被使用的具体数量
Block Bitmap:比特位的位置和块号映射起来,比特位的内容,表示该块有没有被使用删一个文件的时候,找到对应文件的inode,把对应的inode编号在位图清空,这个文件就被删除了
inode Bitmap:比特位的位置和inode的编号映射起来,比特位的内容表示inode是否是有效的
inode Table:inode:单个文件的所有的属性,一般是128字节,一个文件,一个inode,每一个inode都有自己的inode编号(inode的设置,是以分区为单位的,不能跨分区),inode表示文件的所有属性,文件名,并不属于inode内的属性
Data Blocks:存文件内容的区域,以块的形式呈现。常见的是4KB大小—文件系统的块大小! 例如:在磁盘中,新建一个文件,就算往文件中写入一个字节的内容,在文件系统中,在某一个分区中,找到某一个块组,在该块组中申请一个块,大小也要为4KB
一个文件使用的数据块和inode结构的对应关系,是通过一个数组进行维护的,该数组一般可以存储15个元素,其中前12个元素分别对应该文件使用的12个数据块,剩余的三个元素分别是一级索引、二级索引和三级索引,当该文件使用数据块的个数超过12个时,可以用这三个索引进行数据块扩充
inode
磁盘文件由两部分构成,分别是文件内容和文件属性。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名、文件大小以及文件创建时间等信息都是文件属性,文件属性又被称为元信息
显示当前目录下各文件的inode编号
ls -i
[cxq@iZ7xviiy0goapxtblgih6oZ lesson21]$ touch test.c
[cxq@iZ7xviiy0goapxtblgih6oZ lesson21]$ ls -i
789121 test.c
[cxq@iZ7xviiy0goapxtblgih6oZ lesson21]$ ls -il
total 0
789121 -rw-rw-r-- 1 cxq cxq 0 May 23 15:46 test.c
查看磁盘的分区信息
[cxq@iZ7xviiy0goapxtblgih6oZ lesson21]$ ls /dev/vda* -l
brw-rw---- 1 root disk 253, 0 Apr 15 09:28 /dev/vda
brw-rw---- 1 root disk 253, 1 Apr 15 09:28 /dev/vda1
在文件系统中,如何理解创建一个空文件?
查Group Descriptor Table ,如果inode使用率较低,
再查inode Bitmap,扫描位图结构,找到最近未被使用的inode编号,并确认在哪个组中,inode Bitmap里对应的位图将0改为1,通过inode编号在inode Table中找到对应的inode,将写入的文件属性填入Block Bitmap
在文件系统中,如何理解对文件写入信息?
通过文件的inode编号找到对应的inode结构。
通过inode结构找到存储该文件内容的数据块,并将数据写入数据块。
若不存在数据块或申请的数据块已被写满,则通过遍历块位图的方式找到一个空闲的块号,并在数据区当中找到对应的空闲块,再将数据写入数据块,最后还需要建立数据块和inode结构的对应关系
在文件系统中,如何理解目录?
目录也是文件,也有自己的inode。目录也有自己的属性
目录的inode结构当中存储的就是目录的属性信息,比如目录的大小、目录的拥有者等。
目录也有自己的内容,目录的数据块当中存储的是,该目录下,文件的文件名和对应文件的inode的映射关系
在文件系统中,如何理解删除一个文件
找到文件对应的inode编号,根据文件所处在的目录,得知文件处在具体的分区,在根据inode范围,确定具体的分组, 在inode Bitmap中,将对应的比特位由1改为0,根据Block Bitmap,由1该为0
在文件系统中,如何理解查找一个文件?
找到这个文件的inode编号,在inode Bitmap中,确定对应的比特位为1,读取inode Table,将文件属性拿出来,通过inode结构体里面的blocks数组,找到对应的数据块
软链接
任意一个文件,无论是目录,还是普通文件,都有inode每一个inode内部,都有一个叫做引用计数的计数器(有多少个文件名指向我)!
创建一个文件的软连接
[cxq@iZ7xviiy0goapxtblgih6oZ lesson21]$ ln -s myproc myproc-s
[cxq@iZ7xviiy0goapxtblgih6oZ lesson21]$ ll
total 20
-rw-rw-r-- 1 cxq cxq 70 May 24 09:19 Makefile
-rwxrwxr-x 1 cxq cxq 8360 May 24 09:21 myproc
-rw-rw-r-- 1 cxq cxq 73 May 24 09:13 myproc.c
lrwxrwxrwx 1 cxq cxq 6 May 24 09:41 myproc-s -> myproc
软链接文件的inode号与源文件的inode号是不同的,软连接是一个独立的文件,有独立的inode,也有独立的数据块,软连接的数据块里面保存的是指向的目标文件的路径
并且软链接文件的大小比源文件的大小要小得多
软链接又叫做符号链接,软链接文件相对于源文件来说是一个独立的文件,该文件有自己的inode号,但是该文件只包含了源文件的路径名,所以软链接文件的大小要比源文件小得多。软链接就类似于Windows操作系统当中的快捷方式
软链接文件只是其源文件的一个标记,当删除了源文件后,链接文件不能独立存在,虽然仍保留文件名,但却不能执行或是查看软链接的内容
硬链接
建立硬链接,本质其实就是在特定目录的数据块中,新增文件名和指向的文件的inode编号的映射关系
硬链接应用场景:通常用来进行路径定位,采用硬链接,可以进行目录间切换
cxq@iZ7xviiy0goapxtblgih6oZ lesson21]$ ln myproc myproc-h
硬链接文件的inode号与源文件的inode号是相同的,并且硬链接文件的大小与源文件的大小也是相同的,特别注意的是,当创建了一个硬链接文件后,该硬链接文件和源文件的硬链接数都变成了2
硬链接文件就是源文件的一个别名,一个文件有几个文件名,该文件的硬链接数就是几,这里inode号为789123的文件有myproc和myproc-h两个文件名,因此该文件的硬链接数为2。
与软连接不同的是,当硬链接的源文件被删除后,硬链接文件仍能正常执行,只是文件的链接数减少了一个,因为此时该文件的文件名少了一个
硬链接就是让多个不在或者同在一个目录下的文件名,同时能够修改同一个文件,其中一个修改后,所有与其有硬链接的文件都一起修改了
[cxq@iZ7xviiy0goapxtblgih6oZ lesson22]$ mkdir dir
[cxq@iZ7xviiy0goapxtblgih6oZ lesson22]$ ll
total 4
drwxrwxr-x 2 cxq cxq 4096 May 26 19:28 dir
[cxq@iZ7xviiy0goapxtblgih6oZ lesson22]$ ls -li
total 4
789125 drwxrwxr-x 2 cxq cxq 4096 May 26 19:28 dir
[cxq@iZ7xviiy0goapxtblgih6oZ lesson22]$ cd dir
[cxq@iZ7xviiy0goapxtblgih6oZ dir]$ ls -lia
total 8
789125 drwxrwxr-x 2 cxq cxq 4096 May 26 19:28 .
789124 drwxrwxr-x 3 cxq cxq 4096 May 26 19:28 ..
dir的硬链接数是2
linux系统不允许对目录建立硬链接
stat 文件名来查看对应文件的信息
[cxq@iZ7xviiy0goapxtblgih6oZ lesson21]$ stat MakefileFile: ‘Makefile’Size: 70 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 789122 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ cxq) Gid: ( 1000/ cxq)
Access: 2024-05-24 09:21:10.264336601 +0800
Modify: 2024-05-24 09:19:44.614656971 +0800
Change: 2024-05-24 09:19:44.614656971 +0800Birth: -
这其中包含了文件的三个时间信息:
Access: 文件最后被访问的时间。
Modify: 文件内容最后的修改时间。
Change: 文件属性最后的修改时间。
当我们修改文件内容时,文件的大小一般也会随之改变,所以一般情况下Modify的改变会带动Change一起改变,但修改文件属性一般不会影响到文件内容,所以一般情况下Change的改变不会带动Modify的改变。
可以使用命令touch 文件名,将文件的这三个时间都更新到最新状态
注意: 当某一文件存在时使用touch命令,此时touch命令的作用变为更新文件信息