1.什么是当前路径
exe -> /home/lin/Desktop/Linux_learn/fork_learn/test
当前进程执行是磁盘路径下的哪一个程序
cwd -> /home/lin/Desktop/Linux_learn/fork_learn
当前进程的工作目录------》当前进程
1.1当前路径这个地址能改吗?
可以,使用函数chdir()
chdir("/home/lin");//这个路径一定要存在
2.重谈文件
1.空文件也要占磁盘空间
2.文件的内容加属性
3.文件操作=对内容+对属性or内容和属性
4.标定一个文件的时候,必须使用文件路径+文家名(唯一性)
5.如果没有指定文件路径,默认时在当前路径下(进程当前路径),进行文件访问。
6.当我们把fopen,fclose,fread,fwrite等接口写完之后,代码编译之后,形成的二进制程序之后,但是没有运行,文件对应的操作做了没有?没有------》对文件的操作是进程对文件的操作。
7.一个文件没有被打开,可以直接进行文件访问吗?不可以。一个文件要被访问,一定要被打开(并不是所有文件都被打开)。-------》用户+进程+OS
所以文件分类:1.被打开的文件 2.没被打开的文件
结论:文件操作的本质是:进程和被打开文件的关系
文件在磁盘上(硬件),只有OS有资格访问文件-------->所有人访问文件都绕不过OS---->使用OS提供的接口-------->提供文件级别的系统调用接口--------》可以操作系统的只有一个。=====》所以,1.上层高级语言如何变化,库函数底层 必须 调用系统接口 2.库函数可以千变万化,但底层不变!!----》如何降低学习成本呢?学习不变的东西。
3.重谈文件操作
3.1C语言上的文件操作
文件使用方式 含义 如果指定文件不存在
“r”(只读) 为了输入数据,打开一个已经存在的文本文件 出错
“w”(只写) 为了输出数据,打开一个文本文件 建立一个新的文件
“a”(追加) 向文本文件尾添加数据 建立一个新的文件
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件
“ab”(追加) 向一个二进制文件尾添加数据 建立一个新的文件
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,建议一个新的文件 建立一个新的文件
“a+”(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
“rb+”(读写) 为了读和写打开一个二进制文件 出错
“wb+”(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
“ab+”(读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件
3.2hello.c写文件
#include <string.h>
#include <stdio.h>
int main()
{
FILE *fp = fopen("myfile", "w");
if(!fp){
printf("fopen error!\n");
}
const char *msg = "hello bit!\n";
int count = 5;
while(count--){
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);
return 0;
}
3.3hello.c读文件
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "r");
if(!fp){
printf("fopen error!\n");
}
char buf[1024];
const char *msg = "hello bit!\n";
while(1){
//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
size_t s = fread(buf, 1, strlen(msg), fp);
if(s > 0){
buf[s] = 0;
printf("%s", buf);
}
if(feof(fp)){
break;
}
}
fclose(fp);
return 0;
}
4.系统调用文件接口
4.0open
返回值文件描述符(整数)或-1
参数:mode就是权限
flags选项:全大写(宏)
在C语言中我们会给函数中传一个标记位,C90没有bool类型,用标记位表示某一个事情发生了,或者将标记位作为返回值,把结果拿出去。C传标记位,一个int----》只传一个标记位====》当要传多个呢,一个int有32个bit位,这样我们就可以使用比特位来传递选项。
O_TRUNC选项对文件作清空,O_APPEND追加
int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_TRUNC ,0666);//对应c语言的w
int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_APPEND ,0666);//对应c语言的a
int fd = open(FILE_NAME,O_RDONLY); //对应c语言的r
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR: 读,写打开
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1
mode_t理解:直接 man 手册
4.1如何使用比特位传递选项?
//标记位,每一个宏标记的数值,只有一个比特位,且是1,彼此位置不重叠
//好的写法是#define ONE (1<<0)
#define ONE 0X1 //0001
#define TWO 0X2 //0010
#define THREE 0X4 // 0100
#define EIGHT 0x8 //1000
void show(int flags)
{
if(flags&ONE) {printf("one\n");}
if(flags&TWO) {printf("two\n");}
if(flags&THREE) {printf("three\n");}
if(flags&EIGHT) {printf("eight\n");}
}
int main()
{
//像打谁传谁
show(ONE);
show(ONE | TWO);
show(ONE | TWO | THREE);
show(TWO);
}
我们再回头卡看这个open函数的标记位
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
//没有该文件的时候,用下面这个函数,这个文件权限0666
O_RDONLY(只读), O_WRONLY(只写), or O_CRAT(没有创建).这就是宏,对应着标志位。
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define FILE_NAME "log.txt"
int main()
{
int fd = open(FILE_NAME,O_WRONLY | O_CREAT ,0666);
if(fd < 0)
{
perror("open");
}
(void)fd;
close(fd);
return 0;
}
当我们不想用系统给的umask创建文件的时候,权限由我们设置
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define FILE_NAME "log.txt"
int main()
{
umask(0);//改的是子进程的umask
int fd = open(FILE_NAME,O_WRONLY | O_CREAT ,0666);
if(fd < 0)
{
perror("open");
}
(void)fd;
close(fd);
return 0;
}
0666的文件权限
4.2文件描述符
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define FILE_NAME "log.txt"
int main()
{
umask(0);//改的是子进程的umask
int fd = open(FILE_NAME,O_WRONLY | O_CREAT ,0666);
if(fd < 0)
{
perror("open");
}
printf("%d\n",fd);
(void)fd;
close(fd);
return 0;
}
从3开始,为什么从3开始,0,1,2呢?
printf("stdin->fd:%d\n",stdin->_fileno);
printf("stdout->fd:%d\n",stdout->_fileno);
printf("stderr->fd:%d\n",stderr->_fileno);
三个标准输入输出流
- stdin=====键盘
- stdout====显示器
- stderr====显示器
FILE *fp = fopen()
FILE是啥?
是一个结构体----------》底层系统调用管理文件的是文件描述符------》所以这个结构体必定有一个字段(文件描述符)
文件描述符的本质是啥?
地址下标
文件描述符的分配规则
先关闭0和2文件,再分配两个文件就是0和2,close(0),close(2);
文件log.txt在磁盘中存储,当加载到内存时,OS为了管理,为其创建结构体struct file结构体,包含了文件的属性。这时进程为了管理这些文件,就会创建一个struct files_struct *file的多文件管理结构体指针,他通过指向多文件管理结构体struct files_struct,来找到要管理的文件,在这个struct files_struct结构体中有一个结构体数组struct file* fd_arry[],这个结构体数组就存储着struct file,对内存中的文件进行管理。这样进程与被打开的文件就关联了起来。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt.h>
int main()
{
close(1);
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
printf("%d\n",fd);
fflaush(stdout); //加上刷新
close(fd);
return 0;
}
当0关闭的时候,这时候从文件描述符表的表里面找一个位置放,按从小到大,按照顺序,找到最小的且没有被占用的fd-----------这就是文件描述符的分配规则
关闭0和2的时候没有问题,这时候如果关闭1,printf("%d",fd)就会出现不显示输出了,因为1为比标准输出,当1被关闭的时候,就不会有输出了。
printf就是向stdout打的,stdout是向显示器fd1中打的,所以现在的fd1为文件log.txt,最终的printf("%d",fd)就会输出到文件中。但是文件没有,这是因为,向显示器打印的缓冲区的刷新策略和向普通文件打印的缓冲区的刷新策略不一样。
上面的情况,本来应该到显示器上,但是到了文件里,这种现象就称为重定向。(本质:上层的fd不变,内核更改fd对应的struct file* 的地址)
- > 输出重定向
- >> 追加重定向
- < 输入重定向
系统给我们做了关于重定向的接口
4.3dup重定向函数系统接口(dup2)
int dup2(int oldfd, int newfd);//newfd是oldfd的拷贝。newfd必须和oldfd一样。
在两个文件描述符之间的拷贝。把file * fd_arry[]数组对应的file*对应文件的内容进行拷贝。fd为文件的标识符,1为显示器文件的标识符,要想把内容输出到文件中,就要把fd的内容拷贝的1。让1为fd的文件标识符。也就是把fd的内容拷贝到1。
这时fd的内容和1的内容都以fd为主了。这时候fd就是oldfd,1就是newfd。
这样上面的代码,我们就可以写成
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt.h>
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
dup2(fd,1); //fd为oldfd,1就是newfd。
printf("%d\n",fd);
fflush(stdout);
close(fd);
return 0;
}
- >输出重定向
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC, 0666);
dup2(fd,1); //fd为oldfd,1就是newfd。
- >>追加重定向
int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);
dup2(fd,1); //fd为oldfd,1就是newfd。
const char*msg = "hello world";
write(1,msg,strlen(msg));
- <输入重定向
int fd = open("log.txt",O_RDONLY);
char line[64];
du2p(fd,0); //输入重定向 //fd-->0,从文件中输入。
while(1)
{
printf(">");
if(gets(line,sizeof(line),stdin) == NULL),break;
printf("%s",line);
}
dup2(fd,1); //fd为oldfd,1就是newfd。
4.4 write文件写入(向文件描述符中写入)
ssize_t write(int fd, const void *buf, size_t count);
参数: fd:文件描述符
buf:对应缓冲区数据在哪?(void*)
count:缓冲区字节个数
返回值: 写了几个字节
文件读写分类(C语言提供的)
- ASCLL类(文本类)
- 二进制类
这里用的是const void *buf,void*操作系统看来,都是二进制。
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fd = open(FILE_NAME,O_WRONLY | O_CREAT ,0666);
if(fd < 0)
{
perror("open");
}
char outBuffer[64];
int cnt = 5;
while(cnt);
{
sprintf(outBuffer,"%s:%d\n","hello lin",cnt--);
//C语言规定\0结尾,跟系统没有关系
write(fd,outBuffer,strlen(outBuffer));
//所以向文件中写入字符串,不用加1,除非你就想把\0写进去
}
//printf("%d\n",fd);
//(void)fd;
close(fd);
return 0;
}
4.5read文件读取(向文件描述符中读取)
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#define FILE_NAME "log.txt"
int main()
{
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open");
return 1;
}
char RBuffer[1024];
ssize_t num = read(fd,RBuffer,sizeof(RBuffer)-1);
if(num>0)
{
RBuffer[num] = 0; // 0,\0,NULL--->0
printf("%s",RBuffer);
}
(void)fd;
close(fd);
return 0;
}
4.6lseek
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd
: 文件描述符,指向已经打开的文件。offset
: 要设置的偏移量,根据whence
参数来解释。whence
: 指定偏移量的基准位置,取值如下:
SEEK_SET
: 文件开头。SEEK_CUR
: 当前文件位置。SEEK_END
: 文件末尾。返回值:
- 返回新的文件偏移量(以字节为单位)。
- 如果失败,返回 -1,并设置
errno
以指示错误原因。
5.库函数接口与系统调用接口
库函数接口(封装了系统调用的接口)
fopen/fclose/fwrite/fread/fseek
系统调用接口
open/close/write/read/lseek
6.被打开的文件与进程
文件操作的本质:进程和被打文件的关系。
进程可以打开多个文件-------》系统中一定存在大量被打开的文件-------》被打开的文件要被OS管理起来------》先描述再组织------》操作系统为了管理对应的文件,必定要位文件创建相应的内核结构标识文件------》struct_file{}------>包含了文件的大部分的属性。
文件log.txt在磁盘中存储,当加载到内存时,OS为了管理,为其创建结构体struct file结构体,包含了文件的属性。这时进程为了管理这些文件,就会创建一个struct files_struct file的多文件管理结构体指针,他通过指向多文件管理结构体struct files_struct,来找到要管理的文件,在这个struct files_struct结构体中有一个结构体数组struct file fd_arry[],这个结构体数组就存储着struct file,对内存中的文件进行管理。这样进程与被打开的文件就关联了起来。
6.1Linux系统下一切皆文件
每一个硬件都有对应的结构体。
键盘有对应的struct keyboard{}
显示器有对应的struct tty{}
键盘有对应的struct disk{}
网卡有对应的struct netcard{}
这些都有对应的IO函数。
例如键盘
int keyboardRead(){} //向键盘中读
int keyboardWrite(){NULL} //向键盘中写,有,但是方法为空
显示器
int ttyRead(){NULL} //显示器中读,有,但是方法为空
int ttyWrite(){} //向显示器中写
其中这些读写程序都应该在自己的驱动程序里面。
并且每种硬件,他的访问方法他是不一样的。
6.2缓冲区
- 缓冲区本质:就是一段内存,谁申请的?属于谁?为啥要有它。
当我们要将北京的快递发到四川。四川就相当于内存,北京相当于磁盘,写文件就相当于进程,快递就相当于数据,你要把数据直接给你朋友,就相当于你自己从四川到北京,太消耗时加了,外设的传速(IO)太慢,太消耗时间。缓冲区就相当于快递,可以直接将数据交给缓冲,那么缓冲区的意义为:节省进程数据IO 的时间。
但我们没有看到数据拷贝到缓冲区呀。我们使用的是fwrite--------》与其理解fwrite为写入函数,不如理解fwrite为拷贝函数!!!!!!将数据从进程,拷贝到“缓冲区”或者外设中!!
- 缓冲区刷新策略
就像快递点,发快递一样,不可能一个一个发,这样消耗会增加,一般都是固定频率的来取。
如果有一块数据,一次写入到外设(效率高) VS 如果有一块数据,多次少批量的写入到外设
a.立即刷新 ------ 无缓冲(一般不考虑)
b.行刷新 ----- 行缓存 ----- 显示器(进度条小程序) ------- 给人看的(提高效率的同时,提高使用性)
c.缓冲区满 ------ 全缓存 -------磁盘文件()
d.用户强制刷新-------(fflush)
e.进程退出
- 缓冲区在那里?
#include <stdio.h> #include <unistd.h> #include <string.h> int main() { //C接口 printf("hello printf\n"); fprintf(stdout,"hello fprintf\n"); fputs("hello fputs\n",stdout); //系统接口 const char * s = "hello write\n"; write(1,s,strlen(s)); fork(); return 0; }
当从定向到log2.txt会出现(./buffer > log2.txt)
会发现这里的C接口函数被打印了两次,系统调用接口被打印了一次,why?
跟fork有关,当我们的数据被写道显示器上,就不属于父进程了,但数据没有显示到显示器上,此时的数据依旧属于父进程,调完了C接口,数据还在缓存区。
上面的现象可以看出,缓冲区一定不在内核中。因为如果在内核中,write也要被打印两次。
我们之前谈到缓冲区都是在用户级语言给我们提供的缓冲区!!!!
这个缓冲区在stdout,stdin,stderr----->FILE*----->FILE结构体 ----------》fd&&还包括了一个缓冲区!!!!!
所以我们自己强制刷新,fflush(文件指针),close(文件指针) ,为啥有文件指针,因为里面包含了缓冲区。
C语言上的操作,printf,fprintf,fgets.....所有的这些消息,会被写进fp(我们所定义的FILE* fp = fopen())对应的结构体内部的缓冲区里。因为缓冲区里面封装了fd(文件标识符),所以我们的C语言,他会在合适的时候,将我们的数据,刷新到缓冲区里面。
解释上面C接口出现打印两个
1.如果我们没有>(重定向),我们看到了四条消息。
stdout----->默认使用的是行刷新,在进程fork之前,三条C函数,已经将消息打印输出到显示器上了,进程内部已经不存在对应的数据了。
2.如果我们进行>(重定向),写入文件不再是显示器,而是普通的文件(磁盘文件),刷新策略使用的是全缓冲。虽然数据带了"\n”,但是不足以写满stdout缓冲区,数据并没有刷新!!!
再进行fork时,stdout属于父进程,创建子进程的时候,紧接着就是进程退出。谁先退出,就会刷新缓冲区域。刷新,就相当于修改,这时发生写时拷贝。另一个进程退出的时候,就会接着刷新,重新写一份。
3.write没有写两次,是因为,write没有FILE,而用的是文件描述符,就没有缓冲区。
6.3文件写入磁盘的过程
在我们自己写的程序中,hello bit\n实际上,并不是直接他通过我们的刷新策略,写道磁盘上的,而是先通过struct file结构体中的内核缓冲区,内核缓冲区的数据,由操作系统决定刷新,跟我们写的刷新策略没有关系。
如果我们在写数据的时候电脑突然宕机了,但我们是对数据丢失0容忍的,这是就有了fsync函数,可以立即将内核缓冲区的数据写到磁盘上。
7.文件系统
进程与被打开的文件,我们已经了解,那如果是没有被打开的文件呢?
没被打开的文件只能在磁盘上。磁盘上有大量的文件,这些文件也是要被管理起来的,方便我们随时打开!
文件被管理就是文件系统!
1.磁盘的物理结构
固态磁盘(SSD)和机械磁盘,机械磁盘是我们计算机中唯一的一个机械结构,还是个外设,访问很慢(ms),企业主要的存储结构,便宜。
SSD,存储效率高,造价高,性价比不高,读写次数限制,数据丢失,不能恢复。
两面都可以存数据,磁盘表面磁化来保存数据,磁化就相当于,磁面上有一些小的磁铁,通过南北极掉转,来进行磁化。
7.1.磁盘的储存结构
磁盘的盘面被分为一个一个磁道,每一个磁道又被分为一个个扇区。每一个扇区的存储量为512bite(字节),磁盘在寻址的时候,基本单位是bit,也不是byte,而是扇面。
为啥 周长不一样,但是数据量是一样的?因为byte(字节)密度是不一样的。
7.1.1在磁面上是如何确定扇面?
1. 先确定磁道(柱面),再确定扇区。
2.磁头通过电机的旋转,来回摆动,就可以确定在那一个磁道上,然后磁片通过电机的转动,让磁头确定扇区的位置。
柱面就是多个盘面的同一磁道组成的,其本质等价与磁道。柱面 的产生是因为,多个盘面的组合,多个磁头的组合,多个磁头是共进退的。
7.1.2CHS定位法
- 先确定磁道(柱面) cylinder
- 再定位磁头(定位盘面) head
- 再确定扇区。 sector
7.1.3LBA访问磁盘
下面我们假设一个磁盘,利用LBA方法进行访问,再将LBA与CHS,进行映射(物理结构抽象成逻辑结构)
盘面:4
磁道/面:10
扇区/磁道:100
扇区:512字节
那么该磁盘的总容量为: 4*10*100*512 byte = 2 * 1024 * 1024 = 2MB
那么对应每个扇区的编号就有4*10*100,每个面的扇区有10*100
要找到123号扇区,就是
确定扇面:123 / 10*100 = 0面H
确定磁道:123 / 100 = 1磁道C
确定扇面:123 % 100 = 23扇面 S
为什么OS要进行逻辑抽象呢?直接用CHS不行吗?
- 便于管理
- 不想让OS的代码与硬件强耦合。
- 不管你磁盘是如何存储的,我操作系统就是认为你是LBA存储,你不管怎么变,我都能使用
磁盘对应的基本单位扇区为512byte,每次访问依旧是很小,如何解决?
每一次IO,等磁盘找, 线程要等(sleep状态 ),操作系统得到数据了(加载到内存了 )。 线程唤醒, 这时候才吧文件加载进来。
虽然对应的磁盘的访问的基本单位是512字节,但是依旧很小!!OS内的文件系统定制的进行多个扇区的读取–〉 1KB,2KB,4KB为基本单位!!
你哪怕指向读取/修改1bit,必须将4KB load内存,进行读取或者修改,如果必要,在写回磁盘!!
解决办法
采用局部性原理========多加载一些,减少IO次数,提高效率。提高数据命中的效率=========用空间换时间
局部性原理!内存是被划分成为了4KB大小的空间–页框〈——磁盘中的文件尤其是可执行文件―--按照4KB大小划分好的块--页帧
7.2分而治之的磁盘
先分区,再分组
(一个分区的第一小块)Boot Block是启动块,计算机加电的时候,首先进行加电自检, 通过硬件的方式,来检测硬件的状态。当识别到盘符,会进行读取,从C盘(根目录), 盘面0号柱面的一号扇区,它上面加载的是我们的分区表,和操作系统所在的位置,跳转到操作系统所在分区,加载操作体统代码,然后加载图形化界面和命令行解释器。操作系统开机的所有信息都在这个区域。
每个分组都有一个Supper Block分组,多个SuperBlock,就以为着备份,分组中有SuperBlock,这就是分区中有多个,就以为着备份,防止数据丢失
Inodes块,利用ls -li可以查看文件的inode,查看一个文件的时候,统一使用的是:inode的编号,这个编号是按区进行编号的,拿到编号很容易找在那个分区的。
inode table在分区的时候就已经被创建了,每一个inode是固定大小256字节或128字节,保存在他里面。
在创建文件的时候就要在inodetable里面,找一个inode,把文件所有属性,存操里面。inode数据结构中有对应的Dateblock信息。
Dateblock放的是文件的内容,每一个文件占用一个或多个块,那些块是属于哪一个文件的,可以标识它。
GroupDescriptorTable:inode一个共多少个,被用多少,Dateblock被用了多少。
写创建文件的过程
1. 存储属性 内核先找到一个空闲的inode节点(这里是2xxxx)。内核把文件信息记录到其中。
2. 存储数据 该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
3. 记录分配情况 文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
4. 添加文件名到目录
新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
删除过程
直接将文件Inodemap,由1致0,就直接删除了,文件删除时可以恢复的
可是我们用的都是文件名呀,没用过inode呀。
任何一个文件都在一个目录下,目录也是文件,目录有属性,也有内容,有自己的inode,和数据块
目录的数据块放的是当前目录下的文件名与inode的映射关系
这也就是我们在创建文件目录的时候,必须要有写的权限
当新增内容的时候必须要向数据块写inde与文件名的映射关系
必须罗列一下当前目录下面的文件名
必须要有读权限
要根据文件名找到inode,才能读取到文件所有属性