This chapter is really two chapters in one. I will discuss two topics here:
After you read this chapter, you will have an idea of how to draw to an X drawable, and use Motif and PEX together. This chapter is not a tutorial on PEX, nor will you become the world's expert on writing additions to X servers. This chapter will
introduce you to techniques that you can use to add features to the X server. You will also learn where to look for more information about PEX and X servers.
The X Window system is primarily a two-dimensional graphical system. Due to the lack of standards in the three-dimensional (3-D) area, there hasn't been an evolution of good 3-D developmental tool libraries. However, PEX is supposed to alleviate this
problem by providing a consistent set of toolkit calls, which enables a user to support 3-D software with little effort.
PEX originally stood for PHIGS Extensions to X. PHIGS stands for Programmer's Hierarchical Interactive Graphics System. PEX has been adopted by the Common Open Software Environment (COSE) for X11 releases that are later than 2.2.
However, PEX is simply historical at this point, because the last version of the PEX protocol (Version 6.0) is not designed specifically for PHIGS at all. PEX is now designed to support 3-D application programs. PEX is an extension to the Core X
Protocol to provide 3-D graphics support within the X Window environment. Included in the X11R5 distribution is code for the Sample Implementation of the extensions to the X Window server, which implements the functionality defined by the PEX Protocol
Extensions.
In order to access the PEX functional extensions to the X server, one must use an application that generates PEX Protocol. The application can either generate the Protocol bytestream itself, or use something called an Application Protocol Interface
(API). One such API provided with the X11R5 distribution is the PHIGS 3D graphics standard. This is a port of the PHIGS C language binding onto an internal layer, which generates the PEX Protocol enabling this particular PHIGS implementation to work within
the X Window environment. Other alternate APIs are available via anonymous FTP from export.lcs.mit.edu.
When discussing PEX, it is important not to confuse the protocol with the API. The API is the conceptual model of 3-D graphics that the application developer sees when developing a client program. The PEX protocol is generated by the API, and is
interpreted by the X server to perform graphics requests on behalf of the client program.
One API provided with the R5 PEX-SI is a PHIGS/PHIGS-PLUS API. The PHIGS/PHIGS-PLUS standards are specified in two parts. First, a functional description explains each operation conceptually, in a language-independent manner. Second, language bindings
are used to bind the particular PHIGS functions to the semantics of the language. The PEX-SI comes with an application programmer interface that conforms to the latest revision of the PHIGS/PHIGS-PLUS C language binding.
If your version of R5 is patched through patch number 22, you have a second MIT-supplied API called PEXlib. PEXlib is to the PEX protocol what Xlib is to the core X protocol. PEXlib provides an interface that is as close as possible to a one-to-one
correspondence between functions and protocol requests. It is intended to be a systems programming interface (so, people developing graphics toolkits and graphics systems will implement their system on top of PEXlib). It is proposed that the PHIGS API be
ported to PEXlib when PEXlib is finalized. This change would not affect programs written to the existing PHIGS API.
However, because PEXlib is intimately tied to the protocol, it is expected that there will be changes between the current PEXlib (which supports Version 5.0 and 5.1 of the PEX protocol) and the PEXlib that supports the next major version of the PEX
protocol, Version 6.0. Naturally, every attempt will be made to make the changes to the API minimal. The nature of the changes from 5.1 to 6.0 are not such that every primitive will be affected; rather the changes deal with the sticky problems of subsets,
multibuffering, and other issues of global rendering semantics.
Find out whether you have PEX available on your X server first. By default, PEX support is not built into the servers you received for Linux. The xdpyinfo command displays all the extensions supported by a server.
Output from the PEX command should contain strings of the following form:
number of extensions: 7 XTestExtension1 SHAPE MIT-SHM X3D-PEX (< This is the line you are looking for) Multi-buffering MIT-SUNDRY-NONSTANDARD
If one of the extensions listed is X3D-PEX, your server supports PEX. If you do not see this line, you must build the server yourself.
To build a server that only includes the drivers you need, use the LinkKit instead of compiling the complete X system. Using the LinkKit package is much easier. The LinkKit package can be found in /usr/X386/lib/Server.
The LinkKit package contains a file called site.def for you to edit. The site.def file contains the site-specific information for your system. Edit the site.def file to define which servers you want to build, and the drivers and font renderers (programs
that generate fonts for display) you want to include.
You must run all the commands in this section as root.
Let's examine the site.def file in a bit of detail. See Listing 55.1 for the site.def file that I used to create a PEX server for my machine.
Listing 55.1. Sample site.def file.
XCOMM $XFree86: mit/server/ddx/x386/LinkKit/site.def.LK,v 2.11 1994/04/10 05:49:56 dawes Exp $ /* Configuration file for Server Link Kit */ #ifdef BeforeVendorCF /* * Change these definitions if you need to override the defaults: */ /* * HasGcc: defaults: * SVR3,4: YES * Mach, 386bsd: YES */ /* #define HasGcc NO */ /* * HasGcc2: (should also set HasGcc) * defaults: * SVR3,4: YES * Mach: YES * 386bsd: NO */ /* #define HasGcc2 NO */ /* * If the link kit you are using was built with gcc2, and you are using * a different compiler: * 1. Install libgcc.a in a directory searched by your 'ld' * 2. Set NeedLibGcc to YES */ #define NeedLibGcc NO /* * Uncomment this if you want to link with the Gnu malloc library */ /* #define GnuMalloc YES */ /* * GnuMallocLib: link-time flags to include the Gnu malloc library. * this is only used when GnuMalloc is set to YES. * defaults: * 386bsd: -lgnumalloc * others: -lgmalloc */ /* #define GnuMallocLib -L/usr/local/gnu -lmalloc */ /* * Server configuration parameters */ #define FontRenderers Speedo Type1 #define X386Vga2Drivers et4000 et3000 pvga1 gvga tvga8900 ncr \ compaq oak generic #define X386Vga16Drivers et4000 tvga8900 generic #define X386Vga256Drivers et4000 et3000 pvga1 gvga ati tvga8900 cirrus \ ncr compaq oak #define X386Hga2Drivers /**/ /* To enable the hga2 driver, replace the above line with the following */ /* #define X386Hga2Drivers hga6845 */ /* * To include the generic banked monochrome driver in the monochrome server, * uncomment this with one of the following low level drivers * hgc1280 [Hyundai HGC-1280 1280x1024] * sigma [Sigma L-View] * visa [???] * apollo [???] * ... * (list is subject to grow) */ /* #define X386Bdm2Drivers hgc1280 sigma visa apollo */ /* #define XF86S3Drivers mmio_928 s3_generic */ /* * Set which servers to build. Change the YES to NO for servers you don't * want to build. */ /* The SVGA color server */ #define XF86SVGAServer YES /* The 16-color VGA server */ #define XF86VGA16Server NO /* The VGA mono server */ #define XF86MonoServer NO /* The S3 server */ #define XF86S3Server NO /* The IBM 8514/A server */ #define XF86I8514Server NO /* The Mach8 server */ #define XF86Mach8Server NO /* The Mach32 server */ #define XF86Mach32Server NO /* Set the default server (ie the one that gets the sym-link to "X") */ /* #define XFree86DefaultServer XF86_S3 */ /* * If you want PEX (and this link kit was built with PEX support), uncomment * the following */ /* #define BuildPexExt YES */ #define BuildPexExt YES #endif /* BeforeVendorCF */ #ifdef AfterVendorCF /* If you are using a different ProjectRoot, set it here */ /* #ifdef ProjectRoot #undef ProjectRoot #endif #define ProjectRoot /usr/X11R5 */ #endif /* AfterVendorCF */
Note the following items about this site.def file:
For Linux, NeedLibGcc is set to NO.
The servers available to you via LinkKit are shown in Table 55.1. To create any of these servers, you have to set the value of the corresponding variable to YES.
Server Type | Variable To Set |
256-color server | XF86SVGAServer |
16-color server | XF86VGA16Server |
Monochrome server | XF86MonoServer |
S3 server | XF86S3Server |
Mach8 server | XF86Mach8Server |
Mach32 server | XF86Mach32Server |
IBM 8514/A server | XF86I8514Server |
In the sample site.def in Listing 55.1, I have set only the 256-color SVGA server to be built. All other servers will not be built.
The PEX extensions you have do not support the Monochrome server.
The Drivers variables define the video drivers that you want to include in a server. The order of drivers determines the order in which the server probes the video card to determine which driver to use. The generic driver should be the last one included
in the monochrome and 16-color servers because its probe always succeeds.
The generic_s3 driver should be the last one included in the S3 servers for similar reasons.
After you have edited the site.def file, you must create the Makefile.
To build the Makefile, run this command:
# ./mkmf
Then, run make to link the servers that you have configured in the site.def file. This command takes a while. After this command is done, run the make install command to install the new servers:
# make install
Run make clean to remove the files that were created by this procedure.
This frees the directory structures of any unnecessary files, which is important because disk space is at a premium under Linux.
Now start X, run the window manager, and in an xterm use the xdpyinfo command to see whether the PEX extensions are there. It is possible to see which drivers are included in the server by running the X server with the -showconfig flag.
If you are including a driver that it not part of the standard distribution, make a directory in drivers/vga256 (drivers/vga2 if it is for the monochrome server; drivers/vga16 if it is for the 16-color server; or drivers/bdm2 if it is for the bdm2
monochrome server's bdm2 screen). Copy either the source or the .o file, and a suitable Imakefile, into that directory. The name of the directory should be the same as the name of the driver. If you are adding an additional font renderer, put the library
in ./renderers. Look at the VGADriver.Doc file for more details.
You have PEX files on the CD-ROM at the back of this guide.
There are several examples of PEX code available on the Internet. One sample library can be found at the site export.lcs.mit.edu in the /R5contrib/R5contrib-fixes directory as the file PEX.examples.tar.Z.
For Tk and Tcl users, PEXtk is available directly from export.mit.lcs.edu, and is located in /contrib. The files are pextk.PS.tar.Z, pextk.README, and pextk.tar.Z. PEXtk uses X as its windowing system, so the UI is X-based.
Let's look at an example of a simple PEX program that prints the line Howdy World. (Hello World is a bit overused.) The following is the listing for printing this line:
#include "phigs/phigs.h" #include "X11/Xlib.h" #include "X11/Xatom.h" #include "strings.h" char windowName[] = "PEX in Linux"; char HelloStr[] = "Howdy World"; main() { Pconnid_x_drawable connid; Display *display; int screen; Ppoint text_pt; popen_phigs(NULL,0); /* open a conn. to PHIGS server */ /* ** Set the error file name to NULL ** Set default memory size to zero */ connid.display = display = XOpenDisplay( NULL ); screen = DefaultScreen( display ); connid.drawable_id = XCreateSimpleWindow( display, RootWindow( display, screen ), 0, 0, 600, 600, 4, WhitePixel( display, screen ), BlackPixel( display, screen ) ); XChangeProperty( display, connid.drawable_id, XA_WM_NAME, XA_STRING, 8, PropModeReplace, (unsigned char *) windowName , strlen(windowName) ); XMapWindow( display, connid.drawable_id ); popen_ws( 1, &connid, phigs_ws_type_x_drawable ); popen_struct( 1 ); text_pt.x = 0.2; text_pt.y = 0.5; pset_char_ht( 0.05 ); ptext( &text_pt, HelloStr, strlen(HelloStr)): pclose_struct( ); ppost_struct( 1, 1, 1.0 ); printf("Hit return to exit"); getchar(); pclose_ws( 1 ); pclose_phigs( ); }
The first executable line in this file opens a connection to the PEX server with a call to popen_phigs(NULL,0). The NULL parameter sets the error filename to nothing, and the 0 sets the default memory size to 0.
The next lines set the display, screen, and window IDs for this application. The window ID is set to point to the root window for the application with a call to the XCreateSimpleWindow function. The display and screen IDs are set to the defaults. Note
that you are using low-level X Window system function calls to create the root window. This example tells you that it's possible to access all the low-level X Window functions, in addition to the PEX functions.
The XChangeProperty function call sets the window name to the one desired. The display and window ID (connid.drawable_id) are set to that of the root window of the application.
The XMapWindow function maps your display to the current window ID.
The popen_ws() call opens the workspace for the display for you to be able to draw on. You then set the test position for the Howdy World string. Then, open a structure for writing with PEX primitives to set the text_pt structure with the text. It is
necessary to call the pclose_struct() function when done with the drawing area buffer. Lastly, post the structure to ppost_struct.
Wait until the user gives the keystroke you want, and then end the application. In this case, you must call two functions before ending the application. One is a call to pclose_ws( 1 ) for closing the workspace, and the other is a call to pclose_phigs(
) for shutting down the PEX server.
In the previous example, the drawable was used to create the drawing patterns on a drawing area widget. PEXlib by itself is intended to be an interface into the lower-level Xlib. PEXlib gives you the capability to do 3-D mappings, shading, and so on,
along with other geometric transformations.
This section covers the basic act of combining PEXlib with Motif. This way, you can get a drawable area under Motif and be able to use PEX functions on it. In addition to this, you still have the Motif framework to add your own menus to it. The steps to
combine Motif and PEXlib are as follows:
For initializing the toolkit, you must use a long method instead of XtAppInitialize. This long method enables you to select the best visual you can get for PEX. The following code segment will suffice:
XtToolkitInitialize(); app_context = XtCreateApplicationContext(); cp_argc = argc; argv_sz = argc * sizeof(char *); cp_argv = (char **)XtMalloc(argv_sz); memcpy(cp_argv, argv, argv_sz); /* copy the pointers */ display = XtOpenDisplay(app_context, argv[0], argv[0], "pEX", /* Class Name */ NULL, 0, /* no resource options */ &argc, argv); if (display == (Display *) NULL) abort("Unable to open display");
You are making a copy of argc and argv to preserve their values, because the call to XtOpenDisplay() mangles these original values. It's necessary to check whether the display pointer is set to a valid value when you return from XtOpenDisplay() because
the display may not have been opened for a variety of reasons, and you do not want to work a NULL pointer for the display.
Initialize the PEXlib functions with a call to the function PEXInitialize(). The syntax for this call is as follows:
#include <X11/PEX5/PEXlib.h> int PEXInitialize(Display *dp, PEXExtensionInfo *info_ptr, int message_length, char *msg);
The PEXExtensionInfo pointer should point to a structure to which you want this function to return information about the PEX server. The message array should be about 80 characters long, and contain the text for any returned error messages. The function
returns 0 if no errors occurred; otherwise, it returns one of the following values:
Also, note that I used PEX5 as the location of the include files. When PEX 6.0 comes along, you may have to change this PEX5 reference to PEX6 in all the code you have to date. A bummer indeed, but necessary for an upgrade.
The returned PEXExtensionInfo structure is of the following form:
typedef struct { unsigned_short major_version; unsigned_short minor_version; unsigned long release; unsigned long subset_info; char *vendor_name; int major_opcode; int first_event; int first_error; } PEXExtensionInfo;
The major version is usually 5 or 6, and the minor version either 0 or 1. The vendor name and release are vendor-specific. The subset information contains information about the features in your PEX server, and can have the following values:
PEXImmediateMode Enables drawing primitives that are sent directly to the display.
PEXWorkstationOnly Enables workstation resources.
PEXStructureMode Enables drawing primitives to be stored in a structure before being sent to the display.
PEXCompleteImplementation Enables all of these functions.
Check this value to see what features your X server supports. If your server does not have either the complete or Immediate mode graphics (the Linux server is and should be complete), you should exit the application with an error message.
Now create the window with the colormap and the best visual for PEX. The way to do this is as follows:
XStandardColormap colormap; Colormap PEX_colormap; /* open display as before */ screen = DefaultScreen(display); visual = DefaultVisual(display,screen); depth = DefaultDepth(display,screen); status = GetStdColormap(display, screen, visual, depth, &colormap); if (status == True) PEX_colormap = colormap; blue = AllocNamedColor(display, PEX_colormap, "Blue", 0L); white = AllocNamedColor(display, PEX_colormap, "White", 0L); n = 0; XtSetArg(args[n],XmNvisual, visual); n++; XtSetArg(args[n],XmNdepth, depth); n++; XtSetArg(args[n],XmNcolormap, colormap); n++; XtSetArg(args[n],XmNallowResize, True); n++; XtSetArg(args[n],XmNmapWhenManaged, False); n++; XtSetArg(args[n],XmNbackground, blue); n++; XtSetArg(args[n],XmNforeground, white); n++; XtSetArg(args[n],XmNargc, cp_argc); n++; XtSetArg(args[n],XmNargv, cp_argv); n++; XtSetArg(args[n],XmNheight, 400); n++; XtSetArg(args[n],XmNwidth, 400); n++; toplevel = XtAppCreateShell(NULL, "peX", applicationShellWidgetClass, display, args, n); mainWindow = XmCreateMainWindow(toplevel, "mainWin", NULL, 0); drawMe = XmCreateDrawingArea(mainWindow, "pexdraw", args, n);
If you are unfamiliar with programming in Motif or X, please refer to Chapter 32, "Motif for Programmers."
The application requires the toplevel shell. For this shell, you have to manually set its visual, screen, display, and colormap. Note that the resize resource is set to True and mapWhenManaged is set to False. You have to set these values via the XtArgs
args array at creation time for this to work. The toplevel shell is where you create the main window to place your Motif widgets. After you have created the main window with the XmCreateMainWindow function call, you create the drawing area called drawMe on
top of this window.
Next, you add callbacks to the drawing area widget to allow for redrawing. Add the callbacks to the drawing area for the following types of events: resize, expose, and input.
The complete code for a very simple application is shown in Listing 55.2.
Listing 55.2. A sample PEX application with Motif.
#include <Xm/Xm.h> #include <Xm/DrawingA.h> #include <Xm/MainW.h> #include <Xm/RowColumn.h> #include <X11/PEX5/PEXlib.h> #include <stdio.h> int bailout(char *str) { printf ("\n %s", str); exit(1); } int pex_set_line_color(Display *dpy, PEXRenderer p, float r, float g, float b) { PEXColor pc; pc.rgb.red = r; pc.rgb.green = g; pc.rgb.blue = b; PEXSetLineColor(dpy, p, PEXOCRender, PEXColorTypeRGB, &pc); } void doSamplePEX( Display *dpy, Window win, PEXRenderer ren) { PEXCoord coords[10]; PEXBeginRendering(dpy, win, ren); PEXSetLineWidth(dpy, ren, PEXOCRender, 8.0); pex_set_line_color(dpy, ren, 0.5, 0.5, 1.0); coords[0].x = 0.3; coords[0].y = 0.3; coords[0].z = 0.0; coords[1].x = 0.3; coords[1].y = 0.6; coords[1].z = 0.0; coords[2].x = 0.6; coords[2].y = 0.6; coords[2].z = 0.0; coords[3].x = 0.6; coords[3].y = 0.3; coords[3].z = 0.0; PEXPolyline(dpy, ren, PEXOCRender, 4, coords); pex_set_line_color(dpy, ren, 1.5, 0.5, 1.5); coords[0].x = 0.3; coords[0].y = 0.3; coords[0].z = 0.5; coords[1].x = 0.3; coords[1].y = 0.6; coords[1].z = 0.5; coords[2].x = 0.6; coords[2].y = 0.6; coords[2].z = 0.5; coords[3].x = 0.6; coords[3].y = 0.3; coords[3].z = 0.5; PEXPolyline(dpy, ren, PEXOCRender, 4, coords); PEXEndRendering(dpy, win, ren); XFlush(dpy); /* important */ } void quitBtn( Widget w, void *p, void *pp) { exit(0); } void drawBtn( Widget w, XmDrawingAreaCallbackStruct *sp, XtPointer *client_data) { Dimension wd, ht; PEXRenderer *rp; if (sp == NULL) return; switch(sp->reason) { case XmCR_EXPOSE: if (sp->event->xexpose.count == 0) { rp = (PEXRenderer *)client_data; doSamplePEX(XtDisplay(w), XtWindow(w), *rp); } break; case XmCR_INPUT: break; } } int pexInit(Display *dpy, PEXExtensionInfo **pexparms) { int err; char errorMsg[PEXErrorStringLength+1]; PEXExtensionInfo *pex_info; err = PEXInitialize(dpy, pexparms, PEXErrorStringLength, errorMsg); if (err) return False; pex_info = (PEXExtensionInfo *)(*pexparms); if( (pex_info->subset_info & PEXImmediateMode) || ((pex_info->subset_info & 0xffff) == PEXCompleteImplementation)) { return True; } return False; } int main(int argc, char *argv[]) { Widget parent; XtAppContext app_context; int cp_size; int cp_argc; int cp_argv; int status; int screen; int depth; int fore; int bkg; int n; Arg wars[20]; Visual *visual; Colormap colormap; XStandardColormap std_cmp; PEXRendererAttributes pex_attr; Display *dpy; PEXExtensionInfo *pexParms; PEXRenderer ren; Widget mainw; Widget filemenu; Widget menubar; Widget exitBtn; Widget drawme; XtToolkitInitialize(); app_context = XtCreateApplicationContext(); cp_argc = argc; cp_size = argc * (sizeof(char *)); cp_argv = (char **)XtMalloc(cp_size); memcpy(cp_argv, argv, cp_size); dpy = XtOpenDisplay(app_context, NULL, NULL, "pexSample", NULL, 0, &argc, argv); if (dpy == (Display *) NULL) bailout("Cannot open display"); status = pexInit(dpy,&pexParms); if (status == False) bailout("Cannot use PEX"); screen = DefaultScreen(dpy); visual = DefaultVisual(dpy, screen); depth = DefaultDepth(dpy,screen); status = GetStdColormap(dpy, screen, visual, depth, &std_cmp); colormap = std_cmp.colormap; bkg = BlackPixel(dpy,screen); fore = WhitePixel(dpy,screen); n = 0; XtSetArg(wars[n], XmNvisual, visual); n++; XtSetArg(wars[n], XmNdepth, depth); n++; XtSetArg(wars[n], XmNcolormap, colormap); n++; XtSetArg(wars[n], XmNbackground, bkg); n++; XtSetArg(wars[n], XmNborderColor, fore); n++; XtSetArg(wars[n], XmNargc, cp_argc); n++; XtSetArg(wars[n], XmNargv,cp_argv); n++; XtSetArg(wars[n], XmNallowResize, True); n++; XtSetArg(wars[n], XmNmappedWhenManaged, False); n++; XtSetArg(wars[n], XmNwidth, 300); n++; XtSetArg(wars[n], XmNheight, 300); n++; parent = XtAppCreateShell(NULL,"pexSample", applicationShellWidgetClass, dpy, wars, n); n = 0; mainw = XmCreateMainWindow(parent, "mainwindow", wars, n); n = 0; XtSetArg(wars[n], XmNresizePolicy, XmRESIZE_ANY); n++; XtSetArg(wars[n], XmNbackground, bkg); n++; XtSetArg(wars[n], XmNborderColor, fore); n++; drawme = XmCreateDrawingArea(mainw, "da", wars, n); XtAddCallback(drawme, XmNexposeCallback, (XtCallbackProc)drawBtn, (XtPointer) &ren); XtAddCallback(drawme, XmNinputCallback, (XtCallbackProc)drawBtn, (XtPointer) &ren); XtAddCallback(drawme, XmNresizeCallback, (XtCallbackProc)drawBtn, (XtPointer) &ren); XtManageChild(drawme); XtManageChild(mainw); XtRealizeWidget(parent); pex_set_color_approx(dpy,XtWindow(drawme), &std_cmp, &pex_attr); XtMapWidget(parent); ren = PEXCreateRenderer(XtDisplay(mainw), XtWindow(drawme), PEXRAColorApproxTable, &pex_attr); if (ren == 0) { printf("\n Bad renderer \n"); exit (1); } XtAppMainLoop(app_context); return(0); }
A few points to note about Listing 55.2 are that the immediate mode was used for rendering on the screen. PEX-SI provides no support for double buffering. This is a serious bug because lack of double buffering hinders performance.
Even worse, the PEX-SI API assumes that the client desires an XClearArea on the window before each frame is drawn. This causes unnecessary flickering while the screen is cleared and redrawn on all primitive drawing calls. What should have been done was
to provide an end-of-render procedure hook, with the default hook installed to do a clear area function call.
Individual vendors (because of market pressure) have provided their own solutions to the double buffering problem. (Most PHIGS workstations do double buffering. If you do immediate mode you get single buffering along with the PEX-SI's XClearArea call.)
A final word about adding too many features in an X server. The more you add to the X server, the more memory it chews up in your system. Unless you absolutely require PEX (or other) support, do not add it to your X server, especially if RAM is 8MB or
less. The PEX support for Linux added about 420KB on my system, which is not a lot, but it does start adding up. Also, the overhead of PEX applications tend to make my 8MB, 486/33 somewhat slow when running PEX demos, which leads me to believe that PEX on
Linux with less than 16MB is not worth the hassle. If you are serious about PEX on Linux, get a faster machine and put gobs of memory on it. The performance improved very dramatically on a 486/66 with 32MB of RAM.
If you would like more information and examples of source code for PEX, check out these FTP sites:
This chapter has been a whirlwind tour of PEX. The topic of PEX could be a guide in itself. In fact, there are several texts available that go into excruciating detail about PEX. You should have learned the following information from this chapter:
Where to look for more information about PEX and Linux.
fd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0) { myabort("Unable to bind socket"); } for (;;) { /* wait here */ n = recvfrom(sockfd, mesg, MAXM, 0, (struct sockaddr *)&clientInfo, sizeof(struct sockaddr)); doSomethingToIt(mesg); sendto(sockfd,mesg,n,0, (struct sockaddr *)&clientInfo, sizeof(struct sockaddr)); } }
As you can see, the two function calls to process each message make this an easier implementation than a connection-oriented one. However, you have to process each message one at a time because messages from multiple clients can be multiplexed together.
In a connection-oriented scheme, the child process always knows where each message originated.
The client does not have to call the connect() system call either. Instead, the client can call the sendto() function directly. The client side is identical to the server side, with the exception that the sendto call is made before the recvfrom() call.
#include <sys/types.h> #include <sys/socket.h> int sendto((int sockfd, const void *message__, /* the pointer to message */ int length, /* of message */ unsigned int type, /* of routing, leave 0 * const struct sockaddr * client, /* where to send it */ int length ); /* of sockaddr);
If you are a BSD user, use the sendto() call, do not use sendmsg() call. The sendto() call is more efficient.
Any errors are indicated by a return value of -1. Only local errors are detected.
The recvfrom() system call is defined as follows:
#include <sys/types.h> #include <sys/socket.h> int recvfrom(int sockfd, const void *message__, /* the pointer to message */ int length, /* of message */ unsigned int flags, /* of routing, leave 0 * const struct sockaddr * client, /* where to send it */ int length ); /* of sockaddr);
If a message is too long to fit in the supplied buffer, the extra bytes are discarded. The call may return immediately or wait forever, depending on the type of the flag being set. You can even set time out values. Check the man pages for recvfrom for
more information.
There you have it: the very basics of how to program applications to take advantage of the networking capabilities under Linux. We have not even scratched the surface of all the intricacies of programming for networks. A good starting point for more
detailed information would be UNIX Network Programming by W. Richard Stevens, published in 1990 by Prentice Hall. This guide is a classic used in universities and is, by far, the most detailed guide to date.
When two processes want to share a file, the danger exists that one process might affect the contents of the file, and thereby affect the other process. For this reason, most operating systems use a mutually exclusive principle: When one process has a
file open, no other process can touch it. This is called file locking.
The technique is simple to implement. What usually happens is that a "lock file" is created with the same name as the original file but with the extension .lock, which tells other processes that the file is unavailable. This is how many Linux
spoolers, such as the print system and UUCP, implement file locking. It is a brute-force method, perhaps, but effective and easy to program.
Unfortunately, this technique is not good when you must have several processes access the same information quickly because the delays waiting for file opening and closing can grow to be appreciable. Also, if one process doesn't release the file
properly, other processes can hang there, waiting for access.
For this reason, record locking is sometimes implemented. With record locking, a single part of a larger file is locked to prevent two processes from changing its contents at the same time. Record locking enables many processes to access the same file
at the same time, each updating different records within the file, if necessary. The programming necessary to implement record locking is more complex than file locking, of course.
Normally, to implement record locking, you use a file offset, or the number of characters from the beginning of the file. In most cases, a range of characters are locked, so the program has to note the start of the locking region and the length of it,
and then store that information somewhere other processes can examine it.
Writing either file locking or record locking code requires a good understanding of the operating system, but is otherwise not difficult, especially because there are thousands of programs readily available from the Internet, in networking programming
guides, and on BBSes to examine for example code.
Network programming always involves two or more processes talking to each other (interprocess communications), so the way in which processes communicate is vitally important to network programmers. Network programming differs from the usual method of
programming in a few important aspects. A traditional program can talk to different modules (or even other applications on the same machine) through global variables and function calls. That doesn't work across networks.
A key goal of network programming is to ensure that processes don't interfere with each other. Otherwise, systems can get bogged down or lock up. Therefore, processes must have a clean and efficient method of communicating. UNIX is particularly strong
in this regard, because many of the basic UNIX capabilities, such as pipes and queues, are used effectively across networks.
Writing code for interprocess communications is quite difficult compared to single application coding. If you want to write this type of routine, you should study sample programs from a network programming guide or a BBS site to see how they accomplish
the task.
Few people need to write network applications, so the details of the process are best left to those who want them. Experience and lots of examples are the best way to begin writing network code, and mastering the skills can take many years.
ver is compiled and linked to the kernel,
and then placed in the /dev directory. (See Chapter 52, "Working with the Kernel," for more information on adding to the Linux kernel.) Finally, the system is rebooted and the device driver tested. Obviously, changes to
the driver require the process to be repeated, so device driver debugging is an art that minimizes the number of machine reboots!
Two basic don'ts are important for device driver programming. Don't use sleep() or seterror() during interrupt suspensions, and don't use floating-point operations.
Interrupt suspensions must be minimized, but they must be used to avoid corruption of clist (or other buffer) data. Finally, it is important to minimize stack space.
You can simplify debugging device drivers in many cases by using judicious printf or getchar statements to another device, such as the console. Statements like printf and getchar enable you to set up code that traces the execution steps of the device
driver. If you are testing the device when logged in as root, the adb debugger can be used to allow examination of the kernel's memory while the device driver executes. Careful use of adb allows direct testing of minor changes in variables or addresses,
but be careful as incorrect use of adb may result in system crashes!
One of the most common problems with device drivers (other than faulty coding) is the loss of interrupts or the suspension of a device while an interrupt is pending. This causes the device to hang. A time-out routine is included in most device drivers
to prevent this. Typically, if an interrupt is expected and has not been received within a specified amount of time, the device is checked directly to ensure the interrupt was not missed. If an interrupt was missed, it can be simulated by code. You can use
the spl functions during debugging usually helps to isolate these problems.
Block mode-based device drivers are generally written using interrupts. However, more programmers are now using polling for character mode devices. Polling means the device driver checks at frequent intervals to determine the device's status. The device
driver doesn't wait for interrupts but this does add to the CPU overhead the process requires. Polling is not suitable for many devices, such as mass storage systems, but for character mode devices it can be of benefit. Serial devices generally are polled
to save interrupt overhead.
A 19,200 baud terminal will cause approximately 1,920 interrupts per second, causing the operating system to interrupt and enter the device driver that many times. By replacing the interrupt routines with polling routines, the interval between CPU
demands can be decreased by an order of magnitude, using a small device buffer to hold intermediate characters generated to or from the device. Real time devices also benefit from polling, since the number of interrupts does not overwhelm the CPU. If you
want to use polling in your device drivers, you should read one of the guides dedicated to device driver design, as this is a complex subject.
Most Linux users will never have to write a device driver, as most devices you can buy already have a device driver available. If you acquire brand new hardware, or have the adventurous bug, you may want to try writing a driver, though. Device drivers are not really difficult to write (as long as you are comfortable coding in a high-level language like C), but drivers tend to be very difficult to debug. The device driver programmer must at all times be careful of impacting other processes or devices. However, there is a peculiar sense of accomplishment when a device driver executes properly.