By Dmitriy Budko
When was the last time you used a RAM drive? Do you still remember what a RAM drive is?
On my first PC (an AT 286, 12 MHz, 2 MB RAM) a RAM disk was the only workable way to use more than 1 MB of memory while running under MS-DOS. Unlike creaky MS-DOS, a current OS like the BeOS usually uses free memory in the file system cache; in general, caching algorithms provide much better performance than a simple RAM disk. Recently though, I was overcome by inexplicable nostalgia for the good old days and I decided to write a driver for that "disk" of yesteryear.
So if you have memory to burn, you may want to try using some of your surplus as a RAM "disk." If nothing else you'll find out that a RAM disk driver is an excellent example of using a device driver not only as a hardware interface but also to emulate it! And if you develop a file system driver, a big RAM disk can speed up the testing.
I assume that you know the basics of BeOS device drivers, so I'll focus on features specific to mass storage devices (floppy drive, IDE disk, IEEE 1394 [FireWire] disk) and on some tricks related to the volatile nature of RAM.
The driver has to support all standard entry points. In addition it has to handle some mass storage device I/O control codes.
I tried to make the driver as simple as possible, so it supports only one RAM disk. Its dynamic configuration is almost nonexistent, and its emulation of the hard drive seek process is very primitive. This simplification let me forget about synchronization problems.
Here's the driver source code. You can find the original source, unaltered for or by e-mail transmission, at:
ftp://ftp.be.com/pub/samples/drivers/obsolete/ramdrive.zip
/****************** cut here ******************************/ #include <OS.h> #include <Drivers.h> #include <KernelExport.h> #include <stdlib.h> status_t vd_open(const char *name, uint32 flags, void **cookie); status_t vd_free (void *cookie); status_t vd_close(void *cookie); status_t vd_control(void *cookie, uint32 msg, void *buf, size_t size); status_t vd_read(void *cookie, off_t pos, void *buf, size_t *count); status_t vd_write(void *cookie, off_t pos, const void *buf, size_t *count); static void format_ram_drive(void* buf); static uchar* create_ram_drive_area(size_t drive_size); static status_t delete_ram_drive_area(void); static void emulate_seek(off_t pos); #define RAM_DRIVE_RELEASE_MEMORY (B_DEVICE_OP_CODES_END+1) #define RAM_DRIVE_EMULATE_SEEK (B_DEVICE_OP_CODES_END+2) #define RAM_DRIVE_SIZE (8*1024*1024) #define RAM_BLOCK_SIZE 512 #define MAX_SEEK_TIME 1000.0 /* microseconds */ #define PREFETCH_BUFFER_SIZE (32*1024) static const char* const ram_drive_area_name = "RAM drive area"; uchar icon_disk[B_LARGE_ICON * B_LARGE_ICON]; uchar icon_disk_mini[B_MINI_ICON * B_MINI_ICON]; int emulate_seek_flag = FALSE; uchar * ram = NULL; static const char *vd_name[] = { "disk/virtual/ram_drive", NULL }; device_hooks vd_devices = { vd_open, vd_close, vd_free, vd_control, vd_read, vd_write }; status_t init_driver(void) { dprintf("vd driver: %s %s, init_driver()\n", __DATE__, __TIME__); ram = create_ram_drive_area(RAM_DRIVE_SIZE); if(ram == NULL) return B_ERROR; return B_NO_ERROR; } void uninit_driver(void) { dprintf("vd driver: uninit_driver()\n"); } const char** publish_devices() { dprintf("vd driver: publish_devices()\n"); return vd_name; } device_hooks* find_device(const char* name) { dprintf("vd driver: find_device()\n"); return &vd_devices; } status_t vd_open(const char *dname, uint32 flags, void **cookie) { dprintf("vd driver: open(%s)\n", dname); return B_NO_ERROR; } status_t vd_free (void *cookie) { dprintf("vd driver: free()\n"); return B_NO_ERROR; } status_t vd_close(void *cookie) { dprintf("vd driver: close()\n"); return B_NO_ERROR; } status_t vd_read(void *cookie, off_t pos, void *buf, size_t *count) { size_t len; status_t ret = B_NO_ERROR; if(pos >= RAM_DRIVE_SIZE) { len = 0; } else { len = (pos + (*count) > RAM_DRIVE_SIZE) ? (RAM_DRIVE_SIZE - pos) : (*count); emulate_seek(pos); memcpy(buf, ram+pos, len); } *count = len; return ret; } status_t vd_write(void *cookie, off_t pos, const void *buf, size_t *count) { size_t len; status_t ret = B_NO_ERROR; if(pos >= RAM_DRIVE_SIZE) { len = 0; } else { len = (pos + (*count) > RAM_DRIVE_SIZE) ? (RAM_DRIVE_SIZE - pos) : (*count); emulate_seek(pos); memcpy(ram+pos, buf, len); } *count = len; return ret; } status_t vd_control(void *cookie, uint32 ioctl, void *arg1, size_t len) { device_geometry *dinfo; dprintf("vd driver: control(%d)\n", ioctl); switch (ioctl) { /* generic mass storage device IO control codes */ case B_GET_GEOMETRY: dinfo = (device_geometry *) arg1; dinfo->sectors_per_track = RAM_DRIVE_SIZE/RAM_BLOCK_SIZE; dinfo->cylinder_count = 1; dinfo->head_count = 1; dinfo->bytes_per_sector = RAM_BLOCK_SIZE; dinfo->removable = FALSE ; dinfo->read_only = FALSE; dinfo->device_type = B_DISK; dinfo->write_once = FALSE; return B_NO_ERROR; case B_FORMAT_DEVICE: format_ram_drive(ram); return B_NO_ERROR; case B_GET_DEVICE_SIZE: *(size_t*)arg1 = RAM_DRIVE_SIZE; return B_NO_ERROR; case B_GET_ICON: switch (((device_icon *)arg1)->icon_size) { case B_LARGE_ICON: memcpy(((device_icon *)arg1)->icon_data, icon_disk, B_LARGE_ICON * B_LARGE_ICON); break; case B_MINI_ICON: memcpy(((device_icon *)arg1)->icon_data, icon_disk_mini, B_MINI_ICON * B_MINI_ICON); break; default: return B_BAD_TYPE; } return B_NO_ERROR; /* device specific IO control codes */ case RAM_DRIVE_RELEASE_MEMORY: return delete_ram_drive_area(); case RAM_DRIVE_EMULATE_SEEK: emulate_seek_flag = *(int*)arg1; return B_NO_ERROR; default: return B_ERROR; } } static void format_ram_drive(void* buf) { static const char format_str[16] = "RAM drive "; uchar* ptr = (uchar*)buf; off_t i; dprintf("vd driver: format_ram_drive(%08x)\n", buf); for(i=0; i<RAM_DRIVE_SIZE/16; i++) { memcpy(ptr, format_str, 16); ptr += 16; } } static uchar* create_ram_drive_area(size_t drive_size) { void* addr; area_id area = find_area(ram_drive_area_name); if(area == B_NAME_NOT_FOUND) { area = create_area (ram_drive_area_name, &addr, B_ANY_KERNEL_ADDRESS,/*kernel team will own this area*/ drive_size, B_LAZY_LOCK, B_READ_AREA | B_WRITE_AREA); if((area==B_ERROR) || (area==B_NO_MEMORY) || (area==B_BAD_VALUE)) addr = NULL; } else { area_info info; get_area_info(area, &info); addr = info.address; } return (uchar*)addr; } static status_t delete_ram_drive_area(void) { area_id area = find_area(ram_drive_area_name); if(area == B_NAME_NOT_FOUND) return B_ERROR; else return delete_area(area); } static void emulate_seek(off_t pos) { static off_t old_pos = 0; if(!emulate_seek_flag) return; if(abs(pos-old_pos)>PREFETCH_BUFFER_SIZE) { old_pos = pos; snooze((int)(rand()* MAX_SEEK_TIME)/RAND_MAX); } } /****************** cut here ******************************/
What happens is that init_driver()
creates the kernel memory
area that's used to store the data. The driver can't use
malloc()
/free()
because when the driver is unloaded the
memory (and all data) is lost. It's actually present
somewhere, but there's no easy way to find it when the
driver is loaded again.
So init_driver()
calls create_ram_drive_area()
, which tries
to find the previously created memory area with the name
"RAM drive area." If the search is unsuccessful the driver
is loaded the first time. In that case
create_ram_drive_area()
creates the memory area. It uses a
currently undocumented flag, B_ANY_KERNEL_ADDRESS
. This flag
gives ownership of this memory area to the kernel, so the
area will not be deleted when the application that opened
the driver quits.
It also uses the B_LAZY_LOCK
flag so the driver doesn't
consume RAM until the first time you use it. I could have
used B_FULL_LOCK
and put the memory allocation in vd_open()
,
but being lazy I didn't want to provide a critical section
synchronization for it.
vd_open()
can be called a few times simultaneously;
init_driver
cannot. publish_devices()
creates the device
file in /dev/disk/virtual/ram_drive
. The
"/virtual/ram_drive
" part of the name is arbitrary, but
"/dev/disk
" is mandatory if you want to use a standard disk
setup program -- like DriveSetup -- to configure the RAM
disk.
vd_open()
, vd_free()
, and vd_close()
do nothing and return
success.
vd_read()
and vd_write()
check to see if the caller tries to
read/write over the end of the disk. They adjust the
requested length accordingly, then simply copy the data
from/to the requested offset in the RAM area to/from the
caller's buffer.
If emulate_seek_flag
is set the driver calls emulate_seek()
.
emulate_seek()
is a crude imitation of real hard drive
mechanics and caching. The current implementation is not in
a critical section so it is capable of multiple concurrent
seeks. It would be great to have a real drive with an
infinite number of head arms! Readers are welcome to create
a more realistic model.
Still, with this function implemented, the RAM drive is better suited for testing a file system driver. It can block the caller's tread as a driver for a real drive would do.
vd_control()
handles four generic I/O control codes for a
mass storage device: B_GET_GEOMETRY
, B_FORMAT_DEVICE
,
B_GET_DEVICE_SIZE
, B_GET_ICON
. The first three are
mandatory; the last one is optional.
This driver has zero-initialized arrays for large and small icons. The real driver should provide more pleasing icons than black rectangles.
RAM_DRIVE_RELEASE_MEMORY
deletes the allocated memory area
and thus destroys all information on the RAM drive.
RAM_DRIVE_EMULATE_SEEK
sets or clears the emulate_seek_flag
.
A control panel application for the RAM drive could send
such commands.
Now here's how to build and install the driver:
kernel_joe
,
kernel_mac
, or kernel_intel
) in your project directory.
Link against this file.
/boot/home/config/add-ons/kernel/drivers
directory.
preferences/DriveSetup
to partition and/or install BFS on
the /dev/disk/virtual/ram_drive
device.
Copyright ©1997 Be, Inc.
Be is a registered trademark, and BeOS, BeBox, BeWare, GeekPort, the Be logo and the BeOS logo
are trademarks of Be, Inc. All other trademarks mentioned are the property of their respective owners.
Comments about this site? Please write us at webmaster@be.com.