This appendix explains the following real-time disk I/O concepts:
The methods described in this appendix use digital media buffers (DMbuffers), a real-time data transport facility. See the Digital Media Programming Guide (document number 007-1799-060 or later, hereafter referred to as the DMPG) for more details. The emphasis here is not on how data is acquired from or transported to the video device, but rather on how data is moved to disk in real time.
The DMPG covers basic digital media programming concepts. The directory /usr/share/src/dmedia/video/XTHD contains two simple programming examples:
sdhd_memtovid.c illustrates how video data is copied out of the DMbuffers for the simpler non-real-time case
sdhd_vidtogfx.c illustrates how video data is transferred into memory and from there into graphics
At an abstract level, high-bandwidth throughput is simple; the work is in the details, as explained in this appendix.
| Note: For source code examples for developer I/O, contact the SGI Developer program, for example, through the Web site http://www.sgi.com. |
In IRIX 6.5.4 and later, you can tune how memory is allocated for digital media buffer pools. This capability can offer a performance advantage for some HD I/O applications.
For example, the parameter DM_POOL_NODE_MASK allows you to request memory allocation on particular physical node cards. Each bit in the node mask enables memory allocation on that node. For example, to allocate buffers distributed across nodes 0 and 1, set the node mask to 3:
long long mask = 0x3;
if(dmParamsSetLongLong(plist,DM_POOL_NODE_MASK,mask) ) {
fprintf(stderr, “Error setting dmparam for pool node mask\n”);
exit(-1);
}
if (dmBufferCreatePool(plist, &pool) != DM_SUCCESS) {
fprintf(stderr,”Error creating dmbuffer pool\n”);
exit(-1);
}
|
Note that the node mask is a long long parameter. To see the effect of setting this parameter, use osview -a and examine the free memory on each node. For more information on dmBuffers, see the dmbuffercreatepool(3dm) man page.
The most efficient way to move data on and off a disk device is to use the XFS filesystem with direct I/O mode and large data transfer sizes. If large transfer sizes cannot be achieved, you can combine memory pages from noncontiguous locations using writev(2) or readv(2) . Finally, you can use asynchronous I/O to queue multiple I/O requests to the kernel without waiting for blocked calls to return. Other real-time software features and products, such as REACT, can be used to assure low-latency interrupts and high-priority scheduling, but are not absolutely necessary for digital media applications.
Normally, when a disk file is opened with no status flags specified, a call to write(2) for that file returns as soon as the data is copied to a buffer managed by the device driver (see open(2) ). The actual disk write may not occur until considerable time elapses. A common pool of disk buffers is used for all disk files.
Disk buffering is integrated with the virtual memory paging mechanism. A daemon executes periodically and initiates output of buffered blocks according to the age of the data and the needs of the system. You can force the writing of all pending output for a file by calling fsync(2) or by opening the file and specifying the O_SYNC flag. However, the process blocks until the data is written to disk, and all output data must still be copied from the buffer in the user address space to a buffer in the kernel address space. See Chapter 8, “Optimizing Disk I/O for a Real-Time Program,” in the REACT Real Time Programmer's Guide for details.
If you use the O_DIRECT flag, writes to the file are executed directly from your program's buffer, and the data is not copied to a buffer in the kernel first. Because the filesystem cache is bypassed, your application must manage buffer alignment and block size specification. To use O_DIRECT, you must transfer data in quantities that are multiples of the filesystem block size. The following code shows how to query the filesystem block size and system DMA transfer size limit.
struct dioattr da;
struct stat fileStat;
char *ioFileName = “videodata”;
int ioBlockSize, ioMaxXferSize;
ioFileFD = open(ioFileName,O_DIRECT | O_RDWR | O_CREAT | O_TRUNC,0644);
if (ioFileFD < 0)
return(DM_FAILURE);
if (fcntl(ioFileFD, F_DIOINFO, &da) < 0)
return(DM_FAILURE);
ioBlockSize = da.d_miniosz;
ioMaxXferSize = da.d_maxiosz;
|
The two important constraints of direct I/O with XFS are memory address alignment and buffer length. Direct I/O requires all memory addresses to be page-aligned. XFS requires buffers to be allocated as a multiple of the filesystem block size, ioBlockSize. DMbuffers are guaranteed to be page-aligned, but to ensure that the buffers are properly padded, you must set the buffer size, bytesPerXfer, to the size of the image data you will transfer rounded up to the nearest multiple of ioBlockSize.
VLServer vlServer;
VLPath vlPath;
DMparams * paramsList;
int dmBufferPoolSize = 30; /* 1 second of video */
int vlBytesPerImage = vlGetTransferSize(vlServer, vlPath);
int ioBlocksPerImage = (vlBytesPerImage+ioBlockSize - 1) / ioBlockSize;
int bytesPerXfer = ioBlocksPerImage * ioBlockSize;
if (dmBufferSetPoolDefaults(paramsList,dmBufferPoolSize,bytesPerXfer,
DM_TRUE, DM_TRUE) == DM_FAILURE) {
fprintf(stderr, “error setting pool defaults\n”);
return(DM_FAILURE);
}
|
All Silicon Graphics systems have a configurable maximum DMA transfer size (see systune(1M) ). This value should be compared with the user's I/O request size.
if (bytesPerXfer > ioMaxXferSize) {
fprintf(“DMA request size is too small. Reconfigure with
systune()\n”);
return(DM_FAILURE);
}
|
Some aspects of digital media programming lend themselves to a multiprocessing programming model. On a multiprocessor system, the various tasks of moving multiple streams of video and audio data on and off disk, serial I/O control of external video equipment and input devices, processing of video data, or the transport of video data in and out of the graphics framebuffer can be assigned to different processors. New processes must be created with all virtual space attributes (shared memory, mapped files, data space) shared. The following fragment illustrates how to create a process to perform video recording.
if ((video_recorder_pid = sproc(video_recorder, PR_SADDR|PR_SFDS))<0){
perror(“video_recorder”);
exit(DM_FAILURE);
}
|
If you use multiprocessing, note the following caveats:
When VL calls are made, VL objects such as VLServer, VLPath, VLNode, and so on, are passed through the kernel to the video driver. However, you cannot create any VL objects without first creating a VLServer, from which everything else is instanced.
In a process share group, only one VL call whose arguments derive from a VLServer can execute at a time. This requirement applies even to VL calls that do not explicitly take a VLServer as an argument (for example, vlBufferAdvise(3dm) ).
You can use objects derived from a given VLServer in any number of threads as long as you use a locking scheme, such as usnewsema(3P) or pthread_mutex_init(3P) , to make the use in each thread mutually exclusive of a use in any of the other threads.
The VL error state, returned by vlGetErrno(3dm) , is global to a share group, not per VLServer. If a VL call using one VLServer in one thread executes simultaneously with a VL call using another VLServer in another thread, both calls try to set the error state returned by vlGetErrno(). This call should be global only to the thread, not to the entire process share group.
A synchronous I/O allows an application to process multiple read or write requests simultaneously. On Silicon Graphics platforms, asynchronous I/O is available through the aio facility. This facility, based on sproc(2) 'ed processes, provides all of the benefits of multiprocessing for free. Because multiple I/O requests might be outstanding, when you use asynchronous I/O, the round-trip delay between making a request, having it serviced, and issuing another request is removed. Any process-scheduling delay between these steps is also eliminated.
Because asynchronous I/O operations complete out of sequence, the application must keep track of the order in which data appears in the DMbuffers. DMbuffers are contained in a DMbufferPool; the pool itself is unordered and buffers can be obtained and returned to the pool in any order. Ordering is achieved by a first-in-first-out queue and maintained only while the buffers reside in the queue. Once the buffers are dequeued, the application is free to impose any processing order.