Let the Data Stay in the Buffer as Long as Possible

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

7. Buffer and Multiprocess File 343

7.3 The Function of b_dev, b_blocknr, and Request

7.3.2 Let the Data Stay in the Buffer as Long as Possible

The b_dev and b_blocknr not only ensure accuracy, but also lay the foundation for letting the data stay longer in the buffer.

Whether the data stays in the buffer depends on whether there is a binding relation- ship between the buffer and the data block of the hard disk. The code is as follows:

… …

… …

… …

… …

… …

… …

… …

… …

… …

Process data Buffer zone

Then load into process space

Load into buffer

Disk

File A File B Process data

Process data Buffer zone

Buffer zone Disk

Disk

File A File B

File A File B

Insert data

Reapply buffer block and write the data after jointing

Copy the data from buffer to disk

File C Figure 7.3 Add data within the block.

//code path:fs/buffer.c:

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

struct buffer_head * tmp, * bh;

repeat:

if (bh = get_hash_table(dev,block)) //try to continue to use from the existing //binding relationship of the the buffer return bh;

tmp = free_list;

……

}

struct buffer_head * get_hash_table(int dev, int block)

We can see from the code above that the kernel only uses the device number and the block number when searching the existing buffer from the hash table. The kernel main- tains that the data in the data block still stays in the buffer and can be used directly with- out reading from the hard disk as long as the binding relationship between the buffer and the hard disk data still exists. It saves 100 times the time reading from the hard disk.

After going through the buffer, all the buffers have a binding relationship with the data in the disk, but b_dev and b_blocknr of blocks in the buffer are not needed by the process. If we still cannot find the proper one, then the kernel uses a free buffer not being

… …

… …

… …

… …

… …

… …

… …

… …

… …

Process data

Process data

Process data Buffer zone

Buffer zone

Buffer zone Disk

Disk

Disk

File A File B

File A File B

File A File B File C

Then load into process space

Load into buffer

Reapply buffer block and write the data after jointing

Synchronize the data from buffer to disk

Insert data

Figure 7.4 Add data between two different blocks.

{

……

for (;;) {

if (!(bh = find_buffer(dev,block))) //find the buffer whose device number and //the block number meets the request return NULL;

bh->b_count++;

wait_on_buffer(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) //go through the hash //table to make comparison if (tmp->b_dev = =dev && tmp->b_blocknr = =block)

return tmp; //if find a existing one, return tmp

return NULL; //return NULL if not find a existing one

}

used by the process temporarily (b_count is 0), abolishes the existing binding relationship, and replaces it with a new binding relationship, that is, a new-built buffer block. Until now, the data in the disk data block does not stay in the buffer. The code is as follows:

The meaning of these two lines of code is to build the binding relationship of the buf- fer just allocated and the data block. There will be two cases when allocating a new buffer.

There will be two cases; in the first case, when the system just boots, the buffer does not build any binding relationship with a data block. In the other case, the operating system has been running for some time and has done enough file to read and write operations, so all the buffers have established a binding relationship with the hard disk data block and all the buffers cannot be shared because they do not assort with the b_dev and b_blocknr of the new buffer. So we could only occupy a buffer forcefully from the buffers not used (b_count is 0) by the process. In this situation, these two lines of code have two meanings:

1. To build a new binding relationship between the buffer and the hard disk data block.

2. At the same time, to abolish the existing binding relationship between the buffer and the data block of the hard disk.

It is worth noting that there is not any mechanism or code in the kernel that could release the binding relationship established between the buffer and the hard disk data block deliberately and proactively. Only in compelling circumstances is it replaced by the newly established binding relationship forcefully. The purpose of all these is only to let the data stay in the buffer as long as possible. Now we can see that b_dev and b_blocknr are very important management information of the hard disk data block to stay in the buffer longer.

The design idea of the request is just opposite to the buffer. Its purpose is to let the buffer interact data with the hard disk as soon as possible. As we introduced earlier, there are fields similar to the b_dev and b_blocknr in the request, which are the device number

“dev” and the first sector number “sector”, and they can not only ensure the accuracy of the interaction between the buffer and the hard disk data block, but can let the buffer and the data block interact as soon as possible. Let’s look at the following code:

//code path:fs/buffer.c:

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

……

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

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;

}

Two scenarios could emerge when performing the add_request function: If the hard disk is free, then use it to process the current request. If the hard disk is busy process- ing a request at the moment, here comes a new request and it is inserted into the request queue. The “next” pointer in the request structure is used to build the request, as Figure 7.5 shows.

//code path:kernel/blk_drv/ll_rw_blk.c:

void ll_rw_block(int rw, struct buffer_head * bh) //the underlying block device operation {

unsigned int major;

if ((major = MAJOR(bh->b_dev)) > = NR_BLK_DEV ||

!(blk_dev[major].request_fn)) {

printk(“Trying to read nonexistent block-device\n\r”);

return;

}

make_request(major,rw,bh); //set the request }

