Hi,
It seems fairly standard practice to have a couple of device classes supported by an OS: block and character (in addition to display and so on). Fine, I can see the logic of this - some devices like hard disks are block devices in the way they work ans things like com ports are really character devices.
My question is, does the additional overhead introduced by treating character devices as a special case of a block device (block size = 1 byte), really matter? If not, all devices can be treated in the same way by the devfs and as far as I can see, the only waste of processing time is the fact that you will have the occasional 'divide by 1' or 'multiplied by 1' appearing in code (for example, numblocks = totalsize / blocksize where blocksize == 1).
Perhaps this slight waste is made up for by the reduction in complexity of kernel code (one fewer branch in those cases where you have to do switch(devicetype)?
Thoughts appreciated.
Cheers,
Adam
Separate Block and Character Devices
Re: Separate Block and Character Devices
Hi,
In my own design I don't make a difference between classes of devices as far as the kernel interface is concerned - you always send a command to a device and (optionally) a data transfer in whichever direction can take place. In the next level of abstranction, *which* commands are understood by the device depends on its class, the command set for storage devices, files in a filesystem, serial ports or NICs differ completely (and of course there is a standard command set just as well). This removes most of (IMHO unnecessary) complexity from the kernel and moves it to the driver and/or driver framework where it (again IMO) really belongs.
I should add that I am no way heading towards POSIX/UNIX compliance, however, I don't know much about your design, so your mileage my of course vary.
cheers
Joe
I think that this level of overhead in processing time is absolutely negligible as soon as real IO is involved - furthermore it all depends on how the underlying drivers handle your requests. IMO the real differences between char devices and block devices are semantics - the latter are always accessed with absolute addresses while char devices are treated as streams - addressing might not be needed at all, as for example, with a serial port.AJ wrote:My question is, does the additional overhead introduced by treating character devices as a special case of a block device (block size = 1 byte), really matter? If not, all devices can be treated in the same way by the devfs and as far as I can see, the only waste of processing time is the fact that you will have the occasional 'divide by 1' or 'multiplied by 1' appearing in code (for example, numblocks = totalsize / blocksize where blocksize == 1).
In my own design I don't make a difference between classes of devices as far as the kernel interface is concerned - you always send a command to a device and (optionally) a data transfer in whichever direction can take place. In the next level of abstranction, *which* commands are understood by the device depends on its class, the command set for storage devices, files in a filesystem, serial ports or NICs differ completely (and of course there is a standard command set just as well). This removes most of (IMHO unnecessary) complexity from the kernel and moves it to the driver and/or driver framework where it (again IMO) really belongs.
I should add that I am no way heading towards POSIX/UNIX compliance, however, I don't know much about your design, so your mileage my of course vary.
cheers
Joe
Oh, sorry - I didn't say that I'm not heading for POSIX compliance at a basic level either - although to get a few apps running I am hoping to add a *nix compatibility layer at some point (just to provide the expected syscall interface).
In my intended design, all devices are treated in a similar way to files and I have a very much object oriented approach. Each device class has a read function, taking the parameters FileInstance (kind of does the job of FILE*), the start offset and the length to read. I was planning on keeping this interface the same whatever the device class.
As usual, the length actually read is returned - I'm sure you get the picture for the 'write' equivalent!
Thanks for your thoughts.
Cheers,
Adam
In my intended design, all devices are treated in a similar way to files and I have a very much object oriented approach. Each device class has a read function, taking the parameters FileInstance (kind of does the job of FILE*), the start offset and the length to read. I was planning on keeping this interface the same whatever the device class.
As usual, the length actually read is returned - I'm sure you get the picture for the 'write' equivalent!
Thanks for your thoughts.
Cheers,
Adam
I don't think that in the kernel or even object/device interface you do need to distinguish them at all (for compliance AFAIK theres no need in the POSIX standard for this). I'll go for the object-oriented design as well where all devices inherits from some abstract interface, some devices support seek and some does not. As for addressing theres no need to mandate that for an block device since it makes perfect sense to continue reading where last read ended. Likewise for a file type of device theres no real need to support read/write functions directly in the device driver since the same semantics should be possible to implement in the client code using pread/pwrite like functions instead (for unseekable streams that will differ, but then doesn't these really differ more from files than file differs from block devices?).
Everything as block or character devices is trying to fit a square peg in a round hole.
It's true that everything could be serialized in to a stream, but this isn't necessarily desirable. Why would a device driver want to cram its data in to an abstraction it doesn't fit? The consuming application of the device driver data stream just needs to recreate the data from the stream so why was it put in to a stream in the first place?
Everything as a block device isn't inheritance or abstraction, it's reverse inheritance.
Example: On windows every control is a window. All windows can have borders, can be minimized, and can play sounds. Why does a button need to minimize? Why does a text box need to beep? The correct object oriented abstraction would be, some controls need a visual representation and some don't. The ones that do, draw themselves to a rectangle area. Some rectangle areas can be minimized. The ones that don't can be either interacted with or not, the ones that can, can either accept mouse input or keyboard input, the ones that accept keyboard input can have a default font or a special font. Etc.
It's true that everything could be serialized in to a stream, but this isn't necessarily desirable. Why would a device driver want to cram its data in to an abstraction it doesn't fit? The consuming application of the device driver data stream just needs to recreate the data from the stream so why was it put in to a stream in the first place?
Everything as a block device isn't inheritance or abstraction, it's reverse inheritance.
Example: On windows every control is a window. All windows can have borders, can be minimized, and can play sounds. Why does a button need to minimize? Why does a text box need to beep? The correct object oriented abstraction would be, some controls need a visual representation and some don't. The ones that do, draw themselves to a rectangle area. Some rectangle areas can be minimized. The ones that don't can be either interacted with or not, the ones that can, can either accept mouse input or keyboard input, the ones that accept keyboard input can have a default font or a special font. Etc.