天天看點

Linux-0.11 檔案系統buffer.c詳解

作者:程式員小x

buffer_init

void buffer_init(long buffer_end)           

該函數的作用主要是初始化磁盤的高速緩沖區。

剛開始使用h指針指向了start_buffer的位置。

struct buffer_head * h = start_buffer;
void * b;
int i;           

start_buffer定義為end的位置,即記憶體中system子產品的結束的位置。

struct buffer_head * start_buffer = (struct buffer_head *) &end;           

經過這個步驟之後h實際上指向了核心高速緩沖區的低位址。

接下來使用了b指針指向了核心告訴緩沖區的高位址。

if (buffer_end == 1<<20)
  b = (void *) (640*1024);
else
  b = (void *) buffer_end;           

這裡根據buffer_end的值的不同,決定了b的指向。

在main.c檔案中給定了buffer_end的大小定義:

  • 記憶體大小>12Mb,buffer_end=4Mb
  • 6Mb<記憶體大小<=12Mb,buffer_end=2Mb
  • 如果記憶體大小<=6Mb,buffer_end=1Mb

知道了buffer_end的值,那麼很容易通過條件語句得到b的值。

那麼為什麼要對buffer_end的值進行讨論呢?

這主要是因為實體記憶體中640K-1M的區域記憶體放了顯存和BIOS ROM,是以當buffer_end=1M, 高速緩沖區是一塊, 如果buffer_end>1M, 那麼高速緩沖區是兩塊, 這個點通過下面這張圖可以清晰的了解到。

Linux-0.11 檔案系統buffer.c詳解

buffer_init

經過上述步驟。 h指針(頭指針)指向了高速緩沖區的起點, b指針(資料塊指針)指向了高速緩沖區的終點。

buffer_init接下來要做的就是對這些高速緩沖區進行初始化。

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;
}           

讓每一個buffer_header節點使用b_data指針指向一個資料塊block(1k)。然後h指針加1,b指針減1。如此往複,直到h和b指針指向的差別相交。

這些buffer_header用一個雙向連結清單進行串聯。

需要注意的是, 當記憶體大于6Mb時, 高速緩沖區有兩塊, 當b指針在移動時,如果移動到了1Mb的位址時,也就是到了顯存和BIOS ROM的高位址邊界時, 需要跳過它,直接來到640Kb的位址。也就是下面這兩行代碼。

if (b == (void *) 0x100000)
  b = (void *) 0xA0000;           

h指針和b指針移動進行初始化的效果如下圖所示:

Linux-0.11 檔案系統buffer.c詳解

buffer_init

最後這段代碼, 将free_list指向了第一個buffer_header塊。然後讓首尾兩個buffer_header相接, 形成完整的雙向連結清單。 最後的話,将高速哈希表中的每一行初始化為NULL。

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;           

這裡的free_list翻譯為自由連結清單, 實際意思就是所有的高速緩沖區構成的雙向連結清單, 在下面的函數中的将經常出現。

find_buffer

static struct buffer_head * find_buffer(int dev, int block)           

該函數的作用是從哈希連結清單中按照裝置号和邏輯塊号去查詢對應的緩沖區塊。

首先根據裝置号和塊号查找到哈希數組的下标,找到下标對應的bh, 周遊該bh通過b_next連接配接起來的連結清單, 看該連結清單中是否有比對的。如下圖所示:

Linux-0.11 檔案系統buffer.c詳解

find_buffer

這個過程的代碼如下:

for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)//計算哈希值, 周遊該哈希桶上的連結清單
  if (tmp->b_dev==dev && tmp->b_blocknr==block)  //如果裝置号和塊号相同, 代表找到了, 傳回bh塊
    return tmp;
return NULL;           

get_hash_table

struct buffer_head * get_hash_table(int dev, int block)           

該函數的作用是從哈希連結清單中找到指定的bh塊。其内部調用了find_buffer函數, 内部增加了如果bh塊被其他程序占用情況的處理。

入參中的block指的是磁盤的盤塊号。

從下面的代碼可以看出, 首先調用了find_buffer函數尋找執行的bh塊, 如果沒有找到, 就直接傳回,暗示可能要再去自由連結清單上去搜尋。

for (;;) {
  if (!(bh=find_buffer(dev,block)))
    return NULL;           

如果找到了bh塊,就是看這個塊是否有其他程序加鎖,如果沒有就将引用計數加1。如果有的話就等待鎖的釋放。注意等待過程中,該bh塊可能被修改,是以wake up之後需要重新判斷該塊的裝置号與塊号是否相等。如果在等待過程中,該bh塊沒有被修改, 就直接傳回。 如果被修改了,那麼則需要重新尋找。

bh->b_count++;
wait_on_buffer(bh);
if (bh->b_dev == dev && bh->b_blocknr == block)
  return bh;
bh->b_count--;//該block在等待中被修改。 減少其引用計數, 進入下一次循環,重新尋找符合要求的bh塊。           

getblk

struct buffer_head * getblk(int dev,int block)           

該函數的作用是從哈希連結清單和自由連結清單兩個地方尋找可用的bh塊。

該函數先從哈希連結清單中尋找指定的bh塊。如果找到了就直接傳回。

if ((bh = get_hash_table(dev,block)))
  return bh;           

當從哈希表中沒有找到對應的bh塊時, 就需要去自由連結清單中查找一個品質最好的塊。品質是根據bh塊的b_dirt屬性和b_lock屬性去計算的。 在周遊過程中, 會檢視某個塊是否引用計數為0, 為0則放入備選項, 然後繼續周遊, 如果後續找到了更品質的塊, 就更新該備選項。周遊結束之後就找到了最佳的bh塊。

do {
  if (tmp->b_count)
    continue;
  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);           

如果這個過程, 仍然沒有找到可用的bh塊, 代表現在高速緩沖區很繁忙,則需要等待。

if (!bh) {
  sleep_on(&buffer_wait);
  goto repeat;
}           

如果經過上述步驟找到了一個最佳品質的bh塊之後,如果該塊有鎖,則等待鎖的釋放, 如果沒有鎖, 但是有髒資料, 就将髒資料寫盤。

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;
}           

