0% found this document useful (0 votes)
9 views55 pages

EECS 471: Networked OS Course Overview

Network Operating Systems

Uploaded by

Oratexa
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views55 pages

EECS 471: Networked OS Course Overview

Network Operating Systems

Uploaded by

Oratexa
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

EECS 471 Networked Operating Systems

Instructor: Patrick Troy


Teaching Assistant: Arunkumar Elango

Time: 1:00 - 2:00 MTWF


Room: LC E2

Description:
EECS 471 is a 4 credit ( 5 credits for graduate students ) course and covers network programming
and systems programming from the operating system viewpoint. Special emphasis is given on
standards based APIs, with explanation of typical interfaces.
The course work consists of:

4 Programming Assignments
2 Midterms
1 final

Tests will cover conceptual issues from kernel design and programming techniques covered in the
class.

Prerequisites:
EECS 371

Topics:

Process
Interprocessor Communication
Network and Network Protocols
Distributed processing
Network Programming

Academic integrity:

Students who fail to demonstrate on tests an understanding of the programs they have handed in will fail
the course.

You are not to show your program to anyone nor to look at anyone else's program. You may discuss
programming concepts but only in general terms.

A word of caution : Unix gives read permissions on your files to everyone, by default. This means, unless
you turn the permissions off, you run the risk of someone looking into your code.
Syllabus

1. The Unix Kernel and Processes System calls, POSIX and XOPEN APIs, Kernel design, Processes

2. Signals POSIX signals, signal handlers, default actions

3. Creating New Processes fork, wait, waitpid, exec

4. File systems File Table, File Descriptor Table, open, close, write, read, dup.

5. Inter-Process Communication pipes, named pipes, message queues, shared memory, and
semaphores

6. Networking Ethernet, internetwork, naming, ip numbers and resolution, ARP, IP semantics.

7. Sockets TCP and UDP.


Course Notes:
Kernel
Signals
Processes
Files
IPC
Networking
Sockets
TCP/IP
RPC
Kernel
In this section, we examine the basics of kernels and processes. These are the fundamental building blocks of
operating systems, and of sophisticated services.

Processes
Consider a process is a program in execution. A pure process is said to run on a virtual processor and thus has
the following properties:

