如果让普通用户使用生磁盘(raw disk),许多人连扇区都不知道是什么?要求他们根据盘块号来访问磁盘…这是不可能的。
所以,需要在盘块上引入更高一层次的抽象概念—文件
提示:以下是本篇文章正文内容
磁盘使用的第三层抽象——文件,文件是一个连续的字符流
用户可以在字符流上随意操作,操作系统会根据映射表找到和字符流位置对应的磁盘块号,操作系统完成了从磁盘块到字符流的映射。
实现文件抽象的关键就在于能根据字符流位置找到对应的盘块号,即字符流和盘块号之间的映射关系,文件: 建立字符流到盘块集合的映射关系
文件使用顺序结构储存在磁盘上,文件的FCB(文件控制块)存储该文件的起始块号(第一个),和块数,根据这个就能知道对应的字符在那个盘块
若盘块的大小为100,则文件中200-212对应在盘块8
但是,用顺序存储的结构适合文件的直接读写,不适合文件的动态增长,与数组一样,不方便插入元素
链式存储结构:操作系统在 FCB 中需要存放的主要映射信息是第一个磁盘块的盘块号,利用这个信息可以找到文件的第一个磁盘块,再利用每个磁盘块中存放的下一个盘块号的指针,可以找到第二个磁盘块……
若盘块的大小为100,则文件中200-212对应在盘块9
索引存储结构:文件字符流被分割成多个逻辑块,在物理磁盘上寻找一些空闲物理盘块(无需连续)将这些逻辑块的内容存放进去,再找一个磁盘块作为索引块,其中按序存放各个逻辑块对应的物理磁盘块号(索引块来记录文件使用的盘块号)
索引结构指一个文件的信息存放在若干不连续的物理块中,系统为每个文件建立一个专用的数据结构——索引表,并将这些块的块号存放在索引表中。
优点是保留了链接结构的优点,同时解决了其缺点,即能顺序存取,又能随机存取,满足了文件动态增长,插入删除的需求,也能充分利用外存空间
缺点是索引表本身带来一定的系统开销
优点:
1.可以表示很大的文件
2.很小的文件高效访问
3.中等大小的文件访问速度也不慢
通过文件对磁盘进行读写
在fs/read_write.c中
int sys_write(int fd, const char* buf, int count)
{
struct file *file = current->filp[fd];
struct m_inode *inode = file->inode;
if(S_ISREG(inode->i_mode))
return file_write(inode, file, buf, count);
}
既然对文件操作就要调用sys_write(),参数:fd文件描述符,buf内存缓冲区,count读写字符的个数
根据文件信息 inode 对应的不是字符设备,而是常规文件,跳到 file_write() 去执行
file_write()
int file_write(struct m_inode *inode, struct file *filp, char *buf, int count)
{
off_t pos;
if(filp->f_flags&O_APPEND)//如果是追加,从文末开始
pos=inode->i_size; else pos=filp->f_pos;
.....
while(i<count)
{
block=create_block(inode, pos/BLOCK_SIZE);//算出对应的块
bh=bread(inode->i_dev, block);//发送请求,放入“电梯” 队列!
int c=pos%BLOCK_SIZE; char *p=c+bh->b_data; //写入数据后,修改修改pos,
bh->b_dirt=1; c=BLOCK_SIZE-c; pos+=c; // pos指向文件的读写位置(字符流末位置)
...
//一块一块拷贝用户字符, 并且释放写出
while(c-->0) *(p++)=get_fs_byte(buf++);
brelse(bh);
}
filp->f_pos=pos;
}
//pos 先找到 文件的读写位置 (记录在 字段 f_pos 中)
//block 计算物理盘块号
//bread 向磁盘发出请求
工作过程:
1.根据file中的一个读写指针fseek(文件的当前读写位置)及 count 找到文件读写对应的字符流位置
2.根据 字符流上的读写位置 算出逻辑块号 ,由inode 找到物理盘块号
3.用磁盘号,buf等形成request放入请求队列(“电梯”),就可以读写磁盘
create_block()计算盘号,文件抽象的核心
这里采用的多级索引
block:(0-6):直接数据块(直接索引), (7):一重间接, (8):二重间接
如果逻辑盘块号小于等于 6,说明 inode中的直接数据块就能映射出盘块号
若这个逻辑盘块没有映射到物理盘块,就调用 new_block() 从磁盘上申请一个空闲物理盘块
block-=7 , if(block<512) 说明逻辑盘块号对应的物理盘块号存放在一阶间接索引,接下来需要 bread(inode->i_dev,inode->i_zone[7]) 读入这个一阶索引块,接下来需要在这个索引块中寻找和逻辑块相对应的物理盘块号
m_inode结构体
struct m_inode{
//读入内存后的inode
unsigned short i_mode; //文件的类型和属性
...
unsigned short i_zone[9]; //指向文件内容数据块
struct task_struct *i_wait;
unsigned short i_count;
unsigned char i_lock;
unsigned char i_dirt;
...
}
根据inode区分文件的属性和类型
int sys_open(const char* filename, int flag)
{
if(S_ISCHR(inode->i_mode)) //字符设备
{
if(MAJOR(inode->i_zone[0])==4) //设备文件
current->tty=MINOR(inode->i_zone[0]);
}
...
}
如果是普通文件需要根据inode里面的映射表来建立磁盘号到字符流直接的映射
如果是特殊文件不需要inode去完成映射,inode存放主设备号(设备文件)
MAJOR的宏定义
#define MAJOR(a)(((unsigned)(a))>>8)) //取高字节
#define MINOR(a)((a)&0xff) //取低字节
提示:这里对文章进行总结: