Document revision date: 15 July 2002 | |
Previous | Contents | Index |
The Fortran specifier READONLY and the SHARED specifier allow multiple processes to open the same file simultaneously, provided that each process uses one of these specifiers when opening the file. The READONLY specifier allows the process read access to the file; the SHARED specifier allows other processes read and write access to the file. If a process opens the file without specifying READONLY or SHARED, no other process can open that file even by specifying READONLY or SHARED.
In the following Fortran segment, if the read operation indicates that the record is locked, the read operation is repeated. You should not attempt to read a locked record without providing a delay (in this example, the call to ERRSNS) to allow the other process time to complete its operation and unlock the record.
! Status variables and values INTEGER STATUS, 2 IOSTAT, 2 IO_OK PARAMETER (IO_OK = 0) INCLUDE '($FORDEF)' ! Logical unit number INTEGER LUN /1/ ! Record variables INTEGER LEN CHARACTER*80 RECORD . . . READ (UNIT = LUN, 2 FMT = '(Q,A)' 2 IOSTAT = IOSTAT) LEN, RECORD (1:LEN) IF (IOSTAT .NE. IO_OK) THEN CALL ERRSNS (,,,,STATUS) IF (STATUS .EQ. FOR$_SPERECLOC) THEN DO WHILE (STATUS .EQ. FOR$_SPERECLOC) READ (UNIT = LUN, 2 FMT = '(Q,A)' 2 IOSTAT = IOSTAT) LEN, RECORD(1:LEN) IF (IOSTAT .NE. IO_OK) THEN CALL ERRSNS (,,,,STATUS) IF (STATUS .NE. FOR$_SPERECLOC) THEN CALL LIB$SIGNAL(%VAL(STATUS)) END IF END IF END DO ELSE CALL LIB$SIGNAL (%VAL(STATUS)) END IF END IF . . . |
In Fortran, each time you access a record in a shared file, that record
is automatically locked either until you perform another I/O operation
on the same logical unit, or until you explicitly unlock the record
using the UNLOCK statement. If you plan to modify a record, you should
do so before unlocking it; otherwise, you should unlock the record as
soon as possible.
28.4 File Access and Mapping
To copy an entire data file from the disk to program variables and back
again, either use language I/O statements to read and write the data or
use the Create and Map Section (SYS$CRMPSC) system service to map the
data. Often times, mapping the file is faster than reading it. However,
a mapped file usually uses more virtual memory than one that is read
using language I/O statements. Using I/O statements, you have to store
only the data that you have entered. Using SYS$CRMPSC, you have to
initialize the database and store the entire structure in virtual
memory including the parts that do not yet contain data.
28.4.1 Using SYS$CRMPSC
Mapping a file means associating each byte of the file with a byte of program storage. You access data in a mapped file by referencing the program storage; your program does not use I/O statements.
Files created using OpenVMS RMS typically contain control information. Unless you are familiar with the structure of these files, do not attempt to map one. The best practice is to map only those files that have been created as the result of mapping. |
To map a file, perform the following operations:
PSECT_ATTR = name, PAGE |
PSECT_ATTR = name, solitary |
Do not initialize variables in a common block that you plan to map; the
initial values will be lost when SYS$CRMPSC maps the common block.
28.4.1.1 Mapping a File
The format for SYS$CRMPSC is as follows:
SYS$CRMPSC [inadr],[retadr],[acmode],[flags],[gsdnam],[ident],[relpag], [chan], [pagcnt],[vbn],[prot],[pfc] |
For a complete description of the SYS$CRMPSC system service, see the OpenVMS System Services Reference Manual.
Starting and Ending Addresses of the Mapped Section
On VAX systems, specify the location of the first variable in the common block as the value of the first array element of the array passed by the inadr argument. Specify the location of the last variable in the common block as the value of the second array element.
On Alpha systems, specify the location of the first variable in the common block as the value of the first array element of the array passed by the inadr argument; the second array element must be the address of the last variable in the common block, which is derived by performing a logical OR with the value of the size of a memory page minus 1. The size of the memory page can be retrieved by a call to the SYS$GETSYI system service.
If the first variable in the common block is an array or string, the first variable in the common block is the first element of that array or string. If the last variable in the common block is an array or string, the last variable in the common block is the last element in that array or string.
Returning the Location of the Mapped Section
On VAX systems, SYS$CRMPSC returns the location of the first and last elements mapped in the retadr argument. The value returned as the starting virtual address should be the same as the starting address passed to the inadr argument. The value returned as the ending virtual address should be equal to or slightly more than (within 512 bytes, or 1 block) the value of the ending virtual address passed to the inadr argument.
On Alpha systems, SYS$CRMPSC returns the location of the first and last elements mapped in the retadr argument. The value returned as the starting virtual address should be the same as the starting address passed to the inadr argument. The value returned as the ending virtual address should be equal to or slightly less than (within a single page size) the value of the ending virtual address passed to the inadr argument.
If the first element is in error, you probably forgot to page-align the common block containing the mapped data.
If the second element is in error, you were probably creating a new data file and forgot to specify the size of the file in your program (see Section 28.4.1.3).
Specify SEC$M_WRT for the flags to indicate that the section is writable. If the file is new, also specify SEC$M_DZRO to indicate that the section should be initialized to zero.
You must use a user-open routine to get the channel number (see Section 28.4.1.2). Pass the channel number to the chan argument.
On VAX systems, Example 28-1 maps a data file consisting of one longword and three real arrays to the INC_DATA common block. The options file INCOME.OPT page-aligns the INC_DATA common block.
If SYS$CRMPSC returns a status of SS$_IVSECFLG and you have correctly specified the flags in the mask argument, check to see if you are passing a channel number of 0.
Example 28-1 Mapping a Data File to the Common Block on a VAX System |
---|
!INCOME.OPT PSECT_ATTR = INC_DATA, PAGE |
! Declare variables to hold statistics REAL PERSONS_HOUSE (2048), 2 ADULTS_HOUSE (2048), 2 INCOME_HOUSE (2048) INTEGER TOTAL_HOUSES ! Declare section information ! Data area COMMON /INC_DATA/ PERSONS_HOUSE, 2 ADULTS_HOUSE, 2 INCOME_HOUSE, 2 TOTAL_HOUSES ! Addresses INTEGER ADDR(2), 2 RET_ADDR(2) ! Section length INTEGER SEC_LEN ! Channel INTEGER*2 CHAN, 2 GARBAGE COMMON /CHANNEL/ CHAN, 2 GARBAGE ! Mask values INTEGER MASK INCLUDE '($SECDEF)' ! User-open routines INTEGER UFO_OPEN, 2 UFO_CREATE EXTERNAL UFO_OPEN, 2 UFO_CREATE ! Declare logical unit number INTEGER STATS_LUN ! Declare status variables and values INTEGER STATUS, 2 IOSTAT, 2 IO_OK PARAMETER (IO_OK = 0) INCLUDE '($FORDEF)' EXTERNAL INCOME_BADMAP ! Declare logical for INQUIRE statement LOGICAL EXIST ! Declare subprograms invoked as functions INTEGER LIB$GET_LUN, 2 SYS$CRMPSC, 2 SYS$DELTVA, 2 SYS$DASSGN ! Get logical unit number for STATS.SAV STATUS = LIB$GET_LUN (STATS_LUN) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) INQUIRE (FILE = 'STATS.SAV', 2 EXIST = EXIST) IF (EXIST) THEN OPEN (UNIT=STATS_LUN, 2 FILE='STATS.SAV', 2 STATUS='OLD', 2 USEROPEN = UFO_OPEN) MASK = SEC$M_WRT ELSE ! If STATS.SAV does not exist, create new database MASK = SEC$M_WRT .OR. SEC$M_DZRO SEC_LEN = ! (address of last - address of first + size of last + 511)/512 2 ( (%LOC(TOTAL_HOUSES) - %LOC(PERSONS_HOUSE(1)) + 4 + 511)/512 ) OPEN (UNIT=STATS_LUN, 2 FILE='STATS.SAV', 2 STATUS='NEW', 2 INITIALSIZE = SEC_LEN, 2 USEROPEN = UFO_CREATE) END IF ! Free logical unit number and map section CLOSE (STATS_LUN) ! ******** ! MAP DATA ! ******** ! Specify first and last address of section ADDR(1) = %LOC(PERSONS_HOUSE(1)) ADDR(2) = %LOC(TOTAL_HOUSES) ! Map the section STATUS = SYS$CRMPSC (ADDR, 2 RET_ADDR, 2 , 2 %VAL(MASK), 2 ,,, 2 %VAL(CHAN), 2 ,,,) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) ! Check for correct mapping IF (ADDR(1) .NE. RET_ADDR (1)) 2 CALL LIB$SIGNAL (%VAL (%LOC(INCOME_BADMAP))) . . . ! Reference data using the ! data structures listed ! in the common block . . . ! Close and update STATS.SAV STATUS = SYS$DELTVA (RET_ADDR,,) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) STATUS = SYS$DASSGN (%VAL(CHAN)) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) END |
Example 28-2 shows the code for performing the same functions as Example 28-1 but in an Alpha system's environment.
Example 28-2 Mapping a Data File to the Common Block on an Alpha System |
---|
!INCOME.OPT PSECT_ATTR = INC_DATA, SOLITARY, SHR, WRT |
! Declare variables to hold statistics REAL PERSONS_HOUSE (2048), 2 ADULTS_HOUSE (2048), 2 INCOME_HOUSE (2048) INTEGER TOTAL_HOUSES, STATUS ! Declare section information ! Data area COMMON /INC_DATA/ PERSONS_HOUSE, 2 ADULTS_HOUSE, 2 INCOME_HOUSE, 2 TOTAL_HOUSES ! Addresses INTEGER ADDR(2), 2 RET_ADDR(2) ! Section length INTEGER SEC_LEN ! Channel INTEGER*2 CHAN, 2 GARBAGE COMMON /CHANNEL/ CHAN, 2 GARBAGE ! Mask values INTEGER MASK INCLUDE '($SECDEF)' ! User-open routines INTEGER UFO_OPEN, 2 UFO_CREATE EXTERNAL UFO_OPEN, 2 UFO_CREATE ! Declare logical unit number INTEGER STATS_LUN ! Declare status variables and values INTEGER STATUS, 2 IOSTAT, 2 IO_OK PARAMETER (IO_OK = 0) INCLUDE '($FORDEF)' EXTERNAL INCOME_BADMAP ! Declare logical for INQUIRE statement LOGICAL EXIST ! Declare subprograms invoked as functions INTEGER LIB$GET_LUN, 2 SYS$CRMPSC, 2 SYS$DELTVA, 2 SYS$DASSGN ! Get logical unit number for STATS.SAV STATUS = LIB$GET_LUN (STATS_LUN) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) INQUIRE (FILE = 'STATS.SAV', 2 EXIST = EXIST) IF (EXIST) THEN OPEN (UNIT=STATS_LUN, 2 FILE='STATS.SAV', 2 STATUS='OLD', 2 USEROPEN = UFO_OPEN) MASK = SEC$M_WRT ELSE ! If STATS.SAV does not exist, create new database MASK = SEC$M_WRT .OR. SEC$M_DZRO SEC_LEN = ! (address of last - address of first + size of last + 511)/512 2 ( (%LOC(TOTAL_HOUSES) - %LOC(PERSONS_HOUSE(1)) + 4 + 511)/512 ) OPEN (UNIT=STATS_LUN, 2 FILE='STATS.SAV', 2 STATUS='NEW', 2 INITIALSIZE = SEC_LEN, 2 USEROPEN = UFO_CREATE) END IF ! Free logical unit number and map section CLOSE (STATS_LUN) ! ******** ! MAP DATA ! ******** STATUS = LIB$GETSYI(SYI$_PAGE_SIZE, PAGE_MAX,,,,) IF (.NOT. STATUS) CALL LIB$STOP (%VAL (STATUS)) ! Specify first and last address of section ADDR(1) = %LOC(PERSONS_HOUSE(1)) ! Section will always be smaller than page_max bytes ADDR(2) = ADDR(1) + PAGE_MAX -1 ! Map the section STATUS = SYS$CRMPSC (ADDR, 2 RET_ADDR, 2 , 2 %VAL(MASK), 2 ,,, 2 %VAL(CHAN), 2 ,,,) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) ! Check for correct mapping IF (ADDR(1) .NE. RET_ADDR (1)) 2 CALL LIB$SIGNAL (%VAL (%LOC(INCOME_BADMAP))) . . . ! Reference data using the ! data structures listed ! in the common block . . . ! Close and update STATS.SAV STATUS = SYS$DELTVA (RET_ADDR,,) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) STATUS = SYS$DASSGN (%VAL(CHAN)) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) END |
When you open a file for mapping in Fortran, for example, you must specify a user-open routine ( Section 28.6 discusses user-open routines) to perform the following operations:
After setting the user-file open bit in the FAB options mask, you cannot use language I/O statements to access data in that file. Therefore, you should free the logical unit number associated with the file. The file is still open. You access the file with the channel number.
Example 28-3 shows a user-open routine invoked by the sample program in Section 28.4.1.1 if the file STATS.SAV exists. (If STATS.SAV does not exist, the user-open routine must invoke SYS$CREATE rather than SYS$OPEN.)
Example 28-3 Using a User-Open Routine |
---|
!UFO_OPEN.FOR INTEGER FUNCTION UFO_OPEN (FAB, 2 RAB, 2 LUN) ! Include Open VMS RMS definitions INCLUDE '($FABDEF)' INCLUDE '($RABDEF)' ! Declare dummy arguments RECORD /FABDEF/ FAB RECORD /RABDEF/ RAB INTEGER LUN ! Declare channel INTEGER*4 CHAN COMMON /CHANNEL/ CHAN ! Declare status variable INTEGER STATUS ! Declare system procedures INTEGER SYS$OPEN ! Set useropen bit in the FAB options longword FAB.FAB$L_FOP = FAB.FAB$L_FOP .OR. FAB$M_UFO ! Open file STATUS = SYS$OPEN (FAB) ! Read channel from FAB status word CHAN = FAB.FAB$L_STV ! Return status of open operation UFO_OPEN = STATUS END |
The first time you map a file you must perform the following operations in addition to those listed at the beginning of Section 28.4.1:
The user-open routine for creating a file is the same as the user-open
routine for opening a file except that SYS$OPEN is replaced by
SYS$CREATE.
28.4.1.4 Saving a Mapped File
To close a data file that was opened for user I/O, you must deassign the I/O channel assigned to that file. Before you can deassign a channel assigned to a mapped file, you must delete the virtual memory associated with the file (the memory used by the common block). When you delete the virtual memory used by a mapped file, any changes made while the file was mapped are written back to the disk file. Use the Delete Virtual Address Space (SYS$DELTVA) system service to delete the virtual memory used by a mapped file. Use the Deassign I/O Channel (SYS$DASSGN) system service to deassign the I/O channel assigned to a file.
The program segment shown in Example 28-4 closes a mapped file, automatically writing any modifications back to the disk. To ensure that the proper locations are deleted, pass SYS$DELTVA the addresses returned to your program by SYS$CRMPSC rather than the addresses you passed to SYS$CRMPSC. If you want to save modifications made to the mapped section without closing the file, use the Update Section File on Disk (SYS$UPDSEC) system service. To ensure that the proper locations are updated, pass SYS$UPDSEC the addresses returned to your program by SYS$CRMPSC rather than the addresses you passed to SYS$CRMPSC. Typically, you want to wait until the update operation completes before continuing program execution. Therefore, use the efn argument of SYS$UPDSEC to specify an event flag to be set when the update is complete, and wait for the system service to complete before continuing. For a complete description of the SYS$DELTVA, SYS$DASSGN, and SYS$UPDSEC system services, see the OpenVMS System Services Reference Manual.
Example 28-4 Closing a Mapped File |
---|
! Section address INTEGER*4 ADDR(2), 2 RET_ADDR(2) ! Event flag INTEGER*4 FLAG ! Status block STRUCTURE /IO_BLOCK/ INTEGER*2 IOSTAT, 2 HARDWARE INTEGER*4 BAD_PAGE END STRUCTURE RECORD /IO_BLOCK/ IOSTATUS . . . ! Get an event flag STATUS = LIB$GET_EF (FLAG) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL(STATUS)) ! Update the section STATUS = SYS$UPDSEC (RET_ADDR, 2 ,,, 2 %VAL(FLAG) 2 , 2 IOSTATUS,,) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL(STATUS)) ! Wait for section to be updated STATUS = SYS$SYNCH (%VAL(FLAG), 2 IOSTATUS) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL(STATUS)) . . . |
This section provides an example, written in Compaq Fortran, of how to open and update a sequential file on a VAX system. A sequential file consists of records arranged one after the other in the order in which they are written to the file. Records can only be added to the end of the file. Typically, sequential files are accessed sequentially.
To create a sequential file, use the OPEN statement and specify the following keywords and keyword values:
The file structure keyword ORGANIZATION also accepts the value 'INDEXED' or 'RELATIVE'.
Example 28-5 creates a sequential file of fixed-length records.
Example 28-5 Creating a Sequential File of Fixed-Length Records |
---|
. . . INTEGER STATUS, 2 LUN, 2 LIB$GET_INPUT, 2 LIB$GET_LUN, 2 STR$UPCASE INTEGER*2 FN_SIZE, 2 REC_SIZE CHARACTER*256 FILENAME CHARACTER*80 RECORD ! Get file name STATUS = LIB$GET_INPUT (FILENAME, 2 'File name: ', 2 FN_SIZE) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Get free unit number STATUS = LIB$GET_LUN (LUN) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Open the file OPEN (UNIT = LUN, 2 FILE = FILENAME (1:FN_SIZE), 2 ORGANIZATION = 'SEQUENTIAL', 2 ACCESS = 'SEQUENTIAL', 2 RECORDTYPE = 'FIXED', 2 FORM = 'UNFORMATTED', 2 RECL = 20, 2 STATUS = 'NEW') ! Get the record input STATUS = LIB$GET_INPUT (RECORD, 2 'Input: ', 2 REC_SIZE) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) DO WHILE (REC_SIZE .NE. 0) ! Convert to uppercase STATUS = STR$UPCASE (RECORD,RECORD) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) WRITE (UNIT=LUN) RECORD(1:REC_SIZE) ! Get more record input STATUS = LIB$GET_INPUT (RECORD, 2 'Input: ', 2 REC_SIZE) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) END DO END |
To update a sequential file, read each record from the file, update it, and write it to a new sequential file. Updated records cannot be written back as replacement records for the same sequential file from which they were read.
Example 28-6 updates a sequential file, giving the user the option of modifying a record before writing it to the new file. The same file name is used for both files; because the new update file was opened after the old file, the new file has a higher version number.
Example 28-6 Updating a Sequential File |
---|
. . . INTEGER STATUS, 2 LUN1, 2 LUN2, 2 IOSTAT INTEGER*2 FN_SIZE CHARACTER*256 FILENAME CHARACTER*80 RECORD CHARACTER*80 NEW_RECORD INCLUDE '($FORDEF)' INTEGER*4 LIB$GET_INPUT, 2 LIB$GET_LUN, 2 STR$UPCASE ! Get file name STATUS = LIB$GET_INPUT (FILENAME, 2 'File name: ', 2 FN_SIZE) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Get free unit number STATUS = LIB$GET_LUN (LUN1) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Open the old file OPEN (UNIT=LUN1, 2 FILE=FILENAME (1:FN_SIZE), 2 ORGANIZATION='SEQUENTIAL', 2 ACCESS='SEQUENTIAL', 2 RECORDTYPE='FIXED', 2 FORM='UNFORMATTED', 2 RECL=20, 2 STATUS='OLD') ! Get free unit number STATUS = LIB$GET_LUN (LUN2) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Open the new file OPEN (UNIT=LUN2, 2 FILE=FILENAME (1:FN_SIZE), 2 ORGANIZATION='SEQUENTIAL', 2 ACCESS='SEQUENTIAL', 2 RECORDTYPE='FIXED', 2 FORM='UNFORMATTED', 2 RECL=20, 2 STATUS='NEW') ! Read a record from the old file READ (UNIT=LUN1, 2 IOSTAT=IOSTAT) RECORD IF (IOSTAT .NE. IOSTAT_OK) THEN CALL ERRSNS (,,,,STATUS) IF (STATUS .NE. FOR$_ENDDURREA) THEN CALL LIB$SIGNAL (%VAL(STATUS)) END IF END IF DO WHILE (STATUS .NE. FOR$_ENDDURREA) TYPE *, RECORD ! Get record update STATUS = LIB$GET_INPUT (NEW_RECORD, 2 'Update: ') IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Convert to uppercase STATUS = STR$UPCASE (NEW_RECORD, 2 NEW_RECORD) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Write unchanged record or updated record IF (NEW_RECORD .EQ. ' ' ) THEN WRITE (UNIT=LUN2) RECORD ELSE WRITE (UNIT=LUN2) NEW_RECORD END IF ! Read the next record READ (UNIT=LUN1, 2 IOSTAT=IOSTAT) RECORD IF (IOSTAT .NE. IOSTAT_OK) THEN CALL ERRSNS (,,,,STATUS) IF (STATUS .NE. FOR$_ENDDURREA) THEN CALL LIB$SIGNAL (%VAL(STATUS)) END IF END IF END DO END |
Previous | Next | Contents | Index |
privacy and legal statement | ||
5841PRO_075.HTML |