文件系统源码分析之buffer.c
/** linux/fs/buffer.c** (C) 1991 Linus Torvalds*//** 'buffer.c' implements the buffer-cache functions. Race-conditions have* been avoided by NEVER letting a interrupt change a buffer (except for the* data, of course), but instead letting the caller do it. NOTE! As interrupts* can wake up a caller, some cli-sti sequences are needed to check for* sleep-on-calls. These should be extremely quick, though (I hope).*//** NOTE! There is one discordant note here: checking floppies for* disk change. This is where it fits best, I think, as it should* invalidate changed floppy-disk-caches.*/#include <stdarg.h>#include <linux/config.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/io.h>extern int end;
// 内存中开辟的一块内存,end是内核代码的结束地址
struct buffer_head * start_buffer = (struct buffer_head *) &end;
// 哈希链表,主要是为了快速找到数据
struct buffer_head * hash_table[NR_HASH];
// 缓存区的结构是双向循环链表,free_list指向第一个理论上可用的节点,他的最后一个节点是最近被使用的节点
static struct buffer_head * free_list;
// 没有buffer可用而被阻塞的进程挂载这个队列上
static struct task_struct * buffer_wait = NULL;
// 一共有多少个buffer块
int NR_BUFFERS = 0;
// 加锁,互斥访问
static inline void wait_on_buffer(struct buffer_head * bh)
{cli();while (bh->b_lock)sleep_on(&bh->b_wait);sti();
}int sys_sync(void)
{int i;struct buffer_head * bh;// 把所有inode写入buffer,等待回写,见下面代码sync_inodes(); /* write out inodes into buffers */bh = start_buffer;for (i=0 ; i<NR_BUFFERS ; i++,bh++) {wait_on_buffer(bh);if (bh->b_dirt)// 请求底层写硬盘操作,等待底层驱动回写到硬盘,不一定立刻写入ll_rw_block(WRITE,bh);}return 0;
}// 把buffer中属于dev设备的缓存全部回写到硬盘
int sync_dev(int dev)
{int i;struct buffer_head * bh;bh = start_buffer;// 先把属于该dev的缓存回写硬盘for (i=0 ; i<NR_BUFFERS ; i++,bh++) {if (bh->b_dev != dev)continue;wait_on_buffer(bh);if (bh->b_dev == dev && bh->b_dirt)ll_rw_block(WRITE,bh);}// 同步所有inode到buffer中sync_inodes();// 把属于该dev的buffer再写一次bh = start_buffer;for (i=0 ; i<NR_BUFFERS ; i++,bh++) {if (bh->b_dev != dev)continue;wait_on_buffer(bh);if (bh->b_dev == dev && bh->b_dirt)ll_rw_block(WRITE,bh);}return 0;
}
// 使属于dev的buffer全部失效
void inline invalidate_buffers(int dev)
{int i;struct buffer_head * bh;bh = start_buffer;for (i=0 ; i<NR_BUFFERS ; i++,bh++) {if (bh->b_dev != dev)continue;wait_on_buffer(bh);if (bh->b_dev == dev)bh->b_uptodate = bh->b_dirt = 0;}
}/** This routine checks whether a floppy has been changed, and* invalidates all buffer-cache-entries in that case. This* is a relatively slow routine, so we have to try to minimize using* it. Thus it is called only upon a 'mount' or 'open'. This* is the best way of combining speed and utility, I think.* People changing diskettes in the middle of an operation deserve* to loose :-)** NOTE! Although currently this is only for floppies, the idea is* that any additional removable block-device will use this routine,* and that mount/open needn't know that floppies/whatever are* special.*/
void check_disk_change(int dev)
{int i;if (MAJOR(dev) != 2)return;if (!floppy_change(dev & 0x03))return;for (i=0 ; i<NR_SUPER ; i++)if (super_block[i].s_dev == dev)put_super(super_block[i].s_dev);invalidate_inodes(dev);invalidate_buffers(dev);
}
// 通过dev和block算出在哈希表的位置
#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
// 取得哈希链表中某条链表
#define hash(dev,block) hash_table[_hashfn(dev,block)]
// 把节点移出哈希链表和空闲链表
static inline void remove_from_queues(struct buffer_head * bh)
{
/* remove from hash-queue */if (bh->b_next)bh->b_next->b_prev = bh->b_prev;if (bh->b_prev)bh->b_prev->b_next = bh->b_next;// bh是哈希链表的第一个节点的话则更新哈希链表的头指针if (hash(bh->b_dev,bh->b_blocknr) == bh)hash(bh->b_dev,bh->b_blocknr) = bh->b_next;
/* remove from free list */// 双向循环链表不能存在这种情况if (!(bh->b_prev_free) || !(bh->b_next_free))panic("Free block list corrupted");bh->b_prev_free->b_next_free = bh->b_next_free;bh->b_next_free->b_prev_free = bh->b_prev_free;// bh是当前空闲链表的第一个节点则更新空闲链表的头指针if (free_list == bh)free_list = bh->b_next_free;
}static inline void insert_into_queues(struct buffer_head * bh)
{
/* put at end of free list *//* free_list指向第一个空闲的buffer,free_list->b_prev_free指向最近刚使用的buffer,即每次找到一个可用buffer的时候都成为free_list的尾节点*/bh->b_next_free = free_list;bh->b_prev_free = free_list->b_prev_free;free_list->b_prev_free->b_next_free = bh;free_list->b_prev_free = bh;
/* put the buffer in new hash-queue if it has a device */bh->b_prev = NULL;bh->b_next = NULL;if (!bh->b_dev)return;// 头插法插到哈希链表// 指向哈希链表当前的第一个节点bh->b_next = hash(bh->b_dev,bh->b_blocknr);// 哈希链表头指针指向bhhash(bh->b_dev,bh->b_blocknr) = bh;// 旧的头指针的prev指针指向bhbh->b_next->b_prev = bh;
}
// 从哈希链表中找到某个节点
static struct buffer_head * find_buffer(int dev, int block)
{ struct buffer_head * tmp;// 先找到哈希链表中的某条链表的头指针for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)if (tmp->b_dev==dev && tmp->b_blocknr==block)return tmp;return NULL;
}/** Why like this, I hear you say... The reason is race-conditions.* As we don't lock buffers (unless we are readint them, that is),* something might happen to it while we sleep (ie a read-error* will force it bad). This shouldn't really happen currently, but* the code is ready.*/
// 找dev+block对应的buffer
struct buffer_head * get_hash_table(int dev, int block)
{struct buffer_head * bh;for (;;) {// 找不到直接返回NULLif (!(bh=find_buffer(dev,block)))return NULL;// 存在则先把引用数加1,防止别人释放bh->b_count++;// 看该buffer是不是正在被使用wait_on_buffer(bh);// 可能在阻塞的时候buffer已经被修改过if (bh->b_dev == dev && bh->b_blocknr == block)return bh;// 引用数减一,重新再找bh->b_count--;}
}/** Ok, this is getblk, and it isn't very clear, again to hinder* race-conditions. Most of the code is seldom used, (ie repeating),* so it should be much more efficient than it looks.** The algoritm is changed: hopefully better, and an elusive bug removed.*/
// 数据脏或者被锁,脏的位置比锁更高,即被锁了可以接收,脏数据的buffer尽量不用,除非找不到buffer
#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)
struct buffer_head * getblk(int dev,int block)
{struct buffer_head * tmp, * bh;repeat:// 找到直接返回if (bh = get_hash_table(dev,block))return bhtmp = free_list;do {// 已经被使用则找下一个节点if (tmp->b_count)continue;/*尽量找到一个数据是干净并且没有被锁的buffer,如果没有,则找干净但被锁的,还没有就找不干净又被锁的1 找到第一个buffer的时候,!bh成立,bh等于第一个可用的buffer,如果干净又没有被锁直接返回,继续尝试查找更好的buffer2 后面再找到buffer的时候,!bh就不会成立了,从而执行BADNESS(tmp)<BADNESS(bh),根据定义,我们知道BADNESS的结果和数据是否干净、被锁相关,其中干净的权重更大,即如果两个buffer,一个脏,一个被锁,则被锁是更好的选择,所以BADNESS(tmp)<BADNESS(bh)的意思是,找到一个比当前节点更好的,如果没有,则继续找如果有则执行bh=tmp,即记录当前最好的节点,然后再判断该节点是不是干净又没有被锁的,是则返回,否则继续找更好的节点。*/if (!bh || BADNESS(tmp)<BADNESS(bh)) {bh = tmp; // 记录当前最好的节点// 当前最好的节点是否满足要求,是则返回,否则继续找更好的if (!BADNESS(tmp))break;}
/* and repeat until we find something good */} while ((tmp = tmp->b_next_free) != free_list);// 没有buffer可用,则阻塞等待if (!bh) {sleep_on(&buffer_wait);goto repeat;}// 到这里说明有buffer可用,但是情况有,1被锁的 2 数据不干净的 3 不干净且被锁 4 干净又没有被锁// 处理lock的情况wait_on_buffer(bh);// 阻塞的时候被其他进程使用了,则继续找if (bh->b_count)goto repeat;// 处理数据脏的情况while (bh->b_dirt) {// 回写数据到硬盘sync_dev(bh->b_dev);wait_on_buffer(bh);if (bh->b_count)goto repeat;}
/* NOTE!! While we slept waiting for this block, somebody else might */
/* already have added "this" block to the cache. check it */if (find_buffer(dev,block))goto repeat;
/* OK, FINALLY we know that this buffer is the only one of it's kind, */
/* and that it's unused (b_count=0), unlocked (b_lock=0), and clean */bh->b_count=1;bh->b_dirt=0;bh->b_uptodate=0;// 移除空闲链表remove_from_queues(bh);bh->b_dev=dev;bh->b_blocknr=block;// 插入空insert_into_queues(bh);return bh;
}
// 有buffer可用, 唤醒等待buffer的队列
void brelse(struct buffer_head * buf)
{if (!buf)return;wait_on_buffer(buf);if (!(buf->b_count--))panic("Trying to free free buffer");wake_up(&buffer_wait);
}/** bread() reads a specified block and returns the buffer that contains* it. It returns NULL if the block was unreadable.*/
struct buffer_head * bread(int dev,int block)
{struct buffer_head * bh;// 先从buffer链表中获取一个bufferif (!(bh=getblk(dev,block)))panic("bread: getblk returned NULL\n");// 之前已经读取过并且有效,则直接返回if (bh->b_uptodate)return bh;// 返回读取硬盘的数据ll_rw_block(READ,bh);//ll_rw_block会锁住bh,所以会先阻塞在这然后等待唤醒 wait_on_buffer(bh);// 底层读取数据成功后会更新该字段为1,否则就是读取出错了if (bh->b_uptodate)return bh;brelse(bh);return NULL;
}
// movsl每次传送四个字节,所以cx等于BLOCK_SIZE除以4
#define COPYBLK(from,to) \
__asm__("cld\n\t" \"rep\n\t" \"movsl\n\t" \::"c" (BLOCK_SIZE/4),"S" (from),"D" (to) \:"cx","di","si")/** bread_page reads four buffers into memory at the desired address. It's* a function of its own, as there is some speed to be got by reading them* all at the same time, not waiting for one to be read, and then another* etc.*/
// 读取四个块到address
void bread_page(unsigned long address,int dev,int b[4])
{struct buffer_head * bh[4];int i;for (i=0 ; i<4 ; i++)if (b[i]) {if (bh[i] = getblk(dev,b[i]))if (!bh[i]->b_uptodate)ll_rw_block(READ,bh[i]);} elsebh[i] = NULL;for (i=0 ; i<4 ; i++,address += BLOCK_SIZE)if (bh[i]) {wait_on_buffer(bh[i]);if (bh[i]->b_uptodate)COPYBLK((unsigned long) bh[i]->b_data,address);brelse(bh[i]);}
}/** Ok, breada can be used as bread, but additionally to mark other* blocks for reading as well. End the argument list with a negative* number.*/
// 预读多个块,最后一个参数以负数结尾,只返回第一块的buffer指针,预读得存在buffer哈希链表里,等待以后用
struct buffer_head * breada(int dev,int first, ...)
{va_list args;struct buffer_head * bh, *tmp;va_start(args,first);if (!(bh=getblk(dev,first)))panic("bread: getblk returned NULL\n");if (!bh->b_uptodate)ll_rw_block(READ,bh);while ((first=va_arg(args,int))>=0) {tmp=getblk(dev,first);if (tmp) {if (!tmp->b_uptodate)// bh应该是tmp,因为需要每次给底层传一个新的buffer结构,如果一直用bh,预读的数据会覆盖前面的数据ll_rw_block(READA,bh);tmp->b_count--;}}va_end(args);// 等待底层唤醒wait_on_buffer(bh);if (bh->b_uptodate)return bh;brelse(bh);return (NULL);
}
// 系统初始化的时候执行该函数,主要是建立buffer对应的数据结构,一个双向循环链表
void buffer_init(long buffer_end)
{struct buffer_head * h = start_buffer;void * b;int i;// buffer的结束地址if (buffer_end == 1<<20)b = (void *) (640*1024);elseb = (void *) buffer_end;// buffer_head在头部分配,data字段对应的内容在末端分配,data字段的地址和buffer_head结构的地址要相差至少一个struct buffer_headwhile ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) {h->b_dev = 0;h->b_dirt = 0;h->b_count = 0;h->b_lock = 0;h->b_uptodate = 0;h->b_wait = NULL;h->b_next = NULL;h->b_prev = NULL;h->b_data = (char *) b;// 初始化时每个节点都是空闲的,形成一条freelisth->b_prev_free = h-1;h->b_next_free = h+1;h++;// buffer个数NR_BUFFERS++;if (b == (void *) 0x100000)b = (void *) 0xA0000;}// h--后h为最后一个buffer_head结构的地址h--;// 整条链都是空闲的free_list = start_buffer;// 更新第一个节点的prev指针和最后一个节点的next指针,因为在while循环的时候他们指向了无效的地址free_list->b_prev_free = h;h->b_next_free = free_list;for (i=0;i<NR_HASH;i++)hash_table[i]=NULL;
}
复制代码
转载于:https://juejin.im/post/5c3df40ae51d4551cb348113
文件系统源码分析之buffer.c相关推荐
- [NIO系列]NIO源码分析之Buffer
在以前的一篇文章中我们介绍过IO模型 IO模型总结 http://www.cnblogs.com/coldridgeValley/p/5449758.html ,而在实际运用中多路复用IO使用很多,J ...
- JuiceFS分布式文件系统源码分析(Java层)
文章目录 01 引言 02 JuiceFS Hadoop Java API 2.1 如何使用? 2.2 入口 2.2.1 getFileSystem方法 2.2.2 小结 2.3 JuiceFS源码 ...
- linux之虚拟文件系统源码分析(详解)
文章目录 前言 基础知识 VFS的数据结构 正篇 前言 虚拟文件系统是一个很庞大的架构,如果要分析的面面俱到,会显得特别复杂而笨拙,让人看着看着,就不知所云了(当然主要还是笔者太菜),所以这篇博客 ...
- FATFS文件系统+源码分析——学习笔记
一.概述 1.目的 在移植之前,先将源代码大概的阅读一遍,主要是了解文件系统的结构.各个函数的功能和接口.与移植 相关的代码等等. 2.准备工作 在官方网站下载了0.12c版本的源代码,利用UE进行阅 ...
- 【muduo源码分析】Buffer类的设计
目录 1.muduo的IO模型 2.为什么 non-blocking 网络编程中应用层 buffer 是必须的? 2.1 TcpConnection 必须要有 output buffer 2.2 Tc ...
- muduo源码分析之Buffer
这一次我们来分析下muduo中Buffer的作用,我们知道,当我们客户端向服务器发送数据时候,服务器就会读取我们发送的数据,然后进行一系列处理,然后再发送到其他地方,在这里我们想象一下最简单的Echo ...
- FATFS文件系统框架及源码分析
FATFS是一个为小型嵌入式系统设计的通用FAT(File Allocation Table)文件系统模块.FatFs 的编写遵循ANSI C,并且完全与磁盘I/O层分开.因此,它独立(不依赖)于硬件 ...
- zlib源码分析—DEFLATE算法原理及实现
从上一篇博客zlib源码分析-compress函数学习了compress函数的代码,这一篇我们来详细分析一下deflate算法的流程.先从compress代码中所体现出来的deflate函数的返回值和 ...
- 鸿蒙轻内核源码分析:文件系统LittleFS
摘要:本文先介绍下LFS文件系统结构体的结构体和全局变量,然后分析下LFS文件操作接口. 本文分享自华为云社区<# 鸿蒙轻内核M核源码分析系列二一 02 文件系统LittleFS>,作者: ...
最新文章
- 机器人进攻民用市场:踏实做好小优美
- python爬取图片全网通_UC头条:全网通杀——暴强工具推荐: 下载你看到的任意视频...
- jdbc hibernate ibatis 操作Blob 和Clob类型字段(不断更新)
- 2013计算机视觉代码合集
- centos 安装idea 非可视化_太厉害了!目前 Redis 可视化工具最全的横向评测
- 【文末赠书】牛顿:伸向未知量的利爪
- git 应用 远程仓库分歧
- 李洪强iOS之集成极光推送二iOS 证书 设置指南
- linux+listen错误,linux listen()
- 使用firefox44版本,弃用chrome
- lstrip和rstrip_Python装饰字符串– rstrip(),lstrip(),strip()
- 最流行的国家级域名是什么?不是.cn 也不是.uk
- 解决办法:eclipse查看安卓8.0及以上设备的LOG
- 三星note10 android q,【极光ROM】-【三星NOTE10/NOTE10+/5G N97XX-855】-【V6.0 Android-Q-TE1】...
- python 拆分excel 随机分组
- 图解C/C++底层:函数栈帧的创建和销毁(下篇)
- PVT论文精读:Pyramid Vision Transformer: A Versatile Backbone for Dense Predictionwithout Convolutions
- c++20中的span
- 【R语言】预测模型最合适阈值Cutoff选取及其它指标计算
- MacBook更换开机登录界面壁纸
热门文章
- 【教链一周谈】覆巢之下,安有完卵
- php对参数校验(名称、地址、掩码、日期、时间、端口)
- 第100章 SQL函数 NULLIF
- c语言里amp;amp;,c语言中amp;amp;是什么意思?
- OGRE的学习资源简单总结
- 一步步读懂Pytorch Chatbot Tutorial代码(四) - 为模型准备数据
- [渝粤教育] 东北大学 现代科学运算—MATLAB语言与应用 参考 资料
- 人人都是 LSP?—— 种子与文件下载的相爱相杀
- Tomcat双向SSL认证及CA数字证书安装和配置QQ即时通信协议窥探
- Wi-Fi Display协议介绍