Much has changed with the block device layer, and most of those changes happened between the 2.2 and 2.4 stable releases. Here is a quick summary of what was different before. As always, you can look at the drivers in the sample source, which work on 2.0, 2.2, and 2.4, to see how the portability challenges have been handled.
The block_device_operations
structure did not exist
in Linux 2.2. Instead, block drivers used a
file_operations
structure just like char drivers.
The check_media_change and
revalidate methods used to be a part of that
structure. The kernel also provided a set of generic
functions—block_read,
block_write, and
block_fsync—which most drivers used in
their file_operations
structures. A typical 2.2 or
2.0 file_operations
initialization looked like
this:
struct file_operations sbull_bdops = { read: block_read, write: block_write, ioctl: sbull_ioctl, open: sbull_open, release: sbull_release, fsync: block_fsync, check_media_change: sbull_check_change, revalidate: sbull_revalidate };
Note that block drivers are subject to the same changes in the
file_operations
prototypes between 2.0 and 2.2 as
char drivers.
In 2.2 and previous kernels, the request function
was stored in the blk_dev
global array.
Initialization required a line like
blk_dev[major].request_fn = sbull_request;
Because this method allows for only one queue per major number, the multiqueue capability of 2.4 kernels is not present in earlier releases. Because there was only one queue, the request function did not need the queue as an argument, so it took none. Its prototype was as follows:
void (*request) (void);
Also, all queues had active heads, so
blk_queue_headactive
did not exist.
There was no blk_ioctl function in 2.2 and prior
releases. There was, however, a macro called
RO_IOCTLS
, which could be inserted in a
switch
statement to implement
BLKROSET
and BLKROGET
.
sysdep.h
in the sample source includes an
implementation of blk_ioctl that uses
RO_IOCTLS
and implements a few other of the
standard ioctl commands as well:
#ifdef RO_IOCTLS static inline int blk_ioctl(kdev_t dev, unsigned int cmd, unsigned long arg) { int err; switch (cmd) { case BLKRAGET: /* return the read-ahead value */ if (!arg) return -EINVAL; err = ! access_ok(VERIFY_WRITE, arg, sizeof(long)); if (err) return -EFAULT; PUT_USER(read_ahead[MAJOR(dev)],(long *) arg); return 0; case BLKRASET: /* set the read-ahead value */ if (!capable(CAP_SYS_ADMIN)) return -EACCES; if (arg > 0xff) return -EINVAL; /* limit it */ read_ahead[MAJOR(dev)] = arg; return 0; case BLKFLSBUF: /* flush */ if (! capable(CAP_SYS_ADMIN)) return -EACCES; /* only root */ fsync_dev(dev); invalidate_buffers(dev); return 0; RO_IOCTLS(dev, arg); } return -ENOTTY; } #endif /* RO_IOCTLS */
The BLKFRAGET
, BLKFRASET
,
BLKSECTGET
, BLKSECTSET
,
BLKELVGET
, and BLKELVSET
commands were added with Linux 2.2, and BLKPG
was
added in 2.4.
Linux 2.0 did not have the max_readahead
array.
The max_segments
array, instead, existed and was
used in Linux 2.0 and 2.2, but device drivers did not normally need to
set it.
Finally, register_disk did not exist until Linux 2.4. There was, instead, a function called resetup_one_dev, which performed a similar function:
resetup_one_dev(struct gendisk *gd, int drive);
register_disk is emulated in
sysdep.h
with the following code:
static inline void register_disk(struct gendisk *gdev, kdev_t dev, unsigned minors, struct file_operations *ops, long size) { if (! gdev) return; resetup_one_dev(gdev, MINOR(dev) >> gdev->minor_shift); }
Linux 2.0 was different, of course, in not supporting any sort of
fine-grained SMP. Thus, there was no
io_request_lock
and much less need to worry about
concurrent access to the I/O request queue.
One final thing worth keeping in mind: although nobody really knows what will happen in the 2.5 development series, a major block device overhaul is almost certain. Many people are unhappy with the design of this layer, and there is a lot of pressure to redo it.