This in-depth discussion about the IBM i job switches may reveal surprising—and helpful—information.
Written by Junlei Li
The design of the job switches can be traced back to earlier IBM midrange systems such as System/3, the first generation of IBM midrange systems built in-house. As more and more native or open-standard IPC mechanisms have been introduced to IBM i's ancestors and IBM i, people started forgetting this ease-to-use and efficient work management technique. I admit this technique is old, but old does not mean not good.
If you are unfamiliar with the concept of job switches, here's a brief explanation of this concept: Job switches are a group of eight logical values that can be used to pass information to a submitted job or to exchange information between programs running in the same job. Note that the term logical value means either '0' (false) or '1' (true), but not other character values.
The job switches are also referred to as User Program Status Indicator (UPSI) switches in IBM i COBOL documents—for example, the ILE COBOL Language Reference.
Passing Information to a Submitted Job via the Job Switches
The job switches can be used as a convenient way to pass information to a submitted job. When a job enters the system, the initial job switches value can be set via the following ways:
- The Job switches (SWS) parameter of a Submit Job (SBMJOB) command
- The job switches attribute of the Job Description (JOBD) object used to start the job
To specify the initial value of the job switches of a submitted job, you have two choices. You can specify the SWS parameter in a SBMJOB command directly like this:
SBMJOB CMD(cl-command-to-run) SWS(01001001) /* The 8-byte character value '01001001' is */ /* the example job switches value to pass to the */ /* submitted job. */
|
Or you can set the JOBD parameter to a JOBD object that contains the wanted SWS value like this:
/* Assume each job switch indicates whether a specific task among */ /* all the eight tasks needs to be done by the submitted job. */ CRTJOBD JOBD(DONOTHING) SWS(00000000) CRTJOBD JOBD(EVERYTHING) SWS(11111111) SBMJOB CMD(cl-command-to-run) JOBD(DONOTHING) /* The submitted job needn't do anything. */ SBMJOB CMD(cl-command-to-run) JOBD(EVERYTHING) /* The submitted job needs to do everything. */ |
Additionally, you can also set the initial job switches value of a job (either a batch job or an interactive job) started under a specific user profile via the SWS parameter of the JOBD of the user profile. Like to see a real usage example of this? The following is a post from Albert York in the MIDRANGE-L mailing list at midrange.com:
Re: How to set user profiles for multiple time zones.
* Subject: Re: How to set user profiles for multiple time zones.
* From: "Albert York" <albertyork@xxxxxxxxx>
* Date: Tue, 12 May 2009 07:30:00 -0800
* List-archive: <http://archive.midrange.com/midrange-l>
Years ago I had a similar problem, only I had multiple time zones. I used job descriptions which I assigned to the user profiles. Then I used the job switches in the job description to indicate which time zone they were in.
Albert
Accessing the Job Switches
Different languages have different approaches to job switches.
CL Commands and Built-in Function
To check the job switch settings of a job, you can use the Display Job (DSPJOB) command. For example, the following command will show the job switch settings of the current interactive job:
DSPJOB OPTION(*DFNA) |
The CL switch built-in function (%SWITCH) tests one or more of the eight switches of the current job under an 8-character mask and returns a logical value of '0' or '1'. The syntax of the %SWITCH built-in function is this:
%SWITCH(8-character-mask) |
Each position in the mask can be specified as one of three values: 0, 1, or X.
- The corresponding job switch is to be tested for a 0 (off).
- The corresponding job switch is to be tested for a 1 (on).
- The corresponding job switch is not to be tested (X). The value in the switch does not affect the result of %SWITCH.
In the following example, if job switch 1 contains '0', and job switch 3 contains '1', the program will branch to label HOME. The job switches 2, 4, 5, 6, 7, and 8 will not be tested.
IF COND(%SWITCH(0X1XXXXX)) THEN(GOTO HOME) |
For detailed information on the %SWITCH built-in function, please refer to the CL overview and concepts.
The Retrieve Job Attributes (RTVJOBA) command and the Change Job (CHGJOB) command can be used to get or set one or more job switches of the current job or another job, respectively. For example, the following command changes the value of the first job switch of the current job to '1'.
CHGJOB SWS(1XXXXXXX) |
Built-in Support for Accessing Job Switches in RPG
At the language level, OPM RPG and ILE RPG support job switches via external indicators—in other words, indicator U1 through U8. At the beginning of the RPG program cycle, the current job switches' settings are copied to indicator U1-U8, and at the end of the program cycle, the values of U1-U8 are copied back to the job switches. Note that returning from an RPG program with the LR indicator set off will not get the job switches' settings of the current job updated. Please refer to the ILE RPG Reference for details. To access the job switches directly in an RPG program, please refer to the section later in this article called Like To Go Forward Another Small Step?. Here is an example of accessing job switches in an RPG program.
d v s n inz(*off) /free *inu1 = v; // set off indicator U1 *inlr = *on; /end-free |
Note that although the RPG compiler will stop assigning constant values other than '0' or '1' (aka *ON or *OFF) to an indicator variable, it is still possible to spoil the contents of an external indicator with non-logical values. For example, assigning a character variable to an indicator variable is allowed in RPG. If a job switch is set to a non-logical value via a character variable at runtime, you know then what would happen. So, if you're going to change a job switch in an ILE RPG program via a variable, use a type N (indicator type) character variable instead of a type A character variable.
Built-in Support for Accessing Job Switches in COBOL
Job switches are referred to as User Program Status Indicator (UPSI) switches in IBM i COBOL documents. In OPM and ILE COBOL, the eight job switches are supported as external switches UPSI-0 through UPSI-7. You can test a specific job switch as a switch-status condition or set a job switch on or off via a predefined mnemonic name associated with the job switch. Please refer to the ILE COBOL Language Reference.
Note that unlike RPG, which works with a copy of the job switches and actually changes the job switch settings at the end of the RPG program cycle, a change made to a job switch in a COBOL program takes effect immediately, since COBOL accesses the 8-byte job switch settings in the current job's LUWA directly (see the section later in this article called The Language/Utility Work Area (LUWA)). The following is an example of accessing the job switches of the current job from an OPM COBOL program, upsi.cbl.
/** * @file upsi.cbl * * Example of accessing job switches (UPSI switches) in COBOL. */ ID DIVISION. PROGRAM-ID. UPSI.
ENVIRONMENT DIVISION. CONFIGURATION SECTION. SPECIAL-NAMES. * Define a mnemonic-name to be associated with job switch 1 UPSI-0 IS UUU * Define switch-status conditions for job switch 1 ON STATUS IS UUU-ON OFF STATUS IS UUU-OFF. DATA DIVISION.
LINKAGE SECTION.
PROCEDURE DIVISION. MAIN-PROGRAM. * Reverse the current setting of job switch 1 IF UUU-ON THEN DISPLAY "JOB SWITCH 1 IS ON" SET UUU TO OFF ELSE DISPLAY "JOB SWITCH 1 IS OFF" SET UUU TO ON END-IF.
SEE-YOU. STOP RUN. |
APIs
APIs that perform special functionality is good for all high-level languages (HLLs), since we can call an API from all HLLs. The following work management APIs can be used to retrieve and set the job switch settings of the current job or another job, respectively:
- The Retrieve Job Information (QUSRJOBI) API (with format JOBI0600)
- The Change Job (QWTCHGJB) API (with format JOBC0100 or JOBC0200)
Like To Go Forward Another Small Step?
By now, we know that the job switches offer an easy-to-use technique to pass information to a submitted job or to exchange information among programs running in the same job. We've also seen different ways to make use of them. So why should we go forward another step into it? The reason is very simple: there are still exciting things hiding behind the job switches.
The Language/Utility Work Area (LUWA)
First, the job switches are part of the 32-byte Language/Utility Work Area (LUWA), which works as a container of several miscellaneous job-level configuration items. The life cycle of the LUWA is the same as the job's active phase. The LUWA is stored at the start of the associated space of a space object called QWCLUWA. A QWCLUWA is a temporary, fixed-length MI space object (with object type-code/subtype-code hex 19EF) in the user domain, and the associated space of it is readable/writeable for user-state programs. At V5R4, the size of the associated space of a QWCLUWA space object is 4KB.
When a job becomes active, a QWCLUWA space object is provided to the job by the system, and the space pointer addressing the start position of its associated space is stored in the job's Process Communication Object (PCO) at offset hex C0, as shown in Figure 1. A user program can address the LUWA via this space pointer directly or via the space pointer returned by the _LUWRKA system built-in. Similar to the Local Data Area (LDA) objects, the QWCLUWA space objects are managed by the system and are reused to serve different jobs.
Figure 1: How do we locate the current job's LUWA?
The structure of LUWA and the prototype of _LUWRKA are documented in the C header, QSYSINC/H.MILIB (see Figure 2). The corresponding definitions in RPG can be found in https://i5toolkit.svn.sourceforge.net/svnroot/i5toolkit/rpg/mih-pgmexec.rpgleinc.
The following piece of code is extracted from QSYSINC/H.MILIB.
Figure 2: These are the C declarations of the structure of LUWA and the _LUWRKA system built-in.
I failed to find more detailed documentation on the LUWA fields. But it would be an interesting exercise to discover the exact meaning and usage of each LUWA field by tests. For example:
- When an RPG program exits normally with indicator LR set on, the LU_RC (Language/Utility Return Code) field is set to 1.
- When an ILE C program exits, the User_RC (ILE User Return Code) field is set to the return code of the program's main procedure. Calling the one-line C program "int main(int argc, char* argv[]) { return *(int*)argv[1]; }" with parameter hex 5678ABCD will set the User_RC field to hex 5678ABCD.
- The 7-byte character value of the Job_Date field is used by each RPG program running in the current job as the job date (which is represented by the RPG reserved words UDATE and *DATE).
- And, of course, the 8-byte Uspi_Switches field stores the job switches of the current job.
For HLL programs, accessing the LUWA of the current job is quite straightforward. The following is an example of retrieving the current job switches settings via the _LUWRKA system built-in, t177.rpgle.
h dftactgrp(*no)
/copy mih-pgmexec
d spp s * d luwa ds likeds(luwa_t) d based(spp)
/free spp = luwrka(); dsply 'SWS' '' luwa.job_sws;
*inlr = *on; /end-free |
The Spare Storage in QWCLUWA
As mentioned above, the size of the associated space of a QWCLUWA space is 4KB, and the LUWA uses only the beginning 32 bytes of it. Consider the facts that the life cycle of the storage in QWCLUWA is the same as the job's active phase, and the QWCLUWA is accessible from all programs running in the same job. Can we make use of the spare storage in QWCLUWA to implement our own work area, to store data that is expected to have the same life cycle as the job, or to share information among multiple programs running in the same job?
Here is a simple MI program, callcnt.emi, that uses the 4-byte storage immediately following the LUWA's 32-byte contents as a 4-byte binary program-call counter.
dcl spc pco baspco ; dcl ptr *(12) dir ; dcl spcptr luwa-spp dir ; /* Space pointer to LUWA at offset hex C0 */
dcl spc luwa bas(luwa-spp) ; /* The Language/Utility Work Area */ dcl dd luwa-data char(32) dir ; /* 32-byte contents of the LUWA */ dcl dd call-counter bin(4) dir ;
dcl dd msg char(32) auto ; dcl dd * char(26) def(msg) pos(1) init( "Times being called:" ) ; dcl dd znd-cnt znd(6, 0) def(msg) pos(27) ;
addn(s) call-counter, 1 ; /* Increment call-counter by 1 */ cpynv znd-cnt, call-counter ; %sendmsg(msg, 32) ; /* Display call-counter */ brk "SEEYOU" ; rtx * ; pend ; |
Call program CALLCNT twice and the result might look like the following:
4 > call callcnt Times being called: 000001 4 > call callcnt Times being called: 000002 |
When an IBM i job leaves the active phase, the QWCLUWA space object allocated to it is returned to the system. The system re-initializes the contents of the associated space of QWCLUWA. Pay attention that not the whole 4K bytes associated space is initialized! At V5R4, the system initializes only the beginning 80 bytes of the associated space of a QWCLUWA space, and the 48 bytes following the LUWA is cleared to hex 00. So it is recommended that you clear user data items you've stored in the spare storage in the QWCLUWA when they're no longer used, since they will become dirty data for other jobs that would reuse the QWCLUWA object.
Final Thoughts
First, I learned the _LUWRKA system built-in and the Language/Utility Work Area from a post of Gene Gaunt's in the MI400 mailing list at midrange.com. Thank you, Gene! I learned so much from you!
Second, after reading this article, someone might come up with the following question: If what I need is only to exchange information among programs running in the same job, why not store it in static storage or heap storage? Why should I use a technique that is as old as System/3, which was born in 1969? The reason is quite simple: static storage and heap storage are activation group-based storage and might go away due to the end of the activation group in which they are allocated or an unintended resource reclaiming operation. For example, an ILE activation group (a non-default activation group) can be deleted by the Reclaim Activation Group (RCLACTGRP) command or by a resource-related exception raised in the activation group, e.g., the Commitment definition not found (CPF8350) exception, or the I/O operation was applied to a closed file (RNX1211) exception. Storage managed by the default activation groups can be reclaimed by the Reclaim Resources (RCLRSC) command, the Transfer Job (TFRJOB) command, or the Reroute Job (RRTJOB) command.
LATEST COMMENTS
MC Press Online