Primeros pasos en el Mainframe: Lenguaje JCL

Autor: Kujaku | El viernes 19 de enero del 2007 @ 19:40.

Bueno, el documento de hoy estará enfocado en explicar más profundamente cómo hacer el trabajo día a día en un Host. Nos centraremos en los distintos procesos y mas concretamente en el lenguaje JCL (Job Control Lenguaje).

Al término de este documento, seremos capaces de ejecutar un job que nos deje en salida impresa, el texto "Hola Mamones" (lo del "hola mundo" esta muy trillado xDDDD).

Lo primero que debemos hacer es un reseteo de nuestro cerebro, porque en el mundo host, las cosas son muy distintas que en el mundo PC. Por ejemplo, crear un fichero no es algo tan sencillo como pinchar con el ratón sobre el menú Archivo, para luego hacer clic en Nuevo. En un host, todo son unidades de trabajo o jobs que al ejecutarse, producen un resultado (aunque a veces, no el deseado ;)).

Vamos un poco con la explicación de lo que es un Job o Trabajo: Un Job puede ser interactivo (ej, una sesión TSO), puede ser una tarea que se inicia y permanece en memoria para siempre (como un daemon en los distintos sabores de Unix), o puede ser un Job batch es decir: un proceso por lotes.

Todo job se basa en los mismos principios y sigue el mismo flujo: Existe una entrada, un procesamiento y luego una salida con el resultado. De la infraestructura software que dará soporte a todo este flujo de trabajo, se encarga el JES2 (Job Entry Subsystem). Para ello, este subsistema esta organizado de la siguiente manera:

  • Existen una o más clases de entrada (una clase viene a ser una cola FIFO) a la que puedes configurar diversos parámetros de prioridad, ejecución, etc. En este tipo de clase, se alojan los jobs que están esperando para ser ejecutados.

  • Los iniciadores o INITs son los encargados de coger un job que está esperando ser procesado y lo ejecuta. Evidentemente, un iniciador estará asociado a una o varias clases. Vendría a ser como un Thread. Cuantos más threads haya, mas proceso paralelo existirá. Un iniciador también puede hacer caso a más de una clase.

  • Y por último, están las clases de salida, que, al igual que las de entrada, son las colas donde se dejan los resultados de los jobs que posteriormente serán impresos.

En el trabajo diario de un host, para cualquier cosa que debas hacer, necesitas ejecutar muchos jobs para que la máquina haga las cosas que le pides (a este proceso se le llama submit). Si no tenemos ningún iniciador lanzado (cosa rara en un host arrancado), siempre se puede ir a la Master Console y teclear el siguiente Comando: S INIT.INIT1,,,A. Esto hará que lancemos un iniciador que lo hemos llamado INIT1 (pero puede ser cualquier nombre) que solo ejecutará los jobs que sean de la Clase A. Con esto, tenemos todo el sistema listo: Si ejecutamos un job de clase A entrará por INIT1 y si mientras está en ejecución lanzamos 20 jobs más de tipo A, se quedarán en la cola de entrada hasta que les llegue su turno (a menos que lancemos más iniciadores que hagan caso de la clase A, con lo que podrían ejecutarse en paralelo).

Los jobs, una vez ejecutados, quedarán en la clase o cola de salida que especifiquemos en el job hasta que una impresora que haga caso a dicha clase los imprima.

Hasta aquí, creo que no hay muchas dudas, ¿no? Bueno, antes de seguir, vamos a exponer un par de definiciones:

  • Paso o Step: Conjunto de líneas asociadas a un programa, que incluyen el programa en si y los datos que utilizará, así como sus parámetros.
  • Job: Conjunto de Pasos que se ejecutan consecutivamente.

Bien, nos metemos de lleno al tema: Para definir o crear un job, haremos uso del lenguaje JCL. Para ello, hay que tener clara la sintaxis, ya que existen ciertas reglas a cumplir, que son las siguientes:

  1. La gran mayoría de las sentencias JCL comienzan con // (existe un delimitador /* que más tarde comentaré).
  2. Todo lo que se escriba a partir de la columna 72, será ignorado.
  3. Todo Job comienza con la sentencia JOB.
  4. Se debe escribir todo en MAYÚSCULAS (si editas el JCL en el ISPF te lo convierte en mayúsculas directamente, pero si no se edita desde ese editor, hay que tenerlo muy en cuenta).
  5. Todo job debe tener como mínimo 1 Paso (sentencia EXEC).
  6. Todo paso debe incluir como mínimo una definición de datos (sentencia DD, viene a ser como el “var” de Pascal).
  7. El job se dará por finalizado cuando se encuentre una única línea con nada mas que //.

Bueno, con estas 7 reglas sencillas, ya podemos meternos de lleno a escribir nuestro JCL. Empecemos por la primera ficha (realmente es una línea, pero antaño cada línea era una ficha perforada y a fuerza de oír a mis mayores, me he quedado con ficha, además, da un aire retro-actual que pa que xDDDDD):

          1         2         3         4         5         6         7
