Como obtener el UNIX UID desde Java: Jugando con JNI

Java

Este es uno de esos experimientos que sale por la necesidad, ya que recientemente alguien en mi compañia me preguntó como hacer algo parecido y le dije que usando JNI. Sin embargo cuando me preguntó si alguna vez lo había hecho y respondí que no, y no quedó muy impresionado.

Así que me decidí a tomar cartas en el asunto, y decidí escribir un pequeño programa que obtiene el UNIX PID y el PPID de un programa en Java, para ver como es el asunto. Como ustedes recordarán, el PID (Program Id) de un programa en UNIX no es más que un identificador único que una aplicación tiene en la tabla de procesos del sistema operativo cuando es ejecutada. El Parent Program Id no es más que el PID del programa que llamó a la aplicación actual.

Java no sabe nada de eso ya que es una caracteristica propia del sistema operativo en donde corre el programa, por lo cual usamos un poco de JNI para obtener el valor.

Para más detalles de como funciona la llamada nativa, solamente hagan lo siguiente:

[josevnz@localhost test]$ man getpid

Existen muy buenos tutoriales en la red, entre ellos un libro completo y otro procedimiento que detalla pasos especificos con el sistema operativo Linux. Mi intención no es escribir un tutorial sino mostrarles algunas piezas de código que considero interesantes.

Terminado este preambulo, vamos a escribir el código; lo primero es declarar una clase en Java la cual define dos métodos nativos, los cuales tendrán una implementación en C:

   1:package com.kodegeek.blog.unix;
2:
3:/**
4: * Native access to pid and ppid process information under UNIX.
5: * @author josevnz
6: * @version 0.1 - 09/04/2006
7: */
8:public final class UnixPid {
9:
10: static {
11: System.loadLibrary("unix");
12: }
13:
14: /**
15: * Class constructor
16: */
17: public UnixPid() {
18: super();
19: }
20:
21: /**
22: * Get the program UNIX id
23: * @return int
24: */
25: public native int getPid();
26:
27: /**
28: * get the program parent id
29: * @return int
30: */
31: public native int getPpId();
32:
33: /**
34: * Run the class from the command line. Test case
35: * @param args
36: * @throws Exception If the thread is interrupted
37: */
38: public static void main(String[] args) throws Exception {
39: UnixPid instance = new UnixPid();
40: System.out.println("PID: " + instance.getPid());
41: System.out.println("PPID: " + instance.getPpId());
42: Thread.sleep(300);
43: }
44:}

Nada complicado, cargamos la librería compartida (Linea 11, .so) en una declaración estática para garantizar que esta lista antes que cualquiera de los métodos sea llamado. Los métodos nativos (lineas 25 y 31) se declaran aqui.

Después vienen un par de pasos más o menos automaticos:

  • Compilar el código en Java
  • Crear el archivo .h de nuestra librería, usando javah en base al código compilado de Java
  • Implementar los métodos en C
  • Compilar el código en C, creando una librería compartida
  • Poner la librería en la ruta del LD_LIBRARY_PATH
  • Correr el programa y celebrar

Dado que soy muy perezoso, me decidí a escribir una tarea en Ant que hace todo el trabajo. Les coloco el archivo con sólo los pedazos importantes:

   1:<?xml version="1.0" encoding="ISO-8859-1"?>