static void make_request(int major,int rw, struct buffer_head * bh) {

……

if (req < request) { if (rw_ahead) {

unlock_buffer(bh);

return;

}

sleep_on(&wait_for_request);

goto repeat;

}

req->dev = bh->b_dev; //use the b_dev in the buffer to set the request req->cmd = rw;

req->errors = 0;

req->sector = bh->b_blocknr<<1; //use the b_blocknr set in the buffer to set the request req->nr_sectors = 2;

req->buffer = bh->b_data;

req->waiting = NULL;

req->bh = bh;

req->next = NULL;

add_request(major+blk_dev,req); //load the request item }

static void add_request(struct blk_dev_struct * dev, struct request * req) {

struct request * tmp;

req->next = NULL;

cli();

if (req->bh)

req->bh->b_dirt = 0;

if (!(tmp = dev->current_request)) { //let the current buffer corresponding to the request //interact with the hard disk immediately as long as //the hard disk is free

dev->current_request = req;

sti();

(dev->request_fn)();

return;

}

for (; tmp->next ; tmp = tmp->next) //load the request into the request queue if the hard //disk is busy

if ((IN_ORDER(tmp,req) ||

!IN_ORDER(tmp,tmp->next)) &&

IN_ORDER(req,tmp->next)) break;

req->next = tmp->next; //next pointer is used to set up the queue tmp->next = req;

sti();

}

Now, have a look at the code processing the request in the queue:

Buffer block

Request queue Disk

Figure 7.5 Build the request queue.

//code path:kernel/blk_drv/hd.c:

static void read_intr(void) {

if (win_result()) { bad_rw_intr();

do_hd_request();

return;

}

port_read(HD_DATA,CURRENT->buffer,256);

CURRENT->errors = 0;

CURRENT->buffer + = 512;

CURRENT->sector++;

if (— CURRENT->nr_sectors) { do_hd = &read_intr;

return;

}

end_request(1); //cope with the aftermath after processing a request do_hd_request(); //keep giving interaction command if there remain

//many requests; else, return }

static void write_intr(void) {

if (win_result()) { bad_rw_intr();

do_hd_request();

return;

}

if (— CURRENT->nr_sectors) { CURRENT->sector++;

CURRENT->buffer + = 512;

do_hd = &write_intr;

port_write(HD_DATA,CURRENT->buffer,256);

return;

}

end_request(1); //cope with the aftermath after processing a request do_hd_request(); //keep giving interaction command if there remain

//many requests; else, return }

void do_hd_request(void) {

int i,r;

unsigned int block,dev;

unsigned int sec,head,cyl;

unsigned int nsect;

We can see from the code that, no matter the read interrupt service routine or the write interrupt service routine, they will all call the end_request() function and the do_hd_request() function after processing the interaction of a buffer and the data block.

This produces the loop operation processing the requests in the queue. The macro INIT_

REQUEST in the do_hd_request() function is used to determine whether the loop is completed. If the request is not empty currently, which means there remains buffer space corresponding to the requested need to interact, keep giving the interaction command until all the tasks in the request are processed, and the CURRENT is empty, then return.

This loop performs as shown in Figure 7.6.

Request queue Request queue Request queue

Request queue Request queue

Buffer block Buffer block Buffer block

Buffer block Buffer block Buffer block

Disk Disk Disk

Disk Disk Disk

1 2 3

4 5 6

Figure 7.6 OS process the request queue.

INIT_REQUEST; //determine whether there remaining requests right here dev = MINOR(CURRENT->dev);

block = CURRENT->sector;

if (dev > = 5*NR_HD || block+2 > hd[dev].nr_sects) { end_request(0);

goto repeat;

……

}

//code path:kernel/blk_drv/hd.c:

extern inline void end_request(int uptodate) {

……

wake_up(&CURRENT->waiting);

wake_up(&wait_for_request);

CURRENT->dev = -1;

CURRENT = CURRENT->next; //set the current request be the next, make //preparations for processing the left requests }

#define INIT_REQUEST \ repeat: \

if (!CURRENT) \ //CURRENT is empty meaning there is no request left return; \

if (MAJOR(CURRENT->dev) ! = MAJOR_NR) \

panic(DEVICE_NAME “: request list destroyed”); \ if (CURRENT->bh) {\

if (!CURRENT->bh->b_lock) \

panic(DEVICE_NAME “: block not locked”); \ }

The only purpose of the request design is to let the buffer and the hard disk data block interact with each other as soon as possible.

It is noteworthy that the size of the request is 32 as request[32], so why is it 32 and not 16 or 64?

This is because the data interaction in the host computer has a speed 100 times quicker than in the hard disk, which means that, on average, 100 pieces of buffer data interact with the process while only 1 piece of buffer data interacts with the hard disk. The maximum number of the buffer blocks in the host computer is 3000. The ratio of the buffer to the request is exactly 100, which matches the ratio of their interaction speed. If the request is too great for the hard disk to handle, the request will be idle and waste the memory. If the number of the requests is too small, there is not enough request, leading to the new com- mand cannot be given, and the hard disk is free while the process has no proper buffer to use and suspend frequently; this will reduce the operating efficiency of the entire system.

And 32 is just the right size.

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

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

(524 trang)