?? buffer.c
字號(hào):
/*
* linux/fs/buffer.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
/*
* 'buffer.c' implements the buffer-cache functions. Race-conditions have
* been avoided by NEVER letting an interrupt change a buffer (except for the
* data, of course), but instead letting the caller do it.
*/
/*
* NOTE! There is one discordant note here: checking floppies for
* disk change. This is where it fits best, I think, as it should
* invalidate changed floppy-disk-caches.
*/
#include <stdarg.h>
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/locks.h>
#include <linux/errno.h>
#include <asm/system.h>
#include <asm/io.h>
#ifdef CONFIG_SCSI
#ifdef CONFIG_BLK_DEV_SR
extern int check_cdrom_media_change(int, int);
#endif
#ifdef CONFIG_BLK_DEV_SD
extern int check_scsidisk_media_change(int, int);
extern int revalidate_scsidisk(int, int);
#endif
#endif
#ifdef CONFIG_CDU31A
extern int check_cdu31a_media_change(int, int);
#endif
#ifdef CONFIG_MCD
extern int check_mcd_media_change(int, int);
#endif
static int grow_buffers(int pri, int size);
static struct buffer_head * hash_table[NR_HASH];
static struct buffer_head * free_list = NULL;
static struct buffer_head * unused_list = NULL;
static struct wait_queue * buffer_wait = NULL;
int nr_buffers = 0;
int buffermem = 0;
int nr_buffer_heads = 0;
static int min_free_pages = 20; /* nr free pages needed before buffer grows */
extern int *blksize_size[];
/*
* Rewrote the wait-routines to use the "new" wait-queue functionality,
* and getting rid of the cli-sti pairs. The wait-queue routines still
* need cli-sti, but now it's just a couple of 386 instructions or so.
*
* Note that the real wait_on_buffer() is an inline function that checks
* if 'b_wait' is set before calling this, so that the queues aren't set
* up unnecessarily.
*/
void __wait_on_buffer(struct buffer_head * bh)
{
struct wait_queue wait = { current, NULL };
bh->b_count++;
add_wait_queue(&bh->b_wait, &wait);
repeat:
current->state = TASK_UNINTERRUPTIBLE;
if (bh->b_lock) {
schedule();
goto repeat;
}
remove_wait_queue(&bh->b_wait, &wait);
bh->b_count--;
current->state = TASK_RUNNING;
}
/* Call sync_buffers with wait!=0 to ensure that the call does not
return until all buffer writes have completed. Sync() may return
before the writes have finished; fsync() may not. */
static int sync_buffers(dev_t dev, int wait)
{
int i, retry, pass = 0, err = 0;
struct buffer_head * bh;
/* One pass for no-wait, three for wait:
0) write out all dirty, unlocked buffers;
1) write out all dirty buffers, waiting if locked;
2) wait for completion by waiting for all buffers to unlock.
*/
repeat:
retry = 0;
bh = free_list;
for (i = nr_buffers*2 ; i-- > 0 ; bh = bh->b_next_free) {
if (dev && bh->b_dev != dev)
continue;
#ifdef 0 /* Disable bad-block debugging code */
if (bh->b_req && !bh->b_lock &&
!bh->b_dirt && !bh->b_uptodate)
printk ("Warning (IO error) - orphaned block %08x on %04x\n",
bh->b_blocknr, bh->b_dev);
#endif
if (bh->b_lock)
{
/* Buffer is locked; skip it unless wait is
requested AND pass > 0. */
if (!wait || !pass) {
retry = 1;
continue;
}
wait_on_buffer (bh);
}
/* If an unlocked buffer is not uptodate, there has been
an IO error. Skip it. */
if (wait && bh->b_req && !bh->b_lock &&
!bh->b_dirt && !bh->b_uptodate)
{
err = 1;
continue;
}
/* Don't write clean buffers. Don't write ANY buffers
on the third pass. */
if (!bh->b_dirt || pass>=2)
continue;
bh->b_count++;
ll_rw_block(WRITE, 1, &bh);
bh->b_count--;
retry = 1;
}
/* If we are waiting for the sync to succeed, and if any dirty
blocks were written, then repeat; on the second pass, only
wait for buffers being written (do not pass to write any
more buffers on the second pass). */
if (wait && retry && ++pass<=2)
goto repeat;
return err;
}
void sync_dev(dev_t dev)
{
sync_buffers(dev, 0);
sync_supers(dev);
sync_inodes(dev);
sync_buffers(dev, 0);
}
int fsync_dev(dev_t dev)
{
sync_buffers(dev, 0);
sync_supers(dev);
sync_inodes(dev);
return sync_buffers(dev, 1);
}
asmlinkage int sys_sync(void)
{
sync_dev(0);
return 0;
}
int file_fsync (struct inode *inode, struct file *filp)
{
return fsync_dev(inode->i_dev);
}
asmlinkage int sys_fsync(unsigned int fd)
{
struct file * file;
struct inode * inode;
if (fd>=NR_OPEN || !(file=current->filp[fd]) || !(inode=file->f_inode))
return -EBADF;
if (!file->f_op || !file->f_op->fsync)
return -EINVAL;
if (file->f_op->fsync(inode,file))
return -EIO;
return 0;
}
void invalidate_buffers(dev_t dev)
{
int i;
struct buffer_head * bh;
bh = free_list;
for (i = nr_buffers*2 ; --i > 0 ; bh = bh->b_next_free) {
if (bh->b_dev != dev)
continue;
wait_on_buffer(bh);
if (bh->b_dev == dev)
bh->b_uptodate = bh->b_dirt = bh->b_req = 0;
}
}
/*
* This routine checks whether a floppy has been changed, and
* invalidates all buffer-cache-entries in that case. This
* is a relatively slow routine, so we have to try to minimize using
* it. Thus it is called only upon a 'mount' or 'open'. This
* is the best way of combining speed and utility, I think.
* People changing diskettes in the middle of an operation deserve
* to loose :-)
*
* NOTE! Although currently this is only for floppies, the idea is
* that any additional removable block-device will use this routine,
* and that mount/open needn't know that floppies/whatever are
* special.
*/
void check_disk_change(dev_t dev)
{
int i;
struct buffer_head * bh;
switch(MAJOR(dev)){
case FLOPPY_MAJOR:
if (!(bh = getblk(dev,0,1024)))
return;
i = floppy_change(bh);
brelse(bh);
break;
#if defined(CONFIG_BLK_DEV_SD) && defined(CONFIG_SCSI)
case SCSI_DISK_MAJOR:
i = check_scsidisk_media_change(dev, 0);
break;
#endif
#if defined(CONFIG_BLK_DEV_SR) && defined(CONFIG_SCSI)
case SCSI_CDROM_MAJOR:
i = check_cdrom_media_change(dev, 0);
break;
#endif
#if defined(CONFIG_CDU31A)
case CDU31A_CDROM_MAJOR:
i = check_cdu31a_media_change(dev, 0);
break;
#endif
#if defined(CONFIG_MCD)
case MITSUMI_CDROM_MAJOR:
i = check_mcd_media_change(dev, 0);
break;
#endif
default:
return;
};
if (!i) return;
printk("VFS: Disk change detected on device %d/%d\n",
MAJOR(dev), MINOR(dev));
for (i=0 ; i<NR_SUPER ; i++)
if (super_blocks[i].s_dev == dev)
put_super(super_blocks[i].s_dev);
invalidate_inodes(dev);
invalidate_buffers(dev);
#if defined(CONFIG_BLK_DEV_SD) && defined(CONFIG_SCSI)
/* This is trickier for a removable hardisk, because we have to invalidate
all of the partitions that lie on the disk. */
if (MAJOR(dev) == SCSI_DISK_MAJOR)
revalidate_scsidisk(dev, 0);
#endif
}
#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
#define hash(dev,block) hash_table[_hashfn(dev,block)]
static inline void remove_from_hash_queue(struct buffer_head * 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->b_next = bh->b_prev = NULL;
}
static inline void remove_from_free_list(struct buffer_head * bh)
{
if (!(bh->b_prev_free) || !(bh->b_next_free))
panic("VFS: 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;
bh->b_next_free = bh->b_prev_free = NULL;
}
static inline void remove_from_queues(struct buffer_head * bh)
{
remove_from_hash_queue(bh);
remove_from_free_list(bh);
}
static inline void put_first_free(struct buffer_head * bh)
{
if (!bh || (bh == free_list))
return;
remove_from_free_list(bh);
/* add to front 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;
free_list = bh;
}
static inline void put_last_free(struct buffer_head * bh)
{
if (!bh)
return;
if (bh == free_list) {
free_list = bh->b_next_free;
return;
}
remove_from_free_list(bh);
/* add to back 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;
}
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;
/* 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(bh->b_dev,bh->b_blocknr) = bh;
if (bh->b_next)
bh->b_next->b_prev = bh;
}
static struct buffer_head * find_buffer(dev_t dev, int block, int size)
{
struct buffer_head * tmp;
for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)
if (tmp->b_dev==dev && tmp->b_blocknr==block)
if (tmp->b_size == size)
return tmp;
else {
printk("VFS: Wrong blocksize on device %d/%d\n",
MAJOR(dev), MINOR(dev));
return NULL;
}
return NULL;
}
/*
* 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(dev_t dev, int block, int size)
{
struct buffer_head * bh;
for (;;) {
if (!(bh=find_buffer(dev,block,size)))
return NULL;
bh->b_count++;
wait_on_buffer(bh);
if (bh->b_dev == dev && bh->b_blocknr == block && bh->b_size == size)
return bh;
bh->b_count--;
}
}
void set_blocksize(dev_t dev, int size)
{
int i;
struct buffer_head * bh, *bhnext;
if (!blksize_size[MAJOR(dev)])
return;
switch(size) {
default: panic("Invalid blocksize passed to set_blocksize");
case 512: case 1024: case 2048: case 4096:;
}
if (blksize_size[MAJOR(dev)][MINOR(dev)] == 0 && size == BLOCK_SIZE) {
blksize_size[MAJOR(dev)][MINOR(dev)] = size;
return;
}
if (blksize_size[MAJOR(dev)][MINOR(dev)] == size)
return;
sync_buffers(dev, 2);
blksize_size[MAJOR(dev)][MINOR(dev)] = size;
/* We need to be quite careful how we do this - we are moving entries
around on the free list, and we can get in a loop if we are not careful.*/
bh = free_list;
for (i = nr_buffers*2 ; --i > 0 ; bh = bhnext) {
bhnext = bh->b_next_free;
if (bh->b_dev != dev)
continue;
if (bh->b_size == size)
continue;
wait_on_buffer(bh);
if (bh->b_dev == dev && bh->b_size != size)
bh->b_uptodate = bh->b_dirt = 0;
remove_from_hash_queue(bh);
/* put_first_free(bh); */
}
}
/*
* Ok, this is getblk, and it isn't very clear, again to hinder
* race-conditions. Most of the code is seldom used, (ie repeating),
* so it should be much more efficient than it looks.
*
* The algoritm is changed: hopefully better, and an elusive bug removed.
*
* 14.02.92: changed it to sync dirty buffers a bit: better performance
* when the filesystem starts to get full of dirty blocks (I hope).
*/
#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)
struct buffer_head * getblk(dev_t dev, int block, int size)
{
struct buffer_head * bh, * tmp;
int buffers;
static int grow_size = 0;
repeat:
bh = get_hash_table(dev, block, size);
if (bh) {
if (bh->b_uptodate && !bh->b_dirt)
put_last_free(bh);
return bh;
}
grow_size -= size;
if (nr_free_pages > min_free_pages && grow_size <= 0) {
if (grow_buffers(GFP_BUFFER, size))
grow_size = PAGE_SIZE;
}
buffers = nr_buffers;
bh = NULL;
for (tmp = free_list; buffers-- > 0 ; tmp = tmp->b_next_free) {
if (tmp->b_count || tmp->b_size != size)
continue;
if (mem_map[MAP_NR((unsigned long) tmp->b_data)] != 1)
continue;
if (!bh || BADNESS(tmp)<BADNESS(bh)) {
bh = tmp;
if (!BADNESS(tmp))
break;
}
#if 0
if (tmp->b_dirt) {
tmp->b_count++;
ll_rw_block(WRITEA, 1, &tmp);
tmp->b_count--;
}
#endif
}
if (!bh) {
if (nr_free_pages > 5)
if (grow_buffers(GFP_BUFFER, size))
goto repeat;
if (!grow_buffers(GFP_ATOMIC, size))
sleep_on(&buffer_wait);
goto repeat;
}
wait_on_buffer(bh);
if (bh->b_count || bh->b_size != size)
goto repeat;
if (bh->b_dirt) {
sync_buffers(0,0);
goto repeat;
}
/* NOTE!! While we slept waiting for this block, somebody else might */
/* already have added "this" block to the cache. check it */
if (find_buffer(dev,block,size))
goto repeat;
/* OK, FINALLY we know that this buffer is the only one of its 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;
bh->b_req=0;
remove_from_queues(bh);
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -