Explained: CICS Link Modules

Codelooru cics link modules

A CICS transaction arrives. It validates the input, applies some business rules, looks up a customer record, and calculates a result. If all of that logic lived in one monolithic COBOL program, changing the business rules would mean recompiling and redeploying the entire thing. Testing the calculation in isolation would be impossible. Sharing the customer lookup with ten other transactions would mean copying the code ten times.

This is why CICS programs call each other. A well-designed CICS application is a set of cooperating programs: a thin front-end that handles the screen, a service program that contains the business logic, a data access program that owns the file reads and writes. Each program does one thing. Each can be changed, tested, and redeployed without touching the others.

The mechanism that makes this possible is the CICS link. This post covers how CICS programs call each other, the three ways to do it, how data passes between them, and where each approach fits in a real application. It assumes you have read Architecture: CICS and Explained: COBOL.


The Three Ways to Call a Program

Inside a CICS task, one program can invoke another in three different ways. They differ in whether control returns to the caller, whether both programs occupy memory simultaneously, and whether CICS treats them as separate modules or a single unit. Choosing the wrong one is a common source of bugs and performance problems.

EXEC CICS LINK

LINK is the standard subroutine call in CICS. It transfers control to a named program and expects control to come back. The calling program remains in memory and active while the called program runs. When the called program issues `EXEC CICS RETURN`, control passes back to the statement immediately after the LINK command in the calling program.

LINK introduces a new logical level. If the calling program is at level 1, the linked-to program runs at level 2. If that program links to another, that third program runs at level 3. Levels can nest up to 255 deep, though in practice more than three or four levels is unusual.

* Calling program (level 1)
WORKING-STORAGE SECTION.
01 WS-COMMAREA.
   05 CA-ACCOUNT-NUMBER  PIC 9(10).
   05 CA-BALANCE         PIC S9(10)V99 COMP-3.
   05 CA-STATUS          PIC X(1).

PROCEDURE DIVISION.
MAIN-LOGIC.
    MOVE ACCT-NUMBER TO CA-ACCOUNT-NUMBER

    EXEC CICS LINK
        PROGRAM('ACCTINQ')
        COMMAREA(WS-COMMAREA)
        LENGTH(LENGTH OF WS-COMMAREA)
        RESP(WS-RESP)
    END-EXEC

    IF WS-RESP = DFHRESP(NORMAL)
        PERFORM DISPLAY-BALANCE
    ELSE
        PERFORM HANDLE-ERROR
    END-IF

    EXEC CICS RETURN END-EXEC.

After the LINK returns, `WS-COMMAREA` contains whatever the called program wrote into it. Both programs share the same commarea storage area: changes made by the called program are visible to the caller when control returns.

EXEC CICS XCTL

XCTL (transfer control) hands control to another program at the same logical level. The calling program is removed from memory. Control does not return to it. XCTL is the CICS equivalent of a `GO TO`: the calling program is done, and the new program takes over the task.

* Program A hands off to Program B — A will not resume
EXEC CICS XCTL
    PROGRAM('MENUDISP')
    COMMAREA(WS-COMMAREA)
    LENGTH(LENGTH OF WS-COMMAREA)
    RESP(WS-RESP)
END-EXEC.
* Any code here is unreachable — XCTL does not return

Because XCTL releases the calling program's memory before loading the new one, it is more storage-efficient than LINK when the calling program is no longer needed. It is appropriate for navigation between screens in a multi-step transaction: after the user confirms an action on screen 1, XCTL to screen 2. There is no reason to keep screen 1's program in memory.

COBOL CALL

A standard COBOL CALL statement invokes a subprogram that is linked into the same load module as the calling program. From CICS's perspective, there is only one program: the main load module that contains both the caller and the subprogram. CICS is not involved in the call at all.

CALL is the fastest option because it is a simple branch instruction with no CICS overhead. It is appropriate for utility routines: date conversion, string formatting, checksum calculation. These are pure functions with no CICS resource access. A subprogram invoked by CALL cannot issue CICS commands because it is not running as a separate CICS program entity.

The key constraint with CALL is state: a CALLed subprogram retains its Working Storage between calls within the same task. If the subprogram uses a counter or a flag in its Working Storage, the value from the previous call is still there on the next call. This is usually what you want for a utility routine. But it is the opposite of how LINK and XCTL work: a LINKed program always starts with freshly initialized Working Storage because CICS gives each link its own storage allocation.

EXEC CICS LINK EXEC CICS XCTL COBOL CALL Prog A (L1) Prog B (L2) call return Both in memory B at lower level A resumes after B B gets fresh WS Data via commarea or channel Prog A (L1) Prog C (L1) xctl A released A released from memory C at same level as A No return to A C gets fresh WS Data via commarea or channel Single load module Prog A + subprog D One module in memory No CICS involvement Control returns to A D retains its WS Data via parameters or COBOL LINKAGE Returns to caller? CICS aware? Fresh WS each call? LINK: Yes LINK: Yes LINK: Yes XCTL: No XCTL: Yes XCTL: Yes Figure 1: LINK, XCTL, and CALL compared — memory model, control flow, and Working Storage behavior.

The Commarea

When one CICS program calls another via LINK or XCTL, data passes between them through a commarea (communication area). The commarea is a contiguous block of memory defined in the calling program's Working Storage and passed by reference on the LINK or XCTL command.

In the called program, the commarea arrives as `DFHCOMMAREA` in the Linkage Section. The called program reads input data from `DFHCOMMAREA`, does its work, writes its results back into `DFHCOMMAREA`, and returns. Because the commarea is passed by reference, the calling program sees the changes the called program made as soon as control returns.

* Called program — receives commarea, updates it, returns
LINKAGE SECTION.
01 DFHCOMMAREA.
   05 CA-ACCOUNT-NUMBER  PIC 9(10).
   05 CA-BALANCE         PIC S9(10)V99 COMP-3.
   05 CA-STATUS          PIC X(1).

PROCEDURE DIVISION.
MAIN-LOGIC.
    EXEC CICS READ
        DATASET('ACCOUNTS')
        INTO(WS-ACCOUNT-RECORD)
        RIDFLD(CA-ACCOUNT-NUMBER)
        RESP(WS-RESP)
    END-EXEC

    EVALUATE WS-RESP
        WHEN DFHRESP(NORMAL)
            MOVE WS-ACCT-BALANCE TO CA-BALANCE
            MOVE 'A'             TO CA-STATUS
        WHEN DFHRESP(NOTFND)
            MOVE ZEROS           TO CA-BALANCE
            MOVE 'N'             TO CA-STATUS
        WHEN OTHER
            MOVE ZEROS           TO CA-BALANCE
            MOVE 'E'             TO CA-STATUS
    END-EVALUATE

    EXEC CICS RETURN END-EXEC.

The `DFHCOMMAREA` in the called program must never be declared larger than the commarea that was actually passed. If `DFHCOMMAREA` is declared as 200 bytes but only 50 bytes were passed, the remaining 150 bytes in the Linkage Section map over whatever memory follows the commarea. Writing into those extra bytes corrupts Working Storage in the calling program. This is a silent bug: no abend, no error, just wrong data. Validating `EIBCALEN` (the commarea length field in the EIB) against the expected length before accessing the commarea is the defensive practice that prevents it.

The 32KB Limit

The commarea has a hard size limit of 32,767 bytes (32 KB minus 1). This was sufficient when CICS was built around 3270 terminal transactions passing small records. It is insufficient for modern workloads where transactions may need to pass XML documents, JSON payloads, or large result sets between programs.

Several workarounds existed before a proper solution arrived: passing a pointer to a large GETMAIN storage area, using a Temporary Storage Queue name, or writing data to a VSAM file and passing only the key. All of these are fragile and introduce operational complexity.


