Contention Management

MPRES introduces the resource and source datatypes. The resource is merely a mailbox datatype, used for storing the available sources of the resource as messages. The source datatype is a message with two extra fields. The first specifies the task which owns the source (or NIL if none) and the second is a double-pointer structure (same as the mpmsg header) which keeps all the sources of a resource in a ring. Operations are provided to take ( mpres$\_take$()) and give or release ( mpres$\_recv$()) a source of a resource; they are implemented directly with mptsk$\_send$() and mptsk$\_recv$(). Additional operations are provided to initialize a resource of a particular type, and to deinitialize it (see MAN page for exact details).

Figure: MPRES: Resource Datatype Internals
\begin{figure}\par
\begin{verbatim}
Message Header (for message manageme...
...urce
/
Task (if any) which Owns the Resource\end{verbatim}
\par
\end{figure}

The two fields allow all sources (and users) of a resource to be located. Though this feature is not taken advantage of, it could be useful for reclaiming resources from crashed or discourteous tasks. By incorporating the knowledge gained from the waitingfor and owns field of tasks, its also possible to detect deadlock (a cycle of tasks waiting endlessly for each other) before it occurs; when a task is about to be suspended waitingfor a resource, it first checks if any other tasks which have the resource are directly or indirectly already waitingfor it. Because this check potentially involves a great deal of searching for every access to a resource, it is not implemented, but could be, if deadlock is a serious problem. (Nevertheless, users of Mailbox should beware of the base case of deadlock, where a task requests a resource that it already has.)

The MPRES layer defines a few general resources. The most universal resource is mpres$\_memory$. With memory contention management, we can finally write a one-argument mpthd$\_init$():


MPTHDID mpthd_create(MPTHDFN mpthdfn)
{
        int     mydeftasksize=  mpthd_mydef(mpdef_tasksize,int);
        MPTHDID mpthd;

        mpres_take(mpres_memory);               /* CLAIM MEMORY RESOURCE */
        mpthd=  (MPTHDID)malloc(mydeftasksize);
        mpres_give(mpres_memory);               /* RELEASE MEMORY RESOURCE */

        if (mpthd)      mpthd_init(mpthd,mydeftasksize,mpthdfn);
        return(mpthd);
}

Notice how the resource must be claimed and released around the standard malloc memory allocation call. This insures no other task can allocate memory at the same time. Likewise protection must be taken around the free memory deallocation call and other memory management calls. In general, every ``C'' function which accesses resources must be executed within an appropriate resource lock. This is true for any concurrent system. To make the job easier, the MPRES package defines a macro which takes as arguments a resource and a block of code; it claims the resource, executes the code, then (remembers to) free the resource before exiting. MPRES also duplicates many of the common ``C'' function calls with this extra locking added (eg, mpres$\_malloc$()). Operations such as malloc(), which are completed in a single transaction, are well suited for this; in contrast, operations as printf usually require several calls (``or the'' ``Hello'' ``output may not stick'' ``Goodbye'' ``together''), and are best locked explicitly by the caller. Explicit locking is also necessary when calling (library) functions which use resources and are unaware they are executing in a concurrent environment.

One way to simplify the complexities of having to lock and unlock resources is to assume a uniprocessor environment (often the case) and have tasks only yield control explicitly, when they are not holding resources. This may be an acceptable compromise in many circumstances, and Mailbox allows for this style of programming.

MPRES also provides three levels of I/O locking granularity. On the high stream level are the standard streams: mpres$\_stdin$, mpres$\_stdout$, and mpres$\_stderr$. On the middle component level are standard operating system components: mpres$\_filesystem$, mpres$\_network$, mpres$\_terminalin$ and mpres$\_terminalout$. And on the low device level are standard devices: mpres$\_harddisk$, mpres$\_floppydisk$, mpres$\_modem$, mpres$\_printer$, mpres$\_parallel$, and mpres$\_serial$. Locking at a lower granularity level allows for higher degree of concurrency at a cost of complexity and portability. These locking levels are likely to be incomplete and to overlap; more than anything else, they are merely suggested names for resources and their locks. It is up to the programmer to tailor the locking appropriate to his environment and task.