The facilities available to a UNIX user process are logically divided into two parts: kernel facilities directly implemented by UNIX code running in the operating system, and system facilities implemented either by the system, or in cooperation with a server process. These kernel facilities are described in this section 1.
The facilities implemented in the kernel are those which define the UNIX virtual machine in which each process runs. Like many real machines, this virtual machine has memory management hardware, an interrupt facility, timers and counters. The UNIX virtual machine also allows access to files and other objects through a set of descriptors. Each descriptor resembles a device controller, and supports a set of operations. Like devices on real machines, some of which are internal to the machine and some of which are external, parts of the descriptor machinery are built-in to the operating system, while other parts are often implemented in server processes on other machines. The facilities provided through the descriptor machinery are described in section 2.
Each UNIX host has associated with it a 32-bit host id, and a host name of up to 64 characters (as defined by MAXHOSTNAMELEN in <sys/param.h>). These are set (by a privileged user) and returned by the calls:
sethostid(hostid) long hostid; hostid = gethostid(); result long hostid; sethostname(name, len) char *name; int len; len = gethostname(buf, buflen) result int len; result char *buf; int buflen;
Each process in a host is named by an integer called the process id. This number is in the range 1-30000 and is returned by the getpid routine:
pid = getpid(); result int pid;
A new process is created by making a logical duplicate of an existing process:
pid = fork(); result int pid;
A process may terminate by executing an exit call:
exit(status) int status;
When a child process exits or terminates abnormally, the parent process receives information about any event which caused termination of the child process. A second call provides a non-blocking interface and may also be used to retrieve information about resources consumed by the process during its lifetime.
#include <sys/wait.h> pid = wait(astatus); result int pid; result union wait *astatus; pid = wait3(astatus, options, arusage); result int pid; result union waitstatus *astatus; int options; result struct rusage *arusage;
A process can overlay itself with the memory image of another process, passing the newly created process a set of parameters, using the call:
execve(name, argv, envp) char *name, **argv, **envp;
Each process in the system has associated with it two user-id's: a real user id and a effective user id, both 16 bit unsigned integers (type uid_t). Each process has an real accounting group id and an effective accounting group id and a set of access group id's. The group id's are 16 bit unsigned integers (type gid_t). Each process may be in several different access groups, with the maximum concurrent number of access groups a system compilation parameter, the constant NGROUPS in the file <sys/param.h>, guaranteed to be at least 8.
The real and effective user ids associated with a process are returned by:
ruid = getuid(); result uid_t ruid; euid = geteuid(); result uid_t euid;
rgid = getgid(); result gid_t rgid; egid = getegid(); result gid_t egid;
ngroups = getgroups(gidsetsize, gidset); result int ngroups; int gidsetsize; result int gidset[gidsetsize];
The user and group id's are assigned at login time using the setreuid, setregid, and setgroups calls:
setreuid(ruid, euid); int ruid, euid; setregid(rgid, egid); int rgid, egid; setgroups(gidsetsize, gidset) int gidsetsize; int gidset[gidsetsize];
Each process in the system is also normally associated with a process group. The group of processes in a process group is sometimes referred to as a job and manipulated by high-level system software (such as the shell). The current process group of a process is returned by the getpgrp call:
pgrp = getpgrp(pid); result int pgrp; int pid;
The process group associated with a process may be changed by the setpgrp call:
setpgrp(pid, pgrp); int pid, pgrp;
Each process begins execution with three logical areas of memory called text, data and stack. The text area is read-only and shared, while the data and stack areas are private to the process. Both the data and stack areas may be extended and contracted on program request. The call
addr = sbrk(incr); result caddr_t addr; int incr;
addr = sstk(incr); result caddr_t addr; int incr;
The system supports sharing of data between processes by allowing pages to be mapped into memory. These mapped pages may be shared with other processes or private to the process. Protection and sharing options are defined in <sys/mman.h> as:
/* protections are chosen from these bits, or-ed together */ #define PROT_READ 0x04 /* pages can be read */ #define PROT_WRITE 0x02 /* pages can be written */ #define PROT_EXEC 0x01 /* pages can be executed */
/* flags contain mapping type, sharing type and options */ /* mapping type; choose one */ #define MAP_FILE 0x0001 /* mapped from a file or device */ #define MAP_ANON 0x0002 /* allocated from memory, swap space */ #define MAP_TYPE 0x000f /* mask for type field */
/* sharing types; choose one */ #define MAP_SHARED 0x0010 /* share changes */ #define MAP_PRIVATE 0x0000 /* changes are private */
/* other flags */ #define MAP_FIXED 0x0020 /* map addr must be exactly as requested */ #define MAP_INHERIT 0x0040 /* region is retained after exec */ #define MAP_HASSEMAPHORE 0x0080 /* region may contain semaphores */ #define MAP_NOPREALLOC 0x0100 /* do not preallocate space */
pagesize = getpagesize(); result int pagesize;
The call:
maddr = mmap(addr, len, prot, flags, fd, pos); result caddr_t maddr; caddr_t addr; int *len, prot, flags, fd; off_t pos;
A facility is provided to synchronize a mapped region with the file it maps; the call
msync(addr, len); caddr_t addr; int len;
A mapping can be removed by the call
munmap(addr, len); caddr_t addr; int len;
A process can control the protection of pages using the call
mprotect(addr, len, prot); caddr_t addr; int len, prot;
A process that has knowledge of its memory behavior may use the madvise call:
madvise(addr, len, behav); caddr_t addr; int len, behav;
#define MADV_NORMAL 0 /* no further special treatment */ #define MADV_RANDOM 1 /* expect random page references */ #define MADV_SEQUENTIAL 2 /* expect sequential references */ #define MADV_WILLNEED 3 /* will need these pages */ #define MADV_DONTNEED 4 /* don't need these pages */ #define MADV_SPACEAVAIL 5 /* insure that resources are reserved */
mincore(addr, len, vec) caddr_t addr; int len; result char *vec;
Primitives are provided for synchronization using semaphores in shared memory. Semaphores must lie within a MAP_SHARED region with at least modes PROT_READ and PROT_WRITE. The MAP_HASSEMAPHORE flag must have been specified when the region was created. To acquire a lock a process calls:
value = mset(sem, wait) result int value; semaphore *sem; int wait;
To release a lock a process calls:
mclear(sem) semaphore *sem;
Two routines provide services analogous to the kernel sleep and wakeup functions interpreted in the domain of shared memory. A process may relinquish the processor by calling msleep with a set semaphore:
msleep(sem) semaphore *sem;
mwakeup(sem) semaphore *sem;
The system defines a set of signals that may be delivered to a process. Signal delivery resembles the occurrence of a hardware interrupt: the signal is blocked from further occurrence, the current process context is saved, and a new one is built. A process may specify the handler to which a signal is delivered, or specify that the signal is to be blocked or ignored. A process may also specify that a default action is to be taken when signals occur.
Some signals will cause a process to exit when they are not caught. This may be accompanied by creation of a core image file, containing the current memory image of the process for use in post-mortem debugging. A process may choose to have signals delivered on a special stack, so that sophisticated software stack manipulations are possible.
All signals have the same priority. If multiple signals are pending simultaneously, the order in which they are delivered to a process is implementation specific. Signal routines execute with the signal that caused their invocation blocked, but other signals may yet occur. Mechanisms are provided whereby critical sections of code may protect themselves against the occurrence of specified signals.
The signals defined by the system fall into one of five classes: hardware conditions, software conditions, input/output notification, process control, or resource control. The set of signals is defined in the file <signal.h>.
Hardware signals are derived from exceptional conditions which may occur during execution. Such signals include SIGFPE representing floating point and other arithmetic exceptions, SIGILL for illegal instruction execution, SIGSEGV for addresses outside the currently assigned area of memory, and SIGBUS for accesses that violate memory protection constraints. Other, more cpu-specific hardware signals exist, such as those for the various customer-reserved instructions on the VAX (SIGIOT, SIGEMT, and SIGTRAP).
Software signals reflect interrupts generated by user request: SIGINT for the normal interrupt signal; SIGQUIT for the more powerful quit signal, that normally causes a core image to be generated; SIGHUP and SIGTERM that cause graceful process termination, either because a user has ``hung up'', or by user or program request; and SIGKILL, a more powerful termination signal which a process cannot catch or ignore. Programs may define their own asynchronous events using SIGUSR1 and SIGUSR2. Other software signals (SIGALRM, SIGVTALRM, SIGPROF) indicate the expiration of interval timers.
A process can request notification via a SIGIO signal when input or output is possible on a descriptor, or when a non-blocking operation completes. A process may request to receive a SIGURG signal when an urgent condition arises.
A process may be stopped by a signal sent to it or the members of its process group. The SIGSTOP signal is a powerful stop signal, because it cannot be caught. Other stop signals SIGTSTP, SIGTTIN, and SIGTTOU are used when a user request, input request, or output request respectively is the reason for stopping the process. A SIGCONT signal is sent to a process when it is continued from a stopped state. Processes may receive notification with a SIGCHLD signal when a child process changes state, either by stopping or by terminating.
Exceeding resource limits may cause signals to be generated. SIGXCPU occurs when a process nears its CPU time limit and SIGXFSZ warns that the limit on file size creation has been reached.
A process has a handler associated with each signal. The handler controls the way the signal is delivered. The call
#include <signal.h> struct sigvec { int (*sv_handler)(); int sv_mask; int sv_flags; }; sigvec(signo, sv, osv) int signo; struct sigvec *sv; result struct sigvec *osv;
When a signal condition arises for a process, the signal is added to a set of signals pending for the process. If the signal is not currently blocked by the process then it will be delivered. The process of signal delivery adds the signal to be delivered and those signals specified in the associated signal handler's sv_mask to a set of those masked for the process, saves the current process context, and places the process in the context of the signal handling routine. The call is arranged so that if the signal handling routine exits normally the signal mask will be restored and the process will resume execution in the original context. If the process wishes to resume in a different context, then it must arrange to restore the signal mask itself.
The mask of blocked signals is independent of handlers for signals. It delays signals from being delivered much as a raised hardware interrupt priority level delays hardware interrupts. Preventing an interrupt from occurring by changing the handler is analogous to disabling a device from further interrupts.
The signal handling routine sv_handler is called by a C call of the form
(*sv_handler)(signo, code, scp); int signo; long code; struct sigcontext *scp;
A process can send a signal to another process or group of processes with the calls:
kill(pid, signo) int pid, signo; killpgrp(pgrp, signo) int pgrp, signo;
Signals are also sent implicitly from a terminal device to the process group associated with the terminal when certain input characters are typed.
To block a section of code against one or more signals, a sigblock call may be used to add a set of signals to the existing mask, returning the old mask:
oldmask = sigblock(mask); result long oldmask; long mask;
oldmask = sigsetmask(mask); result long oldmask; long mask;
It is possible to check conditions with some signals blocked, and then to pause waiting for a signal and restoring the mask, by using:
sigpause(mask); long mask;
Applications that maintain complex or fixed size stacks can use the call
struct sigstack { caddr_t ss_sp; int ss_onstack; }; sigstack(ss, oss) struct sigstack *ss; result struct sigstack *oss;
When a signal is to be delivered, the system checks whether the process is on a signal stack. If not, then the process is switched to the signal stack for delivery, with the return from the signal arranged to restore the previous stack.
If the process wishes to take a non-local exit from the signal routine, or run code from the signal stack that uses a different stack, a sigstack call should be used to reset the signal stack.
The system's notion of the current Greenwich time and the current time zone is set and returned by the call by the calls:
#include <sys/time.h> settimeofday(tvp, tzp); struct timeval *tp; struct timezone *tzp; gettimeofday(tp, tzp); result struct timeval *tp; result struct timezone *tzp;
struct timeval { long tv_sec; /* seconds since Jan 1, 1970 */ long tv_usec; /* and microseconds */ }; struct timezone { int tz_minuteswest; /* of Greenwich */ int tz_dsttime; /* type of dst correction to apply */ };
time(tvsec) result long *tvsec;
The system provides each process with three interval timers, defined in <sys/time.h>:
#define ITIMER_REAL 0 /* real time intervals */ #define ITIMER_VIRTUAL 1 /* virtual time intervals */ #define ITIMER_PROF 2 /* user and system virtual time */
The ITIMER_VIRTUAL timer decrements in process virtual time. It runs only when the process is executing. A SIGVTALRM signal is delivered when it expires.
The ITIMER_PROF timer decrements both in process virtual time and when the system is running on behalf of the process. It is designed to be used by processes to statistically profile their execution. A SIGPROF signal is delivered when it expires.
A timer value is defined by the itimerval structure:
struct itimerval { struct timeval it_interval; /* timer interval */ struct timeval it_value; /* current value */ };
getitimer(which, value); int which; result struct itimerval *value; setitimer(which, value, ovalue); int which; struct itimerval *value; result struct itimerval *ovalue;
The system rounds argument timer intervals to be not less than the resolution of its clock. This clock resolution can be determined by loading a very small value into a timer and reading the timer back to see what value resulted.
The alarm system call of earlier versions of UNIX is provided as a library routine using the ITIMER_REAL timer. The process profiling facilities of earlier versions of UNIX remain because it is not always possible to guarantee the automatic restart of system calls after receipt of a signal. The profil call arranges for the kernel to begin gathering execution statistics for a process:
profil(buf, bufsize, offset, scale); result char *buf; int bufsize, offset, scale;
Each process has access to resources through descriptors. Each descriptor is a handle allowing the process to reference objects such as files, devices and communications links.
Rather than allowing processes direct access to descriptors, the system introduces a level of indirection, so that descriptors may be shared between processes. Each process has a descriptor reference table, containing pointers to the actual descriptors. The descriptors themselves thus have multiple references, and are reference counted by the system.
Each process has a fixed size descriptor reference table, where the size is returned by the getdtablesize call:
nds = getdtablesize(); result int nds;
Each descriptor has a logical set of properties maintained by the system and defined by its type. Each type supports a set of operations; some operations, such as reading and writing, are common to several abstractions, while others are unique. The generic operations applying to many of these types are described in section 2.1. Naming contexts, files and directories are described in section 2.2. Section 2.3 describes communications domains and sockets. Terminals and (structured and unstructured) devices are described in section 2.4.
A duplicate of a descriptor reference may be made by doing
new = dup(old); result int new; int old;
dup2(old, new); int old, new;
close(old); int old;
The system provides a standard way to do synchronous and asynchronous multiplexing of operations.
Synchronous multiplexing is performed by using the select call to examine the state of multiple descriptors simultaneously, and to wait for state changes on those descriptors. Sets of descriptors of interest are specified as bit masks, as follows:
#include <sys/types.h> nds = select(nd, in, out, except, tvp); result int nds; int nd; result fd_set *in, *out, *except; struct timeval *tvp; FD_ZERO(&fdset); FD_SET(fd, &fdset); FD_CLR(fd, &fdset); FD_ISSET(fd, &fdset); int fs; fs_set fdset;
If none of the specified conditions is true, the operation waits for one of the conditions to arise, blocking at most the amount of time specified by tvp. If tvp is given as 0, the select waits indefinitely.
Options affecting I/O on a descriptor may be read and set by the call:
dopt = fcntl(d, cmd, arg) result int dopt; int d, cmd, arg; /* interesting values for cmd */ #define F_SETFL 3 /* set descriptor options */ #define F_GETFL 4 /* get descriptor options */ #define F_SETOWN 5 /* set descriptor owner (pid/pgrp) */ #define F_GETOWN 6 /* get descriptor owner (pid/pgrp) */
Operations on non-blocking descriptors will either complete immediately, note an error EWOULDBLOCK, partially complete an input or output operation returning a partial count, or return an error EINPROGRESS noting that the requested operation is in progress. A descriptor which has signalling enabled will cause the specified process and/or process group be signaled, with a SIGIO for input, output, or in-progress operation complete, or a SIGURG for exceptional conditions.
For example, when writing to a terminal using non-blocking output, the system will accept only as much data as there is buffer space for and return; when making a connection on a socket, the operation may return indicating that the connection establishment is ``in progress''. The select facility can be used to determine when further output is possible on the terminal, or when the connection establishment attempt is complete.
A user process may build descriptors of a specified type by wrapping a communications channel with a system supplied protocol translator:
new = wrap(old, proto) result int new; int old; struct dprop *proto;
Protocols may be based on communications multiplexing or a rights-passing style of handling multiple requests made on the same object. For instance, a protocol for implementing a file abstraction may or may not include locally generated ``read-ahead'' requests. A protocol that provides for read-ahead may provide higher performance but have a more difficult implementation.
Another example is the terminal driving facilities. Normally a terminal is associated with a communications line, and the terminal type and standard terminal access protocol are wrapped around a synchronous communications line and given to the user. If a virtual terminal is required, the terminal driver can be wrapped around a communications link, the other end of which is held by a virtual terminal protocol interpreter.
The system gives CPU scheduling priority to processes that have not used CPU time recently. This tends to favor interactive processes and processes that execute only for short periods. It is possible to determine the priority currently assigned to a process, process group, or the processes of a specified user, or to alter this priority using the calls:
#define PRIO_PROCESS 0 /* process */ #define PRIO_PGRP 1 /* process group */ #define PRIO_USER 2 /* user id */ prio = getpriority(which, who); result int prio; int which, who; setpriority(which, who, prio); int which, who, prio;
The resources used by a process are returned by a getrusage call, returning information in a structure defined in <sys/resource.h>:
#define RUSAGE_SELF 0 /* usage by this process */ #define RUSAGE_CHILDREN -1 /* usage by all children */ getrusage(who, rusage) int who; result struct rusage *rusage; struct rusage { struct timeval ru_utime; /* user time used */ struct timeval ru_stime; /* system time used */ int ru_maxrss; /* maximum core resident set size: kbytes */ int ru_ixrss; /* integral shared memory size (kbytes*sec) */ int ru_idrss; /* unshared data memory size */ int ru_isrss; /* unshared stack memory size */ int ru_minflt; /* page-reclaims */ int ru_majflt; /* page faults */ int ru_nswap; /* swaps */ int ru_inblock; /* block input operations */ int ru_oublock; /* block output operations */ int ru_msgsnd; /* messages sent */ int ru_msgrcv; /* messages received */ int ru_nsignals; /* signals received */ int ru_nvcsw; /* voluntary context switches */ int ru_nivcsw; /* involuntary context switches */ };
The resources of a process for which limits are controlled by the kernel are defined in <sys/resource.h>, and controlled by the getrlimit and setrlimit calls:
#define RLIMIT_CPU 0 /* cpu time in milliseconds */ #define RLIMIT_FSIZE 1 /* maximum file size */ #define RLIMIT_DATA 2 /* maximum data segment size */ #define RLIMIT_STACK 3 /* maximum stack segment size */ #define RLIMIT_CORE 4 /* maximum core file size */ #define RLIMIT_RSS 5 /* maximum resident set size */ #define RLIM_NLIMITS 6 #define RLIM_INFINITY 0x7fffffff struct rlimit { int rlim_cur; /* current (soft) limit */ int rlim_max; /* hard limit */ }; getrlimit(resource, rlp) int resource; result struct rlimit *rlp; setrlimit(resource, rlp) int resource; struct rlimit *rlp;
Only the super-user can raise the maximum limits. Other users may only alter rlim_cur within the range from 0 to rlim_max or (irreversibly) lower rlim_max.
Unless noted otherwise, the calls in this section are permitted only to a privileged user.
The call
mount(blkdev, dir, ronly); char *blkdev, *dir; int ronly;
The call
swapon(blkdev, size); char *blkdev; int size;
The call
unmount(dir); char *dir;
The call
sync();
The call
reboot(how) int how;
The system optionally keeps an accounting record in a file for each process that exits on the system. The format of this record is beyond the scope of this document. The accounting may be enabled to a file name by doing
acct(path); char *path;