7. Buffer and Multiprocess File 343
7.3 The Function of b_dev, b_blocknr, and Request
7.3.1 Ensure the Correctness of the Data Interaction between
Process and buffer make the interactive data not in units of files but in a buffer block.
Several blocks interact at one time, and the data that is less than the size of one block still occupies one buffer block. Interaction between buffer and disk is still in a block, and a buf- fer block has the same size as a hard block. When a process operates files, the request of document operation, which is proposed by the process, is implemented by the operating system to interact with a specific data block in the hard disk. Because there is a buffer, the data block in the process and hard disk is not in interaction directly but through a buffer.
//code path:include/linux/fs.h:
struct buffer_head {
char * b_data; /* pointer to data block (1024 bytes) */
unsigned long b_blocknr; /* block number */
unsigned short b_dev; /* device (0 = free) */
unsigned char b_uptodate;
unsigned char b_dirt; /* 0-clean,1-dirty */
unsigned char b_count; /* users using this block */
unsigned char b_lock; /* 0 - ok, 1 -locked */
struct task_struct * b_wait;
struct buffer_head * b_prev;
struct buffer_head * b_next;
struct buffer_head * b_prev_free;
struct buffer_head * b_next_free;
};
//code path:kernel/blk_drv/blk.h:
struct request {
int dev; /* -1 if no request */
int cmd; /* READ or WRITE */
int errors;
unsigned long sector;
unsigned long nr_sectors;
char * buffer;
struct task_struct * waiting;
struct buffer_head * bh;
struct request * next;
};
If we want to ensure the correctness of data interaction, first we have to ensure that the data block of the hard disk must strictly correspond with the buffer block.
Because the hard disk device number and block number can only identify a specific hard block, and from the second chapter, we know that each block has only one buffer_head to manage, the strategy of the operating system is as follows: The kernel binds the relation- ship between the buffer block and the hard disk through b_dev and b_blocknr in the buffer_head structure, and this will ensure the uniqueness of the relationship between the disk block and the buffer block, furthermore, it is equivalent in data interaction between the buffer block and the hard disk and the interaction between process and buffer, so as to ensure data interaction without confusion. The code is as follows:
From the code, we can see that when applying for a new one, it will lock in the rela- tionship between the buffer block and the data block. This makes the kernel in the process direction and determines the location of the file and switches it to b_dev and b_blocknr.
Do not consider the relationship between the hard disk data block and the buffer block because the interaction with the hard disk is definitely right finally.
When reading the file, the kernel calculates b_dev and b_blocknr where the file data content is through a file pointer. In the process, it goes to the buffer block. After
//code path:fs/buffer.c:
struct buffer_head * getblk(int dev,int block) //apply for buffer block {
repeat:
if (bh = get_hash_table(dev,block)) //if it is found that buffer //block has been bound with //the specified device (DEV) //and the data block (block) return bh; //return, use it directly
tmp = free_list; //if bound buffer block
//which is in standard is //not found, apply for a new //buffer block
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);
……
/* 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; //set up device number of //new buffer block
bh->b_blocknr = block; //set up block number of new //buffer block
insert_into_queues(bh);
return bh;
}
implementing the bread() function, it should no longer deal with the data block of the hard disk directly. The code is as follows:
It is the same as reading a file. When writing a file, the kernel calculates b_dev and b_blocknr where the file data content is through a file pointer. In the process, it goes to the buffer block. The code is as follows:
//code path:fs/file_dev.c:
int file_read(struct m_inode * inode, struct file * filp, char * buf, int count) //read file {
……
if ((left = count)< = 0) return 0;
while (left) {
if (nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE) { //through the file offset //pointer, calculate the //block number
if (!(bh = bread(inode->i_dev,nr))) //in the actual parameters inode->i_dev is //device number, nr is block number break;
} else bh = NULL;
nr = filp->f_pos% BLOCK_SIZE;
chars = MIN(BLOCK_SIZE-nr, left);
……
}
……
}
//code path:fs/buffer.c:
struct buffer_head * bread(int dev,int block) //read the equipment data of bottom block {
struct buffer_head * bh;
if (!(bh = getblk(dev,block))) //when apply for buffer block, the number //device of equipment and block we will use panic(ôbread: getblk returned NULL\nằ);
if (bh->b_uptodate) return bh;
……
}
//code path:fs/file_dev.c:
int file_write(struct m_inode * inode, struct file * filp, char * buf, int count) //write file
{
……
if (filp->f_flags & O_APPEND) pos = inode->i_size;
else
pos = filp->f_pos;
while (i<count) {
if (!(block = create_block(inode,pos/BLOCK_SIZE))) //through the file offset //pointer, calculate the //block number
break;
if (!(bh = bread(inode->i_dev,block))) //in the actual parameters inode->i_dev is //device number, nr is block number break;
c = pos% BLOCK_SIZE;
p = c + bh->b_data;
bh->b_dirt = 1;
The direction of extending interchange file is the same as the content and manage ment.
When the kernel reads the i node, it calculates the b_dev of the i node and the b_blocknr through the number of the i node and the information in the super block without operating the hard disk data blocks across the buffer directly. The code is as follows:
……
}
……
}
//code path:fs/buffer.c:
struct buffer_head * bread(int dev,int block) //read the equipment data of bottom block //device
{
struct buffer_head * bh;
if (!(bh = getblk(dev,block))) //when apply for buffer block, the number //of equipment and block we will use panic(“bread: getblk returned NULL\n”);
if (bh->b_uptodate) return bh;
……
}
//code path:fs/inode.c:
static void read_inode(struct m_inode * inode) //read the i node {
……
lock_inode(inode);
if (!(sb = get_super(inode->i_dev)))
panic(“trying to read inode without dev”);
block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks + //determine the block (number) //through the number of the //i node and the information of //the super block
(inode->i_num-1)/INODES_PER_BLOCK;
if (!(bh = bread(inode->i_dev,block))) //the inode->i_dev in the actual parameter is //the device number, nr is the block number panic(“unable to read i-node block”);
*(struct d_inode *)inode = ((struct d_inode *)bh->b_data)
[(inode->i_num-1)%INODES_PER_BLOCK]; //extract the i node from the buffer //and load it into the inode_table[32]
brelse(bh);
unlock_inode(inode);
}
//code path:fs/buffer.c:
struct buffer_head * bread(int dev,int block) //read the underlying block device data {
struct buffer_head * bh;
if (!(bh = getblk(dev,block))) //the device number and the block number //needed when allocate the buffer panic(ôbread: getblk returned NULL\nằ);
if (bh->b_uptodate) return bh;
……
}
Similarly to the kernel reading the i node, it calculates the b_dev of the i node and the b_blocknr through the number of the i node and the information in the super block when writing into the i node. The action stops here. The code is as follows:
Similarly, when the kernel loads the super block, it calculates the b_dev and b_blocknr of the super block, the i node bitmap, and the logical block bitmap through the device number and the block number specified; the action continues here. The code is as follows:
//code path:fs/inode.c:
static void write_inode(struct m_inode * inode) //write the i node {
……
if (!(sb = get_super(inode->i_dev)))
panic(“trying to write inode without device”);
block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks + //determine the block (number) //through the i node
//number and the information //of the super block (inode->i_num-1)/INODES_PER_BLOCK;
if (!(bh = bread(inode->i_dev,block))) //the inode->i_dev in the actual parameter //is the device number, and nr is the //block number
panic(“unable to read i-node block”);
((struct d_inode *)bh->b_data) [(inode->i_num-1)%INODES_PER_BLOCK] =
*(struct d_inode *)inode; //extract the i node from the inode_table[32]
//and load it into the buffer bh->b_dirt = 1;
inode->i_dirt = 0;
……
}
//code path:fs/buffer.c:
struct buffer_head * bread(int dev,int block) //read the underlying block device data {
struct buffer_head * bh;
if (!(bh = getblk(dev,block))) //the device number and the block //number needed when allocate the buffer panic(“bread: getblk returned NULL\n”);
if (bh->b_uptodate) return bh;
……
}
//code path:fs/super.c:
static struct super_block * read_super(int dev) //read the super block {
……
s->s_time = 0;
s->s_rd_only = 0;
s->s_dirt = 0;
lock_super(s);
if (!(bh = bread(dev,1))) { //1 is the block number, super block //is the first data block of the //device
s->s_dev = 0;
free_super(s);
return NULL;
}
The code above shows that, in the direction of the process, the accuracy is assured by making sure the device number and the block number correspond to the buffer. And the getblk() function achieves the binding function.
From the hard disk direction, the kernel supports the interaction between the buffer and the hard disk through another request data structure, the device number dev and the first sector number of the block (the block is the operating system concept, and the hard drive only has the concept of a sector) in the request to determine the position of the data interaction, and the value of these two fields is also set by the values of b_dev and b_blocknr in the buffer_head. This shows that, as long as the device number and the block number of the buffer are determined, it is enough to consider the buffer when the kernel interacts with the hard disk through the request. There is no need to extend to the process of considering the file operations.
*((struct d_super_block *) s) =
*((struct d_super_block *) bh->b_data);
brelse(bh);
if (s->s_magic ! = SUPER_MAGIC) { s->s_dev = 0;
free_super(s);
return NULL;
}
……
block = 2; //2 is the block number of the first i node bitmap for (i = 0 ; i < s->s_imap_blocks ; i++)
if (s->s_imap[i] = bread(dev,block)) block++;
else break;
for (i = 0 ; i < s->s_zmap_blocks ; i++) //block continue to //accumulate, load the super //block bitmap according to //this
if (s->s_zmap[i] = bread(dev,block)) block++;
else break;
if (block ! = 2+s->s_imap_blocks+s->s_zmap_blocks) { for(i = 0;i<I_MAP_SLOTS;i++)
brelse(s->s_imap[i]);
……
}
//code path:fs/buffer.c:
struct buffer_head * bread(int dev,int block) //read the underlying block //device data
{
struct buffer_head * bh;
if (!(bh = getblk(dev,block))) //the device number and the block //number needed when allocate the //buffer
panic(ôbread: getblk returned NULL\nằ);
if (bh->b_uptodate) return bh;
……
}
The code is as follows:
//code path:kernel/blk_drv/ll_rw_blk.c:
void ll_rw_block(int rw, struct buffer_head * bh) //operate the underlying block device {
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 item }
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 block to set the //request item
req->cmd = rw;
req->errors = 0;
req->sector = bh->b_blocknr<<1; //use the b_blocknr in the Buffer block to //set the request item
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) {
……
if (!(tmp = dev->current_request)) { dev->current_request = req;
sti();
(dev->request_fn)(); //make hard disk operation command return;
}
……
}
//code path:kernel/blk_drv/hd.c:
void do_hd_request(void) {
……
INIT_REQUEST;
dev = MINOR(CURRENT->dev); //get the device number from the request block = CURRENT->sector; //get the block number from the request if (dev > = 5*NR_HD || block+2 > hd[dev].nr_sects) {
end_request(0);
goto repeat;
}
……
__asm__(“divl%4”:” = a” (block),” = d” (sec):”0” (block),”1” (0), //calculate the heads, //sectors and the cylinders //by the block number
“r” (hd_info[dev].sect));
__asm__(“divl%4”:” = a” (cyl),” = d” (head):”0” (block),”1” (0),
“r” (hd_info[dev].head));
sec++;
nsect = CURRENT->nr_sectors;
……
}
In summary, in the direction of the process, any complex file operation, such as modi- fying, inserting, and deleting data in any part of the file, could ensure the accuracy as long as these two fields (b_dev and b_blocknr) and data block are locked. From the direction of the process, the interaction with the buffer is equivalent to the interaction with the data block in the hard disk.
The situation that additional data is just within a block is shown in Figure 7.3. The additional data between blocks is shown in Figure 7.4.