Fusion:Device

From DirectFBWiki

Table of contents

Overview

The Fusion kernel device is in the kernel source tree at drivers/char/fusion. It uses the module_init and module_exit macros to implement a kernel module, with fusion_init() and fusion_exit() implementing the module startup/shutdown.

The kernel device is used to implement interprocess communication when fusion is built in the multi-app configuration, allowing multiple processes to access the same fusion items. It uses memory allocated in kernel memory (with kmalloc) to hold the items and the structures needed to keep track of them.

Not all fusion abstractions need the kernel driver. The ones that do are:

Idioms

Lists

The kernel module makes use of linked lists in numerous places, mostly to hold all instances of a particular item type. These are generally (always?) implemented by putting a FusionLink structure as the first element of the structures that can be saved in a list. This allows all lists to be manipulated with three common functions: fusion_list_prepend(), fusion_list_remove(), fusion_list_move_to_front(). In addition there's a fusion_list_foreach() macro that expands to a for statement that iterates from the oldest to the newest item in the list (i.e. last to first since new items are prepended to the head of lists).

Locking

Granular locking of individual items in the fusion module is implemented with a spinlock element (called lock) in the item's structure. In addition, the FusionDev structure keeps one spinlock for each type of item, which is used while manipulating the list containing that item type. So, for example, to lock a particular item, first the item-type is locked, the item is located in the list of all items of the type, the item is locked, and finally the item-type is unlocked. Also, since the most recently locked item is likely to be used again soon, it is moved to the front of the list so that it is found more quickly the next time.

IDs

Item IDs are generated by keeping a last_id for each item type in the global device structure. When a new item is created this counter is incremented and the new value becomes the item's unique id.

World

You start using fusion with a call to fusion_init() (in the fusion library), specifying which world you want to use (or -1 to choose the next available world). (The DirectFB session defines which fusion world to use. If no session is specified on the command line, it gets the current session from the environment variable DIRECTFB_SESSION) Each world is implemented by an independent fusion device in the /dev/fusion directory (the 1:1 correspondence between a world and its underlying device leads to the terms sometimes being treated synonymously).

Eight character devices are implemented in fusiondev.c, using the major 253 (FUSION_MAJOR), and the minors 0-7. During module initialization register_devices() uses devfs to create the entries it needs, like this:

 $ ls -l /dev/fusion*
 crw-r--r--    1 root     root     253,   0 Jun 30 14:54 /dev/fusion/0
 crw-r--r--    1 root     root     253,   1 Jun 30 14:54 /dev/fusion/1
 crw-r--r--    1 root     root     253,   2 Jun 30 14:54 /dev/fusion/2
 . . .

When the device file is opened (i.e. when someone joins the fusion world), fusion checks its fusion_devs[] array to see if the device has already been opened (i.e. If the world already exists). If not, the device is initialized, including allocating all structures needed to manage the abstractions as well as creating an entry in the /proc file system. If the device is already in use, it checks that it's not opened for exclusive access.

Fusionee

After fusion_init() knows all is well with the world, the caller is added as a new fusionee (an inhabitant in the fusion world associated with the device). The fusionee is assigned a unique id (see idioms) and the process id of the creator is recorded, after which the new fusionee is added to the list of all fusionees in the world. The device stores the fusionee's id (fusion_id) as the private data in the file structure so it can be retrieved again any time that open file handle is used (i.e. there is a 1:1 correspondence between open file handles and fusionees).

The role of the fusionee is to send and receive fusion messages, which are the basis for all other abstractions. Messages and the other fusion abstractions are discussed below.

Device Methods

ioctl()

The main role of the fusion devices (and thus the whole kernel module) is to respond to about 30 IOCTLs that implement the inter-process work for the multi-app configuration of fusion. There are only three generic IOCTLs, with the rest being just the privileged portion of the implementations of the abstractions, and are therefore not specifically discussed here.

The generic ioctls are:

FUSION_GET_ID gets the ID of calling the fusionee (determined from the open file handle).

FUSION_KILL kills the process of one fusionee, or of all fusionees in the world except the one calling the ioctl. You cannot commit suicide by sending your own fusion ID as the target. Since killing a process releases all files opened by the process, the release method (see below) will clean up any kernel storage for the associated fusionees.

FUSION_SEND_MESSAGE wakes up the read-loop thread of the target fusionee in order to execute calls and reactors, or to cleanly terminate the thread before killing the fusionee.

read()

Reading from a fusion device is an alternate(?) way of retrieving messages that are pending for the associated fusionee. As many messages as fit in the provided buffer will be dequeued and returned. Partial messages will not be returned, so the number of bytes read will almost always be fewer than the number of bytes requested. The header of each message includes its size, which is needed when iterating through the retrieved messages.

This is an asymmetric call since you do not use write to send messages.

poll()

Returns POLLIN|POLLRDNORM to indicate data is ready for reading when messages are pending for the fusionee. Otherwise returns 0 to indicate data is not ready for reading.

open()

See discussion of world and fusionee above.

flush()

When a process is being shut down (PF_EXITING), the flush method ensures that all skirmishes (see below) blocked by the process are dismissed. Otherwise, flush doesn't do anything.

release()

When the file is closes, the associated fusionee is destroyed. The fusionee is removed from the list of all fusionees in the world, the fusionee is woken up in order to unblock any pending operations, and then all abstractions for the fusionee are cleaned up which includes discarding any pending messages queued for the fusionee.

Messages

Along with shared memory, messages are the basis for fusion’s inter-process magic. They are needed to initiate activity in another fusionee’s process, but are also used even when it turns out that the activity needs to happen in the sending fusionee’s process.

A message basically consists of an ID (which is the message type), and data specific to the message ID. Messages are sent to a specific fusionee, using the kernel module as needed to get between processes.

The FusionSendMessage structure is the basis for sending messages, which in addition to the ID and a pointer to the data contains the ID of the recipient fusionee and the size

At present messages are used for three things:

  1. Generic (FMT_SEND = 0)
  2. Making calls (FMT_CALL)
  3. Initiating reactions (FMT_REACTOR)

Messages sending is implemented in fusionee_send_message(), which is called directly by the call and reactor support in the kernel module to send messages themselves. Generic messages are sent in response to the FUSION_SEND_MESSAGE ioctl (Generic messages are currently used only to wake up a fusionee’s read-loop in order to cleanly termina