Finite progress: over each period of time of length t, the probability that some process executes is bounded
by p>0.
Execution of a pure process is independent of any other process that is executing on the system (and is, in
particular, independent of the order or "interleaving" of execution.

The definition of pure process does not allow any interaction between processes, and that is not desirable.
However, the amount of interaction needs to be strictly controlled, to prevent errors in one process from being
propagated to unrelated processes. This unconstrained error propagation occurs in MS/DOS and MS/Windows
(But not Windows NT), enabling faulty programs to corrupt working programs.

Hence, we begin from pure processes and enable carefully controlled visibility of other processes. The most
central inter-process visibility is the file system, which is always available for sharing without any special
arrangements. For example, it is possible use an editor to modify a program's source code, save it, and then
without exiting the editor to invoke the compiler on the source code file.

Other types of interaction can be controlled through both protection mechanisms and explict calls. Examples of
these impurities are:

Signals: notification of asynchronous events (including control of some processes by other processes).
Message passing: sending data directly to another process (not indirectly through another process.
Shared memory: this is physical memory which is mapped into multiple processes address space.
Semaphores
Sockets codes

Kernel
The kernel is not a process. Rather it extends the base hardware to a virtual machine, which hides various details
of the low level implementation. The purpose of the kernel is to:

Implement the process abstraction


To provide protection for different users from each other and from the outside world.
To deal with error conditions and architectural ideosyncracies.

The Unix kernel is written almost entirely in C, with about 4,000-8,000 lines of code of assembler. Since the
kernel is the only part of the system which can execute privileged instructions, these instructions are coded in
assembly language.

Failures

The kernel is extremely important; if the kernel detects a failure its typical response is to panic, which causes a
reboot. There are two alternatives.
Ignore the error: this technique is almost never used. Although the computer would reboot less often, the
error may contaminate the system allowing unauthorized accessed, giving incorrect results, etc.
Correct the state of a kernel: thereby removing the effect of the error. This is much more difficult, and may
allow errors to propagate.

The complexity of failed systems is much higher than of working systems; moreover failures are relatively rare.
Hence, trying to determine and fixed a problem is fraught with perils, and is impossible to exhaustively test.
Instead, it is a better policy to fail fast (ie. fail at the earliest detection of erroneous state), and restart, rather than
correct problems.

System calls
Processes communicate with the kernel via system calls which are entry points into the kernel. The set of system
calls in Unix can be found in section 2 of the manual pages (see man -s 2 Intro in Solaris). In addition, there are
many library calls listed in section 3 of the manual; all library codes invoke system calls for kernel services.
These code for these library calls are linked with the user executable by ld; by default the standard C library
(libc) is linked in together with C code. System calls themselves are implemented with trap instructions which
simultaneously enter the kernel at an indexed entry point and enable the execution of privileged instructions.

The limited number of system calls is prefered to branching to arbitrary location is the kernel since it restricts the
number of entry points. Since the kernel is charged with providing security, every one of these entry points must
be well guarded, checking that the process parameters are legal and that the process has permission to perform
the required operations.

Superuser

In Unix, the operating system functions are split between system processes and the kernel. Unix has two levels
of users in the system: ordinary users and the superuser. The difference between these two is that the super user
is allowed much less restriction on the system calls that are made--for example, the superuser can modify any
user's files whereas ordinary users can modify only their files and the files for which they have been given
explict access.

To be sure that these permissions are not abused, it is the processes responsibility to check appropriate
conditions since the kernel is extremely permissive with the superusers. Most violations of security in Unix
systems are the result of superuser processes failing in this responsibility--not kernel problems.

Concurrency
The Unix kernel is designed so that it is always in the context of a single process (after the first process, init is
created). The traditional Unix kernel allows only one process to be executing in the kernel at a time, and control
is given up voluntarily while in the kernel. Hence, there is no need for semaphores when constructing a Unix
systems. More modern Unix systems allow multiple processes to be executing in the kernel (in a multiprocessor
system), but the kernel has be modularized into subsystems which are largely independent. Any interactions
between subsystems must then be guarded with semaphores.

Process failures

There are many conditions which can cause a process to terminate, such as divide by zero, external termination
via kill -9, etc. The kernel cleans up after these processes as best it can, so for each process the kernel keeps
some process related info in the kernel.

Kernel-based process structures


The kernel tracks two types of information on a per process basis:

Infomation which is needed only when the process is running is kept in the u-area. The u-area is part of
the process and can be swapped out when the process is not running. But it exists in kernel space, not user
space, and hence can only be accessed by the kernel.
Information which is needed at all times is kept in the process table.
Signals
Signals are (primarily) asynchronous notification of events. There are many different signals which can arise
from within or outside the process.

Signals arrive for a process and are posted in the kernel. The kernel can then arrange:

Perform some default action on the process


To notify the process by causing a signal handler to be invoked in the process. The handler is a user level
procedure.

Where do signals come from


Signals come from either:

A different process: the process invokes a system call which posts a signal to the target process.
The same process: typically, an error such as divide-by-zero.
from the kernel: timer signal (requested by process)

Example:

Signals allow users to control processes. For example, typing the following in csh results:

^C in csh causes the shell to send a SIGINT to the current foreground process if any.
^Z in csh causes the shell to send a SIGSTOP to the current foreground process.
typing "fg" sends a SIGCONT signal to the foreground process.

Signal types:
termination of a process: death of a child
process induced exceptions: address violation, floating point error
unrecoverable errors during a system call: running out of resources such as memory
unexpected error during a system call: writing a pipe which has no reader, non existant system call, illegal
return. Could send an error indication, but programs which don't test error indications would never know.
emanating from processes in user mode: timer alarm or by sending a signal.
Terminal interaction: hangup on a login terminal
Tracing execution of the program.

What can be done with a signal?

abort:
ignore: as if it never happened (tosses the signal)
handle: provide a procedure to be called when the signal arrives (is pending)

In addition, another variable for each signal is for:

block: don't allow it to be serviced for now

What are the signal data structures in the Kernel?

a bit/signal for pending signals


a bit/signal for blocked signals
a bit vector for each signals describing other signals to be blocked while in the signal handler.
a pointer/signal for handler

When are signals posted?


Either a process is executing (process in user mode) or the kernel is executing. After system startup, the
kernel executes in the context of some process (that in the u area), and the process is said to execute
in kernel mode.
Processes running in kernel mode are non-pre-emptible. The only way to give up control of the kernel is to
exit the kernel or to go to sleep. (Processes running in user mode are pre-emtable.)
The kernel selects another process to run either:
1. when the current process goes to sleep
2. when the current process is coming out of kernel mode.
When the process executing changes, it is called a context switch.
Signals are posted only when in kernel mode. These signals can come from:
The kernel executing on behalf of the process
Other processes which trap
Interrupts
trap for the current process
Since a process can send another process a signal, the set of posted signals are kept in the process table
(not the u area).
Note that signals are represented by a bit in the kernel. Multiple signals can result in a single call

What happens if there is a signal handler?

the signal is caught as follows

the kernel fakes a procedure call on the user stack, and the signal is blocked
the procedure is called (by the process returning to user mode)
when it returns the procedure executing at the time of the call is invoked.
delivery of the caught signal is unblocked when the handler completes

Why do we want signal handlers?


Signal handlers allows us to defer signal actions until cleanup, allow us to prevent corruption of data structures
(ex iosignal arrives while we are updating I/O structure), to paint the screen (in an editor when we bring it to the
foreground).

POSIX signals

POSIX signals
Signal name Use action
SIGALRM Timer alarm (see alarm()) exit
SIGABORT Abort process (see abort()) core
SIGFPE Illegal floating point operation (hardware fp exeption) core
SIGHUP Controlling terminal hung up exit
SIGILL Executed an illegal machine instruction core
SIGINT Process interuption (type ^C) exit
SIGKILL Kills a process (cannot be ignored) (kill -9) exit
SIGPIPE Illegal write to a pipe exit
SIGQUIT Process quit (type ^\) core
SIGSEGV Memory address violation (ex. deref null pointer) core
SIGTERM Process termination (kill pid) exit
SIGUSR1 User defined signals exit
SIGUSR2 User defined signals exit
SIGCHLD Sent to a parent when it's child dies ignore
SIGCONT Resume execution of a stopped process (type fg) ignore
SIGSTOP Stop a process stop
SIGTTIN Stop a background process when it tries to read to its controlling terminal stop
SIGTSTP Stop a process by sending a ^Z stop
SIGTIOU Stop a background process when it tries to write to its controlling terminal stop

Signal library call


The original version of signals, which described actions associated with signals via the signal system call, had a
race condition. When a handler was called, the default behavior was installed into the handler. If another call
occured before the handler could call signal again, the default behavior (ex. termination) could occur.

sigaction

Installs/removes a signal handler.

#include < signal.h >

int sigaction(int signum, struct sigaction *action, struct sigaction *oldAction);

where

struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flag;
};

where sa_handler is one of:

SIG_IGN: ignore
SIG_DFL: reinstall default action
handler function pointer

and sa_mask is set of signals to block while in the handler in addition to signum and currently blocked signals.
The third element sa_flag is either 0 or SA_NOCLDSTOP.

SA_NOCLDSTOP can be used with SIGCHLD which causes a signal to be sent to the parent when the child is
terminated but not when the child is stopped.

sigprocmask
We can set the set of signals to be blocked with the call:

int sigprocmask(int cmd, const sigset_t *new_mask, sigset_t *old_mask)

Where cmd is one of the following:

SIG_SETMASK: set the set of blocked signals to exactly that contained in new_mask. (old_mask is set to
the previous value)
SIG_BLOCK: add the signals specified in new_mask to the set of signals already blocked.
SIG_UNBLOCK: remove the signals specified in new_mask from the set of signals already blocked.

Creating masks
Since the type of a signal mask will differ between system, portable code requiers the manipulation of signal
masks through an API.

int sigemptyset(sigset_t *sigmask);


int sigaddset(sigset_t *sigmask, const int signal_num);
int sigdelset(sigset_t *sigmask, const int signal_num);
int sigfillset(sigset_t *sigmask);
int sigismember(const sigset_t *sigmask, const int signal_num);

Concurrency issues

Consider a signal handler which maniputes a data structure which is also maniputated in non-signal handler part
of the code. Then it is possible that the signal handler is called while the data structure is being manipulated,
leaving the data stucture in an inconsistent state.

In such cases, it is necessary to block the signal during the manipulation of the data structure
using sigprocmaskIt is also possible that two signal handler manipulate the same data structure (or that the same
handler is called for different signals). In that case, it is necesssary to block all of these signals when servicing
any of them. For this purpose of sigaction element sa_mask is used.

Long jump vs. Exceptions


A signal may make it desirable to go back to a previous point in the program. This is useful when an error must
be dealt with at a higher level than the context in which it appears.

The APIs setjmp and longjmp allow multiple procedure call nesting levels to be returned from at once. The
signal versions of these reset signal flags.

int sigsetjmp(sigjmpbuf env, int save_sigmask)

If save_sigmask is non-zero, then the sigmask is saved at the sigsetjmp point in the env.

int siglongjmp(sigjmpbuf env, int ret_val)

Jumps bak to the sight of the sigsetjmp and "completes" the call by giving a non-zero return value of ret_val. (A
zero return value is returned the first time). In C++, exception handling is used for this purpose via
the try/catch/throw language primitives. The advantage of C++ exception handling is that automatic variables
(local variable allocated on the stack) are destucted, while longjmp does not.
kill

The kill call enables a signal to be sent between processes. The processes must have the same real or effective
user ID, or the sender must be super user.

The call specifies the process Id to be the target and the signal to be sent to that process
int kill(pid_t pid, int signal_number)

which returns 0 if success and -1 on failure.

The meaning of pid is as follows:

pid > 0: the process id to receive the signal


pid=0: to all processes in the same process group as the sender
pid=-1: send to all processes whose real user ID is the same as the effective user ID of the calling process.
If superuser, to all processes (This is not POSIX behavior, just UNIX).

Alarm

Some work needs to be done periodically in a system. Ex. clock synchronization, check that all the processes for
an application are running, forcing disk writes out to physical storage.

unsigned int alarm(unsigned int time_interval);

Where time_interval is in seconds. If time interval is 0 the alarm is turned offed. After the timer expires, a
SIGALRM signal is sent to the process. Returns the amount of time from the previous alarm call.

BSD has ualarm in which time is specified in microseconds.

File Descriptor, Tables, and Operations


In this section, we describe the basic way in which files are accessed through the kernel. We defer to a later
section the means by which files are constructed on disk, and the data structures which are used to represent
files.
Processes
Information stored in the process:
Field Meaning
rUID the real user ID (UID of parent)
rGID the real group ID (GID of parent)
eUID the effective user ID (changed on setuid bit or seteuid)
eGID the effective group ID (changed on setgid bit or setegid)
saved set-UIC
saved set-GIC
Process group ID: process ID which identifies multiple processes and is the PID of the
PGID
lead (ancestor) process
Session ID: process ID which identfies multiple processes which are part of the same
SID
terminal session
Additional GIDs a user can belong to multiple groups
Current directory current working directory inode id
Root directory root directory inode id
Signal handling pending signals, signal handlers, blocking during signal handlers
Blocked signals set of signals which are blocked
Umask set of permissions which are removed from created files
Nice value priority level of the process
Controlling
Terminal associated with process
Termininal

Ordinary users cannot change UID/GID except from the set of UIDs/GIDs (effective, real, or saved) or via
setuid/setgid bit.

Process creation and management


New process creation

The initial process init is created by the kernel. All other processes are created from some process by a fork. On
a fork, a new process is cloned from the original process (including the u area). The cloned process is called
the child, the original process is called the parent.

The child differs from the parent in the following way:

Process ID (PID)
Parent Process ID (PPID)
Pending Signals are cleared in the child
Alarm clock time: reset to 0
file locks are not inherited by child

Calls
The process management calls are:

fork:
exec:
wait:
exit:

fork

prototype:

pid_t fork(void);

If the fork succeeds, it returns 0 to the child, and the PID of the child to the parent.

If the fork fails, it returns -1 and sets the value errno with the value:

ENOMEM: Insufficient memory to create the new process


EAGAIN: The number of processes in the system exceeds the limit

POSIX defines the maximum number of processes that can exist in a system (MAXPID) and for a single user
(CHILD_MAX).

Clearly, a new process means copying over the u area, and inserting a new process in the process table. There are
three parts of the process which reside in user space:

text region: contains executable machine instructions


stack:
data:

The text region, like file table entries, are shared (since they are read-only). Stack and data are not, and logically
need to be copied, even though most likely a forked process will exec a different executable, replacing all the
regions associated with the parent.

To save extraneous copies, a common implementation technique is copy-on-write in which the child shares read
only copies of all regions. If the process writes a read-only region, a copy is then made with write permission.

_exit

The exit call terminates a process, freeing up the regions associated with the process (user memory), closing file
descriptors, and releasing the u area. However, the process table entry remains intact. A process without u area
but with a process table entry is called a zombie.

void _exit(int exit_code)

where the lower 8 bits of exit code is the value returned for the process. By convention, 0 indicates a successful
termination.

Usually, users call exit, which performs the following functions:

flushes all streams


calls procedures registered with atexit
calls _exit

Removing zombies
If a child process, c, dies before its parent, than c becomes a child of init. To remove the zombied process, a wait
(or waitpid) must be performed by the parent.

pid_t wait(int *status_p);

pid_t waitpid(pid_t child_pid, int *status_p, int options);

Returns the process id of the child process, or -1 on failure. The parameters are:

child_pid
> 0: Waits for the child with that PID
= 0: Waits for any child in the same process group as parent
=-1: Waits for any child
< -1: Waits for any child whose process group is the absolute value of child_pid
status_p
child exit status (bits 8-15)
core file flag (bit 7)
signal number (bits 0-6): zero if termination was via _exit
options
WFNOHANG: declared in . Non-blocking call
WNOTRACED: will wait for a process that is stopped (for shell job control)

The errno for wait/waitpid are:

EINTR system call interupted by signal


ECHILD
wait calling process has no unwaited-for child processes
waitpid childPid value illegal or process cannot be in state defined by option value.
EFAULT statusPtr is an illegal address
EINVAL options value is illegal

waitpid can be either blocking or non-blocking, and can wait for any child that is stopped due to job control.

Rather than use the bit positions, you should use the following macros:

WIFEXITED(* status_p): returns non-zero iff process was terminated via _exit.
WEEXITSTATUS(* status_p): If process terminated via _exit, this returns the exit paramter
WIFSIGNALED(* status_p): Returns a non-zero value iff a child was terminated due to a signal.
WTERMSIG(*status_p): If process terminated via a signal, signal number that caused termination
WSTOPPED(*status_p): Returns a non-zero value if a child process has been stopped due to job control
WSTOPSIG(*status_P): Returns the signal number that had stopped a child process.

exec
There are several different exec calls, enabling:

The exec call contains either a filename or path:


filename: if the file name contains a "/", then it specifies the location of the file, otherwise the shell
PATH variable is used to specfy the directories to look into for filename. (Can be either shell script
or binary)
path: specifies the directory of the file. (must be binary)
An environment to be passed to the executable (has an e in the exec name.
A null terminated set of arguments or a null terminated argument vector.

int execl(const char *path, const char *arg, ...):


int execlp(const char *filename, const char *arg, ...):
int execle(const char *path, const char *arg, ..., const char **env);
int execv(const char *path, const char *argv[]):
int execvp(const char *filename, const char *argv[]):
int execve(const char *path, const char *argv[], const char **env);

The exec system call does the following:

It passes the arguments (strings) to the new program. These arguments are passed to main in the following
two variables
argc: the count of the number of arguments - 1.
argv: an array of strings 0...argc
It passes the environment which is an array of name=value strings. The array comes from:
the exec system call (if there is an "e" in the name after exec),
otherwise
the environ global variable if ANSI C, otherwise
as a third argument from main.
Note that by default, most compilers are ANSI C although some (gcc) also support K&R C.
It changes a number of settings in the u area based on the executable file name

If the exec succeeds:

the process's stack, data, and text regions will be replaced


file descriptors will be closed if fcntl close-on-exec flags were set.
effective UID: changed if exec'ed program has set-UID flag set
effective GID: changed if exec'ed program has set-GID flag set
saved set-UID: changed if exec'ed program has set-UID flag set
installed signal handlers are replaced with signal's default action.

Note that fork and exec are seperate calls, increasing the flexibility and enabling actions to occur between the
fork and exec.

Race conditions, etc.


Process fork, wait and exec are a very effective means of building multi-process applications.

When fork returns to either both parent and child exist


Parent can execute system calls to define the state of the child at start up (eg. blocking signals).
Parent can synchronize with the termination of child (via wait), and hence know that all the childs actions
have completed.

We shall see when we get to networked machines, that these conditions do not hold.

Process groups

A process group is a set of process with the same PGID. The PGID is the PID of the lead process.

This is the primary mechanism for dealing with groups of processes.


#include
#include

pid_t setpgp(void);
pid_t getpgid(void);
The setpgp sets the process group id to the PID.

The getpgid returns the process group ID.

Sessions
A session contain one or more process groups
#include
#include

pid_t setsid(void);

The setsid sets the session Id and pocess group id to the PID.

Other API's
#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void); // get process id


pid_t getppid(void); // get parent process id
pid_t getuid(void); // get real user id
pid_t getgid(void); // get real group id
pid_t geteuid(void); // get effective user id
pid_t getegid(void); // get effective group id
pid_t setuid(uid_t uid); // get real user id
pid_t setgid(gid_t gid); // get real group id
pid_t seteuid(uid_t uid); // get effective user id
pid_t setegid(gid_t gid); // get effective group id

Notes:

setuid (setgid)
Superuser: set real, effective, and save-set uid (gid)
otherwise: set effective UID to the parameter if it matches the real or saved-set UID.
seteuid (seteuid) like setuid, but if superuser only sets the effective UID
File types

regular: ordinary Unix files


directory: similar to ordinary files but can only be accessed and modified through restrictive system calls
device files: associates device names with their driver. There exists two types of devices:
block special: Memory devices such as disk which enable caching (ie buffering in the kernel) and
block accesses.
character files: also called raw devices. (It is possible for disks to be both a character and block
device).
fifo: pipes
symbolic links

Of course, new types can be added as Unix evolves.

Disk data structures

A disk is divided into partitions.

Partitions are joined together logically only after the kernel boots, via mount. However, this is system
administration, and hence not covered by POSIX.

Partitions

The disk is divided into partitions, which are logical disks. Each partition contains a logical file system.

Not only is each partition logically complete, but different types of filesystems can be stored on different
partitions. For example, a linux file system in one partition and a windows FAT16 file system in a different
partition.

It is necessary to perform a file system specific format on the disk to lay down the data structures on which the
file system will be built. We describe this in the following sections.

A partition is divided into blocks, typically of 4-8Kb. The blocks are of the following forms:

boot block: used to start the OS


super block: describes the partition
inode blocks: describes a file
file blocks: the data contained in the file
indirect blocks: used to construct large files
double indirect blocks: used to construct very large files

Boot block

Not all partitions are boot partitions. However, there must be a bootable partitions, which enables the kernel to
be loaded. Only after the kernel is loaded, can the kernel understand the rest of the partitions to access the file
system.

Superblock

Each partition is described by a superblock. The super block contains:

The size of the partition


The number of inodes
The number of file blocks
The size of flile blocks
the set of free blocks
the set of free inodes
Shutdown status

inodes

Each file on disk is described by an inode, which contains the following information about a file:

inode number
file type
hard link count
UID
GID
size in bytes
permissions: read,write,execute, set-uid, set-gid
last accessed time
last modified time
last change time: last time the file access permissions, UID, GID, or hardlink count have changed.
indirect block
double indirect block

Each inode is kept at a fixed address, and the root inode is at index 2 within the partition.

Directories

A directory could almost be an ordinary file, but because of its importance structurally to the filesystem, there
are special APIs to manipulate it.

The directories consist of a number of pairs of <names, inode>.

File operations

open: create a new entry in the File Descriptor table


creat: like open, but a new file is created
dup: copy over a file descriptor into the lowest number file descriptor
pipe: create a pipe
close: remove a File Descriptor entry
mknod: creates special, regular file, or named pipe
link: Create a directory entry for an existing file
unlink: remove a directory entry
chown: Change the owner and group of a file
chmod: Change access modes of the file
stat: Info about files
read: input
write: output
lseek: change the file pointer
chdir: Change the current directory

File Descriptor Table

There is a File Descriptor table per process in the u area. A file descriptor entry contains:
A pointer to a file table entry

Several File Descriptors are opened for every process:

1. stdin
2. stdout
3. stderr

The File Descriptor table contains OPEN_MAX entries, which must be at least as large as
POSIX_OPEN_MAX.

File Table
There is one File Table in the kernel. File Table entries are pointed to by File Descriptors, and in turn point to
file IDs:

count of the number of File Descriptors pointing to this entry


access mode: read or write
file ID: (called inode in Unix parlance)
current offset into the file

Opening, Closing and Manipulating file description table


Open

Finds the lowest numbered free file descriptor entry, and creates a new entry which points to a new file table
entry.
#include <sys/types.h>
#include <fcnt.h>

int open(const char *pathName, int accessMode, mode_t permission);

which returns -1 on failure, the file descriptor table index on success.

The parameters are:

pathName is either an:


absolute path: if it begins with a /
relative to current working directory: otherwise
accessMode contains one of the following:
O_RDONLY: open the file with read only access
O_WRONLY: open the file with write only access
O_RDWR: open the file with both read and write access.
In addition, the following options may be ored with the above:
O_APPEND: Append data to the end of the file. (regular file only)
O_CREAT: Create the file if it does not exist. (regular file only)
O_EXCL: Used only w/O_CREAT, to specify that the open fails if the file already exists. (regular
file only)
O_TRUNC: If the file exists, delete its contents setting file size to zero. (regular file only)
O_NONBLOCK: Any subsequent read or write on the file is non-blocking. (FIFO and device files
only)
O_NOCTTY: Specifies that the named terminal device file is not to be used as the calling process
control terminal. (terminal device files only)
permission: used only if the file is created to set the owner/group/other file permissions, otherwise
ignored. Defined in <sys/stat.h>. The actual file permissions set are permission - umask.

Creat
Create a new file and open it using first unused file descriptor.
#include <sys/types.h>
#include <fcnt.h>

int creat(const char *pathName, mode_t permission);

This is equivalent to:


open(pathname, O_WRONLY|O_CREAT|O_TRUNC, permissions);

Dup
int dup(int fd)

Finds the first unused file descriptor and copies the file descriptor at fd to it.

Example of replacing stdin with "/tmp/x": close(0); fd = open("/tmp/x", O_RDONLY); dup(fd); close(fd);

Close
Frees the file descriptor in the process.
#include <unistd.h>

int close(int fdesc)

returns -1 on failure, 0 on success.

Controlling open files


Read

Read a specified number of bytes into a buffer, given a file descriptor.


#include <sys/types.h>
#include <unistd.h>

ssize_t read(int fdesc, void *buff, size_t size)

returns number of bytes read on success, -1 on failure. The number of bytes read can be less than that requested
on end-of-file. arguments:

fdesc: an open file descriptor


buff: buffer of at least size bytes
size: number of bytes to be read.

Can be interupted by a signal.

Write
Write size bytes from buff to file specified by fdesc.
#include <sys/types.h>
#include <unistd.h>

ssize_t write(int fdesc, const void *buff, size_t size)

returns -1 on failure, number of bytes written on success.

fsync
#include

int fsync(int fildes);

The fsync() function moves all modified data and attributes of the file descriptor fildes to a storage device. When
fsync() returns, all in-memory modified copies of buffers associated with fildes have been written to the physical
medium.

This call is useful since write to block devices (such as file systems) are buffered and not written for up to 30
seconds typically. In cases where the completion or ordering of writes is important, fsync's must be performed.

Lseek

Change the position in the file.


#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fdesc, off_t pos, int whence);

returns -1 on failure, number of bytes written on success.

Whence specifies what pos is relative to

SEEK_CURR: Current file pointer address


SEEK_SET: the beggining of the file
SEEK_END: the end of the file

if lseek seeks to a position beyond the end of the file, and the file has been open for write then the file is
extended with missing blocks (which are given default value of 0). Hence, a file may have a megabyte size, but
not consume a megabyte of storage. (If file was open read-only, the operation fails)

fcntl
#include <fcntl.h>

int fcntl(int fdesc, int cmd, ...);

G_GETFL: returns the access control flags of the file descriptor fdesc
G_SETFL: Sets the O_NONBLOCK and O_APPEND to the values specified in the third argument of
fcntl.
G_GETFD: returns the close-on-exec flag (0 for false, non-zero for true).
G_SETFD: third argument is 0 to clear and 1 to set the close-on-exec flag.
G_DUPFD: duplicates the file descriptor in the first unused file descriptor which is greater than or equal
to the third paramenter. Returns the duplicated file descriptor.
Miscellaneous
Chown

Change the owner or group of the file


#include <unistd.h>

int fchown(int fdesc, uid_t uid, gid_t gid);


int chown(const char *pathName, uid_t uid, gid_t gid);
int lchown(const char *pathName, uid_t uid, gid_t gid);

fchown works on the file descriptor, while chown and lchown work on path. The difference between chown and
lchown is that if the pathName specifies a symbolic link, lchown changes the symbolic link's UID and GID,
while chown changes the referenced files UID and GID.

If _POSIX_CHOWN_RESTRICTED is

defined: then if superuser can change any uid,gid otherwise if eUID=fileUID and gid is either an effective
or supplemental group id, then the gid can be changed.
undefined: if eUID=fileUID or eUID=0, then we can change the fileUID and fileGID. If not super user,
changing fileUID (fileGID) will clear the set-UID (set-GID) bit. (implementation dependent if superuser).

The above restrictions are to prevent security holes.

If uid (gid) is equal to -1, then uid (gid) is unchanged.

Chmod

Change owner, group, other permissions, set-UID, set-GID, and sticky bit. The caller must be the owner of the
file or super user.
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int fchmod(int fdesc, mode_t flag);


int chmod(const char *pathName, mode_t flag);

Directory
Link

Adds another directory entry which points to the file's inode.


#include <unistd.h>

int link(const char *currPathName, const char *newPathName);

returns -1 on failure, 0 on success. Arguments

currPathName is a absolute or relative pathname of a file


newPathName is an absolute or relative pathname

In unix, both pathnames must be in the same partition.


Unlink

Removes a directory entry.


#include <unistd.h>

int unlink(const char *pathName);

returns -1 on failure, 0 on success.

Rename

Removes a file name from one directory and adds it to another.


#include <unistd.h>

int rename(const char *currPathName, const char *newPathName);

returns -1 on failure, 0 on success.

Mkdir

Create a new empty directory.


#include <sys/stat.h>
#include <unistd.h>

int mkdir(const char *pathName, mode_t mode);

returns 0 on success, -1 on failure.

The pathName specifies the directory to be created, the mode less the umask is used to set the file access
permissions.

Rmdir

Remove an empty directory


#include <sys/stat.h>
#include <unistd.h>

int rmdir(const char *pathName);

returns 0 on success, -1 on failure.

The pathName specifies the directory to be created, the mode less the umask is used to set the file access
permissions.

Traversing the directory


Read-only access to the directory can be provided by the following calls:
#include <sys/types.h>
#include <dirent.h>

typedef struct dirent Dirent;

DIR *opendir(const char *pathName); // open the directory for read & point to first entry
Dirent *readir(DIR *dirFdesc); // get the next directory entry in the file
int closedir(DIR* dirFdesc); // close the directory file
void rewinddir(DIR *dirFdesc); // point DIR at the first entry in the directory

Fifo Files (named pipes)


#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int mkfifo(const char *pathName, mode_t mode);

Symbolic Links
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int symlink(const char *existingPathName, const char *symLinkPath);


int readlink(const char *existingPathName, char *symLinkPath, int size);
int lstat(const char *existingPathName, struct stat *StatPtr);

File locking
Unix is highly oriented towards shared access of files. File locking can either be mandatory (enforced by the
kernel on all accesses) or advisory (correct locking requires processes to use a given locking sequence.

POSIX supports only advisory locks. To use advisory locks all accesses to possibly locked files follow the
following sequence.

set a lock on the file region desired


access the locked region
release the lock

Fcntl for file locking


#include <fcntl.h>

int fcntl(int fdesc, int cmd, ...);

where cmd is one of:

F_SETLK: set file lock, but don't block if cannot succeed immediatly.
F_SETLKW: set file lock, blocking if cannot succeed immediatly
F_GETLK: finds out what process has locked the given file.

the third argument is a pointer to the flock structure:


struct flock {
short l_type; /* lock type */
short l_whence; /* relative to where */
off_t l_start; /* starting offset relative to whence */
off_t l_len; /* length of locked region */
pid_t l_pid; /* PID of a process which has locked the file */
};

where l_type is one of


F_RDLCK: sets a read (shared) lock
F_WRLCK: sets a write (exclusive) lock
F_UNLCK: unlocks a specified region

where l_len:

>0: size of the locked region in bytes


=0: lock the entire rest of the file, even if it grows.
Interprocessor Communication
We have already discuss one very limited form of communications, called signals. Signals are used primarily to
control groups of processes.

System calls are atomic in Unix. That is either all of the effects of a system call are visible to other processes or
none of them are. Hence, basic synchronization is performed using system calls.

There are two types of system calls; fast and slow. Fast system calls can be performed without any waiting; an
example is reading the processes PID. Slow system calls may require waiting; for example, many file system
syscalls (including all that use a pathname) may have one or more waits as they traverse directories and inodes.

A slow system call can be suspended, but only at well defined points which are explicitly coded into the kernel.
Hence, the filesystem path accessing syscall may suspend and wake up after each inode or directory read. If the
system call is suspended (as opposed to completed) the process will be blocked.

There are two ways to make slow system calls into fast (non-blocking) calls:

Non-blocking: these calls fail rather than block.


Asynchronous: these calls provide fast returns, even if they don't complete. The process (user space)
continues executing after the system call returns. The process must later check whether the call completed,
via system call or with a signal.

We now begin discussion of other forms of communications between processes. This communication includes:

Files: most basic form of communication (And the only one that survives system crashes).
Pipes: specialized file "streams"
POSIX 1.b (Formerly POSIX 4)
Message: atomic variable length communications
Shared Memory: multiple processes sharing the same physical memory
Semaphores: memory-based synchronization
Unix History
IPC Uses
Domain Name System (DNS)
The name scheme of the internet is heirarchical. A name such as [Link] shows for level of the
heirarchy:

1. edu: is the top level domain, and contains all universities


2. uic: is a subdomain of edu
3. eecs: is a subdomain of uic
4. bert: is a hostname, not a subdomain

This scheme allows universities to determine and administer third level names (which must be unique at uic) and
hostnames can be chosen and administered by the units.

Names

Can be either absolute or relative. Relative name end with a period (and must be interpreted in some
context) absolute names are fully qualified.
Domain Names are case insensitive, components are up to 63 characters long, and full name is less than
255 characters.
Administration of subdomains or hosts is determined by the (sub)domain which contains the entity.

Name servers
Domains are divided into non-overlapping zones

Example: [Link] wants to communicate with [Link].

it requests its local domain name server [Link] to look up [Link].


if the server does not know [Link], it requests [Link].
[Link] knows where [Link] is.
[Link] knows where [Link] is.
[Link] knows where [Link] is.

Address Resolution Protocol (ARP)


To find the data link address (typically ethernet) for a given IP number, a hosts broadcasts "who is IP address
x.y.z.w?" and encloses its return address. Host x.y.z.w responds with its ethernet address which can be used to
transmit the data.
Network Layers
Networks are divided into layers:

Network layer: Adds routing beyond the local network. (packet)


Data link layer: takes a raw transmission layer and converts it into a form that is free from undetected
errors. Data link works on the "local network". (frame)
Physical layer: includes electrical characteristics, connector characteristics, length of wires. (bit)

We first describe the Physical and Link layers, and then show how they are connected into the network layer.

Physical and Link layer


We describe the base technology with a joint description of the Physical and Data link levels. We are primarily
interested in ethernet, since that is the dominant technology, on the LAN.

Ethernet
Ethernet is a shared, single logical wire system:

Each wire segment is limited to 250m


Up to 4 segments allowed (using 3 repeaters) yielding a 1km maximum on an ethernet.
Vampire taps (which puncture the cable) can be made in the wire at 2 meter intervals

The ethernet protocol needs to ensure one adapter gets exclusive use of the ethernet. Protocol is as follows:

An adapter desiring to transmit listens to the ethernet and waits until no one else is transmitting
The adapter simultaneously transmits and listens to the ethernet. If there is no conflict, the transmission is
successful and the protocol ends.
if there is a conflict, the adapter waits a random amount of time, and then starts at the beginning.

The wait period doubles with every unsuccessful transmission (up to some maximum), increasing by a factor of
2 the probability of a successful transmission. (This is called binary exponential backoff)

Ethernet is one of a family of protocols based on CSMA/CD, which stands for Carrier Sense Multiple
Access/Collision Detection. The first CSMA/CD network was AloahNet, a wireless network in Hawaii which
was designed to communicate between the Hawaiian islands.

The works well when traffic is light---in which case there is no waiting--- and when traffic is heavy, the protocol
rapidly converges and hence the permission to broadcast does not use too much bandwidth (logarithmic in the
number of transmitters).

Packet format:

Preamble: 7 bytes (10101010)


Start of frame: 1 byte (10101011)
Destination Address: 2 or 6 bytes
Source Address: 2 or 6 bytes
Length of data: 2 bytes
Data: 0--1500 bytes
Padding: 0-46 bytes
Checksum: 4 bytes
Notes:

Addresses which start with a 1 are group addresses (which can have multiple recipients) and all 1s is a
broadcast which goes to every ethernet address on the network.
10MBit ethernet requires 6 byte addresses.
Minimum packet size is 64 bytes, to ensure detection of conflicts everywhere (This is called 512 bit
times).

Vampire taps are not as reliable as connectors, since it is possible to break a cable, and also the tap if missplaced
can cause problems. Hence, most ethernet installations usedn hubs, which allow all the computers in an area to
be connected to the hub, and then the hub is connected to the backbone (by a vampire tap).

Cabling has also change from coaxial cabling to telephone style using modular connectors (RJ-45). The
connectors are slightly larger than telephone connectors (RJ-11) since RJ-45 has 8 wires vs. RJ-11's 4.

Ethernet Switching
As computers became faster, the 10Mb speed of ethernet (1.25 MByte) became a bottleneck. Moreover, the
ethernet segment is shared so that is the total bandwidth.

To increase the bandwidth available per processor, the industry invented ethernet switches. Switches look like
hubs: the difference is that computers connected up do not share the switches bandwidth. Switches are
physically different ethernets but logically the same network; one consequence is that wire lenght segments on
switch is for each port (in hubs they sum).

In other words, if 4 computers, A,B,C, and D, are connected to a switch, then A can communicate with B
simultaneously with C communicating with D. Nevertheless, the ethernet acts logically as a single ethernet.

An N port switch can increase bandwidth up N/2 times an N port hub.

Fast Ethernet
Ethernet runs at 10Mbit per second, and is rapidly being replace by fast ethernet 100Mb/s. The Fast ethernet
standard, 802.3u, is actually an addendum to the base ethernet standard. So everything was designed to be as
little changed as possible from the original standard.

Because the length of the ethernet packet is tied to maximum ethernet size, increasing the speed by a factor of
10, means decreasing the maximum length to 100m. This cabling is usually either:

100 Base-TX -- Unshielded Twisted Pair Category 5 cable (2 pair)


100 Base-T4 -- Unshielded Twisted Pair Category 3 cable (4 pair)

Category 3 has inferior performance to Category 5 (need twice as much of it), but is common telephone wire, so
it already exist in many buildings. Unless there is some reason why this cable must be used, Category 5 is
preferred.

Of course, buildings are not going to shrink as new ethernet technology is introduced, so there must be a way to
connect together ethernets. The solution is fiber optic (100 Base-FX) for lengths of up to 2000m of multimode
fiber (dual strand).

Fast ethernet also allows full duplex operation, but only in non-shared media environments such as a switch. Full
duplex is supported in 100 Base-TX and 100 Base-Fx. Full duplex enables distance requirements to be based
only on signal strength, not timing considerations since there can be no conflict on a full duplex link.
Other networking
Ethernet is the dominant local area networking technology, and is likely to remain so. In addition to fast ethernet,
there is Gigabit ethernet (1000Mbit/second). As performance increases, there will be an increasing emphasis on
point-to-point designs.

Other networking technologies, such as ATM and FDDI are fiber optic and switch based.
Network Programming
Sockets
Sockets are a protocol independent method of creating a connection between processes. Sockets can be either

connection based or connectionless: Is a connection established before communication or does each


packet describe the destination.
packet based or streams based: Are there message boundaries is it one stream.
reliable or unreliable. Can messages be lost, duplicated, reordered, or corrupted?

Socket are characterized by their domain, type and transport protocol. Common domains are:

AF_UNIX: address format is UNIX pathname


AF_INET: address format is host and port number

Common types are:

virtual circuit: received in order transmitted and reliably


datagram: arbitrary order, unreliable

Each socket has one or more protocols. Ex:

TCP/IP (virtual circuits)


UDP (datagram)

Connection--based sockets communicate client-server: the server waits for a connection from the client
Connectionless sockets are peer-to-peer: each process is symmetric.

socket: creates a socket of a given domain, type, protocol (buy a phone)


bind: assigns a name to the socket (get a telephone number)
listen: specifies the number of pending client messages that can be queued for a server socket. (call
waiting allowance)
accept: server accepts a connection request from a client (answer phone)
connect: client requests a connection request to a server (call)
send, sendto: write to connection
recv, recvfrom: read from connection
shutdown: end the call

Connection based communication

Server performs the following actions

socket: create the socket


bind: give the address of the socket on the server
listen: specifies the maximum number of connection requests that can be pending for this process
accept: establish the connection with a specific client
send,recv: stream-based equivalents of read and write (repeated)
shutdown: end reading or writing
close: release kernel data stuctures

Client performs the following actions


socket: create the socket
connect: connect to a server
send,recv: (repeated)
shutdown
close

Connectionless communication
Communication is symmetric (peer-to-peer)

socket
bind
sendto, recvfrom (repeated)
shutdown
close

Sockets API

socket

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol)

Returns a file descriptor (called a socket ID) if successful, -1 otherwise.

The type arguments are

SOCK_STREAM: Established a virtual circuit for stream


SOCK_DGRAM: Establishes a datagram for communication
SOCK_SEQPACKET: Establishes a reliable, connection based, two way communication with maximum
message size. (This is not available on most machines.)

Protocol is usually zero, so the.

Note that the socket returns a socket descriptor which is the same as a file descriptor (-1 if failure).

bind

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sid, struct sockaddr *addrPtr, int len)

Where

sid: is the socket id


addrPtr: is a pointer to the address family dependent address structure
len: is the size of *addrPtr

For the internet family:


struct sockaddr_in {
short sin_family; // = AF_INET
u_short sin_port; // is a port number
struct in_addr sin_addr; // an IP address
}

For unix sockets (only works between processes on the same machine)
struct sockaddr {
short sin_family; // = AF_UNIX
char sin_port[]; // is a port number
}

listen

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sid, int size)

