Issue 4-21, May 26, 1999

Be Engineering Insights: Kernel Programming on the BeOS: Part 1

By Mani Varadarajan

The kernel team is planning to document several aspects of kernel-level programming in a series of Newsletter articles. This will be useful to anyone who ever intends to write or directly use a driver, and will also be of general interest to those who are simply interested in knowing how the pieces of the kernel fit together.

This article provides an overview of the entire series. Future articles will focus on the various components a developer can contribute—user- and kernel-level drivers, modules, bus managers, busses, etc., and will contain helpful suggestions as to how to properly and safely integrate these pieces into the system.

Introduction

As most of you know, the BeOS kernel itself consists only of core functionality, sufficient to start the boot process and manage memory and threads. Also built into the kernel are the ISA bus manager, the PCI bus manager, the device file system (devfs) which manages /dev, the root file system (rootfs) which handles things in /, and a few other odds and ends.

Since this is nowhere near enough to do anything useful, as early as the boot process the kernel uses add-ons to extend its functionality. File systems, device and bus drivers, for example, are all add-ons that are loaded by the kernel.

These kernel add-ons can be broadly classified into three categories:

  1. device drivers—code that communicates directly with a device

  2. modules—add-ons that export an API for use by other modules or drivers, all within kernel space

  3. file systems—BFS, dosfs, hfs, etc. These will not be covered in this series.

Device drivers and file systems, while they add functionality to the kernel, are accessible from user space, in the sense that you can open them and address them via file descriptors. Modules, however, are kernel-only extensions, since they provide functionality for drivers and other modules. The raison d'etre of modules will become clearer below.

Note: some of this has been covered before in some detail in an article by Arve Hjønnevåg. See "Be Engineering Insights: Splitting Device Drivers and Bus Managers".

What exactly is a device driver?

A device driver is something that actually talks to a specific device or class of devices. The communication usually involves some device-specific protocol—for example, code that specifically addresses a graphics card, an Ethernet card, or serial port is a device driver. Similarly, each piece of code that speaks to a class of devices such as SCSI disks, ATAPI devices, ATA devices, etc., is also a device driver. This code actually understands the device itself and manages it.

What is a module?

As explained above, modules present a uniform API for use by other modules or drivers. This is a useful way of separating out commonly used functionality from a driver.

Take the example of a SCSI device driver talking to a SCSI device. The device hangs off a SCSI bus, which in turn may be one of many busses on the system. All SCSI devices speak a common command set that is independent of the controller used to send the commands. Rather than have each SCSI driver (SCSI disk, SCSI CD, SCSI scanner, etc.) know how to deal with each of the possible types of SCSI cards, it would be nice if there were a generic interface to a SCSI card for each driver to use. This is accomplished by having one module implement each SCSI card, which then presents a generic API.

These modules are further managed by a SCSI "bus manager" module, which knows how to deal with multiple busses and present them in an encapsulated format to each driver. The bus manager's API is what the driver has to deal with, reducing its complexity a great deal. We also have USB, IDE and PCMCIA bus managers.

Another example of the use of the module architecture is a sound driver which publishes a MIDI device. MIDI functionality can be encapsulated in a module so that all sound drivers can access it, avoiding duplication in each driver.

How do these relate to the Kernel?

The kernel provides basic services so that drivers and modules can function. Typically these are

  • Enabling and disabling interrupts.

  • Setting up memory for DMA, etc.

  • Access to other devices and modules.

The kernel also provides access at user level to devices using a "Posixy" API. Devices can be opened by user programs through Posix calls such as open, read, write, and ioctl, which address the devices through file descriptors. These calls turn into system calls in the kernel, and are passed by devfs to the appropriate device driver, which then performs the specified operation.

User- or Kernel-level Driver?

If you want to write a driver for a device, one of the first decisions you need to make is whether it should be at user or kernel level. A kernel-level driver was described above; a user-level driver does the same task by using an extant "raw" driver that knows how to handle that class of device. For example, if you wanted to write a driver for a SCSI scanner, you could write an add-on at user level that opens up the scsi raw device and send commands through scsi raw to the scanner. The alternative would be to write a conventional kernel-level driver to directly speak to the scanner.

The main advantage of operating at kernel level is speed. In this case, one less context switch is required for command completion, so the latency is lower compared to user level. In addition, if your driver needs access to some peculiar hardware, it's rather difficult to do this using a raw driver from user space.

The trade off is that it's much easier to debug at user level. You can use conventional debugging techniques and there's less chance of taking down the entire system in the process.

The above is merely a brief introduction to the topic. Stay tuned for more in-depth pieces from various members of the kernel team.