-----+----0----+----0----+----0----+----0----+----0----+----0----+----0--
//MAMONJOB JOB GODDESS,URD,CLASS=A

Los caracteres y números de encima son simplemente para ver en la columna que estamos, así que no hacer demasiado caso.

Analicemos esta ficha: después de las //, aparece el nombre del Job. Puedes poner el que te de la gana, siempre y cuando tenga de 1 a 8 caracteres alfanuméricos, excepto el primer carácter, que debe ser siempre alfabético. He elegido como nombre MAMONJOB porque es un job que imprimirá "Hola Mamones".

Lo siguiente es la sentencia JOB, que indica al SO (en este caso al JES2) que comienza nuestro Job.

Lo siguiente a explicar son los parámetros que puede llevar una sentencia JOB. Los hay de dos tipos: Parámetros posicionales y de Keyword o Referencia. Los de Keyword se diferencian en los posicionales en que llevan un = que les da un valor. Los posicionales, son en sí variables y de relevancia para el JES2. Como podéis ver, los parámetros se separan con comas. Eso si, hay una regla: Si vas a tener los dos tipos de parámetros en el job, debes poner primero los posicionales y luego los de keyword.

En nuestro ejemplo, la primera ficha muestra 3 parámetros: dos posicionales y un Keyword. Para el parser del JES2, el primer parámetro (opcional) GODDESS hace referencia al Accounting del job, es decir, si existe una entrada en la contabilidad llamada GODDESS, los ciclos de CPU que consuma este job irán a parar a esa entrada, de forma que puedes conocer cuanta CPU ha consumido dicha entrada a lo largo del mes, por ejemplo. El segundo parámetro lo toma el JES2 como el nombre del/la programador/a, que en este caso es URD (una de las mejoras administradoras del mundo), y el ultimo parámetro, que es Keyword, indica la clase donde se ejecutará el job. Este último parámetro dará la pista sobre que iniciador se ejecutara. Sencillo, ¿verdad?.

Existen más parámetros (algunos opcionales, otros no tanto) que se pueden poner en el job, como descripciones del job y demás, pero nos vamos a encontrar con que no nos van a entrar en una sola ficha. Por lo que se debe escribir una ficha o línea de continuación. Y ¿Cómo se hace esto? Tirado:

He comentado antes que los parámetros se ponen separados por comas. Si el JES2 encuentra una coma y luego un espacio en blanco, asume que los parámetros siguen en la siguiente ficha, en algún lugar de entre las columnas 4 y 16. Se recomienda que la continuación la escribamos a partir de la columna 16, porque queda mas tabulado. Así que si queremos poner más parámetros, basta con añadir en la segunda línea, en los caracteres 1 y 2, las // y continuar en la columna 16, quedando el tema tal que así:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,**MSGCLASS=A,   
// MSGLEVEL=(1,1)**

Tenemos 2 sentencias nuevas:

  • MSGCLASS que indica en qué cola donde dejar los mensajes que genere la ejecución del JCL.

  • MSGLEVEL que, mediante dos sub-parámetros posicionales (van entre paréntesis) que como veis se separan asimismo por comas, indican que queremos todos los mensajes JCL que se generen en el sistema (el primer 1) y luego, que también queremos los mensajes relacionados si reservamos o "allocamos" ficheros que vaya a usar el job (el segundo 1). Si sólo quisiéramos los mensajes JCL, el MSGLEVEL quedaría así: MSGLEVEL(1,0).

De todas formas, yo recomiendo siempre que salga todo (de hecho, en el trabajo diario, la ficha job yo suelo usar siempre la misma copiando y pegando). Por finalizar, si ya no hay comas detrás del último parámetro, el parser da por terminada la sentencia JOB.

Ahora, vamos con la ejecución del programa. ¡Cooooño! ¡Que no hemos escrito ningún programa! Bueno, afortunadamente IBM nos proporciona cientos de programas con el SO MVS, así que vamos a hacer uso de ellos. Estos programas se llaman UTILITIES en la jerga mainframera. De modo que haremos uso de una utility muy socorrida, llamada IEBGENER. Esta utility, entre otras muchas cosas, copia datos de un sitio a otro. O copia datos a un fichero, vamos, copia cualquier tipo de cosas. Nuestro JCL quedaría así:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1) 
//PASO1   EXEC PGM=IEBGENER

Claro, esa utility necesita ser colocada en memoria. Como ignoro lo que ocupa, vamos a decirle al JCL que por lo menos el JES2 reserve 256KB de RAM para que dicha utility pueda ser ejecutada. Eso se consigue de dos maneras:

  1. Añadiendo REGION=256K a la línea en la ficha JOB, con lo que todo el job tendrá 256 KB de RAM para su uso.

  2. Añadiendo REGION=256K al paso en cuestión, con lo que si nuestro job tuviera mas de un paso, cada uno podría tener su propia memoria reservada.