Where size it the number of pending connection requests allowed (typically limited by Unix kernels to 5).

accept

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sid, struct sockaddr *addrPtr, int *lenPtr)

Returns the address of client connecting to socket

if lenPtr or addrPtr equal zero, no address structure is returned

lenPtr is the maximum size of address structure that can be called, returns the actual value.

send

#include <sys/types.h>
#include <sys/socket.h>

int send(int sid, const char *bufferPtr, int len, int flags)

flag is either

0: default
MSG_OOB: Out-of-band high priority communication

recv

#include <sys/types.h>
#include <sys/socket.h>

int recv(int sid, char *bufferPtr, int len, int flags)


flags can be either

0: default
MSG_OOB: out-of-bound message
MSG_PEEK: look at message without removing

shutdown

#include <sys/types.h>
#include <sys/socket.h>

int shutdown(int sid, int how)

Disables sending (how=1 or how=2) or receiving (how=0 or how=2).

Client calls

connect

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sid, struct sockaddr *addrPtr, int len)

Connectionless protocols

sendto

#include <sys/types.h>
#include <sys/socket.h>

int sendto(int sid, const char *bufferPtr, int len, int flag,
struct sockaddr *addrPtr, int len)

recvfrom

#include <sys/types.h>
#include <sys/socket.h>

int recvfrom(int sid, const char *bufferPtr, int len, int flag,
struct sockaddr *addrPtr, int *lenPtr)

Auxiliary functions

Gethostbyname

#include <netdb.h>

struct hostent *gethostbyname(const char *hostname)

Translates a DNS name into a hostent.


The hostent stucture is as follows:
struct hostent {
char *h_name; // official (canonical) name of the host
char **h_aliases; // null terminated array of alternative hostnames
int h_addrtype; // host address type AF_INET or AF_INET6
int h_length; // 4 or 16 bytes
char **h_addr_list;// IPv4 or IPv6 list of addresses
}

Error is return through h_error which can be:

HOST_NOT_FOUND
TRY_AGAIN
NO_RECOVERY
NO_DATA

Gethostname

#include <unistd.h>

int gethostname(char *hostname, size_t nameLength)

Returns -1 on failure, 0 on success.

MAXHOSTNAMELEN is defined in <sys/param.h>.

Network byte ordering

Network ordering in big endian. (Sparc is big endian, Intel is little endian).

htons

Host to network byte order for shorts (16 bit)


uint_16t htons(uint_16t v);

htonl

Host to network byte order for long (32 bit)


uint_32t htonl(uint_32t v);

ntohs

Network to host byte order for long (16 bit)


uint_16t ntohs(uint_16t v);

ntohl

Network to host byte order for long (32 bit)


uint_32t ntohl(uint_32t v);

IP address strings to 32 bit number


In what follows, 'p' stands for presentation.

inet_pton

#include <arpa/inet.h>

int inet_pton(int family, const char *strPtr, void *addrPtr);

returns 1 if OK, 0 if presentation error, -1 error

Where family is either AF_INET or AF_INET6.

The strPtr is the ip address as a dotted string.

Finally, addrPtr points to either the 32 bit result (AF_INET) or 128 bit result (AF_INET6).

inet_ntop

#include <arpa/inet.h>

int inet_ntop(int family, const char *addrPtr, char *strPtr, size_t len);

returns 1 if OK, 0 if presentation error, -1 error

Where family is either AF_INET or AF_INET6.

The strPtr is the return ip address as a dotted string.

Finally, addrPtr points to either the 32 bit (AF_INET) or 128 bit (AF_INET6).

Length is the size of destination.

Example

TCP/IP Server

Without error checking.


int listenFd, connectFd;
struct sockaddr_in serverAddr;

listenFd = socket(AF_INET, SOCK_STREAM, 0); // get a tcp/ip socket

bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); // any iternet interface
// on this server.
serverAddr.sin_port = htons(13);

bind(listenFd, (struct sockaddr_in *) &serverAddr, sizeof(serverAddr));


listen(listenFd, 5);

for ( ; ; ) {
connectFd = accept(listenFd, (struct sockaddr_in *) NULL, NULL);
// .. read and write operations on connectFd ..
shutdown(connectFd, 2);
close(connectFd);
}

