linux檔案系統之高速緩衝區
linux快取記憶體區是採用hash陣列的格式對快取頭資訊進行管理。通過一個free_list指標指向空閒的快取頭節點,快取頭節點間使用雙向連結串列進行連線,並且採用lru快取管理演算法對資料進行維護。
大致見如下筆記圖:
fs/buffer.c原始碼瀏覽
extern int end; // linux核心程式碼的末端,快取記憶體的起始位置在核心程式碼的末端位置。
struct buffer_head * start_buffer = (struct buffer_head *) &end; // 首個buffer_head 的結構
struct buffer_head * hash_table[NR_HASH]; // hash陣列表,對應上圖。其中NR_HASH為307項
static struct buffer_head * free_list; // 空閒的首快取頭指標
static struct task_struct * buffer_wait = NULL; //等待空閒快取塊而睡眠的任務佇列頭指標,用於申請一個快取塊時無可用的空閒快取塊而進行等待加入佇列。
int NR_BUFFERS = 0;
複製程式碼
wait_on_buffer
static inline void wait_on_buffer(struct buffer_head * bh)
{
cli(); // 關中斷,設定程式不可中斷地睡眠在該快取區的b_wait 中。
while (bh->b_lock)
sleep_on(&bh->b_wait); // 需要wake_up明確的進行喚醒。
sti(); // 開中斷
}
複製程式碼
static int sync_dev(int dev)
{
/****
**** 對裝置的快取進行入盤操作
****
****/
int i;
struct buffer_head * bh;
bh = start_buffer; // 遍歷NR_BUFFERS對應的所有快取頭
for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
if (bh->b_dev != dev)
continue;
wait_on_buffer(bh); // wait for b_lock util get lock.
if (bh->b_dirt)
ll_rw_block(WRITE,bh); // refresh dirty content to disk
}
return 0;
}
複製程式碼
#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
該巨集定義的是hash演算法,通過對dev和block進行冪次計算,然後計算其所屬於的陣列位置。
#define hash(dev,block) hash_table[_hashfn(dev,block)]
返回hash表的首指標位置
複製程式碼
remove_from_queues 從快取中摘除該快取頭
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;
// 調整前後指標
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前後的free node,且當其等於free_list時,進行更正
if (free_list == bh)
free_list = bh->b_next_free;
}
複製程式碼
insert_into_queues
static inline void insert_into_queues(struct buffer_head * bh)
{
/* put at end of 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;
// 將buffer_head插入到free_list的最後一個位置,同時更新各個指標的指向
/* 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);
//插入到hash對應dev項的第一個位置
hash(bh->b_dev,bh->b_blocknr) = bh;
bh->b_next->b_prev = bh;
}
複製程式碼
find_buffer
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;
}
複製程式碼
get_hash_table 找到合適的hash table buffer
/*
* 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.
*/
struct buffer_head * get_hash_table(int dev, int block)
{
struct buffer_head * bh;
repeat:
if (!(bh=find_buffer(dev,block)))
return NULL;
bh->b_count++;
wait_on_buffer(bh);
if (bh->b_dev != dev || bh->b_blocknr != block) {
// if bh is not current block,then release it.
brelse(bh);
goto repeat;
}
return bh;
}
複製程式碼
get_blk 找尋一塊合適的快取頭塊
struct buffer_head * getblk(int dev,int block)
{
struct buffer_head * tmp;
repeat:
if (tmp=get_hash_table(dev,block))
return tmp;
tmp = free_list;
// 遍歷free_list連結串列,找到滿足count為0後嘗試上鎖,之後進行上鎖等待,解鎖後探測是否被引用
do {
if (!tmp->b_count) {
wait_on_buffer(tmp); /* we still have to wait */
if (!tmp->b_count) /* on it, it might be dirty */
break;
}
tmp = tmp->b_next_free;
} while (tmp != free_list || (tmp=NULL));
/* Kids, don't try THIS at home ^^^^^. Magic */
// 假設沒有合適的bufferr,等待buffer_wait的釋放
if (!tmp) {
printk("Sleeping on free buffer ..");
sleep_on(&buffer_wait);
printk("ok\n");
goto repeat;
}
tmp->b_count++;
remove_from_queues(tmp);
/*
* Now, when we know nobody can get to this node (as it's removed from the
* free list), we write it out. We can sleep here without fear of race-
* conditions.
*/
if (tmp->b_dirt)
sync_dev(tmp->b_dev);
/* update buffer contents */
tmp->b_dev=dev;
tmp->b_blocknr=block;
tmp->b_dirt=0;
tmp->b_uptodate=0;
/* NOTE!! While we possibly slept in sync_dev(), somebody else might have
* added "this" block already, so check for that. Thank God for goto's.
*/
if (find_buffer(dev,block)) {
tmp->b_dev=0; /* ok, someone else has beaten us */
tmp->b_blocknr=0; /* to it - free this block and */
tmp->b_count=0; /* try again */
insert_into_queues(tmp);
goto repeat;
}
/* and then insert into correct position */
insert_into_queues(tmp);
return tmp;
}
複製程式碼
brelse 釋放快取塊並釋放全域性buffer_wait
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);
}
複製程式碼
buffer初始化
void buffer_init(void)
{
struct buffer_head * h = start_buffer;
void * b = (void *) BUFFER_END;
int i;
while ( (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;
h->b_prev_free = h-1;
h->b_next_free = h+1;
h++;
NR_BUFFERS++;
if (b == (void *) 0x100000)
b = (void *) 0xA0000;
}
h--;
free_list = start_buffer;
free_list->b_prev_free = h;
h->b_next_free = free_list;
for (i=0;i<NR_HASH;i++)
hash_table[i]=NULL;
}
複製程式碼
copyblk彙編指令
#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")
)
複製程式碼