Channels and Containers: The Modern Alternative

CICS Transaction Server introduced channels and containers as the replacement for commareas when data exceeds 32 KB or when multiple named data structures need to pass between programs.

A container is a named, CICS-managed storage area. It can hold any amount of data in any format: character, binary, JSON, XML. There is no size limit. The calling program writes data into a container using `EXEC CICS PUT CONTAINER`. The called program reads it using `EXEC CICS GET CONTAINER`.

A channel is a named collection of containers. A single LINK command passes the channel name rather than a commarea. The called program receives the channel and can access all containers within it by name. A channel carrying an order transaction might contain separate containers for the order header, the line items, the customer record, and the payment details.

* Calling program: populate a channel and link
EXEC CICS PUT CONTAINER('ORDER-HEADER')
    FROM(WS-ORDER-HDR)
    FLENGTH(LENGTH OF WS-ORDER-HDR)
    CHANNEL('ORDER-CHANNEL')
END-EXEC

EXEC CICS PUT CONTAINER('LINE-ITEMS')
    FROM(WS-LINE-ITEMS)
    FLENGTH(WS-ITEMS-LENGTH)
    CHANNEL('ORDER-CHANNEL')
END-EXEC

EXEC CICS LINK
    PROGRAM('ORDPROC')
    CHANNEL('ORDER-CHANNEL')
    RESP(WS-RESP)
END-EXEC

* Called program: read from the channel
EXEC CICS GET CONTAINER('ORDER-HEADER')
    INTO(WS-ORDER-HDR)
    FLENGTH(WS-HDR-LENGTH)
    CHANNEL('ORDER-CHANNEL')
END-EXEC

Channels work across MRO links and remote LINK calls (DPL), the same as commareas. A channel-based program is therefore deployable in a distributed CICSPlex with no code changes to handle the cross-region case. This is a significant advantage over the GETMAIN pointer workaround, which only works within a single CICS address space.

COMMAREA (legacy) Prog A WS-COMMAREA Max 32KB, one structure LINK Prog B DFHCOMMAREA Linked to same memory DFHCOMMAREA must not exceed passed length Check EIBCALEN before accessing commarea fields CHANNEL + CONTAINERS (modern) Prog A PUT CONTAINER 'ORDER-HEADER' 'LINE-ITEMS' LINK CHANNEL 'ORDER-CHANNEL' Container: ORDER-HEADER Container: LINE-ITEMS Container: PAYMENT No size limit per container Multiple named structures Works across MRO / DPL Figure 2: Commarea (single structure, 32KB max) vs channel and containers (named, unlimited size).

Remote LINK: Distributed Program Link

LINK is not restricted to programs in the same CICS region. By adding a `SYSID` parameter, a program can link to a program in a remote CICS region across an MRO or ISC connection. This is called DPL (Distributed Program Link).

EXEC CICS LINK
    PROGRAM('RISKCHEK')
    SYSID('RISK')
    COMMAREA(WS-COMMAREA)
    LENGTH(LENGTH OF WS-COMMAREA)
    RESP(WS-RESP)
END-EXEC

From the calling program's perspective, a DPL call looks identical to a local LINK: it passes a commarea (or channel), waits for control to return, and checks the response code. CICS handles the network communication transparently. The called program does not know it was invoked remotely.

DPL has one important restriction: the called program cannot issue `EXEC CICS RETURN TRANSID` (it cannot start a new transaction) and cannot issue terminal control commands. The called program must be written as a purely computational service, with no conversational interaction with a terminal. This constraint is enforced by CICS and results in an `INVREQ` response code if violated.

DPL is the foundation for service-oriented designs in CICS: a front-end AOR handles the terminal, and a back-end AOR in a different region (or a different LPAR) provides the shared business service. Routing the LINK to the right region is handled by CICSPlex dynamic routing, transparently to the calling program.


Program Isolation and Quasi-Reentrancy