Note that the above is an iterative server, which means that it serves one connection at a time.

To build a concurrent server, a fork is performed after the accept. The child process closes listenFd, and
communicates using connectFd. The parent process closses connectFd, and then loops back to the accept to wait
for another connection request.

TCP/IP Client code

int sockFd;
struct sockaddr_in serverAddr;

sockFd = socket(AF_INET, SOCK_STREAM, 0); // get a tcp/ip socket

bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
inet_pton(AF_INET, serverName, serverAddr.sin_addr); // host IP #
serverAddr.sin_port = htons(13);

connect(sockFd, (struct sockaddr_in *) serverAddr, sizeof(serverAddr));


// .. read and write operations on sockFd ..
shutdown(sockFd, 2);
close(sockFd);
TCP/IP
TCP/IP provides a streams, connection-oriented abstraction. It must therefore be reliable in the presence of
"network problems" which do not completely disrupt communications. Network problems which cannot be
masked include a downed host or partitioned network.

In addition, TCP/IP performs congestion control. Here, the problem is two fold:

If the network fills up, a router can just throw away packets.
If a router fills up, it cannot receive any packets.

Reliability
In order to be reliable, we need to model the types of failures that can occur. In IP these are:

Lost packets
duplicated messages
arbitrarily delayed packets
packets received out of order
corrupted packets

Corrupted packets

Lets consider the corrupted packets issue first. Assume that the corrupted packets occur as the result of randomly
occuring failures. Than we can extend each packet with a checksum, such that random errors can be detecting.
(The checksum, in terms depend upon modeling the way that corruption can occur, but that is coding theory).

If on the other hand, packets could be systematically corrupted by an advesary, then cryptographic techniques
would be required to recognize whether packets come from a known source.

Once a corrupted packet is found, it can be thrown away, that is, treated as a lost packet.

Ensuring packets arrive, and are in sequence

The primary technique is to include in each packet a serial number, so that the ith packet from C1 to C2 has
serial number i. (Packets sent in the other direction, C2 to C1 are seperately numbered.)

The recipient initiates a counter at 0. If a message arrives which is equal to the counter, the recipient sends an
ACK packet with the counter, and then increments the counter. If a message arrives whose serial number is less
than the counter it is thrown away (it is a duplicate). If it is greater than the counter NACKs (negative
acknowledgements) are sent for the packets greater than the counter through the serial number of the arriving
packet, and the arriving packet is thrown away.

The ACKs/NACKs can be lost as well. Hence the sender sets a timer, by which it expects an ACK. If the ACK
does not arrive by the timeout, the packet is resent. This also protects against the last message in a session being
lost, which would not be NAKed.

Optimizations

Acknowledge groups of messages instead of individual messages. Works best if the probabilty of message lost is
low. If its high, that individual messages should be acknowledged.
Send many messages before waiting for an acknowledgement enables toleration of long latencies (eg. satelight
links), but is slower to detect errors.
Remote Procedure Call
On this page, we consider the general issue of remote procedure call. An RPC is a procedure call across
processes, whether they are located on the same or different hosts. (Sometimes the term local RPC is used to
indicate an RPC within a hosts).

RPCs have several advantages over sockets-level programming:

Procedure calls are a convenient abstraction for the request of remote services.
RPCs are type safe, and transparently manage host differences in datatypes and layouts
RPC seperates the interface from the implementation, yielding more readable designs.

Type safety
Computer architecture-Operating Systems differ from one another in several aspects of datatype representation.
These include:

alignment
endianess
floating point format

The first two issues are because there are multibyte primitive data types (such as 4-byte integers), yet memory is
byte addressible. The last issue is because there still exists some legacy floating point formats, although all
microprocessor originated designs are based on IEEE floating point format.

Alignment

High performance computers require multibyte primitive to be aligned. That is, a k byte primitive must have a
starting address which is divisible by k in an aligned architecture.

For example, the structure:


struct {
char a;
int i;
}

in an aligned architecture requires 8 bytes (a is followed by three bytes of padding), since newly allocated
storage is always at the highest alignment of the architecture: in an unaligned archtecture it would require 5
bytes.

A more subtle example is:

struct {
char a;
int i;
char b;
}

At first glance, this might seem to require only 9 bytes in an aligned archecture (data elements cannot be
reordered in C). However, this requires 12 bytes (three bytes of padding at the end) since the size of a array
element is the same as the size of an object. (Note that an array of characters needs no internal padding since the
requirement is only that the size of an object must be a multiple of its largest primitive object).
Endianess

Architectures can either be:

big endian: most significant byte at lowest address


little endian: least signifcant byte at lowest address.

Network standard traffic is big endian. (htonl and htons do this endian conversion, if necessary).

Floating point

Before IEEE floating point standard in the 1980's each computer manufacturer designed its own floating point
system. Of those, perhaps the two most important are Cray and IBM.

Between IEEE floating point, issues of byte order are still important but the size and meaning of mantissa and
exponent have been standardized.

ONC RPC
RPC Interface Definition Language (IDL)

definition-list := definition ;|
definition ; definition-list
definition := const-definition |
enum-definition |
struct-definition |
union-definition |
typedef-definition |
program-definition
const-definition := const ident = integer
enum-definition := enum ident { enum-value-list }
enum-value-list := enum-value |
enum-value , enum-value-list
enum-value := ident = value |
ident
struct-definition := struct ident { declaration-list }
declaration-list := declaration ; |
declaration ; declaration-list
union-definition := union ident switch ( declaration ) { case-list }
case-list := case value : declaration ; case-list |
case value : declaration ; |
default : declaration ;
typedef-definition := typedef declaration
program-definition := program ident { version-list } = program-number
version-list := version-decl ;|
version-decl ; version-list
version-decl := version ident { procedure-list } = version-number
procedure-list := procedure ; |
procedure ; procedure-list
procedure := type ident ( type ) = procedure-number
declaration := simple-declaration |
fixed-array-declaration |
variable-array-declaration |
pointer-declaration
simple-declaration := type ident
fixed-array-declaration := type ident [ integer ]
variable-array-declaration := type ident < integer > |
type ident < >
pointer-declaration := type * ident

Notes:
union-definition is a descriminated union, meaning that there is a tag that gives the type
program-definition describes just the interfaces, but none of the code.
union-declaration must specify a value associated with each type.
program-number is assigned as follows:
0x00000000 - 0x1fffffff defined by SUN
0x20000000 - 0x3fffffff defined by user
0x40000000 - 0x5fffffff transient
0x60000000 - 0xffffffff reserved
Use a program number in the defined by user range (adding in last 4 digits of your social security
number).
ONC-RPC allows only a single parameter and a single result (these can each be void).
The procedures defined by the IDL are obtained by converting the procedure to lower case and appending
"_" and the version number. This allows an RPC definition to support multiple version of procedure calls.
procedure-number should start from 1 (0 is reserved for the ping (or null) procedure which can be used to
test if the server is alive).
variable-array-declaration specified either a maximum size (if there is an integer between angle brackets)
or does not specify a maximum size.
pointer-declaration pointers across processes are not very useful. Pointer types are used to copy over
sparse structures such as linked lists from server to client.

rpcgen

The RPC IDL (also called RPCL) needs to be compiled into C so that it can be used in a program. Assume the
IDL is stored inprog.x. Then:
rpcgen prog.x

Running rpcgen creates three files:

prog.h: which contains common information for the server and client.
prog_svc.c: C code to be linked with the server.
prog_cli.c: C code to be linked with the client.

Compiling on solaris
Then you can compile as follows:
gcc client.c prog_cli.c -o client -lnsl
gcc server.c prog_svc.c -o server -lnsl

client.c and server.c should include:


#include < rpc/rpc.h >
#include < netconfig.h >
#include "prog.h"

Calling remote procedures


Remote procedure proc, version v is called with the name proc_v. The parameters and the return value for the
procedure each have an exra level of indirection. For example:

procedure p
version 2
declared as int p(char)

The procedure is a bit different on the client and server side:

On the client side it would be written in C as: int *p_2(char *, CLIENT *)

where the CLIENT * argument specifies the target of the RPC.


On the server side it would be written in C as: int *p_2(char *)

In addtion, the value returned (if declared within the procedure) must be declared static.

RPC data types in C program

Most data types are directly mappable into C. The exceptions include:

Variable size arrays


Unions
Pointers

Pointers cannot be easily used across process boundaries, so the primary reason for using pointers is to copy
over sparse structures (such as linked lists or trees), between processes. ONC-RPC provides for pointer-chasing
to do this automatically, but this is beyond the scope of the notes. Both of the remaining types, Variable size
arrays and Uninos, are described by structures.

Variable size arrays

Consider the variable size array vararray whose components are of type int.
typedef int vararray<100>;

generates the structure:


typedef struct {
u_int vararray_len;
int *vararray_val;
} vararray;
Where the type vararray is a structure in C with two components, a length field vararray_len and a pointer to the
arrayvararray_val which is of course type int.

Unions

Consider the RPC specification:


union retval switch (int desc) {
case 0: headline hl;
default: void;
};

This gets compiled into the following C structure.


struct retval {
int desc;
union {
headline hl;
} retval_u;
};

To use this structure, the C programmer must set both the descriminator (desc) to 0 (or something outside the
range of defined values), and the union component to the appropriate value.

Using RPC

You need to write client.c and server.c code.

client

The client will need initialize the RPC


CLIENT *cl;

if (( cl = clnt_create(server, PROG_NUMBER, PROG_VERS, "tcp")) == NULL) {


/* error, could not open server */
}

and to close down the connection:


clnt_destroy(cl);

server

The server does not have a main (this is supplied by the stub). It need only implement the remote procedures.
Recommended Texts
Unix Network Programming, W. Richard Stevens, Prentice Hall. (The bible on network programming)