Developers' Workshop: The High Cost of Memory: Using rtm_alloc()

By Owen Smith

The following transcript comes from a recent interview between myself and Morgan le Be, the editor of SQUONK! Magazine. [NOTE: any resemblance to other magazines, either real or imagined, is entirely coincidental.]

Q:

What is this rtm_alloc(), anyway?

A:

rtm_alloc() is part of a high-performance memory management system that the BeOS genki/6 Media Kit provides.

Q:

Who uses rtm_alloc()?

A:

rtm_alloc() is primarily used in critical portions of the Media Kit. Media node writers can also take advantage of rtm_alloc() in the time-sensitive portions of their code.

Q:

But why wouldn't you just use malloc like everybody else?

A:

To answer this, it's important to understand how malloc works. malloc first tries to reserve memory in RAM to give you what you need. If there isn't enough room in RAM, the kernel's virtual memory system takes over. The kernel then swaps out pages of memory from RAM to your hard drive to make room for your request. In addition, memory created by malloc can be swapped to disk to make room for other memory that needs to be used. For most applications, the overhead incurred by malloc and virtual memory is negligible compared to other performance factors that affect your code, and the advantages offered by virtual memory far outweigh the cost.

For media nodes that must run in real time, however, malloc is not good enough. Why? Because the virtual memory management system costs you time, not only from pushing blocks of memory around, but also from switching between your thread and the kernel. And if this isn't heinous enough, there's always the chance that your memory will be swapped out to disk later on, resulting in possible additional overhead whenever you try to access the memory. For time-sensitive media nodes, this overhead can be crippling, causing audio glitches or other undesirable performance artifacts.

Q:

How does rtm_alloc() solve this problem?

A:

rtm_alloc() is a mechanism which bypasses virtual memory and its vices. As memory management systems go, rtm_alloc() is almost painfully simple. You use it by creating a pool of memory ahead of time. rtm_alloc() then assigns chunks of memory from this pool. In this case, allocating and freeing memory are trivial operations on already extant memory. So, as long the pool has enough available memory to deliver to you, no trip to the kernel is necessary. In addition, the media real-time allocator will lock this pool of memory into RAM, meaning that the memory cannot be paged out by the virtual memory engine. In short, allocating and freeing memory doesn't trigger VM, and the memory you get will stick around in RAM, so there's no VM overhead there.

Q:

What are the drawbacks to using rtm_alloc()?

A:

The power of rtm_alloc() is also its major drawback. Because you're creating and locking a pool of memory for your application to use, that memory is completely dedicated to your application—which means that the effective amount of RAM available for other applications is reduced, and their performance will suffer. Another potential problem you might run into is memory fragmentation, as rtm_alloc() does almost nothing to deal with this issue. For these reasons, we recommend that you only use rtm_alloc() when you absolutely need it.

Although memory is cheap, we realize that not everybody may want to devote a significant chunk of their RAM exclusively to media applications at the expense of the rest of their system. For this reason, the media real-time allocator will only lock its memory pools into RAM if either Real-Time Audio or Real-Time Video is enabled. These two options are set by the user by check boxes in the Media Preferences panel.

Q:

So, how do I use rtm_alloc()?

A:

Here's how it basically works. See the header file RealtimeAlloc.h in the Media Kit for more details.

First, use rtm_create_pool to create the memory pool you wish to use. Because this creates an area, which involves the VM system, you should do this before the real-time madness starts. You give it the size of the pool and the name (which should be B_OS_NAME_LENGTH bytes or less). You get back an opaque pointer, rtm_pool*, that uniquely identifies your pool.

Keep in mind that, when creating pools, you should try to specify the minimum amount of memory that you'll need. Also note that there is some overhead for every pool that you create, and the number of pools you can have is limited, so it's better to create one pool for your purposes rather than several smaller ones.

Another important note about pools: there is always one pool available for use called the "default pool." This is the pool used by the Media Kit for its own real-time allocation needs. I will forego subtlety and say: don't use the default pool! Create your own pool instead, so that you don't starve the Media Kit and associated classes.

rtm_pool* my_pool;
status_t err = rtm_create_pool(&my_pool, 0x1000,
  "My Real-Time Pool");
if (err != B_OK) {
  // there's probably no RAM left for you to lock down:
  // the system is seriously stressed!
  ...
}

Now, once you have a pool, use rtm_alloc() to get the memory you need. You identify the pool and the amount of memory to reserve. If this pool doesn't have enough memory left, not all is lost! rtm_alloc() will try to "grow" the pool by creating a new area big enough to hold the memory, before failing. (Doing this could potentially involve VM, though, so this will cost you.)