如果該block已經被添加到哈希連結清單中, 則需要重新尋找。

if (find_buffer(dev,block))
  goto repeat;           

到此為止,終于找到了可用的bh塊,将其初始化,并且插入到哈希連結清單中,這裡實際上實作了一個LRU緩存。有關remove_from_queues和insert_into_queues将在對應的函數講解中詳解。

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);           

remove_from_queues

static inline void remove_from_queues(struct buffer_head * bh)           

該函數的作用是将buffer_header(簡稱bh)從空閑連結清單和哈希隊列中移除。

下面這段代碼作用是将bh從哈希隊列中移除。

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;           

下面這段代碼作用是将bh從自由連結清單中移除。

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;
if (free_list == bh)
  free_list = bh->b_next_free;           

insert_into_queues

static inline void insert_into_queues(struct buffer_head * bh)           

該函數的作用就是将bh插入到空閑連結清單的尾部,并通入哈希函數插入到指定的哈希隊列中。

下面這段代碼的作用就是将bh插入到雙向連結清單的尾部

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;           

下面這段代碼就是将bh插入到哈希表的首部。

bh->b_prev = NULL;
bh->b_next = NULL;
if (!bh->b_dev)
  return;
bh->b_next = hash(bh->b_dev,bh->b_blocknr);
hash(bh->b_dev,bh->b_blocknr) = bh;
bh->b_next->b_prev = bh;           

brelse

void brelse(struct buffer_head * buf)           

該函數的作用是釋放一個bh塊。

将該塊的引用計數減1。

if (!(buf->b_count--))
  panic("Trying to free free buffer");           

bread

struct buffer_head * bread(int dev,int block)           

該函數的作用是用于去指定的裝置上讀取相應的塊。

首先調用getblk在高速緩沖區中找到一個bh塊, 随後調用磁盤讀寫函數ll_rw_block向磁盤設别發出讀請求, 将磁盤内容讀取到bh塊中。

if (!(bh=getblk(dev,block)))
  panic("bread: getblk returned NULL\n");
if (bh->b_uptodate)
  return bh;
ll_rw_block(READ,bh);
if (bh->b_uptodate)//如果緩沖區已經更新, 則直接傳回
  return bh;           

bread_page

void bread_page(unsigned long address,int dev,int b[4])           

該函數的作用是用于去指定的裝置上讀取4個邏輯塊到記憶體中, 也就是讀取4k磁盤内容到一個記憶體頁中。

該函數分為兩個過程, 第一個過程是将磁盤資料塊拷貝到bh塊中。

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]);
  } else
    bh[i] = NULL;           

第二個過程是将bh塊中的内容拷貝到指定的記憶體中。

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]);
  }           

breada

struct buffer_head * breada(int dev,int first, ...)           

breada函數是bread函數的拓展,如果隻傳遞一個塊号, 那麼就是bread。 如果傳遞多個塊号,就會讀取多個邏輯塊的值到高速緩存。

這個函數使用了可變參數清單, 但是其功能與bread類似, 不再贅述。

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)
      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);           

wait_on_buffer

static inline void wait_on_buffer(struct buffer_head * bh)
           

該函數的作用是如果一個bh塊被加鎖, 那麼将等待該bh塊解鎖。

sys_sync

int sys_sync(void)           

該函數的作用是将所有高速緩沖bh塊的髒資料寫盤。

首先将inode_table表中修改的節點重新整理到高速緩沖區的bh塊中,

int i;
struct buffer_head * bh;

sync_inodes();		           

緊接着對所有的bh塊進行循環,将其中有髒資料的bh塊調用ll_rw_block發送寫磁盤指令,将資料寫到磁盤裝置中。

for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
  wait_on_buffer(bh);
  if (bh->b_dirt)
    ll_rw_block(WRITE,bh);
}           

sync_dev

int sync_dev(int dev)           

該函數的作用是将所有高速緩沖中某個設别的bh塊的髒資料寫盤。

該函數總體與sync_sys類似,隻不過在其中增加了dev号的判斷。

invalidate_buffer

static void inline invalidate_buffers(int dev)           

該函數的作用是将所有某個裝置的bh塊中的b_uptodate和b_dirt置為0。

check_disk_change

void check_disk_change(int dev)           

該函數的作用是檢查磁盤是否已經更換。 如果已經更換, 就要對更新高速緩沖區的狀态。

繼續閱讀