The Design of the Unix Operating System, Maurice Bach, Prentice Hall. (Dated description of Unix
internals, but very readable)

POSIX.4:Programming for the Real World, Bill O. Gallmeister, O'Reilly and Associates. (The book on
real-time POSIX programming covering IPC, async. I/O, and more).

Computer Networks (3rd Edition), Andrew S. Tannenbaum, Prentice Hall. (Detailed coverage of low level
network stuff)

Computer Networks: A Systems Approach Larry L. Peterson and Bruce S. Davies, Morgan Kaufman.
(More advanced coverage then Tannenbaum)

The Magic Garden Explained, Benny Goodheart and James Cox, Prentice Hall. (Describes basis for
Solaris [SrV5R4] internals).

STL Tutorial and Reference Guide David R. Musser and Atul Saini, Addison-Wesley. #

Power Programming with RPC # John Bloomer, # O'Reilly and Associates. # (ONC RPC in depth)
Assignments
Assignment 1
Write up
Sample solution
Assignment 2
Write up
Assignment 3
Write up
Sample socket progams
Assignment 4
Write up
Assignment 1

For this assignment, we will use signals to solve the problem of cascading termination. This problem is when a
parent process terminates, the child processes are allowed to continue to execute. This can result in orphan and
zombie processes being left on the system.

When a process terminates, it is to first force the termination of all of its child processes (to prevent any
orphaned or zombie processes left on the system) and then terminate itself. It should force the termination of its
children by sending the SIGTERM signal to all of its children. The safest method to do this is to use the atexit()
function. The atexit() function takes as its parameter a pointer to a function. This function will be called as the
program terminates. This function must of type void and have no parameters.

We will want to run our program in background mode executing an infinite pause() loop. This will allow us to
interact with each process by using the command line kill(1) operation to send various signals to each process.
We will be sending four different signal: SIGHUP, SIGUSR1, SIGUSR2 and SIGTERM.

When a process receives the SIGHUP signal, it is to create a new child process. As each process is created, the
program must print a message stating the process ID's of the child and the parent.

When a process receives the SIGUSR1 signal, it is to print out the process ID's of all child processes that are
currently running.

When a process receives the SIGUSR2 signal, it is to (again) print out the process ID's of all child processes that
are currently running and send the SIGUSR2 signal to all of these processes. This will cause the process ID's of
all descendants of the original process to be printed. Since we are printing the processes descendent tree, we
should attempt to print these values in some "tree-like" manner.

When a process receives the SIGTERM signal, it is to first force the termination of all of its children and then
terminate itself. The use of the atexit() function may be helpful with this in case the process terminates normally.

As each child process is created, its process ID must be remembered. This should be done by writing a special
creation function that performs the fork() command and maintains a list of child process ID's. This function
should add the necessary information to the list for the parent process, while creating an empty list to hold any
future children that may be created by the current child process. Remember the child will get a copy of the
parent's list, so it may need to remove the parent's information from its list.

When the child process terminates, its process ID must be removed from its parent's list. The SIGCHLD signal
should be caught so it can perform the necessary wait() operation and then remove the proper information from
the list.

To aid with the creation of the processes, we will use an optional command line argument. If the user enters the
value of "1" as an argument to the command to execute the program, the original process is to create four (4)
child processes and three (3) "grandchild" processes. Two of the "grandchild" processes will be the child
processes of the original process's first child process. The third "grandchild" process will be the child process of
the original process's second child process. The easiest way to create these additional processes will be to have
the original process send the SIGHUP signal to itself four times, then send the SIGHUP signal to its first child
twice and finally send the SIGHUP signal to its second child once. If no command line argument is given (or a
command line argument other than "1"), all child processes will be created explicitly by the user.

Remember to use the UNIX ps command to make sure you do not leave any extra processes on the system.
The assignment is expected to be the result of individual work. You will submit the project electronically using
the UNIX "turnin" command. Refer to the man page on the system for information about the command.

All of your programs must be written in a good programming style. This is to include in-line comments, a file
header, function headers, blank lines, indentation, meaningful variable names, readable output, etc. The first two
line of your file must comments stating how you compiled your program on the EECS department's UNIX
machines and how you ran your program.
Assignment 2
Due Date: Wednesday, October 20, 1999, at 11:59 pm
Entended to: Monday, October 25, 1999, at 11:59 pm

For this assignment, you are to create a two player-two process tic-tac-toe game using IPC message
queues for communication between the two players. The input and output of the program is to be text
based.

Since there are nine positions on the tic-tac-toe board, a move by a player is indicated by entering the
value from 1 through 9 as follows:

1 2 3

4 5 6

7 8 9

The object of the game is to get three places in a row either horizontally, vertically or diagonally.
Traditionally, the first player uses an X to mark their moves and the second player uses an O to mark
their moves. For the first round, the player that creates the IPC message queue goes first. After each
game, both players are asked if they want to play again. If both players respond "yes", another game is
played. After the first game, which ever player lost the previous game goes first. If the previous game
ended in a tie (sometimes called "cats"), the player that went second in the previous game goes first in
the current game.

For a players turn, they enter in the values from 1-9 to indicate a move. If the position is already taken,
give an error message and allow the player to make another choice. If the player's move gets three in a
row, the program should announce the winner immediately. The ninth move of a game should be
made automatically (since there is only one position where the move could go). During the player's
turn, the player could enter in a "h" or "?" to get some help on the game. This help should list basics of
the game and the table that shows the correspondence between the playing board positions and the
values 1 through 9. During the player's turn, the player may concede the game (admit they have lost)
by entering a "c". During the player's turn, the player may quit the game and the program by entering
a "q". If this happens, your program is to prompt to see if the player really wants to quit.

While a player is waiting for the other player to take their turn, the program should print a message
such as:

Waiting for the other player's turn......

where a new period should get printed at some regular interval (i.e. every 5 seconds). You will most
likely need to use signals for allow for this.

Your program is to be written in good programming style and should attempt to take advantage of
C++ classes and the C++ Standard Template Library. You will find a number of good links from
the EECS 471 home page.
Assignment 3
Due Date: Wednesday, November 17, 1999, at 11:59 pm

For this assignment, you are to create a two player-two process tic-tac-toe game using sockets for
communication between the two players.
You are to write two programs, one for the server (with the executable file named ttts) and one for the
client (with the executable file named tttc). The client is to take an optional command line argument of
a machine name that specifies the machine the server is running on. If no machine name is give
assume the server is running on the same machine as the client. The communication between the two
processes is to use the client's socket.

The input and output of the program is to be text based. Since there are nine positions on the tic-tac-
toe board, a move by a player is indicated by entering the value from 1 through 9 as follows:

1 2 3

4 5 6

7 8 9

The object of the game is to get three places in a row either horizontally, vertically or diagonally.
Traditionally, the first player uses an X to mark their moves and the second player uses an O to mark
their moves. For the first round, the server goes first. After each game, both players are asked if they
want to play again. If both players respond "yes", another game is played. After the first game, which
ever player lost the previous game goes first. If the previous game ended in a tie (sometimes called
"cats"), the player that went second in the previous game goes first in the current game.

For a players turn, they enter in the values from 1-9 to indicate a move. If the position is already taken,
give an error message and allow the player to make another choice. If the player's move gets three in a
row, the program should announce the winner immediately. The ninth move of a game should be
made automatically (since there is only one position where the move could go). During the player's
turn, the player could enter in a "h" or "?" to get some help on the game. This help should list basics of
the game and the table that shows the correspondence between the playing board positions and the
values 1 through 9. During the player's turn, the player may concede the game (admit they have lost)
by entering a "c". During the player's turn, the player may quit the game and the program by entering
a "q". If this happens, your program is to prompt to see if the player really wants to quit.

While a player is waiting for the other player to take their turn, the program should print a message
such as:

Waiting for the other player's turn......