CICS programs must be quasi-reentrant. Many tasks may run the same program simultaneously, but each task must have its own private Working Storage. CICS enforces this by giving each LINKed program a fresh copy of its Working Storage when the link is issued. The load module itself (the executable code) is shared and read-only. Only the Working Storage is per-task.

This design has a practical implication: a LINKed program cannot rely on the value of any Working Storage field from a previous call. Every call starts with Working Storage initialized to the VALUES defined in the DATA DIVISION. This is not a limitation; it is the mechanism that makes it safe to have 3,000 concurrent tasks all running the same program without interfering with each other's data.

COBOL CALL behaves differently, as noted earlier: the CALLed subprogram retains its Working Storage across calls within the same task. This is safe for a single task but means the subprogram is effectively stateful, which must be accounted for in its design. If the subprogram uses Working Storage as a scratch area that it initializes at entry, the retained state is harmless. If it relies on Working Storage being initialized to zeros on every call, it will produce wrong results on every call after the first.


Practical Design Patterns

Most production CICS applications use LINK for the majority of program-to-program calls. The standard pattern is a three-tier structure:

  • Presentation program: handles BMS maps, reads terminal input, formats output. LINKs to the business logic program, passing input data via commarea.
  • Business logic program: validates input, applies rules, orchestrates the work. LINKs to one or more data access programs. Returns result data to the presentation program via the commarea.
  • Data access program: reads and writes VSAM or DB2. Has no knowledge of screens or business rules. Returns data to whatever program called it.

XCTL is appropriate at the presentation layer for multi-step transactions: after the user confirms an action, XCTL to the confirmation screen program. The payment entry program XCTLs to the payment confirmation program. The confirmation program XCTLs back to the main menu. Each XCTL releases the previous program's storage, keeping memory consumption bounded across a long user session.

COBOL CALL is appropriate for pure utilities: date arithmetic, data formatting, checksum validation. These are best packaged as subprograms linked into the calling program's load module. They execute at native COBOL speed with no CICS overhead and are completely invisible to the CICS dispatcher.


Common Mistakes

The most frequent LINK-related bug is not checking `RESP` after the LINK command. If the named program does not exist in the CICS CSD, the response code is `PGMIDERR`. If the call runs but the response is not checked, the calling program continues with the commarea in its pre-call state, producing wrong results with no indication that anything failed.

Passing a commarea larger than 32,767 bytes produces `LENGERR`. The fix is to switch to channels and containers, not to try to squeeze data into 32KB.

Declaring `DFHCOMMAREA` larger than the passed commarea is the silent corruption bug described earlier. The fix is to always check `EIBCALEN` before accessing commarea fields beyond a known minimum length, and to never declare `DFHCOMMAREA` longer than the smallest commarea the program expects to receive.

Issuing `EXEC CICS RETURN TRANSID` inside a LINKed program (rather than the top-level program) does not start a new transaction. RETURN TRANSID at a non-zero logical level returns control to the calling program, not to CICS. Only at logical level 1 does RETURN TRANSID have its expected pseudo-conversational effect.


Summary

CICS program-to-program calling has three mechanisms with distinct trade-offs. LINK is the general-purpose subroutine call: control returns, both programs are in memory, the called program gets fresh Working Storage. XCTL is the handoff: control transfers, the calling program is released, no return. COBOL CALL is the private utility: no CICS involvement, fastest, but stateful Working Storage.

The commarea is the standard data passing mechanism: a shared memory block passed by reference with a 32KB limit. Channels and containers are the modern replacement for workloads that need larger or more complex data structures, and they work transparently across MRO links.

Remote LINK via DPL extends the same model across regions and LPARs, enabling service-oriented decomposition within a CICSPlex. The calling program sees no difference between a local and a remote LINK. That transparency is what allows CICS applications to be restructured across regions without changing the programs that use them.

Part of the Mainframe Decoded series — IBM Z and z/OS, clearly explained for engineers.



×