2:<project
3: name="Unix"
4: default="build"
5: basedir=".">
6: <description>Access to native facilities using Java and
JNI</description>
7: <property description="Global properties" file="build.properties"/>
8:
9: <property description="Load any environment variables into Ant"
environment="env"/>
10:.......
48:
49: <target
50: name="build"
51: depends="init"
52: description="Compile the Java and native source code">
53: <javac
54: srcdir="${src.java}/main"
55: destdir="${build}"
56: deprecation="true"
57: optimize="false"
58: debug="true">
59: <include name="com/kodegeek/blog/unix/**/*.java"/>
60: <classpath refid="path.classpath"/>
61: </javac>
62:
63: <copy
64: todir="${build}" overwrite="true">
65: <fileset dir="${properties}/main">
66: <include
name
="com/kodegeek/blog/unix/**/*.properties"/>
67: </fileset>
68: </copy>
69:
70: <javah
71: class="com.kodegeek.blog.unix.UnixPid"
72: force="yes"
73: outputfile="${build}/include/UnixPid.h">
74: <classpath refid="path.classpath"/>
75: </javah>
76:
77: <exec
78: executable="make"
79: failonerror="true">
80: <arg value="-f"/>
81: <arg file="Makefile"/>
82: <arg value="INCLUDES=${native.include}"/>
83: <arg value="JAVA_HOME=${java.home}"/>
84: <arg value="SHARED_UNIX=${native.shared}"/>
85: <arg value="SRC=${src.cpp}/${ant.project.name}"/>
86: <arg value="DIST=${dist}"/>
87: <arg value="all"/>
88: </exec>
89:
90: </target>
91:
92: ....
110:
111: <target
112: name="jar"
113: depends="build"
114: description="Pack the project sources for distribution">
115:
116: <manifest file="${jar.file.manifest}">
117: <section name="${ant.project.name}/">
118: <attribute name="Built-By" value="${user.name}"/>
119: <attribute name="Sealed" value="true"/>
120: </section>
121: <attribute name="Main-Class"
value
="com.kodegeek.blog.unix.UnixPid"/>
122: </manifest>
123:
124: <jar
125: jarfile="${jar.file}"
126: basedir="${build}"
127: description="build the jar file"
128: excludes="**/include/**"
129: manifest="${jar.file.manifest}">
130: </jar>
131:
132: <delete file="${jar.file.manifest}" quiet="true"/>
133: </target>
134:
135:...
174:
175:</project>

Cuando generamos los encabezados en C (UnixPid.h), obtenemos algo como esto, a partir de la clase de Java:

   1:/* DO NOT EDIT THIS FILE - it is machine generated */
2:#include <jni.h>
3:/* Header for class com_kodegeek_blog_unix_UnixPid */
4:
5:#ifndef _Included_com_kodegeek_blog_unix_UnixPid
6:#define _Included_com_kodegeek_blog_unix_UnixPid
7:#ifdef __cplusplus
8:extern "C" {
9:#endif
10:/*
11: * Class: com_kodegeek_blog_unix_UnixPid
12: * Method: getPid
13: * Signature: ()I
14: */
15:JNIEXPORT jint JNICALL Java_com_kodegeek_blog_unix_UnixPid_getPid
16: (JNIEnv *, jobject);
17:
18:/*
19: * Class: com_kodegeek_blog_unix_UnixPid
20: * Method: getPpId
21: * Signature: ()I
22: */
23:JNIEXPORT jint JNICALL Java_com_kodegeek_blog_unix_UnixPid_getPpId
24: (JNIEnv *, jobject);
25:
26:#ifdef __cplusplus
27:}
28:#endif
29:#endif

Sin embargo la parte realmente interesante es la conexión del código nativo con Java. Eso lo hacemos en el archivo .C en donde vamos a implementar las declaraciones de los metodos nativos (copiados del archivo UnixPid.h):

   1:#include <jni.h>
2:#include <unistd.h>
3:#include <sys/types.h>
4:#include "UnixPid.h"
5:
6:/**
7: * Provides native access to the PID access of the current
8: * Java process.
9: * man getpid, getppid - get process identification
10: * @author josevnz@kodegeek.com
11: */
12:
13:/*
14: * Class: com_kodegeek_blog_unix_UnixPid
15: * Method: getPid
16: * Signature: ()I
17: */
18:JNIEXPORT jint JNICALL Java_com_kodegeek_blog_unix_UnixPid_getPid
19: (JNIEnv *, jobject) {
20: jint pid = getpid();
21: return pid;
22:}
23:
24:/*
25: * Class: com_kodegeek_blog_unix_UnixPid
26: * Method: getPpId
27: * Signature: ()I
28: */
29:JNIEXPORT jint JNICALL Java_com_kodegeek_blog_unix_UnixPid_getPpId
30: (JNIEnv *, jobject) {
31: jint ppid = getppid();
32: return ppid;
33:}
34:

La parte complicada es el manejo de los tipos de java versus los tipos de C. Sin embargo como estas rutinas lo que retornan son enteros entonces no hay mucho rollo (son tipos primitivos, no apuntadores).

Me decidí escribir un archivo GNU Make para controlar la compilación del código nativo en C, ya que Make esta mucho mejor dotado que Ant para estas tareas. Lo único que hice fué pasarle parametros desde Ant (cosas obvias como la ubicación de los archivos ya que eso se controla desde allí):

   1:# Native portion of the FileType, UnixPid classes
2:#
3:# Author: josevnz@kodegeek.com
4:#
5:# The following variables are defined from Ant:
6:# - SHARED_UNIX
7:# - INCLUDES
8:#
9:# Also this makefile is tailored to run on Linux
10:#
11:
12:CFLAGS=-c -I$(INCLUDES) -I$(JAVA_HOME)/../include
-I$(JAVA_HOME)/../include/linux -Wall -fPIC
13:LDFLAGS=-shared -static -lc
14:
15:all:
16: g++ $(CFLAGS) $(SRC)/UnixPid.C -o $(DIST)/UnixPid.o
17: g++ $(LDFLAGS) -o $(SHARED_UNIX) $(DIST)/*.o
18: rm -f $(DIST)/*.o

Una vez compilado el programa (todo lo hacemos desde Ant), lo corremos:

[josevnz@localhost kodegeek]$ export LD_LIBRARY_PATH=/home/josevnz/sf/kodegeek/dist/Unix
[josevnz@localhost kodegeek]$ java -jar /home/josevnz/sf/kodegeek/dist/Unix/Unix.jar
PID: 5737
PPID: 17896
[josevnz@localhost kodegeek]$

Ya para finalizar los dejo con un enlace a el código fuente completo, así como los binarios.

Buscar en Technorati: , , ,
Buscar en Blogalaxia: , , ,

5 thoughts on “Como obtener el UNIX UID desde Java: Jugando con JNI

  1. Como me indicaran en la cara obscura, el código tiene un pequeño error en el tipo de datos retornado por la función. Para evitar problemas, lo cambié de ‘int’ a ‘long’ y los cambios están ya disponibles en CVS.

  2. He buscado por cielo mar y tierra la forma de que una clase java pueda conectarse a un servidor unix remotamente y obtener los procesos que corren y su estado….esto es posible?
    – he recibido variadas ideas…
    entre ellas se cuentan desde la creacion de un daemon en java en el servidor(lo que encuentro poco optimo) hasta la ejecucion remota de comandos unix para crear un archivo de texto… pero como ejecuto estos comandos…existe otra posibilidad? se puede con jni?

    Ed

  3. Anonimo:

    JNI solamente te permite acceder las facilidades nativas del sistema operativo usando C y Java, no tiene nada que ver con comunicación remota.

    Si escribes tu nombre de verdad en respuesta a este comentario entonces te puedo sugerir algunas cosas para que las pruebes.

    –Jose

  4. hola ya hice todos los pasos y me sale esto que puedo hacer
    Exception in thread “main” java.lang.UnsatisfiedLinkError: no unix in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1682)
    at java.lang.Runtime.loadLibrary0(Runtime.java:822)
    at java.lang.System.loadLibrary(System.java:993)
    at com.messageplus.pid.UnixPid.(UnixPid.java:4)

  5. Rodrigo, dejame preguntarte un par de cosas:

    – ¿Lograste compilar las librerías nativas usando Make?
    – Colocaste la ruta a tu libreria en la variable LD_LIBRARY_PATH?

    El código funciona, alguno de los pasos anteriores no funcionó.

Los comentarios estan cerrados