size_t size = ...;
void* ptr = rtm_alloc(my_pool, size);
if (! ptr) {
  // Bad news, Buck: we're out of memory!
  ...
}

Once you're done with the memory, simply free it by using rtm_free():

status_t err = rtm_free(ptr);
if (err != B_OK) {
  // ptr was probably NULL
  ...
}

As soon as you're done using the memory pool, be sure to delete it, so that the system can reclaim your RAM for others to use.

status_t err = rtm_delete_pool(my_pool);
if (err != B_OK) {
  // my pool was probably already deleted
  ...
}

For an example of the real-time allocator in action, and how you can redefine the C++ operators new and delete to use real-time allocation, check out the sample code:

<ftp://ftp.be.com/pub/samples/media kit/rtm_test.zip>

This provides a couple of super-simple tests you can run on either regularly allocated objects or rtm_alloc()'ed objects. Hopefully, this code will demonstrate just how significant the performance savings can be, though of course, your actual mileage will vary.

Q:

Can I use rtm_alloc() in other situations than media nodes?

A:

Because rtm_alloc() memory locking is controlled by the "Enable Real-Time Audio/Video" check boxes in the Media Preferences panel, this facility is not really appropriate for non-media applications. Any application can, however, create a pool of memory that is locked into RAM by using areas:

size_t size = ...;

// round up to a multiple of the page size
// this works because B_PAGE_SIZE is a power of 2
size = (size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
void* my_pool;

 area_id area = create_area("My Pool", &my_pool,
  B_ANY_ADDRESS, size, B_FULL_LOCK,
  B_READ_AREA | B_WRITE_AREA);

 if (area < 0) {
  // some error occurred
  ...
}

Doing memory allocation from this pool (perhaps as a C++ allocator template?) is left as an exercise for the reader.


Once More, With Feeling: Life after the PC

By Jean-Louis Gassée

A popular topic. Like the flu, it vanishes for a while, then returns as a newer, more virulent strain. But far from me to complain—I like it when Business Week and Newsweek stir up the pot. When Newsweek describes a brave new Ether in which everyday objects swim in an ocean of IP packets, I'm happy. That's because I look forward to using wireless PDAs and to programming my VCR with a mouse click from a NetPositive page somewhere in the house—or in the world. I'm happy because I see more and more opportunities for the BeOS as the mutltimedia engine in these connected applications.

Unsurprisingly, Bill Gates has views to share on the matter. He argues in the same issue of Newsweek that the PC is with us forever, the center and nexus of this brave new connected world (I paraphrase here, with no warranties expressed or implied). Apparently, his statements are in reaction to predictions that the PC will disappear, giving way to specialized, task-specific devices that do a better job for less money and less aggravation. Part of Chairman Bill's argument is that PCs will become easier to understand, with a simpler user interface. They'll provide the simplicity of a task-specific device, combined with the protean permutations we've come to love in our computers.

Yes, I like personal computers. They're useful, fun, and if you really invest enough time into making them work just right, you'll grow hair on your chest. If I understand correctly, The Chairman is promising chest alopecia some time in our future. Other people agree with him and believe that a collaboration between Intel and Microsoft will deliver us to the Promised Land of The Simple PC. From there, with a careful application of Moore's Law, The Simple PC will become The Free PC, with the marginal cost of the physical device absorbed by a service provider. Just like free cell phones.

This is a seductive strategy. But last weekend, while cleaning the shelves in my home office, I came across a Bob CD-ROM. It promised in the early 90s what The SimplePC promises now—a simple, friendly interface. The fact that it didn't work then doesn't necessarily predict that it won't work in the future. Actually, I find Windows offers a well-designed UI. But it does little to hide the growing complexity underneath. The idea that you can put an interface layer above the gas refinery, and the foul-smelling problems beneath it will never percolate to the surface is just that—an idea—a seductive one without foundation in observed behavior. This is an instance of a well-known sophism: It'll work because it would be cool if it did. That's what we thought of handwriting recognition.

I like PCs for what they do well; I agree with Microsoft's Chairman that they're irreplaceable. But they can be complemented. This isn't an either/or proposition. The Post-PC world is a misnomer—it makes good headlines, but it masks a more complex reality. Just as automobiles differentiated into all sorts of vehicles—some of which General Motors refused to acknowledge for years—everyday computing devices will continue to differentiate. In the twilight of the PC-centric era, we are moving toward a Web-centric stage and our dear PC will be one of many devices happily swimming in the ocean of IP packets.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.