Overall Look at the Buffer Block and the Request Item

Một phần của tài liệu The art of linux kernel design (Trang 423 - 426)

7. Buffer and Multiprocess File 343

7.7 Overall Look at the Buffer Block and the Request Item

The b_dev and b_blocknr are flags of data that stays in the buffer block in the practical application of the buffer block; the kernel did not remove these two fields intentionally.

This means that, if it applies for the buffer block continuously, then all buffer blocks would be bound to the data block soon. At this time, if it continues to apply for the buffer block, it can only replace the old binding relationship with the new binding relationship, and the data in this buffer block has become invalid. This reflects a strategy that lets the buffer data stay in the buffer as long as possible.

In order to make the data stay for a long time, the kernel does as much as possible not to apply for the new buffer block, and it had better use the binding relationship that has been established, and if there is no other way to go, then apply for a new one. This approach is embodied in the code as follows:

Process A Process B Process C

tmp

……

……

task_struct task_struct task_struct

Kernel stack NULL b_wait

bh

Figure 7.34 Process A was woken up and exited from the process wait queue.

//code path:fs/buffer.c:

struct buffer_head * getblk(int dev,int block) //apply for the buffer block {

repeat:

if (bh = get_hash_table(dev,block)) //if it is found that a buffer block has //already bound

//with the specify data block (block) of

//the specified

//device (dev)

return bh; //use the off-the-shell directly

tmp = free_list; //if the binding buffer block according //with the specified standard

//can’t be found, then apply for a

//new buffer block

do {

……

/* and repeat until we find something good */

} while ((tmp = tmp->b_next_free) ! = free_list);

……

}

From the code, it is not difficult to find that the kernel searches the hash table first, then compares b_dev and b_blocknr to analyze whether it can still be used. If not, then it executes the loop do... While and applies for a new buffer block.

Here we look at the scene of applying for a new buffer block.

The code is shown as below:

The present situation is that the hash table has been traversed, and all the buffer blocks in the buffer can’t be used by the process, so the kernel must apply for a new buffer block. When the kernel apples for the new buffer block, it should start the search from the header of free_list without destroying the buffer block that has bound with the data block as much as possible. Let them remain in the buffer for a while. If there is really no other way (such as all buffer blocks in the buffer have bound with data blocks), it has to replace the old relationship the new relationship.

The implementation in the circulation starts under this premise.

It does not analyze the b_uptodate field in the circulation because the searching hash table in front has confirmed that no suitable buffer block can still be used. This means one thing for the current process that all the buffer blocks in the buffer are not available. It does not matter whether they are updated or not as long as b_uptodate is 1 or 0. So, at this time, there is no need to analyze values of b_uptodate.

It judges whether the b_count is 0 or not first in the loop. If not, it shows that the buf- fer block is shared by other processes. The current process can’t abolish the buffer block that is being shared by another process, and this buffer block cannot be used, so the kernel should apply for a new buffer block for it. If the b_count 0 buffer block cannot be found in the buffer, this process can only be suspended.

The code is as shown below:

//code path:fs/buffer.c:

#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)

struct buffer_head * getblk(int dev,int block) //apply for the buffer block {

……

tmp = free_list;

do {

if (tmp->b_count) //if the buffer block is occupied, then //skip the cycle

continue;

if (!bh || BADNESS(tmp)<BADNESS(bh)) { //weigh BADNESS and select the buffer block bh = tmp;

if (!BADNESS(tmp)) break;

}

/* and repeat until we find something good */

} while ((tmp = tmp->b_next_free) ! = free_list);

……

}

//code path:fs/buffer.c:

#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)

struct buffer_head * getblk(int dev,int block) //apply for the buffer block {

……

tmp = free_list;

do {

if (tmp->b_count) //if the buffer block is //occupied, skip the loop

If a buffer block with b_count as 0 has been found, there are about two fields that will be further chosen. The one is b_dirt, and the other is b_lock. If these two fields are 0, the buffer block is appropriate, and it can be used directly. If b_lock is 1 or b_dirt is 1, then which is more appropriate? Comparing the two choices, b_lock at 1 is the favorable one.

The reason is that if there is a 1 in these two fields, the current process cannot be used cer- tainly, and it has to wait. In contrast, the less time the better. B_lock is 1, indicating that the buffer block is interacting data with the hard disk. When it has finished, it will be used by the current process finally. However, when b_dirt is 1 it indicates that before creating a new binding relationship, it needs to synchronize data to the hard disk definitely. When it synchronizes the data, it should be locked definitely, and b_lock is set to 1. Therefore, the choice that b_lock is 1 to b_dirt is 1 is less waiting time than the choice that b_dirt is 1 to b_lock is 1. This can also be seen in the code.

The code is as follows:

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

if (!bh) { //b_count 0 buffer block can’t be found finally sleep_on(&buffer_wait); //the current process has to be suspended goto repeat;

}

……

}

//code path:fs/buffer.c:

#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)

struct buffer_head * getblk(int dev,int block) //apply for the buffer block {

……

tmp = free_list;

do {

if (tmp->b_count) //if the buffer block is occupied,skip the loop 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);

if (!bh) { //b_count 0 buffer block can’t be found finally sleep_on(&buffer_wait); //the current process has to be suspended goto repeat;

}

……

while (bh->b_dirt) { //if b_dirt 1 buffer block has been applied, //write directly to the disk.

//If write outside, then use the current process sync_dev(bh->b_dev);

wait_on_buffer(bh);

if (bh->b_count) goto repeat;

}

……

}

Visibly, not applying for the b_dirt 1 buffer block first will allow the process to imple- ment as soon as possible, and it is more beneficial. So in #define BADNESS(bh) (((bh)->b_

dirt<<1)+(bh)->b_lock), we should move b_dirt one bit to the left in order to make it gain a higher weight. BADNESS(tmp)<BADNESS(bh), in this line of logic, make b_dirt be applied as late as possible when b_dev, b_blocknr, and b_count are under the same conditions.

Một phần của tài liệu The art of linux kernel design (Trang 423 - 426)

Tải bản đầy đủ (PDF)

(524 trang)