Pero para no marear la perdiz, añadiremos al todo el job la reserva de memoria. Así pues, nuestro job quedará así:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER

Como antes, comentaremos esta nueva ficha: PASO1 es un nombre opcional para el paso de ejecución, y le aplica las mismas normas de nomenclatura que para el nombre del JOB.

Respecto al IEBGENER, tiene una serie de parámetros que se deben introducir, como por ejemplo de donde copiar, a donde copiar, donde introducir parámetros especiales… Para ello, si vamos al manual de MVS System Utilities, y buscamos IEBGENER, nos encontramos con los siguientes parámetros que utiliza esta utility:

  • SYSUT1: Desde donde vas a copiar.
  • SYSUT2: Hacia donde copias.
  • SYSIN: Parámetros adicionales que le quieras meter para condicionar la copia.
  • SYSPRINT: Salida del resultado de esos parámetros adicionales.

En definitiva, IEBGENER copia lo que esta en SYSUT1 a SYSUT2 de acuerdo a los parámetros de SYSIN dejando el resultado de esos parámetros en SYSPRINT.

Por lo tanto, debemos meter esas 4 definiciones de datos en nuestro JCL. Para ello, haremos uso de la sentencia DD (Data Definition). Como he dicho mas arriba, DD es como el "var" de Pascal para definir las variables. Así que vamos a actualizar nuestro JCL, añadiendo también las dos // del final del job:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER 
//SYSUT1   DD  *
Hola, Mamones!!
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Atentos a este tema que es el quiz de la cuestión: Como no disponemos de un fichero cuyo contenido sea Hola Mamones, hay una manera de incluir ese dato dentro del propio jobstream o flujo del job: Utilizando la definición DD *. Cuando el parser se encuentra con esta construcción, asume que las siguientes líneas serán el contenido del dato en si, que dejara en SYSUT1. ¿Cuando termina de asumir que las siguientes fichas son datos? Cuando se encuentra con un /* o una ficha nueva del job que se verá en seguida porque aparecen las // en las columnas 1 y 2.

De hecho, es más bonito visualmente que el job quede así, añadiendo ese /*:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  * 
Hola, Mamones!!
/* 
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Entonces, si podemos añadir el dato directamente en el job, podríamos hasta hacer un banner y todo:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  *
**********************************************************
*                                                        *
*                       Hola, Mamones!!                  *
*                                                        *
********************************************************** 
/*
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Pero… ¿qué pasaría si en lugar de asteriscos, queremos poner todo /? El parser se volvería loco porque interpretaría que las siguientes fichas son sentencias de JCL, dando un error como una catedral. Pues hay un truco: Si en lugar de poner DD * se pone DD DATA, todo lo que encuentre desde ahí hasta el /* para él será un dato. Por lo que nuestro job podría quedar así:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DATA
/////////////////////////////////////////////////////////
//                                                     //
//                      Hola, Mamones!!                //
//                                                     //
///////////////////////////////////////////////////////// 
/*
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Ya os veo venir con la siguiente pregunta: ¿Y qué pasa si en algún lugar de nuestro dato, aparece un /*? ¡¡Pues que también esta previsto, cacho-perros!! xDDDD.

En ese caso, se crea un delimitador con una serie de caracteres que sabes que no se van a repetir en todo el job (por ejemplo, $$ o <>), con la sentencia DD DATA DLM=$$ o DD DATA DLM=<> quedando el tema así:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DATA,DLM=$$ 
/*******************************************************/
/*                                                     */
/*                      Hola, Mamones!!                */
/*                                                     */
/*******************************************************/
$$ 
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

¿Contentos? xDDD. Con esto, ya hemos definido lo que queremos copiar en SYSUT1. Pero seguimos explicando el JOB: SYSUT2 tiene una Data Definition que apunta a la clase de salida A (SYSOUT=A), lo mismo que SYSPRINT.

Es decir, que el resultado se escribirá en la salida de la clase A. Y por último tenemos una ultima DD que es SYSIN, y que en este caso hay un DUMMY. DUMMY se pone cuando no quieres poner parámetros adicionales. Es como poner /dev/null. Por lo tanto, es de esperar que SYSPRINT no escriba nada en la cola A ya que no hay ningún SYSIN.

Entonces, ¿qué hará este job? Usando IEBGENER, copiara el banner que está definido en SYSUT1 y lo dejará en SYSUT2, que como esta última variable tiene asociada una clase de salida, dejará el banner en esa salida. Y, si tenemos una impresora asociada a esa clase, imprimirá dicho banner. Tirado, ¿no?.

Hasta aquí el primer capítulo de JCL. En el siguiente explicaré como se tratan los ficheros o datasets, basándonos en este ejemplo del JCL (si tenéis un host a mano o habéis instalado el MVS freeware, probad este job, porque funciona).

Comentarios