MT. MultiTasking - JulTob/Ada GitHub Wiki

Multitasking

Multitasking creates child processes to do tasks for your main program. On multiprocessor machines, different processes can be assigned to different processors allowing work to be done simultaneously. On single processor machines, the processor switches several times a second between processes and does a little work on each.

The function to create a new child process is called fork. When Linux creates a new process, it doesn't start by creating a blank process. Instead, it makes a copy of the original process so there are effectively two copies of your program running. The fork function returns a value to tells your program if it is the original program or the new copy.

If you want to run a different program, you'll have to use one of the exec family of functions to load and run the new program. The exec functions destroy the old program and run a new program in its place. The above section, Using System and OSLib.Spawn, has an example C function called CrunIt that uses fork to start a new process and run a new program. type pid_t is new integer; function fork return pid_t; pragma import( C, fork ); Create a new child process identical to the original process and return 0 if the program is running as the child process or the PID of the parent if the program is running as the original parent process. Example: myPID := fork;

procedure wait( pid : out pid_t; status : in out integer ); pragma import( C, wait); pragma import_valued_procedure( wait ); Wait until a child process have finished running. Pid is the PID of the child. status is an integer code indicating whether the child finished normally, and if it was stopped by a signal, which signal terminated the program. Status can be a null pointer if you don't want status information. Example: wait( wait_pid, wait_status );

procedure waitpid( pid : out pid_t, pid_or_gid : in out pid_t; status : in out integer; options : integer ); pragma import( C, waitpid); pragma import_valued_procedure( waitpid); Wait for a specific child. If pid_or_gid is less than -1, waitpid waits for any child in the specified group id. If pid_or_gid is -1, it waits for any child (the same as wait). If pid_or_gid is 0, it waits for any child in the same group id as the parent. If pid_or_gid is greater than zero, waits for the child with the specified pid. Status can be a null pointer if you don't want status information. Options can determine whether waitpid returns immediately or blocks indefintely. Example: waitpid( child_pid, -children_gid, wait_status, 0 );

Wait3 and Wait4 are BSD UNIX variations which perform the same functions as wait and waitpid but with slightly different parameters.

When multitasking, if a child process stops, it's retained in memory so that the parent can use wait to find out the reason it stopped running. These children are called "zombies". If the parent process doesn't use wait, the zombies will remain indefinitely, using up system resources. For any large multitasking program, make sure you handle SIGCHLD signals: these are created when a child stops running. The SIGCHLD handler only needs to call wait and Linux will then remove the child process from memory.

The following is a simple multitasking example that multitasks two Put_Line statements.

-- a simple example of multitasking that multitasks -- two put_line statements

with ada.text_io; use ada.text_io; procedure multitask is type pid_t is new integer;

function fork return pid_t; pragma import( C, fork); -- create a new process

errno : integer; pragma import( C, errno); -- the last error code

procedure wait( pid : out pid_t; status : in out integer); pragma import( C, wait); pragma import_valued_procedure( wait); -- wait until all child processes are finished

myPID : pid_t; wait_pid : pid_t; wait_status : integer;

begin

Put_Line( "Welcome to this multitasking example" ); Put_Line( "This is the original process." ); New_Line;

-- the fork function duplicates this program into -- two identical processes.

Put_Line( "Splitting into two identical processes..." ); Put_Line( "-----------------------------------------" ); myPID := fork; -- split in two!

-- This program is now the original process or the -- new child process. myPID tells you which process -- you are.

