Linux Unit IV
Linux Unit IV
Processes Concepts:
A process is more than just a program. Especially in a multi-user, multi-tasking operating system
such as Linux there is much more to consider. Each program has a set of data that it uses to do
what it needs. Often, this data is not part of the program. For example, if you are using a text
editor, the file you are editing is not part of the program on disk, but is part of the process in
memory. If someone else were to be using the same editor, both of you would be using the same
program. However, each of you would have a different process in memory. See the figure below
to see how this looks graphically.
Under Linux many different users can be on the system at the same time. In other words, they
have processes that are in memory all at the same time. The system needs to keep track of what
user is running what process, which terminal the process is running on, and what other resources
the process has (such as open files). All of this is part of the process.
With the exception of the init process (PID 1) every process is the child of another process.
Another example we see in the next figure. When you login, you normally have a single process,
which is your login shell(bash). If you start the X Windowing System, your shell starts another
process, xinit. At this point, both your shell and xinit are running, but the shell is waiting for xinit
to complete. Once X starts, you may want a terminal in which you can enter commands, so you
start xterm.
Process API
Fork():
The fork() system call will spawn a new child process which is an identical process to the parent
except that has a new system process ID. The process is copied in memory from the parent and a
new process structure is assigned by the kernel. The return value of the function is which
discriminates the two threads of execution. A zero is returned by the fork function in the child's
process.
exit() vs _exit():
The C library function exit() calls the kernel system call _exit() internally. The kernel system call
_exit() will cause the kernel to close descriptors, free memory, and perform the kernel
terminating process clean- up. The C library function exit() call will flush I/O buffers and
perform aditional clean-up before calling
_exit() internally. The function exit(status) causes the executable to return "status" as the return
code for main(). When exit(status) is called by a child process, it allows the parent process to
examine the terminating status of the child (if it terminates first). Without this call (or a call from
main() to return()) and specifying the status argument, the process will not return a value.
The Vfork() function is the same as fork() except that it does not make a copy of the address
space. The memory is shared reducing the overhead of spawning a new process with a unique
copy of all the memory. This is typically used when using fork() to exec() a process and
terminate. The vfork() function also executes the child process first and resumes the parent
process when the child terminates.
wait(): Blocks calling process until the child process terminates. If child process has already
teminated, the wait() call returns immediately. if the calling process has multiple child processes,
the function returns when one returns.
waitpid(): Options available to block calling process for a particular child process not the first
one.
Kill():
This is the real reason to set up a process group. One may kill all the processes in the
process group without having to keep track of how many processes have been forked and all of
their process id's.
The function call "execl()" initiates a new program in the same environment in which it is
operating. An executable (with fully qualified path. i.e. /bin/ls) and arguments are passed to the
function. Note that "arg0" is the command/file name to execute.
int execl(const char *path, const char *arg0, const char *arg1, const char
*arg2, ... const char *argn, (char *) 0);
Where all function arguments are null terminated strings. The list of arguments is
terminated by NULL.
The routine execlp() will perform the same purpose except that it will use environment variable
PATH to determine which executable to process. Thus a fully qualified path name would not
have to be used. The first argument to the function could instead be "ls". The function execlp()
can also take the fully qualified name as it also resolves explicitly.
execv() and execvp():
This is the same as execl() except that the arguments are passed as null terminated array of pointers to
char. The first element "argv[0]" is the command name.
The routine execvp() will perform the same purpose except that it will use environment variable
PATH to determine which executable to process. Thus a fully qualified path name would not
have to be used. The first argument to the function could instead be "ls". The function execvp()
can also take the fully qualified name as it also resolves explicitly.
execve():
Zombie Process
On Linux operating systems, a zombie process or defunct process is a process that has completed
execution but still has an entry in the process table, allowing the process that started it to read its
exit status. In the term's colorful metaphor, the child process has died but has not yet
been reaped.
When a process ends, all of the memory and resources associated with it are deallocated so they
can be used by other processes. However, the process's entry in the process table remains. The
parent is sent a SIGCHLD signal indicating that a child has died; the handler for this signal will
typically execute the wait system call, which reads the exit status and removes the zombie. The
zombie's process ID and entry in the process table can then be reused. However, if a parent
ignores the SIGCHLD, the zombie will be left in the process table. In some situations this may
be desirable, for example if the parent creates another child process it ensures that it
will not be allocated the same process ID. A zombie process is not the
same as an orphan process. Orphan processes don't become zombie processes; instead, they
are adopted by init (process ID 1), which waits on its children.
The term zombie process derives from the common definition of zombie an undead
person. Zombies can be identified in the output from the Unix PS command by the presence of a
"Z" in the STAT
column. Zombies that exist for more than a short period of time typically indicate a bug in the
parent program.
To remove zombies from a system, the SIGCHLD signal can be sent to the parent manually,
using the kill command. If the parent process still refuses to reap the zombie, the next step would
be to remove the parent process. When a process loses its parent, init becomes its new parent.
Init periodically executes the wait system call to reap any zombies with init as parent.
Orphan Process
An orphan process is a computer process whose parent process has finished or terminated.
A process can become orphaned during remote invocation when the client process crashes after
making a request of the server.
Orphans waste server resources and can potentially leave a server in trouble. However there
are several solutions to the orphan process problem:
1. Extermination is the most commonly used technique; in this case the orphan process is killed.
2. Reincarnation is a technique in which machines periodically try to locate the parents of
any remote computations; at which point orphaned processes are killed.
3. Expiration is a technique where each process is allotted a certain amount of time to finish
before being killed. If need be a process may "ask" for more time to finish before the
allotted time expires.
A process can also be orphaned running on the same machine as its parent process. In a UNIX-
like operating system any orphaned process will be immediately adopted by the special "init"
system process. This operation is called re-parenting and occurs automatically. Even though
technically the process has the "init" process as its parent, it is still called an orphan process
since the process which originally created it no longer exists.
Objective: how more than one process communicates with other processes sand calling
functions, kernel support
IPC:Message Queues:<sys/msg.h>
Basic Message Passing IPC messaging lets processes send and receive messages, and queue
messages for processing in an arbitrary order. Unlike the file byte-stream data flow of pipes,
each IPC message has an explicit length. Messages can be assigned a specific type. Because of
this, a server process can direct message traffic between clients on its queue by using the client
process PID as the message type. For single-message transactions, multiple server processes can
work in parallel on transactions sent to a shared message queue.
Before a process can send or receive a message, the queue must be initialized (through the
msgget function see below) Operations to send and receive messages are performed by the
msgsnd() and msgrcv() functions, respectively.
When a message is sent, its text is copied to the message queue. The msgsnd() and msgrcv()
functions can be performed as either blocking or non-blocking operations. Non-blocking
operations allow for asynchronous message transfer -- the process is not suspended as a result of
sending or receiving a message. In blocking or synchronous message passing the sending process
cannot continue until the message has been transferred or has even been acknowledged by a
receiver. IPC signal and other mechanisms can be employed to implement such transfer. A
blocked message operation remains suspended until one of the following three conditions occurs:
It can also return the message queue ID (msqid) of the queue corresponding to the key argument.
The value passed as the msgflg argument must be an octal integer with settings for the queue's
permissions and control flags.
...
key = ...
msgflg =
...
Processes requesting access to an IPC facility must be able to identify it. To do this, functions
that initialize or provide access to an IPC facility use a key_t key argument. (key_t is essentially
an int type defined in <sys/types.h>
The key is an arbitrary value or one that can be derived from a common seed at run time. One
way is with ftok() , which converts a filename to a key value that is unique within the system.
Functions that initialize or get access to messages (also semaphores or shared memory see later)
return an ID number of type int. IPC functions that perform read, write, and control operations
use this ID. If the key argument is specified as IPC_PRIVATE, the call initializes a new instance
of an IPC facility that is private to the creating process. When the IPC_CREAT flag is supplied
in the flags argument appropriate to the call, the function
tries to create the facility if it does not exist already. When called with both the IPC_CREAT and
IPC_EXCL flags, the function fails if the facility already exists. This can be useful when more
than one process might attempt to initialize the facility. One such case might involve several
server processes having access to the same facility. If they all attempt to create the facility with
IPC_EXCL in effect, only the first attempt succeeds. If neither of these flags is given and the
facility already exists, the functions to get access simply return the ID of the facility. If
IPC_CREAT is omitted and the facility is not already initialized, the calls fail. These control
flags are combined, using logical (bitwise) OR, with the octal permission modes to form the
flags argument. For example, the statement below initializes a new message queue if the queue
does not exist.
msqid = msgget(ftok("/tmp",
The first argument evaluates to a key based on the string ("/tmp"). The second argument
evaluates to the combined permissions and control flags.
The msgctl() function alters the permissions and other characteristics of a message queue. The
owner or creator of a queue can change its ownership or permissions using msgctl() Also, any
process with permission to do so can use msgctl() for control operations.
The msqid argument must be the ID of an existing message queue. The cmd argument is one of:
IPC_STAT
-- Place information about the status of the queue in the data structure pointed to by buf.
The process must have read permission for this call to succeed.
IPC_SET
-- Set the owner's user and group ID, the permissions, and the size (in number of bytes) of
the message queue. A process must have the effective user ID of the owner, creator, or
superuser for this call to succeed.
IPC_RMID
#include
<sys/ipc.h>
#include
<sys/msg.h>
...
if (msgctl(msqid, IPC_STAT, &buf) == -
1) { perror("msgctl: msgctl failed");
exit(1);
}
...
if (msgctl(msqid, IPC_SET, &buf) == -
1) { perror("msgctl: msgctl failed");
exit(1);
}
...
The msgsnd() and msgrcv() functions send and receive messages, respectively:
The msqid argument must be the ID of an existing message queue. The msgp argument is a
pointer to a structure that contains the type of the message and its text. The structure below is an
example of what this user-defined buffer might look like:
struct mymsg {
process. The argument msgflg specifies the action to be taken if one or more of the
If (msgflg & IPC_NOWAIT) is non-zero, the message will not be sent and the calling
process will return immediately.
If (msgflg & IPC_NOWAIT) is 0, the calling process will suspend execution until
one of the following occurs:
o The condition responsible for the suspension no longer exists, in which case the
message is sent.
o The message queue identifier msqid is removed from the system; when this
occurs, errno is set equal to EIDRM and -1 is returned.
o The calling process receives a signal that is to be caught; in this case the
message is not sent and the calling process resumes execution.
Upon successful completion, the following actions are taken with respect to the data
structure associated with msqid:
o msg_qnum is incremented by 1.
o msg_lspid is set equal to the process ID of the calling process.
o msg_stime is set equal to the current time.
#include
<sys/ipc.h>
#include
<sys/msg.h>
...
int msgflg; /* message flags for the operation */
if (msgp == NULL) {
msgsz
= ...
msgflg =
...
...
msgsz = ...
msgtyp =
first_on_queue; msgflg
= ...
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
/*
* Declare the message structure.
*/
main()
{
int msqid;
int msgflg = IPC_CREAT |
0666; key_t key;
message_buf sbuf;
size_t buf_length;
/*
* Get the message queue id for the
* "name" 1234, which was created by
* the server.
*/
key = 1234;
/*
* We'll send message type 1
*/
sbuf.mtype = 1;
buf_length = strlen(sbuf.mtext) + 1 ;
/*
* Send a message.
*/
if (msgsnd(msqid, &sbuf, buf_length, IPC_NOWAIT) < 0) {
printf ("%d, %d, %s, %d\n", msqid, sbuf.mtype, sbuf.mtext, buf_length);
perror("msgsnd");
exit(1);
}
else
printf("Message: \"%s\" Sent\n", sbuf.mtext);
exit(0);
}
The Message queue is created with a basic key and message flag msgflg = IPC_CREAT | 0666
-- create queue and make it read and appendable by all.
A message of type (sbuf.mtype) 1 is sent to the queue with the message ``Did you get this?''
message_rec.c -- receiving the above message
The full code listing for message_send.c's companion process, message_rec.c is as follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
main()
{
int msqid;
key_t key;
message_buf rbuf;
/*
* Get the message queue id for the
* "name" 1234, which was created by
* the server.
*/
key = 1234;
/*
* Receive an answer of message type 1.
*/
if (msgrcv(msqid, &rbuf, MSGSZ, 1, 0) < 0) {
perror("msgrcv");
exit(1);
}
/*
* Print the answer.
*/
printf("%s\n", rbuf.mtext);
exit(0);
}
The Message queue is opened with msgget (message flag 0666) and the same key as
message_send.c.
A message of the same type 1 is received from the queue with the message ``Did you
get this?'' stored in r