Where a new period gets printed at some regular interval (i.e. every 5 seconds). You will most likely
need to use signals to allow for this.
Your program is to be written in good programming style and should attempt to take advantage of
C++ classes and the C++ Standard Template Library.
You will find a number of good links from the EECS 471 home page
([Link]
MP 4 - Network Database Server
Due: 12/3/1999

For this assignment, you are to create a client/server socket program that will allow multiple clients to
simultaneously connect to a simple database. When a client wishes to use the database, it will first make a
connection request to the server. The server will then fork a child to communication with the client across the
client's socket. The server child will perform the actual reading and writing to the file database. The file
database must make use of read and write locks on individual records to allow/disallow simultaneous accesses
to the database.

The server process must have the name of dbs, and the client process must have the name of dbc. The client
process must take on optional command line argument. If no argument is given, it will assume the current
machine contains the server socket. If an argument is given, it will assume that the argument is the name of
the machine that contains the server socket.

The database will have ten records, numbered from 0 to 9. Each record will have two fields: a name field (of
size 30 characters) and a phone number field (of size 20 characters). When accesses either field in the record,
the entire record must be locked.

To use the database, the client will send a command across the client socket. The server child will retrieve this
command and attempt to perform the desired action. If the action is successful, the server child will send
either the resulting information across the client socket or a confirmation that the command was successfully
performed to the client. If the action is unsuccessful, the server child will send a message stating the action
was unable to be performed. The reasons for the unsuccessful action is because of conflicting locks set on the
desired record(s). Your program is not to block the process, but simply to report the unsuccessful action.

The interactive commands that the client process must access are:

s <pos> <name>

Set the name field to <name> at record <pos>. The <name> will start with the first non-white
space character after <pos> and continue until the end of the line. If there are no non-white
space characters after <pos>, the name is to be set to the null string. If there are more than 30
characters after the first non-white space character, truncate the name to the first 30 characters.
This command must a write lock at record <pos> prior to the writing of the field. It must
unlock the record after the writing of the field. If the write lock cannot be set, report an
unsuccessful access to the record.

f <pos> <phone number>

Set the phone number field to <phone number> at record <pos>. The <phone number> will
start with the first non-white space character after <pos> and continue until the end of the line.
If there are no non-white space characters after <pos>, the phone number is to be set to the null
string. If there are more than 20 characters after the first non-white space character, truncate
the phone number to the first 20 characters. This command must a write lock at record <pos>
prior to the writing of the field. It must unlock the record after the writing of the field. If the
write lock cannot be set, report an unsuccessful access to the record.
n <pos>

Retrieve the name field from record <pos>. This command must set a read lock at record
<pos> prior to the access and unlock the record after the access. If the read lock cannot be set,
report an unsuccessful access to the record.

p <pos>

Retrieve the phone number field from record <pos>. This command must set a read lock at
record <pos> prior to the access and unlock the record after the access. If the read lock cannot
be set, report an unsuccessful access to the record.

b <pos>

Retrieve both the name and phone number fields from record <pos>. This command must set a
read lock at record <pos> prior to the access and unlock the record after the access. If the read
lock cannot be set, report an unsuccessful access to the record.

Retrieve both the name and phone number fields from all records. This command must set a
read lock at each record prior to the access of that record and unlock the record after the
access. If the read lock cannot be set for a particular record, report an unsuccessful access to
that record and continue with the access to the other rocords.

r <pos>

Set a read lock at record <pos>. If the read lock cannot be set, report an unsuccessful access to
the record.

w <pos>

Set a write lock at record <pos>. If the write lock cannot be set, report an unsuccessful access
to the record.

u <pos>

Unlock the record <pos>.

List all of the commands for this program with a short description on how to use each one.

Have the client process exit. This will cause the server child process to exit as well. The server
process must properly clean up (i.e. wait() ) after the termination of its child (i.e. catch the
SIGCHILD signal).
Once started, the server will remain running until it is killed by the SIGKILL signal. For 10 points extra credit,
allow for graceful shutdown of the server. When the server receives a SIGTERM signal, before it shuts down
it must:

1. Close the server socket.

2. Send a signal to all server children, stating that they must terminate. As each server child terminates it
must send some message to its client that the server is no longer available and will not be accepting
any more database requests.

3. Wait for all server children to terminate before the server terminates.

For additional extra credit the assignment may be turned in early. If the assignment is turned in by Wednesday
11/24/99, you will get 20 points extra credit. If the assignment is turned in by Saturday 11/27/99, you will get
15 points extra credit (note, if the system goes down over the Thanksgiving Break there will be no extensions
given to allow for this situation). If the assignment is turned in by Monday 11/29/99, you will get 10 points
extra credit. If the assignment is turned in by Wednesday 12/1/99, you will get 5 points extra credit.

Your program is to be written in good programming style and should attempt to take advantage of C++ classes
and the C++ Standard Template Library. All programs must be turned in electronically using the UNIX turnin
command with the project name of mp4.

Common questions

Powered by AI

The alarm mechanism and file locking in UNIX both serve to manage resources, ensuring ordered execution and resource allocation, but they differ in application and detail. The 'alarm' system call schedules future signal delivery (SIGALRM), providing temporal resource management, such as ensuring process actions occur within precise timeframes, crucial for tasks like periodic synchronization or timeout handling . It provides a temporal resource check, influencing program flow but not directly managing shared resources like files . File locking, in contrast, deals with direct access control over file resources, preventing simultaneous write or read/write operations, thus maintaining data integrity . It uses read and write locks (recorded via system calls) to manage access, critical for multi-client database environments, allowing parallel execution without data collisions . Both mechanisms synchronize processes—alarm via temporal cues, file locking via spatial resource control—yet function distinctly, each essential to resource management across separate operational contexts.

UNIX systems utilize Process IDs (PIDs) and Session IDs (SIDs) to manage process grouping and relationships hierarchically. A PID uniquely identifies each process, facilitating targeting for signal delivery and process management functions like ‘kill’ for forceful termination or signal dispatch . Session IDs further group processes in the same terminal session, allowing a collective management of signals such as terminating all session processes on user logout . Processes manifest a hierarchy wherein parent-child relationships defined by PIDs ensure structural organization and process life cycle synchronization . Implications for signal delivery are significant: session and process groups enable broad signal delivery—like sending kill(-1) for managing all processes under the calling session by a superuser, showcasing hierarchical importance in controlled execution and process dependencies . Such structures encourage organized control across varied process states, allowing granular control over peripheral and central actions within a multi-user environment.

Signals in UNIX play a critical role in process control by allowing processes to respond to asynchronous events or conditions, such as exceptions or user inputs. They can be sent by both the operating system and other processes, intruding into the normal execution flow of a process to perform specific actions . The system can handle signals by taking default actions like terminating a process or invoking a signal handler provided by the user, which is a procedure programmed to address the signal . Users can customize signal handling by defining routines that are executed when signals are received, or they can choose to ignore certain signals, effectively nullifying their impact, except those like SIGKILL and SIGSTOP which cannot be ignored . Signal handling also introduces mechanisms for temporarily blocking signals, ensuring that handlers are not interrupted unnecessarily, allowing precise control over the execution flow .

Signal handling and process interruption methods in UNIX address significant challenges in managing processes, particularly those involving non-pre-emptible operations. Signals act as asynchronous notifications allowing processes to react to system-level or user-initiated events. This is vital when processes execute in kernel mode—non-pre-emptible state—where direct interruption isn’t feasible. Here, process management must ensure minimal disruption while scheduling signal handling effectively upon a return to user mode . Furthermore, blocking and managing signal queues within the kernel preemptively prevent chain reactions from signals that might result in crashes or deadlocks, thus maintaining system stability and performance . Processes use signals for handling exceptions and performing essential clean-ups before termination, directing resources appropriately without leaving residual states, such as zombi processes . Through strategic scheduling decoupled from the main execution flow, UNIX effectively navigates multi-processing challenges with robustness and efficiency.

In UNIX, processes can communicate through signals, with conditions ensuring process security and integrity. The communication is initiated using signals when one process needs to alert another to an event, such as a requested termination or a specific condition. However, this type of communication has restrictions: the sending and receiving processes must have the same real or effective user ID, or the sender must be the superuser. This limitation is implemented to prevent unauthorized process interference . The function 'kill(pid_t pid, int signal_number)' is used to send a specific signal to a process, identified by its process ID ('pid'), and returns 0 on success or -1 on failure, adhering to the signaling conventions .

In UNIX, the real user ID (rUID) identifies the actual owner of a process, while the effective user ID (eUID) determines the permissions associated with the process during execution. The rUID remains constant and is usually inherited from the parent process, reflecting the user's login details . Conversely, the eUID can change, commonly set via 'setuid' or 'seteuid' calls, allowing a process to gain privileges necessary for performing tasks requiring greater permissions than those associated with the rUID . This dual-ID system lets processes temporarily elevate privileges for sensitive operations while maintaining overall system security by retaining core user identity boundaries . Changes to eUID permit tasks like accessing protected files or executing restricted commands, controlled by process effective group ID (eGID) for group-based permissions .

The 'alarm' system call in UNIX is used to schedule SIGALRM signals to a process after a specified time interval, expressed in seconds. This call allows processes to perform periodic tasks like clock synchronization or checking application states . The programmed `alarm(unsigned int time_interval)` sets up a timer; upon its expiration, UNIX will send a SIGALRM signal to the process, usable for various periodic actions such as triggering regular file writes to physical storage or system notifications . If the timer is set to 0, any pending alarms are disabled, showing the flexibility of resource scheduling and meticulous time management within process operations . This mechanism facilitates both timeouts and routines needed for maintaining system efficiency and order.

UNIX provides various mechanisms for managing process termination, the primary being the 'exit' and '_exit' system calls. The '_exit' call terminates a process, releasing user memory and closing open file descriptors, but retains the process table entry of the terminated process, creating a 'zombie' state until the parent process acknowledges the termination using 'wait' or 'waitpid' . The zombie state ensures resource reclamation doesn't occur until the parent retrieves the child's exit status, aiding synchronization . 'Wait' functions allow the parent to clean up these entries by obtaining termination status, effectively freeing up all child process resources . Additionally, UNIX systems can assign orphaned zombie processes to an 'init' process, which subsequently concludes the wait operation, ensuring that no dangling processes remain .

In UNIX, the creation of a new process is primarily performed using the 'fork()' system call. When a process calls fork, a new process, called the child, is created as an almost exact duplicate of the parent process. Key distinctions post-fork include the child process having a unique process ID (PID), while maintaining a parent process ID (PPID) that matches the parent's PID . Immediately after the fork, the child process has a reset alarm clock time and no pending signals, ensuring it begins its lifecycle without overhead from the parent . Both parent and child processes continue execution at the point of the fork call, but 'fork()' returns 0 to the child process and the child's PID to the parent, allowing them to distinguish their roles post-fork .

Signal handling in UNIX involves distinct roles for kernel and user modes. Signals are posted by the kernel when a process operates in kernel mode, a critical phase where the process is non-pre-emptible and must complete current operations before yielding control, either by exiting the kernel or sleeping . While the process executes in user mode, it is pre-emptible, allowing signals to trigger appropriate handlers defined by the user or invoke default actions . The management by the kernel includes maintaining lists in the process table that track pending signals, and signals that are blocked or allowed while executing handlers, ensuring managed transitions between different process states and modes . Kernel mode allows signals to be effectively added to a queue, where they await appropriate execution without disrupting the system integrity .

You might also like