if myPID < 0 then Put_Line( Standard_Error, "Fork has failed. Error code " & errno'img ); elsif myPID = 0 then Put_Line( "This is the child process" ); else Put_Line( "This is the original process." ); -- wait until child is finished wait( wait_pid, wait_status); if wait_pid < 0 then Put_Line( Standard_Error, "Wait error: wait returned PID " & wait_pid'img & " and error number " & errno'img); end if; end if;

end multitask;

Welcome to this multitasking example This is the original process. Splitting into two identical processes...

This is the original process. This is the child process

16.7 Linux File Operations

The Linux file operations are part of the standard C library, and don't need to be linked in with the -lc option. The C calls are defined in the "fcntl.h" header file.

[Explain Linux files here]

NoteLinux never shortens files. If your file gets smaller, you must shorten it yourself using truncate.

The following bindings assume these types have been defined.

type file_id is new integer; -- file ID number are discussed below

type mode_t is new integer; type gid_t is new integer; type uid_t is new integer; type size_t is new long_integer;

function unlink( pathname : string ) return integer; pragma import( C, unlink ); Delete a file. Example: Result := unlink( "/tmp/temp.txt" & ASCII.NUL );

function link( oldpath, newpath : string) return integer; pragma import( C, link ); Make a shortcut (hard link) to a file. Example: Result := link( "/tmp/temp.txt" & ASCII.NUL, "/tmp/newtemp.txt" & ASCII.NUL );

procedure getcwd( buf1 : out StringPtr; buf2 : in out stringptr; size : integer ); pragma import( C, getcwd ); pragma import_valued_procedure( getcwd) Return the current working directory.

function mkdir( pathname : stringPtr; mode : mode_t ) return integer; pragma import( C, mkdir ); Create a new directory and set default permissions.

function rmdir( pathname : string ) return integer; pragma import( C, rmdir ); Delete a directory. Example: Result := rmdir( "/tmp/tempdir" & ASCII.NUL );

function umask( mask : integer ) return integer; pragma import( c, umask ); Sets the default file permissions.

function stat( filename : stringPtr; buf : stat_struct ) return integer; pragma import( C, stat ); Get information about a file, such as size and when it was last opened.

function lstat( filename : stringPtr; buf : stat_struct ) return integer pragma import( C, lstat ); Same as stat function, but doesn't follow symbolic links.

function tmpnam( s : stringPtr ) return stringPtr; pragma import( C, tmpnam ); Create a random name for a temporary file.

function chown( path : string; owner : uid_t; group : gid_t) return integer; pragma import( C, chown ); function fchown( file : file_id; owner : uid_t; group : gid_t return integer; pragma import( C, fchown ); Change the ownership of a file to the specified owner and group. Example: Result := chown( "root.txt" & ASCII.NUL, 0, 0 );

function chmod( path : string; mode : mode_t ) return integer; pragma import( C, chmod ); function fchmod( file : file_id; mode : mode_t ) return integer; pragma import( C, fchmod ); Change the read/write/execute permissions on a file. Example: Result := chmod( "secure.txt" & ASCII.NUL, #8#640 );

Other low-level file operations are all done with the fcntl (file control) function. There are three variations to fcntl: it may have an operation code, an operation code and a long integer argument, or an operation code and a locking record argument.

The operation numbers are defined in /usr/src/linux/asm-i386/fnctl.h:

F_DUPFD : constant integer := 0; F_GETFD : constant integer := 1; F_SETFD : constant integer := 2; F_GETFL : constant integer := 3; F_SETFL : constant integer := 4; F_GETLK : constant integer := 5; F_SETLK : constant integer := 6; F_SETLKW : constant integer := 7; F_SETOWN : constant integer := 8; F_GETOWN : constant integer := 9; F_SETSIG : constant integer := 10; F_GETSIG : constant integer := 11;

function fcntl( fd : file_id; operation => F_DUPFD ) pragma import( C, fcntl ); Duplicates a file descriptor (same as dup2, but different errors returned). New descriptor shares everything except close-on-exec. New descriptor is returned.

function fcntl( fd : file_id; operation => F_GETFD ) pragma import( C, fcntl ); Get close-on-exec flag; low bit is zero, file will close on exec kernel call.

function fcntl( fd : file_id; operation => F_SETFD; arg : long_integer ) pragma import( C, fcntl ); Set the close-on-exec flag; low bit is 1 to make file close on exec kernel call.

function fcntl( fd : file_id; operation => F_GETFL ) pragma import( C, fcntl ); Get flags used on open kernel call used to open the file

function fcntl( fd : file_id; operation => F_SETFL; arg : long_integer ) pragma import( C, fcntl ); Set flags for open kernel call. Only async, nonblock and appending can be changed.

procedure fcntl( result : out integer; fd : file_id; operation => F_GETLK; lock : in out lockstruct ) return integer pragma import( C, fcntl ); pragma import_valued_procedure( fcntl ); Return a copy of the lock that prevents the program from accessing the file, or else if there is nothing blocking, the type of lock

procedure fcntl( result : out integer; fd : file_id; operation => F_SETLK; lock : in out lockstruct ) return integer pragma import( C, fcntl ); pragma import_valued_procedure( fcntl ); Place a lock on the file. If someone else has locked the file already, -1 is returned and errno contains the locking error.

procedure fcntl( result : out integer; fd : file_id; operation => F_SETLKW; lock : in out lockstruct ) return integer pragma import( C, fcntl ); pragma import_valued_procedure( fcntl ); Place a read or write lock on the file, or to unlock it. If someone else has locked the file already, wait until the lock can be placed.

Additional information about locks are found in /usr/src/linux/Documentation/locks.txt

type aLock is new short_integer; F_RDLCK : constant aLock := 0; -- read lock F_WRLCK : constant aLock := 1; -- write lock F_UNLCK : constant aLock := 2; -- unlock (remove a lock) F_EXLCK : constant aLock := 3; -- exclusive lock F_SHLCK : constant aLock := 4; -- shared lock

type aWhenceMode is new short_integer; SEEK_SET : constant aWhenceMode := 0; -- absolute position SEEK_CUR : constant aWhenceMode := 1; -- offset from current position SEEK_END : constant aWhenceMode := 2; -- offset from end of file

type lockstruct is record l_type : aLock; -- type of lock l_whence : short_integer; -- how to interpret l_start l_start : integer; -- offset or position l_len : integer; -- number of bytes to lock (0 for all) l_pid : integer; -- with GETLK, process ID owning lock end record;

To lock a file, create a lockstruct record and fill in the details about the kind of lock you want.

A read lock (F_RDLCK) makes the part of the file you specify read-only. No one can write to that part of the file.

A write lock prevents any other program from reading or writing to the part of the file you specify. Your program may change that part of the file without being concerned that another process will try to read it before you're finished.

If your program stops prematurely, the locks will be released. Example: Get exclusive right to write to the file, waiting until it's possible:

-- lock file myLockStruct : lockStruct; result : integer; ... myLockStruct.l_type := F_WRLCK; myLockStruct.l_whence := 0; myLockStruct.l_start := 0; myLockStruct.l_end := 0; fcntl( result, fd, F_SETLKW, myLockStruct ); if result = -1 then put_line( standard_error, "fcntl failed" ); end if; -- file is now locked ... -- unlock file myLockStruct.l_type := F_UNLCK; myLockStruct.l_whence := 0; fcntl( result, fd, F_SETLKW, myLockStruct ); if result = -1 then put_line( standard_error, "fcntl failed" ); end if;

[Double check off_t size for l_start, l_len--KB] function fcntl( fd : file_id; operation => F_GETOWN ) pragma import( C, fcntl ); Get the process (or process group) id of owner of file. The owner is the process that handles SIGIO and SIGURG signals for that file.

function fcntl( fd : file_id; operation => F_SETOWN, arg : long_integer ) pragma import( C, fcntl ); Set the process (or process group) id of owner of file. The owner is the process that handles SIGIO and SIGURG signals for that file. This affects async files and sockets.

function fcntl( fd : file_id; operation => F_GETSIG ) pragma import( C, fcntl ); Get the signal number of the signal sent when input or output becomes possible on a file (usually SIGIO or zero). (This is a Linux-specific function.)

function fcntl( fd : file_id; operation => F_SETSIG, arg : long_integer ) pragma import( C, fcntl ); Set the signal number of the signal sent when input or output becomes possible on a file (zero being the default SIGIO). Use this to set up a signal handler alternative to the kernel calls select and poll. See the man page for more information. (This is a Linux-specifc function.)